做字典管理

This commit is contained in:
2026-09-28 23:16:26 +08:00
parent 7c75edcc61
commit c49e8d0d3f
59 changed files with 5005 additions and 4673 deletions
+815
View File
@@ -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,
}, "")
}