diff --git a/.env.production b/.env.production index 9e73a2b..0c9e272 100644 --- a/.env.production +++ b/.env.production @@ -9,7 +9,7 @@ # 只在生产模式中被载入 # 网站前缀 -VITE_BASE_API_URL = http://192.168.3.118:10010/server/ +VITE_BASE_API_URL = http://43.137.2.78:8082/server/ # base api VITE_BASE_API = '/server/' diff --git a/components.d.ts b/components.d.ts index 3228ba0..60deb63 100644 --- a/components.d.ts +++ b/components.d.ts @@ -9,16 +9,27 @@ declare module '@vue/runtime-core' { export interface GlobalComponents { AButton: typeof import('ant-design-vue/es')['Button'] ACard: typeof import('ant-design-vue/es')['Card'] + ACardMeta: typeof import('ant-design-vue/es')['CardMeta'] ACol: typeof import('ant-design-vue/es')['Col'] + AEmpty: typeof import('ant-design-vue/es')['Empty'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] AInput: typeof import('ant-design-vue/es')['Input'] + AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] ALayout: typeof import('ant-design-vue/es')['Layout'] ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] ApiSelect: typeof import('./src/components/core/schema-form/src/components/ApiSelect.vue')['default'] ARow: typeof import('ant-design-vue/es')['Row'] + ASpace: typeof import('ant-design-vue/es')['Space'] + ASpin: typeof import('ant-design-vue/es')['Spin'] + AStep: typeof import('ant-design-vue/es')['Step'] + ASteps: typeof import('ant-design-vue/es')['Steps'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] ATag: typeof import('ant-design-vue/es')['Tag'] + ATooltip: typeof import('ant-design-vue/es')['Tooltip'] + ATree: typeof import('ant-design-vue/es')['Tree'] ATypographyText: typeof import('ant-design-vue/es')['TypographyText'] BasicArrow: typeof import('./src/components/basic/basic-arrow/index.vue')['default'] BasicDrawer: typeof import('./src/components/core/Drawer/src/BasicDrawer.vue')['default'] diff --git a/package.json b/package.json index ba99d66..c6ef2d7 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "vite": "^4.3.2", "vite-plugin-svg-icons": "~2.0.1", "vite-tsconfig-paths": "^4.2.0", - "vue-tsc": "^1.4.2" + "vue-tsc": "^1.4.2", + "vite-plugin-top-level-await": "^1.5.0" } } diff --git a/src/api/issue/model.d.ts b/src/api/issue/model.d.ts index 3f26455..8b5807f 100644 --- a/src/api/issue/model.d.ts +++ b/src/api/issue/model.d.ts @@ -24,6 +24,8 @@ declare namespace API { updateUserid?: number; // 更新人ID version?: string; // 版本 fileList?: any[]; // 附件列表 + tags?: string[] | string; // 标签 + solution?: string; // 解决方案 }; type CreateIssueParams = { @@ -47,6 +49,8 @@ declare namespace API { updateUserid?: number; // 更新人ID version?: string; // 版本 fileList?: any[]; // 附件列表 + tags?: string[] | string; // 标签 + solution?: string; // 解决方案 }; type DeleteIssueParams = { diff --git a/src/api/user/model.d.ts b/src/api/user/model.d.ts index d8b4109..1840ca4 100644 --- a/src/api/user/model.d.ts +++ b/src/api/user/model.d.ts @@ -19,6 +19,7 @@ declare namespace API { remark: string; createTime: string; isAdmin: number; + email: string; pendingStatus?: boolean; }; @@ -31,6 +32,7 @@ declare namespace API { state: number; remark: string; isAdmin: number; + email: string; }; type DeleteUserParams = { diff --git a/src/assets/icons/logo.png b/src/assets/icons/logo.png new file mode 100644 index 0000000..0002e23 Binary files /dev/null and b/src/assets/icons/logo.png differ diff --git a/src/assets/images/background.jpg b/src/assets/images/background.jpg new file mode 100644 index 0000000..ceb66a0 Binary files /dev/null and b/src/assets/images/background.jpg differ diff --git a/src/enums/dictEnum.ts b/src/enums/dictEnum.ts index 4b60c06..d0a6a9f 100644 --- a/src/enums/dictEnum.ts +++ b/src/enums/dictEnum.ts @@ -1,3 +1,4 @@ export enum DictEnum { QUESTION_TYPE = '问题属性', + TAG_TYPE = '标签', } diff --git a/src/layout/logo/index.vue b/src/layout/logo/index.vue index 4864565..28a43e0 100644 --- a/src/layout/logo/index.vue +++ b/src/layout/logo/index.vue @@ -54,7 +54,7 @@ .title-text { font-size: 18px; font-weight: 600; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: @primary-color; -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: 1px; @@ -64,7 +64,7 @@ height: 2px; width: 24px; margin-top: 4px; - background: linear-gradient(90deg, #667eea, #764ba2); + background: @primary-color; transition: width 0.3s ease; } diff --git a/src/router/constant.ts b/src/router/constant.ts index 7a24a0b..e984796 100644 --- a/src/router/constant.ts +++ b/src/router/constant.ts @@ -2,6 +2,10 @@ export const LOGIN_NAME = 'Login'; export const KNOWLEDGE_NAME = 'Knowledge'; +export const ISSUE_NAME = 'issue'; + +export const ENTRANCE_NAME = 'entrance'; + export const REDIRECT_NAME = 'Redirect'; export const PARENT_LAYOUT_NAME = 'ParentLayout'; @@ -13,4 +17,4 @@ export const whiteNameList = [LOGIN_NAME, 'icons', 'error', 'error-404'] as cons export type WhiteNameList = typeof whiteNameList; -export type WhiteName = typeof whiteNameList[number]; +export type WhiteName = (typeof whiteNameList)[number]; diff --git a/src/router/outsideLayout.ts b/src/router/outsideLayout.ts index a380e04..dd5558b 100644 --- a/src/router/outsideLayout.ts +++ b/src/router/outsideLayout.ts @@ -1,5 +1,5 @@ import type { RouteRecordRaw } from 'vue-router'; -import { LOGIN_NAME, KNOWLEDGE_NAME } from '@/router/constant'; +import { LOGIN_NAME, KNOWLEDGE_NAME, ISSUE_NAME, ENTRANCE_NAME } from '@/router/constant'; /** * layout布局之外的路由 @@ -16,10 +16,28 @@ export const LoginRoute: RouteRecordRaw = { export const KnowledgeRoute: RouteRecordRaw = { path: '/knowledge', name: KNOWLEDGE_NAME, - component: () => import(/* webpackChunkName: "login" */ '@/views/knowledgeBase/index.vue'), + component: () => import(/* webpackChunkName: "login" */ '@/views/client/knowledgeBase/index.vue'), meta: { title: '知识库', }, }; -export default [LoginRoute, KnowledgeRoute]; +export const Issue: RouteRecordRaw = { + path: '/issue', + name: ISSUE_NAME, + component: () => import(/* webpackChunkName: "login" */ '@/views/client/issue/index.vue'), + meta: { + title: '问题工单', + }, +}; + +export const Entrance: RouteRecordRaw = { + path: '/entrance', + name: ENTRANCE_NAME, + component: () => import(/* webpackChunkName: "login" */ '@/views/client/entrance/index.vue'), + meta: { + title: '入口', + }, +}; + +export default [LoginRoute, KnowledgeRoute, Issue, Entrance]; diff --git a/src/store/modules/projectConfig.ts b/src/store/modules/projectConfig.ts index 116c3a4..514c2c9 100644 --- a/src/store/modules/projectConfig.ts +++ b/src/store/modules/projectConfig.ts @@ -60,7 +60,7 @@ export type ThemeState = { export const defaultConfig: ThemeState = { navTheme: 'dark', // theme for nav menu - primaryColor: 'rgb(24, 144, 255)', // '#F5222D', // primary color of ant design + primaryColor: 'rgb(250, 84, 28)', // '#F5222D', // primary color of ant design layout: 'sidemenu', // nav menu position: `sidemenu` or `topmenu` contentWidth: 'Fluid', // layout of content: `Fluid` or `Fixed`, only works when layout is topmenu fixedHeader: false, // sticky header diff --git a/src/views/client/entrance/index.vue b/src/views/client/entrance/index.vue new file mode 100644 index 0000000..bc94ef6 --- /dev/null +++ b/src/views/client/entrance/index.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/src/views/client/issue/index.vue b/src/views/client/issue/index.vue new file mode 100644 index 0000000..0345ec9 --- /dev/null +++ b/src/views/client/issue/index.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/src/views/client/knowledgeBase/index.vue b/src/views/client/knowledgeBase/index.vue new file mode 100644 index 0000000..dc058b0 --- /dev/null +++ b/src/views/client/knowledgeBase/index.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/src/views/knowledgeBase/index.vue b/src/views/knowledgeBase/index.vue deleted file mode 100644 index 3b2d8e9..0000000 --- a/src/views/knowledgeBase/index.vue +++ /dev/null @@ -1,179 +0,0 @@ - - - - - diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 34ed4a2..cf3c248 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -4,7 +4,7 @@
- + logo

