-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.item_value }}
+
+
+
+
+ {{ row.tag_type }}
+ -
+
+
+
+
+
+ 默认
+ -
+
+
+
+
+
+ {{ row.status === 1 ? '启用' : '停用' }}
+
+
+
+
+
+
+
+ 编辑
+
+
+ {{ row.status === 1 ? '停用' : '启用' }}
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/go/controllers/backend_dict.go b/go/controllers/backend_dict.go
new file mode 100644
index 0000000..30ce2bc
--- /dev/null
+++ b/go/controllers/backend_dict.go
@@ -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,
+ }, "")
+}
diff --git a/go/controllers/platform_dict.go b/go/controllers/platform_dict.go
new file mode 100644
index 0000000..27872d0
--- /dev/null
+++ b/go/controllers/platform_dict.go
@@ -0,0 +1,733 @@
+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"
+)
+
+// PlatformDictController 平台端数据字典:字典列表 + 字典项
+//
+// 表:yz_platform_dict / yz_platform_dict_item(建表 SQL:docs/sql/create_dict.sql)。
+// 平台端字典不按租户隔离;租户端字典见 BackendDictController(/backend/dict/*)。
+type PlatformDictController struct {
+ beego.Controller
+}
+
+// dictCodeRe 字典编码:字母开头,仅字母/数字/下划线
+var dictCodeRe = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{0,99}$`)
+
+// dictTagTypes 字典项标签色(与前端 el-tag 的 type 对齐),空串表示不渲染标签
+var dictTagTypes = map[string]bool{
+ "": true,
+ "primary": true,
+ "success": true,
+ "warning": true,
+ "danger": true,
+ "info": true,
+}
+
+func (c *PlatformDictController) 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 != "platform" {
+ return nil, fmt.Errorf("无权访问")
+ }
+ return claims, nil
+}
+
+func (c *PlatformDictController) 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 *PlatformDictController) 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()
+}
+
+// dictPageSize 统一分页参数处理:page 从 1 开始,pageSize 限制在 1~100
+func dictPageSize(c *beego.Controller) (page, pageSize, offset int) {
+ page, _ = c.GetInt("page", 1)
+ pageSize, _ = c.GetInt("pageSize", 20)
+ if page < 1 {
+ page = 1
+ }
+ if pageSize < 1 || pageSize > 100 {
+ pageSize = 20
+ }
+ return page, pageSize, (page - 1) * pageSize
+}
+
+type dictTypePayload struct {
+ DictName string `json:"dict_name"`
+ DictCode string `json:"dict_code"`
+ Remark string `json:"remark"`
+ Sort int `json:"sort"`
+ Status *int8 `json:"status"`
+}
+
+type dictItemPayload 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"`
+}
+
+func parseDictTypeBody(c *beego.Controller) (*dictTypePayload, error) {
+ body, _ := io.ReadAll(c.Ctx.Request.Body)
+ var p dictTypePayload
+ if err := json.Unmarshal(body, &p); err != nil {
+ return nil, fmt.Errorf("参数错误")
+ }
+ p.DictName = strings.TrimSpace(p.DictName)
+ p.DictCode = strings.TrimSpace(p.DictCode)
+ p.Remark = strings.TrimSpace(p.Remark)
+ return &p, nil
+}
+
+func parseDictItemBody(c *beego.Controller) (*dictItemPayload, error) {
+ body, _ := io.ReadAll(c.Ctx.Request.Body)
+ var p dictItemPayload
+ if err := json.Unmarshal(body, &p); err != nil {
+ return nil, fmt.Errorf("参数错误")
+ }
+ p.ItemLabel = strings.TrimSpace(p.ItemLabel)
+ p.ItemValue = strings.TrimSpace(p.ItemValue)
+ p.TagType = strings.TrimSpace(p.TagType)
+ p.Remark = strings.TrimSpace(p.Remark)
+ return &p, nil
+}
+
+func dictPathID(c *beego.Controller) uint64 {
+ id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
+ return id
+}
+
+/* ---------------- 字典列表 ---------------- */
+
+// ListTypes GET /platform/dict/types
+// 参数:page / pageSize / keyword(名称或编码模糊)/ status
+func (c *PlatformDictController) ListTypes() {
+ if _, err := c.dictClaims(); 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"))
+
+ cond := orm.NewCondition().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.PlatformDict)).SetCond(cond)
+ total, err := qs.Count()
+ if err != nil {
+ c.dictErr(500, 500, "查询失败: "+err.Error())
+ return
+ }
+ var list []models.PlatformDict
+ 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,
+ }, "")
+}
+
+// CreateType POST /platform/dict/types
+func (c *PlatformDictController) CreateType() {
+ claims, err := c.dictClaims()
+ if err != nil {
+ c.dictErr(401, 401, err.Error())
+ return
+ }
+ p, err := parseDictTypeBody(&c.Controller)
+ if err != nil {
+ c.dictErr(400, 400, err.Error())
+ return
+ }
+ if p.DictName == "" {
+ c.dictErr(400, 400, "字典名称不能为空")
+ return
+ }
+ if !dictCodeRe.MatchString(p.DictCode) {
+ c.dictErr(400, 400, "字典编码以字母开头,只能包含字母、数字与下划线")
+ return
+ }
+ exist, _ := models.Orm.QueryTable(new(models.PlatformDict)).
+ 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.PlatformDict{
+ 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 /platform/dict/types/:id
+// 编码是业务取值依据,不允许修改。
+func (c *PlatformDictController) UpdateType() {
+ if _, err := c.dictClaims(); err != nil {
+ c.dictErr(401, 401, err.Error())
+ return
+ }
+ id := dictPathID(&c.Controller)
+ if id == 0 {
+ c.dictErr(400, 400, "无效的ID")
+ return
+ }
+ p, err := parseDictTypeBody(&c.Controller)
+ if err != nil {
+ c.dictErr(400, 400, err.Error())
+ return
+ }
+ if p.DictName == "" {
+ c.dictErr(400, 400, "字典名称不能为空")
+ return
+ }
+
+ var row models.PlatformDict
+ if err := models.Orm.QueryTable(new(models.PlatformDict)).
+ Filter("id", id).
+ Filter("delete_time__isnull", true).
+ One(&row); err != nil {
+ c.dictErr(404, 404, "字典不存在")
+ 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 /platform/dict/types/:id
+// 字典下还有字典项时拒绝删除,避免留下孤儿数据。
+func (c *PlatformDictController) DeleteType() {
+ if _, err := c.dictClaims(); 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.PlatformDict
+ if err := models.Orm.QueryTable(new(models.PlatformDict)).
+ Filter("id", id).
+ Filter("delete_time__isnull", true).
+ One(&row); err != nil {
+ c.dictErr(404, 404, "字典不存在")
+ return
+ }
+ cnt, _ := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ 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.PlatformDict)).
+ 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 /platform/dict/items
+// 参数:dict_id(必填)/ page / pageSize / keyword(名称或值模糊)/ status
+func (c *PlatformDictController) ListItems() {
+ if _, err := c.dictClaims(); err != nil {
+ c.dictErr(401, 401, err.Error())
+ return
+ }
+ dictID, _ := c.GetInt64("dict_id")
+ if dictID <= 0 {
+ c.dictErr(400, 400, "dict_id 不能为空")
+ return
+ }
+ page, pageSize, offset := dictPageSize(&c.Controller)
+ keyword := strings.TrimSpace(c.GetString("keyword"))
+ status := strings.TrimSpace(c.GetString("status"))
+
+ cond := orm.NewCondition().
+ And("delete_time__isnull", true).
+ And("dict_id", dictID)
+ 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.PlatformDictItem)).SetCond(cond)
+ total, err := qs.Count()
+ if err != nil {
+ c.dictErr(500, 500, "查询失败: "+err.Error())
+ return
+ }
+ var list []models.PlatformDictItem
+ 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 /platform/dict/items
+func (c *PlatformDictController) CreateItem() {
+ if _, err := c.dictClaims(); err != nil {
+ c.dictErr(401, 401, err.Error())
+ return
+ }
+ p, err := parseDictItemBody(&c.Controller)
+ if err != nil {
+ c.dictErr(400, 400, err.Error())
+ return
+ }
+ 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 !dictTagTypes[p.TagType] {
+ c.dictErr(400, 400, "标签色只能是 primary/success/warning/danger/info 之一")
+ return
+ }
+
+ var dict models.PlatformDict
+ if err := models.Orm.QueryTable(new(models.PlatformDict)).
+ Filter("id", p.DictID).
+ Filter("delete_time__isnull", true).
+ One(&dict); err != nil {
+ c.dictErr(404, 404, "字典不存在")
+ return
+ }
+ cnt, _ := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ 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.PlatformDictItem{
+ 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 /platform/dict/items/:id
+// 字典项值是业务入库值,不允许修改,避免存量数据对不上。
+func (c *PlatformDictController) UpdateItem() {
+ if _, err := c.dictClaims(); err != nil {
+ c.dictErr(401, 401, err.Error())
+ return
+ }
+ id := dictPathID(&c.Controller)
+ if id == 0 {
+ c.dictErr(400, 400, "无效的ID")
+ return
+ }
+ p, err := parseDictItemBody(&c.Controller)
+ if err != nil {
+ c.dictErr(400, 400, err.Error())
+ return
+ }
+ if p.ItemLabel == "" {
+ c.dictErr(400, 400, "字典项名称不能为空")
+ return
+ }
+ if !dictTagTypes[p.TagType] {
+ c.dictErr(400, 400, "标签色只能是 primary/success/warning/danger/info 之一")
+ return
+ }
+
+ var row models.PlatformDictItem
+ if err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ Filter("id", id).
+ 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 /platform/dict/items/:id/status
+func (c *PlatformDictController) ToggleItemStatus() {
+ if _, err := c.dictClaims(); 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
+ }
+
+ var row models.PlatformDictItem
+ if err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ Filter("id", id).
+ Filter("delete_time__isnull", true).
+ One(&row); err != nil {
+ c.dictErr(404, 404, "字典项不存在")
+ return
+ }
+ now := time.Now()
+ if _, err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ 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 /platform/dict/items/:id
+func (c *PlatformDictController) DeleteItem() {
+ if _, err := c.dictClaims(); 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.PlatformDictItem
+ if err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ Filter("id", id).
+ Filter("delete_time__isnull", true).
+ One(&row); err != nil {
+ c.dictErr(404, 404, "字典项不存在")
+ return
+ }
+ now := time.Now()
+ if _, err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ 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 /platform/dict/options
+// 一次取回全部「启用」字典的字典项,供前端登录后预热缓存:
+// 之后页面按任意 dict_code 都能同步读到,新增字典不用改前端。
+func (c *PlatformDictController) OptionsAll() {
+ if _, err := c.dictClaims(); err != nil {
+ c.dictErr(401, 401, err.Error())
+ return
+ }
+ result := map[string][]models.PlatformDictItem{}
+
+ var dicts []models.PlatformDict
+ if _, err := models.Orm.QueryTable(new(models.PlatformDict)).
+ Filter("status", 1).
+ Filter("delete_time__isnull", true).
+ All(&dicts); err != nil {
+ c.dictErr(500, 500, "查询失败: "+err.Error())
+ return
+ }
+ if len(dicts) == 0 {
+ c.dictOk(map[string]interface{}{"dicts": result}, "")
+ return
+ }
+
+ ids := make([]uint64, 0, len(dicts))
+ codeByID := make(map[uint64]string, len(dicts))
+ for _, d := range dicts {
+ ids = append(ids, d.ID)
+ codeByID[d.ID] = d.DictCode
+ result[d.DictCode] = []models.PlatformDictItem{}
+ }
+
+ var items []models.PlatformDictItem
+ if _, err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ 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 /platform/dict/options
+// 一次取多个字典的启用项,供前端预加载(body: {"codes":["payment_channel",...]})。
+// 编码不存在或已停用时返回空数组,前端不必再单独重试。
+// 注意:这里刻意与 GET /options/:code 分开成「同段不同方法」,避免静态段与通配段同级。
+func (c *PlatformDictController) OptionsBatch() {
+ if _, err := c.dictClaims(); 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.PlatformDictItem{}
+ 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.PlatformDictItem{}
+ codes = append(codes, code)
+ }
+ if len(codes) == 0 {
+ c.dictOk(map[string]interface{}{"dicts": result}, "")
+ return
+ }
+
+ // 先按编码取字典,再按字典ID一次取回全部字典项,避免逐个字典查一轮(N+1)
+ var dicts []models.PlatformDict
+ if _, err := models.Orm.QueryTable(new(models.PlatformDict)).
+ Filter("dict_code__in", codes).
+ Filter("status", 1).
+ Filter("delete_time__isnull", true).
+ All(&dicts); err != nil {
+ c.dictErr(500, 500, "查询失败: "+err.Error())
+ return
+ }
+ if len(dicts) == 0 {
+ c.dictOk(map[string]interface{}{"dicts": result}, "")
+ return
+ }
+
+ ids := make([]uint64, 0, len(dicts))
+ codeByID := make(map[uint64]string, len(dicts))
+ for _, d := range dicts {
+ ids = append(ids, d.ID)
+ codeByID[d.ID] = d.DictCode
+ }
+
+ var items []models.PlatformDictItem
+ if _, err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ 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 /platform/dict/options/:code
+// 业务页面按编码取选项:只返回「字典与字典项都启用」的项,按 sort 升序。
+func (c *PlatformDictController) Options() {
+ if _, err := c.dictClaims(); err != nil {
+ c.dictErr(401, 401, err.Error())
+ return
+ }
+ code := strings.TrimSpace(c.Ctx.Input.Param(":code"))
+ if code == "" {
+ c.dictErr(400, 400, "字典编码不能为空")
+ return
+ }
+
+ var dict models.PlatformDict
+ if err := models.Orm.QueryTable(new(models.PlatformDict)).
+ Filter("dict_code", code).
+ Filter("status", 1).
+ Filter("delete_time__isnull", true).
+ One(&dict); err != nil {
+ c.dictErr(404, 404, "字典不存在或已停用")
+ return
+ }
+
+ var list []models.PlatformDictItem
+ if _, err := models.Orm.QueryTable(new(models.PlatformDictItem)).
+ 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,
+ }, "")
+}
diff --git a/go/docs/sql/create_dict.sql b/go/docs/sql/create_dict.sql
new file mode 100644
index 0000000..cb49e4f
--- /dev/null
+++ b/go/docs/sql/create_dict.sql
@@ -0,0 +1,112 @@
+-- =============================================================
+-- 数据字典 —— 平台端 / 租户端各一套
+--
+-- 表清单:
+-- yz_platform_dict 平台端「字典列表」(字典类型)
+-- yz_platform_dict_item 平台端「字典项」
+-- yz_backend_dict 租户端「字典列表」(按 tenant_id 租户隔离)
+-- yz_backend_dict_item 租户端「字典项」
+--
+-- 设计说明:
+-- 1. 平台端字典与租户端字典完全独立:平台端字典供平台系统自身使用
+-- (不按租户隔离);租户端字典是每个租户可自行维护的业务选项
+-- (如供应商类型、结算方式),按 tenant_id 隔离,与
+-- yz_backend_erp_supplier 的隔离方式保持一致。
+-- 2. 字典项通过 dict_id 关联字典;租户端额外冗余 tenant_id,
+-- 便于不带 JOIN 直接按租户过滤,写入时须与父字典 tenant_id 一致。
+-- 3. tag_type 对应前端 el-tag 的 type(primary/success/warning/danger/info),
+-- 留空表示该项在列表里不渲染彩色标签。
+-- 4. 与 yz_backend_erp_* / yz_backend_crm_* 等表一致,这里只建普通索引、
+-- 不建唯一索引:一是软删除(delete_time)后编码仍会占用唯一键,
+-- 二是库中既有表也普遍不用唯一索引。dict_code / item_value 的唯一性
+-- 由后端接口在写入时校验。
+-- 5. 不显式指定 COLLATE,沿用库默认排序规则,避免 JOIN 时报
+-- Illegal mix of collations。
+--
+-- 执行方式:人工在数据库执行,代码中不做任何自动建表 / 补列。
+-- 说明:下方 DDL 幂等(IF NOT EXISTS),可重复执行。
+-- =============================================================
+
+SET NAMES utf8mb4;
+
+
+-- =============================================================
+-- 平台端
+-- =============================================================
+
+CREATE TABLE IF NOT EXISTS `yz_platform_dict` (
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `dict_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典名称,如:供应商类型',
+ `dict_code` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典编码(业务唯一标识,后端按此取字典),如:supplier_type',
+ `remark` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ `sort` INT NOT NULL DEFAULT 0 COMMENT '排序,数字越小越靠前',
+ `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1 启用 0 停用',
+ `create_user_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人用户ID',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `delete_time` DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
+ PRIMARY KEY (`id`),
+ KEY `idx_dict_code` (`dict_code`),
+ KEY `idx_status_sort` (`status`, `sort`, `id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台端-数据字典(字典列表)';
+
+
+CREATE TABLE IF NOT EXISTS `yz_platform_dict_item` (
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `dict_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属字典ID(yz_platform_dict.id)',
+ `item_label` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典项名称(显示文案),如:原材料供应商',
+ `item_value` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典项值(业务入库值),如:1',
+ `tag_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '标签色(el-tag type):primary/success/warning/danger/info,留空不渲染标签',
+ `sort` INT NOT NULL DEFAULT 0 COMMENT '排序,数字越小越靠前',
+ `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1 启用 0 停用',
+ `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认选中:0 否 1 是',
+ `remark` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `delete_time` DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
+ PRIMARY KEY (`id`),
+ KEY `idx_dict_sort` (`dict_id`, `sort`, `id`),
+ KEY `idx_dict_value` (`dict_id`, `item_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台端-字典项';
+
+
+-- =============================================================
+-- 租户端(按 tenant_id 隔离)
+-- =============================================================
+
+CREATE TABLE IF NOT EXISTS `yz_backend_dict` (
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `tenant_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户ID(同 yz_backend_erp_supplier.tenant_id)',
+ `dict_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典名称,如:供应商类型',
+ `dict_code` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典编码(租户内唯一),如:supplier_type',
+ `remark` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ `sort` INT NOT NULL DEFAULT 0 COMMENT '排序,数字越小越靠前',
+ `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1 启用 0 停用',
+ `create_user_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人用户ID',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `delete_time` DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
+ PRIMARY KEY (`id`),
+ KEY `idx_tenant_dict_code` (`tenant_id`, `dict_code`),
+ KEY `idx_tenant_status_sort` (`tenant_id`, `status`, `sort`, `id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户端-数据字典(字典列表)';
+
+
+CREATE TABLE IF NOT EXISTS `yz_backend_dict_item` (
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `tenant_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户ID,须与所属字典一致',
+ `dict_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属字典ID(yz_backend_dict.id)',
+ `item_label` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典项名称(显示文案),如:原材料供应商',
+ `item_value` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '字典项值(业务入库值),如:1',
+ `tag_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '标签色(el-tag type):primary/success/warning/danger/info,留空不渲染标签',
+ `sort` INT NOT NULL DEFAULT 0 COMMENT '排序,数字越小越靠前',
+ `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1 启用 0 停用',
+ `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认选中:0 否 1 是',
+ `remark` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `delete_time` DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
+ PRIMARY KEY (`id`),
+ KEY `idx_tenant_dict_sort` (`tenant_id`, `dict_id`, `sort`, `id`),
+ KEY `idx_tenant_dict_value` (`tenant_id`, `dict_id`, `item_value`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户端-字典项';
diff --git a/go/docs/sql/insert_backend_dict_data.sql b/go/docs/sql/insert_backend_dict_data.sql
new file mode 100644
index 0000000..78de691
--- /dev/null
+++ b/go/docs/sql/insert_backend_dict_data.sql
@@ -0,0 +1,448 @@
+-- =============================================================
+-- 租户端数据字典 —— 初始数据(系统内置,全局共享)
+--
+-- 表:yz_backend_dict / yz_backend_dict_item(建表见 create_dict.sql)
+--
+-- 重要:本文件写入的是 tenant_id = '0' 的「全局内置字典」,与 yz_system_admin_role
+-- 中 tenant_id=0 的全局租户角色同一套约定:
+-- - 所有租户都能通过 /backend/dict/options/:code 读到;
+-- - 租户若需要改动,请按相同 dict_code 新建一份自己的字典(读接口优先取租户自己的,
+-- 字典管理页里全局字典显示为「内置」只读)。
+--
+-- 内容来源:backend 前端散落的硬编码映射,其中 CRM 部分取自 views/apps/crm/dict.js。
+-- 幂等:字典按 (tenant_id='0', dict_code) 判重,字典项按 (dict_id, item_value) 判重,可重复执行。
+-- 说明:未给出标签色的字典项 tag_type 留空,可在「系统设置 → 数据字典」里按需补。
+-- =============================================================
+
+SET NAMES utf8mb4;
+
+
+-- =============================================================
+-- 1. 字典列表(全局内置)
+-- =============================================================
+
+INSERT INTO `yz_backend_dict` (`tenant_id`, `dict_name`, `dict_code`, `remark`, `sort`, `status`, `create_user_id`, `create_time`)
+SELECT '0', t.dict_name, t.dict_code, t.remark, t.sort, 1, 'sql', NOW()
+FROM (
+ SELECT '通用状态' AS dict_name, 'common_status' AS dict_code, '0 禁用 / 1 正常 / 2 冻结 / 3 已注销(客户、供应商等共用)' AS remark, 10 AS sort
+ UNION ALL SELECT '往来单位等级' AS dict_name, 'common_object_level' AS dict_code, '客户/供应商等级' AS remark, 20 AS sort
+ UNION ALL SELECT '性别' AS dict_name, 'common_gender' AS dict_code, '0 未知 / 1 男 / 2 女' AS remark, 30 AS sort
+ UNION ALL SELECT '通用启用状态' AS dict_name, 'common_enable_status' AS dict_code, '1 启用 / 0 禁用' AS remark, 40 AS sort
+ UNION ALL SELECT '待办状态' AS dict_name, 'common_todo_status' AS dict_code, '0 待办 / 1 已完成' AS remark, 50 AS sort
+ UNION ALL SELECT '优先级' AS dict_name, 'common_priority' AS dict_code, '日程优先级' AS remark, 60 AS sort
+ UNION ALL SELECT '联系人类型' AS dict_name, 'common_contact_type' AS dict_code, '1 企业 / 2 个人' AS remark, 70 AS sort
+ UNION ALL SELECT '提醒渠道' AS dict_name, 'common_notify_channel' AS dict_code, '站内提醒的推送渠道(remind_channels)' AS remark, 80 AS sort
+ UNION ALL SELECT '移入公海原因' AS dict_name, 'crm_pool_reason' AS dict_code, 'CRM 客户移入公海的原因' AS remark, 90 AS sort
+ UNION ALL SELECT '联系人关联类型' AS dict_name, 'erp_related_type' AS dict_code, '1 客户 / 2 供应商' AS remark, 100 AS sort
+ UNION ALL SELECT '客户类型' AS dict_name, 'erp_customer_type' AS dict_code, 'crm/dict.js CUSTOMER_TYPE_OPTIONS' AS remark, 110 AS sort
+ UNION ALL SELECT '供应商类型' AS dict_name, 'erp_supplier_type' AS dict_code, '与 CRM 供应商类型共用' AS remark, 120 AS sort
+ UNION ALL SELECT '线索来源' AS dict_name, 'crm_clue_source' AS dict_code, '' AS remark, 130 AS sort
+ UNION ALL SELECT '线索状态' AS dict_name, 'crm_clue_status' AS dict_code, '' AS remark, 140 AS sort
+ UNION ALL SELECT '线索级别' AS dict_name, 'crm_clue_level' AS dict_code, '' AS remark, 150 AS sort
+ UNION ALL SELECT '商机阶段' AS dict_name, 'crm_business_stage' AS dict_code, '' AS remark, 160 AS sort
+ UNION ALL SELECT '商机状态' AS dict_name, 'crm_business_status' AS dict_code, '' AS remark, 170 AS sort
+ UNION ALL SELECT '跟进类型' AS dict_name, 'crm_follow_type' AS dict_code, '' AS remark, 180 AS sort
+ UNION ALL SELECT '跟进关联类型' AS dict_name, 'crm_pipeline_related_type' AS dict_code, '' AS remark, 190 AS sort
+ UNION ALL SELECT '项目状态' AS dict_name, 'crm_project_status' AS dict_code, '' AS remark, 200 AS sort
+ UNION ALL SELECT '项目类型' AS dict_name, 'crm_project_type' AS dict_code, '' AS remark, 210 AS sort
+ UNION ALL SELECT '合同分类' AS dict_name, 'crm_contract_category' AS dict_code, '' AS remark, 220 AS sort
+ UNION ALL SELECT '合同状态' AS dict_name, 'crm_contract_status' AS dict_code, '' AS remark, 230 AS sort
+ UNION ALL SELECT '合同形式' AS dict_name, 'crm_contract_party_count' AS dict_code, '2 双方 / 3 三方 / 4 四方' AS remark, 240 AS sort
+ UNION ALL SELECT '我方角色' AS dict_name, 'crm_contract_our_role' AS dict_code, '' AS remark, 250 AS sort
+ UNION ALL SELECT '合同产品类别' AS dict_name, 'crm_contract_product_category' AS dict_code, '' AS remark, 260 AS sort
+ UNION ALL SELECT '回款周期' AS dict_name, 'crm_payback_cycle' AS dict_code, '' AS remark, 270 AS sort
+ UNION ALL SELECT '回款方式' AS dict_name, 'crm_payback_method' AS dict_code, '' AS remark, 280 AS sort
+ UNION ALL SELECT '回款计划状态' AS dict_name, 'crm_payback_status' AS dict_code, '' AS remark, 290 AS sort
+ UNION ALL SELECT '回款明细状态' AS dict_name, 'crm_payback_item_status' AS dict_code, '' AS remark, 300 AS sort
+ UNION ALL SELECT '产品状态' AS dict_name, 'crm_product_status' AS dict_code, '1 启用 / 0 停用' AS remark, 310 AS sort
+ UNION ALL SELECT '产品单位' AS dict_name, 'crm_product_unit' AS dict_code, '计量单位,偏业务参数,可自行增删' AS remark, 320 AS sort
+ UNION ALL SELECT '产品研发方式' AS dict_name, 'crm_product_dev_mode' AS dict_code, '' AS remark, 330 AS sort
+ UNION ALL SELECT '招标状态' AS dict_name, 'crm_tender_status' AS dict_code, '' AS remark, 340 AS sort
+ UNION ALL SELECT '招标方式' AS dict_name, 'crm_tender_type' AS dict_code, '' AS remark, 350 AS sort
+ UNION ALL SELECT '招标结果' AS dict_name, 'crm_tender_result_type' AS dict_code, '' AS remark, 360 AS sort
+ UNION ALL SELECT '招标供应商状态' AS dict_name, 'crm_tender_supplier_status' AS dict_code, '' AS remark, 370 AS sort
+ UNION ALL SELECT '招标公告类型' AS dict_name, 'crm_tender_announce_type' AS dict_code, '' AS remark, 380 AS sort
+ UNION ALL SELECT '评标方式' AS dict_name, 'crm_evaluation_method' AS dict_code, '' AS remark, 390 AS sort
+ UNION ALL SELECT '评标结果' AS dict_name, 'crm_evaluation_result' AS dict_code, '' AS remark, 400 AS sort
+ UNION ALL SELECT '投标状态' AS dict_name, 'crm_bid_status' AS dict_code, '' AS remark, 410 AS sort
+ UNION ALL SELECT '投标方式' AS dict_name, 'crm_bid_type' AS dict_code, '' AS remark, 420 AS sort
+ UNION ALL SELECT '投标来源' AS dict_name, 'crm_bid_source_type' AS dict_code, '' AS remark, 430 AS sort
+ UNION ALL SELECT '投标结果' AS dict_name, 'crm_bid_result_type' AS dict_code, '' AS remark, 440 AS sort
+ UNION ALL SELECT '投标任务类型' AS dict_name, 'crm_bid_task_type' AS dict_code, '' AS remark, 450 AS sort
+ UNION ALL SELECT '投标任务状态' AS dict_name, 'crm_bid_task_status' AS dict_code, '' AS remark, 460 AS sort
+ UNION ALL SELECT '投标模板类型' AS dict_name, 'crm_bid_template_type' AS dict_code, '' AS remark, 470 AS sort
+ UNION ALL SELECT '投标报价状态' AS dict_name, 'crm_bid_quote_status' AS dict_code, '' AS remark, 480 AS sort
+ UNION ALL SELECT '投标文件状态' AS dict_name, 'crm_bid_document_status' AS dict_code, '' AS remark, 490 AS sort
+ UNION ALL SELECT '投标费用类型' AS dict_name, 'crm_bid_expense_type' AS dict_code, '' AS remark, 500 AS sort
+ UNION ALL SELECT '竞争对手类型' AS dict_name, 'crm_competitor_type' AS dict_code, '' AS remark, 510 AS sort
+ UNION ALL SELECT '资质类型' AS dict_name, 'crm_qualification_type' AS dict_code, '' AS remark, 520 AS sort
+ UNION ALL SELECT '资质状态' AS dict_name, 'crm_qualification_status' AS dict_code, '' AS remark, 530 AS sort
+ UNION ALL SELECT '收付款状态' AS dict_name, 'crm_payment_status' AS dict_code, '' AS remark, 540 AS sort
+ UNION ALL SELECT '审批类型' AS dict_name, 'crm_approval_type' AS dict_code, '' AS remark, 550 AS sort
+ UNION ALL SELECT '审批状态' AS dict_name, 'crm_approval_status' AS dict_code, '' AS remark, 560 AS sort
+ UNION ALL SELECT '账套会计准则' AS dict_name, 'erp_account_set_standard' AS dict_code, '1 企业会计准则 / 2 小企业会计准则' AS remark, 570 AS sort
+ UNION ALL SELECT '账套本位币' AS dict_name, 'erp_account_set_currency' AS dict_code, '账套支持的币种' AS remark, 580 AS sort
+ UNION ALL SELECT '职位类型' AS dict_name, 'erp_position_type' AS dict_code, '0 普通职位 / 1 主管 / 2 负责人' AS remark, 590 AS sort
+ UNION ALL SELECT '员工账号状态' AS dict_name, 'common_employee_status' AS dict_code, '1 正常 / 0 禁用 / 2 离职' AS remark, 600 AS sort
+ UNION ALL SELECT '用工状态' AS dict_name, 'oa_employee_hire_status' AS dict_code, '人事档案:1 试用 / 2 正式 / 3 离职' AS remark, 610 AS sort
+ UNION ALL SELECT '婚姻状况' AS dict_name, 'oa_employee_marital' AS dict_code, '人事档案' AS remark, 620 AS sort
+ UNION ALL SELECT '报销单状态' AS dict_name, 'oa_reimburse_status' AS dict_code, '0 草稿 / 1 审批中 / 2 已通过 / 3 已驳回 / 4 已撤回 / 5 已打款' AS remark, 630 AS sort
+ UNION ALL SELECT '薪酬单状态' AS dict_name, 'oa_compensation_status' AS dict_code, '0 草稿 / 1 已确认 / 2 已发放 / 3 已作废' AS remark, 640 AS sort
+ UNION ALL SELECT '薪酬项类型' AS dict_name, 'oa_compensation_item_type' AS dict_code, '1 增项 / 2 扣项' AS remark, 650 AS sort
+ UNION ALL SELECT '公告类型' AS dict_name, 'oa_notice_type' AS dict_code, '0 通知 / 1 公告 / 2 活动' AS remark, 660 AS sort
+ UNION ALL SELECT '公告状态' AS dict_name, 'oa_notice_status' AS dict_code, '0 草稿 / 1 已发布 / 2 已下架' AS remark, 670 AS sort
+ UNION ALL SELECT 'OA文档状态' AS dict_name, 'oa_doc_status' AS dict_code, '0 草稿 / 1 已发布 / 2 已归档' AS remark, 680 AS sort
+ UNION ALL SELECT 'OA文档关联类型' AS dict_name, 'oa_doc_relation' AS dict_code, 'reference 引用 / related 相关 / version 版本 / belong 从属' AS remark, 690 AS sort
+ UNION ALL SELECT 'OA文档可见性' AS dict_name, 'oa_doc_visibility' AS dict_code, '0 租户公开 / 1 私密' AS remark, 700 AS sort
+ UNION ALL SELECT '登录方式' AS dict_name, 'system_login_type' AS dict_code, '登录日志' AS remark, 710 AS sort
+ UNION ALL SELECT '请求方法' AS dict_name, 'system_http_method' AS dict_code, '操作日志' AS remark, 720 AS sort
+ UNION ALL SELECT '菜单类型' AS dict_name, 'system_menu_type' AS dict_code, '1 目录 / 2 菜单 / 3 按钮' AS remark, 730 AS sort
+ UNION ALL SELECT '订购单状态' AS dict_name, 'common_order_status' AS dict_code, '产品/套餐购买单状态' AS remark, 740 AS sort
+ UNION ALL SELECT '前台导航类型' AS dict_name, 'cms_front_menu_type' AS dict_code, '1 目录 / 2 页面 / 3 外链 / 4 单页' AS remark, 750 AS sort
+ UNION ALL SELECT '需求状态' AS dict_name, 'cms_demand_status' AS dict_code, '1 待处理 / 2 处理中 / 3 已完成 / 4 已拒绝' AS remark, 760 AS sort
+ UNION ALL SELECT '域名核验状态' AS dict_name, 'tenant_domain_verify_status' AS dict_code, '0 未检测 / 1 已通过 / 2 未通过' AS remark, 770 AS sort
+ UNION ALL SELECT '域名证书状态' AS dict_name, 'tenant_domain_ssl_status' AS dict_code, '0 待处理 / 1 签发中 / 2 已签发 / 3 签发失败' AS remark, 780 AS sort
+) AS t
+WHERE NOT EXISTS (
+ SELECT 1 FROM `yz_backend_dict` AS d
+ WHERE d.tenant_id = '0' AND d.dict_code = t.dict_code AND d.delete_time IS NULL
+);
+
+
+-- =============================================================
+-- 2. 字典项(全局内置)
+-- =============================================================
+
+INSERT INTO `yz_backend_dict_item` (`tenant_id`, `dict_id`, `item_label`, `item_value`, `tag_type`, `sort`, `status`, `is_default`, `create_time`)
+SELECT '0', d.id, t.item_label, t.item_value, t.tag_type, t.sort, 1, 0, NOW()
+FROM (
+ SELECT 'common_status' AS dict_code, '正常' AS item_label, '1' AS item_value, 'success' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_status' AS dict_code, '禁用' AS item_label, '0' AS item_value, 'info' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_status' AS dict_code, '冻结' AS item_label, '2' AS item_value, 'warning' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'common_status' AS dict_code, '已注销' AS item_label, '3' AS item_value, 'danger' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'common_object_level' AS dict_code, '核心' AS item_label, '1' AS item_value, 'danger' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_object_level' AS dict_code, '重要' AS item_label, '2' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_object_level' AS dict_code, '普通' AS item_label, '3' AS item_value, 'info' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'common_object_level' AS dict_code, '潜在' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'common_gender' AS dict_code, '未知' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_gender' AS dict_code, '男' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_gender' AS dict_code, '女' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'common_enable_status' AS dict_code, '禁用' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_enable_status' AS dict_code, '启用' AS item_label, '1' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_todo_status' AS dict_code, '待办' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_todo_status' AS dict_code, '已完成' AS item_label, '1' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_priority' AS dict_code, '重要' AS item_label, '1' AS item_value, 'warning' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_priority' AS dict_code, '紧急' AS item_label, '2' AS item_value, 'danger' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_contact_type' AS dict_code, '企业' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_contact_type' AS dict_code, '个人' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_notify_channel' AS dict_code, '短信' AS item_label, 'SMS' AS item_value, 'success' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_notify_channel' AS dict_code, '邮件' AS item_label, 'EMAIL' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_notify_channel' AS dict_code, 'Bark推送' AS item_label, 'BARK' AS item_value, 'danger' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'common_notify_channel' AS dict_code, '站内信' AS item_label, 'SITE_MSG' AS item_value, 'primary' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_pool_reason' AS dict_code, '长期未跟进' AS item_label, '长期未跟进' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_pool_reason' AS dict_code, '合作终止' AS item_label, '合作终止' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_pool_reason' AS dict_code, '重复建档' AS item_label, '重复建档' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_pool_reason' AS dict_code, '负责人离职' AS item_label, '负责人离职' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_pool_reason' AS dict_code, '手动释放' AS item_label, '手动释放' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_pool_reason' AS dict_code, '其他' AS item_label, '其他' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'erp_related_type' AS dict_code, '客户' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'erp_related_type' AS dict_code, '供应商' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'erp_customer_type' AS dict_code, '企业' AS item_label, '1' AS item_value, 'primary' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'erp_customer_type' AS dict_code, '政府机构' AS item_label, '2' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'erp_customer_type' AS dict_code, '国企' AS item_label, '3' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'erp_customer_type' AS dict_code, '教育机构' AS item_label, '4' AS item_value, 'info' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'erp_customer_type' AS dict_code, '个人' AS item_label, '5' AS item_value, 'info' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'erp_supplier_type' AS dict_code, '原材料供应商' AS item_label, '1' AS item_value, 'primary' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'erp_supplier_type' AS dict_code, '设备供应商' AS item_label, '2' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'erp_supplier_type' AS dict_code, '服务供应商' AS item_label, '3' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'erp_supplier_type' AS dict_code, '其他' AS item_label, '4' AS item_value, 'info' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_clue_source' AS dict_code, '官网咨询' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_clue_source' AS dict_code, '电话咨询' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_clue_source' AS dict_code, '朋友介绍' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_clue_source' AS dict_code, '展会活动' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_clue_source' AS dict_code, '广告投放' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_clue_source' AS dict_code, '陌生拜访' AS item_label, '6' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_clue_source' AS dict_code, '其他' AS item_label, '7' AS item_value, '' AS tag_type, 70 AS sort
+ UNION ALL SELECT 'crm_clue_status' AS dict_code, '跟进中' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_clue_status' AS dict_code, '已转化' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_clue_status' AS dict_code, '已废弃' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_clue_level' AS dict_code, '重点' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_clue_level' AS dict_code, '普通' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_clue_level' AS dict_code, '非优先' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_business_stage' AS dict_code, '初步接洽' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_business_stage' AS dict_code, '需求确认' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_business_stage' AS dict_code, '方案报价' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_business_stage' AS dict_code, '商务谈判' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_business_stage' AS dict_code, '赢单' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_business_stage' AS dict_code, '输单' AS item_label, '6' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_business_status' AS dict_code, '跟进中' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_business_status' AS dict_code, '已转化项目' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_business_status' AS dict_code, '已关闭' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_follow_type' AS dict_code, '电话' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_follow_type' AS dict_code, '微信' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_follow_type' AS dict_code, '上门' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_follow_type' AS dict_code, '邮件' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_follow_type' AS dict_code, '其他' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_pipeline_related_type' AS dict_code, '线索' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_pipeline_related_type' AS dict_code, '商机' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_pipeline_related_type' AS dict_code, '项目' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_project_status' AS dict_code, '未开始' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_project_status' AS dict_code, '进行中' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_project_status' AS dict_code, '已完成' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_project_status' AS dict_code, '已暂停' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_project_status' AS dict_code, '异常' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_project_type' AS dict_code, '单一项目' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_project_type' AS dict_code, '上下游项目' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_project_type' AS dict_code, '上中游项目' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '开发合同' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '服务合同' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '销售合同' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '租赁合同' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '采购合同' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '运维合同' AS item_label, '6' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '咨询合同' AS item_label, '7' AS item_value, '' AS tag_type, 70 AS sort
+ UNION ALL SELECT 'crm_contract_category' AS dict_code, '其他' AS item_label, '8' AS item_value, '' AS tag_type, 80 AS sort
+ UNION ALL SELECT 'crm_contract_status' AS dict_code, '草稿' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_contract_status' AS dict_code, '已完成' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_contract_status' AS dict_code, '履约中' AS item_label, '4' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_contract_status' AS dict_code, '异常' AS item_label, '5' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_contract_status' AS dict_code, '已作废' AS item_label, '3' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_contract_party_count' AS dict_code, '双方合同' AS item_label, '2' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_contract_party_count' AS dict_code, '三方合同' AS item_label, '3' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_contract_party_count' AS dict_code, '四方合同' AS item_label, '4' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_contract_our_role' AS dict_code, '甲方' AS item_label, '1' AS item_value, 'danger' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_contract_our_role' AS dict_code, '乙方' AS item_label, '2' AS item_value, 'primary' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_contract_our_role' AS dict_code, '丙方' AS item_label, '3' AS item_value, 'warning' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_contract_our_role' AS dict_code, '丁方' AS item_label, '4' AS item_value, 'success' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_contract_product_category' AS dict_code, '硬件(货物)' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_contract_product_category' AS dict_code, '软件(许可)' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_contract_product_category' AS dict_code, '服务' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_contract_product_category' AS dict_code, '开发' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_contract_product_category' AS dict_code, '其他' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_payback_cycle' AS dict_code, '月度' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_payback_cycle' AS dict_code, '季度' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_payback_cycle' AS dict_code, '年度' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_payback_cycle' AS dict_code, '进度' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_payback_cycle' AS dict_code, '自定义' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_payback_method' AS dict_code, '对公转账' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_payback_method' AS dict_code, '网银转账' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_payback_method' AS dict_code, '现金' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_payback_method' AS dict_code, '支票' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_payback_method' AS dict_code, '支付宝' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_payback_method' AS dict_code, '微信' AS item_label, '6' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_payback_method' AS dict_code, '其他' AS item_label, '7' AS item_value, '' AS tag_type, 70 AS sort
+ UNION ALL SELECT 'crm_payback_status' AS dict_code, '进行中' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_payback_status' AS dict_code, '已完成' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_payback_item_status' AS dict_code, '未回款' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_payback_item_status' AS dict_code, '部分回款' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_payback_item_status' AS dict_code, '已回款' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_product_status' AS dict_code, '启用' AS item_label, '1' AS item_value, 'success' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_product_status' AS dict_code, '停用' AS item_label, '0' AS item_value, 'info' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '套' AS item_label, '套' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '台' AS item_label, '台' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '个' AS item_label, '个' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '件' AS item_label, '件' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '批' AS item_label, '批' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '次' AS item_label, '次' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '人月' AS item_label, '人月' AS item_value, '' AS tag_type, 70 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '人天' AS item_label, '人天' AS item_value, '' AS tag_type, 80 AS sort
+ UNION ALL SELECT 'crm_product_unit' AS dict_code, '年' AS item_label, '年' AS item_value, '' AS tag_type, 90 AS sort
+ UNION ALL SELECT 'crm_product_dev_mode' AS dict_code, '自行研发' AS item_label, 'self' AS item_value, 'success' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_product_dev_mode' AS dict_code, '外协研发' AS item_label, 'outsource' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '草稿' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '公告中' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '报名中' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '投标中' AS item_label, '3' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '开标中' AS item_label, '4' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '评标中' AS item_label, '5' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '已定标' AS item_label, '6' AS item_value, '' AS tag_type, 70 AS sort
+ UNION ALL SELECT 'crm_tender_status' AS dict_code, '已归档' AS item_label, '7' AS item_value, '' AS tag_type, 80 AS sort
+ UNION ALL SELECT 'crm_tender_type' AS dict_code, '公开招标' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_tender_type' AS dict_code, '邀请招标' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_tender_type' AS dict_code, '竞争性谈判' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_tender_type' AS dict_code, '询比采购' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_tender_type' AS dict_code, '单一来源' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_tender_result_type' AS dict_code, '定标' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_tender_result_type' AS dict_code, '流标' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_tender_result_type' AS dict_code, '废标' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_tender_supplier_status' AS dict_code, '待审核' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_tender_supplier_status' AS dict_code, '已通过' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_tender_supplier_status' AS dict_code, '已拒绝' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_tender_supplier_status' AS dict_code, '已退标' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_tender_announce_type' AS dict_code, '招标公告' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_tender_announce_type' AS dict_code, '变更公告' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_tender_announce_type' AS dict_code, '中标公告' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_tender_announce_type' AS dict_code, '终止公告' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_evaluation_method' AS dict_code, '综合评估法' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_evaluation_method' AS dict_code, '最低价法' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_evaluation_method' AS dict_code, '性价比法' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_evaluation_result' AS dict_code, '待评' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_evaluation_result' AS dict_code, '合格' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_evaluation_result' AS dict_code, '不合格' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_evaluation_result' AS dict_code, '废标' AS item_label, '3' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '草稿' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '立项审批中' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '投标中' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '已开标' AS item_label, '3' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '中标' AS item_label, '4' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '落标' AS item_label, '5' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '已转合同' AS item_label, '6' AS item_value, '' AS tag_type, 70 AS sort
+ UNION ALL SELECT 'crm_bid_status' AS dict_code, '已归档' AS item_label, '7' AS item_value, '' AS tag_type, 80 AS sort
+ UNION ALL SELECT 'crm_bid_type' AS dict_code, '公开招标' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_type' AS dict_code, '邀请招标' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_type' AS dict_code, '竞争性谈判' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_type' AS dict_code, '询比采购' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_type' AS dict_code, '单一来源' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_bid_source_type' AS dict_code, '独立创建' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_source_type' AS dict_code, '商机' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_source_type' AS dict_code, '项目' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_result_type' AS dict_code, '中标' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_result_type' AS dict_code, '未中标' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_result_type' AS dict_code, '废标' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_result_type' AS dict_code, '流标' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_task_type' AS dict_code, '商务' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_task_type' AS dict_code, '技术' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_task_type' AS dict_code, '报价' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_task_type' AS dict_code, '法务' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_task_type' AS dict_code, '财务' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_bid_task_type' AS dict_code, '其他' AS item_label, '6' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_bid_task_status' AS dict_code, '未开始' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_task_status' AS dict_code, '进行中' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_task_status' AS dict_code, '已完成' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_task_status' AS dict_code, '已逾期' AS item_label, '3' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_task_status' AS dict_code, '已取消' AS item_label, '4' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_bid_template_type' AS dict_code, '技术标' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_template_type' AS dict_code, '商务标' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_template_type' AS dict_code, '综合标' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_quote_status' AS dict_code, '草稿' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_quote_status' AS dict_code, '待审批' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_quote_status' AS dict_code, '已审批' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_quote_status' AS dict_code, '已使用' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_document_status' AS dict_code, '编辑中' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_document_status' AS dict_code, '待审核' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_document_status' AS dict_code, '已定稿' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_document_status' AS dict_code, '已提交' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_expense_type' AS dict_code, '标书费' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_bid_expense_type' AS dict_code, '保证金' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_bid_expense_type' AS dict_code, '差旅费' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_bid_expense_type' AS dict_code, '咨询费' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_bid_expense_type' AS dict_code, '代理费' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_bid_expense_type' AS dict_code, '其他' AS item_label, '6' AS item_value, '' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'crm_competitor_type' AS dict_code, '同类厂商' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_competitor_type' AS dict_code, '关联企业' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_competitor_type' AS dict_code, '其他' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_qualification_type' AS dict_code, '营业执照' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_qualification_type' AS dict_code, '资质证书' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_qualification_type' AS dict_code, '业绩证明' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_qualification_type' AS dict_code, '认证证书' AS item_label, '4' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_qualification_type' AS dict_code, '其他' AS item_label, '5' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'crm_qualification_status' AS dict_code, '有效' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_qualification_status' AS dict_code, '即将过期' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_qualification_status' AS dict_code, '失效' AS item_label, '0' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_payment_status' AS dict_code, '未付' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_payment_status' AS dict_code, '已付' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_payment_status' AS dict_code, '已退还' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_payment_status' AS dict_code, '已抵扣' AS item_label, '3' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'crm_approval_type' AS dict_code, '立项审批' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_approval_type' AS dict_code, '报价审批' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_approval_type' AS dict_code, '合同审批' AS item_label, '3' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_approval_status' AS dict_code, '待审批' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'crm_approval_status' AS dict_code, '已通过' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'crm_approval_status' AS dict_code, '已拒绝' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'crm_approval_status' AS dict_code, '已撤回' AS item_label, '3' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'erp_account_set_standard' AS dict_code, '企业会计准则' AS item_label, '1' AS item_value, 'primary' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'erp_account_set_standard' AS dict_code, '小企业会计准则' AS item_label, '2' AS item_value, 'info' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'erp_account_set_currency' AS dict_code, '人民币' AS item_label, 'CNY' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'erp_account_set_currency' AS dict_code, '美元' AS item_label, 'USD' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'erp_account_set_currency' AS dict_code, '欧元' AS item_label, 'EUR' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'erp_account_set_currency' AS dict_code, '港币' AS item_label, 'HKD' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'erp_position_type' AS dict_code, '普通职位' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'erp_position_type' AS dict_code, '主管' AS item_label, '1' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'erp_position_type' AS dict_code, '负责人' AS item_label, '2' AS item_value, 'primary' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'common_employee_status' AS dict_code, '禁用' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_employee_status' AS dict_code, '正常' AS item_label, '1' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_employee_status' AS dict_code, '离职' AS item_label, '2' AS item_value, 'danger' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_employee_hire_status' AS dict_code, '试用' AS item_label, '1' AS item_value, 'warning' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_employee_hire_status' AS dict_code, '正式' AS item_label, '2' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_employee_hire_status' AS dict_code, '离职' AS item_label, '3' AS item_value, 'danger' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_employee_marital' AS dict_code, '未知' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_employee_marital' AS dict_code, '未婚' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_employee_marital' AS dict_code, '已婚' AS item_label, '2' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_employee_marital' AS dict_code, '离异' AS item_label, '3' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'oa_employee_marital' AS dict_code, '丧偶' AS item_label, '4' AS item_value, '' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'oa_reimburse_status' AS dict_code, '草稿' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_reimburse_status' AS dict_code, '审批中' AS item_label, '1' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_reimburse_status' AS dict_code, '已通过' AS item_label, '2' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_reimburse_status' AS dict_code, '已驳回' AS item_label, '3' AS item_value, 'danger' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'oa_reimburse_status' AS dict_code, '已撤回' AS item_label, '4' AS item_value, 'info' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'oa_reimburse_status' AS dict_code, '已打款' AS item_label, '5' AS item_value, 'success' AS tag_type, 60 AS sort
+ UNION ALL SELECT 'oa_compensation_status' AS dict_code, '草稿' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_compensation_status' AS dict_code, '已确认' AS item_label, '1' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_compensation_status' AS dict_code, '已发放' AS item_label, '2' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_compensation_status' AS dict_code, '已作废' AS item_label, '3' AS item_value, 'danger' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'oa_compensation_item_type' AS dict_code, '增项' AS item_label, '1' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_compensation_item_type' AS dict_code, '扣项' AS item_label, '2' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_notice_type' AS dict_code, '通知' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_notice_type' AS dict_code, '公告' AS item_label, '1' AS item_value, 'primary' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_notice_type' AS dict_code, '活动' AS item_label, '2' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_notice_status' AS dict_code, '草稿' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_notice_status' AS dict_code, '已发布' AS item_label, '1' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_notice_status' AS dict_code, '已下架' AS item_label, '2' AS item_value, 'warning' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_doc_status' AS dict_code, '草稿' AS item_label, '0' AS item_value, 'warning' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_doc_status' AS dict_code, '已发布' AS item_label, '1' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_doc_status' AS dict_code, '已归档' AS item_label, '2' AS item_value, 'info' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_doc_relation' AS dict_code, '引用' AS item_label, 'reference' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_doc_relation' AS dict_code, '相关' AS item_label, 'related' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'oa_doc_relation' AS dict_code, '版本' AS item_label, 'version' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'oa_doc_relation' AS dict_code, '从属' AS item_label, 'belong' AS item_value, '' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'oa_doc_visibility' AS dict_code, '租户公开' AS item_label, '0' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'oa_doc_visibility' AS dict_code, '私密' AS item_label, '1' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'system_login_type' AS dict_code, '密码登录' AS item_label, 'password' AS item_value, '' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'system_login_type' AS dict_code, '短信验证码' AS item_label, 'sms' AS item_value, '' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'system_login_type' AS dict_code, '邮箱验证码' AS item_label, 'email' AS item_value, '' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'system_http_method' AS dict_code, 'GET' AS item_label, 'GET' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'system_http_method' AS dict_code, 'POST' AS item_label, 'POST' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'system_http_method' AS dict_code, 'PUT' AS item_label, 'PUT' AS item_value, 'warning' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'system_http_method' AS dict_code, 'DELETE' AS item_label, 'DELETE' AS item_value, 'danger' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'system_http_method' AS dict_code, 'PATCH' AS item_label, 'PATCH' AS item_value, 'primary' AS tag_type, 50 AS sort
+ UNION ALL SELECT 'system_menu_type' AS dict_code, '目录' AS item_label, '1' AS item_value, 'primary' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'system_menu_type' AS dict_code, '菜单' AS item_label, '2' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'system_menu_type' AS dict_code, '按钮' AS item_label, '3' AS item_value, 'warning' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'common_order_status' AS dict_code, '待支付' AS item_label, 'pending' AS item_value, 'warning' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_order_status' AS dict_code, '已支付' AS item_label, 'paid' AS item_value, 'primary' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'common_order_status' AS dict_code, '已开通' AS item_label, 'activated' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'common_order_status' AS dict_code, '已关闭' AS item_label, 'closed' AS item_value, 'info' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'cms_front_menu_type' AS dict_code, '目录' AS item_label, '1' AS item_value, 'primary' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'cms_front_menu_type' AS dict_code, '页面' AS item_label, '2' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'cms_front_menu_type' AS dict_code, '外链' AS item_label, '3' AS item_value, 'info' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'cms_front_menu_type' AS dict_code, '单页' AS item_label, '4' AS item_value, 'warning' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'cms_demand_status' AS dict_code, '待处理' AS item_label, '1' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'cms_demand_status' AS dict_code, '处理中' AS item_label, '2' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'cms_demand_status' AS dict_code, '已完成' AS item_label, '3' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'cms_demand_status' AS dict_code, '已拒绝' AS item_label, '4' AS item_value, 'danger' AS tag_type, 40 AS sort
+ UNION ALL SELECT 'tenant_domain_verify_status' AS dict_code, '未检测' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'tenant_domain_verify_status' AS dict_code, '已通过' AS item_label, '1' AS item_value, 'success' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'tenant_domain_verify_status' AS dict_code, '未通过' AS item_label, '2' AS item_value, 'danger' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'tenant_domain_ssl_status' AS dict_code, '待处理' AS item_label, '0' AS item_value, 'info' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'tenant_domain_ssl_status' AS dict_code, '签发中' AS item_label, '1' AS item_value, 'warning' AS tag_type, 20 AS sort
+ UNION ALL SELECT 'tenant_domain_ssl_status' AS dict_code, '已签发' AS item_label, '2' AS item_value, 'success' AS tag_type, 30 AS sort
+ UNION ALL SELECT 'tenant_domain_ssl_status' AS dict_code, '签发失败' AS item_label, '3' AS item_value, 'danger' AS tag_type, 40 AS sort
+) AS t
+JOIN `yz_backend_dict` AS d
+ ON d.tenant_id = '0' AND d.dict_code = t.dict_code AND d.delete_time IS NULL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `yz_backend_dict_item` AS i
+ WHERE i.dict_id = d.id AND i.item_value = t.item_value AND i.delete_time IS NULL
+);
+
+
+-- =============================================================
+-- 3. 校验
+-- =============================================================
+
+SELECT d.dict_name, d.dict_code, COUNT(i.id) AS item_count
+FROM `yz_backend_dict` d
+LEFT JOIN `yz_backend_dict_item` i ON i.dict_id = d.id AND i.delete_time IS NULL
+WHERE d.tenant_id = '0' AND d.delete_time IS NULL
+GROUP BY d.id, d.dict_name, d.dict_code
+ORDER BY d.sort, d.id;
diff --git a/go/docs/sql/insert_platform_dict_data.sql b/go/docs/sql/insert_platform_dict_data.sql
new file mode 100644
index 0000000..10d02a8
--- /dev/null
+++ b/go/docs/sql/insert_platform_dict_data.sql
@@ -0,0 +1,305 @@
+-- =============================================================
+-- 平台端数据字典 —— 初始数据
+--
+-- 表:yz_platform_dict / yz_platform_dict_item(建表见 create_dict.sql)
+-- 内容来源:platform 前端散落的硬编码映射(值→文案 / 标签色),
+-- 以及各平台端接口返回的状态字段取值。
+--
+-- 幂等:
+-- - 字典按 dict_code 判重,已存在则跳过;
+-- - 字典项按 (dict_id, item_value) 判重,已存在则跳过。
+-- 因此可重复执行,不会产生重复数据。
+--
+-- 说明:
+-- - tag_type 对应前端 el-tag 的 type,留空表示不渲染彩色标签;
+-- - sort 按 10 递增,便于后续插入新字典项;
+-- - 执行完这份数据后,前端改为调用 /platform/dict/options/:code 读取。
+-- =============================================================
+
+SET NAMES utf8mb4;
+
+
+-- =============================================================
+-- 1. 字典列表
+-- =============================================================
+
+INSERT INTO `yz_platform_dict` (`dict_name`, `dict_code`, `remark`, `sort`, `status`, `create_user_id`, `create_time`)
+SELECT t.dict_name, t.dict_code, t.remark, t.sort, 1, 'sql', NOW()
+FROM (
+ -- 通用
+ SELECT '通用启用状态' AS dict_name, 'common_enable_status' AS dict_code, '1 启用 / 0 停用,部分页面把 0 显示为「禁用」' AS remark, 10 AS sort
+ UNION ALL SELECT '是否' , 'common_yes_no' , '1 是 / 0 否' , 20
+ -- 支付
+ UNION ALL SELECT '支付渠道' , 'payment_channel' , 'yz_platform_payment_channel.channel' , 100
+ UNION ALL SELECT '支付单状态' , 'payment_order_status' , 'yz_platform_payment_order.status' , 110
+ UNION ALL SELECT '支付订单类型' , 'payment_order_type' , 'yz_platform_payment_order.order_type' , 120
+ UNION ALL SELECT '支付渠道状态' , 'payment_channel_state' , '渠道配置页展示状态:启用/停用/未配置' , 130
+ UNION ALL SELECT '对账处理状态' , 'payment_reconcile_status' , 'yz_platform_payment_reconcile_diff.handle_status' , 140
+ UNION ALL SELECT '对账差异类型' , 'payment_reconcile_diff_type', 'yz_platform_payment_reconcile_diff.diff_type' , 150
+ UNION ALL SELECT '佣金状态' , 'payment_commission_status', 'yz_platform_commission_order.status' , 160
+ UNION ALL SELECT '佣金发放方式' , 'payment_commission_pay_method', 'yz_platform_commission_order.pay_method' , 170
+ UNION ALL SELECT '佣金计算方式' , 'payment_calc_type' , 'yz_platform_commission_rule.calc_type' , 180
+ -- 通知 / 短信
+ UNION ALL SELECT '通知渠道' , 'notification_channel' , '站内提醒的推送渠道' , 200
+ UNION ALL SELECT '短信任务状态' , 'sms_task_status' , 'yz_system_sms_task.status' , 210
+ -- 系统
+ UNION ALL SELECT '请求方法' , 'http_method' , '操作日志的 HTTP 方法' , 220
+ UNION ALL SELECT '登录方式' , 'login_type' , '登录日志的登录方式' , 230
+ UNION ALL SELECT '菜单类型' , 'menu_type' , 'yz_system_menu.type' , 240
+ -- 官网内容
+ UNION ALL SELECT '导航类型' , 'website_nav_type' , 'yz_platform_website_nav.type' , 250
+ UNION ALL SELECT '评论状态' , 'comment_status' , 'yz_platform_website_comment.status' , 260
+ UNION ALL SELECT '文章状态' , 'article_status' , 'yz_platform_website_article.status' , 270
+ UNION ALL SELECT '公告类型' , 'notice_type' , 'yz_platform_website_notice.type' , 280
+ UNION ALL SELECT '公告状态' , 'notice_status' , 'yz_platform_website_notice.status' , 290
+ -- 账号池 / Cursor
+ UNION ALL SELECT '账号池平台' , 'account_pool_platform' , '账号来源平台(本地/闲鱼/淘宝…)' , 300
+ UNION ALL SELECT '账号池数据类型' , 'account_pool_data_type' , '账号密码 / 账号密码+Token / Token' , 310
+ UNION ALL SELECT '账号池提取状态' , 'account_pool_extract_status', '账号池记录的提取状态' , 320
+ UNION ALL SELECT 'Cursor 设备状态' , 'cursor_equipment_status' , 'yz_platform_cursor_equipment.status' , 330
+ UNION ALL SELECT 'Cursor 激活码状态' , 'cursor_activation_status' , 'yz_platform_cursor_activation_code.status' , 340
+ UNION ALL SELECT '提取记录状态' , 'cursor_extract_record_status', 'Cursor 设备提取记录的状态' , 350
+ UNION ALL SELECT '激活记录状态' , 'cursor_activation_record_status', 'Cursor 设备激活记录的状态' , 360
+ -- 模块商店
+ UNION ALL SELECT '模块发布状态' , 'moduleshop_publish_status' , '模块商店上下架状态' , 370
+ UNION ALL SELECT '模块分类状态' , 'moduleshop_category_status' , '模块商店分类启用状态' , 380
+ -- 投诉工单
+ UNION ALL SELECT '投诉工单状态' , 'complain_status' , 'yz_platform_complaint.status' , 390
+ -- 统一认证
+ UNION ALL SELECT '接入应用类型' , 'auth_app_type' , 'yz_auth_client.app_type' , 400
+ UNION ALL SELECT '认证域' , 'auth_realm' , '租户域 / 平台域' , 410
+ UNION ALL SELECT '登录验证方式' , 'auth_verify_type' , '图形验证码 / 短信 / 邮箱 / 极验 / 不开启' , 420
+ -- 域名
+ UNION ALL SELECT '域名类型' , 'domain_type' , 'yz_system_tenant_domain.domain_type' , 430
+ UNION ALL SELECT '域名状态' , 'domain_status' , 'yz_system_tenant_domain.status' , 440
+ -- 租户 / 组织
+ UNION ALL SELECT '员工状态' , 'employee_status' , 'yz_backend_employee.account_status' , 450
+ UNION ALL SELECT '性别' , 'gender' , '用户/员工性别' , 460
+ UNION ALL SELECT '角色分类' , 'role_category' , 'yz_system_admin_role.cid:1 平台角色 / 2 租户角色' , 470
+ UNION ALL SELECT '角色来源' , 'role_source' , 'yz_system_admin_role.is_custom' , 480
+ UNION ALL SELECT '站内信接收对象' , 'sitereminder_target_type' , 'yz_system_reminder_list.target_type' , 490
+) AS t
+WHERE NOT EXISTS (
+ SELECT 1 FROM `yz_platform_dict` AS d
+ WHERE d.dict_code = t.dict_code AND d.delete_time IS NULL
+);
+
+
+-- =============================================================
+-- 2. 字典项
+-- =============================================================
+
+INSERT INTO `yz_platform_dict_item` (`dict_id`, `item_label`, `item_value`, `tag_type`, `sort`, `status`, `is_default`, `create_time`)
+SELECT d.id, t.item_label, t.item_value, t.tag_type, t.sort, 1, 0, NOW()
+FROM (
+ -- 通用启用状态
+ SELECT 'common_enable_status' AS dict_code, '启用' AS item_label, '1' AS item_value, 'success' AS tag_type, 10 AS sort
+ UNION ALL SELECT 'common_enable_status' , '停用' , '0' , 'info' , 20
+ -- 是否
+ UNION ALL SELECT 'common_yes_no' , '是' , '1' , 'success' , 10
+ UNION ALL SELECT 'common_yes_no' , '否' , '0' , 'info' , 20
+ -- 支付渠道
+ UNION ALL SELECT 'payment_channel' , '微信支付' , 'wechat' , '' , 10
+ UNION ALL SELECT 'payment_channel' , '支付宝' , 'alipay' , '' , 20
+ UNION ALL SELECT 'payment_channel' , '银联' , 'unionpay' , '' , 30
+ UNION ALL SELECT 'payment_channel' , '云闪付' , 'cloudpay' , '' , 40
+ UNION ALL SELECT 'payment_channel' , 'PayPal' , 'paypal' , '' , 50
+ -- 支付单状态
+ UNION ALL SELECT 'payment_order_status' , '已创建' , 'created' , 'info' , 10
+ UNION ALL SELECT 'payment_order_status' , '待支付' , 'pending' , 'warning' , 20
+ UNION ALL SELECT 'payment_order_status' , '支付中' , 'paying' , 'warning' , 30
+ UNION ALL SELECT 'payment_order_status' , '支付成功' , 'paid' , 'success' , 40
+ UNION ALL SELECT 'payment_order_status' , '支付成功' , 'success' , 'success' , 50
+ UNION ALL SELECT 'payment_order_status' , '支付失败' , 'failed' , 'danger' , 60
+ UNION ALL SELECT 'payment_order_status' , '已关闭' , 'closed' , 'info' , 70
+ UNION ALL SELECT 'payment_order_status' , '已超时' , 'timeout' , 'info' , 80
+ UNION ALL SELECT 'payment_order_status' , '退款中' , 'refunding' , 'warning' , 90
+ UNION ALL SELECT 'payment_order_status' , '已退款' , 'refunded' , 'info' , 100
+ UNION ALL SELECT 'payment_order_status' , '部分退款' , 'partial_refunded' , 'warning' , 110
+ -- 支付订单类型
+ UNION ALL SELECT 'payment_order_type' , '平台使用费' , 'platform_usage' , '' , 10
+ UNION ALL SELECT 'payment_order_type' , '模块购买' , 'module_shop' , '' , 20
+ UNION ALL SELECT 'payment_order_type' , '服务费' , 'service_fee' , '' , 30
+ -- 支付渠道状态
+ UNION ALL SELECT 'payment_channel_state' , '已启用' , 'enabled' , 'success' , 10
+ UNION ALL SELECT 'payment_channel_state' , '已停用' , 'disabled' , 'info' , 20
+ UNION ALL SELECT 'payment_channel_state' , '未配置' , 'unconfigured' , 'warning' , 30
+ -- 对账处理状态
+ UNION ALL SELECT 'payment_reconcile_status' , '待处理' , 'unhandled' , 'warning' , 10
+ UNION ALL SELECT 'payment_reconcile_status' , '处理中' , 'handling' , 'primary' , 20
+ UNION ALL SELECT 'payment_reconcile_status' , '已处理' , 'handled' , 'success' , 30
+ UNION ALL SELECT 'payment_reconcile_status' , '已忽略' , 'ignored' , 'info' , 40
+ -- 对账差异类型
+ UNION ALL SELECT 'payment_reconcile_diff_type' , '长款' , 'long' , 'warning' , 10
+ UNION ALL SELECT 'payment_reconcile_diff_type' , '短款' , 'short' , 'danger' , 20
+ UNION ALL SELECT 'payment_reconcile_diff_type' , '状态不一致' , 'status_mismatch' , 'warning' , 30
+ -- 佣金状态
+ UNION ALL SELECT 'payment_commission_status' , '应付' , 'payable' , 'warning' , 10
+ UNION ALL SELECT 'payment_commission_status' , '已付' , 'settled' , 'success' , 20
+ UNION ALL SELECT 'payment_commission_status' , '税务处理中' , 'tax_processing' , 'primary' , 30
+ UNION ALL SELECT 'payment_commission_status' , '已作废' , 'canceled' , 'info' , 40
+ -- 佣金发放方式
+ UNION ALL SELECT 'payment_commission_pay_method', '余额抵扣' , 'balance' , '' , 10
+ UNION ALL SELECT 'payment_commission_pay_method', '银行转账' , 'bank' , '' , 20
+ UNION ALL SELECT 'payment_commission_pay_method', '人工发放' , 'manual' , '' , 30
+ UNION ALL SELECT 'payment_commission_pay_method', '下期抵扣' , 'offset' , '' , 40
+ -- 佣金计算方式
+ UNION ALL SELECT 'payment_calc_type' , '按比例' , 'percent' , 'primary' , 10
+ UNION ALL SELECT 'payment_calc_type' , '固定金额' , 'fixed' , 'success' , 20
+ UNION ALL SELECT 'payment_calc_type' , '阶梯' , 'tiered' , 'warning' , 30
+ -- 通知渠道
+ UNION ALL SELECT 'notification_channel' , '短信' , 'SMS' , 'success' , 10
+ UNION ALL SELECT 'notification_channel' , '邮件' , 'EMAIL' , 'warning' , 20
+ UNION ALL SELECT 'notification_channel' , 'Bark推送' , 'BARK' , 'danger' , 30
+ UNION ALL SELECT 'notification_channel' , '站内信' , 'SITE_MSG' , 'primary' , 40
+ -- 短信任务状态
+ UNION ALL SELECT 'sms_task_status' , '待发送' , '0' , 'info' , 10
+ UNION ALL SELECT 'sms_task_status' , '发送中' , '1' , 'warning' , 20
+ UNION ALL SELECT 'sms_task_status' , '发送失败' , '2' , 'danger' , 30
+ UNION ALL SELECT 'sms_task_status' , '发送成功' , '3' , 'success' , 40
+ -- 请求方法
+ UNION ALL SELECT 'http_method' , 'GET' , 'GET' , 'info' , 10
+ UNION ALL SELECT 'http_method' , 'POST' , 'POST' , 'success' , 20
+ UNION ALL SELECT 'http_method' , 'PUT' , 'PUT' , 'warning' , 30
+ UNION ALL SELECT 'http_method' , 'DELETE' , 'DELETE' , 'danger' , 40
+ UNION ALL SELECT 'http_method' , 'PATCH' , 'PATCH' , 'primary' , 50
+ -- 登录方式
+ UNION ALL SELECT 'login_type' , '密码登录' , 'password' , '' , 10
+ UNION ALL SELECT 'login_type' , '短信验证码' , 'sms' , '' , 20
+ UNION ALL SELECT 'login_type' , '邮箱验证码' , 'email' , '' , 30
+ -- 菜单类型
+ UNION ALL SELECT 'menu_type' , '目录' , '1' , 'primary' , 10
+ UNION ALL SELECT 'menu_type' , '页面' , '2' , 'success' , 20
+ UNION ALL SELECT 'menu_type' , '接口' , '3' , 'info' , 30
+ -- 导航类型
+ UNION ALL SELECT 'website_nav_type' , '目录' , '1' , 'primary' , 10
+ UNION ALL SELECT 'website_nav_type' , '页面' , '2' , 'success' , 20
+ UNION ALL SELECT 'website_nav_type' , '外链' , '3' , 'info' , 30
+ UNION ALL SELECT 'website_nav_type' , '单页' , '4' , 'warning' , 40
+ -- 评论状态
+ UNION ALL SELECT 'comment_status' , '待审核' , '0' , 'warning' , 10
+ UNION ALL SELECT 'comment_status' , '已通过' , '1' , 'success' , 20
+ UNION ALL SELECT 'comment_status' , '已拒绝' , '2' , 'danger' , 30
+ -- 文章状态
+ UNION ALL SELECT 'article_status' , '草稿' , '0' , 'info' , 10
+ UNION ALL SELECT 'article_status' , '待审核' , '1' , 'warning' , 20
+ UNION ALL SELECT 'article_status' , '已发布' , '2' , 'success' , 30
+ UNION ALL SELECT 'article_status' , '已隐藏' , '3' , 'danger' , 40
+ -- 公告类型
+ UNION ALL SELECT 'notice_type' , '通知公告' , '1' , '' , 10
+ UNION ALL SELECT 'notice_type' , '活动公告' , '2' , '' , 20
+ UNION ALL SELECT 'notice_type' , '系统维护' , '3' , '' , 30
+ -- 公告状态
+ UNION ALL SELECT 'notice_status' , '草稿' , '0' , 'info' , 10
+ UNION ALL SELECT 'notice_status' , '已发布' , '1' , 'success' , 20
+ UNION ALL SELECT 'notice_status' , '已下线' , '2' , 'warning' , 30
+ -- 账号池平台
+ UNION ALL SELECT 'account_pool_platform' , '本地' , 'local' , 'info' , 10
+ UNION ALL SELECT 'account_pool_platform' , '闲鱼' , 'xianyu' , 'warning' , 20
+ UNION ALL SELECT 'account_pool_platform' , '拼多多' , 'pinduoduo' , 'danger' , 30
+ UNION ALL SELECT 'account_pool_platform' , '京东' , 'jingdong' , 'primary' , 40
+ UNION ALL SELECT 'account_pool_platform' , '抖音' , 'douyin' , 'success' , 50
+ UNION ALL SELECT 'account_pool_platform' , '淘宝' , 'taobao' , 'info' , 60
+ UNION ALL SELECT 'account_pool_platform' , '自有商城' , 'ziyoushangcheng' , 'warning' , 70
+ UNION ALL SELECT 'account_pool_platform' , '续杯' , 'xubei' , 'primary' , 80
+ -- 账号池数据类型
+ UNION ALL SELECT 'account_pool_data_type' , '账号密码' , 'account' , 'success' , 10
+ UNION ALL SELECT 'account_pool_data_type' , '账号密码+Token' , 'account_tk' , 'primary' , 20
+ UNION ALL SELECT 'account_pool_data_type' , 'Token' , 'tk' , 'warning' , 30
+ -- 账号池提取状态
+ UNION ALL SELECT 'account_pool_extract_status' , '未提取' , '0' , 'info' , 10
+ UNION ALL SELECT 'account_pool_extract_status' , '补号' , '2' , 'warning' , 20
+ UNION ALL SELECT 'account_pool_extract_status' , '续杯' , '3' , 'primary' , 30
+ UNION ALL SELECT 'account_pool_extract_status' , '已提取' , 'extracted' , 'success' , 40
+ -- Cursor 设备状态
+ UNION ALL SELECT 'cursor_equipment_status' , '已激活' , 'active' , 'success' , 10
+ UNION ALL SELECT 'cursor_equipment_status' , '未激活' , 'inactive' , 'info' , 20
+ UNION ALL SELECT 'cursor_equipment_status' , '禁用' , 'disabled' , 'danger' , 30
+ UNION ALL SELECT 'cursor_equipment_status' , '已过期' , 'expired' , 'warning' , 40
+ -- Cursor 激活码状态
+ UNION ALL SELECT 'cursor_activation_status' , '未使用' , '0' , 'info' , 10
+ UNION ALL SELECT 'cursor_activation_status' , '已使用' , '1' , 'success' , 20
+ UNION ALL SELECT 'cursor_activation_status' , '已过期' , '2' , 'warning' , 30
+ UNION ALL SELECT 'cursor_activation_status' , '已禁用' , '3' , 'danger' , 40
+ -- 提取记录状态
+ UNION ALL SELECT 'cursor_extract_record_status', '未提取' , '0' , 'info' , 10
+ UNION ALL SELECT 'cursor_extract_record_status', '已提取' , '1' , 'success' , 20
+ UNION ALL SELECT 'cursor_extract_record_status', '补号' , '2' , 'warning' , 30
+ UNION ALL SELECT 'cursor_extract_record_status', '异常' , '3' , 'danger' , 40
+ -- 激活记录状态
+ UNION ALL SELECT 'cursor_activation_record_status', '失败' , '0' , 'danger' , 10
+ UNION ALL SELECT 'cursor_activation_record_status', '成功' , '1' , 'success' , 20
+ -- 模块发布状态
+ UNION ALL SELECT 'moduleshop_publish_status' , '草稿' , '0' , 'info' , 10
+ UNION ALL SELECT 'moduleshop_publish_status' , '已发布' , '1' , 'success' , 20
+ UNION ALL SELECT 'moduleshop_publish_status' , '已撤回' , '2' , 'warning' , 30
+ -- 模块分类状态
+ UNION ALL SELECT 'moduleshop_category_status' , '禁用' , '0' , 'info' , 10
+ UNION ALL SELECT 'moduleshop_category_status' , '启用' , '1' , 'success' , 20
+ -- 投诉工单状态
+ UNION ALL SELECT 'complain_status' , '待处理' , '0' , 'warning' , 10
+ UNION ALL SELECT 'complain_status' , '处理中' , '1' , 'warning' , 20
+ UNION ALL SELECT 'complain_status' , '已回复' , '2' , 'success' , 30
+ UNION ALL SELECT 'complain_status' , '已关闭' , '3' , 'info' , 40
+ -- 接入应用类型
+ UNION ALL SELECT 'auth_app_type' , 'Web 后端' , '1' , '' , 10
+ UNION ALL SELECT 'auth_app_type' , 'SPA' , '2' , '' , 20
+ UNION ALL SELECT 'auth_app_type' , '原生 APP' , '3' , '' , 30
+ UNION ALL SELECT 'auth_app_type' , '小程序' , '4' , '' , 40
+ -- 认证域
+ UNION ALL SELECT 'auth_realm' , '租户域' , 'tenant' , '' , 10
+ UNION ALL SELECT 'auth_realm' , '平台域' , 'platform' , '' , 20
+ -- 登录验证方式
+ UNION ALL SELECT 'auth_verify_type' , '图形验证码' , 'captcha' , '' , 10
+ UNION ALL SELECT 'auth_verify_type' , '短信验证码' , 'sms' , '' , 20
+ UNION ALL SELECT 'auth_verify_type' , '邮箱验证码' , 'email' , '' , 30
+ UNION ALL SELECT 'auth_verify_type' , '极验人机验证' , 'geetest' , '' , 40
+ UNION ALL SELECT 'auth_verify_type' , '不开启' , 'none' , '' , 50
+ -- 域名类型
+ UNION ALL SELECT 'domain_type' , '平台二级域名' , '1' , '' , 10
+ UNION ALL SELECT 'domain_type' , '自有域名' , '2' , '' , 20
+ -- 域名状态
+ UNION ALL SELECT 'domain_status' , '审核中' , '0' , 'warning' , 10
+ UNION ALL SELECT 'domain_status' , '已生效' , '1' , 'success' , 20
+ UNION ALL SELECT 'domain_status' , '已禁用' , '2' , 'info' , 30
+ -- 员工状态
+ UNION ALL SELECT 'employee_status' , '启用' , '1' , 'success' , 10
+ UNION ALL SELECT 'employee_status' , '禁用' , '0' , 'info' , 20
+ UNION ALL SELECT 'employee_status' , '离职' , '2' , 'danger' , 30
+ -- 性别
+ UNION ALL SELECT 'gender' , '保密' , '0' , 'info' , 10
+ UNION ALL SELECT 'gender' , '男' , '1' , 'primary' , 20
+ UNION ALL SELECT 'gender' , '女' , '2' , 'danger' , 30
+ -- 角色分类
+ UNION ALL SELECT 'role_category' , '平台角色' , '1' , 'primary' , 10
+ UNION ALL SELECT 'role_category' , '租户角色' , '2' , 'warning' , 20
+ -- 角色来源
+ UNION ALL SELECT 'role_source' , '预置' , '0' , 'info' , 10
+ UNION ALL SELECT 'role_source' , '自建' , '1' , 'warning' , 20
+ -- 站内信接收对象
+ UNION ALL SELECT 'sitereminder_target_type' , '全平台(所有人)' , 'all' , 'danger' , 10
+ UNION ALL SELECT 'sitereminder_target_type' , '平台端(所有人)' , 'platform' , 'primary' , 20
+ UNION ALL SELECT 'sitereminder_target_type' , '租户端(所有租户)' , 'tenant_all' , 'success' , 30
+ UNION ALL SELECT 'sitereminder_target_type' , '租户角色' , 'tenant_role' , 'warning' , 40
+ UNION ALL SELECT 'sitereminder_target_type' , '租户' , 'tenant' , 'warning' , 50
+ UNION ALL SELECT 'sitereminder_target_type' , '租户用户' , 'tenant_user' , 'info' , 60
+ UNION ALL SELECT 'sitereminder_target_type' , '平台角色' , 'role' , 'warning' , 70
+) AS t
+JOIN `yz_platform_dict` AS d
+ ON d.dict_code = t.dict_code AND d.delete_time IS NULL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `yz_platform_dict_item` AS i
+ WHERE i.dict_id = d.id AND i.item_value = t.item_value AND i.delete_time IS NULL
+);
+
+
+-- =============================================================
+-- 3. 校验
+-- =============================================================
+
+SELECT d.dict_name, d.dict_code, COUNT(i.id) AS item_count
+FROM `yz_platform_dict` d
+LEFT JOIN `yz_platform_dict_item` i ON i.dict_id = d.id AND i.delete_time IS NULL
+WHERE d.delete_time IS NULL
+GROUP BY d.id, d.dict_name, d.dict_code
+ORDER BY d.sort, d.id;
diff --git a/go/models/backend_dict.go b/go/models/backend_dict.go
new file mode 100644
index 0000000..c496513
--- /dev/null
+++ b/go/models/backend_dict.go
@@ -0,0 +1,45 @@
+package models
+
+import "time"
+
+// BackendDict 租户端数据字典(字典列表): yz_backend_dict
+// 表结构见 docs/sql/create_dict.sql。租户隔离字段 tenant_id 与 yz_backend_erp_supplier 一致。
+type BackendDict struct {
+ ID uint64 `orm:"column(id);pk;auto" json:"id"`
+ TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
+ DictName string `orm:"column(dict_name);size(100)" json:"dict_name"`
+ DictCode string `orm:"column(dict_code);size(100)" json:"dict_code"`
+ Remark string `orm:"column(remark);size(500)" json:"remark"`
+ Sort int `orm:"column(sort);default(0)" json:"sort"`
+ Status int8 `orm:"column(status);default(1)" json:"status"`
+ CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
+ CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
+ UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
+ DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
+}
+
+func (m *BackendDict) TableName() string {
+ return "yz_backend_dict"
+}
+
+// BackendDictItem 租户端字典项: yz_backend_dict_item
+// tenant_id 冗余自所属字典,写入时由后端保证与字典一致。
+type BackendDictItem struct {
+ ID uint64 `orm:"column(id);pk;auto" json:"id"`
+ TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
+ DictID uint64 `orm:"column(dict_id);default(0)" json:"dict_id"`
+ ItemLabel string `orm:"column(item_label);size(100)" json:"item_label"`
+ ItemValue string `orm:"column(item_value);size(100)" json:"item_value"`
+ TagType string `orm:"column(tag_type);size(20)" json:"tag_type"`
+ Sort int `orm:"column(sort);default(0)" json:"sort"`
+ Status int8 `orm:"column(status);default(1)" json:"status"`
+ IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"`
+ Remark string `orm:"column(remark);size(500)" json:"remark"`
+ CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
+ UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
+ DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
+}
+
+func (m *BackendDictItem) TableName() string {
+ return "yz_backend_dict_item"
+}
diff --git a/go/models/init.go b/go/models/init.go
index 21bb297..9249333 100644
--- a/go/models/init.go
+++ b/go/models/init.go
@@ -210,6 +210,12 @@ func Init(_ string) {
new(AuthTenantIdp),
new(AuthTenantAuthConfig),
new(AuthLoginLog),
+
+ // 数据字典(docs/sql/create_dict.sql):平台端 / 租户端各一套
+ new(PlatformDict),
+ new(PlatformDictItem),
+ new(BackendDict),
+ new(BackendDictItem),
)
// 创建全局 Ormer
diff --git a/go/models/platform_dict.go b/go/models/platform_dict.go
new file mode 100644
index 0000000..dc9e003
--- /dev/null
+++ b/go/models/platform_dict.go
@@ -0,0 +1,42 @@
+package models
+
+import "time"
+
+// PlatformDict 平台端数据字典(字典列表): yz_platform_dict
+// 表结构见 docs/sql/create_dict.sql(人工执行建表,代码不做自动建表)。
+type PlatformDict struct {
+ ID uint64 `orm:"column(id);pk;auto" json:"id"`
+ DictName string `orm:"column(dict_name);size(100)" json:"dict_name"`
+ DictCode string `orm:"column(dict_code);size(100)" json:"dict_code"`
+ Remark string `orm:"column(remark);size(500)" json:"remark"`
+ Sort int `orm:"column(sort);default(0)" json:"sort"`
+ Status int8 `orm:"column(status);default(1)" json:"status"`
+ CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
+ CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
+ UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
+ DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
+}
+
+func (m *PlatformDict) TableName() string {
+ return "yz_platform_dict"
+}
+
+// PlatformDictItem 平台端字典项: yz_platform_dict_item
+type PlatformDictItem struct {
+ ID uint64 `orm:"column(id);pk;auto" json:"id"`
+ DictID uint64 `orm:"column(dict_id);default(0)" json:"dict_id"`
+ ItemLabel string `orm:"column(item_label);size(100)" json:"item_label"`
+ ItemValue string `orm:"column(item_value);size(100)" json:"item_value"`
+ TagType string `orm:"column(tag_type);size(20)" json:"tag_type"`
+ Sort int `orm:"column(sort);default(0)" json:"sort"`
+ Status int8 `orm:"column(status);default(1)" json:"status"`
+ IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"`
+ Remark string `orm:"column(remark);size(500)" json:"remark"`
+ CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
+ UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
+ DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
+}
+
+func (m *PlatformDictItem) TableName() string {
+ return "yz_platform_dict_item"
+}
diff --git a/go/routers/backend/backend.go b/go/routers/backend/backend.go
index 7b5c640..7ae4752 100644
--- a/go/routers/backend/backend.go
+++ b/go/routers/backend/backend.go
@@ -14,6 +14,7 @@ func Register() {
RegisterPaymentRoutes()
RegisterProductRoutes()
RegisterWechatMpRoutes()
+ RegisterDictRoutes()
}
// RegisterProductRoutes 注册租户端「产品购买」路由。
diff --git a/go/routers/backend/dict.go b/go/routers/backend/dict.go
new file mode 100644
index 0000000..9401e5d
--- /dev/null
+++ b/go/routers/backend/dict.go
@@ -0,0 +1,24 @@
+package backend
+
+import (
+ "server/controllers"
+
+ beego "github.com/beego/beego/v2/server/web"
+)
+
+// RegisterDictRoutes 注册租户端数据字典路由(表结构见 docs/sql/create_dict.sql)。
+// 租户隔离由 JWT 里的租户ID 完成,前端不传 tenant_id。
+func RegisterDictRoutes() {
+ // 字典列表(字典类型)
+ beego.Router("/backend/dict/types", &controllers.BackendDictController{}, "get:ListTypes;post:CreateType")
+ beego.Router("/backend/dict/types/:id", &controllers.BackendDictController{}, "put:UpdateType;delete:DeleteType")
+
+ // 字典项
+ beego.Router("/backend/dict/items", &controllers.BackendDictController{}, "get:ListItems;post:CreateItem")
+ beego.Router("/backend/dict/items/:id", &controllers.BackendDictController{}, "put:UpdateItem;delete:DeleteItem")
+ beego.Router("/backend/dict/items/:id/status", &controllers.BackendDictController{}, "post:ToggleItemStatus")
+
+ // 业务读取:单个编码 / 批量(指定编码)/ 全量(前端预热)
+ beego.Router("/backend/dict/options/:code", &controllers.BackendDictController{}, "get:Options")
+ beego.Router("/backend/dict/options", &controllers.BackendDictController{}, "get:OptionsAll;post:OptionsBatch")
+}
diff --git a/go/routers/backend/dict_test.go b/go/routers/backend/dict_test.go
new file mode 100644
index 0000000..3b66dfa
--- /dev/null
+++ b/go/routers/backend/dict_test.go
@@ -0,0 +1,15 @@
+package backend
+
+import "testing"
+
+// TestRegisterDictRoutes 确认字典路由能完成注册且不与既有路由冲突。
+// beego 在注册重复/歧义路由或非法方法名时会 panic,这里通过实际注册来兜住这类错误。
+func TestRegisterDictRoutes(t *testing.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ t.Fatalf("注册租户端字典路由失败: %v", r)
+ }
+ }()
+
+ RegisterDictRoutes()
+}
diff --git a/go/routers/platform/dict.go b/go/routers/platform/dict.go
new file mode 100644
index 0000000..ec12493
--- /dev/null
+++ b/go/routers/platform/dict.go
@@ -0,0 +1,23 @@
+package platform
+
+import (
+ "server/controllers"
+
+ beego "github.com/beego/beego/v2/server/web"
+)
+
+// RegisterDictRoutes 注册平台端数据字典路由(表结构见 docs/sql/create_dict.sql)。
+func RegisterDictRoutes() {
+ // 字典列表(字典类型)
+ beego.Router("/platform/dict/types", &controllers.PlatformDictController{}, "get:ListTypes;post:CreateType")
+ beego.Router("/platform/dict/types/:id", &controllers.PlatformDictController{}, "put:UpdateType;delete:DeleteType")
+
+ // 字典项
+ beego.Router("/platform/dict/items", &controllers.PlatformDictController{}, "get:ListItems;post:CreateItem")
+ beego.Router("/platform/dict/items/:id", &controllers.PlatformDictController{}, "put:UpdateItem;delete:DeleteItem")
+ beego.Router("/platform/dict/items/:id/status", &controllers.PlatformDictController{}, "post:ToggleItemStatus")
+
+ // 业务读取:单个编码 / 批量(指定编码)/ 全量(前端预热)
+ beego.Router("/platform/dict/options/:code", &controllers.PlatformDictController{}, "get:Options")
+ beego.Router("/platform/dict/options", &controllers.PlatformDictController{}, "get:OptionsAll;post:OptionsBatch")
+}
diff --git a/go/routers/platform/dict_test.go b/go/routers/platform/dict_test.go
new file mode 100644
index 0000000..39bd558
--- /dev/null
+++ b/go/routers/platform/dict_test.go
@@ -0,0 +1,15 @@
+package platform
+
+import "testing"
+
+// TestRegisterDictRoutes 确认字典路由能完成注册且不与既有路由冲突。
+// beego 在注册重复/歧义路由或非法方法名时会 panic,这里通过实际注册来兜住这类错误。
+func TestRegisterDictRoutes(t *testing.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ t.Fatalf("注册平台端字典路由失败: %v", r)
+ }
+ }()
+
+ RegisterDictRoutes()
+}
diff --git a/go/routers/platform/platform.go b/go/routers/platform/platform.go
index 0272584..35b1faf 100644
--- a/go/routers/platform/platform.go
+++ b/go/routers/platform/platform.go
@@ -14,6 +14,9 @@ func Register() {
// 微信公众号(服务号):配置 / 绑定 / 粉丝 / 推送 / 微信服务器回调
RegisterWechatMpRoutes()
+ // 数据字典(字典列表 / 字典项)
+ RegisterDictRoutes()
+
// 统一认证中心 - 租户登录策略配置
beego.Router("/platform/authConfig/detail", &controllers.PlatformAuthConfigController{}, "get:Detail")
beego.Router("/platform/authConfig/save", &controllers.PlatformAuthConfigController{}, "post:Save")
diff --git a/platform/components.d.ts b/platform/components.d.ts
index 32ee612..be750b5 100644
--- a/platform/components.d.ts
+++ b/platform/components.d.ts
@@ -18,6 +18,8 @@ declare module 'vue' {
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBacktop: typeof import('element-plus/es')['ElBacktop']
ElBadge: typeof import('element-plus/es')['ElBadge']
+ ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
+ ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCascader: typeof import('element-plus/es')['ElCascader']
diff --git a/platform/docs/pinia-dict-guide.md b/platform/docs/pinia-dict-guide.md
deleted file mode 100644
index f3365a4..0000000
--- a/platform/docs/pinia-dict-guide.md
+++ /dev/null
@@ -1,393 +0,0 @@
-# Pinia 字典管理系统使用指南
-
-## 系统架构
-
-```
-┌─────────────────────────────────────┐
-│ API 接口 (getDictItemsByCode) │
-│ /api/dict/items/code/{code} │
-└──────────────┬──────────────────────┘
- │
- ↓
-┌─────────────────────────────────────┐
-│ Pinia Store (useDictStore) │
-│ ✅ 自动缓存字典数据 │
-│ ✅ 避免重复请求 │
-│ ✅ 支持同步/异步访问 │
-└──────────────┬──────────────────────┘
- │
- ┌──────┴──────┐
- ↓ ↓
- ┌────────┐ ┌──────────────┐
- │组件 │ │Composable │
- │直接用 │ │useDict Hook │
- └────────┘ └──────────────┘
-```
-
----
-
-## 核心文件说明
-
-### 1. **Store**: `src/stores/dict.js`
-
-字典数据的全局管理器
-
-**主要方法**:
-```javascript
-import { useDictStore } from '@/stores/dict'
-
-const dictStore = useDictStore()
-
-// ✅ 异步获取字典(推荐)
-const items = await dictStore.getDictItems('user_status')
-
-// ✅ 同步获取字典(已缓存时)
-const items = dictStore.getDictItemsSync('user_status')
-
-// ✅ 获取字典值对应的标签
-const label = dictStore.getDictLabel('user_status', 1)
-
-// ✅ 预加载多个字典
-await dictStore.preloadDicts(['user_status', 'user_role'])
-
-// ✅ 清空缓存
-dictStore.clearCache('user_status')
-```
-
----
-
-### 2. **常量**: `src/constants/dictCodes.js`
-
-集中管理所有字典编码
-
-**使用示例**:
-```javascript
-import { DICT_CODES } from '@/constants/dictCodes'
-
-// 好处:避免硬编码,IDE 有自动完成
-const items = await dictStore.getDictItems(DICT_CODES.USER_STATUS)
-
-// 所有可用的编码:
-DICT_CODES.USER_STATUS // 用户状态
-DICT_CODES.USER_GENDER // 用户性别
-DICT_CODES.USER_ROLE // 用户角色
-DICT_CODES.DEPT_STATUS // 部门状态
-DICT_CODES.POSITION_STATUS // 职位状态
-// ... 更多编码
-```
-
----
-
-### 3. **Composable**: `src/composables/useDict.js`
-
-简化在组件中使用字典的 Hook
-
-**基础用法**:
-```javascript
-import { useDictionary, useUserStatusDict } from '@/composables/useDict'
-import { DICT_CODES } from '@/constants/dictCodes'
-
-// 方式1:使用常量
-const { statusDict, loading } = useDictionary(DICT_CODES.USER_STATUS)
-
-// 方式2:使用字符串
-const { dicts, loading } = useDictionary('user_status')
-
-// 方式3:使用特化 Hook(推荐)
-const { user_statusDict, loading } = useUserStatusDict()
-```
-
----
-
-## 使用场景
-
-### 场景1:在列表页加载字典
-
-**文件**: `src/views/system/users/index.vue`
-
-```vue
-
-
-
-
-
-
-```
-
----
-
-### 场景2:在编辑对话框使用字典
-
-**文件**: `src/views/system/users/components/UserEdit.vue`
-
-```vue
-
-
-
-
-
-
-
-
-
-
-```
-
----
-
-### 场景3:快速使用 Composable Hook
-
-最简单的方式,自动处理加载:
-
-```vue
-
-
-
-
-
-```
-
----
-
-### 场景4:预加载应用启动时需要的字典
-
-**文件**: `src/main.js`
-
-```javascript
-import { createApp } from 'vue'
-import { createPinia } from 'pinia'
-import { useDictStore } from '@/stores/dict'
-import { DICT_CODES } from '@/constants/dictCodes'
-
-const app = createApp(App)
-const pinia = createPinia()
-
-app.use(pinia)
-
-// 在应用启动后预加载常用字典
-const dictStore = useDictStore()
-await dictStore.preloadDicts([
- DICT_CODES.USER_STATUS,
- DICT_CODES.COMMON_STATUS,
- DICT_CODES.YES_NO,
-])
-
-app.mount('#app')
-```
-
----
-
-## 字典数据结构
-
-后端返回的字典数据结构:
-
-```json
-[
- {
- "dict_id": 1,
- "dict_value": "1",
- "dict_label": "启用",
- "dict_type": "user_status",
- "remarks": "用户启用状态",
- "remark": "用户启用状态"
- },
- {
- "dict_id": 2,
- "dict_value": "0",
- "dict_label": "禁用",
- "dict_type": "user_status",
- "remarks": "用户禁用状态",
- "remark": "用户禁用状态"
- }
-]
-```
-
-**关键字段**:
-- `dict_value`: 字典值(存储在数据库中)
-- `dict_label`: 字典标签(显示给用户)
-- `dict_type`: 字典类型编码(如 'user_status')
-
----
-
-## 最佳实践
-
-### ✅ DO
-
-1. **使用常量而不是硬编码字符串**
- ```javascript
- // ✅ 好
- dictStore.getDictItems(DICT_CODES.USER_STATUS)
-
- // ❌ 差
- dictStore.getDictItems('user_status')
- ```
-
-2. **在父组件加载,通过 props 传给子组件**
- ```javascript
- // ✅ 父组件负责数据,子组件负责展示
- // index.vue
- const statusDict = await dictStore.getDictItems(DICT_CODES.USER_STATUS)
-
- // UserEdit.vue
- const props = defineProps({ statusDict: Array })
- ```
-
-3. **用 Composable 简化组件逻辑**
- ```javascript
- // ✅ 一行代码搞定
- const { user_statusDict, loading } = useUserStatusDict()
- ```
-
-4. **预加载常用字典**
- ```javascript
- // ✅ 应用启动时预加载,避免页面初始化时加载
- await dictStore.preloadDicts([...])
- ```
-
-### ❌ DON'T
-
-1. **不要在多个地方重复加载同一个字典**
- ```javascript
- // ❌ 糟糕:重复加载
- // 页面A
- const dict1 = await dictStore.getDictItems('user_status')
-
- // 页面B(Store 会自动缓存,但代码看起来重复)
- const dict2 = await dictStore.getDictItems('user_status')
- ```
-
-2. **不要忘记处理加载状态**
- ```javascript
- // ❌ 可能展示空白
- const { statusDict } = useUserStatusDict()
-
- // ✅ 处理加载状态
- const { statusDict, loading } = useUserStatusDict()
- if (loading) { /* 显示加载中 */ }
- ```
-
-3. **不要混用不同的字典访问方式**
- ```javascript
- // ❌ 混乱
- const dict1 = await dictStore.getDictItems('user_status')
- const dict2 = dictStore.getDictItemsSync('user_role')
-
- // ✅ 统一使用
- const dict1 = await dictStore.getDictItems('user_status')
- const dict2 = await dictStore.getDictItems('user_role')
- ```
-
----
-
-## 性能优化建议
-
-| 优化项 | 说明 |
-|------|------|
-| **缓存** | Store 自动缓存,同一个字典只请求一次 |
-| **预加载** | 在路由切换前预加载需要的字典 |
-| **同步访问** | 已加载的字典可用 `getDictItemsSync` 同步获取 |
-| **避免重复** | 不要在多个组件重复请求同一个字典 |
-
----
-
-## 故障排查
-
-### 问题1:状态选项为空
-
-**原因**:字典未加载
-**解决**:
-```javascript
-// ❌ 错误:字典还未加载
-const statusDict = dictStore.getDictItemsSync('user_status') // 返回 []
-
-// ✅ 正确:等待异步加载完成
-const statusDict = await dictStore.getDictItems('user_status')
-```
-
-### 问题2:重复加载字典
-
-**原因**:没有使用 Store 的缓存
-**解决**:
-```javascript
-// 所有调用都会自动使用缓存,只请求一次
-await dictStore.getDictItems('user_status') // 首次:发送请求
-await dictStore.getDictItems('user_status') // 第二次:返回缓存
-```
-
-### 问题3:字典显示不对
-
-**原因**:value 类型不匹配(如 1 vs "1")
-**解决**:
-```javascript
-// Store 会自动处理类型匹配
-const item = items.find(i =>
- String(i.dict_value) === String(value) || i.dict_value === value
-)
-```
-
----
-
-## 集成检清表
-
-- [ ] 创建 `src/stores/dict.js` - Store
-- [ ] 创建 `src/constants/dictCodes.js` - 常量
-- [ ] 创建 `src/composables/useDict.js` - Composable
-- [ ] 在 `index.vue` 中导入 `useDictStore`
-- [ ] 在 `UserEdit.vue` 中接收 `statusDict` props
-- [ ] 测试字典加载和显示
-- [ ] 验证缓存功能(打开浏览器 DevTools 检查 Network)
-- [ ] 预加载常用字典(可选)
-
----
-
-## 相关文件修改
-
-已修改的文件:
-- ✅ `src/stores/dict.js` - 新建
-- ✅ `src/constants/dictCodes.js` - 新建
-- ✅ `src/composables/useDict.js` - 新建
-- ✅ `src/views/system/users/index.vue` - 使用 `useDictStore`
-- ✅ `src/views/system/users/components/UserEdit.vue` - 导入字典库
-
diff --git a/platform/src/api/dict.js b/platform/src/api/dict.js
index 3a5cb07..b452fd3 100644
--- a/platform/src/api/dict.js
+++ b/platform/src/api/dict.js
@@ -1,114 +1,123 @@
import request from '@/utils/request'
-// 获取字典类型列表
+/**
+ * 数据字典接口(平台端)
+ *
+ * 表:yz_platform_dict / yz_platform_dict_item
+ * 平台端字典不做租户隔离,与租户端(/backend/dict)各自独立。
+ */
+const PREFIX = '/platform/dict'
+
+/* ================= 字典列表(字典类型) ================= */
+
+/** 字典列表(分页)params: { page, pageSize, keyword, status } */
export function getDictTypes(params) {
return request({
- url: '/platform/dict/types',
+ url: `${PREFIX}/types`,
method: 'get',
params
})
}
-// 根据ID获取字典类型
-export function getDictTypeById(id) {
+/** 新增字典 */
+export function createDictType(data) {
return request({
- url: `/platform/dict/types/${id}`,
- method: 'get'
- })
-}
-
-// 添加字典类型
-export function addDictType(data) {
- return request({
- url: '/platform/dict/types',
+ url: `${PREFIX}/types`,
method: 'post',
- data: {
- ...data,
- is_global: data.is_global !== undefined ? data.is_global : 0
- }
+ data
})
}
-// 更新字典类型
+/** 编辑字典(dict_code 不可改,由后端忽略) */
export function updateDictType(id, data) {
return request({
- url: `/platform/dict/types/${id}`,
+ url: `${PREFIX}/types/${id}`,
method: 'put',
- data: {
- ...data,
- is_global: data.is_global !== undefined ? data.is_global : 0
- }
+ data
})
}
-// 删除字典类型
+/** 删除字典(其下仍有字典项时后端会拒绝) */
export function deleteDictType(id) {
return request({
- url: `/platform/dict/types/${id}`,
+ url: `${PREFIX}/types/${id}`,
method: 'delete'
})
}
-// 获取字典项列表
+/* ================= 字典项 ================= */
+
+/** 字典项列表(分页)params: { dict_id, page, pageSize, keyword, status } */
export function getDictItems(params) {
return request({
- url: '/platform/dict/items',
+ url: `${PREFIX}/items`,
method: 'get',
params
})
}
-// 根据ID获取字典项
-export function getDictItemById(id) {
+/** 新增字典项 */
+export function createDictItem(data) {
return request({
- url: `/platform/dict/items/${id}`,
- method: 'get'
- })
-}
-
-// 添加字典项
-export function addDictItem(data) {
- return request({
- url: '/platform/dict/items',
+ url: `${PREFIX}/items`,
method: 'post',
data
})
}
-// 更新字典项
+/** 编辑字典项 */
export function updateDictItem(id, data) {
return request({
- url: `/platform/dict/items/${id}`,
+ url: `${PREFIX}/items/${id}`,
method: 'put',
data
})
}
-// 删除字典项
+/** 启用 / 停用字典项 */
+export function toggleDictItemStatus(id, status) {
+ return request({
+ url: `${PREFIX}/items/${id}/status`,
+ method: 'post',
+ data: { status }
+ })
+}
+
+/** 删除字典项 */
export function deleteDictItem(id) {
return request({
- url: `/platform/dict/items/${id}`,
+ url: `${PREFIX}/items/${id}`,
method: 'delete'
})
}
-// 根据字典编码获取字典项(用于业务查询)
-export function getDictItemsByCode(code, includeDisabled = false) {
+/* ================= 业务读取 ================= */
+
+/** 按字典编码取「字典类型与字典项都启用」的选项,供业务页面下拉使用 */
+export function getDictOptions(code) {
return request({
- url: `/platform/dict/items/code/${code}`,
- method: 'get',
- params: {
- include_disabled: includeDisabled ? '1' : '0'
- }
+ url: `${PREFIX}/options/${code}`,
+ method: 'get'
})
}
-// 批量更新字典项排序
-export function batchUpdateDictItemSort(data) {
+/**
+ * 批量取多个字典的选项(一次请求),供页面/应用启动时预加载。
+ * 返回 data.dicts = { [dict_code]: 字典项[] },不存在的编码返回空数组。
+ * @param {string[]} codes 字典编码数组
+ */
+export function getDictOptionsBatch(codes) {
return request({
- url: '/platform/dict/items/sort',
- method: 'put',
- data
+ url: `${PREFIX}/options`,
+ method: 'post',
+ data: { codes }
})
}
+/** 一次取回全部「启用」字典的字典项,供登录后预热缓存 */
+export function getAllDictOptions() {
+ return request({
+ url: `${PREFIX}/options`,
+ method: 'get'
+ })
+}
diff --git a/platform/src/composables/useDict.js b/platform/src/composables/useDict.js
new file mode 100644
index 0000000..aed3803
--- /dev/null
+++ b/platform/src/composables/useDict.js
@@ -0,0 +1,59 @@
+import { computed } from 'vue'
+import { useDictStore } from '@/stores/dict'
+
+/**
+ * 字典 Composable:把「加载 + 取值 + 文案/标签色查询」收敛成一行调用。
+ *
+ * 用法:
+ * const { items, options, label, tagType } = useDictionary('payment_channel')
+ * // 模板:
+ * //
{{ label(row.status) }}
+ *
+ * @param {string} code 字典编码(库里真实的 dict_code)
+ */
+export function useDictionary(code) {
+ const dictStore = useDictStore()
+ // setup 阶段同步触发加载:缓存命中时不发请求,首次会自动拉取并响应式刷新
+ dictStore.ensure(code)
+
+ /** 字典项原始数组(含 item_value / item_label / tag_type 等) */
+ const items = computed(() => dictStore.getDictItemsSync(code))
+ /** el-select / el-option 直接可用的 { label, value } 列表 */
+ const options = computed(() => items.value.map((it) => ({
+ label: it.item_label,
+ value: it.item_value,
+ tag_type: it.tag_type
+ })))
+
+ return {
+ items,
+ options,
+ label: (value, fallback) => dictStore.getDictLabel(code, value, fallback),
+ tagType: (value, fallback) => dictStore.getDictTagType(code, value, fallback),
+ find: (value) => dictStore.getDictItem(code, value)
+ }
+}
+
+/**
+ * 只要「值 → 文案」或「值 → 标签色」时的轻量用法。
+ * const channelLabel = useDictLabel('payment_channel')
+ */
+export function useDictLabel(code) {
+ const dictStore = useDictStore()
+ dictStore.ensure(code)
+ return (value, fallback) => dictStore.getDictLabel(code, value, fallback)
+}
+
+export function useDictTagType(code) {
+ const dictStore = useDictStore()
+ dictStore.ensure(code)
+ return (value, fallback) => dictStore.getDictTagType(code, value, fallback)
+}
+
+/** 预加载多个字典(页面 onMounted 里调用,一次批量请求) */
+export function useDictPreload(codes) {
+ const dictStore = useDictStore()
+ return () => dictStore.preloadDicts(codes)
+}
+
+export default useDictionary
diff --git a/platform/src/stores/dict.js b/platform/src/stores/dict.js
new file mode 100644
index 0000000..d7f7770
--- /dev/null
+++ b/platform/src/stores/dict.js
@@ -0,0 +1,187 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import { getDictOptions, getDictOptionsBatch, getAllDictOptions } from '@/api/dict'
+
+/**
+ * 字典 Store(平台端)
+ *
+ * 全部字典数据都来自数据库,前端不维护编码清单:
+ * - 登录后调用一次 loadAll(),把库里所有「启用」字典读进缓存;
+ * - 页面按库里真实的 dict_code 取值即可(字符串直接写,无需常量文件);
+ * - 之后在字典管理页新增的字典,第一次被引用时会按需自动加载。
+ *
+ * 用法:
+ * const dictStore = useDictStore()
+ * dictStore.getDictLabel('payment_order_status', row.status) // 文案
+ * dictStore.getDictTagType('payment_order_status', row.status) // 标签色
+ * dictStore.getSelectOptions('payment_channel') // 下拉选项
+ */
+export const useDictStore = defineStore('dict', () => {
+ /** dict_code -> 字典项数组(渲染依赖它,必须是响应式的) */
+ const dictMap = ref({})
+
+ /** 已加载过的编码(仅用于去重,不参与渲染) */
+ let loaded = {}
+ /** 正在请求中的编码 -> Promise(并发调用复用同一个请求) */
+ const pending = new Map()
+ /** 是否已完成全量预热 */
+ let allLoaded = false
+ let allPending = null
+
+ function setItems(code, list) {
+ dictMap.value = { ...dictMap.value, [code]: Array.isArray(list) ? list : [] }
+ }
+
+ function itemsOf(code) {
+ return dictMap.value[code] || []
+ }
+
+ /** 把入参规范成去重后的编码数组 */
+ function normalizeCodes(codes) {
+ return [...new Set((codes || []).map((c) => String(c || '').trim()).filter(Boolean))]
+ }
+
+ /**
+ * 加载字典(带缓存与并发去重)。
+ * 请求失败也会标记为已加载,避免页面反复重试;需要重试请先 clearCache。
+ */
+ async function load(codes) {
+ const list = normalizeCodes(codes)
+ const missing = list.filter((c) => !loaded[c] && !pending.has(c))
+
+ if (missing.length) {
+ const promise = (async () => {
+ if (missing.length === 1) {
+ // 单个字典走专用接口
+ const code = missing[0]
+ try {
+ const res = await getDictOptions(code)
+ setItems(code, res?.code === 200 ? res.data?.list || [] : [])
+ } catch {
+ setItems(code, [])
+ }
+ } else {
+ // 多个字典合并成一次请求
+ try {
+ const res = await getDictOptionsBatch(missing)
+ const dicts = res?.code === 200 ? res.data?.dicts || {} : {}
+ missing.forEach((code) => setItems(code, dicts[code] || []))
+ } catch {
+ missing.forEach((code) => setItems(code, []))
+ }
+ }
+ })().finally(() => {
+ missing.forEach((code) => {
+ loaded[code] = true
+ pending.delete(code)
+ })
+ })
+
+ missing.forEach((code) => pending.set(code, promise))
+ }
+
+ await Promise.all(list.map((c) => pending.get(c) || Promise.resolve()))
+ return list.flatMap((c) => itemsOf(c))
+ }
+
+ /**
+ * 全量预热:一次请求把库里所有启用字典读进缓存(登录后调用一次即可)。
+ * 之后再新增的字典仍会走 load() 按需加载。
+ */
+ function loadAll() {
+ if (allLoaded) return Promise.resolve()
+ if (allPending) return allPending
+
+ allPending = (async () => {
+ try {
+ const res = await getAllDictOptions()
+ const dicts = res?.code === 200 ? res.data?.dicts || {} : {}
+ const next = { ...dictMap.value }
+ Object.entries(dicts).forEach(([code, list]) => {
+ loaded[code] = true
+ next[code] = Array.isArray(list) ? list : []
+ })
+ dictMap.value = next
+ } catch {
+ // 预热失败不影响使用:后续按需加载
+ } finally {
+ allLoaded = true
+ allPending = null
+ }
+ })()
+ return allPending
+ }
+
+ /** 同步触发加载(不等待),用于组件 setup/onMounted 里“先渲染后填充” */
+ function ensure(code) {
+ const list = normalizeCodes([code])
+ if (list.some((c) => !loaded[c] && !pending.has(c))) {
+ load(list)
+ }
+ }
+
+ /** 同步取字典项;未加载时顺带触发加载,拿到后由响应式自动刷新 */
+ function getDictItemsSync(code) {
+ ensure(code)
+ return itemsOf(code)
+ }
+
+ /** 值 → 字典项(值按字符串比较,兼容 1 / '1' 混用) */
+ function getDictItem(code, value) {
+ if (value === undefined || value === null || value === '') return null
+ const key = String(value)
+ return itemsOf(code).find((it) => String(it.item_value) === key) || null
+ }
+
+ /** 值 → 文案;取不到时返回 fallback,未传则原样返回取值 */
+ function getDictLabel(code, value, fallback) {
+ const hit = getDictItem(code, value)
+ if (hit) return hit.item_label
+ if (fallback !== undefined) return fallback
+ return value === undefined || value === null ? '' : String(value)
+ }
+
+ /** 值 → el-tag 的 type;取不到时返回 fallback(默认 info) */
+ function getDictTagType(code, value, fallback = 'info') {
+ const hit = getDictItem(code, value)
+ return hit && hit.tag_type ? hit.tag_type : fallback
+ }
+
+ /** 值 → 下拉选项 { label, value },供 el-select / el-option 直接使用 */
+ function getSelectOptions(code) {
+ return getDictItemsSync(code).map((it) => ({ label: it.item_label, value: it.item_value, tag_type: it.tag_type }))
+ }
+
+ /** 预加载多个字典(一次批量请求) */
+ function preloadDicts(codes) {
+ return load(codes)
+ }
+
+ /** 清空缓存:传编码清单个,不传清全部(同时允许重新全量预热) */
+ function clearCache(code) {
+ if (code) {
+ delete loaded[code]
+ const next = { ...dictMap.value }
+ delete next[code]
+ dictMap.value = next
+ return
+ }
+ loaded = {}
+ dictMap.value = {}
+ allLoaded = false
+ }
+
+ return {
+ dictMap,
+ ensure,
+ loadAll,
+ getDictItems: load,
+ getDictItemsSync,
+ getDictItem,
+ getDictLabel,
+ getDictTagType,
+ getSelectOptions,
+ preloadDicts,
+ clearCache
+ }
+})
diff --git a/platform/src/utils/datetime.ts b/platform/src/utils/datetime.ts
index 6acb1c6..1fcde59 100644
--- a/platform/src/utils/datetime.ts
+++ b/platform/src/utils/datetime.ts
@@ -53,3 +53,4 @@ export function formatDate(value: any): string {
export function formatDateOnly(value: any): string {
return formatDate(value);
}
+
diff --git a/platform/src/views/Main.vue b/platform/src/views/Main.vue
index e7dcbe4..c71e10a 100644
--- a/platform/src/views/Main.vue
+++ b/platform/src/views/Main.vue
@@ -2,12 +2,14 @@
import CommonAside from '@/components/CommonAside.vue';
import CommonHeader from '@/components/CommonHeader.vue';
import { useTabsStore } from '@/stores';
+import { useDictStore } from '@/stores/dict';
import { useRouter, useRoute } from 'vue-router';
import { ref, watch, reactive, nextTick, onMounted, computed } from 'vue';
import { More, Close, CircleClose, ArrowUp } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
const tabsStore = useTabsStore();
+const dictStore = useDictStore();
const router = useRouter();
const route = useRoute();
const asideRef = ref(null);
@@ -39,6 +41,9 @@ function restoreTabFromRoute() {
// 组件挂载后,根据当前路由恢复 tab
onMounted(() => {
+ // 进入后台后把库里所有字典预热到缓存(失败不影响按需加载)
+ dictStore.loadAll();
+
// 等待路由完全加载后再恢复
nextTick(() => {
// 刷新时,localStorage 中已保存的 tabs 会在 store 初始化时恢复
diff --git a/platform/src/views/system/dict/components/DictItemEdit.vue b/platform/src/views/system/dict/components/DictItemEdit.vue
index ed57bd9..4fb0f04 100644
--- a/platform/src/views/system/dict/components/DictItemEdit.vue
+++ b/platform/src/views/system/dict/components/DictItemEdit.vue
@@ -1,335 +1,173 @@
-
-
-
-
- 当前字典类型:{{ dictTypeDisplayName }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 启用
- 禁用
-
-
-
-
-
-
-
-
-
-
-
- 支持十六进制颜色值(如:#FF0000)或颜色名称(如:red)
-
-
-
- 支持emoji或图标字符
-
-
-
-
-
-
- 取消
-
- 保存
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+}
+
diff --git a/platform/src/views/system/dict/components/DictItemEditDialog.vue b/platform/src/views/system/dict/components/DictItemEditDialog.vue
deleted file mode 100644
index 9717310..0000000
--- a/platform/src/views/system/dict/components/DictItemEditDialog.vue
+++ /dev/null
@@ -1,316 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 启用
- 禁用
-
-
-
-
-
-
-
- 用于前端显示时的颜色标记
-
-
-
- 用于前端显示时的图标
-
-
-
-
-
-
- 取消
-
- 保存
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/platform/src/views/system/dict/components/DictItemList.vue b/platform/src/views/system/dict/components/DictItemList.vue
deleted file mode 100644
index e7303d6..0000000
--- a/platform/src/views/system/dict/components/DictItemList.vue
+++ /dev/null
@@ -1,422 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- 重试
-
-
-
-
-
-
-
-
-
- {{ row.dict_value }}
-
-
-
-
-
-
- {{ row.status === 1 ? '启用' : '禁用' }}
-
-
-
-
-
-
-
-
- {{ row.color }}
-
- -
-
-
-
-
- {{ row.icon }}
- -
-
-
-
-
-
- {{ formatDate(row.create_time) }}
-
-
-
-
-
-
- 编辑
-
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/platform/src/views/system/dict/components/DictTypeEdit.vue b/platform/src/views/system/dict/components/DictTypeEdit.vue
index 23a2f97..18dbf5b 100644
--- a/platform/src/views/system/dict/components/DictTypeEdit.vue
+++ b/platform/src/views/system/dict/components/DictTypeEdit.vue
@@ -1,241 +1,145 @@
-
-
-
-
- 只能包含字母、数字或下划线,且必须唯一
-
-
-
-
-
-
- 启用
- 禁用
-
-
-
-
- 是
- 否
-
- 是:所有租户可见;否:仅租户内部可见
-
-
-
-
-
-
-
-
-
- 取消
-
- 保存
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+}
+
diff --git a/platform/src/views/system/dict/components/DictTypeList.vue b/platform/src/views/system/dict/components/DictTypeList.vue
deleted file mode 100644
index 60ad101..0000000
--- a/platform/src/views/system/dict/components/DictTypeList.vue
+++ /dev/null
@@ -1,316 +0,0 @@
-
-
-
-
-
-
-
- 重试
-
-
-
-
-
-
-
-
-
- {{ row.dict_code }}
-
-
-
-
- {{ row.tid || 0 }}
-
-
-
-
-
-
- {{ row.status === 1 ? '启用' : '禁用' }}
-
-
-
-
-
-
-
- {{ formatDate(row.create_time) }}
-
-
-
-
-
-
- 查看字典项
-
-
-
- 编辑
-
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/platform/src/views/system/dict/index.vue b/platform/src/views/system/dict/index.vue
index 60273da..279fb8b 100644
--- a/platform/src/views/system/dict/index.vue
+++ b/platform/src/views/system/dict/index.vue
@@ -1,368 +1,517 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ row.dict_code }}
-
-
-
-
-
-
- {{ row.status === 1 ? '启用' : '禁用' }}
-
-
-
-
-
-
- {{ row.is_global === 1 ? '是' : '否' }}
-
-
-
-
-
-
-
- {{ formatDate(row.create_time) }}
-
-
-
-
-
-
- 编辑
-
-
-
- 查看字典项
-
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.item_value }}
+
+
+
+
+ {{ row.tag_type }}
+ -
+
+
+
+
+
+ 默认
+ -
+
+
+
+
+
+ {{ row.status === 1 ? '启用' : '停用' }}
+
+
+
+
+
+
+
+ 编辑
+
+
+ {{ row.status === 1 ? '停用' : '启用' }}
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/platform/src/views/system/payment/channels/alipay.vue b/platform/src/views/system/payment/channels/alipay.vue
index 28b1988..45d9ab4 100644
--- a/platform/src/views/system/payment/channels/alipay.vue
+++ b/platform/src/views/system/payment/channels/alipay.vue
@@ -5,7 +5,7 @@
支付宝配置
平台使用费收款渠道,支持电脑网站支付与手机网站支付(RSA2 签名)。
-
+
diff --git a/platform/src/views/system/payment/channels/cloudpay.vue b/platform/src/views/system/payment/channels/cloudpay.vue
index 7d6c035..54632cb 100644
--- a/platform/src/views/system/payment/channels/cloudpay.vue
+++ b/platform/src/views/system/payment/channels/cloudpay.vue
@@ -5,7 +5,7 @@
云闪付配置
云闪付走银联通道收款,复用银联商户参数,本页只控制收银台展示与开关。