采购功能差不多了
This commit is contained in:
@@ -13,5 +13,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1776153171221
|
"lastUpdated": 1776154347507
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
# 2026-04-14
|
# 2026-04-14 工作记录
|
||||||
|
|
||||||
## 修复 mapstructure IgnoreUntaggedField 编译错误
|
## showorder.vue 编辑权限控制
|
||||||
- `DecoderConfig` 不存在 `IgnoreUntaggedField` 字段,移除后改为 JSON 中转方案(`json.Marshal` + `json.Unmarshal`),绕过 mapstructure 字段名匹配问题,编译通过。
|
|
||||||
|
|
||||||
## 新增订单编辑功能
|
- 后端 `/getorder` 接口返回 `canModify` 字段,由 `canModifyPurchase(user.ID, order.UserID)` 判断
|
||||||
- 抽取公共组件 `src/components/PurchaseOrderForm.vue`,供 addorder/editorder 共用(标题/备注/链接/款式标签/费用明细/图片上传)
|
- 前端 showorder.vue 编辑按钮条件改为 `v-if="canModify"`
|
||||||
- 创建 `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`(中英双语)
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package routers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"ops/models"
|
"ops/models"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,6 +11,36 @@ import (
|
|||||||
"gorm.io/gorm"
|
"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 的字段名匹配问题
|
// decodeJSON 将 map 通过 JSON 中转解码到目标结构体,绕过 mapstructure 的字段名匹配问题
|
||||||
func decodeJSON(data map[string]interface{}, out interface{}) error {
|
func decodeJSON(data map[string]interface{}, out interface{}) error {
|
||||||
jsonBytes, err := json.Marshal(data)
|
jsonBytes, err := json.Marshal(data)
|
||||||
@@ -102,12 +133,21 @@ func ApiPurchaseInit() {
|
|||||||
models.DB.AutoMigrate(&TabPurchaseLog{})
|
models.DB.AutoMigrate(&TabPurchaseLog{})
|
||||||
models.DB.AutoMigrate(&TabPurchaseCommit{})
|
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) {
|
func ApiPurchase(r *gin.RouterGroup) {
|
||||||
|
|
||||||
r.POST("/getorder", func(ctx *gin.Context) {
|
r.POST("/getorder", func(ctx *gin.Context) {
|
||||||
isAuth, _, data := AuthenticationAuthority(ctx)
|
isAuth, user, data := AuthenticationAuthority(ctx)
|
||||||
if !isAuth {
|
if !isAuth {
|
||||||
ReturnJson(ctx, "userCookieError", nil)
|
ReturnJson(ctx, "userCookieError", nil)
|
||||||
return
|
return
|
||||||
@@ -193,11 +233,15 @@ func ApiPurchase(r *gin.RouterGroup) {
|
|||||||
commitResps = append(commitResps, resp)
|
commitResps = append(commitResps, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断当前用户是否可以修改
|
||||||
|
canModify := canModifyPurchase(user.ID, order.UserID)
|
||||||
|
|
||||||
ReturnJson(ctx, "apiOK", gin.H{
|
ReturnJson(ctx, "apiOK", gin.H{
|
||||||
"order": order,
|
"order": order,
|
||||||
"costs": costs,
|
"canModify": canModify,
|
||||||
"photos": files,
|
"costs": costs,
|
||||||
"commits": commitResps,
|
"photos": files,
|
||||||
|
"commits": commitResps,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -337,20 +381,20 @@ func ApiPurchase(r *gin.RouterGroup) {
|
|||||||
|
|
||||||
if is_data_ok {
|
if is_data_ok {
|
||||||
|
|
||||||
//读取有多少条目
|
//读取有多少条目
|
||||||
var count int64
|
var count int64
|
||||||
query := models.DB.Model(TabPurchaseOrder{})
|
query := models.DB.Model(TabPurchaseOrder{})
|
||||||
if jsondata.Search != "" {
|
if jsondata.Search != "" {
|
||||||
query = query.Where("title LIKE ?", "%"+jsondata.Search+"%")
|
query = query.Where("title LIKE ?", "%"+jsondata.Search+"%")
|
||||||
}
|
}
|
||||||
if jsondata.Status != "" {
|
if jsondata.Status != "" {
|
||||||
query = query.Where("order_status = ?", jsondata.Status)
|
query = query.Where("order_status = ?", jsondata.Status)
|
||||||
}
|
}
|
||||||
query.Count(&count)
|
query.Count(&count)
|
||||||
|
|
||||||
//读取条目
|
//读取条目
|
||||||
var getorders []TabPurchaseOrder
|
var getorders []TabPurchaseOrder
|
||||||
query.Order("created_at DESC").Offset(jsondata.Entries * (jsondata.Page - 1)).Limit(jsondata.Entries).Find(&getorders)
|
query.Order("created_at DESC").Offset(jsondata.Entries * (jsondata.Page - 1)).Limit(jsondata.Entries).Find(&getorders)
|
||||||
|
|
||||||
ReturnJson(ctx, "apiOK", map[string]interface{}{
|
ReturnJson(ctx, "apiOK", map[string]interface{}{
|
||||||
"all_count": count,
|
"all_count": count,
|
||||||
@@ -557,6 +601,12 @@ func ApiPurchase(r *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 权限校验:只有创建者或管理员可以修改
|
||||||
|
if !canModifyPurchase(user.ID, order.UserID) {
|
||||||
|
ReturnJson(ctx, "no_permission", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 记录旧数据
|
// 记录旧数据
|
||||||
oldContent, _ := json.Marshal(order)
|
oldContent, _ := json.Marshal(order)
|
||||||
|
|
||||||
@@ -620,7 +670,7 @@ func ApiPurchase(r *gin.RouterGroup) {
|
|||||||
|
|
||||||
// 删除订单
|
// 删除订单
|
||||||
r.POST("/deleteorder", func(ctx *gin.Context) {
|
r.POST("/deleteorder", func(ctx *gin.Context) {
|
||||||
isAuth, _, data := AuthenticationAuthority(ctx)
|
isAuth, user, data := AuthenticationAuthority(ctx)
|
||||||
if !isAuth {
|
if !isAuth {
|
||||||
ReturnJson(ctx, "userCookieError", nil)
|
ReturnJson(ctx, "userCookieError", nil)
|
||||||
return
|
return
|
||||||
@@ -641,6 +691,12 @@ func ApiPurchase(r *gin.RouterGroup) {
|
|||||||
return
|
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(&TabPurchaseCosts{})
|
||||||
models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{})
|
models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{})
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const order = ref(null);
|
|||||||
const costs = ref([]);
|
const costs = ref([]);
|
||||||
const photos = ref([]);
|
const photos = ref([]);
|
||||||
const commits = ref([]);
|
const commits = ref([]);
|
||||||
|
const canModify = ref(false);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const notFound = ref(false);
|
const notFound = ref(false);
|
||||||
const updatingStatus = ref(false);
|
const updatingStatus = ref(false);
|
||||||
@@ -269,6 +270,7 @@ async function fetchOrder() {
|
|||||||
const { errCode, data } = await purchaseApi.getOrder(orderId.value);
|
const { errCode, data } = await purchaseApi.getOrder(orderId.value);
|
||||||
if (errCode === 0 && data) {
|
if (errCode === 0 && data) {
|
||||||
order.value = data.order ?? null;
|
order.value = data.order ?? null;
|
||||||
|
canModify.value = data.canModify ?? false;
|
||||||
costs.value = data.costs ?? [];
|
costs.value = data.costs ?? [];
|
||||||
photos.value = data.photos ?? [];
|
photos.value = data.photos ?? [];
|
||||||
commits.value = data.commits ?? [];
|
commits.value = data.commits ?? [];
|
||||||
@@ -299,7 +301,7 @@ onMounted(fetchOrder);
|
|||||||
</RouterLink>
|
</RouterLink>
|
||||||
<!-- 编辑按钮 -->
|
<!-- 编辑按钮 -->
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-if="order"
|
v-if="canModify"
|
||||||
:to="`/purchase/editorder/${order.ID}`"
|
:to="`/purchase/editorder/${order.ID}`"
|
||||||
class="inline-flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-dk-muted dark:bg-dk-card dark:text-gray-300 dark:hover:bg-dk-base"
|
class="inline-flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-dk-muted dark:bg-dk-card dark:text-gray-300 dark:hover:bg-dk-base"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -285,11 +285,20 @@ async function handleSubmit() {
|
|||||||
<div
|
<div
|
||||||
class="flex flex-col gap-6 rounded-xl border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
class="flex flex-col gap-6 rounded-xl border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card"
|
||||||
>
|
>
|
||||||
<!-- ==================== 订单信息区块 ==================== -->
|
<!-- ==================== 顶部标题栏 ==================== -->
|
||||||
<div class="border-b border-gray-200 px-6 py-4 dark:border-dk-muted">
|
<div class="flex items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-dk-muted">
|
||||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||||
{{ t("purchase_addorder.order_info") }}
|
{{ t("purchase_addorder.add_order") }}
|
||||||
</h4>
|
</h4>
|
||||||
|
<button
|
||||||
|
class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-dk-base"
|
||||||
|
@click="router.back()"
|
||||||
|
>
|
||||||
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
{{ t("purchase.back") }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 订单信息表单区域 -->
|
<!-- 订单信息表单区域 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user