问题工单管理系统

@@ -26,7 +26,17 @@ - + + + + + + @@ -92,6 +102,7 @@ formInline: { username: 'admin', password: '123', + email: '', // 新增:邮箱字段 confirmPassword: '', // 新增:确认密码字段 }, }); @@ -166,21 +177,89 @@ display: flex; width: 100vw; height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: url('@/assets/images/background.jpg') no-repeat center center fixed; + background-size: cover; + // background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); align-items: center; justify-content: center; + position: relative; + overflow: hidden; + + // &::before, + // &::after { + // content: ''; + // position: absolute; + // width: 1000px; + // height: 1000px; + // border-radius: 50%; + // background: linear-gradient( + // 135deg, + // rgba(255, 255, 255, 0.1) 0%, + // rgba(255, 255, 255, 0.05) 100% + // ); + // animation: float 20s infinite linear; + // } + + // &::before { + // top: -400px; + // right: -200px; + // animation-delay: -5s; + // } + + // &::after { + // bottom: -400px; + // left: -200px; + // animation-duration: 25s; + // } } .login-box { width: 460px; padding: 40px; - background: rgba(255, 255, 255, 0.95); - border-radius: 16px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(10px); + background: rgba(255, 255, 255, 0.4); + border-radius: 24px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(255, 255, 255, 0.1) inset; + backdrop-filter: blur(12px); animation: fadeIn 0.5s ease-out; + position: relative; + z-index: 1; + + &::before { + content: ''; + position: absolute; + inset: 0; + border-radius: 24px; + padding: 2px; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.1)); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + pointer-events: none; + } } + @keyframes float { + 0% { + transform: rotate(0deg) translate(0, 0); + } + 50% { + transform: rotate(180deg) translate(100px, 50px); + } + 100% { + transform: rotate(360deg) translate(0, 0); + } + } + + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } + } .login-content { .login-header { text-align: center; @@ -188,9 +267,12 @@ .logo-wrapper { margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: center; .logo-img { - width: 64px; + width: 150px; height: 64px; object-fit: contain; } @@ -199,7 +281,7 @@ .login-title { font-size: 32px; font-weight: 700; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #e6400b 0%, #ec7049 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 16px; @@ -215,17 +297,17 @@ .line { width: 40px; height: 1px; - background: linear-gradient(90deg, transparent, #667eea); + background: linear-gradient(90deg, transparent, #e6400b); &:last-child { - background: linear-gradient(90deg, #764ba2, transparent); + background: linear-gradient(90deg, #ec7049, transparent); } } .dot { width: 6px; height: 6px; - background: #667eea; + background: #e6400b; border-radius: 50%; margin: 0 8px; } @@ -247,7 +329,7 @@ transform: translateX(-50%); width: 0; height: 2px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #e6400b 0%, #ec7049 100%); transition: width 0.3s ease; } @@ -256,6 +338,7 @@ } } } + } .custom-input { @@ -291,7 +374,7 @@ height: 46px; font-size: 16px; border-radius: 8px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #e6400b 0%, #ec7049 100%); border: none; transition: all 0.3s; diff --git a/src/views/question/issue/columns.tsx b/src/views/question/issue/columns.tsx index 5148df8..e4f4e81 100644 --- a/src/views/question/issue/columns.tsx +++ b/src/views/question/issue/columns.tsx @@ -35,6 +35,13 @@ export const baseColumns: TableColumnItem[] = [ required: false, }, }, + { + title: '标签', + align: 'center', + dataIndex: 'tags', + width: 200, + hideInSearch: true, + }, // { // title: '问题属性', // align: 'center', @@ -139,6 +146,7 @@ export const baseColumns: TableColumnItem[] = [ align: 'center', dataIndex: 'state', width: 150, + fixed: 'right', formItemProps: { defaultValue: undefined, required: false, diff --git a/src/views/question/issue/data.ts b/src/views/question/issue/data.ts index 1afd691..2d8cc6f 100644 --- a/src/views/question/issue/data.ts +++ b/src/views/question/issue/data.ts @@ -1,11 +1,11 @@ export const stateTypeList: any = [ - // Init(0,"初始化"), + // Init(0,"待处理"), // Back(1,"退回"), - // Develop(2,"开发"), - // Test(3,"测试"), + // Develop(2,"开发中"), + // Test(3,"测试中"), // End(4,"结束"), { - label: '初始化', + label: '待处理', value: 0, color: '#f50', }, @@ -15,17 +15,17 @@ export const stateTypeList: any = [ color: '#2db7f5', }, { - label: '开发', + label: '开发中', value: 2, color: '#87d068', }, { - label: '测试', + label: '测试中', value: 3, color: '#108ee9', }, { - label: '结束', + label: '已解决', value: 4, color: '#f50', }, diff --git a/src/views/question/issue/detail.vue b/src/views/question/issue/detail.vue index 3525665..33df7f4 100644 --- a/src/views/question/issue/detail.vue +++ b/src/views/question/issue/detail.vue @@ -13,7 +13,7 @@
- + @@ -24,17 +24,17 @@ - + - + - + @@ -79,10 +79,18 @@ import { useForm } from '@/components/core/schema-form'; import { getEditFormSchema } from './formSchemas'; import { useRoute } from 'vue-router'; - import { onMounted, ref } from 'vue'; + import { onMounted, ref, watch } from 'vue'; import { findOneById } from '@/api/issue'; import { SvgIcon } from '@/components/basic/svg-icon'; import { stateTypeList } from './data'; + + const props = defineProps({ + id: { + type: String, + default: '', + }, + }); + const route = useRoute(); const editorOptions = ref({ theme: 'snow', @@ -107,8 +115,6 @@ const current = ref(0); - const { id } = route.query; - const [SchemaForm, formRef] = useForm({ labelWidth: 150, schemas: getEditFormSchema({}, true), @@ -122,7 +128,14 @@ disabled: true, }); - onMounted(async () => { + const initData = async () => { + // 判断route.query.id和props.id是否存在,哪个存在用哪个 + const id = props.id || route.query.id; + + if (!id) { + return; + } + loading.value = true; const res = await findOneById({ id: id as string }); loading.value = false; @@ -151,6 +164,20 @@ formRef?.setFieldsValue(res); formRef?.clearValidate(); + }; + + watch( + () => props.id, + (newVal) => { + if (newVal) { + initData(); + } + }, + { immediate: true, deep: true }, + ); + + onMounted(async () => { + initData(); }); diff --git a/src/views/question/issue/formSchemas.tsx b/src/views/question/issue/formSchemas.tsx index 309e8f4..e0fd9cb 100644 --- a/src/views/question/issue/formSchemas.tsx +++ b/src/views/question/issue/formSchemas.tsx @@ -39,6 +39,25 @@ export const getEditFormSchema: ( }, 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', @@ -168,6 +187,7 @@ export const getEditFormSchema: ( { field: 'state', component: 'Select', + defaultValue: 0, label: '状态', colProps: { span: 12, diff --git a/src/views/question/issue/index.vue b/src/views/question/issue/index.vue index 603dd66..183846a 100644 --- a/src/views/question/issue/index.vue +++ b/src/views/question/issue/index.vue @@ -55,6 +55,7 @@