diff --git a/server/api/v1/system/sys_dictionary.go b/server/api/v1/system/sys_dictionary.go index 5e6ca79c3..65833dee4 100644 --- a/server/api/v1/system/sys_dictionary.go +++ b/server/api/v1/system/sys_dictionary.go @@ -135,3 +135,57 @@ func (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) { } response.OkWithDetailed(list, "获取成功", c) } + +// ExportSysDictionary +// @Tags SysDictionary +// @Summary 导出字典JSON(包含字典详情) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query system.SysDictionary true "字典ID" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "导出字典JSON" +// @Router /sysDictionary/exportSysDictionary [get] +func (s *DictionaryApi) ExportSysDictionary(c *gin.Context) { + var dictionary system.SysDictionary + err := c.ShouldBindQuery(&dictionary) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if dictionary.ID == 0 { + response.FailWithMessage("字典ID不能为空", c) + return + } + exportData, err := dictionaryService.ExportSysDictionary(dictionary.ID) + if err != nil { + global.GVA_LOG.Error("导出失败!", zap.Error(err)) + response.FailWithMessage("导出失败", c) + return + } + response.OkWithDetailed(exportData, "导出成功", c) +} + +// ImportSysDictionary +// @Tags SysDictionary +// @Summary 导入字典JSON(包含字典详情) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body map[string]interface{} true "字典JSON数据" +// @Success 200 {object} response.Response{msg=string} "导入字典" +// @Router /sysDictionary/importSysDictionary [post] +func (s *DictionaryApi) ImportSysDictionary(c *gin.Context) { + var importData map[string]interface{} + err := c.ShouldBindJSON(&importData) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = dictionaryService.ImportSysDictionary(importData) + if err != nil { + global.GVA_LOG.Error("导入失败!", zap.Error(err)) + response.FailWithMessage("导入失败: "+err.Error(), c) + return + } + response.OkWithMessage("导入成功", c) +} diff --git a/server/router/system/sys_dictionary.go b/server/router/system/sys_dictionary.go index 41ce85ec9..c95e8ffdd 100644 --- a/server/router/system/sys_dictionary.go +++ b/server/router/system/sys_dictionary.go @@ -14,6 +14,8 @@ func (s *DictionaryRouter) InitSysDictionaryRouter(Router *gin.RouterGroup) { sysDictionaryRouter.POST("createSysDictionary", dictionaryApi.CreateSysDictionary) // 新建SysDictionary sysDictionaryRouter.DELETE("deleteSysDictionary", dictionaryApi.DeleteSysDictionary) // 删除SysDictionary sysDictionaryRouter.PUT("updateSysDictionary", dictionaryApi.UpdateSysDictionary) // 更新SysDictionary + sysDictionaryRouter.POST("importSysDictionary", dictionaryApi.ImportSysDictionary) // 导入SysDictionary + sysDictionaryRouter.GET("exportSysDictionary", dictionaryApi.ExportSysDictionary) // 导出SysDictionary } { sysDictionaryRouterWithoutRecord.GET("findSysDictionary", dictionaryApi.FindSysDictionary) // 根据ID获取SysDictionary diff --git a/server/service/system/sys_dictionary.go b/server/service/system/sys_dictionary.go index 10b2d264f..90750d847 100644 --- a/server/service/system/sys_dictionary.go +++ b/server/service/system/sys_dictionary.go @@ -152,3 +152,166 @@ func (dictionaryService *DictionaryService) checkCircularReference(currentID uin return nil } + +//@author: [yourname] +//@function: ExportSysDictionary +//@description: 导出字典JSON(包含字典详情) +//@param: id uint +//@return: exportData map[string]interface{}, err error + +func (dictionaryService *DictionaryService) ExportSysDictionary(id uint) (exportData map[string]interface{}, err error) { + var dictionary system.SysDictionary + // 查询字典及其所有详情 + err = global.GVA_DB.Where("id = ?", id).Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB { + return db.Order("sort") + }).First(&dictionary).Error + if err != nil { + return nil, err + } + + // 构造导出数据 + exportData = map[string]interface{}{ + "name": dictionary.Name, + "type": dictionary.Type, + "status": dictionary.Status, + "desc": dictionary.Desc, + "details": dictionary.SysDictionaryDetails, + } + + return exportData, nil +} + +//@author: [yourname] +//@function: ImportSysDictionary +//@description: 导入字典JSON(包含字典详情) +//@param: importData map[string]interface{} +//@return: err error + +func (dictionaryService *DictionaryService) ImportSysDictionary(importData map[string]interface{}) error { + // 解析基本字典信息 + name, ok := importData["name"].(string) + if !ok || name == "" { + return errors.New("字典名称不能为空") + } + + dictType, ok := importData["type"].(string) + if !ok || dictType == "" { + return errors.New("字典类型不能为空") + } + + // 检查字典类型是否已存在 + if !errors.Is(global.GVA_DB.First(&system.SysDictionary{}, "type = ?", dictType).Error, gorm.ErrRecordNotFound) { + return errors.New("存在相同的type,不允许导入") + } + + // 创建字典 + dictionary := system.SysDictionary{ + Name: name, + Type: dictType, + } + + // 处理status字段 + if status, ok := importData["status"].(bool); ok { + dictionary.Status = &status + } + + // 处理desc字段 + if desc, ok := importData["desc"].(string); ok { + dictionary.Desc = desc + } + + // 开启事务 + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + // 创建字典 + if err := tx.Create(&dictionary).Error; err != nil { + return err + } + + // 处理字典详情 + if details, ok := importData["details"].([]interface{}); ok && len(details) > 0 { + // 创建一个映射来跟踪旧ID到新ID的对应关系 + idMap := make(map[uint]uint) + + // 第一遍:创建所有详情记录(不设置parent_id) + for _, detail := range details { + detailMap, ok := detail.(map[string]interface{}) + if !ok { + continue + } + + label, _ := detailMap["label"].(string) + value, _ := detailMap["value"].(string) + + if label == "" || value == "" { + continue + } + + detailRecord := system.SysDictionaryDetail{ + Label: label, + Value: value, + SysDictionaryID: int(dictionary.ID), + } + + // 处理extend字段 + if extend, ok := detailMap["extend"].(string); ok { + detailRecord.Extend = extend + } + + // 处理status字段 + if status, ok := detailMap["status"].(bool); ok { + detailRecord.Status = &status + } + + // 处理sort字段 + if sort, ok := detailMap["sort"].(float64); ok { + detailRecord.Sort = int(sort) + } + + // 创建详情记录 + if err := tx.Create(&detailRecord).Error; err != nil { + return err + } + + // 记录ID映射(如果有原始ID) + if oldID, ok := detailMap["ID"].(float64); ok { + idMap[uint(oldID)] = detailRecord.ID + } + } + + // 第二遍:更新parent_id关系 + for i, detail := range details { + detailMap, ok := detail.(map[string]interface{}) + if !ok { + continue + } + + // 如果有parentID,更新它 + if oldParentID, ok := detailMap["parentID"].(float64); ok && oldParentID > 0 { + if newParentID, exists := idMap[uint(oldParentID)]; exists { + // 获取新创建的记录ID(按顺序) + if oldID, ok := detailMap["ID"].(float64); ok { + if newID, exists := idMap[uint(oldID)]; exists { + if err := tx.Model(&system.SysDictionaryDetail{}).Where("id = ?", newID).Update("parent_id", newParentID).Error; err != nil { + return err + } + } + } else { + // 如果没有ID,使用索引来查找 + var allDetails []system.SysDictionaryDetail + if err := tx.Where("sys_dictionary_id = ?", dictionary.ID).Order("id").Find(&allDetails).Error; err != nil { + return err + } + if i < len(allDetails) { + if err := tx.Model(&system.SysDictionaryDetail{}).Where("id = ?", allDetails[i].ID).Update("parent_id", newParentID).Error; err != nil { + return err + } + } + } + } + } + } + } + + return nil + }) +} diff --git a/server/source/system/api.go b/server/source/system/api.go index 9500ee865..863847bd4 100644 --- a/server/source/system/api.go +++ b/server/source/system/api.go @@ -149,6 +149,8 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) { {ApiGroup: "系统字典", Method: "PUT", Path: "/sysDictionary/updateSysDictionary", Description: "更新字典"}, {ApiGroup: "系统字典", Method: "GET", Path: "/sysDictionary/findSysDictionary", Description: "根据ID获取字典(建议选择)"}, {ApiGroup: "系统字典", Method: "GET", Path: "/sysDictionary/getSysDictionaryList", Description: "获取字典列表"}, + {ApiGroup: "系统字典", Method: "POST", Path: "/sysDictionary/importSysDictionary", Description: "导入字典JSON"}, + {ApiGroup: "系统字典", Method: "GET", Path: "/sysDictionary/exportSysDictionary", Description: "导出字典JSON"}, {ApiGroup: "操作记录", Method: "POST", Path: "/sysOperationRecord/createSysOperationRecord", Description: "新增操作记录"}, {ApiGroup: "操作记录", Method: "GET", Path: "/sysOperationRecord/findSysOperationRecord", Description: "根据ID获取操作记录"}, diff --git a/server/source/system/casbin.go b/server/source/system/casbin.go index c63dfa1b7..a7e49d757 100644 --- a/server/source/system/casbin.go +++ b/server/source/system/casbin.go @@ -149,6 +149,8 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error {Ptype: "p", V0: "888", V1: "/sysDictionary/getSysDictionaryList", V2: "GET"}, {Ptype: "p", V0: "888", V1: "/sysDictionary/createSysDictionary", V2: "POST"}, {Ptype: "p", V0: "888", V1: "/sysDictionary/deleteSysDictionary", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/sysDictionary/importSysDictionary", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/sysDictionary/exportSysDictionary", V2: "GET"}, {Ptype: "p", V0: "888", V1: "/sysOperationRecord/findSysOperationRecord", V2: "GET"}, {Ptype: "p", V0: "888", V1: "/sysOperationRecord/updateSysOperationRecord", V2: "PUT"}, diff --git a/web/src/api/sysDictionary.js b/web/src/api/sysDictionary.js index f5d6c8620..90a2583e4 100644 --- a/web/src/api/sysDictionary.js +++ b/web/src/api/sysDictionary.js @@ -78,3 +78,35 @@ export const getSysDictionaryList = (params) => { params }) } + +// @Tags SysDictionary +// @Summary 导出字典JSON(包含字典详情) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query model.SysDictionary true "字典ID" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"导出成功"}" +// @Router /sysDictionary/exportSysDictionary [get] +export const exportSysDictionary = (params) => { + return service({ + url: '/sysDictionary/exportSysDictionary', + method: 'get', + params + }) +} + +// @Tags SysDictionary +// @Summary 导入字典JSON(包含字典详情) +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body object true "字典JSON数据" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"导入成功"}" +// @Router /sysDictionary/importSysDictionary [post] +export const importSysDictionary = (data) => { + return service({ + url: '/sysDictionary/importSysDictionary', + method: 'post', + data + }) +} diff --git a/web/src/view/superAdmin/dictionary/sysDictionary.vue b/web/src/view/superAdmin/dictionary/sysDictionary.vue index 894e8a3c1..672150cba 100644 --- a/web/src/view/superAdmin/dictionary/sysDictionary.vue +++ b/web/src/view/superAdmin/dictionary/sysDictionary.vue @@ -35,6 +35,8 @@ :icon="Search" @click="showSearchInputHandler" > + + @@ -61,7 +63,14 @@ ({{ dictionary.type }}) -
+
+ + + + + + +
+
+ +
+
+ +
+
+ +
+
+ JSON预览 +
+
{{ jsonPreviewFormatted }}
+
+
+
+ +
@@ -169,14 +248,16 @@ deleteSysDictionary, updateSysDictionary, findSysDictionary, - getSysDictionaryList + getSysDictionaryList, + exportSysDictionary, + importSysDictionary } from '@/api/sysDictionary' // 此处请自行替换地址 import WarningBar from '@/components/warningBar/warningBar.vue' - import { ref } from 'vue' + import { ref, computed, watch } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import sysDictionaryDetail from './sysDictionaryDetail.vue' - import { Edit, Plus, Search } from '@element-plus/icons-vue' + import { Edit, Plus, Search, Download, Upload } from '@element-plus/icons-vue' import { useAppStore } from '@/pinia' defineOptions({ @@ -223,6 +304,35 @@ const dictionaryData = ref([]) const availableParentDictionaries = ref([]) + // 导入相关 + const importDialogVisible = ref(false) + const importJsonText = ref('') + const importing = ref(false) + const jsonPreviewError = ref('') + const jsonPreview = ref(null) + + // 监听JSON文本变化,实时预览 + watch(importJsonText, (newVal) => { + if (!newVal.trim()) { + jsonPreview.value = null + jsonPreviewError.value = '' + return + } + try { + jsonPreview.value = JSON.parse(newVal) + jsonPreviewError.value = '' + } catch (e) { + jsonPreviewError.value = 'JSON格式错误: ' + e.message + jsonPreview.value = null + } + }) + + // 格式化JSON预览 + const jsonPreviewFormatted = computed(() => { + if (!jsonPreview.value) return '' + return JSON.stringify(jsonPreview.value, null, 2) + }) + // 查询 const getTableData = async () => { const res = await getSysDictionaryList({ @@ -358,9 +468,76 @@ getTableData() } } + + // 导出字典 + const exportDictionary = async (row) => { + try { + const res = await exportSysDictionary({ ID: row.ID }) + if (res.code === 0) { + // 将JSON数据转换为字符串并下载 + const jsonStr = JSON.stringify(res.data, null, 2) + const blob = new Blob([jsonStr], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = `${row.type}_${row.name}_dictionary.json` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(url) + ElMessage.success('导出成功') + } + } catch (error) { + ElMessage.error('导出失败: ' + error.message) + } + } + + // 打开导入对话框 + const openImportDialog = () => { + importDialogVisible.value = true + importJsonText.value = '' + jsonPreview.value = null + jsonPreviewError.value = '' + } + + // 关闭导入对话框 + const closeImportDialog = () => { + importDialogVisible.value = false + importJsonText.value = '' + jsonPreview.value = null + jsonPreviewError.value = '' + } + + // 处理导入 + const handleImport = async () => { + if (!importJsonText.value.trim()) { + ElMessage.warning('请输入JSON数据') + return + } + + if (jsonPreviewError.value) { + ElMessage.error('JSON格式错误,请检查后重试') + return + } + + try { + importing.value = true + const jsonData = JSON.parse(importJsonText.value) + const res = await importSysDictionary(jsonData) + if (res.code === 0) { + ElMessage.success('导入成功') + closeImportDialog() + getTableData() + } + } catch (error) { + ElMessage.error('导入失败: ' + error.message) + } finally { + importing.value = false + } + } -