Skip to content

Commit c9bb6f5

Browse files
authored
Merge pull request #2109 from bypanghu/dict
fix:字典支持 tree 结构
2 parents 7c268b2 + 6a2e135 commit c9bb6f5

File tree

15 files changed

+1342
-154
lines changed

15 files changed

+1342
-154
lines changed

server/api/v1/system/sys_dictionary_detail.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package system
22

33
import (
4+
"strconv"
5+
46
"github.com/flipped-aurora/gin-vue-admin/server/global"
57
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
68
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
@@ -146,3 +148,120 @@ func (s *DictionaryDetailApi) GetSysDictionaryDetailList(c *gin.Context) {
146148
PageSize: pageInfo.PageSize,
147149
}, "获取成功", c)
148150
}
151+
152+
// GetDictionaryTreeList
153+
// @Tags SysDictionaryDetail
154+
// @Summary 获取字典详情树形结构
155+
// @Security ApiKeyAuth
156+
// @accept application/json
157+
// @Produce application/json
158+
// @Param sysDictionaryID query int true "字典ID"
159+
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
160+
// @Router /sysDictionaryDetail/getDictionaryTreeList [get]
161+
func (s *DictionaryDetailApi) GetDictionaryTreeList(c *gin.Context) {
162+
sysDictionaryID := c.Query("sysDictionaryID")
163+
if sysDictionaryID == "" {
164+
response.FailWithMessage("字典ID不能为空", c)
165+
return
166+
}
167+
168+
var id uint
169+
if idUint64, err := strconv.ParseUint(sysDictionaryID, 10, 32); err != nil {
170+
response.FailWithMessage("字典ID格式错误", c)
171+
return
172+
} else {
173+
id = uint(idUint64)
174+
}
175+
176+
list, err := dictionaryDetailService.GetDictionaryTreeList(id)
177+
if err != nil {
178+
global.GVA_LOG.Error("获取失败!", zap.Error(err))
179+
response.FailWithMessage("获取失败", c)
180+
return
181+
}
182+
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
183+
}
184+
185+
// GetDictionaryTreeListByType
186+
// @Tags SysDictionaryDetail
187+
// @Summary 根据字典类型获取字典详情树形结构
188+
// @Security ApiKeyAuth
189+
// @accept application/json
190+
// @Produce application/json
191+
// @Param type query string true "字典类型"
192+
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
193+
// @Router /sysDictionaryDetail/getDictionaryTreeListByType [get]
194+
func (s *DictionaryDetailApi) GetDictionaryTreeListByType(c *gin.Context) {
195+
dictType := c.Query("type")
196+
if dictType == "" {
197+
response.FailWithMessage("字典类型不能为空", c)
198+
return
199+
}
200+
201+
list, err := dictionaryDetailService.GetDictionaryTreeListByType(dictType)
202+
if err != nil {
203+
global.GVA_LOG.Error("获取失败!", zap.Error(err))
204+
response.FailWithMessage("获取失败", c)
205+
return
206+
}
207+
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
208+
}
209+
210+
// GetDictionaryDetailsByParent
211+
// @Tags SysDictionaryDetail
212+
// @Summary 根据父级ID获取字典详情
213+
// @Security ApiKeyAuth
214+
// @accept application/json
215+
// @Produce application/json
216+
// @Param data query request.GetDictionaryDetailsByParentRequest true "查询参数"
217+
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情列表"
218+
// @Router /sysDictionaryDetail/getDictionaryDetailsByParent [get]
219+
func (s *DictionaryDetailApi) GetDictionaryDetailsByParent(c *gin.Context) {
220+
var req request.GetDictionaryDetailsByParentRequest
221+
err := c.ShouldBindQuery(&req)
222+
if err != nil {
223+
response.FailWithMessage(err.Error(), c)
224+
return
225+
}
226+
227+
list, err := dictionaryDetailService.GetDictionaryDetailsByParent(req)
228+
if err != nil {
229+
global.GVA_LOG.Error("获取失败!", zap.Error(err))
230+
response.FailWithMessage("获取失败", c)
231+
return
232+
}
233+
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
234+
}
235+
236+
// GetDictionaryPath
237+
// @Tags SysDictionaryDetail
238+
// @Summary 获取字典详情的完整路径
239+
// @Security ApiKeyAuth
240+
// @accept application/json
241+
// @Produce application/json
242+
// @Param id query uint true "字典详情ID"
243+
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情路径"
244+
// @Router /sysDictionaryDetail/getDictionaryPath [get]
245+
func (s *DictionaryDetailApi) GetDictionaryPath(c *gin.Context) {
246+
idStr := c.Query("id")
247+
if idStr == "" {
248+
response.FailWithMessage("字典详情ID不能为空", c)
249+
return
250+
}
251+
252+
var id uint
253+
if idUint64, err := strconv.ParseUint(idStr, 10, 32); err != nil {
254+
response.FailWithMessage("字典详情ID格式错误", c)
255+
return
256+
} else {
257+
id = uint(idUint64)
258+
}
259+
260+
path, err := dictionaryDetailService.GetDictionaryPath(id)
261+
if err != nil {
262+
global.GVA_LOG.Error("获取失败!", zap.Error(err))
263+
response.FailWithMessage("获取失败", c)
264+
return
265+
}
266+
response.OkWithDetailed(gin.H{"path": path}, "获取成功", c)
267+
}

