做字典管理
This commit is contained in:
@@ -0,0 +1,815 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// BackendDictController 租户端数据字典:字典列表 + 字典项
|
||||
//
|
||||
// 表:yz_backend_dict / yz_backend_dict_item(建表 SQL:docs/sql/create_dict.sql)。
|
||||
// 按 tenant_id 隔离(取 JWT 里的租户ID,前端不传),与 yz_backend_erp_supplier 一致。
|
||||
// 平台端字典见 PlatformDictController(/platform/dict/*)。
|
||||
type BackendDictController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// globalDictTenantID 全局内置字典的租户标记:所有租户可见、只读,
|
||||
// 与 yz_system_admin_role 中 tenant_id=0 的「全局租户角色」同一套约定。
|
||||
// 租户要改动时就按相同 dict_code 建一份自己的字典覆盖它。
|
||||
const globalDictTenantID = "0"
|
||||
|
||||
var backendDictCodeRe = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{0,99}$`)
|
||||
|
||||
var backendDictTagTypes = map[string]bool{
|
||||
"": true,
|
||||
"primary": true,
|
||||
"success": true,
|
||||
"warning": true,
|
||||
"danger": true,
|
||||
"info": true,
|
||||
}
|
||||
|
||||
func (c *BackendDictController) dictClaims() (*jwtutil.Claims, error) {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return nil, fmt.Errorf("未登录")
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, fmt.Errorf("认证信息格式错误")
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
if claims.UserType != "backend" {
|
||||
return nil, fmt.Errorf("无权访问")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (c *BackendDictController) dictErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *BackendDictController) dictOk(data interface{}, msg string) {
|
||||
if msg == "" {
|
||||
msg = "success"
|
||||
}
|
||||
resp := map[string]interface{}{"code": 200, "msg": msg}
|
||||
if data != nil {
|
||||
resp["data"] = data
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type backendDictTypePayload struct {
|
||||
DictName string `json:"dict_name"`
|
||||
DictCode string `json:"dict_code"`
|
||||
Remark string `json:"remark"`
|
||||
Sort int `json:"sort"`
|
||||
Status *int8 `json:"status"`
|
||||
}
|
||||
|
||||
type backendDictItemPayload struct {
|
||||
DictID uint64 `json:"dict_id"`
|
||||
ItemLabel string `json:"item_label"`
|
||||
ItemValue string `json:"item_value"`
|
||||
TagType string `json:"tag_type"`
|
||||
Remark string `json:"remark"`
|
||||
Sort int `json:"sort"`
|
||||
Status *int8 `json:"status"`
|
||||
IsDefault *int8 `json:"is_default"`
|
||||
}
|
||||
|
||||
// backendDictRow 字典列表行:在实体上追加「是否全局内置」,供前端禁用编辑/删除
|
||||
type backendDictRow struct {
|
||||
models.BackendDict
|
||||
IsGlobal bool `json:"is_global"`
|
||||
}
|
||||
|
||||
// dictTenantScope 当前请求可见的字典租户范围:全局内置 + 本租户自己的
|
||||
func dictTenantScope(claims *jwtutil.Claims) []string {
|
||||
return []string{globalDictTenantID, strconv.Itoa(claims.TenantId)}
|
||||
}
|
||||
|
||||
// findVisibleDict 按编码取当前租户可见的字典:优先本租户自己的,没有再回落全局内置
|
||||
func findVisibleDict(tenantID, code string) (*models.BackendDict, error) {
|
||||
var dicts []models.BackendDict
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("dict_code", code).
|
||||
Filter("tenant_id__in", []string{globalDictTenantID, tenantID}).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&dicts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range dicts {
|
||||
if dicts[i].TenantID == tenantID {
|
||||
return &dicts[i], nil
|
||||
}
|
||||
}
|
||||
if len(dicts) > 0 {
|
||||
return &dicts[0], nil
|
||||
}
|
||||
return nil, orm.ErrNoRows
|
||||
}
|
||||
|
||||
// isGlobalDictRow 全局内置字典:任何租户都不允许修改 / 删除
|
||||
func isGlobalDictRow(tenantID string) bool { return tenantID == globalDictTenantID }
|
||||
|
||||
/* ---------------- 字典列表 ---------------- */
|
||||
|
||||
// ListTypes GET /backend/dict/types
|
||||
func (c *BackendDictController) ListTypes() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
page, pageSize, offset := dictPageSize(&c.Controller)
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
status := strings.TrimSpace(c.GetString("status"))
|
||||
|
||||
// 可见范围:全局内置 + 本租户自己的(同编码时两条都会出现,各自带 is_global 标记)
|
||||
cond := orm.NewCondition().
|
||||
And("tenant_id__in", dictTenantScope(claims)).
|
||||
And("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
cond = cond.AndCond(orm.NewCondition().
|
||||
Or("dict_name__contains", keyword).
|
||||
Or("dict_code__contains", keyword))
|
||||
}
|
||||
if status != "" {
|
||||
if v, err := strconv.Atoi(status); err == nil {
|
||||
cond = cond.And("status", v)
|
||||
}
|
||||
}
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.BackendDict)).SetCond(cond)
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
var list []models.BackendDict
|
||||
if _, err := qs.OrderBy("sort", "id").Limit(pageSize, offset).All(&list); err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
rows := make([]backendDictRow, 0, len(list))
|
||||
for _, r := range list {
|
||||
rows = append(rows, backendDictRow{BackendDict: r, IsGlobal: isGlobalDictRow(r.TenantID)})
|
||||
}
|
||||
c.dictOk(map[string]interface{}{
|
||||
"list": rows,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"pageSize": pageSize,
|
||||
}, "")
|
||||
}
|
||||
|
||||
// CreateType POST /backend/dict/types
|
||||
func (c *BackendDictController) CreateType() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
var p backendDictTypePayload
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
c.dictErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
p.DictName = strings.TrimSpace(p.DictName)
|
||||
p.DictCode = strings.TrimSpace(p.DictCode)
|
||||
p.Remark = strings.TrimSpace(p.Remark)
|
||||
if p.DictName == "" {
|
||||
c.dictErr(400, 400, "字典名称不能为空")
|
||||
return
|
||||
}
|
||||
if !backendDictCodeRe.MatchString(p.DictCode) {
|
||||
c.dictErr(400, 400, "字典编码以字母开头,只能包含字母、数字与下划线")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := fmt.Sprintf("%d", claims.TenantId)
|
||||
exist, _ := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("tenant_id", tenantID).
|
||||
Filter("dict_code", p.DictCode).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if exist > 0 {
|
||||
c.dictErr(400, 400, "字典编码已存在")
|
||||
return
|
||||
}
|
||||
|
||||
status := int8(1)
|
||||
if p.Status != nil {
|
||||
status = *p.Status
|
||||
}
|
||||
now := time.Now()
|
||||
row := &models.BackendDict{
|
||||
TenantID: tenantID,
|
||||
DictName: p.DictName,
|
||||
DictCode: p.DictCode,
|
||||
Remark: p.Remark,
|
||||
Sort: p.Sort,
|
||||
Status: status,
|
||||
CreateUserID: strconv.Itoa(claims.UserID),
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(row)
|
||||
if err != nil {
|
||||
c.dictErr(500, 500, "新增失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
row.ID = uint64(id)
|
||||
c.dictOk(row, "新增成功")
|
||||
}
|
||||
|
||||
// UpdateType PUT /backend/dict/types/:id
|
||||
// 编码是业务取值依据,不允许修改。
|
||||
func (c *BackendDictController) UpdateType() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
id := dictPathID(&c.Controller)
|
||||
if id == 0 {
|
||||
c.dictErr(400, 400, "无效的ID")
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
var p backendDictTypePayload
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
c.dictErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
p.DictName = strings.TrimSpace(p.DictName)
|
||||
p.Remark = strings.TrimSpace(p.Remark)
|
||||
if p.DictName == "" {
|
||||
c.dictErr(400, 400, "字典名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var row models.BackendDict
|
||||
if err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("id", id).
|
||||
Filter("tenant_id__in", dictTenantScope(claims)).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
c.dictErr(404, 404, "字典不存在")
|
||||
return
|
||||
}
|
||||
if isGlobalDictRow(row.TenantID) {
|
||||
c.dictErr(403, 403, "系统内置字典不可修改,如需调整请在当前租户下按相同编码新建一份")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row.DictName = p.DictName
|
||||
row.Remark = p.Remark
|
||||
row.Sort = p.Sort
|
||||
if p.Status != nil {
|
||||
row.Status = *p.Status
|
||||
}
|
||||
row.UpdateTime = &now
|
||||
if _, err := models.Orm.Update(&row); err != nil {
|
||||
c.dictErr(500, 500, "保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.dictOk(row, "保存成功")
|
||||
}
|
||||
|
||||
// DeleteType DELETE /backend/dict/types/:id
|
||||
// 字典下还有字典项时拒绝删除,避免留下孤儿数据。
|
||||
func (c *BackendDictController) DeleteType() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
id := dictPathID(&c.Controller)
|
||||
if id == 0 {
|
||||
c.dictErr(400, 400, "无效的ID")
|
||||
return
|
||||
}
|
||||
var row models.BackendDict
|
||||
if err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("id", id).
|
||||
Filter("tenant_id__in", dictTenantScope(claims)).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
c.dictErr(404, 404, "字典不存在")
|
||||
return
|
||||
}
|
||||
if isGlobalDictRow(row.TenantID) {
|
||||
c.dictErr(403, 403, "系统内置字典不可删除,如需调整请在当前租户下按相同编码新建一份")
|
||||
return
|
||||
}
|
||||
cnt, _ := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("dict_id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
c.dictErr(400, 400, fmt.Sprintf("该字典下还有 %d 个字典项,请先删除字典项", cnt))
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("id", id).
|
||||
Update(orm.Params{"delete_time": now, "update_time": now}); err != nil {
|
||||
c.dictErr(500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.dictOk(nil, "删除成功")
|
||||
}
|
||||
|
||||
/* ---------------- 字典项 ---------------- */
|
||||
|
||||
// ListItems GET /backend/dict/items
|
||||
func (c *BackendDictController) ListItems() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
dictID, _ := c.GetInt64("dict_id")
|
||||
if dictID <= 0 {
|
||||
c.dictErr(400, 400, "dict_id 不能为空")
|
||||
return
|
||||
}
|
||||
// 字典必须在本租户可见范围内(自己的或全局内置),避免越权读取他人字典
|
||||
var dict models.BackendDict
|
||||
if err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("id", dictID).
|
||||
Filter("tenant_id__in", dictTenantScope(claims)).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&dict); err != nil {
|
||||
c.dictErr(404, 404, "字典不存在")
|
||||
return
|
||||
}
|
||||
|
||||
page, pageSize, offset := dictPageSize(&c.Controller)
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
status := strings.TrimSpace(c.GetString("status"))
|
||||
|
||||
// 字典项跟随所属字典的租户:全局内置字典的项对所有租户可见
|
||||
cond := orm.NewCondition().
|
||||
And("tenant_id", dict.TenantID).
|
||||
And("dict_id", dictID).
|
||||
And("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
cond = cond.AndCond(orm.NewCondition().
|
||||
Or("item_label__contains", keyword).
|
||||
Or("item_value__contains", keyword))
|
||||
}
|
||||
if status != "" {
|
||||
if v, err := strconv.Atoi(status); err == nil {
|
||||
cond = cond.And("status", v)
|
||||
}
|
||||
}
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.BackendDictItem)).SetCond(cond)
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
var list []models.BackendDictItem
|
||||
if _, err := qs.OrderBy("sort", "id").Limit(pageSize, offset).All(&list); err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.dictOk(map[string]interface{}{
|
||||
"list": list,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"pageSize": pageSize,
|
||||
}, "")
|
||||
}
|
||||
|
||||
// CreateItem POST /backend/dict/items
|
||||
func (c *BackendDictController) CreateItem() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
var p backendDictItemPayload
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
c.dictErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
p.ItemLabel = strings.TrimSpace(p.ItemLabel)
|
||||
p.ItemValue = strings.TrimSpace(p.ItemValue)
|
||||
p.TagType = strings.TrimSpace(p.TagType)
|
||||
p.Remark = strings.TrimSpace(p.Remark)
|
||||
if p.DictID == 0 {
|
||||
c.dictErr(400, 400, "dict_id 不能为空")
|
||||
return
|
||||
}
|
||||
if p.ItemLabel == "" {
|
||||
c.dictErr(400, 400, "字典项名称不能为空")
|
||||
return
|
||||
}
|
||||
if p.ItemValue == "" {
|
||||
c.dictErr(400, 400, "字典项值不能为空")
|
||||
return
|
||||
}
|
||||
if !backendDictTagTypes[p.TagType] {
|
||||
c.dictErr(400, 400, "标签色只能是 primary/success/warning/danger/info 之一")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := fmt.Sprintf("%d", claims.TenantId)
|
||||
var dict models.BackendDict
|
||||
if err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("id", p.DictID).
|
||||
Filter("tenant_id__in", dictTenantScope(claims)).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&dict); err != nil {
|
||||
c.dictErr(404, 404, "字典不存在")
|
||||
return
|
||||
}
|
||||
if isGlobalDictRow(dict.TenantID) {
|
||||
c.dictErr(403, 403, "系统内置字典不可新增字典项,如需调整请在当前租户下按相同编码新建一份")
|
||||
return
|
||||
}
|
||||
cnt, _ := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("tenant_id", tenantID).
|
||||
Filter("dict_id", p.DictID).
|
||||
Filter("item_value", p.ItemValue).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
c.dictErr(400, 400, "同一字典下的字典项值不能重复")
|
||||
return
|
||||
}
|
||||
|
||||
status := int8(1)
|
||||
if p.Status != nil {
|
||||
status = *p.Status
|
||||
}
|
||||
isDefault := int8(0)
|
||||
if p.IsDefault != nil {
|
||||
isDefault = *p.IsDefault
|
||||
}
|
||||
now := time.Now()
|
||||
row := &models.BackendDictItem{
|
||||
TenantID: tenantID,
|
||||
DictID: p.DictID,
|
||||
ItemLabel: p.ItemLabel,
|
||||
ItemValue: p.ItemValue,
|
||||
TagType: p.TagType,
|
||||
Sort: p.Sort,
|
||||
Status: status,
|
||||
IsDefault: isDefault,
|
||||
Remark: p.Remark,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(row)
|
||||
if err != nil {
|
||||
c.dictErr(500, 500, "新增失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
row.ID = uint64(id)
|
||||
c.dictOk(row, "新增成功")
|
||||
}
|
||||
|
||||
// UpdateItem PUT /backend/dict/items/:id
|
||||
// 字典项值是业务入库值,不允许修改,避免存量数据对不上。
|
||||
func (c *BackendDictController) UpdateItem() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
id := dictPathID(&c.Controller)
|
||||
if id == 0 {
|
||||
c.dictErr(400, 400, "无效的ID")
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
var p backendDictItemPayload
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
c.dictErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
p.ItemLabel = strings.TrimSpace(p.ItemLabel)
|
||||
p.TagType = strings.TrimSpace(p.TagType)
|
||||
p.Remark = strings.TrimSpace(p.Remark)
|
||||
if p.ItemLabel == "" {
|
||||
c.dictErr(400, 400, "字典项名称不能为空")
|
||||
return
|
||||
}
|
||||
if !backendDictTagTypes[p.TagType] {
|
||||
c.dictErr(400, 400, "标签色只能是 primary/success/warning/danger/info 之一")
|
||||
return
|
||||
}
|
||||
|
||||
var row models.BackendDictItem
|
||||
if err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("id", id).
|
||||
Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
c.dictErr(404, 404, "字典项不存在")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
row.ItemLabel = p.ItemLabel
|
||||
row.TagType = p.TagType
|
||||
row.Sort = p.Sort
|
||||
row.Remark = p.Remark
|
||||
if p.Status != nil {
|
||||
row.Status = *p.Status
|
||||
}
|
||||
if p.IsDefault != nil {
|
||||
row.IsDefault = *p.IsDefault
|
||||
}
|
||||
row.UpdateTime = &now
|
||||
if _, err := models.Orm.Update(&row); err != nil {
|
||||
c.dictErr(500, 500, "保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.dictOk(row, "保存成功")
|
||||
}
|
||||
|
||||
// ToggleItemStatus POST /backend/dict/items/:id/status
|
||||
func (c *BackendDictController) ToggleItemStatus() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
id := dictPathID(&c.Controller)
|
||||
if id == 0 {
|
||||
c.dictErr(400, 400, "无效的ID")
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
var p struct {
|
||||
Status *int8 `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &p); err != nil || p.Status == nil {
|
||||
c.dictErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if *p.Status != 0 && *p.Status != 1 {
|
||||
c.dictErr(400, 400, "状态只能是 0 或 1")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := fmt.Sprintf("%d", claims.TenantId)
|
||||
var row models.BackendDictItem
|
||||
if err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("id", id).
|
||||
Filter("tenant_id", tenantID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
c.dictErr(404, 404, "字典项不存在")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("id", id).
|
||||
Update(orm.Params{"status": *p.Status, "update_time": now}); err != nil {
|
||||
c.dictErr(500, 500, "操作失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.dictOk(nil, "操作成功")
|
||||
}
|
||||
|
||||
// DeleteItem DELETE /backend/dict/items/:id
|
||||
func (c *BackendDictController) DeleteItem() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
id := dictPathID(&c.Controller)
|
||||
if id == 0 {
|
||||
c.dictErr(400, 400, "无效的ID")
|
||||
return
|
||||
}
|
||||
var row models.BackendDictItem
|
||||
if err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("id", id).
|
||||
Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
c.dictErr(404, 404, "字典项不存在")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("id", id).
|
||||
Update(orm.Params{"delete_time": now, "update_time": now}); err != nil {
|
||||
c.dictErr(500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.dictOk(nil, "删除成功")
|
||||
}
|
||||
|
||||
/* ---------------- 业务读取 ---------------- */
|
||||
|
||||
// OptionsAll GET /backend/dict/options
|
||||
// 一次取回本租户可见的全部「启用」字典项(全局内置 + 本租户自己的,同编码租户优先),
|
||||
// 供前端登录后预热缓存:之后页面按任意 dict_code 都能同步读到。
|
||||
func (c *BackendDictController) OptionsAll() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tenantID := strconv.Itoa(claims.TenantId)
|
||||
result := map[string][]models.BackendDictItem{}
|
||||
|
||||
var dicts []models.BackendDict
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("tenant_id__in", dictTenantScope(claims)).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&dicts); err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
// 同编码优先本租户:先记录全局,再用租户自己的覆盖
|
||||
chosen := map[string]uint64{}
|
||||
for _, d := range dicts {
|
||||
if _, ok := chosen[d.DictCode]; !ok || d.TenantID == tenantID {
|
||||
chosen[d.DictCode] = d.ID
|
||||
}
|
||||
}
|
||||
if len(chosen) == 0 {
|
||||
c.dictOk(map[string]interface{}{"dicts": result}, "")
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]uint64, 0, len(chosen))
|
||||
codeByID := make(map[uint64]string, len(chosen))
|
||||
for code, id := range chosen {
|
||||
ids = append(ids, id)
|
||||
codeByID[id] = code
|
||||
result[code] = []models.BackendDictItem{}
|
||||
}
|
||||
|
||||
var items []models.BackendDictItem
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("dict_id__in", ids).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort", "id").
|
||||
All(&items); err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
for _, it := range items {
|
||||
code := codeByID[it.DictID]
|
||||
result[code] = append(result[code], it)
|
||||
}
|
||||
c.dictOk(map[string]interface{}{"dicts": result}, "")
|
||||
}
|
||||
|
||||
// OptionsBatch POST /backend/dict/options
|
||||
// 一次取多个字典的启用项,供前端预加载(body: {"codes":[...]})。
|
||||
// 同编码时优先本租户自己的字典,没有再回落全局内置字典。
|
||||
func (c *BackendDictController) OptionsBatch() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
var p struct {
|
||||
Codes []string `json:"codes"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &p); err != nil {
|
||||
c.dictErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string][]models.BackendDictItem{}
|
||||
codes := make([]string, 0, len(p.Codes))
|
||||
for _, code := range p.Codes {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := result[code]; ok {
|
||||
continue
|
||||
}
|
||||
result[code] = []models.BackendDictItem{}
|
||||
codes = append(codes, code)
|
||||
}
|
||||
if len(codes) == 0 {
|
||||
c.dictOk(map[string]interface{}{"dicts": result}, "")
|
||||
return
|
||||
}
|
||||
|
||||
tenantID := strconv.Itoa(claims.TenantId)
|
||||
var dicts []models.BackendDict
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDict)).
|
||||
Filter("dict_code__in", codes).
|
||||
Filter("tenant_id__in", dictTenantScope(claims)).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&dicts); err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 同编码优先本租户:先记录全局,再用租户自己的覆盖
|
||||
chosen := map[string]uint64{}
|
||||
for _, d := range dicts {
|
||||
if _, ok := chosen[d.DictCode]; !ok || d.TenantID == tenantID {
|
||||
chosen[d.DictCode] = d.ID
|
||||
}
|
||||
}
|
||||
if len(chosen) == 0 {
|
||||
c.dictOk(map[string]interface{}{"dicts": result}, "")
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]uint64, 0, len(chosen))
|
||||
codeByID := make(map[uint64]string, len(chosen))
|
||||
for code, id := range chosen {
|
||||
ids = append(ids, id)
|
||||
codeByID[id] = code
|
||||
}
|
||||
|
||||
var items []models.BackendDictItem
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("dict_id__in", ids).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort", "id").
|
||||
All(&items); err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
for _, it := range items {
|
||||
code := codeByID[it.DictID]
|
||||
result[code] = append(result[code], it)
|
||||
}
|
||||
c.dictOk(map[string]interface{}{"dicts": result}, "")
|
||||
}
|
||||
|
||||
// Options GET /backend/dict/options/:code
|
||||
// 业务页面按编码取选项:只返回「字典与字典项都启用」的项,按 sort 升序。
|
||||
func (c *BackendDictController) Options() {
|
||||
claims, err := c.dictClaims()
|
||||
if err != nil {
|
||||
c.dictErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
code := strings.TrimSpace(c.Ctx.Input.Param(":code"))
|
||||
if code == "" {
|
||||
c.dictErr(400, 400, "字典编码不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 优先取本租户自己的字典,没有再回落全局内置字典
|
||||
dict, err := findVisibleDict(strconv.Itoa(claims.TenantId), code)
|
||||
if err != nil {
|
||||
c.dictErr(404, 404, "字典不存在或已停用")
|
||||
return
|
||||
}
|
||||
|
||||
var list []models.BackendDictItem
|
||||
if _, err := models.Orm.QueryTable(new(models.BackendDictItem)).
|
||||
Filter("dict_id", dict.ID).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort", "id").
|
||||
All(&list); err != nil {
|
||||
c.dictErr(500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.dictOk(map[string]interface{}{
|
||||
"dict_id": dict.ID,
|
||||
"dict_code": dict.DictCode,
|
||||
"dict_name": dict.DictName,
|
||||
"list": list,
|
||||
}, "")
|
||||
}
|
||||
Reference in New Issue
Block a user