diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json index 0768fd6..544ff68 100644 --- a/.workbuddy/expert-history.json +++ b/.workbuddy/expert-history.json @@ -13,5 +13,5 @@ } ] }, - "lastUpdated": 1776153171221 + "lastUpdated": 1776154347507 } \ No newline at end of file diff --git a/.workbuddy/memory/2026-04-14.md b/.workbuddy/memory/2026-04-14.md index b99443f..9d14c43 100644 --- a/.workbuddy/memory/2026-04-14.md +++ b/.workbuddy/memory/2026-04-14.md @@ -1,12 +1,6 @@ -# 2026-04-14 +# 2026-04-14 工作记录 -## 修复 mapstructure IgnoreUntaggedField 编译错误 -- `DecoderConfig` 不存在 `IgnoreUntaggedField` 字段,移除后改为 JSON 中转方案(`json.Marshal` + `json.Unmarshal`),绕过 mapstructure 字段名匹配问题,编译通过。 +## showorder.vue 编辑权限控制 -## 新增订单编辑功能 -- 抽取公共组件 `src/components/PurchaseOrderForm.vue`,供 addorder/editorder 共用(标题/备注/链接/款式标签/费用明细/图片上传) -- 创建 `src/views/purchase/editorder.vue`:路由参数 `:id` 加载订单 → 回填 → 调用 `/purchase/updateorder` 保存 -- 后端新增 `POST /purchase/updateorder` 接口:更新基本字段 + 重建费用明细 + 重建图片绑定 + 写操作日志,编译通过 -- 前端 `purchase.js` 新增 `updateOrder(id, data)` 方法 -- 注册路由 `purchase/editorder/:id`,ShowOrder.vue 顶部加编辑按钮 -- i18n 补充:`purchase.edit_order`、`purchase_addorder.edit_order`、`message.loading`(中英双语) +- 后端 `/getorder` 接口返回 `canModify` 字段,由 `canModifyPurchase(user.ID, order.UserID)` 判断 +- 前端 showorder.vue 编辑按钮条件改为 `v-if="canModify"` diff --git a/backend/my_work/routers/apiPurchase.go b/backend/my_work/routers/apiPurchase.go index 6e8e380..82bebc1 100644 --- a/backend/my_work/routers/apiPurchase.go +++ b/backend/my_work/routers/apiPurchase.go @@ -3,6 +3,7 @@ package routers import ( "encoding/json" "ops/models" + "slices" "strings" "time" @@ -10,6 +11,36 @@ import ( "gorm.io/gorm" ) +var ( + purchaseUserGroup models.TabUserGroups_ + purchaseAdmins []uint +) + +// 更新管理员成员缓存 +func updatePurchaseAdminsCash() { + purchaseAdmins = nil + // id 1 是系统管理员 + purchaseAdmins = append(purchaseAdmins, 1) + var binds []models.TabUserGroupBinds_ + models.DB.Where("group_id = ?", purchaseUserGroup.ID).Find(&binds) + for _, item := range binds { + if !slices.Contains(purchaseAdmins, item.UserID) { + purchaseAdmins = append(purchaseAdmins, item.UserID) + } + } +} + +// 判断用户是否有权限修改/删除订单(创建者或管理员) +func canModifyPurchase(userID, creatorUserID uint) bool { + if slices.Contains(purchaseAdmins, userID) { + return true + } + if userID == creatorUserID { + return true + } + return false +} + // decodeJSON 将 map 通过 JSON 中转解码到目标结构体,绕过 mapstructure 的字段名匹配问题 func decodeJSON(data map[string]interface{}, out interface{}) error { jsonBytes, err := json.Marshal(data) @@ -102,12 +133,21 @@ func ApiPurchaseInit() { models.DB.AutoMigrate(&TabPurchaseLog{}) models.DB.AutoMigrate(&TabPurchaseCommit{}) + //先检查用户组有没有这个key purchase + purchaseUserGroup.Name = "purchase_admin" + if models.DB.Where(&purchaseUserGroup).First(&purchaseUserGroup).Error == nil { + updatePurchaseAdminsCash() + } else { + purchaseUserGroup.Type = "usergroup" + models.DB.Create(&purchaseUserGroup) + } + } func ApiPurchase(r *gin.RouterGroup) { r.POST("/getorder", func(ctx *gin.Context) { - isAuth, _, data := AuthenticationAuthority(ctx) + isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return @@ -193,11 +233,15 @@ func ApiPurchase(r *gin.RouterGroup) { commitResps = append(commitResps, resp) } + // 判断当前用户是否可以修改 + canModify := canModifyPurchase(user.ID, order.UserID) + ReturnJson(ctx, "apiOK", gin.H{ - "order": order, - "costs": costs, - "photos": files, - "commits": commitResps, + "order": order, + "canModify": canModify, + "costs": costs, + "photos": files, + "commits": commitResps, }) }) @@ -337,20 +381,20 @@ func ApiPurchase(r *gin.RouterGroup) { if is_data_ok { - //读取有多少条目 - var count int64 - query := models.DB.Model(TabPurchaseOrder{}) - if jsondata.Search != "" { - query = query.Where("title LIKE ?", "%"+jsondata.Search+"%") - } - if jsondata.Status != "" { - query = query.Where("order_status = ?", jsondata.Status) - } - query.Count(&count) + //读取有多少条目 + var count int64 + query := models.DB.Model(TabPurchaseOrder{}) + if jsondata.Search != "" { + query = query.Where("title LIKE ?", "%"+jsondata.Search+"%") + } + if jsondata.Status != "" { + query = query.Where("order_status = ?", jsondata.Status) + } + query.Count(&count) - //读取条目 - var getorders []TabPurchaseOrder - query.Order("created_at DESC").Offset(jsondata.Entries * (jsondata.Page - 1)).Limit(jsondata.Entries).Find(&getorders) + //读取条目 + var getorders []TabPurchaseOrder + query.Order("created_at DESC").Offset(jsondata.Entries * (jsondata.Page - 1)).Limit(jsondata.Entries).Find(&getorders) ReturnJson(ctx, "apiOK", map[string]interface{}{ "all_count": count, @@ -557,6 +601,12 @@ func ApiPurchase(r *gin.RouterGroup) { return } + // 权限校验:只有创建者或管理员可以修改 + if !canModifyPurchase(user.ID, order.UserID) { + ReturnJson(ctx, "no_permission", nil) + return + } + // 记录旧数据 oldContent, _ := json.Marshal(order) @@ -620,7 +670,7 @@ func ApiPurchase(r *gin.RouterGroup) { // 删除订单 r.POST("/deleteorder", func(ctx *gin.Context) { - isAuth, _, data := AuthenticationAuthority(ctx) + isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return @@ -641,6 +691,12 @@ func ApiPurchase(r *gin.RouterGroup) { return } + // 权限校验:只有创建者或管理员可以删除 + if !canModifyPurchase(user.ID, order.UserID) { + ReturnJson(ctx, "no_permission", nil) + return + } + // 关联删除(硬删,不保留) models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseCosts{}) models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{}) diff --git a/frontend/ops_vue_js/src/views/purchase/ShowOrder.vue b/frontend/ops_vue_js/src/views/purchase/ShowOrder.vue index 2564678..ba3dcaa 100644 --- a/frontend/ops_vue_js/src/views/purchase/ShowOrder.vue +++ b/frontend/ops_vue_js/src/views/purchase/ShowOrder.vue @@ -32,6 +32,7 @@ const order = ref(null); const costs = ref([]); const photos = ref([]); const commits = ref([]); +const canModify = ref(false); const loading = ref(true); const notFound = ref(false); const updatingStatus = ref(false); @@ -269,6 +270,7 @@ async function fetchOrder() { const { errCode, data } = await purchaseApi.getOrder(orderId.value); if (errCode === 0 && data) { order.value = data.order ?? null; + canModify.value = data.canModify ?? false; costs.value = data.costs ?? []; photos.value = data.photos ?? []; commits.value = data.commits ?? []; @@ -299,7 +301,7 @@ onMounted(fetchOrder); diff --git a/frontend/ops_vue_js/src/views/purchase/addorder.vue b/frontend/ops_vue_js/src/views/purchase/addorder.vue index 57c5119..378f0b1 100644 --- a/frontend/ops_vue_js/src/views/purchase/addorder.vue +++ b/frontend/ops_vue_js/src/views/purchase/addorder.vue @@ -285,11 +285,20 @@ async function handleSubmit() {
- -
+ +

- {{ t("purchase_addorder.order_info") }} + {{ t("purchase_addorder.add_order") }}

+