From de16608e33adbf9c1affbc3d6dcc124948a086f3 Mon Sep 17 00:00:00 2001 From: Code Migration Date: Sun, 18 Oct 2026 13:26:10 +0800 Subject: [PATCH] chore: seed migration objects batch 17 --- web/src/views/login/login/index.vue | 22 + web/src/views/login/register/agreement.vue | 54 ++ web/src/views/login/register/index.vue | 287 +++++++++ web/src/views/monitor/netconn/columns.ts | 130 ++++ web/src/views/monitor/netconn/index.vue | 224 +++++++ web/src/views/monitor/netconn/modal/edit.vue | 198 +++++++ web/src/views/monitor/netconn/modal/index.vue | 359 ++++++++++++ web/src/views/monitor/netconn/modal/modal.vue | 29 + web/src/views/monitor/netconn/modal/model.ts | 254 ++++++++ web/src/views/monitor/online/columns.ts | 122 ++++ web/src/views/monitor/online/index.vue | 153 +++++ web/src/views/monitor/serve-log/index.vue | 232 ++++++++ web/src/views/monitor/serve-log/model.ts | 137 +++++ .../serve-monitor/components/DataItem.vue | 98 ++++ .../components/chart/FullYearSalesChart.vue | 262 +++++++++ .../components/chart/LoadChart.vue | 130 ++++ web/src/views/monitor/serve-monitor/index.vue | 270 +++++++++ web/src/views/normalTreeDemo/edit.vue | 169 ++++++ web/src/views/normalTreeDemo/index.vue | 209 +++++++ web/src/views/normalTreeDemo/model.ts | 171 ++++++ web/src/views/optionTreeDemo/edit.vue | 170 ++++++ web/src/views/optionTreeDemo/index.vue | 319 ++++++++++ web/src/views/optionTreeDemo/model.ts | 165 ++++++ web/src/views/org/dept/dept.vue | 232 ++++++++ web/src/views/org/dept/edit.vue | 194 ++++++ web/src/views/org/dept/model.ts | 201 +++++++ web/src/views/org/post/columns.ts | 61 ++ web/src/views/org/post/post.vue | 305 ++++++++++ web/src/views/org/user/addBalance.vue | 147 +++++ web/src/views/org/user/addIntegral.vue | 147 +++++ web/src/views/org/user/columns.ts | 148 +++++ web/src/views/org/user/list.vue | 526 +++++++++++++++++ web/src/views/org/user/model.ts | 237 ++++++++ web/src/views/org/user/user.vue | 46 ++ web/src/views/permission/menu/addModal.vue | 84 +++ web/src/views/permission/menu/editForm.vue | 415 +++++++++++++ web/src/views/permission/menu/menu.vue | 215 +++++++ web/src/views/permission/menu/menuModal.vue | 43 ++ web/src/views/permission/menu/model.ts | 102 ++++ web/src/views/permission/role/columns.ts | 100 ++++ .../views/permission/role/editDataAuth.vue | 117 ++++ .../views/permission/role/editMenuAuth.vue | 221 +++++++ web/src/views/permission/role/editRole.vue | 146 +++++ web/src/views/permission/role/model.ts | 40 ++ web/src/views/permission/role/role.vue | 153 +++++ web/src/views/redirect/index.vue | 22 + web/src/views/system/blacklist/index.vue | 344 +++++++++++ web/src/views/system/config/BasicSetting.vue | 142 +++++ web/src/views/system/config/CashSetting.vue | 102 ++++ web/src/views/system/config/EmailSetting.vue | 210 +++++++ web/src/views/system/config/GeoSetting.vue | 85 +++ web/src/views/system/config/LoginSetting.vue | 208 +++++++ web/src/views/system/config/PaySetting.vue | 237 ++++++++ web/src/views/system/config/RevealSetting.vue | 193 ++++++ web/src/views/system/config/SmsSetting.vue | 322 ++++++++++ web/src/views/system/config/ThemeSetting.vue | 83 +++ web/src/views/system/config/UploadSetting.vue | 409 +++++++++++++ web/src/views/system/config/WechatSetting.vue | 121 ++++ web/src/views/system/config/system.vue | 206 +++++++ web/src/views/system/cron/columns.ts | 93 +++ web/src/views/system/cron/index.vue | 554 ++++++++++++++++++ web/src/views/system/cron/modal/columns.ts | 83 +++ web/src/views/system/cron/modal/index.vue | 237 ++++++++ web/src/views/system/cron/modal/modal.vue | 34 ++ web/src/views/system/dict/CreateDrawer.vue | 132 +++++ web/src/views/system/dict/columns.ts | 74 +++ web/src/views/system/dict/index.vue | 217 +++++++ web/src/views/system/dict/list.vue | 325 ++++++++++ web/src/views/system/dict/model.ts | 18 + web/src/views/testCategory/edit.vue | 157 +++++ web/src/views/testCategory/index.vue | 193 ++++++ web/src/views/testCategory/model.ts | 153 +++++ web/stylelint.config.js | 70 +++ web/tailwind.config.js | 7 + web/tsconfig.json | 60 ++ web/types/config.d.ts | 71 +++ web/types/global.d.ts | 100 ++++ web/types/index.d.ts | 28 + web/types/modules.d.ts | 10 + web/types/utils.d.ts | 5 + web/vite.config.ts | 83 +++ 81 files changed, 13432 insertions(+) create mode 100644 web/src/views/login/login/index.vue create mode 100644 web/src/views/login/register/agreement.vue create mode 100644 web/src/views/login/register/index.vue create mode 100644 web/src/views/monitor/netconn/columns.ts create mode 100644 web/src/views/monitor/netconn/index.vue create mode 100644 web/src/views/monitor/netconn/modal/edit.vue create mode 100644 web/src/views/monitor/netconn/modal/index.vue create mode 100644 web/src/views/monitor/netconn/modal/modal.vue create mode 100644 web/src/views/monitor/netconn/modal/model.ts create mode 100644 web/src/views/monitor/online/columns.ts create mode 100644 web/src/views/monitor/online/index.vue create mode 100644 web/src/views/monitor/serve-log/index.vue create mode 100644 web/src/views/monitor/serve-log/model.ts create mode 100644 web/src/views/monitor/serve-monitor/components/DataItem.vue create mode 100644 web/src/views/monitor/serve-monitor/components/chart/FullYearSalesChart.vue create mode 100644 web/src/views/monitor/serve-monitor/components/chart/LoadChart.vue create mode 100644 web/src/views/monitor/serve-monitor/index.vue create mode 100644 web/src/views/normalTreeDemo/edit.vue create mode 100644 web/src/views/normalTreeDemo/index.vue create mode 100644 web/src/views/normalTreeDemo/model.ts create mode 100644 web/src/views/optionTreeDemo/edit.vue create mode 100644 web/src/views/optionTreeDemo/index.vue create mode 100644 web/src/views/optionTreeDemo/model.ts create mode 100644 web/src/views/org/dept/dept.vue create mode 100644 web/src/views/org/dept/edit.vue create mode 100644 web/src/views/org/dept/model.ts create mode 100644 web/src/views/org/post/columns.ts create mode 100644 web/src/views/org/post/post.vue create mode 100644 web/src/views/org/user/addBalance.vue create mode 100644 web/src/views/org/user/addIntegral.vue create mode 100644 web/src/views/org/user/columns.ts create mode 100644 web/src/views/org/user/list.vue create mode 100644 web/src/views/org/user/model.ts create mode 100644 web/src/views/org/user/user.vue create mode 100644 web/src/views/permission/menu/addModal.vue create mode 100644 web/src/views/permission/menu/editForm.vue create mode 100644 web/src/views/permission/menu/menu.vue create mode 100644 web/src/views/permission/menu/menuModal.vue create mode 100644 web/src/views/permission/menu/model.ts create mode 100644 web/src/views/permission/role/columns.ts create mode 100644 web/src/views/permission/role/editDataAuth.vue create mode 100644 web/src/views/permission/role/editMenuAuth.vue create mode 100644 web/src/views/permission/role/editRole.vue create mode 100644 web/src/views/permission/role/model.ts create mode 100644 web/src/views/permission/role/role.vue create mode 100644 web/src/views/redirect/index.vue create mode 100644 web/src/views/system/blacklist/index.vue create mode 100644 web/src/views/system/config/BasicSetting.vue create mode 100644 web/src/views/system/config/CashSetting.vue create mode 100644 web/src/views/system/config/EmailSetting.vue create mode 100644 web/src/views/system/config/GeoSetting.vue create mode 100644 web/src/views/system/config/LoginSetting.vue create mode 100644 web/src/views/system/config/PaySetting.vue create mode 100644 web/src/views/system/config/RevealSetting.vue create mode 100644 web/src/views/system/config/SmsSetting.vue create mode 100644 web/src/views/system/config/ThemeSetting.vue create mode 100644 web/src/views/system/config/UploadSetting.vue create mode 100644 web/src/views/system/config/WechatSetting.vue create mode 100644 web/src/views/system/config/system.vue create mode 100644 web/src/views/system/cron/columns.ts create mode 100644 web/src/views/system/cron/index.vue create mode 100644 web/src/views/system/cron/modal/columns.ts create mode 100644 web/src/views/system/cron/modal/index.vue create mode 100644 web/src/views/system/cron/modal/modal.vue create mode 100644 web/src/views/system/dict/CreateDrawer.vue create mode 100644 web/src/views/system/dict/columns.ts create mode 100644 web/src/views/system/dict/index.vue create mode 100644 web/src/views/system/dict/list.vue create mode 100644 web/src/views/system/dict/model.ts create mode 100644 web/src/views/testCategory/edit.vue create mode 100644 web/src/views/testCategory/index.vue create mode 100644 web/src/views/testCategory/model.ts create mode 100644 web/stylelint.config.js create mode 100644 web/tailwind.config.js create mode 100644 web/tsconfig.json create mode 100644 web/types/config.d.ts create mode 100644 web/types/global.d.ts create mode 100644 web/types/index.d.ts create mode 100644 web/types/modules.d.ts create mode 100644 web/types/utils.d.ts create mode 100644 web/vite.config.ts diff --git a/web/src/views/login/login/index.vue b/web/src/views/login/login/index.vue new file mode 100644 index 0000000..a6d09db --- /dev/null +++ b/web/src/views/login/login/index.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/web/src/views/login/register/agreement.vue b/web/src/views/login/register/agreement.vue new file mode 100644 index 0000000..840c7b1 --- /dev/null +++ b/web/src/views/login/register/agreement.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/web/src/views/login/register/index.vue b/web/src/views/login/register/index.vue new file mode 100644 index 0000000..f4ce11f --- /dev/null +++ b/web/src/views/login/register/index.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/web/src/views/monitor/netconn/columns.ts b/web/src/views/monitor/netconn/columns.ts new file mode 100644 index 0000000..74c254c --- /dev/null +++ b/web/src/views/monitor/netconn/columns.ts @@ -0,0 +1,130 @@ +import { h } from 'vue'; +import { NButton, NTag } from 'naive-ui'; +import { timestampToTime, formatBefore } from '@/utils/dateUtil'; +import { renderIcon, renderOptionTag, renderTooltip } from '@/utils'; +import { HelpCircleOutline } from '@vicons/ionicons5'; + +export const columns = [ + { + title: '连接ID', + key: 'id', + width: 80, + render(row) { + return '# ' + row.id; + }, + }, + { + title: '连接协议', + key: 'proto', + width: 80, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'info', + bordered: false, + }, + { + default: () => row.proto, + } + ); + }, + }, + { + title: '服务端口', + key: 'port', + width: 80, + render(row) { + return row.port; + }, + }, + { + title: '应用名称', + key: 'name', + width: 100, + render: (rows, _) => { + return rows.name; + }, + }, + { + title: '登录地址', + key: 'addr', + width: 150, + }, + { + title(_column) { + return renderTooltip( + h( + NButton, + { + strong: true, + size: 'small', + text: true, + iconPlacement: 'right', + }, + { default: () => '认证状态', icon: renderIcon(HelpCircleOutline) } + ), + '成功连接到服务端口并通过登录许可认证的客户端连接会显示为 `已认证` 状态' + ); + }, + key: 'isAuth', + width: 80, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.isAuth ? 'success' : 'warning', + bordered: false, + }, + { + default: () => (row.isAuth ? '已认证' : '未认证'), + } + ); + }, + }, + { + title: '授权分组', + key: 'group', + width: 100, + render(row) { + return renderOptionTag('ServerLicenseGroupOptions', row.group); + }, + }, + { + title: '授权许可', + key: 'name', + render(row) { + return h('p', { id: 'app' }, [ + h('div', { + innerHTML: '

名称:' + row.licenseName + '

', + }), + h('div', { + innerHTML: '

APPID:' + row.appId + '

', + }), + ]); + }, + width: 180, + }, + { + title: '心跳', + key: 'heartbeatTime', + width: 100, + render: (rows, _) => { + return formatBefore(new Date(rows.heartbeatTime * 1000)); + }, + }, + { + title: '登录时间', + key: 'firstTime', + width: 150, + render: (rows, _) => { + return timestampToTime(rows.firstTime); + }, + }, +]; diff --git a/web/src/views/monitor/netconn/index.vue b/web/src/views/monitor/netconn/index.vue new file mode 100644 index 0000000..0dd0130 --- /dev/null +++ b/web/src/views/monitor/netconn/index.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/edit.vue b/web/src/views/monitor/netconn/modal/edit.vue new file mode 100644 index 0000000..3a70330 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/edit.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/index.vue b/web/src/views/monitor/netconn/modal/index.vue new file mode 100644 index 0000000..3fcdaf8 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/index.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/modal.vue b/web/src/views/monitor/netconn/modal/modal.vue new file mode 100644 index 0000000..cbdd692 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/modal.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/model.ts b/web/src/views/monitor/netconn/modal/model.ts new file mode 100644 index 0000000..bab1967 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/model.ts @@ -0,0 +1,254 @@ +import { h, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts, formatBefore } from '@/utils/dateUtil'; +import { useDictStore } from '@/store/modules/dict'; +import { renderOptionTag } from '@/utils'; + +const dict = useDictStore(); + +export interface State { + id: number; + group: string; + name: string; + appid: string; + secretKey: string; + remoteAddr: string; + onlineLimit: number; + loginTimes: number; + lastLoginAt: string; + lastActiveAt: string; + routes: any; + allowedIps: string; + endAt: string; + remark: string; + status: number; + createdAt: string; + updatedAt: string; +} + +export const defaultState = { + id: 0, + group: '', + name: '', + appid: '', + secretKey: '', + remoteAddr: '', + onlineLimit: 1, + loginTimes: 0, + lastLoginAt: '', + lastActiveAt: '', + routes: null, + allowedIps: '', + endAt: '', + remark: '', + status: 1, + createdAt: '', + updatedAt: '', +}; + +export function newState(state: State | null): State { + if (state !== null) { + return cloneDeep(state); + } + return cloneDeep(defaultState); +} + +export const rules = { + group: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入分组', + }, + name: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入许可名称', + }, + appid: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入应用ID', + }, + endAt: { + required: true, + trigger: ['blur', 'input', 'focus'], + type: 'string', + message: '请输入授权结束时间', + }, +}; + +export const schemas = ref([ + { + field: 'id', + component: 'NInput', + label: '许可ID', + componentProps: { + placeholder: '请输入许可ID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'group', + component: 'NSelect', + label: '授权分组', + defaultValue: null, + componentProps: { + placeholder: '请选择授权分组', + options: dict.getOption('ServerLicenseGroupOptions'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'name', + component: 'NInput', + label: '许可名称', + componentProps: { + placeholder: '请输入许可名称', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'appid', + component: 'NInput', + label: 'APPID', + componentProps: { + placeholder: '请输入APPID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + defaultValue: null, + componentProps: { + placeholder: '请选择状态', + options: dict.getOption('sys_normal_disable'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'endAt', + component: 'NDatePicker', + label: '过期时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +export const columns = [ + { + title: '许可ID', + key: 'id', + width: 100, + }, + { + title: '授权分组', + key: 'group', + width: 100, + render(row) { + return renderOptionTag('ServerLicenseGroupOptions', row.group); + }, + }, + { + title: '授权许可', + key: 'name', + render(row) { + return h('p', { id: 'app' }, [ + h('div', { + innerHTML: '

名称:' + row.name + '

', + }), + h('div', { + innerHTML: '

APPID:' + row.appid + '

', + }), + ]); + }, + width: 180, + }, + { + title: '在线', + key: 'online', + render(row) { + return row.online + ' / ' + row.onlineLimit; + }, + width: 100, + }, + { + title: '授权有效期', + key: 'endAt', + width: 150, + }, + { + title: '状态', + key: 'status', + width: 100, + render(row) { + return renderOptionTag('sys_normal_disable', row.status); + }, + }, + { + title: '最后连接', + key: 'remoteAddr', + width: 150, + }, + { + title: '最近登录 / 心跳', + key: 'name', + render(row) { + if (row.lastLoginAt === null) { + return '从未登录'; + } + return ( + formatBefore(new Date(row.lastLoginAt)) + ' / ' + formatBefore(new Date(row.lastActiveAt)) + ); + }, + width: 180, + }, + { + title: '累计登录', + key: 'loginTimes', + width: 100, + }, + { + title: '创建时间', + key: 'createdAt', + width: 150, + }, +]; + +export function loadOptions() { + dict.loadOptions(['sys_normal_disable', 'ServerLicenseGroupOptions']); +} diff --git a/web/src/views/monitor/online/columns.ts b/web/src/views/monitor/online/columns.ts new file mode 100644 index 0000000..8b3308e --- /dev/null +++ b/web/src/views/monitor/online/columns.ts @@ -0,0 +1,122 @@ +import { h } from 'vue'; +import { NAvatar, NTag } from 'naive-ui'; +import { timestampToTime, formatBefore } from '@/utils/dateUtil'; + +export const columns = [ + { + title: '会话编号', + key: 'id', + width: 280, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'info', + bordered: false, + }, + { + default: () => row.id, + } + ); + }, + }, + { + title: '登录应用', + key: 'app', + width: 80, + render(row) { + return row.app; + }, + }, + { + title: '用户ID', + key: 'userId', + width: 100, + }, + { + title: '用户名', + key: 'username', + width: 100, + }, + { + title: '头像', + key: 'avatar', + width: 80, + render(row) { + if (row.avatar !== '') { + return h(NAvatar, { + circle: true, + size: 'small', + src: row.avatar, + }); + } else { + return h( + NAvatar, + { + circle: true, + size: 'small', + }, + { + default: () => row.username.substring(0, 2), + } + ); + } + }, + }, + { + title: '登录IP', + key: 'ip', + width: 150, + }, + // { + // title: 'IP地区', + // key: 'region', + // }, + { + title: '浏览器', + key: 'browser', + width: 200, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'success', + bordered: false, + }, + { + default: () => row.browser, + } + ); + }, + }, + { + title: '操作系统', + key: 'os', + width: 150, + render(row) { + return row.os; + }, + }, + { + title: '最后活跃', + key: 'heartbeatTime', + width: 80, + render: (rows, _) => { + return formatBefore(new Date(rows.heartbeatTime * 1000)); + }, + }, + { + title: '登录时间', + key: 'firstTime', + width: 220, + render: (rows, _) => { + return timestampToTime(rows.firstTime); + }, + }, +]; diff --git a/web/src/views/monitor/online/index.vue b/web/src/views/monitor/online/index.vue new file mode 100644 index 0000000..c210d05 --- /dev/null +++ b/web/src/views/monitor/online/index.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/web/src/views/monitor/serve-log/index.vue b/web/src/views/monitor/serve-log/index.vue new file mode 100644 index 0000000..e54880c --- /dev/null +++ b/web/src/views/monitor/serve-log/index.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/web/src/views/monitor/serve-log/model.ts b/web/src/views/monitor/serve-log/model.ts new file mode 100644 index 0000000..18921e5 --- /dev/null +++ b/web/src/views/monitor/serve-log/model.ts @@ -0,0 +1,137 @@ +import { h, ref } from 'vue'; +import { NTag, NButton } from 'naive-ui'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { format } from 'date-fns'; +import { renderIcon, renderOptionTag, renderTooltip } from '@/utils'; +import { HelpCircleOutline } from '@vicons/ionicons5'; +import { useDictStore } from '@/store/modules/dict'; + +const dict = useDictStore(); + +export const schemas = ref([ + { + field: 'traceId', + component: 'NInput', + label: '链路ID', + componentProps: { + placeholder: '请输入链路ID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'content', + component: 'NInput', + label: '日志内容', + componentProps: { + placeholder: '请输入日志内容关键词', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'levelFormat', + component: 'NSelect', + label: '日志级别', + defaultValue: null, + componentProps: { + placeholder: '请选择日志级别', + options: dict.getOption('sys_log_type'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +export const columns = [ + { + title: '日志ID', + key: 'id', + width: 80, + }, + { + title(_column) { + return renderTooltip( + h( + NButton, + { + strong: true, + size: 'small', + text: true, + iconPlacement: 'right', + }, + { default: () => '链路ID', icon: renderIcon(HelpCircleOutline) } + ), + 'hotgo默认支持链路追踪,如果是web请求产生的日志则还可以关联对应的访问日志' + ); + }, + key: 'traceId', + width: 280, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'default', + bordered: false, + checkable: true, + }, + { + default: () => row.traceId, + } + ); + }, + }, + { + title: '日志级别', + key: 'levelFormat', + render(row) { + return renderOptionTag('sys_log_type', row.levelFormat); + }, + width: 120, + }, + { + title: '日志内容', + key: 'content', + width: 320, + }, + { + title: '触发时间', + key: 'triggerNs', + width: 200, + render(row) { + if (row.triggerNs <= 0) { + return '-'; + } + return format(new Date(row.triggerNs / 1000000), 'yyyy-MM-dd HH:mm:ss.SSS'); + }, + }, + { + title: '记录时间', + key: 'createdAt', + width: 150, + }, +]; + +export function loadOptions() { + dict.loadOptions(['sys_normal_disable', 'sys_log_type']); +} diff --git a/web/src/views/monitor/serve-monitor/components/DataItem.vue b/web/src/views/monitor/serve-monitor/components/DataItem.vue new file mode 100644 index 0000000..cff9e6f --- /dev/null +++ b/web/src/views/monitor/serve-monitor/components/DataItem.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/web/src/views/monitor/serve-monitor/components/chart/FullYearSalesChart.vue b/web/src/views/monitor/serve-monitor/components/chart/FullYearSalesChart.vue new file mode 100644 index 0000000..9bccef4 --- /dev/null +++ b/web/src/views/monitor/serve-monitor/components/chart/FullYearSalesChart.vue @@ -0,0 +1,262 @@ + + + + diff --git a/web/src/views/monitor/serve-monitor/components/chart/LoadChart.vue b/web/src/views/monitor/serve-monitor/components/chart/LoadChart.vue new file mode 100644 index 0000000..4652166 --- /dev/null +++ b/web/src/views/monitor/serve-monitor/components/chart/LoadChart.vue @@ -0,0 +1,130 @@ + + + diff --git a/web/src/views/monitor/serve-monitor/index.vue b/web/src/views/monitor/serve-monitor/index.vue new file mode 100644 index 0000000..9252f5c --- /dev/null +++ b/web/src/views/monitor/serve-monitor/index.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/web/src/views/normalTreeDemo/edit.vue b/web/src/views/normalTreeDemo/edit.vue new file mode 100644 index 0000000..712cc71 --- /dev/null +++ b/web/src/views/normalTreeDemo/edit.vue @@ -0,0 +1,169 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/normalTreeDemo/index.vue b/web/src/views/normalTreeDemo/index.vue new file mode 100644 index 0000000..c964ba4 --- /dev/null +++ b/web/src/views/normalTreeDemo/index.vue @@ -0,0 +1,209 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/normalTreeDemo/model.ts b/web/src/views/normalTreeDemo/model.ts new file mode 100644 index 0000000..361d809 --- /dev/null +++ b/web/src/views/normalTreeDemo/model.ts @@ -0,0 +1,171 @@ +import { h, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { renderOptionTag, renderPopoverMemberSumma, MemberSumma } from '@/utils'; +import { TreeOption } from '@/api/normalTreeDemo'; +import { useDictStore } from '@/store/modules/dict'; + +const dict = useDictStore(); + +export class State { + public title = ''; // 标题 + public id = 0; // ID + public pid = 0; // 上级 + public level = 1; // 关系树级别 + public tree = ''; // 关系树 + public categoryId = null; // 测试分类 + public description = ''; // 描述 + public sort = 0; // 排序 + public status = 1; // 状态 + public createdBy = 0; // 创建者 + public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息 + public updatedBy = 0; // 更新者 + public createdAt = ''; // 创建时间 + public updatedAt = ''; // 修改时间 + public deletedAt = ''; // 删除时间 + + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表单验证规则 +export const rules = { + title: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入标题', + }, +}; + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'title', + component: 'NInput', + label: '标题', + componentProps: { + placeholder: '请输入标题', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'categoryId', + component: 'NSelect', + label: '测试分类', + defaultValue: null, + componentProps: { + placeholder: '请选择测试分类', + options: dict.getOption('testCategoryOption'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + defaultValue: null, + componentProps: { + placeholder: '请选择状态', + options: dict.getOption('sys_normal_disable'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +// 表格列 +export const columns = [ + { + title: '标题', + key: 'title', + align: 'left', + width: 200, + }, + { + title: '测试分类', + key: 'categoryId', + align: 'left', + width: 100, + render(row: State) { + return renderOptionTag('testCategoryOption', row.categoryId); + }, + }, + { + title: '描述', + key: 'description', + align: 'left', + width: 300, + }, + { + title: '状态', + key: 'status', + align: 'left', + width: 150, + render(row: State) { + return renderOptionTag('sys_normal_disable', row.status); + }, + }, + { + title: '创建者', + key: 'createdBy', + align: 'left', + width: 100, + render(row: State) { + return renderPopoverMemberSumma(row.createdBySumma); + }, + }, + { + title: '创建时间', + key: 'createdAt', + align: 'left', + width: 180, + }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['sys_normal_disable', 'testCategoryOption']); +} + +// 关系树选项 +export const treeOption = ref([]); + +// 加载关系树选项 +export function loadTreeOption() { + TreeOption().then((res) => { + treeOption.value = res; + }); +} \ No newline at end of file diff --git a/web/src/views/optionTreeDemo/edit.vue b/web/src/views/optionTreeDemo/edit.vue new file mode 100644 index 0000000..4749d0e --- /dev/null +++ b/web/src/views/optionTreeDemo/edit.vue @@ -0,0 +1,170 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/optionTreeDemo/index.vue b/web/src/views/optionTreeDemo/index.vue new file mode 100644 index 0000000..c87322a --- /dev/null +++ b/web/src/views/optionTreeDemo/index.vue @@ -0,0 +1,319 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/optionTreeDemo/model.ts b/web/src/views/optionTreeDemo/model.ts new file mode 100644 index 0000000..b407f70 --- /dev/null +++ b/web/src/views/optionTreeDemo/model.ts @@ -0,0 +1,165 @@ +import { h, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { renderOptionTag, renderPopoverMemberSumma, MemberSumma } from '@/utils'; +import { TreeOption } from '@/api/optionTreeDemo'; +import { useDictStore } from '@/store/modules/dict'; + +const dict = useDictStore(); + +export class State { + + public title = '';//标题 + public id = 0;//ID + public pid = 0;//上级 + public level = 1;//关系树级别 + public tree = null;//关系树 + public categoryId = null;//测试分类 + public description = '';//描述 + public sort = 0;//排序 + public status = 1;//状态 + public createdBy = 0;//创建者 + public createdBySumma?: null | MemberSumma = null;//创建者摘要信息 + public updatedBy = 0;//更新者 + public createdAt = '';//创建时间 + public updatedAt = '';//修改时间 + public deletedAt = '';//删除时间 + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表单验证规则 +export const rules = { + title: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入标题', + }, +}; + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'title', + component: 'NInput', + label: '标题', + componentProps: { + placeholder: '请输入标题', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'categoryId', + component: 'NSelect', + label: '测试分类', + defaultValue: null, + componentProps: { + placeholder: '请选择测试分类', + options: dict.getOption('testCategoryOption'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + defaultValue: null, + componentProps: { + placeholder: '请选择状态', + options: dict.getOption('sys_normal_disable'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +// 表格列 +export const columns = [ + { + title: '标题', + key: 'title', + align: 'left', + width: 100, + }, + { + title: '测试分类', + key: 'categoryId', + align: 'left', + width: 100, + render(row: State) { + return renderOptionTag('testCategoryOption', row.categoryId); + }, + }, + { + title: '状态', + key: 'status', + align: 'left', + width: 150, + render(row: State) { + return renderOptionTag('sys_normal_disable', row.status); + }, + }, + { + title: '创建者', + key: 'createdBy', + align: 'left', + width: 100, + render(row: State) { + return renderPopoverMemberSumma(row.createdBySumma); + }, + }, + { + title: '创建时间', + key: 'createdAt', + align: 'left', + width: 180, + }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['sys_normal_disable', 'testCategoryOption']); +} + +// 关系树选项 +export const treeOption = ref([]); + +// 加载关系树选项 +export function loadTreeOption() { + TreeOption().then((res) => { + treeOption.value = res; + }); +} \ No newline at end of file diff --git a/web/src/views/org/dept/dept.vue b/web/src/views/org/dept/dept.vue new file mode 100644 index 0000000..e1faea6 --- /dev/null +++ b/web/src/views/org/dept/dept.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/web/src/views/org/dept/edit.vue b/web/src/views/org/dept/edit.vue new file mode 100644 index 0000000..c1aacdb --- /dev/null +++ b/web/src/views/org/dept/edit.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/web/src/views/org/dept/model.ts b/web/src/views/org/dept/model.ts new file mode 100644 index 0000000..30fc87e --- /dev/null +++ b/web/src/views/org/dept/model.ts @@ -0,0 +1,201 @@ +import { h, ref } from 'vue'; +import { NTag, NButton } from 'naive-ui'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { validate } from '@/utils/validateUtil'; +import { renderTooltip, renderIcon, renderOptionTag } from '@/utils'; +import { HelpCircleOutline } from '@vicons/ionicons5'; +import { TreeOption } from '@/api/org/dept'; +import { useDictStore } from '@/store/modules/dict'; +import type { FormRules } from 'naive-ui/es/form/src/interface'; + +const dict = useDictStore(); + +export class State { + public id = 0; // 部门ID + public pid = 0; // 父部门ID + public name = ''; // 部门名称 + public code = ''; // 部门编码 + public type = 'company'; // 部门类型 + public leader = ''; // 负责人 + public phone = ''; // 联系电话 + public email = ''; // 邮箱 + public level = 0; // 关系树等级 + public tree = ''; // 关系树 + public sort = 0; // 排序 + public status = 1; // 部门状态 + public createdAt = ''; // 创建时间 + public updatedAt = ''; // 更新时间 + + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表单验证规则 +export const rules: FormRules = { + email: { + required: false, + trigger: ['blur', 'input'], + type: 'string', + validator: validate.email, + }, +}; + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'name', + component: 'NInput', + label: '部门名称', + componentProps: { + placeholder: '请输入部门名称', + onInput: (e: any) => { + console.log(e); + }, + }, + rules: [{ message: '请输入部门名称', trigger: ['blur'] }], + }, + { + field: 'code', + component: 'NInput', + label: '部门编码', + componentProps: { + placeholder: '请输入部门编码', + showButton: false, + onInput: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'leader', + component: 'NInput', + label: '负责人', + componentProps: { + placeholder: '请输入负责人', + showButton: false, + onInput: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +export const filterIds = ref([]); + +// 表格列 +export const columns = [ + { + title(_column) { + return renderTooltip( + h( + NButton, + { + strong: true, + size: 'small', + text: true, + iconPlacement: 'right', + }, + { default: () => '部门名称', icon: renderIcon(HelpCircleOutline) } + ), + '支持上下级部门,点击列表中左侧 > 按钮可展开下级部门列表' + ); + }, + key: 'name', + render(row) { + const filter = filterIds.value.includes(row.id as never); + return h( + NTag, + { + type: 'info', + checkable: filter, + checked: filter, + }, + { + default: () => row.name, + } + ); + }, + width: 200, + }, + { + title: '部门编码', + key: 'code', + width: 100, + }, + { + title: '部门类型', + key: 'type', + align: 'left', + width: 100, + render(row) { + return renderOptionTag('deptType', row.type); + }, + }, + { + title: '负责人', + key: 'leader', + width: 100, + }, + { + title: '联系电话', + key: 'phone', + width: 150, + }, + { + title: '状态', + key: 'status', + align: 'left', + width: 80, + render(row) { + return renderOptionTag('sys_normal_disable', row.status); + }, + }, + { + title: '创建时间', + key: 'createdAt', + width: 150, + }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['sys_normal_disable', 'deptType']); +} + +// 关系树选项 +export const treeOption = ref([]); + +// 加载关系树选项 +export function loadTreeOption() { + TreeOption().then((res) => { + treeOption.value = res; + }); +} diff --git a/web/src/views/org/post/columns.ts b/web/src/views/org/post/columns.ts new file mode 100644 index 0000000..a6992fe --- /dev/null +++ b/web/src/views/org/post/columns.ts @@ -0,0 +1,61 @@ +import { h } from 'vue'; +import { NTag } from 'naive-ui'; + +export const columns = [ + { + title: 'ID', + key: 'id', + width: 100, + }, + { + title: '岗位', + key: 'name', + width: 200, + render(row) { + return h( + NTag, + { + type: 'info', + }, + { + default: () => row.name, + } + ); + }, + }, + { + title: '岗位编码', + key: 'code', + width: 100, + }, + { + title: '状态', + key: 'status', + width: 100, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.status == 1 ? 'info' : 'error', + bordered: false, + }, + { + default: () => (row.status == 1 ? '正常' : '已禁用'), + } + ); + }, + }, + { + title: '备注', + key: 'sort', + width: 150, + }, + { + title: '创建时间', + key: 'createdAt', + width: 180, + }, +]; diff --git a/web/src/views/org/post/post.vue b/web/src/views/org/post/post.vue new file mode 100644 index 0000000..7d61d30 --- /dev/null +++ b/web/src/views/org/post/post.vue @@ -0,0 +1,305 @@ + + + + + diff --git a/web/src/views/org/user/addBalance.vue b/web/src/views/org/user/addBalance.vue new file mode 100644 index 0000000..e552f9b --- /dev/null +++ b/web/src/views/org/user/addBalance.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/web/src/views/org/user/addIntegral.vue b/web/src/views/org/user/addIntegral.vue new file mode 100644 index 0000000..ff28412 --- /dev/null +++ b/web/src/views/org/user/addIntegral.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/web/src/views/org/user/columns.ts b/web/src/views/org/user/columns.ts new file mode 100644 index 0000000..3638eb9 --- /dev/null +++ b/web/src/views/org/user/columns.ts @@ -0,0 +1,148 @@ +import { h } from 'vue'; +import { NAvatar, NTag, NText } from 'naive-ui'; +import { formatBefore } from '@/utils/dateUtil'; + +export const columns = [ + { + title: '管理员ID', + key: 'id', + width: 100, + }, + { + title: '用户名', + key: 'username', + width: 100, + }, + { + title: '姓名', + key: 'realName', + width: 100, + render(row) { + if (row.realName == '') { + return h(NText, { depth: 3 }, { default: () => '未设置' }); + } + return row.realName; + }, + }, + { + title: '头像', + key: 'avatar', + width: 70, + render(row) { + if (row.avatar !== '') { + return h(NAvatar, { + circle: true, + size: 'small', + src: row.avatar, + }); + } else { + return h( + NAvatar, + { + circle: true, + size: 'small', + }, + { + default: () => + row.realName !== '' ? row.realName.substring(0, 1) : row.username.substring(0, 2), + } + ); + } + }, + }, + { + title: '绑定角色', + key: 'roleName', + width: 100, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'success', + bordered: false, + }, + { + default: () => row.roleName, + } + ); + }, + }, + { + title: '所属部门', + key: 'deptName', + width: 100, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'info', + bordered: false, + }, + { + default: () => row.deptName, + } + ); + }, + }, + { + title: '余额', + key: 'balance', + width: 120, + render(row) { + return '¥' + Number(row.balance).toFixed(2); + }, + }, + { + title: '积分', + key: 'integral', + width: 120, + render(row) { + return Number(row.integral).toFixed(2); + }, + }, + { + title: '状态', + key: 'status', + width: 80, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.status == 1 ? 'info' : 'error', + bordered: false, + }, + { + default: () => (row.status == 1 ? '正常' : '已禁用'), + } + ); + }, + }, + { + title: '最近活跃', + key: 'lastActiveAt', + width: 100, + render(row) { + if (row.lastActiveAt === null) { + return '从未登录'; + } + return formatBefore(new Date(row.lastActiveAt)); + }, + }, + { + title: '创建时间', + key: 'createdAt', + width: 180, + render: (rows, _) => { + return rows.createdAt; + }, + }, +]; diff --git a/web/src/views/org/user/list.vue b/web/src/views/org/user/list.vue new file mode 100644 index 0000000..22336e5 --- /dev/null +++ b/web/src/views/org/user/list.vue @@ -0,0 +1,526 @@ + + + + + diff --git a/web/src/views/org/user/model.ts b/web/src/views/org/user/model.ts new file mode 100644 index 0000000..4f18199 --- /dev/null +++ b/web/src/views/org/user/model.ts @@ -0,0 +1,237 @@ +import { cloneDeep } from 'lodash-es'; +import { ref } from 'vue'; +import { getDeptOption } from '@/api/org/dept'; +import { getRoleOption } from '@/api/system/role'; +import { FormSchema, useForm } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { useDictStore, Option } from '@/store/modules/dict'; + +// 增加余额/积分. + +const dict = useDictStore(); + +export interface addState { + id: number; + username: string; + realName: string; + integral: number; + balance: number; + operateMode: number; + num: number | null; +} + +export const addDefaultState = { + id: 0, + realName: '', + username: '', + integral: 0, + balance: 0, + operateMode: 1, + num: null, +}; + +export function addNewState(state: addState | null): addState { + if (state !== null) { + return cloneDeep(state); + } + return cloneDeep(addDefaultState); +} + +export const operateModes = [ + { + value: 1, + label: '加款', + }, + { + value: 2, + label: '扣款', + }, +]; + +export const addRules = {}; + +// 用户列表. + +export const defaultState = { + id: 0, + roleId: null, + realName: '', + username: '', + password: '', + deptId: null, + postIds: null, + mobile: '', + email: '', + sex: 1, + leader: '', + phone: '', + sort: 0, + status: 1, + createdAt: '', + updatedAt: '', +}; + +export interface State { + id: number; + roleId: number | null; + realName: string; + username: string; + password: string; + deptId: number | null; + postIds: any; + mobile: string; + email: string; + sex: number; + leader: string; + phone: string; + sort: number; + status: number; + createdAt: string; + updatedAt: string; +} + +const schemas: FormSchema[] = [ + { + field: 'username', + component: 'NInput', + label: '用户名', + componentProps: { + placeholder: '请输入用户名', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + rules: [{ message: '请输入用户名', trigger: ['blur'] }], + }, + { + field: 'realName', + component: 'NInput', + label: '姓名', + componentProps: { + placeholder: '请输入姓名', + showButton: false, + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'mobile', + component: 'NInputNumber', + label: '手机号', + componentProps: { + placeholder: '请输入手机号码', + showButton: false, + onInput: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'email', + component: 'NInput', + label: '邮箱', + componentProps: { + placeholder: '请输入邮箱地址', + onInput: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'id', + component: 'NInput', + label: '用户ID', + componentProps: { + placeholder: '请输入用户ID', + onInput: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'pid', + component: 'NInput', + label: '上级ID', + componentProps: { + placeholder: '请输入上级用户ID', + onInput: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + defaultValue: null, + componentProps: { + placeholder: '请选择类型', + options: dict.getOption('sys_normal_disable'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'created_at', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]; + +export const [register, {}] = useForm({ + gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' }, + labelWidth: 80, + schemas, +}); + +export const deptTreeOptions = ref([]); +export const roleTreeOptions = ref([]); + +export async function loadOptions() { + dict.loadOptions(['adminPostOption', 'sys_user_sex', 'sys_normal_disable']); + + getDeptOption().then((res) => { + if (res.list) { + deptTreeOptions.value = res.list; + } + }); + + getRoleOption().then((res) => { + if (res.list) { + roleTreeOptions.value = res.list; + registerRoleTabsOption(); + } + }); +} + +// 将角色Tabs选项注册到字典 +function registerRoleTabsOption() { + const items = [{ id: -1, name: '全部' }]; + treeDataToCompressed(items, roleTreeOptions.value); + + const roleTabs: Option[] = []; + for (const item of items) { + roleTabs.push(dict.genOption(item.id, item.name)); + } + dict.setOption('roleTabs', roleTabs); +} + +function treeDataToCompressed(items: any[], source: any) { + for (const i in source) { + items.push(source[i]); + source[i].children && source[i].children.length > 0 + ? treeDataToCompressed(items, source[i].children) + : ''; // 子级递归 + } + return items; +} diff --git a/web/src/views/org/user/user.vue b/web/src/views/org/user/user.vue new file mode 100644 index 0000000..1f3986d --- /dev/null +++ b/web/src/views/org/user/user.vue @@ -0,0 +1,46 @@ + + + diff --git a/web/src/views/permission/menu/addModal.vue b/web/src/views/permission/menu/addModal.vue new file mode 100644 index 0000000..26f49c6 --- /dev/null +++ b/web/src/views/permission/menu/addModal.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/web/src/views/permission/menu/editForm.vue b/web/src/views/permission/menu/editForm.vue new file mode 100644 index 0000000..efa31b5 --- /dev/null +++ b/web/src/views/permission/menu/editForm.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/web/src/views/permission/menu/menu.vue b/web/src/views/permission/menu/menu.vue new file mode 100644 index 0000000..7da0a46 --- /dev/null +++ b/web/src/views/permission/menu/menu.vue @@ -0,0 +1,215 @@ + + diff --git a/web/src/views/permission/menu/menuModal.vue b/web/src/views/permission/menu/menuModal.vue new file mode 100644 index 0000000..16405bc --- /dev/null +++ b/web/src/views/permission/menu/menuModal.vue @@ -0,0 +1,43 @@ + + + diff --git a/web/src/views/permission/menu/model.ts b/web/src/views/permission/menu/model.ts new file mode 100644 index 0000000..44929ee --- /dev/null +++ b/web/src/views/permission/menu/model.ts @@ -0,0 +1,102 @@ +import { cloneDeep } from 'lodash-es'; +import { useDictStore } from '@/store/modules/dict'; + +const dict = useDictStore(); + +export interface State { + id: number; + pid?: number | null; + title: string; + name: string; + path: string; + label: string; + icon: string; + type: number; + redirect: string; + permissions: string; + permissionName: string; + component?: string | null; + alwaysShow: number; + activeMenu: string; + isRoot: number; + isFrame: number; + frameSrc: string; + keepAlive: number; + hidden: number; + affix: number; + status: number; + sort: number; + disabled: boolean; + children?: State[] | null; +} + +export const defaultState: State = { + id: 0, + pid: 0, + title: '', + name: '', + path: '', + label: '', + icon: '', + type: 1, + redirect: '', + permissions: '', + permissionName: '', + component: null, + alwaysShow: 2, + activeMenu: '', + isRoot: 2, + isFrame: 2, + frameSrc: '', + keepAlive: 2, + hidden: 2, + affix: 2, + status: 1, + sort: 10, + disabled: false, + children: null, +}; + +export function newState(state: State | null): State { + if (state !== null) { + return defaultValueCheck(cloneDeep(state)); + } + return defaultValueCheck(cloneDeep(defaultState)); +} + +// 默认值校正,主要为了解决历史数据格式不规范问题 +export function defaultValueCheck(state: State): State { + if (!state) { + state = newState(null); + } + if (!state.pid || state.pid < 1) { + state.pid = null; + } + if (state.alwaysShow != 1) { + state.alwaysShow = 2; + } + if (state.isRoot != 1) { + state.isRoot = 2; + } + if (state.isFrame != 1) { + state.isFrame = 2; + } + if (state.keepAlive != 1) { + state.keepAlive = 2; + } + if (state.hidden != 1) { + state.hidden = 2; + } + if (state.affix != 1) { + state.affix = 2; + } + if (state.status != 1) { + state.status = 2; + } + return state; +} + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['sys_menu_types', 'sys_menu_component', 'sys_normal_disable', 'sys_switch']); +} diff --git a/web/src/views/permission/role/columns.ts b/web/src/views/permission/role/columns.ts new file mode 100644 index 0000000..0d3caf3 --- /dev/null +++ b/web/src/views/permission/role/columns.ts @@ -0,0 +1,100 @@ +import { h } from 'vue'; +import { NTag, NButton } from 'naive-ui'; +import { HelpCircleOutline } from '@vicons/ionicons5'; +import { renderTooltip, renderIcon } from '@/utils'; +export const columns = [ + // { + // title: '角色ID', + // key: 'id', + // }, + { + title(_column) { + return renderTooltip( + h( + NButton, + { + strong: true, + size: 'small', + text: true, + iconPlacement: 'right', + }, + { default: () => '角色', icon: renderIcon(HelpCircleOutline) } + ), + '支持上下级角色,点击列表中左侧 > 按钮可展开下级角色列表' + ); + }, + key: 'name', + render(row) { + return h( + NTag, + { + type: 'info', + }, + { + default: () => row.name, + } + ); + }, + width: 200, + }, + { + title: '角色编码', + key: 'key', + width: 150, + }, + // { + // title: '上级角色', + // key: 'pid', + // }, + // { + // title: '默认角色', + // key: 'isDefault', + // render(row) { + // return h( + // NTag, + // { + // type: row.id == 1 ? 'success' : 'error', + // }, + // { + // default: () => (row.id == 1 ? '是' : '否'), + // } + // ); + // }, + // width: 80, + // }, + // { + // title: '排序', + // key: 'sort', + // width: 100, + // }, + { + title: '备注', + key: 'remark', + width: 180, + }, + { + title: '状态', + key: 'status', + width: 80, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.status == 1 ? 'info' : 'error', + bordered: false, + }, + { + default: () => (row.status == 1 ? '正常' : '已禁用'), + } + ); + }, + }, + { + title: '创建时间', + key: 'createdAt', + width: 180, + }, +]; diff --git a/web/src/views/permission/role/editDataAuth.vue b/web/src/views/permission/role/editDataAuth.vue new file mode 100644 index 0000000..ccaaf8b --- /dev/null +++ b/web/src/views/permission/role/editDataAuth.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/web/src/views/permission/role/editMenuAuth.vue b/web/src/views/permission/role/editMenuAuth.vue new file mode 100644 index 0000000..44710fb --- /dev/null +++ b/web/src/views/permission/role/editMenuAuth.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/web/src/views/permission/role/editRole.vue b/web/src/views/permission/role/editRole.vue new file mode 100644 index 0000000..efa2b69 --- /dev/null +++ b/web/src/views/permission/role/editRole.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/web/src/views/permission/role/model.ts b/web/src/views/permission/role/model.ts new file mode 100644 index 0000000..7fd503c --- /dev/null +++ b/web/src/views/permission/role/model.ts @@ -0,0 +1,40 @@ +import { cloneDeep } from 'lodash-es'; + +export class State { + id = 0; + name = ''; + key = ''; + dataScope = 1; + customDept: any[] = []; + pid = 0; + level = 1; + tree = ''; + remark = ''; + sort = 0; + status = 1; + createdAt = ''; + updatedAt = ''; + label = ''; + value = 0; + children: State[] | null = null; + + constructor(state?: Partial) { + if (state) { + Object.keys(this).forEach((key) => { + if (state[key] !== undefined) { + this[key] = state[key]; + } + }); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} diff --git a/web/src/views/permission/role/role.vue b/web/src/views/permission/role/role.vue new file mode 100644 index 0000000..fab8c6d --- /dev/null +++ b/web/src/views/permission/role/role.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/web/src/views/redirect/index.vue b/web/src/views/redirect/index.vue new file mode 100644 index 0000000..175e0ae --- /dev/null +++ b/web/src/views/redirect/index.vue @@ -0,0 +1,22 @@ + diff --git a/web/src/views/system/blacklist/index.vue b/web/src/views/system/blacklist/index.vue new file mode 100644 index 0000000..00e6fbc --- /dev/null +++ b/web/src/views/system/blacklist/index.vue @@ -0,0 +1,344 @@ + + + + + diff --git a/web/src/views/system/config/BasicSetting.vue b/web/src/views/system/config/BasicSetting.vue new file mode 100644 index 0000000..13c8338 --- /dev/null +++ b/web/src/views/system/config/BasicSetting.vue @@ -0,0 +1,142 @@ + + + diff --git a/web/src/views/system/config/CashSetting.vue b/web/src/views/system/config/CashSetting.vue new file mode 100644 index 0000000..c005e4c --- /dev/null +++ b/web/src/views/system/config/CashSetting.vue @@ -0,0 +1,102 @@ + + + diff --git a/web/src/views/system/config/EmailSetting.vue b/web/src/views/system/config/EmailSetting.vue new file mode 100644 index 0000000..51707a7 --- /dev/null +++ b/web/src/views/system/config/EmailSetting.vue @@ -0,0 +1,210 @@ + + + diff --git a/web/src/views/system/config/GeoSetting.vue b/web/src/views/system/config/GeoSetting.vue new file mode 100644 index 0000000..365883c --- /dev/null +++ b/web/src/views/system/config/GeoSetting.vue @@ -0,0 +1,85 @@ + + + diff --git a/web/src/views/system/config/LoginSetting.vue b/web/src/views/system/config/LoginSetting.vue new file mode 100644 index 0000000..998ec64 --- /dev/null +++ b/web/src/views/system/config/LoginSetting.vue @@ -0,0 +1,208 @@ + + + diff --git a/web/src/views/system/config/PaySetting.vue b/web/src/views/system/config/PaySetting.vue new file mode 100644 index 0000000..f7715fb --- /dev/null +++ b/web/src/views/system/config/PaySetting.vue @@ -0,0 +1,237 @@ + + + diff --git a/web/src/views/system/config/RevealSetting.vue b/web/src/views/system/config/RevealSetting.vue new file mode 100644 index 0000000..363997b --- /dev/null +++ b/web/src/views/system/config/RevealSetting.vue @@ -0,0 +1,193 @@ + + + diff --git a/web/src/views/system/config/SmsSetting.vue b/web/src/views/system/config/SmsSetting.vue new file mode 100644 index 0000000..9c1755d --- /dev/null +++ b/web/src/views/system/config/SmsSetting.vue @@ -0,0 +1,322 @@ + + + diff --git a/web/src/views/system/config/ThemeSetting.vue b/web/src/views/system/config/ThemeSetting.vue new file mode 100644 index 0000000..08baab2 --- /dev/null +++ b/web/src/views/system/config/ThemeSetting.vue @@ -0,0 +1,83 @@ + + + diff --git a/web/src/views/system/config/UploadSetting.vue b/web/src/views/system/config/UploadSetting.vue new file mode 100644 index 0000000..2ad5573 --- /dev/null +++ b/web/src/views/system/config/UploadSetting.vue @@ -0,0 +1,409 @@ + + + diff --git a/web/src/views/system/config/WechatSetting.vue b/web/src/views/system/config/WechatSetting.vue new file mode 100644 index 0000000..d14fe2b --- /dev/null +++ b/web/src/views/system/config/WechatSetting.vue @@ -0,0 +1,121 @@ + + + diff --git a/web/src/views/system/config/system.vue b/web/src/views/system/config/system.vue new file mode 100644 index 0000000..81b8451 --- /dev/null +++ b/web/src/views/system/config/system.vue @@ -0,0 +1,206 @@ + + + diff --git a/web/src/views/system/cron/columns.ts b/web/src/views/system/cron/columns.ts new file mode 100644 index 0000000..35f5c33 --- /dev/null +++ b/web/src/views/system/cron/columns.ts @@ -0,0 +1,93 @@ +import { h } from 'vue'; +import { NTag } from 'naive-ui'; + +const policyOptions = { + 1: '并行策略', + 2: '单例策略', + 3: '单次策略', + 4: '多次策略', +}; + +export const columns = [ + { + title: 'ID', + key: 'id', + width: 80, + }, + { + title: '任务标题', + key: 'title', + width: 150, + }, + { + title: '任务分组', + key: 'groupName', + width: 100, + }, + { + title: '执行方法', + key: 'name', + width: 100, + }, + // { + // title: '执行参数', + // key: 'params', + // render(row) { + // return row.params; + // }, + // }, + { + title: '执行策略', + key: 'policy', + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'info', + bordered: false, + }, + { + default: () => policyOptions[row.policy] ?? '未知', + } + ); + }, + width: 100, + }, + { + title: '表达式', + key: 'pattern', + width: 150, + }, + // { + // title: '执行次数', + // key: 'count', + // }, + { + title: '状态', + key: 'status', + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.status == 1 ? 'success' : 'warning', + bordered: false, + }, + { + default: () => (row.status == 1 ? '运行中' : '已结束'), + } + ); + }, + width: 100, + }, + { + title: '创建时间', + key: 'createdAt', + width: 180, + }, +]; diff --git a/web/src/views/system/cron/index.vue b/web/src/views/system/cron/index.vue new file mode 100644 index 0000000..4195c5b --- /dev/null +++ b/web/src/views/system/cron/index.vue @@ -0,0 +1,554 @@ + + + + + diff --git a/web/src/views/system/cron/modal/columns.ts b/web/src/views/system/cron/modal/columns.ts new file mode 100644 index 0000000..7774dcf --- /dev/null +++ b/web/src/views/system/cron/modal/columns.ts @@ -0,0 +1,83 @@ +import { h } from 'vue'; +import { NTag } from 'naive-ui'; + +export const columns = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 100, + }, + { + title: '分组名称', + dataIndex: 'name', + key: 'name', + width: 150, + }, + { + title: '上级分组', + dataIndex: 'supName', + key: 'supName', + width: 150, + render(row) { + if (row.supName == ''){ + return '顶级分组'; + } + return row.supName; + }, + }, + { + title: '默认', + dataIndex: 'isDefault', + key: 'isDefault', + render(row) { + return row.isDefault === 1 ? '是' : '否'; + }, + width: 80, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.status === 1 ? 'info' : 'error', + bordered: false, + }, + { + default: () => (row.status === 1 ? '正常' : '禁用'), + } + ); + }, + width: 150, + }, + // { + // title: '排序', + // dataIndex: 'sort', + // key: 'sort', + // width: 100, + // }, + { + title: '备注', + dataIndex: 'remark', + key: 'remark', + width: 150, + }, + // { + // title: '更新时间', + // dataIndex: 'updatedAt', + // key: 'updatedAt', + // width: 160, + // }, + { + title: '创建时间', + dataIndex: 'createdAt', + key: 'createdAt', + width: 160, + }, +]; diff --git a/web/src/views/system/cron/modal/index.vue b/web/src/views/system/cron/modal/index.vue new file mode 100644 index 0000000..8e6a1df --- /dev/null +++ b/web/src/views/system/cron/modal/index.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/web/src/views/system/cron/modal/modal.vue b/web/src/views/system/cron/modal/modal.vue new file mode 100644 index 0000000..d851f78 --- /dev/null +++ b/web/src/views/system/cron/modal/modal.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/web/src/views/system/dict/CreateDrawer.vue b/web/src/views/system/dict/CreateDrawer.vue new file mode 100644 index 0000000..5e8da0d --- /dev/null +++ b/web/src/views/system/dict/CreateDrawer.vue @@ -0,0 +1,132 @@ + + + diff --git a/web/src/views/system/dict/columns.ts b/web/src/views/system/dict/columns.ts new file mode 100644 index 0000000..8dfaa4d --- /dev/null +++ b/web/src/views/system/dict/columns.ts @@ -0,0 +1,74 @@ +import { h } from 'vue'; +import { NTag } from 'naive-ui'; + +export const columns = [ + { + title: '字典ID', + key: 'id', + }, + { + title: '字典类型', + key: 'type', + }, + { + title: '字典标签', + key: 'type', + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.listClass, + bordered: false, + }, + { + default: () => row.label, + } + ); + }, + }, + { + title: '字典键值', + key: 'value', + }, + { + title: '键值类型', + key: 'valueType', + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'default', + bordered: false, + }, + { + default: () => row.valueType, + } + ); + }, + }, + { + title: '状态', + key: 'status', + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: row.status == 1 ? 'success' : 'warning', + bordered: false, + }, + { + default: () => (row.status == 1 ? '正常' : '隐藏'), + } + ); + }, + }, +]; diff --git a/web/src/views/system/dict/index.vue b/web/src/views/system/dict/index.vue new file mode 100644 index 0000000..fbcbc7b --- /dev/null +++ b/web/src/views/system/dict/index.vue @@ -0,0 +1,217 @@ + + diff --git a/web/src/views/system/dict/list.vue b/web/src/views/system/dict/list.vue new file mode 100644 index 0000000..552ddde --- /dev/null +++ b/web/src/views/system/dict/list.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/web/src/views/system/dict/model.ts b/web/src/views/system/dict/model.ts new file mode 100644 index 0000000..de6ccf2 --- /dev/null +++ b/web/src/views/system/dict/model.ts @@ -0,0 +1,18 @@ +export class State { + typeId = 0; + key = ''; + id = 0; + label = ''; + value = ''; + valueType = 'string'; + type = ''; + listClass = 'primary'; + isDefault = 0; + sort = 0; + remark = ''; + status = 1; + createdAt = ''; + updatedAt = ''; + pid: number | null = 0; + name = ''; +} diff --git a/web/src/views/testCategory/edit.vue b/web/src/views/testCategory/edit.vue new file mode 100644 index 0000000..d9f267f --- /dev/null +++ b/web/src/views/testCategory/edit.vue @@ -0,0 +1,157 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/testCategory/index.vue b/web/src/views/testCategory/index.vue new file mode 100644 index 0000000..a1b4dbd --- /dev/null +++ b/web/src/views/testCategory/index.vue @@ -0,0 +1,193 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/testCategory/model.ts b/web/src/views/testCategory/model.ts new file mode 100644 index 0000000..ff7bd36 --- /dev/null +++ b/web/src/views/testCategory/model.ts @@ -0,0 +1,153 @@ +import { h, ref } from 'vue'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { defRangeShortcuts } from '@/utils/dateUtil'; +import { renderOptionTag } from '@/utils'; +import { useDictStore } from '@/store/modules/dict'; + +const dict = useDictStore(); + +export class State { + public id = 0; // 分类ID + public name = ''; // 分类名称 + public shortName = ''; // 简称 + public description = ''; // 描述 + public sort = 0; // 排序 + public status = 1; // 状态 + public remark = ''; // 备注 + public createdAt = ''; // 创建时间 + public updatedAt = ''; // 修改时间 + public deletedAt = ''; // 删除时间 + + constructor(state?: Partial) { + if (state) { + Object.assign(this, state); + } + } +} + +export function newState(state: State | Record | null): State { + if (state !== null) { + if (state instanceof State) { + return cloneDeep(state); + } + return new State(state); + } + return new State(); +} + +// 表单验证规则 +export const rules = { + name: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入分类名称', + }, + sort: { + required: true, + trigger: ['blur', 'input'], + type: 'number', + message: '请输入排序', + }, +}; + +// 表格搜索表单 +export const schemas = ref([ + { + field: 'id', + component: 'NInputNumber', + label: '分类ID', + componentProps: { + placeholder: '请输入分类ID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'name', + component: 'NInput', + label: '分类名称', + componentProps: { + placeholder: '请输入分类名称', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + defaultValue: null, + componentProps: { + placeholder: '请选择状态', + options: dict.getOption('sys_normal_disable'), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +// 表格列 +export const columns = [ + { + title: '分类ID', + key: 'id', + align: 'left', + width: 80, + }, + { + title: '分类名称', + key: 'name', + align: 'left', + width: -1, + }, + { + title: '简称', + key: 'shortName', + align: 'left', + width: 80, + }, + { + title: '描述', + key: 'description', + align: 'left', + width: 300, + }, + { + title: '状态', + key: 'status', + align: 'left', + width: -1, + render(row: State) { + return renderOptionTag('sys_normal_disable', row.status); + }, + }, + { + title: '创建时间', + key: 'createdAt', + align: 'left', + width: 180, + }, +]; + +// 加载字典数据选项 +export function loadOptions() { + dict.loadOptions(['sys_normal_disable']); +} \ No newline at end of file diff --git a/web/stylelint.config.js b/web/stylelint.config.js new file mode 100644 index 0000000..06951ed --- /dev/null +++ b/web/stylelint.config.js @@ -0,0 +1,70 @@ +module.exports = { + root: true, + plugins: ['stylelint-order'], + extends: ['stylelint-config-standard', 'stylelint-config-prettier'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global'], + }, + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep'], + }, + ], + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'if', + 'each', + 'include', + 'mixin', + ], + }, + ], + 'no-empty-source': null, + 'named-grid-areas-no-invalid': null, + 'unicode-bom': 'never', + 'no-descending-specificity': null, + 'font-family-no-missing-generic-family-keyword': null, + 'declaration-colon-space-after': 'always-single-line', + 'declaration-colon-space-before': 'never', + // 'declaration-block-trailing-semicolon': 'always', + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'], + }, + ], + 'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }], + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + type: 'at-rule', + name: 'supports', + }, + { + type: 'at-rule', + name: 'media', + }, + 'rules', + ], + { severity: 'warning' }, + ], + }, + ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], +}; diff --git a/web/tailwind.config.js b/web/tailwind.config.js new file mode 100644 index 0000000..ed05a3d --- /dev/null +++ b/web/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + content: ['./index.html', './src/**/*.{vue,ts,tsx}'], + important: true, + theme: { + extend: {}, + }, +}; diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..12b3587 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,60 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "jsx": "preserve", + "baseUrl": ".", + "allowJs": true, + "sourceMap": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "experimentalDecorators": true, + "lib": [ + "dom", + "esnext" + ], + "types": [ + "vite/client" + ], + "typeRoots": [ + "./node_modules/@types/", + "./types" + ], + "noImplicitAny": false, + "skipLibCheck": true, + "paths": { + "@/*": [ + "src/*" + ], + "/#/*": [ + "types/*" + ] + } + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "types/**/*.d.ts", + "types/**/*.ts", + "build/**/*.ts", + "build/**/*.d.ts", + "mock/**/*.ts", + "components.d.ts", + "auto-imports.d.ts", + "vite.config.ts" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.js" + ] +} diff --git a/web/types/config.d.ts b/web/types/config.d.ts new file mode 100644 index 0000000..f326389 --- /dev/null +++ b/web/types/config.d.ts @@ -0,0 +1,71 @@ +export interface ProjectSettingState { + //导航模式 + navMode: string; + //导航风格 + navTheme: string; + //顶部设置 + headerSetting: object; + //页脚 + showFooter: boolean; + //菜单设置 + menuSetting: object; + //多标签 + multiTabsSetting: object; + //面包屑 + crumbsSetting: object; + //权限模式 + permissionMode: string; +} + +export interface IbodySetting { + fixed: boolean; +} + +export interface IheaderSetting { + bgColor: string; + fixed: boolean; + isReload: boolean; +} + +export interface ImenuSetting { + minMenuWidth: number; + menuWidth: number; + fixed: boolean; + mixMenu: boolean; + collapsed: boolean; + mobileWidth: number; +} + +export interface IcrumbsSetting { + show: boolean; + showIcon: boolean; +} + +export interface ImultiTabsSetting { + bgColor: string; + fixed: boolean; + show: boolean; +} +export interface GlobConfig { + title: string; + apiUrl: string; + shortName: string; + urlPrefix?: string; + uploadUrl?: string; + imgUrl?: string; +} + +export interface GlobEnvConfig { + // 标题 + VITE_GLOB_APP_TITLE: string; + // 接口地址 + VITE_GLOB_API_URL: string; + // 接口前缀 + VITE_GLOB_API_URL_PREFIX?: string; + // Project abbreviation + VITE_GLOB_APP_SHORT_NAME: string; + // 图片上传地址 + VITE_GLOB_UPLOAD_URL?: string; + //图片前缀地址 + VITE_GLOB_IMG_URL?: string; +} diff --git a/web/types/global.d.ts b/web/types/global.d.ts new file mode 100644 index 0000000..2506c61 --- /dev/null +++ b/web/types/global.d.ts @@ -0,0 +1,100 @@ +import type { + ComponentRenderProxy, + VNode, + VNodeChild, + ComponentPublicInstance, + FunctionalComponent, + PropType as VuePropType, +} from 'vue'; + +declare global { + const __APP_INFO__: { + pkg: { + name: string; + version: string; + dependencies: Recordable; + devDependencies: Recordable; + }; + lastBuildTime: string; + }; + // declare interface Window { + // // Global vue app instance + // __APP__: App; + // } + + // vue + declare type PropType = VuePropType; + declare type VueNode = VNodeChild | JSX.Element; + + export type Writable = { + -readonly [P in keyof T]: T[P]; + }; + + declare type Nullable = T | null; + declare type NonNullable = T extends null | undefined ? never : T; + declare type Recordable = Record; + declare type ReadonlyRecordable = { + readonly [key: string]: T; + }; + declare type Indexable = { + [key: string]: T; + }; + declare type DeepPartial = { + [P in keyof T]?: DeepPartial; + }; + declare type TimeoutHandle = ReturnType; + declare type IntervalHandle = ReturnType; + + declare interface ChangeEvent extends Event { + target: HTMLInputElement; + } + + declare interface WheelEvent { + path?: EventTarget[]; + } + + interface ImportMetaEnv extends ViteEnv { + __: unknown; + } + + declare interface ViteEnv { + VITE_PORT: number; + VITE_PUBLIC_PATH: string; + VITE_GLOB_APP_TITLE: string; + VITE_GLOB_APP_SHORT_NAME: string; + VITE_DROP_CONSOLE: boolean; + VITE_GLOB_IMG_URL: string; + VITE_PROXY: [string, string][]; + VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'; + VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean; + } + + declare function parseInt(s: string | number, radix?: number): number; + + declare function parseFloat(string: string | number): number; + + namespace JSX { + // tslint:disable no-empty-interface + type Element = VNode; + // tslint:disable no-empty-interface + type ElementClass = ComponentRenderProxy; + + interface ElementAttributesProperty { + $props: any; + } + + interface IntrinsicElements { + [elem: string]: any; + } + + interface IntrinsicAttributes { + [elem: string]: any; + } + } +} + +declare module 'vue' { + export type JSXComponent = + | { new (): ComponentPublicInstance } + | FunctionalComponent; +} diff --git a/web/types/index.d.ts b/web/types/index.d.ts new file mode 100644 index 0000000..85d2492 --- /dev/null +++ b/web/types/index.d.ts @@ -0,0 +1,28 @@ +declare interface Fn { + (...arg: T[]): R; +} + +declare interface PromiseFn { + (...arg: T[]): Promise; +} + +declare type RefType = T | null; + +declare type LabelValueOptions = { + label: string; + value: any; + disabled: boolean; + [key: string]: string | number | boolean; +}[]; + +declare type EmitType = (event: string, ...args: any[]) => void; + +declare type TargetContext = '_self' | '_blank'; + +declare interface ComponentElRef { + $el: T; +} + +declare type ComponentRef = ComponentElRef | null; + +declare type ElRef = Nullable; diff --git a/web/types/modules.d.ts b/web/types/modules.d.ts new file mode 100644 index 0000000..b57b3cf --- /dev/null +++ b/web/types/modules.d.ts @@ -0,0 +1,10 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue'; + const Component: DefineComponent<{}, {}, any>; + export default Component; +} + +declare module 'virtual:*' { + const result: any; + export default result; +} diff --git a/web/types/utils.d.ts b/web/types/utils.d.ts new file mode 100644 index 0000000..6500d44 --- /dev/null +++ b/web/types/utils.d.ts @@ -0,0 +1,5 @@ +import type { ComputedRef, Ref } from 'vue'; + +export type DynamicProps = { + [P in keyof T]: Ref | T[P] | ComputedRef; +}; diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 0000000..831dcd1 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,83 @@ +import type { UserConfig, ConfigEnv } from 'vite'; +import { loadEnv } from 'vite'; +import { resolve } from 'path'; +import { wrapperEnv } from './build/utils'; +import { createVitePlugins } from './build/vite/plugin'; +import { OUTPUT_DIR } from './build/constant'; +import { createProxy } from './build/vite/proxy'; +import pkg from './package.json'; +import { format } from 'date-fns'; +const { dependencies, devDependencies, name, version } = pkg; + +const __APP_INFO__ = { + pkg: { dependencies, devDependencies, name, version }, + lastBuildTime: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), +}; + +function pathResolve(dir: string) { + return resolve(process.cwd(), '.', dir); +} + +export default ({ command, mode }: ConfigEnv): UserConfig => { + const root = process.cwd(); + const env = loadEnv(mode, root); + const viteEnv = wrapperEnv(env); + const { VITE_PUBLIC_PATH, VITE_PORT, VITE_PROXY } = viteEnv; + const isBuild = command === 'build'; + return { + base: VITE_PUBLIC_PATH, + esbuild: {}, + resolve: { + alias: [ + { + find: /\/#\//, + replacement: pathResolve('types') + '/', + }, + { + find: '@', + replacement: pathResolve('src') + '/', + }, + ], + dedupe: ['vue'], + }, + plugins: createVitePlugins(viteEnv, isBuild), + define: { + __APP_INFO__: JSON.stringify(__APP_INFO__), + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true, + }, + css: { + preprocessorOptions: { + less: { + modifyVars: {}, + javascriptEnabled: true, + additionalData: `@import "src/styles/var.less";`, + }, + }, + }, + server: { + host: true, + port: VITE_PORT, + proxy: createProxy(VITE_PROXY), + // proxy: { + // '/api': { + // target: '', + // changeOrigin: true, + // rewrite: (path) => path.replace(/^\/api/, '/api/v1') + // } + // } + }, + optimizeDeps: { + include: [], + exclude: ['vue-demi'], + }, + build: { + target: 'es2015', + cssTarget: 'chrome80', + outDir: OUTPUT_DIR, + reportCompressedSize: false, + chunkSizeWarningLimit: 3000, + }, + }; +};