提交首页

This commit is contained in:
2024-03-20 16:03:46 +08:00
parent 9db9c13bab
commit 61fa3dcc61
23 changed files with 503 additions and 536 deletions

View File

@ -11,6 +11,7 @@ export interface TicketCreateRecord {
auditorId: string; auditorId: string;
submit: boolean; submit: boolean;
userId: undefined; userId: undefined;
comment: string;
} }
export interface TicketRecord extends TicketCreateRecord { export interface TicketRecord extends TicketCreateRecord {
@ -18,12 +19,7 @@ export interface TicketRecord extends TicketCreateRecord {
id: undefined; id: undefined;
status: string status: string
} }
export interface auditRecord {
auditorId: string;
comment: string;
result: string;
ticketId: string;
}
// 票据列表 // 票据列表
export function queryTicket(data: any) { export function queryTicket(data: any) {
@ -62,20 +58,10 @@ export function uploadFile(file: any) {
// 获取附件信息 // 获取附件信息
export function attachment(id:string){ export function attachment(id:string){
return axios.get(`/api/rest/attachment/find/${id}`) return axios.get(`/api/rest/attachment/find/${id}`);
} }
// 审核 // 首页数据
export function audit(ticketId: string, params: auditRecord) { export function home(data: any){
return axios.patch(`/api/rest/bill/audit/${ticketId}`, params); return axios.get('/api/rest/bill/home',data);
} }
// 审核员管理的票据列表
export function auditTickctList(data: any){
return axios({
url: '/api/rest/bill/audit/list', // 路径
method: 'get',
params: data, // 参数
});
}

View File

@ -160,6 +160,8 @@ export function deptAudit(id: string,roleId:string){
}); });
} }
export function switchRole(roleId: number) { export function switchRole(roleId: number) {
return axios.patch<UserState>(`/api/user/self/switch-role/${roleId}`); return axios.patch<UserState>(`/api/user/self/switch-role/${roleId}`);
} }

View File

@ -154,22 +154,6 @@
<img alt="avatar" :src="avatar" /> <img alt="avatar" :src="avatar" />
</a-avatar> </a-avatar>
<template #content> <template #content>
<!-- <a-doption>-->
<!-- <a-space @click="switchRoles">-->
<!-- <icon-tag />-->
<!-- <span>-->
<!-- {{ $t('messageBox.switchRoles') }}-->
<!-- </span>-->
<!-- </a-space>-->
<!-- </a-doption>-->
<a-doption>
<a-space @click="$router.push({ name: 'Info' })">
<icon-user />
<span>
{{ $t('messageBox.userCenter') }}
</span>
</a-space>
</a-doption>
<a-doption> <a-doption>
<a-space @click="$router.push({ name: 'Setting' })"> <a-space @click="$router.push({ name: 'Setting' })">
<icon-settings /> <icon-settings />

View File

@ -5,11 +5,13 @@ import { intersection } from 'lodash';
function checkPermission(el: HTMLElement, binding: DirectiveBinding) { function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding; const { value } = binding;
const userStore = useUserStore(); const userStore = useUserStore();
const { permissions } = userStore; const { authorities } = userStore;
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (value.length > 0) { if (value.length > 0) {
const hasPermission = intersection(value, permissions).length > 0; const hasPermission = intersection(value, authorities).length > 0;
if (!hasPermission && el.parentNode) { if (!hasPermission && el.parentNode) {
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
} }

View File

@ -36,7 +36,7 @@ import localeSettings from './en-US/settings';
export default { export default {
'ticket.manage.system':'Ticket Manage System', 'ticket.manage.system':'Ticket Manage System',
'menu.dashboard': 'Dashboard', 'menu.dashboard': 'Home',
'menu.server.dashboard': 'Dashboard-Server', 'menu.server.dashboard': 'Dashboard-Server',
'menu.server.workplace': 'Workplace-Server', 'menu.server.workplace': 'Workplace-Server',
'menu.server.monitor': 'Monitor-Server', 'menu.server.monitor': 'Monitor-Server',

View File

@ -36,7 +36,7 @@ import localeSettings from './zh-CN/settings';
export default { export default {
'ticket.manage.system':'票据管理系统', 'ticket.manage.system':'票据管理系统',
'menu.dashboard': '仪表盘', 'menu.dashboard': '首页',
'menu.server.dashboard': '仪表盘-服务端', 'menu.server.dashboard': '仪表盘-服务端',
'menu.server.workplace': '工作台-服务端', 'menu.server.workplace': '工作台-服务端',
'menu.server.monitor': '实时监控-服务端', 'menu.server.monitor': '实时监控-服务端',

View File

@ -12,16 +12,16 @@ const USER: AppRouteRecordRaw = {
order: 7, order: 7,
}, },
children: [ children: [
{ // {
path: 'info', // path: 'info',
name: 'Info', // name: 'Info',
component: () => import('@/views/user/info/index.vue'), // component: () => import('@/views/user/info/index.vue'),
meta: { // meta: {
locale: 'menu.user.info', // locale: 'menu.user.info',
requiresAuth: true, // requiresAuth: true,
permissions: ['*'], // permissions: ['*'],
}, // },
}, // },
{ {
path: 'setting', path: 'setting',
name: 'Setting', name: 'Setting',

View File

@ -35,5 +35,4 @@ const VISUALIZATION: AppRouteRecordRaw = {
}, },
], ],
}; };
// export default VISUALIZATION; // export default VISUALIZATION;

View File

@ -2,7 +2,6 @@ import { defineStore } from 'pinia';
import { import {
TicketRecord, TicketRecord,
TicketCreateRecord, TicketCreateRecord,
auditRecord,
queryTicket, queryTicket,
remove, remove,
getDetail, getDetail,
@ -10,9 +9,7 @@ import {
update, update,
uploadFile, uploadFile,
attachment, attachment,
audit, home
auditTickctList,
} from '@/api/ticket'; } from '@/api/ticket';
import { ticketStore } from './type'; import { ticketStore } from './type';
@ -29,6 +26,7 @@ const useTicketStore = defineStore('ticket', {
contactEmail: undefined, contactEmail: undefined,
companyName: undefined, companyName: undefined,
userId: undefined, userId: undefined,
submit: undefined
}), }),
getters: { getters: {
@ -65,13 +63,9 @@ const useTicketStore = defineStore('ticket', {
return attachment(id); return attachment(id);
}, },
async auditTicket(ticketId: string, data: auditRecord) { async getHome(data: any){
return audit(ticketId, data); return home(data);
}, }
async auditTickctList(data:any){
return auditTickctList(data);
}
}, },
}); });