server/middleware/casbin_rbac.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package middleware
22

33
import (
4-
"strconv"
5-
"strings"
6-
74
"github.com/flipped-aurora/gin-vue-admin/server/global"
85
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
96
"github.com/flipped-aurora/gin-vue-admin/server/utils"
107
"github.com/gin-gonic/gin"
8+
"strconv"
9+
"strings"
1110
)
1211

1312
// CasbinHandler 拦截器

server/model/system/request/sys_dictionary_detail.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,36 @@ import (
88
type SysDictionaryDetailSearch struct {
99
system.SysDictionaryDetail
1010
request.PageInfo
11+
ParentID *uint `json:"parentID" form:"parentID"` // 父级字典详情ID,用于查询指定父级下的子项
12+
Level *int `json:"level" form:"level"` // 层级深度,用于查询指定层级的数据
13+
}
14+
15+
// CreateSysDictionaryDetailRequest 创建字典详情请求
16+
type CreateSysDictionaryDetailRequest struct {
17+
Label string `json:"label" form:"label" binding:"required"` // 展示值
18+
Value string `json:"value" form:"value" binding:"required"` // 字典值
19+
Extend string `json:"extend" form:"extend"` // 扩展值
20+
Status *bool `json:"status" form:"status"` // 启用状态
21+
Sort int `json:"sort" form:"sort"` // 排序标记
22+
SysDictionaryID int `json:"sysDictionaryID" form:"sysDictionaryID" binding:"required"` // 关联标记
23+
ParentID *uint `json:"parentID" form:"parentID"` // 父级字典详情ID
24+
}
25+
26+
// UpdateSysDictionaryDetailRequest 更新字典详情请求
27+
type UpdateSysDictionaryDetailRequest struct {
28+
ID uint `json:"ID" form:"ID" binding:"required"` // 主键ID
29+
Label string `json:"label" form:"label" binding:"required"` // 展示值
30+
Value string `json:"value" form:"value" binding:"required"` // 字典值
31+
Extend string `json:"extend" form:"extend"` // 扩展值
32+
Status *bool `json:"status" form:"status"` // 启用状态
33+
Sort int `json:"sort" form:"sort"` // 排序标记
34+
SysDictionaryID int `json:"sysDictionaryID" form:"sysDictionaryID" binding:"required"` // 关联标记
35+
ParentID *uint `json:"parentID" form:"parentID"` // 父级字典详情ID
36+
}
37+
38+
// GetDictionaryDetailsByParentRequest 根据父级ID获取字典详情请求
39+
type GetDictionaryDetailsByParentRequest struct {
40+
SysDictionaryID int `json:"sysDictionaryID" form:"sysDictionaryID" binding:"required"` // 字典ID
41+
ParentID *uint `json:"parentID" form:"parentID"` // 父级字典详情ID,为空时获取顶级
42+
IncludeChildren bool `json:"includeChildren" form:"includeChildren"` // 是否包含子级数据
1143
}

server/model/system/sys_dictionary.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import (
88
// 如果含有time.Time 请自行import time包
99
type SysDictionary struct {
1010
global.GVA_MODEL
11-
Name string `json:"name" form:"name" gorm:"column:name;comment:字典名(中)"` // 字典名(中)
12-
Type string `json:"type" form:"type" gorm:"column:type;comment:字典名(英)"` // 字典名(英)
13-
Status *bool `json:"status" form:"status" gorm:"column:status;comment:状态"` // 状态
14-
Desc string `json:"desc" form:"desc" gorm:"column:desc;comment:描述"` // 描述
11+
Name string `json:"name" form:"name" gorm:"column:name;comment:字典名(中)"` // 字典名(中)
12+
Type string `json:"type" form:"type" gorm:"column:type;comment:字典名(英)"` // 字典名(英)
13+
Status *bool `json:"status" form:"status" gorm:"column:status;comment:状态"` // 状态
14+
Desc string `json:"desc" form:"desc" gorm:"column:desc;comment:描述"` // 描述
15+
ParentID *uint `json:"parentID" form:"parentID" gorm:"column:parent_id;comment:父级字典ID"` // 父级字典ID
16+
Children []SysDictionary `json:"children" gorm:"foreignKey:ParentID"` // 子字典
1517
SysDictionaryDetails []SysDictionaryDetail `json:"sysDictionaryDetails" form:"sysDictionaryDetails"`
1618
}
1719

server/model/system/sys_dictionary_detail.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import (
88
// 如果含有time.Time 请自行import time包
99
type SysDictionaryDetail struct {
1010
global.GVA_MODEL
11-
Label string `json:"label" form:"label" gorm:"column:label;comment:展示值"` // 展示值
12-
Value string `json:"value" form:"value" gorm:"column:value;comment:字典值"` // 字典值
13-
Extend string `json:"extend" form:"extend" gorm:"column:extend;comment:扩展值"` // 扩展值
14-
Status *bool `json:"status" form:"status" gorm:"column:status;comment:启用状态"` // 启用状态
15-
Sort int `json:"sort" form:"sort" gorm:"column:sort;comment:排序标记"` // 排序标记
16-
SysDictionaryID int `json:"sysDictionaryID" form:"sysDictionaryID" gorm:"column:sys_dictionary_id;comment:关联标记"` // 关联标记
11+
Label string `json:"label" form:"label" gorm:"column:label;comment:展示值"` // 展示值
12+
Value string `json:"value" form:"value" gorm:"column:value;comment:字典值"` // 字典值
13+
Extend string `json:"extend" form:"extend" gorm:"column:extend;comment:扩展值"` // 扩展值
14+
Status *bool `json:"status" form:"status" gorm:"column:status;comment:启用状态"` // 启用状态
15+
Sort int `json:"sort" form:"sort" gorm:"column:sort;comment:排序标记"` // 排序标记
16+
SysDictionaryID int `json:"sysDictionaryID" form:"sysDictionaryID" gorm:"column:sys_dictionary_id;comment:关联标记"` // 关联标记
17+
ParentID *uint `json:"parentID" form:"parentID" gorm:"column:parent_id;comment:父级字典详情ID"` // 父级字典详情ID
18+
Children []SysDictionaryDetail `json:"children" gorm:"foreignKey:ParentID"` // 子字典详情
19+
Level int `json:"level" form:"level" gorm:"column:level;comment:层级深度"` // 层级深度,从0开始
20+
Path string `json:"path" form:"path" gorm:"column:path;comment:层级路径"` // 层级路径,如 "1,2,3"
1721
}
1822

1923
func (SysDictionaryDetail) TableName() string {

server/router/system/sys_dictionary_detail.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ func (s *DictionaryDetailRouter) InitSysDictionaryDetailRouter(Router *gin.Route
1616
dictionaryDetailRouter.PUT("updateSysDictionaryDetail", dictionaryDetailApi.UpdateSysDictionaryDetail) // 更新SysDictionaryDetail
1717
}
1818
{
19-
dictionaryDetailRouterWithoutRecord.GET("findSysDictionaryDetail", dictionaryDetailApi.FindSysDictionaryDetail) // 根据ID获取SysDictionaryDetail
20-
dictionaryDetailRouterWithoutRecord.GET("getSysDictionaryDetailList", dictionaryDetailApi.GetSysDictionaryDetailList) // 获取SysDictionaryDetail列表
19+
dictionaryDetailRouterWithoutRecord.GET("findSysDictionaryDetail", dictionaryDetailApi.FindSysDictionaryDetail) // 根据ID获取SysDictionaryDetail
20+
dictionaryDetailRouterWithoutRecord.GET("getSysDictionaryDetailList", dictionaryDetailApi.GetSysDictionaryDetailList) // 获取SysDictionaryDetail列表
21+
dictionaryDetailRouterWithoutRecord.GET("getDictionaryTreeList", dictionaryDetailApi.GetDictionaryTreeList) // 获取字典详情树形结构
22+
dictionaryDetailRouterWithoutRecord.GET("getDictionaryTreeListByType", dictionaryDetailApi.GetDictionaryTreeListByType) // 根据字典类型获取字典详情树形结构
23+
dictionaryDetailRouterWithoutRecord.GET("getDictionaryDetailsByParent", dictionaryDetailApi.GetDictionaryDetailsByParent) // 根据父级ID获取字典详情
24+
dictionaryDetailRouterWithoutRecord.GET("getDictionaryPath", dictionaryDetailApi.GetDictionaryPath) // 获取字典详情的完整路径
2125
}
2226
}

server/service/system/sys_dictionary.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package system
22

33
import (
44
"errors"
5+
56
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
67
"github.com/gin-gonic/gin"
78

@@ -62,10 +63,11 @@ func (dictionaryService *DictionaryService) DeleteSysDictionary(sysDictionary sy
6263
func (dictionaryService *DictionaryService) UpdateSysDictionary(sysDictionary *system.SysDictionary) (err error) {
6364
var dict system.SysDictionary
6465
sysDictionaryMap := map[string]interface{}{
65-
"Name": sysDictionary.Name,
66-
"Type": sysDictionary.Type,
67-
"Status": sysDictionary.Status,
68-
"Desc": sysDictionary.Desc,
66+
"Name": sysDictionary.Name,
67+
"Type": sysDictionary.Type,
68+
"Status": sysDictionary.Status,
69+
"Desc": sysDictionary.Desc,
70+
"ParentID": sysDictionary.ParentID,
6971
}
7072
err = global.GVA_DB.Where("id = ?", sysDictionary.ID).First(&dict).Error
7173
if err != nil {
@@ -77,6 +79,14 @@ func (dictionaryService *DictionaryService) UpdateSysDictionary(sysDictionary *s
7779
return errors.New("存在相同的type,不允许创建")
7880
}
7981
}
82+
83+
// 检查是否会形成循环引用
84+
if sysDictionary.ParentID != nil && *sysDictionary.ParentID != 0 {
85+
if err := dictionaryService.checkCircularReference(sysDictionary.ID, *sysDictionary.ParentID); err != nil {
86+
return err
87+
}
88+
}
89+
8090
err = global.GVA_DB.Model(&dict).Updates(sysDictionaryMap).Error
8191
return err
8292
}
@@ -113,6 +123,32 @@ func (dictionaryService *DictionaryService) GetSysDictionaryInfoList(c *gin.Cont
113123
if req.Name != "" {
114124
query = query.Where("name LIKE ? OR type LIKE ?", "%"+req.Name+"%", "%"+req.Name+"%")
115125
}
126+
// 预加载子字典
127+
query = query.Preload("Children")
116128
err = query.Find(&sysDictionarys).Error
117129
return sysDictionarys, err
118130
}
131+
132+
// checkCircularReference 检查是否会形成循环引用
133+
func (dictionaryService *DictionaryService) checkCircularReference(currentID uint, parentID uint) error {
134+
if currentID == parentID {
135+
return errors.New("不能将字典设置为自己的父级")
136+
}
137+
138+
// 递归检查父级链条
139+
var parent system.SysDictionary
140+
err := global.GVA_DB.Where("id = ?", parentID).First(&parent).Error
141+
if err != nil {
142+
if errors.Is(err, gorm.ErrRecordNotFound) {
143+
return nil // 父级不存在,允许设置
144+
}
145+
return err
146+
}
147+
148+
// 如果父级还有父级,继续检查
149+
if parent.ParentID != nil && *parent.ParentID != 0 {
150+
return dictionaryService.checkCircularReference(currentID, *parent.ParentID)
151+
}
152+
153+
return nil
154+
}

0 commit comments

Comments
 (0)