diff --git a/package.json b/package.json index 8a84556d..7f486d9d 100644 --- a/package.json +++ b/package.json @@ -62,9 +62,11 @@ "nprogress": "0.2.0", "pinia": "3.0.0", "tailwind-merge": "3.0.1", + "tinymce": "^7", "vue": "3.5.13", "vue-draggable-plus": "0.6.0", "vue-i18n": "11.1.1", + "vue-json-pretty": "^2.4.0", "vue-router": "4.5.0" }, "devDependencies": { diff --git a/src/components/custom/dict-tag.vue b/src/components/custom/dict-tag.vue index e3a41139..906cc3d8 100644 --- a/src/components/custom/dict-tag.vue +++ b/src/components/custom/dict-tag.vue @@ -2,11 +2,12 @@ import { computed, useAttrs } from 'vue'; import type { TagProps } from 'naive-ui'; import { useDict } from '@/hooks/business/dict'; +import { isNotNull } from '@/utils/common'; defineOptions({ name: 'DictTag' }); interface Props { - value?: string; + value?: string | number; dictCode?: string; immediate?: boolean; dictData?: Api.System.DictData; @@ -26,10 +27,10 @@ const dictTagData = computed(() => { if (props.dictData) { return props.dictData; } - - if (props.dictCode && props.value) { + // 避免 props.value 为 0 时,无法触发 + if (props.dictCode && isNotNull(props.value)) { const { transformDictData } = useDict(props.dictCode, props.immediate); - return transformDictData(props.value); + return transformDictData(String(props.value)); } return null; diff --git a/src/components/custom/json-preview.vue b/src/components/custom/json-preview.vue new file mode 100644 index 00000000..efae49be --- /dev/null +++ b/src/components/custom/json-preview.vue @@ -0,0 +1,93 @@ + + + + + + + + {{ props.data }} + 暂无数据 + + + + diff --git a/src/hooks/common/table.ts b/src/hooks/common/table.ts index 63cf6517..765d3d2a 100644 --- a/src/hooks/common/table.ts +++ b/src/hooks/common/table.ts @@ -16,7 +16,7 @@ export function useTable(config: NaiveUI.NaiveTabl const isMobile = computed(() => appStore.isMobile); - const { apiFn, apiParams, immediate, showTotal } = config; + const { apiFn, apiParams, immediate, showTotal = true } = config; const SELECTION_KEY = '__selection__'; diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index 65f369b8..e3bd9318 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -176,7 +176,10 @@ const local: App.I18n.Schema = { system_tenant: 'Tenant Management', system_config: 'Config Management', system_dept: 'Dept Management', - system_post: 'Post Management' + system_post: 'Post Management', + monitor: 'Monitor Management', + 'monitor_login-infor': 'Login Log', + 'monitor_oper-log': 'Operate Log' }, page: { login: { diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 43ea8b1d..363d1d23 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -176,7 +176,10 @@ const local: App.I18n.Schema = { system_tenant: '租户管理', system_config: '参数设置', system_dept: '部门管理', - system_post: '岗位管理' + system_post: '岗位管理', + monitor: '系统监控', + 'monitor_login-infor': '登录日志', + 'monitor_oper-log': '操作日志' }, page: { login: { diff --git a/src/router/elegant/imports.ts b/src/router/elegant/imports.ts index 00df680d..6acfa124 100644 --- a/src/router/elegant/imports.ts +++ b/src/router/elegant/imports.ts @@ -21,6 +21,8 @@ export const views: Record Promise import("@/views/_builtin/iframe-page/[url].vue"), login: () => import("@/views/_builtin/login/index.vue"), home: () => import("@/views/home/index.vue"), + "monitor_login-infor": () => import("@/views/monitor/login-infor/index.vue"), + "monitor_oper-log": () => import("@/views/monitor/oper-log/index.vue"), system_config: () => import("@/views/system/config/index.vue"), system_dept: () => import("@/views/system/dept/index.vue"), system_dict_data: () => import("@/views/system/dict/data/index.vue"), diff --git a/src/router/elegant/routes.ts b/src/router/elegant/routes.ts index 0078f7d4..89ec6920 100644 --- a/src/router/elegant/routes.ts +++ b/src/router/elegant/routes.ts @@ -75,6 +75,35 @@ export const generatedRoutes: GeneratedRoute[] = [ hideInMenu: true } }, + { + name: 'monitor', + path: '/monitor', + component: 'layout.base', + meta: { + title: 'monitor', + i18nKey: 'route.monitor' + }, + children: [ + { + name: 'monitor_login-infor', + path: '/monitor/login-infor', + component: 'view.monitor_login-infor', + meta: { + title: 'monitor_login-infor', + i18nKey: 'route.monitor_login-infor' + } + }, + { + name: 'monitor_oper-log', + path: '/monitor/oper-log', + component: 'view.monitor_oper-log', + meta: { + title: 'monitor_oper-log', + i18nKey: 'route.monitor_oper-log' + } + } + ] + }, { name: 'system', path: '/system', diff --git a/src/router/elegant/transform.ts b/src/router/elegant/transform.ts index 883dc97f..d2f710ba 100644 --- a/src/router/elegant/transform.ts +++ b/src/router/elegant/transform.ts @@ -169,6 +169,9 @@ const routeMap: RouteMap = { "home": "/home", "iframe-page": "/iframe-page/:url", "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?", + "monitor": "/monitor", + "monitor_login-infor": "/monitor/login-infor", + "monitor_oper-log": "/monitor/oper-log", "system": "/system", "system_config": "/system/config", "system_dept": "/system/dept", diff --git a/src/service/api/monitor/login-infor.ts b/src/service/api/monitor/login-infor.ts new file mode 100644 index 00000000..8a3186fd --- /dev/null +++ b/src/service/api/monitor/login-infor.ts @@ -0,0 +1,36 @@ +import { request } from '@/service/request'; + +/** 获取系统访问记录列表 */ +export function fetchGetLoginInforList(params?: Api.Monitor.LoginInforSearchParams) { + return request({ + url: '/monitor/logininfor/list', + method: 'get', + params + }); +} + +/** 新增系统访问记录 */ +export function fetchCreateLoginInfor(data: Api.Monitor.LoginInforOperateParams) { + return request({ + url: '/monitor/logininfor', + method: 'post', + data + }); +} + +/** 修改系统访问记录 */ +export function fetchUpdateLoginInfor(data: Api.Monitor.LoginInforOperateParams) { + return request({ + url: '/monitor/logininfor', + method: 'put', + data + }); +} + +/** 批量删除系统访问记录 */ +export function fetchBatchDeleteLoginInfor(infoIds: CommonType.IdType[]) { + return request({ + url: `/monitor/logininfor/${infoIds.join(',')}`, + method: 'delete' + }); +} diff --git a/src/service/api/monitor/oper-log.ts b/src/service/api/monitor/oper-log.ts new file mode 100644 index 00000000..f005f118 --- /dev/null +++ b/src/service/api/monitor/oper-log.ts @@ -0,0 +1,26 @@ +import { request } from '@/service/request'; + +/** 获取操作日志记录列表 */ +export function fetchGetOperLogList(params?: Api.Monitor.OperLogSearchParams) { + return request({ + url: '/monitor/operlog/list', + method: 'get', + params + }); +} + +/** 批量删除操作日志记录 */ +export function fetchBatchDeleteOperLog(operIds: CommonType.IdType[]) { + return request({ + url: `/monitor/operlog/${operIds.join(',')}`, + method: 'delete' + }); +} + +/** 清理操作日志记录 */ +export function fetchCleanOperLog() { + return request({ + url: '/monitor/operlog/clean', + method: 'delete' + }); +} diff --git a/src/service/api/system/config.ts b/src/service/api/system/config.ts index ce873254..61a88f62 100644 --- a/src/service/api/system/config.ts +++ b/src/service/api/system/config.ts @@ -1,36 +1,43 @@ import { request } from '@/service/request'; /** 获取参数配置列表 */ -export function fetchGetConfigList (params?: Api.System.ConfigSearchParams) { - return request({ - url: '/system/config/list', - method: 'get', - params - }); +export function fetchGetConfigList(params?: Api.System.ConfigSearchParams) { + return request({ + url: '/system/config/list', + method: 'get', + params + }); } /** 新增参数配置 */ -export function fetchCreateConfig (data: Api.System.ConfigOperateParams) { - return request({ - url: '/system/config', - method: 'post', - data - }); +export function fetchCreateConfig(data: Api.System.ConfigOperateParams) { + return request({ + url: '/system/config', + method: 'post', + data + }); } /** 修改参数配置 */ -export function fetchUpdateConfig (data: Api.System.ConfigOperateParams) { - return request({ - url: '/system/config', - method: 'put', - data - }); +export function fetchUpdateConfig(data: Api.System.ConfigOperateParams) { + return request({ + url: '/system/config', + method: 'put', + data + }); } /** 批量删除参数配置 */ -export function fetchBatchDeleteConfig (configIds: CommonType.IdType[]) { - return request({ - url: `/system/config/${configIds.join(',')}`, - method: 'delete' - }); +export function fetchBatchDeleteConfig(configIds: CommonType.IdType[]) { + return request({ + url: `/system/config/${configIds.join(',')}`, + method: 'delete' + }); +} +/** 刷新缓存 */ +export function fetchRefreshCache() { + return request({ + url: `/system/config/refreshCache`, + method: 'delete' + }); } diff --git a/src/service/api/system/dict.ts b/src/service/api/system/dict.ts index 0a8ec669..4e5a1789 100644 --- a/src/service/api/system/dict.ts +++ b/src/service/api/system/dict.ts @@ -49,3 +49,10 @@ export function fetchBatchDeleteDictType(dictIds: CommonType.IdType[]) { method: 'delete' }); } +/** 刷新缓存 */ +export function fetchRefreshCache() { + return request({ + url: `/system/dict/type/refreshCache`, + method: 'delete' + }); +} diff --git a/src/theme/settings.ts b/src/theme/settings.ts index a62d2935..fe57ff4c 100644 --- a/src/theme/settings.ts +++ b/src/theme/settings.ts @@ -1,15 +1,15 @@ /** Default theme settings */ export const themeSettings: App.Theme.ThemeSetting = { - themeScheme: 'auto', + themeScheme: 'light', grayscale: false, colourWeakness: false, recommendColor: false, - themeColor: '#0E42D2', + themeColor: '#646CFF', otherColor: { - info: '#0E42D2', - success: '#009A29', - warning: '#D25F00', - error: '#CB2634' + info: '#646CFF', + success: '#52C41A', + warning: '#FAAD14', + error: '#F5222D' }, isInfoFollowPrimary: true, resetCacheStrategy: 'close', diff --git a/src/typings/api/monitor.api.d.ts b/src/typings/api/monitor.api.d.ts new file mode 100644 index 00000000..682d4068 --- /dev/null +++ b/src/typings/api/monitor.api.d.ts @@ -0,0 +1,98 @@ +/** + * Namespace Api + * + * All backend api type + */ +declare namespace Api { + /** + * namespace Monitor + * + * backend api module: "monitor" + */ + namespace Monitor { + /** oper log */ + type OperLog = Common.CommonRecord<{ + /** 日志主键 */ + operId: CommonType.IdType; + /** 租户编号 */ + tenantId: CommonType.IdType; + /** 系统模块 */ + title: string; + /** 操作类型 */ + businessType: number; + /** 方法名称 */ + method: string; + /** 请求方式 */ + requestMethod: string; + /** 操作类别 */ + operatorType: number; + /** 操作人员 */ + operName: string; + /** 部门名称 */ + deptName: string; + /** 请求URL */ + operUrl: string; + /** 操作IP */ + operIp: string; + /** 操作地点 */ + operLocation: string; + /** 请求参数 */ + operParam: string; + /** 返回参数 */ + jsonResult: string; + /** 操作状态 */ + status: number; + /** 错误消息 */ + errorMsg: string; + /** 操作时间 */ + operTime: string; + /** 消耗时间 */ + costTime: number; + }>; + + /** oper log search params */ + type OperLogSearchParams = CommonType.RecordNullable< + Pick & + Api.Common.CommonSearchParams + >; + + /** oper log list */ + type OperLogList = Api.Common.PaginatingQueryRecord; + + /** login infor */ + type LoginInfor = Common.CommonRecord<{ + /** 访问ID */ + infoId: CommonType.IdType; + /** 租户编号 */ + tenantId: CommonType.IdType; + /** 用户账号 */ + userName: string; + /** 客户端 */ + clientKey: string; + /** 设备类型 */ + deviceType: string; + /** 登录IP地址 */ + ipaddr: string; + /** 登录地点 */ + loginLocation: string; + /** 浏览器类型 */ + browser: string; + /** 操作系统 */ + os: string; + /** 登录状态(0成功 1失败) */ + status: string; + /** 提示消息 */ + msg: string; + /** 访问时间 */ + loginTime: string; + }>; + + /** login infor search params */ + type LoginInforSearchParams = CommonType.RecordNullable< + Pick & Api.Common.CommonSearchParams + >; + + /** login infor list */ + type LoginInforList = Api.Common.PaginatingQueryRecord; + } +} diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index e1934758..903806e4 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -50,6 +50,7 @@ declare module 'vue' { IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] 'IconMingcute:questionLine': typeof import('~icons/mingcute/question-line')['default'] IconUilSearch: typeof import('~icons/uil/search')['default'] + JsonPreview: typeof import('./../components/custom/json-preview.vue')['default'] LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] LookForward: typeof import('./../components/custom/look-forward.vue')['default'] MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default'] diff --git a/src/typings/elegant-router.d.ts b/src/typings/elegant-router.d.ts index 147323f1..2a8817cd 100644 --- a/src/typings/elegant-router.d.ts +++ b/src/typings/elegant-router.d.ts @@ -23,6 +23,9 @@ declare module "@elegant-router/types" { "home": "/home"; "iframe-page": "/iframe-page/:url"; "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; + "monitor": "/monitor"; + "monitor_login-infor": "/monitor/login-infor"; + "monitor_oper-log": "/monitor/oper-log"; "system": "/system"; "system_config": "/system/config"; "system_dept": "/system/dept"; @@ -72,6 +75,7 @@ declare module "@elegant-router/types" { | "home" | "iframe-page" | "login" + | "monitor" | "system" | "tool" >; @@ -96,6 +100,8 @@ declare module "@elegant-router/types" { | "iframe-page" | "login" | "home" + | "monitor_login-infor" + | "monitor_oper-log" | "system_config" | "system_dept" | "system_dict_data" diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 00000000..0aab0764 --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,61 @@ +/** + * 获取请求方法标签类型 + * + * @param method 请求方法 + * @returns 标签类型 + */ +export function getRequestMethodTagType(method: string): NaiveUI.ThemeColor { + const methodUpper = method.toUpperCase(); + const colors: { [key: string]: NaiveUI.ThemeColor } = { + DELETE: 'error', + GET: 'success', + POST: 'primary', + PUT: 'warning' + }; + + return colors[methodUpper] ?? 'default'; +} + +const browserOptions = [ + { icon: 'logos:chrome', value: 'chrome' }, + { icon: 'logos:microsoft-edge', value: 'edge' }, + { icon: 'logos:firefox', value: 'firefox' }, + { icon: 'logos:opera', value: 'opera' }, + { icon: 'logos:safari', value: 'safari' }, + { icon: 'ic:baseline-wechat', value: 'micromessenger' }, + { icon: 'ic:baseline-wechat', value: 'windowswechat' }, + { icon: 'arcticons:quark-browser', value: 'quark' }, + { icon: 'ic:baseline-wechat', value: 'wxwork' }, + { icon: 'simple-icons:tencentqq', value: 'qq' }, + { icon: 'arcticons:dingtalk', value: 'dingtalk' }, + { icon: 'arcticons:uc-browser', value: 'uc' }, + { icon: 'ri:baidu-fill', value: 'baidu' } +]; +const osOptions = [ + { icon: 'devicon:windows8', value: 'windows' }, + { icon: 'wpf:macoss', value: 'osx' }, + { icon: 'devicon:linux', value: 'linux' }, + { icon: 'material-icon-theme:android:', value: 'android' }, + { icon: 'wpf:iphone', value: 'ios' } +]; +/** + * 获取浏览器图标 + * + * @param browser 浏览器 + * @returns 浏览器图标 + */ +export function getBrowserIcon(browser: string): string { + const icon = browserOptions.find(item => browser.toLocaleLowerCase().includes(item.value)); + return icon?.icon ?? 'stash:browser-light'; +} + +/** + * 获取操作系统图标 + * + * @param os 操作系统 + * @returns 操作系统图标 + */ +export function getOsIcon(os: string): string { + const icon = osOptions.find(item => os.toLocaleLowerCase().includes(item.value)); + return icon?.icon ?? 'mingcute:device-fill'; +} diff --git a/src/views/monitor/login-infor/index.vue b/src/views/monitor/login-infor/index.vue new file mode 100644 index 00000000..d44e91ad --- /dev/null +++ b/src/views/monitor/login-infor/index.vue @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + diff --git a/src/views/monitor/login-infor/modules/login-infor-search.vue b/src/views/monitor/login-infor/modules/login-infor-search.vue new file mode 100644 index 00000000..1ae98a3b --- /dev/null +++ b/src/views/monitor/login-infor/modules/login-infor-search.vue @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + {{ $t('common.reset') }} + + + + + + {{ $t('common.search') }} + + + + + + + + + + + diff --git a/src/views/monitor/login-infor/modules/login-infor-view-drawer.vue b/src/views/monitor/login-infor/modules/login-infor-view-drawer.vue new file mode 100644 index 00000000..6405655c --- /dev/null +++ b/src/views/monitor/login-infor/modules/login-infor-view-drawer.vue @@ -0,0 +1,80 @@ + + + + + + + + {{ props.rowData?.userName }} + + + {{ props.rowData?.clientKey }} + + + + + + {{ props.rowData?.ipaddr }} + + + {{ props.rowData?.loginLocation }} + + + + + {{ props.rowData?.browser }} + + + + + + {{ props.rowData?.os }} + + + + + + + {{ props.rowData?.msg }} + + + {{ props.rowData?.loginTime }} + + + + + {{ $t('common.close') }} + + + + + + + diff --git a/src/views/monitor/oper-log/index.vue b/src/views/monitor/oper-log/index.vue new file mode 100644 index 00000000..dd3d0126 --- /dev/null +++ b/src/views/monitor/oper-log/index.vue @@ -0,0 +1,208 @@ + + + + + + + + + handleCleanOperLog()"> + + + + + + 清空 + + + 确认清空操作日志? + + + + + + + + + + + diff --git a/src/views/monitor/oper-log/modules/oper-log-search.vue b/src/views/monitor/oper-log/modules/oper-log-search.vue new file mode 100644 index 00000000..257f6217 --- /dev/null +++ b/src/views/monitor/oper-log/modules/oper-log-search.vue @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ $t('common.reset') }} + + + + + + {{ $t('common.search') }} + + + + + + + + + + + diff --git a/src/views/monitor/oper-log/modules/oper-log-view-drawer.vue b/src/views/monitor/oper-log/modules/oper-log-view-drawer.vue new file mode 100644 index 00000000..184e2dc2 --- /dev/null +++ b/src/views/monitor/oper-log/modules/oper-log-view-drawer.vue @@ -0,0 +1,71 @@ + + + + + + + {{ props.rowData?.operId }} + + + + + + {{ props.rowData?.title }}模块 + + + + + {{ props.rowData?.operName }} | {{ props.rowData?.deptName }} | {{ props.rowData?.operIp }} | + {{ props.rowData?.operLocation }} + + + + + {{ `${props.rowData?.requestMethod?.toUpperCase()}请求` }} + + {{ props.rowData?.operUrl }} + + + {{ props.rowData?.operTime }} + + + + + + + + {{ `${props.rowData?.costTime} ms` }} + + {{ props.rowData?.errorMsg }} + + + + {{ $t('common.close') }} + + + + + diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue index 2812a922..f6f8d6d2 100644 --- a/src/views/system/config/index.vue +++ b/src/views/system/config/index.vue @@ -1,6 +1,6 @@ @@ -176,18 +183,23 @@ async function handleExport() { - + + + 刷新缓存 + + + @@ -177,18 +184,23 @@ async function handleExport() { - + + + 刷新缓存 + + +