View File

@ -10,4 +10,5 @@ export interface ticketStore {
contactEmail?: string; contactEmail?: string;
companyName?: string; companyName?: string;
userId?: undefined; userId?: undefined;
submit?: undefined
} }

View File

@ -56,8 +56,9 @@ const useUserStore = defineStore('user', {
async info() { async info() {
const res = await getUserInfo(); const res = await getUserInfo();
res.data.user.permissions = res.data.permissions res.data.user.permissions = res.data.permissions;
this.setInfo(res.data.user); res.data.user.authorities = res.data.authorities;
this.setInfo(res.data.user);
}, },
// Get user's crsf // Get user's crsf

View File

@ -12,15 +12,14 @@
/> />
</a-avatar> </a-avatar>
<a-statistic <a-statistic
:title="$t('workplace.onlineContent')" :title="$t('workplace.pass')"
:value="373.5" :value="formData.pass"
:precision="1"
:value-from="0" :value-from="0"
animation animation
show-group-separator show-group-separator
> >
<template #suffix> <template #suffix>
W+ <span class="unit">{{ $t('workplace.pecs') }}</span> <span class="unit">{{ $t('workplace.pecs') }}</span>
</template> </template>
</a-statistic> </a-statistic>
</a-space> </a-space>
@ -37,8 +36,8 @@
/> />
</a-avatar> </a-avatar>
<a-statistic <a-statistic
:title="$t('workplace.putIn')" :title="$t('workplace.notPass')"
:value="368" :value="formData.notPass"
:value-from="0" :value-from="0"
animation animation
show-group-separator show-group-separator
@ -61,8 +60,8 @@
/> />
</a-avatar> </a-avatar>
<a-statistic <a-statistic
:title="$t('workplace.newDay')" :title="$t('workplace.notAudit')"
:value="8874" :value="formData.notAudit"
:value-from="0" :value-from="0"
animation animation
show-group-separator show-group-separator
@ -86,13 +85,16 @@
/> />
</a-avatar> </a-avatar>
<a-statistic <a-statistic
:title="$t('workplace.newFromYesterday')" v-model="formData.notFiled"
:value="2.8" :title="$t('workplace.notFiled')"
:precision="1" :value="formData.notFiled || 0"
:value-from="0" :value-from="0"
animation animation
show-group-separator
> >
<template #suffix> % <icon-caret-up class="up-icon" /> </template> <template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic> </a-statistic>
</a-space> </a-space>
</a-grid-item> </a-grid-item>
@ -102,30 +104,61 @@
</a-grid> </a-grid>
</template> </template>
<script lang="ts" setup></script>
<script lang="ts" setup>
import { useTicketStore, useUserStore } from '@/store';
import { ref } from 'vue';
const ticketStore = useTicketStore();
const userStore = useUserStore();
const formData = ref({
notAudit: undefined,
notFiled: undefined,
notPass: undefined,
pass: undefined,
});
const getHomeData = async (params: {
auditorId: number | undefined;
userId: number | string | undefined;
}) => {
if (userStore.permissions === 'admin') {
params.userId = '';
} else if (userStore.permissions === 'auditor') {
params.auditorId = userStore.id;
} else if (userStore.permissions === 'user') {
params.userId = userStore.id;
}
ticketStore.getHome(params).then((res) => {
formData.value = res.data;
});
};
getHomeData({});
</script>
<style lang="less" scoped> <style lang="less" scoped>
.arco-grid.panel { .arco-grid.panel {
margin-bottom: 0; margin-bottom: 0;
padding: 16px 20px 0 20px; padding: 16px 20px 0 20px;
} }
.panel-col { .panel-col {
padding-left: 43px; padding-left: 43px;
border-right: 1px solid rgb(var(--gray-2)); border-right: 1px solid rgb(var(--gray-2));
} }
.col-avatar { .col-avatar {
margin-right: 12px; margin-right: 12px;
background-color: var(--color-fill-2); background-color: var(--color-fill-2);
} }
.up-icon { .up-icon {
color: rgb(var(--red-6)); color: rgb(var(--red-6));
} }
.unit { .unit {
margin-left: 8px; margin-left: 8px;
color: rgb(var(--gray-8)); color: rgb(var(--gray-8));
font-size: 12px; font-size: 12px;
} }
:deep(.panel-border) { :deep(.panel-border) {
margin: 4px 0 0 0; margin: 4px 0 0 0;
} }
</style> </style>

View File

@ -1,148 +1,105 @@
<template> <template>
<div class="container"> <div class="container">
<div class="left-side"> <div class="left-side">
<div class="panel"> <div class="panel"> <Banner /> <DataPanel /></div>
<Banner />
<DataPanel />
<ContentChart />
</div>
<a-grid :cols="24" :col-gap="16" :row-gap="16" style="margin-top: 16px">
<a-grid-item
:span="{ xs: 24, sm: 24, md: 24, lg: 12, xl: 12, xxl: 12 }"
>
<PopularContent />
</a-grid-item>
<a-grid-item
:span="{ xs: 24, sm: 24, md: 24, lg: 12, xl: 12, xxl: 12 }"
>
<CategoriesPercent />
</a-grid-item>
</a-grid>
</div>
<div class="right-side">
<a-grid :cols="24" :row-gap="16">
<a-grid-item :span="24">
<div class="panel moduler-wrap">
<QuickOperation />
<RecentlyVisited />
</div>
</a-grid-item>
<a-grid-item class="panel" :span="24">
<Carousel />
</a-grid-item>
<a-grid-item class="panel" :span="24">
<Announcement />
</a-grid-item>
<a-grid-item class="panel" :span="24">
<Docs />
</a-grid-item>
</a-grid>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import Banner from './components/banner.vue'; import Banner from './components/banner.vue';
import DataPanel from './components/data-panel.vue'; import DataPanel from './components/data-panel.vue';
import ContentChart from './components/content-chart.vue';
import PopularContent from './components/popular-content.vue';
import CategoriesPercent from './components/categories-percent.vue';
import RecentlyVisited from './components/recently-visited.vue';
import QuickOperation from './components/quick-operation.vue';
import Announcement from './components/announcement.vue';
import Carousel from './components/carousel.vue';
import Docs from './components/docs.vue';
</script> </script>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'Dashboard', // If you want the include property of keep-alive to take effect, you must name the component name: 'Dashboard', // If you want the include property of keep-alive to take effect, you must name the component
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.container { .container {
background-color: var(--color-fill-2); background-color: var(--color-fill-2);
padding: 16px 20px; padding: 16px 20px;
padding-bottom: 0; padding-bottom: 0;
display: flex; display: flex;
}
.left-side {
flex: 1;
overflow: auto;
}
.right-side {
width: 280px;
margin-left: 16px;
}
.panel {
background-color: var(--color-bg-2);
border-radius: 4px;
overflow: auto;
}
:deep(.panel-border) {
margin-bottom: 0;
border-bottom: 1px solid rgb(var(--gray-2));
}
.moduler-wrap {
border-radius: 4px;
background-color: var(--color-bg-2);
:deep(.text) {
font-size: 12px;
text-align: center;
color: rgb(var(--gray-8));
} }
.left-side { :deep(.wrapper) {
flex: 1; margin-bottom: 8px;
overflow: auto; text-align: center;
} cursor: pointer;
.right-side { &:last-child {
width: 280px; .text {
margin-left: 16px; margin-bottom: 0;
}
.panel {
background-color: var(--color-bg-2);
border-radius: 4px;
overflow: auto;
}
:deep(.panel-border) {
margin-bottom: 0;
border-bottom: 1px solid rgb(var(--gray-2));
}
.moduler-wrap {
border-radius: 4px;
background-color: var(--color-bg-2);
:deep(.text) {
font-size: 12px;
text-align: center;
color: rgb(var(--gray-8));
}
:deep(.wrapper) {
margin-bottom: 8px;
text-align: center;
cursor: pointer;
&:last-child {
.text {
margin-bottom: 0;
}
}
&:hover {
.icon {
color: rgb(var(--arcoblue-6));
background-color: #e8f3ff;
}
.text {
color: rgb(var(--arcoblue-6));
}
} }
} }
&:hover {
:deep(.icon) { .icon {
display: inline-block; color: rgb(var(--arcoblue-6));
width: 32px; background-color: #e8f3ff;
height: 32px; }
margin-bottom: 4px; .text {
color: rgb(var(--dark-gray-1)); color: rgb(var(--arcoblue-6));
line-height: 32px; }
font-size: 16px;
text-align: center;
background-color: rgb(var(--gray-1));
border-radius: 4px;
} }
} }
:deep(.icon) {
display: inline-block;
width: 32px;
height: 32px;
margin-bottom: 4px;
color: rgb(var(--dark-gray-1));
line-height: 32px;
font-size: 16px;
text-align: center;
background-color: rgb(var(--gray-1));
border-radius: 4px;
}
}
</style> </style>
<style lang="less" scoped> <style lang="less" scoped>
// responsive // responsive
.mobile { .mobile {
.container { .container {
display: block; display: block;
}
.right-side {
// display: none;
width: 100%;
margin-left: 0;
margin-top: 16px;
}
} }
.right-side {
// display: none;
width: 100%;
margin-left: 0;
margin-top: 16px;
}
}
</style> </style>

View File

@ -35,4 +35,9 @@ export default {
'workplace.popularContent.video': 'video', 'workplace.popularContent.video': 'video',
'workplace.categoriesPercent': 'Categories Percent', 'workplace.categoriesPercent': 'Categories Percent',
'workplace.pecs': 'pecs', 'workplace.pecs': 'pecs',
'workplace.pass': 'pass',
'workplace.notPass': 'notPass',
'workplace.notAudit': 'notAudit',
'workplace.notFiled': 'notFiled',
}; };

View File

@ -34,4 +34,8 @@ export default {
'workplace.popularContent.video': '视频', 'workplace.popularContent.video': '视频',
'workplace.categoriesPercent': '内容类型占比', 'workplace.categoriesPercent': '内容类型占比',
'workplace.pecs': '个', 'workplace.pecs': '个',
'workplace.pass': '审核通过',
'workplace.notPass': '审核不通过',
'workplace.notAudit': '待审核',
'workplace.notFiled': '待提交',
}; };

