diff --git a/.workbuddy/memory/CHANGELOG.md b/.workbuddy/memory/CHANGELOG.md index 185e722..9f8c2b6 100644 --- a/.workbuddy/memory/CHANGELOG.md +++ b/.workbuddy/memory/CHANGELOG.md @@ -1,6 +1,8 @@ # admin.xpcool.com 变更记录 > 倒序:最新在上。格式:YYYY-MM-DD | 类型 | 摘要 +2026-08-27 | FIX | 前后端联查修复「界面/菜单/工作台异常」:①本地库缺 010_server_log_enhance 增量列(admin_operation_log 无 ip_location/admin_username 等)致操作日志接口 500,已导;②本地缺工作台/招聘菜单+admin_role_menu 关联,已导 013_workbench_menu+recruitment/003_menu;③工作台快捷入口 /system/log/* 是死链(真实路由 /log/*)已改;④打通 Drawer 重构遗留编译错:6 页 @vben-core/popup-ui 误引改 @vben/common-ui、preferences 补 languageToggleButtonPosition、清未用变量、menu/log 页 lint 债(7dc9bc6,钩子全绿);⑤发现审计 admin_username/user_agent 长期为空=线上跑旧二进制,新构建验证 248 号记录字段完整,历史空账号已按 user_id 回填(121 条);⑥本地 xxcool/opuser 密码已重置为 opuser123 便于联调 + 2026-08-27 | CHG | 三大改造:①移除国际化多语言仅留中文——preferences.ts 固定 locale=zh-CN;删除 header/toolbar/user-dropdown 三处语言切换入口、偏好面板语言选择项(general.vue/appLocale/widget.vue languageToggle 位置配置);config.ts 禁 languageToggle 且 order 移除;删除主应用与 packages/locales 的 en-US 语言包;locales/index.ts loadDayjsLocale 仅留 zh-CN。②新增主题色管理——theme.css 品牌色改为引用 --primary 变量(不再写死 #4f5bff),背景/主按钮渐变用 color-mix 跟随主题色,配合 useTDesignDesignTokens 使偏好面板 BuiltinTheme 切换主题色真正生效;constants.ts default 主题色与 config.ts colorPrimary 统一为靛蓝 hsl(235 100% 65%)。③业务弹窗统一为 vben 抽屉——6 页(system/{role,menu,admin}、recruitment/push、house/{listing,community})的 t-dialog 全部替换为 useVbenDrawer(),drawerApi.open/close/setState(标题)/lock(提交中);community/listing 抽屉加宽(class w-160/w-150) 2026-08-27 | CHG | 服务器安全日志页(挂在系统监控菜单下,id=96):views/monitor/security/index.vue——顶部 4 统计卡片(SSH爆破/fail2ban封禁/成功登录/总数)+ TOP 攻击源标签区 + 筛选(来源IP/事件类型/时间范围 t-date-range-picker)+ Vben VxeTable 列表(时间/来源IP/来源端口/目标端口/事件类型标签/详情 tooltip,事件标签按类型配色);api/serverSecurity.ts(list/stats 全 POST,无 URL 参数);typecheck 通过 2026-08-27 | CHG | 全量表格重构:views 下全部 11 个 t-table 页面统一改为 Vben Vxe Table(useVbenVxeGrid,#/adapter/vxe-table)+ Vben TableAction 行操作——system/{log(范式),login-log,admin,role}、recruitment/{info,crawler,push}、house/{data,listing,community,presale}、dashboard/workbench(两处本地小表 proxyConfig.enabled=false + gridApi.setGridOptions 写数据)。要点:gridOptions 需注解 VxeTableGridOptions(否则 type:'seq' 推断 string 报错);单元格自定义用 columns[].slots.default + h(TDesign组件);远程分页走 proxyConfig.ajax.query 返回 {items,total};筛选区保留手写 TDesign 组件不动;admin/role/listing/community 行操作 ActionItem 带 auth 权限码、删除 danger 标红;listing 多选批量标记改 vxe checkboxConfig + gridApi.grid.getCheckboxRecords();confirm 弹窗交互(删除/重置密码)保留 DialogPlugin 不变;typecheck 通过 diff --git a/apps/web-tdesign/src/api/autoJob.ts b/apps/web-tdesign/src/api/autoJob.ts new file mode 100644 index 0000000..f8cab33 --- /dev/null +++ b/apps/web-tdesign/src/api/autoJob.ts @@ -0,0 +1,67 @@ +// 自动任务模块 API 客户端(任务管理/运行日志)。 +// 接口规范:全部 POST;URL 不含参数;入参一律 body。 +import { requestClient } from '#/api/request'; + +// ---------- 任务 ---------- +export interface AutoJobItem { + id: number; + name: string; + code: string; + jobType: string; + cronExpr: string; + enabled: number; + remark: string; + lastRunAt: string; + lastResult: number; + lastError: string; +} + +export function getAutoJobList() { + return requestClient.post<{ list: AutoJobItem[] }>( + '/api/service/admin/auto-job/list', + ); +} + +export function saveAutoJob(data: { + cronExpr?: string; + enabled?: number; + id: number; + remark?: string; +}) { + return requestClient.post('/api/service/admin/auto-job/save', data); +} + +export function triggerAutoJob(data: { id: number }) { + return requestClient.post<{ ok: boolean; summary: string }>( + '/api/service/admin/auto-job/trigger', + data, + ); +} + +// ---------- 运行日志 ---------- +export interface AutoJobLogItem { + id: number; + jobId: number; + jobName: string; + runAt: string; + result: number; + error: string; + summary: string; + durationMs: number; +} + +export interface AutoJobLogListResp { + list: AutoJobLogItem[]; + total: number; +} + +export function getAutoJobLogList(data: { + jobId?: number; + page: number; + size: number; +}) { + return requestClient.post( + '/api/service/admin/auto-job/log/list', + data, + ); +} diff --git a/apps/web-tdesign/src/api/notice.ts b/apps/web-tdesign/src/api/notice.ts new file mode 100644 index 0000000..33ce36d --- /dev/null +++ b/apps/web-tdesign/src/api/notice.ts @@ -0,0 +1,130 @@ +// 通知模块 API 客户端(渠道/规则/日志/测试)。 +// 接口规范:全部 POST;URL 不含参数;入参一律 body。 +import { requestClient } from '#/api/request'; + +// ---------- 渠道 ---------- +export interface NoticeChannelItem { + id: number; + code: string; + name: string; + enabled: number; + config: string; + remark: string; +} + +export function getNoticeChannelList() { + return requestClient.post<{ list: NoticeChannelItem[] }>( + '/api/service/admin/notice/channel/list', + ); +} + +export function saveNoticeChannel(data: { + code: string; + config?: string; + enabled?: number; + id?: number; + name: string; + remark?: string; +}) { + return requestClient.post<{ id: number }>( + '/api/service/admin/notice/channel/save', + data, + ); +} + +// ---------- 规则 ---------- +export interface NoticeRuleItem { + id: number; + name: string; + eventType: string; + channelCodes: string[]; + userIds: number[]; + titleTemplate: string; + bodyTemplate: string; + enabled: number; +} + +export function getNoticeRuleList(data: { eventType?: string }) { + return requestClient.post<{ list: NoticeRuleItem[] }>( + '/api/service/admin/notice/rule/list', + data, + ); +} + +export function saveNoticeRule(data: { + bodyTemplate?: string; + channelCodes: string[]; + enabled?: number; + eventType: string; + id?: number; + name: string; + titleTemplate?: string; + userIds: number[]; +}) { + return requestClient.post<{ id: number }>( + '/api/service/admin/notice/rule/save', + data, + ); +} + +export function deleteNoticeRule(data: { id: number }) { + return requestClient.post('/api/service/admin/notice/rule/delete', data); +} + +// ---------- 日志 ---------- +export interface NoticeLogItem { + id: number; + ruleId: number; + eventType: string; + channelCode: string; + userId: number; + target: string; + title: string; + body: string; + result: number; + error: string; + createdAt: string; +} + +export interface NoticeLogListResp { + list: NoticeLogItem[]; + total: number; +} + +export function getNoticeLogList(data: { + channelCode?: string; + dateFrom?: string; + dateTo?: string; + eventType?: string; + page: number; + result?: number; + size: number; +}) { + return requestClient.post( + '/api/service/admin/notice/log/list', + data, + ); +} + +// ---------- 测试 ---------- +export function testNotice(data: { + body?: string; + channelCode?: string; + ruleId?: number; + target?: string; + title?: string; + userIds?: number[]; +}) { + return requestClient.post<{ message: string; result: boolean }>( + '/api/service/admin/notice/test', + data, + ); +} + +// 事件类型选项 +export const NOTICE_EVENT_OPTIONS = [ + { label: '任务完成', value: 'job_done' }, + { label: '任务失败', value: 'job_fail' }, + { label: '安全告警', value: 'security_alert' }, + { label: '招聘每日推送', value: 'recruit_daily' }, +]; diff --git a/apps/web-tdesign/src/api/system.ts b/apps/web-tdesign/src/api/system.ts index e59eab0..bc5cfe5 100644 --- a/apps/web-tdesign/src/api/system.ts +++ b/apps/web-tdesign/src/api/system.ts @@ -1,11 +1,13 @@ import { requestClient } from '#/api/request'; -// ---------- 管理员(/api/service/admin/admin) ---------- +// ---------- 用户管理(/api/service/admin/admin) ---------- export interface AdminItem { id: number; username: string; nickname: string; status: number; + barkDeviceId: string; + pushplusToken: string; roleIds: number[]; roleNames: string[]; createdAt: string; @@ -28,8 +30,10 @@ export function getAdminList(data: { } export function createAdmin(data: { + barkDeviceId?: string; nickname: string; password: string; + pushplusToken?: string; roleIds: number[]; username: string; }) { @@ -41,7 +45,13 @@ export function createAdmin(data: { export function updateAdmin( id: number, - data: { nickname: string; roleIds: number[]; status: number; }, + data: { + barkDeviceId?: string; + nickname: string; + pushplusToken?: string; + roleIds: number[]; + status: number; + }, ) { return requestClient.post(`/api/service/admin/admin/update/${id}`, data); } diff --git a/apps/web-tdesign/src/views/system/admin/index.vue b/apps/web-tdesign/src/views/system/admin/index.vue index 89fce78..d9a3bc1 100644 --- a/apps/web-tdesign/src/views/system/admin/index.vue +++ b/apps/web-tdesign/src/views/system/admin/index.vue @@ -19,15 +19,15 @@ import { updateAdmin, } from '#/api/system'; -// 人员管理页面:Vben Vxe Table + Vben TableAction 行操作(权限码控制显隐) +// 用户管理页面:Vben Vxe Table + Vben TableAction 行操作(权限码控制显隐) const { hasAccessByCodes } = useAccess(); const roleOptions = ref<{ label: string; value: number }[]>([]); const editing = ref(null); -// 新增/编辑人员抽屉(vben 封装抽屉组件) +// 新增/编辑用户抽屉(vben 封装抽屉组件) const [Drawer, drawerApi] = useVbenDrawer({ - title: '新增人员', + title: '新增用户', onConfirm: submit, }); @@ -38,6 +38,8 @@ const form = reactive({ password: '', nickname: '', status: 1, + barkDeviceId: '', + pushplusToken: '', roleIds: [] as number[], }); @@ -56,9 +58,11 @@ function openCreate() { password: '', nickname: '', status: 1, + barkDeviceId: '', + pushplusToken: '', roleIds: [], }); - drawerApi.setState({ title: '新增人员' }); + drawerApi.setState({ title: '新增用户' }); drawerApi.open(); } @@ -69,9 +73,11 @@ function openEdit(row: AdminItem) { password: '', nickname: row.nickname, status: row.status, + barkDeviceId: row.barkDeviceId ?? '', + pushplusToken: row.pushplusToken ?? '', roleIds: row.roleIds ?? [], }); - drawerApi.setState({ title: '编辑人员' }); + drawerApi.setState({ title: '编辑用户' }); drawerApi.open(); } @@ -81,12 +87,16 @@ async function submit() { ? await updateAdmin(editing.value.id, { nickname: form.nickname, status: form.status, + barkDeviceId: form.barkDeviceId, + pushplusToken: form.pushplusToken, roleIds: form.roleIds, }) : await createAdmin({ username: form.username, password: form.password, nickname: form.nickname, + barkDeviceId: form.barkDeviceId, + pushplusToken: form.pushplusToken, roleIds: form.roleIds, }); MessagePlugin.success('保存成功'); @@ -94,10 +104,10 @@ async function submit() { gridApi.query(); } -/** 删除人员(保留原确认弹窗行为) */ +/** 删除用户(保留原确认弹窗行为) */ function onDelete(row: AdminItem) { const d = DialogPlugin.confirm({ - header: '确认删除该人员?', + header: '确认删除该用户?', onConfirm: async () => { await deleteAdmin(row.id); MessagePlugin.success('删除成功'); @@ -131,6 +141,18 @@ const gridOptions: VxeTableGridOptions = { { field: 'id', title: 'ID', width: 70 }, { field: 'username', title: '用户名' }, { field: 'nickname', title: '昵称' }, + { + field: 'barkDeviceId', + title: 'Bark设备', + width: 120, + showOverflow: 'tooltip', + }, + { + field: 'pushplusToken', + title: 'pushplus', + width: 120, + showOverflow: 'tooltip', + }, { field: 'statusText', title: '状态', @@ -222,8 +244,8 @@ onMounted(loadRoles);