mirror of
https://gitcode.com/flipped-aurora/gin-vue-admin.git
synced 2025-12-30 03:42:26 +00:00
feat: 增加字典的json导出和导入
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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获取操作记录"},
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
:icon="Search"
|
||||
@click="showSearchInputHandler"
|
||||
></el-button>
|
||||
<el-button type="success" @click="openImportDialog" :icon="Upload">
|
||||
</el-button>
|
||||
<el-button type="primary" @click="openDrawer" :icon="Plus">
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -61,7 +63,14 @@
|
||||
<span class="mr-auto text-sm">({{ dictionary.type }})</span>
|
||||
</div>
|
||||
|
||||
<div class="min-w-[40px]">
|
||||
<div class="min-w-[60px] flex items-center gap-2">
|
||||
<el-icon
|
||||
class="text-green-500"
|
||||
@click.stop="exportDictionary(dictionary)"
|
||||
title="导出字典"
|
||||
>
|
||||
<Download />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="text-blue-500"
|
||||
@click.stop="updateSysDictionaryFunc(dictionary)"
|
||||
@@ -160,6 +169,76 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 导入字典对话框 -->
|
||||
<el-dialog
|
||||
v-model="importDialogVisible"
|
||||
title="导入字典JSON"
|
||||
width="70%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="import-dialog-content">
|
||||
<div class="mb-4">
|
||||
<el-alert
|
||||
title="请粘贴或编辑字典JSON数据,支持包含字典详情的完整数据结构"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
<div class="json-editor-container">
|
||||
<el-input
|
||||
v-model="importJsonText"
|
||||
type="textarea"
|
||||
:rows="20"
|
||||
placeholder='请输入JSON数据,例如:
|
||||
{
|
||||
"name": "性别",
|
||||
"type": "gender",
|
||||
"status": true,
|
||||
"desc": "性别字典",
|
||||
"details": [
|
||||
{
|
||||
"label": "男",
|
||||
"value": "1",
|
||||
"status": true,
|
||||
"sort": 1
|
||||
},
|
||||
{
|
||||
"label": "女",
|
||||
"value": "2",
|
||||
"status": true,
|
||||
"sort": 2
|
||||
}
|
||||
]
|
||||
}'
|
||||
class="json-textarea"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-4" v-if="jsonPreviewError">
|
||||
<el-alert
|
||||
:title="jsonPreviewError"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-4" v-if="jsonPreview && !jsonPreviewError">
|
||||
<el-divider content-position="left">JSON预览</el-divider>
|
||||
<div class="json-preview">
|
||||
<pre>{{ jsonPreviewFormatted }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeImportDialog">取 消</el-button>
|
||||
<el-button type="primary" @click="handleImport" :loading="importing">
|
||||
确认导入
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.dict-box {
|
||||
height: calc(100vh - 240px);
|
||||
}
|
||||
@@ -369,4 +546,44 @@
|
||||
background-color: var(--el-color-primary) !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.import-dialog-content {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.json-editor-container {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.json-textarea :deep(.el-textarea__inner) {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.json-preview {
|
||||
background-color: #f5f7fa;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.json-preview pre {
|
||||
margin: 0;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.dark .json-preview {
|
||||
background-color: #1d1e1f;
|
||||
border-color: #414243;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user