View File

@ -208,7 +208,10 @@ const formData = ref<CreateRecord>({
}); });
const deptOptions = computedAsync(async () => { const deptOptions = computedAsync(async () => {
const { data } = await deptList(); const { data } = await deptList();
return data; const deptData = data.filter((item: any) => {
return item.enabled !== false;
});
return deptData;
}); });
const loginConfig = useStorage('login-config', { const loginConfig = useStorage('login-config', {
// 使 useStorage // 使 useStorage

View File

@ -115,14 +115,21 @@
/> />
</a-form-item> </a-form-item>
<a-form-item field="roleId" :label="$t('user.info.role')"> <a-form-item
<a-radio-group field="roleId"
v-for="i in roleOptions" :label="$t('user.info.role')"
:key="i.id" :rules="[{ required: true, message: t('user.info.dept.required') }]"
:validate-trigger="['change']"
>
<a-select
v-model="formData.roleId" v-model="formData.roleId"
> :options="roleOptions"
<a-radio :value="i.id">{{ i.name }}</a-radio> :field-names="{
</a-radio-group> value: 'id',
label: 'name',
}"
:placeholder="$t('user.info.role.placeholder')"
/>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
@ -137,7 +144,6 @@ import { FormInstance } from '@arco-design/web-vue/es/form';
import { queryRoleList } from '@/api/role'; import { queryRoleList } from '@/api/role';
import { deptList } from '@/api/dept'; import { deptList } from '@/api/dept';
import { computedAsync } from '@vueuse/core'; import { computedAsync } from '@vueuse/core';
import { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { useUserStore } from '@/store'; import { useUserStore } from '@/store';
@ -176,13 +182,19 @@ const userStore = useUserStore();
// //
const deptOptions = computedAsync(async () => { const deptOptions = computedAsync(async () => {
const { data } = await deptList(); const { data } = await deptList();
return data; const deptData = data.filter((item: any) => {
return item.enabled !== false;
});
return deptData;
}); });
// //
const roleOptions = computedAsync(async () => { const roleOptions = computedAsync(async () => {
const res = await queryRoleList(); const res = await queryRoleList();
return res.data; const roleData = res.data.filter((item: any) => {
return item.enabled !== false;
});
return roleData;
}); });
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);

View File

@ -62,6 +62,7 @@ export default {
'user.info.dept.placeholder':'Please select Dept', 'user.info.dept.placeholder':'Please select Dept',
'user.info.dept.required': 'Dept is required', 'user.info.dept.required': 'Dept is required',
'user.info.role':'Role', 'user.info.role':'Role',
'user.info.role.placeholder': 'Please select Role',
'user.info.role.required': 'Role is required', 'user.info.role.required': 'Role is required',
}; };

View File

@ -62,6 +62,7 @@ export default {
'user.info.dept.placeholder':'请选择部门', 'user.info.dept.placeholder':'请选择部门',
'user.info.dept.required': '部门不能为空', 'user.info.dept.required': '部门不能为空',
'user.info.role':'角色', 'user.info.role':'角色',
'user.info.role.placeholder': '请选择角色',
'user.info.role.required': '请选择一个', 'user.info.role.required': '请选择一个',
} }

View File

@ -1,5 +1,6 @@
<template> <template>
<a-button <a-button
v-permission="['BILL_QUERY']"
v-if="props.isCreate" v-if="props.isCreate"
type="primary" type="primary"
size="small" size="small"
@ -10,6 +11,7 @@
</a-button> </a-button>
<a-button <a-button
v-permission="['BILL_UPDATE']"
v-if="!props.isCreate" v-if="!props.isCreate"
size="small" size="small"
type="primary" type="primary"
@ -161,9 +163,9 @@
<a-form-item <a-form-item
field="comment" field="comment"
:label="$t('ticket.info.comment')" :label="$t('ticket.info.comment')"
v-if="auditData.comment" v-if="formData.comment"
> >
{{ auditData.comment }} {{ formData.comment }}
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button type="dashed" @click="handleCancel"> <a-button type="dashed" @click="handleCancel">
@ -193,7 +195,7 @@ import { FormInstance } from '@arco-design/web-vue/es/form';
import { SelectOptionData } from '@arco-design/web-vue/es/select/interface'; import { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { useTicketStore, useUserStore, useRoleStore } from '@/store'; import { useTicketStore, useUserStore, useRoleStore } from '@/store';
import { TicketRecord, auditRecord } from '@/api/ticket'; import { TicketRecord } from '@/api/ticket';
import { deptList } from '@/api/dept'; import { deptList } from '@/api/dept';
const props = defineProps({ const props = defineProps({
@ -228,12 +230,7 @@ const formData = ref<TicketRecord>({
submit: '', submit: '',
comment: '', comment: '',
}); });
const auditData = ref<auditRecord>({
auditorId: '',
comment: '',
result: '',
ticketId: '',
});
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);
const deptOptions = computedAsync(async () => { const deptOptions = computedAsync(async () => {
@ -336,10 +333,10 @@ const handleClick = () => {
ticketStore ticketStore
.getDetail(ticketId) .getDetail(ticketId)
.then(async (res) => { .then(async (res) => {
console.log('res', res);
// //
formData.value = res.data.bill; formData.value = res.data;
//
auditData.value = res.data.audit;
// //
if (formData.value.attachId) { if (formData.value.attachId) {
@ -349,12 +346,12 @@ const handleClick = () => {
} }
// //
const auditInfo = await userStore.getUserDetail( const auditInfo = await userStore.getUserDetail(
auditData.value.auditorId formData.value.auditorId
); );
formData.value.deptId = auditInfo.data.deptId; formData.value.deptId = auditInfo.data.deptId;
formData.value.auditorId = auditData.value.auditorId; // formData.value.auditorId = formData.value.auditorId;
formDifer = { ...res.data.bill }; formDifer = { ...res.data };
}) })
.then(() => { .then(() => {
optionDept(false).then(() => { optionDept(false).then(() => {

View File

@ -1,5 +1,6 @@
<template> <template>
<a-button <a-button
v-permission="['BILL_QUERY']"
v-if="props.isDetail" v-if="props.isDetail"
type="outline" type="outline"
size="small" size="small"
@ -10,6 +11,7 @@
</a-button> </a-button>
<a-button <a-button
v-permission="['BILL_AUDIT']"
v-if="!props.isDetail" v-if="!props.isDetail"
size="small" size="small"
type="primary" type="primary"
@ -112,23 +114,25 @@
</a-form-item> </a-form-item>
<a-form-item <a-form-item
v-else
field="status" field="status"
v-else
:label="$t('ticket.info.status')" :label="$t('ticket.info.status')"
:disabled="props.isDetail" :disabled="props.isDetail"
:validate-trigger="['change', 'input']" :validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('ticket.info.status.required') }]" :rules="[{ required: true, message: t('ticket.info.status.required') }]"
> >
<a-select <a-radio-group
v-for="i in statusOptions"
:key="i.value"
v-model="formData.status" v-model="formData.status"
:options="statusOptions" >
:placeholder="$t('ticket.info.status.placeholder')" <a-radio :value="i.value">{{ i.label }}</a-radio>
/> </a-radio-group>
</a-form-item> </a-form-item>
<a-form-item field="comment" :label="$t('ticket.info.comment')"> <a-form-item field="comment" :label="$t('ticket.info.comment')">
<a-textarea v-model="auditData.comment" v-if="!props.isDetail" /> <a-textarea v-model="formData.comment" v-if="!props.isDetail" />
<div v-else>{{ auditData.comment }} </div> <div v-else>{{ formData.comment }} </div>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
@ -174,14 +178,10 @@ const formData = ref<TicketRecord>({
companyName: '', companyName: '',
attachId: '', attachId: '',
auditorId: '', auditorId: '',
submit: '', submit: undefined,
});
const auditData = ref<auditRecord>({
auditorId: '',
comment: '', comment: '',
result: '',
ticketId: '',
}); });
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);
const deptOptions = computedAsync(async () => { const deptOptions = computedAsync(async () => {
const { data } = await deptList(); const { data } = await deptList();
@ -233,9 +233,9 @@ const handleClick = async () => {
// //
const res = await ticketStore.getDetail(ticketId); const res = await ticketStore.getDetail(ticketId);
// //
formData.value = res.data.bill; formData.value = res.data;
// //
if (!props.isDetail) { if (!props.isDetail) {
if (formData.value.status === '审核通过') { if (formData.value.status === '审核通过') {
formData.value.status = 'PASS'; formData.value.status = 'PASS';
@ -243,8 +243,6 @@ const handleClick = async () => {
formData.value.status = 'FAILED'; formData.value.status = 'FAILED';
} }
} }
//
auditData.value = res.data.audit;
// //
if (formData.value.attachId) { if (formData.value.attachId) {
@ -254,9 +252,8 @@ const handleClick = async () => {
} }
// //
const auditInfo = await userStore.getUserDetail(auditData.value.auditorId); const auditInfo = await userStore.getUserDetail(formData.value.auditorId);
formData.value.deptId = auditInfo.data.deptId; formData.value.deptId = auditInfo.data.deptId;
formData.value.auditorId = auditData.value.auditorId;
optionDept().then(() => { optionDept().then(() => {
setVisible(true); setVisible(true);
}); });
@ -271,11 +268,8 @@ const handleSubmit = async () => {
createEditRef.value?.resetFields(); createEditRef.value?.resetFields();
} else { } else {
// //
auditData.value.result = formData.value.status; const { type, ...auditDate } = formData.value;
const res = await ticketStore.auditTicket( const res = await ticketStore.updateTicket(auditDate);
formData.value.id,
auditData.value
);
if (res.status === 200) { if (res.status === 200) {
Message.success({ Message.success({
content: t('audit.sucess'), content: t('audit.sucess'),

View File

@ -92,7 +92,6 @@
:prem="ticketItem" :prem="ticketItem"
:is-create="true" :is-create="true"
@refresh="search" @refresh="search"
v-if="userStore.permissions !== 'auditor'"
/> />
</a-space> </a-space>
</a-col> </a-col>
@ -188,26 +187,15 @@
:prem="record" :prem="record"
:is-detail="true" :is-detail="true"
@refresh="search" @refresh="search"
v-if="
userStore.permissions === 'admin' ||
(userStore.permissions === 'user' &&
(record.status == '审核未通过' || record.status == '待提交'
? false
: true))
"
/> />
<!-- 修改 --> <!-- 修改 -->
<TicketForm <TicketForm
ref="createEditRef" ref="createEditRef"
:prem="record" :prem="record"
:is-create="false" :is-create="false"
@refresh="search" @refresh="search"
v-if=" v-if="record.status === '待提交' || record.status === '审核未通过'"
userStore.permissions == 'user' &&
(record.status == '审核未通过' || record.status == '待提交'
? true
: false)
"
/> />
<!-- 审核 --> <!-- 审核 -->
@ -216,16 +204,20 @@
:prem="record" :prem="record"
:is-detail="false" :is-detail="false"
@refresh="search" @refresh="search"
v-if="userStore.permissions === 'auditor'" v-if="record.status === '待审核'"
/> />
<a-popconfirm <a-popconfirm
:content="t('Confirm the deletion of this ticket')" :content="t('Confirm the deletion of this ticket')"
type="error" type="error"
@ok="handleDelete(record)" @ok="handleDelete(record)"
v-if="userStore.permissions === 'admin'"
> >
<a-button type="primary" size="small" status="danger"> <a-button
type="primary"
size="small"
status="danger"
v-permission="['BILL_DELETE']"
>
{{ $t('delete') }} {{ $t('delete') }}
</a-button> </a-button>
</a-popconfirm> </a-popconfirm>
@ -364,6 +356,7 @@ const onPageChange = (current: number) => {
// //
const fetchData = async (params: { const fetchData = async (params: {
auditorId: number | undefined;
userId: number | undefined; userId: number | undefined;
page: 1; page: 1;
current: 1; current: 1;
@ -373,11 +366,12 @@ const fetchData = async (params: {
try { try {
let res = {}; let res = {};
// // id
if (userStore.permissions === 'admin') { if (userStore.permissions === 'admin') {
res = await ticketStore.getTicketList(params); res = await ticketStore.getTicketList(params);
} else if (userStore.permissions === 'auditor') { } else if (userStore.permissions === 'auditor') {
res = await ticketStore.auditTickctList(params); params.auditorId = userStore.id;
res = await ticketStore.getTicketList(params);
} else if (userStore.permissions === 'user') { } else if (userStore.permissions === 'user') {
params.userId = userStore.id; params.userId = userStore.id;
res = await ticketStore.getTicketList(params); res = await ticketStore.getTicketList(params);

View File

@ -30,22 +30,24 @@
<Chart style="height: 328px; margin-top: 20px" :option="chartOption" /> <Chart style="height: 328px; margin-top: 20px" :option="chartOption" />
</a-card> </a-card>
</a-spin> </a-spin>
123456
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { LineSeriesOption } from 'echarts'; import { LineSeriesOption } from 'echarts';
import { queryDataOverview } from '@/api/visualization'; import { queryDataOverview } from '@/api/visualization';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { ToolTipFormatterParams } from '@/types/echarts'; import { ToolTipFormatterParams } from '@/types/echarts';
import useThemes from '@/hooks/themes'; import useThemes from '@/hooks/themes';
import useChartOption from '@/hooks/chart-option'; import useChartOption from '@/hooks/chart-option';
const tooltipItemsHtmlString = (items: ToolTipFormatterParams[]) => { const tooltipItemsHtmlString = (items: ToolTipFormatterParams[]) => {
return items return items
.map( .map(
(el) => `<div class="content-panel"> (el) => `<div class="content-panel">
<p> <p>
<span style="background-color: ${ <span style="background-color: ${
el.color el.color
@ -53,258 +55,253 @@
</p> </p>
<span class="tooltip-value">${el.value.toLocaleString()}</span> <span class="tooltip-value">${el.value.toLocaleString()}</span>
</div>` </div>`
) )
.reverse() .reverse()
.join(''); .join('');
}; };
const generateSeries = ( const generateSeries = (
name: string, name: string,
lineColor: string, lineColor: string,
itemBorderColor: string, itemBorderColor: string,
data: number[] data: number[]
): LineSeriesOption => { ): LineSeriesOption => {
return { return {
name, name,
data, data,
stack: 'Total', stack: 'Total',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'circle', symbol: 'circle',
symbolSize: 10, symbolSize: 10,
itemStyle: {
color: lineColor,
},
emphasis: {
focus: 'series',
itemStyle: { itemStyle: {
color: lineColor, color: lineColor,
borderWidth: 2,
borderColor: itemBorderColor,
}, },
emphasis: { },
focus: 'series', lineStyle: {
itemStyle: { width: 2,
color: lineColor, color: lineColor,
borderWidth: 2, },
borderColor: itemBorderColor, showSymbol: false,
}, areaStyle: {
}, opacity: 0.1,
lineStyle: { color: lineColor,
width: 2, },
color: lineColor,
},
showSymbol: false,
areaStyle: {
opacity: 0.1,
color: lineColor,
},
};
}; };
const { t } = useI18n(); };
const { loading, setLoading } = useLoading(true); const { t } = useI18n();
const { isDark } = useThemes(); const { loading, setLoading } = useLoading(true);
const renderData = computed(() => [ const { isDark } = useThemes();
{ const renderData = computed(() => [
title: t('multiDAnalysis.dataOverview.contentProduction'), {
value: 1902, title: t('multiDAnalysis.dataOverview.contentProduction'),
prefix: { value: 1902,
icon: 'icon-components', prefix: {
background: isDark.value ? '#593E2F' : '#FFE4BA', icon: 'icon-components',
iconColor: isDark.value ? '#F29A43' : '#F77234', background: isDark.value ? '#593E2F' : '#FFE4BA',
iconColor: isDark.value ? '#F29A43' : '#F77234',
},
},
{
title: t('multiDAnalysis.dataOverview.contentClick'),
value: 2445,
prefix: {
icon: 'icon-thumb-up',
background: isDark.value ? '#3D5A62' : '#E8FFFB',
iconColor: isDark.value ? '#6ED1CE' : '#33D1C9',
},
},
{
title: t('multiDAnalysis.dataOverview.contentExposure'),
value: 3034,
prefix: {
icon: 'icon-heart',
background: isDark.value ? '#354276' : '#E8F3FF',
iconColor: isDark.value ? '#4A7FF7' : '#165DFF',
},
},
{
title: t('multiDAnalysis.dataOverview.activeUsers'),
value: 1275,
prefix: {
icon: 'icon-user',
background: isDark.value ? '#3F385E' : '#F5E8FF',
iconColor: isDark.value ? '#8558D3' : '#722ED1',
},
},
]);
const xAxis = ref<string[]>([]);
const contentProductionData = ref<number[]>([]);
const contentClickData = ref<number[]>([]);
const contentExposureData = ref<number[]>([]);
const activeUsersData = ref<number[]>([]);
const { chartOption } = useChartOption((dark) => {
return {
grid: {
left: '2.6%',
right: '4',
top: '40',
bottom: '40',
},
xAxis: {
type: 'category',
offset: 2,
data: xAxis.value,
boundaryGap: false,
axisLabel: {
color: '#4E5969',
formatter(value: number, idx: number) {
if (idx === 0) return '';
if (idx === xAxis.value.length - 1) return '';
return `${value}`;
},
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: false,
},
axisPointer: {
show: true,
lineStyle: {
color: '#23ADFF',
width: 2,
},
}, },
}, },
{ yAxis: {
title: t('multiDAnalysis.dataOverview.contentClick'), type: 'value',
value: 2445, axisLine: {
prefix: { show: false,
icon: 'icon-thumb-up', },
background: isDark.value ? '#3D5A62' : '#E8FFFB', axisLabel: {
iconColor: isDark.value ? '#6ED1CE' : '#33D1C9', formatter(value: number, idx: number) {
if (idx === 0) return String(value);
return `${value / 1000}k`;
},
},
splitLine: {
lineStyle: {
color: dark ? '#2E2E30' : '#F2F3F5',
},
}, },
}, },
{ tooltip: {
title: t('multiDAnalysis.dataOverview.contentExposure'), trigger: 'axis',
value: 3034, formatter(params) {
prefix: { const [firstElement] = params as ToolTipFormatterParams[];
icon: 'icon-heart', return `<div>
background: isDark.value ? '#354276' : '#E8F3FF',
iconColor: isDark.value ? '#4A7FF7' : '#165DFF',
},
},
{
title: t('multiDAnalysis.dataOverview.activeUsers'),
value: 1275,
prefix: {
icon: 'icon-user',
background: isDark.value ? '#3F385E' : '#F5E8FF',
iconColor: isDark.value ? '#8558D3' : '#722ED1',
},
},
]);
const xAxis = ref<string[]>([]);
const contentProductionData = ref<number[]>([]);
const contentClickData = ref<number[]>([]);
const contentExposureData = ref<number[]>([]);
const activeUsersData = ref<number[]>([]);
const { chartOption } = useChartOption((dark) => {
return {
grid: {
left: '2.6%',
right: '4',
top: '40',
bottom: '40',
},
xAxis: {
type: 'category',
offset: 2,
data: xAxis.value,
boundaryGap: false,
axisLabel: {
color: '#4E5969',
formatter(value: number, idx: number) {
if (idx === 0) return '';
if (idx === xAxis.value.length - 1) return '';
return `${value}`;
},
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: false,
},
axisPointer: {
show: true,
lineStyle: {
color: '#23ADFF',
width: 2,
},
},
},
yAxis: {
type: 'value',
axisLine: {
show: false,
},
axisLabel: {
formatter(value: number, idx: number) {
if (idx === 0) return String(value);
return `${value / 1000}k`;
},
},
splitLine: {
lineStyle: {
color: dark ? '#2E2E30' : '#F2F3F5',
},
},
},
tooltip: {
trigger: 'axis',
formatter(params) {
const [firstElement] = params as ToolTipFormatterParams[];
return `<div>
<p class="tooltip-title">${firstElement.axisValueLabel}</p> <p class="tooltip-title">${firstElement.axisValueLabel}</p>
${tooltipItemsHtmlString(params as ToolTipFormatterParams[])} ${tooltipItemsHtmlString(params as ToolTipFormatterParams[])}
</div>`; </div>`;
},
className: 'echarts-tooltip-diy',
},
graphic: {
elements: [
{
type: 'text',
left: '2.6%',
bottom: '18',
style: {
text: '12.10',
textAlign: 'center',
fill: '#4E5969',
fontSize: 12,
},
}, },
className: 'echarts-tooltip-diy', {
}, type: 'text',
graphic: { right: '0',
elements: [ bottom: '18',
{ style: {
type: 'text', text: '12.17',
left: '2.6%', textAlign: 'center',
bottom: '18', fill: '#4E5969',
style: { fontSize: 12,
text: '12.10',
textAlign: 'center',
fill: '#4E5969',
fontSize: 12,
},
}, },
{ },
type: 'text',
right: '0',
bottom: '18',
style: {
text: '12.17',
textAlign: 'center',
fill: '#4E5969',
fontSize: 12,
},
},
],
},
series: [
generateSeries(
'内容生产量',
'#722ED1',
'#F5E8FF',
contentProductionData.value
),
generateSeries(
'内容点击量',
'#F77234',
'#FFE4BA',
contentClickData.value
),
generateSeries(
'内容曝光量',
'#33D1C9',
'#E8FFFB',
contentExposureData.value
),
generateSeries(
'活跃用户数',
'#3469FF',
'#E8F3FF',
activeUsersData.value
),
], ],
}; },
}); series: [
const fetchData = async () => { generateSeries(
setLoading(true); '内容生产量',
try { '#722ED1',
const { data } = await queryDataOverview(); '#F5E8FF',
xAxis.value = data.xAxis; contentProductionData.value
data.data.forEach((el) => { ),
if (el.name === '内容生产量') { generateSeries(
contentProductionData.value = el.value; '内容点击量',
} else if (el.name === '内容点击量') { '#F77234',
contentClickData.value = el.value; '#FFE4BA',
} else if (el.name === '内容曝光量') { contentClickData.value
contentExposureData.value = el.value; ),
} generateSeries(
activeUsersData.value = el.value; '内容曝光量',
}); '#33D1C9',
} catch (err) { '#E8FFFB',
// you can report use errorHandler or other contentExposureData.value
} finally { ),
setLoading(false); generateSeries('活跃用户数', '#3469FF', '#E8F3FF', activeUsersData.value),
} ],
}; };
fetchData(); });
const fetchData = async () => {
setLoading(true);
try {
const { data } = await queryDataOverview();
xAxis.value = data.xAxis;
data.data.forEach((el) => {
if (el.name === '内容生产量') {
contentProductionData.value = el.value;
} else if (el.name === '内容点击量') {
contentClickData.value = el.value;
} else if (el.name === '内容曝光量') {
contentExposureData.value = el.value;
}
activeUsersData.value = el.value;
});
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
fetchData();
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
:deep(.arco-statistic) { :deep(.arco-statistic) {
.arco-statistic-title { .arco-statistic-title {
color: rgb(var(--gray-10)); color: rgb(var(--gray-10));
font-weight: bold; font-weight: bold;
}
.arco-statistic-value {
display: flex;
align-items: center;
}
} }
.statistic-prefix { .arco-statistic-value {
display: inline-block; display: flex;
width: 32px; align-items: center;
height: 32px;
margin-right: 8px;
color: var(--color-white);
font-size: 16px;
line-height: 32px;
text-align: center;
vertical-align: middle;
border-radius: 6px;
} }
}
.statistic-prefix {
display: inline-block;
width: 32px;
height: 32px;
margin-right: 8px;
color: var(--color-white);
font-size: 16px;
line-height: 32px;
text-align: center;
vertical-align: middle;
border-radius: 6px;
}
</style> </style>