diff --git a/.env.development b/.env.development index 4e32643..1f0b776 100644 --- a/.env.development +++ b/.env.development @@ -10,7 +10,7 @@ # 网站前缀 VITE_BASE_API_URL = http://192.168.2.92:8089/server/ -# VITE_BASE_API_URL = http://43.137.2.78:8082/server/ +# VITE_BASE_API_URL = http://43.137.2.78:8085/server/ # base api VITE_BASE_API = '/server/' diff --git a/.env.production b/.env.production index 0c9e272..da5a0b1 100644 --- a/.env.production +++ b/.env.production @@ -9,7 +9,7 @@ # 只在生产模式中被载入 # 网站前缀 -VITE_BASE_API_URL = http://43.137.2.78:8082/server/ +VITE_BASE_API_URL = http://43.137.2.78:8085/server/ # base api VITE_BASE_API = '/server/' diff --git a/src/api/issue/model.d.ts b/src/api/issue/model.d.ts index 8b5807f..dddf202 100644 --- a/src/api/issue/model.d.ts +++ b/src/api/issue/model.d.ts @@ -26,6 +26,7 @@ declare namespace API { fileList?: any[]; // 附件列表 tags?: string[] | string; // 标签 solution?: string; // 解决方案 + zentaoNos?: string; // 关联的禅道工单号 }; type CreateIssueParams = { @@ -51,6 +52,7 @@ declare namespace API { fileList?: any[]; // 附件列表 tags?: string[] | string; // 标签 solution?: string; // 解决方案 + zentaoNos?: string; // 关联的禅道工单号 }; type DeleteIssueParams = { diff --git a/src/api/user/index.ts b/src/api/user/index.ts index d019c26..aa43a40 100644 --- a/src/api/user/index.ts +++ b/src/api/user/index.ts @@ -81,6 +81,28 @@ export function updateState(params: { id: string; state: number }) { }); } +/** + * @description 修改审核状态 + */ +export function updateAuditState(params: { id: string; auditState: number }) { + return request({ + url: `user/updateAuditState`, + method: 'get', + params, + }); +} + +/** + * @description 修改是否是管理员 + */ +export function updateAdminState(params: { id: string; adminState: number }) { + return request({ + url: `user/updateAdminState`, + method: 'get', + params, + }); +} + /** * @description 查询单条 */ diff --git a/src/api/user/model.d.ts b/src/api/user/model.d.ts index 1840ca4..0267247 100644 --- a/src/api/user/model.d.ts +++ b/src/api/user/model.d.ts @@ -20,6 +20,7 @@ declare namespace API { createTime: string; isAdmin: number; email: string; + auditState?: number; pendingStatus?: boolean; }; diff --git a/src/assets/icons/audit.svg b/src/assets/icons/audit.svg new file mode 100644 index 0000000..76e70d1 --- /dev/null +++ b/src/assets/icons/audit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/layout/header/index.vue b/src/layout/header/index.vue index 86abc5c..028521b 100644 --- a/src/layout/header/index.vue +++ b/src/layout/header/index.vue @@ -2,7 +2,7 @@ - + @@ -47,6 +47,9 @@ {{ $t('routes.account.settings') }} --> + + {{ $t('routes.account.goToClient') }} +
  {{ $t('layout.header.dropdownItemLoginOut') }} @@ -223,6 +226,5 @@ * { cursor: pointer; } - } diff --git a/src/locales/lang/en-US/layout.ts b/src/locales/lang/en-US/layout.ts index 5a9408a..c233858 100644 --- a/src/locales/lang/en-US/layout.ts +++ b/src/locales/lang/en-US/layout.ts @@ -4,6 +4,7 @@ export default { // user dropdown dropdownItemDoc: 'Document', dropdownItemLoginOut: 'Login Out', + dropdownItemLoginIn: 'Login In', tooltipErrorLog: 'Error log', tooltipLock: 'Lock screen', diff --git a/src/locales/lang/en-US/routes/account.ts b/src/locales/lang/en-US/routes/account.ts index 951fc35..5da956b 100644 --- a/src/locales/lang/en-US/routes/account.ts +++ b/src/locales/lang/en-US/routes/account.ts @@ -1,4 +1,5 @@ export default { settings: 'settings', about: 'about', + goToClient: 'go to client', }; diff --git a/src/locales/lang/zh-CN/layout.ts b/src/locales/lang/zh-CN/layout.ts index ed1f853..892facb 100644 --- a/src/locales/lang/zh-CN/layout.ts +++ b/src/locales/lang/zh-CN/layout.ts @@ -4,6 +4,7 @@ export default { // user dropdown dropdownItemDoc: '文档', dropdownItemLoginOut: '退出系统', + dropdownItemLoginIn: '去登录', // tooltip tooltipErrorLog: '错误日志', diff --git a/src/locales/lang/zh-CN/routes/account.ts b/src/locales/lang/zh-CN/routes/account.ts index d7d36ad..3398845 100644 --- a/src/locales/lang/zh-CN/routes/account.ts +++ b/src/locales/lang/zh-CN/routes/account.ts @@ -1,4 +1,5 @@ export default { settings: '个人设置', about: '关于', + goToClient: '去客户端', }; diff --git a/src/plugins/antd.ts b/src/plugins/antd.ts index 2b6cb2a..1644ae4 100644 --- a/src/plugins/antd.ts +++ b/src/plugins/antd.ts @@ -4,7 +4,7 @@ import { AButton } from '@/components/basic/button/index'; // https://www.antdv.com/docs/vue/getting-started-cn#按需加载 import 'ant-design-vue/es/message/style/css'; //vite只能用 ant-design-vue/es 而非 ant-design-vue/lib -// import 'ant-design-vue/dist/antd.css'; +import 'ant-design-vue/dist/antd.css'; import 'ant-design-vue/dist/antd.variable.min.css'; import 'dayjs/locale/zh-cn'; diff --git a/src/router/constant.ts b/src/router/constant.ts index bf1d3b3..839df36 100644 --- a/src/router/constant.ts +++ b/src/router/constant.ts @@ -23,7 +23,14 @@ export const whiteNameList = [ 'error-404', ] as const; // no redirect whitelist -export const withoutLoginNameList = [LOGIN_NAME, 'icons', 'error', 'error-404'] as const; +export const withoutLoginNameList = [ + LOGIN_NAME, + ENTRANCE_NAME, + KNOWLEDGE_NAME, + 'icons', + 'error', + 'error-404', +] as const; export type WhiteNameList = typeof whiteNameList; diff --git a/src/router/index.ts b/src/router/index.ts index 1074e7f..0a03a2b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -12,7 +12,7 @@ export const routes: Array = [ { path: '/', name: 'Layout', - redirect: '/dashboard/welcome', + redirect: '/entrance', component: () => import('@/layout/index.vue'), meta: { title: '首页', diff --git a/src/router/router-guards.ts b/src/router/router-guards.ts index 26670d4..9bef988 100644 --- a/src/router/router-guards.ts +++ b/src/router/router-guards.ts @@ -11,7 +11,7 @@ import { to as _to } from '@/utils/awaitTo'; NProgress.configure({ showSpinner: false }); // NProgress Configuration -const defaultRoutePath = '/dashboard/welcome'; +const defaultRoutePath = '/entrance'; export function createRouterGuards(router: Router, withoutLoginNameList: WhiteNameList) { router.beforeEach(async (to, _, next) => { diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 2ab173f..b6bddd5 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -11,6 +11,7 @@ import { generatorDynamicRouter } from '@/router/generator-router'; import { resetRouter } from '@/router'; interface UserState { + id?: string; token: string; tokenName: string; name: string; diff --git a/src/views/client/entrance/index.vue b/src/views/client/entrance/index.vue index cef7601..3f2e802 100644 --- a/src/views/client/entrance/index.vue +++ b/src/views/client/entrance/index.vue @@ -8,20 +8,23 @@
-
+
-
+
@@ -112,7 +115,7 @@
- 立即进入 → + 立即进入 →
@@ -143,13 +146,14 @@ import { fetchKnowledgeBaseList, findOneById } from '@/api/knowledgeBase'; import { DictEnum } from '@/enums/dictEnum'; import { getDictionaryByTypeName } from '@/utils/dict'; + const router = useRouter(); const route = useRoute(); const userStore = useUserStore(); const keepAliveStore = useKeepAliveStore(); - const userInfo = computed(() => userStore.userInfo); + const userInfo = ref(userStore.userInfo); const searchText = ref(''); const searchTags = ref([]); @@ -213,6 +217,16 @@ const goTo = (route: string) => { console.log(`前往 ${route}`); + if (route === '/issue' && !userInfo.value?.id) { + return Modal.confirm({ + title: '访客状态无法查看问题工单,请先登录!', + icon: , + okText: '去登录', + onOk: async () => { + goToLogin(); + }, + }); + } router.push(route); }; @@ -227,22 +241,31 @@ icon: , centered: true, onOk: async () => { - // 如果不是rootadmin,则退出登录 - if (userStore.userInfo.phone !== '13553550634') { - // logout({}) - await userStore.logout(); - } + await userStore.logout(); + keepAliveStore.clear(); // 移除标签页 localStorage.clear(); message.success('成功退出登录'); await nextTick(); - router.replace({ - name: LOGIN_NAME, - query: { - redirect: route.fullPath, - }, - }); + userInfo.value = { + name: '访客', + }; + // router.replace({ + // name: LOGIN_NAME, + // query: { + // redirect: route.fullPath, + // }, + // }); + }, + }); + }; + + const goToLogin = () => { + router.push({ + name: LOGIN_NAME, + query: { + redirect: route.fullPath, }, }); }; @@ -253,7 +276,13 @@ }; const initData = async () => { - await getTags(); + // await getTags(); + console.log(' userInfo.value: ', userInfo.value); + if (!userInfo.value?.id) { + userInfo.value = { + name: '访客', + }; + } }; initData(); @@ -281,7 +310,7 @@ } span { - font-size: 18px; + font-size: 16px; font-weight: 500; } } @@ -331,7 +360,7 @@ margin-bottom: 48px; h1 { - font-size: 32px; + font-size: 30px; margin-bottom: 24px; } @@ -365,7 +394,7 @@ display: flex; flex-direction: column; justify-content: space-between; - + cursor: pointer; &:hover { transform: translateY(-5px); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08); @@ -397,7 +426,7 @@ flex: 1; h3 { - font-size: 20px; + font-size: 18px; margin-bottom: 8px; color: #1f1f1f; font-weight: 600; @@ -449,7 +478,7 @@ :deep(.ant-input-group-addon) { background: none; .ant-btn { - border-radius: 0 24px 24px 0; + border-radius: 0 24px 24px 0 !important; height: 48px; } } diff --git a/src/views/client/issue/columns.tsx b/src/views/client/issue/columns.tsx new file mode 100644 index 0000000..2d69038 --- /dev/null +++ b/src/views/client/issue/columns.tsx @@ -0,0 +1,165 @@ +import type { TableColumn } from '@/components/core/dynamic-table'; +import { stateTypeList } from './data'; +import { Tag } from 'ant-design-vue'; +import { DictEnum } from '@/enums/dictEnum'; +import { getDictionaryByTypeName } from '@/utils/dict'; +export type TableListItem = API.IssueType; +export type TableColumnItem = TableColumn; + +// const questionTypeList = await getDictionaryByTypeName(DictEnum.QUESTION_TYPE); + +// 数据项类型 +// export type ListItemType = typeof tableData[number]; +// 使用TableColumn 将会限制dataIndex的类型,但换来的是dataIndex有类型提示 +export const baseColumns: TableColumnItem[] = [ + { + title: '问题标题', + align: 'center', + dataIndex: 'title', + // sorter: true, + width: 150, + resizable: true, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '问题描述', + align: 'center', + dataIndex: 'description', + width: 150, + ellipsis: true, + resizable: true, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '标签', + align: 'center', + dataIndex: 'tags', + width: 200, + hideInSearch: true, + }, + // { + // title: '问题属性', + // align: 'center', + // dataIndex: 'attribute', + // width: 150, + // formItemProps: { + // defaultValue: undefined, + // required: false, + // component: 'Select', + // componentProps: { + // options: questionTypeList, + // }, + // }, + // customRender: ({ record }) => { + // const label = questionTypeList?.find((e) => e.value === record.attribute)?.label; + + // return
{{ label }}
; + // }, + // }, + { + title: '问题号', + align: 'center', + dataIndex: 'billcode', + width: 200, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '客户', + align: 'center', + dataIndex: 'customer', + width: 150, + hideInSearch: true, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '产品', + align: 'center', + dataIndex: 'product', + width: 150, + hideInSearch: true, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '版本', + align: 'center', + dataIndex: 'version', + width: 150, + hideInSearch: true, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '代理商', + align: 'center', + dataIndex: 'agent', + width: 150, + hideInSearch: true, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '服务号', + align: 'center', + dataIndex: 'serviceNumber', + width: 150, + hideInSearch: true, + formItemProps: { + defaultValue: '', + required: false, + }, + }, + { + title: '创建时间', + align: 'center', + width: 200, + dataIndex: 'createTime', + formItemProps: { + defaultValue: '', + required: false, + component: 'RangePicker', + componentProps: { + valueFormat: 'YYYY-MM-DD', + }, + }, + }, + // state 问题状态 + { + title: '问题状态', + align: 'center', + dataIndex: 'state', + width: 150, + fixed: 'right', + formItemProps: { + defaultValue: undefined, + required: false, + component: 'Select', + componentProps: { + options: stateTypeList, + }, + }, + customRender: ({ record }) => { + const { label, color } = stateTypeList.find((e) => e.value === record.state); + + return {label}; + }, + }, +]; diff --git a/src/views/client/issue/data.ts b/src/views/client/issue/data.ts new file mode 100644 index 0000000..2d8cc6f --- /dev/null +++ b/src/views/client/issue/data.ts @@ -0,0 +1,32 @@ +export const stateTypeList: any = [ + // Init(0,"待处理"), + // Back(1,"退回"), + // Develop(2,"开发中"), + // Test(3,"测试中"), + // End(4,"结束"), + { + label: '待处理', + value: 0, + color: '#f50', + }, + { + label: '退回', + value: 1, + color: '#2db7f5', + }, + { + label: '开发中', + value: 2, + color: '#87d068', + }, + { + label: '测试中', + value: 3, + color: '#108ee9', + }, + { + label: '已解决', + value: 4, + color: '#f50', + }, +]; diff --git a/src/views/client/issue/formSchemas.tsx b/src/views/client/issue/formSchemas.tsx new file mode 100644 index 0000000..a323569 --- /dev/null +++ b/src/views/client/issue/formSchemas.tsx @@ -0,0 +1,294 @@ +import type { FormSchema } from '@/components/core/schema-form/'; +import { TableListItem } from './columns'; +import { commonUpload } from '@/api/upload'; +import { message, Button } from 'ant-design-vue'; +import { getFileExtension } from '@/utils/fileUtils'; +import { UploadOutlined } from '@ant-design/icons-vue'; +import { stateTypeList } from './data'; +import { DictEnum } from '@/enums/dictEnum'; +import { getDictionaryByTypeName } from '@/utils/dict'; +import { fetchProdList, fetchVersionPageList } from '@/api/prodVersion'; +import { vShow } from 'vue'; +const questionTypeList = await getDictionaryByTypeName(DictEnum.QUESTION_TYPE); +// 编辑页字段 +export const getEditFormSchema: ( + row?: Partial, + isDetail?: boolean, +) => FormSchema[] = (record = {}, isDetail = false) => { + console.log('questionTypeList: ', questionTypeList); + + return [ + { + field: 'billcode', + component: 'Input', + label: '问题号', + dynamicDisabled: true, + colProps: { + span: 24, + }, + }, + { + field: 'title', + component: 'Input', + componentProps: { + showCount: true, + maxlength: 150, + }, + label: '问题标题', + colProps: { + span: 12, + }, + rules: [{ required: true }], + }, + { + field: 'tags', + component: 'Select', + componentProps: { + request: async () => { + const data = await getDictionaryByTypeName(DictEnum.TAG_TYPE); + return data; + }, + multiple: true, + placeholder: '请选择标签', + mode: 'tags', + allowClear: true, + }, + label: '标签', + colProps: { + span: 12, + }, + rules: [{ required: true, type: 'array' }], + }, + // { + // label: '问题属性', + // field: 'arrtibute', + // component: 'Select', + // componentProps: { + // options: questionTypeList, + // }, + // colProps: { + // span: 12, + // }, + // rules: [{ required: true, type: 'string' }], + // }, + + { + field: 'customer', + component: 'Input', + label: '客户', + colProps: { + span: 12, + }, + }, + { + field: 'productId', + component: 'Select', + componentProps: ({ formModel, formInstance }) => { + return { + request: async () => { + const data = (await fetchProdList({})) as any; + return data.map((e) => { + return { + label: e.name, + value: e.id, + }; + }); + }, + onChange: async (e) => { + console.log('e: ', e); + const { data } = await fetchVersionPageList({ + productId: e, + current: 1, + size: 999, + }); + if (data && Array.isArray(data) && data.length) { + formInstance?.setFieldsValue({ + versionId: data[0].id, + }); + formInstance.updateSchema({ + field: 'versionId', + componentProps: { + options: data.map((e) => { + return { + label: e.name, + value: e.id, + }; + }), + }, + }); + } + }, + }; + }, + label: '产品', + colProps: { + span: 12, + }, + rules: [{ required: true, type: 'number' }], + }, + { + field: 'versionId', + component: 'Select', + componentProps: { + options: [], + }, + label: '版本', + colProps: { + span: 12, + }, + rules: [{ required: true, type: 'number' }], + }, + { + field: 'agent', + component: 'Input', + label: '代理商', + colProps: { + span: 12, + }, + }, + + { + field: 'serviceNumber', + component: 'Input', + label: '服务号', + colProps: { + span: 12, + }, + }, + + { + field: 'contacts', + component: 'Input', + label: '联系人', + colProps: { + span: 12, + }, + rules: [{ required: true, type: 'string' }], + }, + { + field: 'contactsEmail', + component: 'Input', + label: '联系人邮箱', + colProps: { + span: 12, + }, + rules: [{ required: true, type: 'string' }], + }, + { + field: 'contactsMobile', + component: 'Input', + label: '联系人手机号', + colProps: { + span: 12, + }, + rules: [ + { + required: true, + message: '请输入正确格式的电话号码', + pattern: + /^(1(3[0-9]|4[01456879]|5[0-3,5-9]|6[2567]|7[0-8]|8[0-9]|9[0-3,5-9])\d{8})$|^0\d{2,3}-?\d{7,8}$/, + }, + ], + }, + // { + // field: 'state', + // component: 'Select', + // defaultValue: 0, + // label: '状态', + // colProps: { + // span: 12, + // }, + // componentProps: { + // options: stateTypeList, + // disabled: true, + // }, + // rules: [{ required: true, type: 'number' }], + // }, + { + field: 'description', + component: 'InputTextArea', + componentProps: { + rows: 4, + placeholder: '请输入问题描述', + showCount: true, + maxlength: 150, + }, + label: '问题描述', + colProps: { + span: 24, + }, + rules: [{ required: true }], + }, + + { + label: '描述附件', + field: 'files', + component: 'Upload', + componentProps: { + disabled: isDetail, + customRequest: async (data) => { + console.log('data: ', data); + const formData = new FormData(); + formData.append('file', data.file); + const res = await commonUpload(formData); + console.log('res: ', res); + data?.onSuccess && data?.onSuccess(res, data.file as any); + }, + beforeUpload: (file) => { + console.log('file: ', file); + + // 限制允许上传的文件类型 + const allowedTypes = ['xlsx', 'xls', 'doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png']; + + // 检查文件类型 + const fileType = getFileExtension(file.name) || 'unknown'; + if (!allowedTypes.includes(fileType)) { + // 文件类型不在允许列表中,拒绝上传 + // 可以在这里展示错误信息 + message.warning('文件类型不正确'); + return false; + } + + // 其他验证逻辑... + + // 允许上传 + return true; + }, + }, + componentSlots: { + default: () => ( + + ), + }, + colProps: { + span: 24, + }, + rules: [{ required: false }], + }, + { + label: '解决方案', + field: 'solution', + colProps: { + span: 24, + }, + vShow: isDetail, + // rules: [{ required: true, type: 'array' }], + slot: 'solution', + }, + ] as any; +}; + +export const getFlowFormSchema: (row?: Partial) => FormSchema[] = (record = {}) => { + return [ + { + field: 'remark', + component: 'InputTextArea', + label: '文字补充内容', + colProps: { + span: 24, + }, + }, + ]; +}; diff --git a/src/views/client/issue/index.vue b/src/views/client/issue/index.vue index 5c9db05..76effa3 100644 --- a/src/views/client/issue/index.vue +++ b/src/views/client/issue/index.vue @@ -132,7 +132,7 @@ import Detail from '@/views/question/issue/detail.vue'; import { DraggableModal } from '@/components/core/draggable-modal'; import { useForm } from '@/components/core/schema-form'; - import { getEditFormSchema } from '@/views/question/issue/formSchemas.tsx'; + import { getEditFormSchema } from './formSchemas.tsx'; import { QuillEditor } from '@vueup/vue-quill'; import '@vueup/vue-quill/dist/vue-quill.snow.css'; import { type TableListItem } from '@/views/question/issue/columns'; @@ -205,13 +205,13 @@ handleAdd(); }, }, - { - title: '编辑工单', - icon: EditOutlined, - onClick: () => { - handleEdit(curRow.value); - }, - }, + // { + // title: '编辑工单', + // icon: EditOutlined, + // onClick: () => { + // handleEdit(curRow.value); + // }, + // }, // { // title: '删除工单', // icon: DeleteOutlined, @@ -258,6 +258,11 @@ values.tags = values.tags.join(','); } + // 新增默认为初始化状态 + if (!values.id) { + values.state = 0; + } + await (values.id ? updateIssue : createIssue)(values); message.success(`${values.id ? '编辑' : '新增'}成功`); visible.value = false; diff --git a/src/views/dashboard/welcome/index.vue b/src/views/dashboard/welcome/index.vue index 6f7a6d8..72d3be2 100644 --- a/src/views/dashboard/welcome/index.vue +++ b/src/views/dashboard/welcome/index.vue @@ -7,7 +7,7 @@ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE -->