diff --git a/.workbuddy/memory/2026-04-29.md b/.workbuddy/memory/2026-04-29.md index 2daa805..54987b2 100644 --- a/.workbuddy/memory/2026-04-29.md +++ b/.workbuddy/memory/2026-04-29.md @@ -1,56 +1,23 @@ # 2026-04-29 工作日志 -- 修复 SysAdminView.vue 第 849 行 `` 占位符 bug(上一轮 i18n 修改误删内容),恢复为 `avatar` -- 在 `en.json` 的 `message` 节点补充缺失的 `"sysadmin": "System Admin"` 翻译 -- 全面重新分析代码结构,更新并精简 MEMORY.md(整合后端 main.go 启动流程、apiSysAdmin 完整路由、前端路由守卫逻辑、stores/user.js isSysAdmin 机制等) -- 将 SysAdminView.vue 拆分为三个子组件:`src/views/sysadmin/UsersTab.vue`(用户管理+详情弹窗)、`GroupsTab.vue`(用户组+添加/移除成员)、`LogsTab.vue`(登录失败日志);父组件改用 v-show 保持子组件挂载,UsersTab 自身 onMounted 加载数据,Groups/Logs 由父组件 watch(activeTab) 懒加载 -- 将 SysAdminView.vue 整体移入 `src/views/sysadmin/` 目录,并更新 router/index.js 中的引用路径为 `@/views/sysadmin/SysAdminView.vue` -- 客户模块权限改造:移除 `/getinfo` 返回的 `isCustomerAdmin`,改为按记录返回 `edit` 权限标记 - - 后端 `apiCustomer.go`:新增 `canModifyCustomer()` 函数(创建者或管理员可编辑),`/list` 每条记录附加 `edit` 字段,`/get` 返回 `canModify`,`/update` 和 `/delete` 改用 `canModifyCustomer` 校验权限 - - 后端 `apiUsers.go`:移除 `isCustomerAdmin` 返回 - - 前端 `stores/user.js`:移除 `isCustomerAdmin` 状态 - - 前端 `CustomerList.vue`:改用 `customer.edit` 控制编辑/删除按钮显示,新增按钮始终显示(后端会校验权限) -- 客户称呼选项调整:移除"夫人"/"博士",新增"单位"作为默认选项 - - `CustomerFormModal.vue`:`titleOptions` 改为 `['Unit', 'Mr', 'Ms']`,默认值改为 `'Unit'` - - `zh-CN.json`:新增 `salutation_unit: "单位"`,移除 `salutation_mrs`/`salutation_dr` - - `en.json`:新增 `salutation_unit: "Unit"`,移除 `salutation_mrs`/`salutation_dr` - - 后端 `apiCustomer.go`:`Title` 字段注释改为 `称呼:Unit/Mr/Ms` -- 客户表单字段顺序调整:姓在前(必填),名在后(非必填) - - `CustomerFormModal.vue`:表单 grid 改为 姓 → 名,移除 `first_name` 必填验证 - - `CustomerList.vue`:列表显示改为 `last_name + first_name` 顺序 -- 创建客户详情页 `CustomerDetail.vue` - - 显示客户基本信息(姓名、称呼、创建时间、创建者) - - 显示电话列表(含主号码标记) - - 显示邮箱列表(含主邮箱标记) - - 显示单位列表(含主单位标记) - - 编辑按钮(仅 `canModify` 为 true 时显示) - - 返回按钮 -- 创建客户编辑页 `CustomerEdit.vue`(包装 `CustomerFormModal`) -- 添加路由:`/customer/detail/:id` 和 `/customer/edit/:id` -- `CustomerList.vue`:姓名改为可点击链接,跳转到详情页 -- i18n:添加 `common.back`、`customer.detail_title`、`customer.basic_info`、`customer.created_by`、`customer.not_found` 等翻译 -- 工单-客户关联功能 - - `binds.go`: 新增 `TabWorkOrderCustomerBind` 关联表,在 `BindsInit()` 中注册 - - `apiWorkOrder.go`: `/get` API 返回 `linkedCustomers`;新增 `/link_customer` 和 `/unlink_customer` API - - `ShowWorkOrder.vue`: 详情页显示关联客户,支持搜索/关联/解除关联客户 - - `work_order.js`: 添加 `linkCustomer` 和 `unlinkCustomer` API 方法 - - `AddEditWorkOrder.vue`: 新增工单时支持搜索并关联客户 - - i18n: 添加 `linked_customer`、`linked_customers`、`link_customer_placeholder` 等翻译 -- 工单编辑页支持关联物品和客户(多选) - - `AddEditWorkOrder.vue`: 移除 `v-if="!isEdit"` 限制,编辑模式也显示关联物品/客户搜索框 - - `AddEditWorkOrder.vue`: 编辑模式加载时回填 `selectedItems` 和 `selectedCustomers` - - `AddEditWorkOrder.vue`: 编辑提交时发送 `item_ids` 和 `customer_ids` - - `apiWorkOrder.go`: `/update` 接口新增 `ItemIDs` 和 `CustomerIDs` 字段,重建物品/客户关联绑定 -- 仓库物品添加页支持关联客户 - - `binds.go`: 新增 `TabWarehouseItemCustomerBind` 关联表(物品-客户多对多) - - `apiWarehouse.go`: `/add_item` 接口新增 `CustomerIDs` 字段,仅新建物品时创建客户关联绑定 - - `WarehouseAddItem.vue`: 添加客户搜索选择组件(多选),提交时发送 `customer_ids` - - 使用已有的 `customerApi.list()` 搜索客户 -- 仓库物品详情页显示关联客户 - - `apiWarehouse.go`: `/get_item` 接口返回 `customers` 列表(包含客户 ID、姓名、称呼) - - `WarehouseItemDetail.vue`: 新增"关联客户" Tab,显示关联客户列表(头像、姓名、称呼),点击可跳转到客户详情页 - - i18n: 添加 `warehouse.customers` 和 `warehouse.no_customers` 翻译 -- 仓库物品编辑页支持关联客户 - - `apiWarehouse.go`: `/update_item` 接口新增 `CustomerIDs` 字段,重建客户关联绑定 - - `WarehouseItemEdit.vue`: 添加客户搜索选择组件(多选),加载时回填已关联客户,提交时发送 `customer_ids` - - i18n: 添加 `warehouse.linked_customers`、`warehouse.linked_customer_placeholder`、`warehouse.linked_customer_not_found` 等翻译到 `warehouse` 节点 +## 完成的工作 + +### 1. 客户详情页添加编辑按钮 +- 文件: `frontend/ops_vue_js/src/views/customer/CustomerDetail.vue` +- 功能: 根据后端返回的 `canModify` 字段显示/隐藏编辑按钮 +- 权限逻辑: 创建者或客户管理员可编辑 + +### 2. 操作日志功能 +- 后端 API: `backend/my_work/routers/apiSysAdmin.go` + - 新增 `/operation_logs` 接口,聚合所有模块的操作日志 + - 支持按模块筛选: all/customer/purchase/schedule/warehouse/work_order + - 支持分页,最新日志在前 + +- 前端组件: `frontend/ops_vue_js/src/views/sysadmin/OperationLogsTab.vue` + - 左侧模块选择器,右侧日志表格 + - 分页显示,最新日志在前 + +- 系统管理页面: `frontend/ops_vue_js/src/views/sysadmin/SysAdminView.vue` + - 新增"操作日志"标签页 + +- 翻译文件: 添加了 `operation_logs` 相关翻译键 diff --git a/backend/my_work/routers/apiCustomer.go b/backend/my_work/routers/apiCustomer.go index e5c3663..18bf34a 100644 --- a/backend/my_work/routers/apiCustomer.go +++ b/backend/my_work/routers/apiCustomer.go @@ -494,11 +494,11 @@ func ApiCustomer(r *gin.RouterGroup) { models.DB.Where("customer_id = ?", req.ID).Find(&companies) // 写查询日志 - models.DB.Create(&TabCustomerLog{ - CustomerID: req.ID, - ActionType: "query", - IP: ctx.ClientIP(), - }) + // models.DB.Create(&TabCustomerLog{ + // CustomerID: req.ID, + // ActionType: "query", + // IP: ctx.ClientIP(), + // }) ReturnJson(ctx, "apiOK", gin.H{ "customer": customer, diff --git a/backend/my_work/routers/apiSysAdmin.go b/backend/my_work/routers/apiSysAdmin.go index 13ec8e0..30a328e 100644 --- a/backend/my_work/routers/apiSysAdmin.go +++ b/backend/my_work/routers/apiSysAdmin.go @@ -2,6 +2,8 @@ package routers import ( "ops/models" + "sort" + "time" "github.com/gin-gonic/gin" "github.com/mitchellh/mapstructure" @@ -150,21 +152,27 @@ func ApiSysAdmin(r *gin.RouterGroup) { // 获取用户组成员列表(仅系统管理员可访问) r.POST("/group_members", func(ctx *gin.Context) { - isAuth, _, data := AuthenticationAuthority(ctx) + isAuth, authUser, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } + // 检查是否为系统管理员 + if !SysAdminCheck(authUser.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + var params struct { - GroupID float64 `json:"group_id" mapstructure:"group_id"` - Page float64 `json:"page" mapstructure:"page"` - PageSize float64 `json:"page_size" mapstructure:"page_size"` - } - if err := mapstructure.Decode(data, ¶ms); err != nil { - params.Page = 1 - params.PageSize = 20 - } + GroupID float64 `json:"group_id" mapstructure:"group_id"` + Page float64 `json:"page" mapstructure:"page"` + PageSize float64 `json:"page_size" mapstructure:"page_size"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil { + params.Page = 1 + params.PageSize = 20 + } if params.Page < 1 { params.Page = 1 } @@ -229,12 +237,18 @@ func ApiSysAdmin(r *gin.RouterGroup) { // 获取用户详细信息(仅系统管理员可访问) r.POST("/user_detail", func(ctx *gin.Context) { - isAuth, _, data := AuthenticationAuthority(ctx) + isAuth, authUser, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } + // 检查是否为系统管理员 + if !SysAdminCheck(authUser.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + var params struct { UserID uint `json:"user_id"` } @@ -369,36 +383,36 @@ func ApiSysAdmin(r *gin.RouterGroup) { return } - // 创建绑定 - newBind := TabUserGroupBinds{ - UserID: uint(params.UserID), - GroupID: uint(params.GroupID), - } - if err := models.DB.Create(&newBind).Error; err != nil { - ReturnJson(ctx, "dbErr", nil) - return - } + // 创建绑定 + newBind := TabUserGroupBinds{ + UserID: uint(params.UserID), + GroupID: uint(params.GroupID), + } + if err := models.DB.Create(&newBind).Error; err != nil { + ReturnJson(ctx, "dbErr", nil) + return + } - // 根据组名刷新对应的权限缓存 - switch group.Name { - case "admins": - updateSysAdminsCash() - case "schedule_admin": - ScheduleUpdateAdminsCash() - case "purchase_admin": - PurchaseUpdateAdminsCash() - case "work_order_admin": - WorkOrderUpdateAdminsCash() - case "warehouse_admin": - WarehouseUpdateAdminsCash() - case "customer_admin": - CustomerUpdateAdminsCash() - } + // 根据组名刷新对应的权限缓存 + switch group.Name { + case "admins": + updateSysAdminsCash() + case "schedule_admin": + ScheduleUpdateAdminsCash() + case "purchase_admin": + PurchaseUpdateAdminsCash() + case "work_order_admin": + WorkOrderUpdateAdminsCash() + case "warehouse_admin": + WarehouseUpdateAdminsCash() + case "customer_admin": + CustomerUpdateAdminsCash() + } - ReturnJson(ctx, "apiOK", nil) -}) + ReturnJson(ctx, "apiOK", nil) + }) -// 移除用户组成员(仅系统管理员可访问) + // 移除用户组成员(仅系统管理员可访问) r.POST("/remove_group_member", func(ctx *gin.Context) { isAuth, adminUser, data := AuthenticationAuthority(ctx) if !isAuth { @@ -428,39 +442,232 @@ func ApiSysAdmin(r *gin.RouterGroup) { return } - // 删除绑定 - if err := models.DB.Where("group_id = ? AND user_id = ?", uint(params.GroupID), uint(params.UserID)).Delete(&TabUserGroupBinds{}).Error; err != nil { - ReturnJson(ctx, "dbErr", nil) - return - } + // 删除绑定 + if err := models.DB.Where("group_id = ? AND user_id = ?", uint(params.GroupID), uint(params.UserID)).Delete(&TabUserGroupBinds{}).Error; err != nil { + ReturnJson(ctx, "dbErr", nil) + return + } - // 根据组名刷新对应的权限缓存 - switch group.Name { - case "admins": - updateSysAdminsCash() - case "schedule_admin": - ScheduleUpdateAdminsCash() - case "purchase_admin": - PurchaseUpdateAdminsCash() - case "work_order_admin": - WorkOrderUpdateAdminsCash() - case "warehouse_admin": - WarehouseUpdateAdminsCash() - case "customer_admin": - CustomerUpdateAdminsCash() - } + // 根据组名刷新对应的权限缓存 + switch group.Name { + case "admins": + updateSysAdminsCash() + case "schedule_admin": + ScheduleUpdateAdminsCash() + case "purchase_admin": + PurchaseUpdateAdminsCash() + case "work_order_admin": + WorkOrderUpdateAdminsCash() + case "warehouse_admin": + WarehouseUpdateAdminsCash() + case "customer_admin": + CustomerUpdateAdminsCash() + } - ReturnJson(ctx, "apiOK", nil) -}) + ReturnJson(ctx, "apiOK", nil) + }) -// 获取登录失败日志(仅系统管理员可访问) - r.POST("/login_fail_logs", func(ctx *gin.Context) { - isAuth, _, data := AuthenticationAuthority(ctx) + // 获取操作日志(仅系统管理员可访问) + r.POST("/operation_logs", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } + // 检查是否为系统管理员 + if !SysAdminCheck(user.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + + // 解析参数 + var params struct { + Page int `json:"page"` + PageSize int `json:"page_size"` + Module string `json:"module"` // 模块: all/customer/purchase/schedule/warehouse/work_order + } + if err := mapstructure.Decode(data, ¶ms); err != nil { + params.Page = 1 + params.PageSize = 20 + params.Module = "all" + } + if params.Page < 1 { + params.Page = 1 + } + if params.PageSize < 1 || params.PageSize > 100 { + params.PageSize = 20 + } + + type LogEntry struct { + ID uint `json:"id"` + Module string `json:"module"` // 模块名称 + EntityID uint `json:"entity_id"` // 关联实体ID + UserID uint `json:"user_id"` // 操作人ID + ActionType string `json:"action_type"` // 操作类型 + IP string `json:"ip"` + Remark string `json:"remark"` + CreatedAt *time.Time `json:"created_at"` + } + + var allLogs []LogEntry + + // 根据模块筛选查询 + if params.Module == "all" || params.Module == "customer" { + var logs []TabCustomerLog + query := models.DB.Model(&TabCustomerLog{}) + if params.Module == "customer" { + query.Order("created_at DESC").Find(&logs) + } else { + query.Order("created_at DESC").Limit(1000).Find(&logs) + } + for _, log := range logs { + allLogs = append(allLogs, LogEntry{ + ID: log.ID, + Module: "customer", + EntityID: log.CustomerID, + UserID: log.UserID, + ActionType: log.ActionType, + IP: log.IP, + Remark: log.Remark, + CreatedAt: log.CreatedAt, + }) + } + } + + if params.Module == "all" || params.Module == "purchase" { + var logs []TabPurchaseLog + query := models.DB.Model(&TabPurchaseLog{}) + if params.Module == "purchase" { + query.Order("created_at DESC").Find(&logs) + } else { + query.Order("created_at DESC").Limit(1000).Find(&logs) + } + for _, log := range logs { + allLogs = append(allLogs, LogEntry{ + ID: log.ID, + Module: "purchase", + EntityID: log.OrderID, + UserID: log.UserID, + ActionType: log.ActionType, + IP: log.IP, + Remark: log.Remark, + CreatedAt: log.CreatedAt, + }) + } + } + + if params.Module == "all" || params.Module == "schedule" { + var logs []TabScheduleLog + query := models.DB.Model(&TabScheduleLog{}) + if params.Module == "schedule" { + query.Order("created_at DESC").Find(&logs) + } else { + query.Order("created_at DESC").Limit(1000).Find(&logs) + } + for _, log := range logs { + allLogs = append(allLogs, LogEntry{ + ID: log.ID, + Module: "schedule", + EntityID: log.ScheduleID, + UserID: log.UserID, + ActionType: log.ActionType, + IP: log.IP, + Remark: log.Remark, + CreatedAt: log.CreatedAt, + }) + } + } + + if params.Module == "all" || params.Module == "warehouse" { + var logs []TabWarehouseLog + query := models.DB.Model(&TabWarehouseLog{}) + if params.Module == "warehouse" { + query.Order("created_at DESC").Find(&logs) + } else { + query.Order("created_at DESC").Limit(1000).Find(&logs) + } + for _, log := range logs { + allLogs = append(allLogs, LogEntry{ + ID: log.ID, + Module: "warehouse", + EntityID: log.EntityID, + UserID: log.UserID, + ActionType: log.ActionType, + IP: log.IP, + Remark: log.Remark, + CreatedAt: &log.CreatedAt, + }) + } + } + + if params.Module == "all" || params.Module == "work_order" { + var logs []TabWorkOrderLog + query := models.DB.Model(&TabWorkOrderLog{}) + if params.Module == "work_order" { + query.Order("created_at DESC").Find(&logs) + } else { + query.Order("created_at DESC").Limit(1000).Find(&logs) + } + for _, log := range logs { + allLogs = append(allLogs, LogEntry{ + ID: log.ID, + Module: "work_order", + EntityID: log.WorkOrderID, + UserID: log.UserID, + ActionType: log.ActionType, + IP: log.IP, + Remark: log.Remark, + CreatedAt: log.CreatedAt, + }) + } + } + + // 按时间倒序排序 + sort.Slice(allLogs, func(i, j int) bool { + if allLogs[i].CreatedAt == nil || allLogs[j].CreatedAt == nil { + return allLogs[i].ID > allLogs[j].ID + } + return allLogs[i].CreatedAt.After(*allLogs[j].CreatedAt) + }) + + total := len(allLogs) + offset := (params.Page - 1) * params.PageSize + end := offset + params.PageSize + if offset > total { + offset = total + } + if end > total { + end = total + } + + var pagedLogs []LogEntry + if offset < total { + pagedLogs = allLogs[offset:end] + } + + ReturnJson(ctx, "apiOK", gin.H{ + "logs": pagedLogs, + "total": total, + "page": params.Page, + "page_size": params.PageSize, + }) + }) + + // 获取登录失败日志(仅系统管理员可访问) + r.POST("/login_fail_logs", func(ctx *gin.Context) { + isAuth, authUser, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + // 检查是否为系统管理员 + if !SysAdminCheck(authUser.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + // 解析分页和搜索参数 var params struct { Page int `json:"page"` diff --git a/frontend/ops_vue_js/src/api/auth.js b/frontend/ops_vue_js/src/api/auth.js index 031c139..acceccd 100644 --- a/frontend/ops_vue_js/src/api/auth.js +++ b/frontend/ops_vue_js/src/api/auth.js @@ -61,6 +61,11 @@ export const authApi = { return api.post('/admin/login_fail_logs', params) }, + /** 获取操作日志(仅管理员可访问) */ + getOperationLogs(params = {}) { + return api.post('/admin/operation_logs', params) + }, + /** 修改密码 */ changePassword(oldPass, newPass) { return api.post('/users/changePassword', { oldpass: oldPass, newpass: newPass }) diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json index de39f7b..2e14ecf 100644 --- a/frontend/ops_vue_js/src/i18n/en.json +++ b/frontend/ops_vue_js/src/i18n/en.json @@ -67,6 +67,7 @@ "click_button_or_drag": "Click upload button or drag & drop image file" }, "purchase": { + "title": "Purchase", "purchase_list": "Purchase List", "item_name": "Item Name", "purpose": "Purpose", @@ -176,6 +177,7 @@ "save_changes": "Save Changes" }, "warehouse": { + "title": "Warehouse", "container_list": "Container List", "container_detail": "Container Detail", "overview": "Warehouse Overview", @@ -283,6 +285,7 @@ "part_name": "Parts Name" }, "schedule": { + "title": "Schedule", "my_schedule": "My Schedule", "event_title": "Event Title", "event_date": "Event Date", @@ -526,7 +529,31 @@ "current_admins": "Current System Admins", "no_admins": "No system admins", "group_list": "Group List", - "extended_info": "Extended Info" + "extended_info": "Extended Info", + "tab_operation_logs": "Operation Logs" + }, + "operation_logs": { + "title": "Operation Logs", + "all": "All Modules", + "search_placeholder": "Search operation logs...", + "no_logs": "No operation logs", + "table_module": "Module", + "table_entity_id": "Entity ID", + "table_user": "User", + "table_action": "Action", + "table_ip": "IP Address", + "table_remark": "Remark", + "table_created_at": "Operation Time", + "module_customer": "Customer", + "module_purchase": "Purchase", + "module_schedule": "Schedule", + "module_warehouse": "Warehouse", + "module_work_order": "Work Order", + "action_create": "Create", + "action_update": "Update", + "action_delete": "Delete", + "action_query": "Query", + "action_move": "Move" }, "customer": { "title": "Customer Management", diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json index 6451947..44dc747 100644 --- a/frontend/ops_vue_js/src/i18n/zh-CN.json +++ b/frontend/ops_vue_js/src/i18n/zh-CN.json @@ -67,6 +67,7 @@ "click_button_or_drag": "点击上传按钮或拖放图片文件" }, "purchase": { + "title": "采购", "purchase_list": "采购列表", "item_name": "物品名称", "purpose": "用途", @@ -176,6 +177,7 @@ "save_changes": "保存修改" }, "warehouse": { + "title": "仓库", "container_list": "容器列表", "container_detail": "容器详情", "overview": "仓库总览", @@ -283,6 +285,7 @@ "part_name": "物件名称" }, "schedule": { + "title": "日程", "my_schedule": "我的日程", "event_title": "事件标题", "event_date": "事件日期", @@ -526,7 +529,31 @@ "current_admins": "当前系统管理员列表", "no_admins": "暂无系统管理员", "group_list": "用户组列表", - "extended_info": "扩展信息" + "extended_info": "扩展信息", + "tab_operation_logs": "操作日志" + }, + "operation_logs": { + "title": "操作日志", + "all": "全部模块", + "search_placeholder": "搜索操作记录...", + "no_logs": "暂无操作记录", + "table_module": "模块", + "table_entity_id": "实体ID", + "table_user": "操作人", + "table_action": "操作类型", + "table_ip": "IP地址", + "table_remark": "备注", + "table_created_at": "操作时间", + "module_customer": "客户", + "module_purchase": "采购", + "module_schedule": "日程", + "module_warehouse": "仓库", + "module_work_order": "工单", + "action_create": "创建", + "action_update": "更新", + "action_delete": "删除", + "action_query": "查询", + "action_move": "移动" }, "customer": { "title": "客户管理", diff --git a/frontend/ops_vue_js/src/views/sysadmin/OperationLogsTab.vue b/frontend/ops_vue_js/src/views/sysadmin/OperationLogsTab.vue new file mode 100644 index 0000000..1c4e493 --- /dev/null +++ b/frontend/ops_vue_js/src/views/sysadmin/OperationLogsTab.vue @@ -0,0 +1,233 @@ + + + diff --git a/frontend/ops_vue_js/src/views/sysadmin/SysAdminView.vue b/frontend/ops_vue_js/src/views/sysadmin/SysAdminView.vue index e77a9e7..4670c0d 100644 --- a/frontend/ops_vue_js/src/views/sysadmin/SysAdminView.vue +++ b/frontend/ops_vue_js/src/views/sysadmin/SysAdminView.vue @@ -8,6 +8,7 @@ import { useRouter } from 'vue-router' import UsersTab from '@/views/sysadmin/UsersTab.vue' import GroupsTab from '@/views/sysadmin/GroupsTab.vue' import LogsTab from '@/views/sysadmin/LogsTab.vue' +import OperationLogsTab from '@/views/sysadmin/OperationLogsTab.vue' const { t } = useI18n() const userStore = useUserStore() @@ -22,11 +23,13 @@ const loading = ref(false) const usersTabRef = ref(null) const groupsTabRef = ref(null) const logsTabRef = ref(null) +const operationLogsTabRef = ref(null) const tabs = [ { id: 'users', label: t('sysadmin.tab_users') }, { id: 'groups', label: t('sysadmin.tab_groups') }, { id: 'logs', label: t('sysadmin.tab_logs') }, + { id: 'operation_logs', label: t('sysadmin.tab_operation_logs') }, { id: 'customer', label: t('customer.title'), to: '/customer' }, ] @@ -93,6 +96,7 @@ onMounted(() => { +