|
|
@@ -0,0 +1,897 @@
|
|
|
+<template>
|
|
|
+ <div class="dictionary-management">
|
|
|
+ <div class="page-header">
|
|
|
+ <h2>字典管理</h2>
|
|
|
+ <p>管理系统字典类型和字典项</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主容器:树 + 列表 -->
|
|
|
+ <div class="content-container">
|
|
|
+ <!-- 左侧:字典类型树 -->
|
|
|
+ <div class="tree-panel">
|
|
|
+ <div class="tree-header">
|
|
|
+ <h3>字典类型</h3>
|
|
|
+ <div class="tree-actions">
|
|
|
+ <el-button type="primary" size="small" @click="openCreateCategoryDialog">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 新增
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="warning"
|
|
|
+ size="small"
|
|
|
+ @click="openEditCategoryDialog"
|
|
|
+ :disabled="!selectedCategory"
|
|
|
+ >
|
|
|
+ <el-icon><Edit /></el-icon>
|
|
|
+ 修改
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ size="small"
|
|
|
+ @click="deleteCategoryConfirm"
|
|
|
+ :disabled="!selectedCategory"
|
|
|
+ >
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-tree
|
|
|
+ ref="treeRef"
|
|
|
+ :data="categoryTree"
|
|
|
+ node-key="category_id"
|
|
|
+ :props="{ children: 'children', label: 'category_name' }"
|
|
|
+ default-expand-all
|
|
|
+ highlight-current
|
|
|
+ @node-click="handleTreeNodeClick"
|
|
|
+ v-loading="treeLoading"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧:字典项列表 -->
|
|
|
+ <div class="list-panel">
|
|
|
+ <div class="list-header">
|
|
|
+ <div v-if="selectedCategory">
|
|
|
+ <h3>{{ selectedCategory.category_name }} 中的字典项</h3>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <h3>请在左侧选择字典类型</h3>
|
|
|
+ </div>
|
|
|
+ <div class="list-actions" v-if="selectedCategory">
|
|
|
+ <el-input
|
|
|
+ v-model="searchKeyword"
|
|
|
+ placeholder="搜索字典项"
|
|
|
+ style="width: 200px; margin-right: 10px"
|
|
|
+ clearable
|
|
|
+ @clear="loadItems"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ <el-select
|
|
|
+ v-model="searchEnableFlag"
|
|
|
+ placeholder="状态"
|
|
|
+ style="width: 120px; margin-right: 10px"
|
|
|
+ clearable
|
|
|
+ @change="loadItems"
|
|
|
+ >
|
|
|
+ <el-option label="全部" value="" />
|
|
|
+ <el-option label="启用" value="1" />
|
|
|
+ <el-option label="禁用" value="0" />
|
|
|
+ </el-select>
|
|
|
+ <el-button type="primary" @click="loadItems">
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
+ 查询
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" @click="openCreateItemDialog">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 新增
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ @click="batchDeleteItems"
|
|
|
+ :disabled="selectedItems.length === 0"
|
|
|
+ >
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ 批量删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 字典项列表 -->
|
|
|
+ <el-table
|
|
|
+ v-if="selectedCategory"
|
|
|
+ v-loading="loading"
|
|
|
+ :data="items"
|
|
|
+ style="width: 100%"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="55" />
|
|
|
+ <el-table-column prop="dict_name" label="字典名称" min-width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="dict_value" label="字典值" width="120" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="dict_desc" label="字典备注" min-width="150" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tooltip v-if="row.dict_desc && row.dict_desc.length > 100" :content="row.dict_desc" placement="top">
|
|
|
+ <span>{{ row.dict_desc.substring(0, 100) }}...</span>
|
|
|
+ </el-tooltip>
|
|
|
+ <span v-else>{{ row.dict_desc }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="sort" label="排序" width="80" align="center" />
|
|
|
+ <el-table-column prop="enable_flag" label="状态" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-switch
|
|
|
+ v-model="row.enable_flag"
|
|
|
+ active-value="1"
|
|
|
+ inactive-value="0"
|
|
|
+ @change="handleStatusChange(row)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="created_by_name" label="创建人" width="100" />
|
|
|
+ <el-table-column prop="created_time" label="创建时间" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatDate(row.created_time) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="updated_by_name" label="修改人" width="100" />
|
|
|
+ <el-table-column prop="updated_time" label="修改时间" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatDate(row.updated_time) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button type="warning" size="small" @click="editItem(row)">
|
|
|
+ 修改
|
|
|
+ </el-button>
|
|
|
+ <el-button type="danger" size="small" @click="deleteItem(row)">
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div v-else class="empty-state">
|
|
|
+ <el-empty description="请在左侧选择一个字典类型" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div v-if="selectedCategory" class="pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="currentPage"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ :total="total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 创建/编辑字典类型对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showCategoryDialog"
|
|
|
+ :title="editingCategory ? '编辑字典类型' : '创建字典类型'"
|
|
|
+ width="600px"
|
|
|
+ @close="resetCategoryForm"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ ref="categoryFormRef"
|
|
|
+ :model="categoryForm"
|
|
|
+ :rules="categoryRules"
|
|
|
+ label-width="120px"
|
|
|
+ >
|
|
|
+ <el-form-item label="字典类型ID" prop="category_id" v-if="editingCategory">
|
|
|
+ <el-input v-model="categoryForm.category_id" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字典类型编号" prop="category_no">
|
|
|
+ <el-input v-model="categoryForm.category_no" placeholder="请输入字典类型编号" maxlength="512" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字典类型名称" prop="category_name">
|
|
|
+ <el-input v-model="categoryForm.category_name" placeholder="请输入字典类型名称" maxlength="512" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字典类型备注" prop="category_desc">
|
|
|
+ <el-input
|
|
|
+ v-model="categoryForm.category_desc"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入字典类型备注"
|
|
|
+ maxlength="512"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="父节点" prop="parent_field">
|
|
|
+ <el-tree-select
|
|
|
+ v-model="categoryForm.parent_field"
|
|
|
+ :data="categoryTreeForSelect"
|
|
|
+ node-key="category_id"
|
|
|
+ :props="{ children: 'children', label: 'category_name' }"
|
|
|
+ placeholder="请选择父节点(默认为根节点)"
|
|
|
+ check-strictly
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字典层级" prop="category_level">
|
|
|
+ <el-input v-model="categoryForm.category_level" placeholder="请输入字典层级" maxlength="1" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="showCategoryDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitCategoryForm" :loading="submitting">
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 创建/编辑字典项对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showItemDialog"
|
|
|
+ :title="editingItem ? '编辑字典项' : '创建字典项'"
|
|
|
+ width="600px"
|
|
|
+ @close="resetItemForm"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ ref="itemFormRef"
|
|
|
+ :model="itemForm"
|
|
|
+ :rules="itemRules"
|
|
|
+ label-width="120px"
|
|
|
+ >
|
|
|
+ <el-form-item label="字典名称" prop="dict_name">
|
|
|
+ <el-input v-model="itemForm.dict_name" placeholder="请输入字典名称" maxlength="512" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字典值" prop="dict_value">
|
|
|
+ <el-input v-model="itemForm.dict_value" placeholder="请输入字典值" maxlength="512" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字典备注" prop="dict_desc">
|
|
|
+ <el-input
|
|
|
+ v-model="itemForm.dict_desc"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入字典备注"
|
|
|
+ maxlength="512"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字典类型" prop="category_id">
|
|
|
+ <el-tree-select
|
|
|
+ v-model="itemForm.category_id"
|
|
|
+ :data="categoryTree"
|
|
|
+ node-key="category_id"
|
|
|
+ :props="{ children: 'children', label: 'category_name' }"
|
|
|
+ placeholder="请选择字典类型"
|
|
|
+ check-strictly
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="排序" prop="sort">
|
|
|
+ <el-input-number v-model="itemForm.sort" :min="0" controls-position="right" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="启用状态" prop="enable_flag">
|
|
|
+ <el-radio-group v-model="itemForm.enable_flag">
|
|
|
+ <el-radio label="1">启用</el-radio>
|
|
|
+ <el-radio label="0">禁用</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="showItemDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitItemForm" :loading="submitting">
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, onMounted, computed } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
|
|
+import { Plus, Edit, Delete, Search } from '@element-plus/icons-vue'
|
|
|
+import { dictApi, type DictCategory, type DictItem, type DictCategoryForm, type DictItemForm } from '@/api/dict'
|
|
|
+
|
|
|
+// 树相关
|
|
|
+const treeRef = ref()
|
|
|
+const treeLoading = ref(false)
|
|
|
+const categoryTree = ref<DictCategory[]>([])
|
|
|
+const selectedCategory = ref<DictCategory | null>(null)
|
|
|
+
|
|
|
+// 列表相关
|
|
|
+const loading = ref(false)
|
|
|
+const items = ref<DictItem[]>([])
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+const total = ref(0)
|
|
|
+const selectedItems = ref<DictItem[]>([])
|
|
|
+
|
|
|
+// 搜索相关
|
|
|
+const searchKeyword = ref('')
|
|
|
+const searchEnableFlag = ref('')
|
|
|
+
|
|
|
+// 字典类型对话框
|
|
|
+const showCategoryDialog = ref(false)
|
|
|
+const editingCategory = ref(false)
|
|
|
+const categoryFormRef = ref<FormInstance>()
|
|
|
+const categoryForm = reactive<DictCategoryForm & { category_id?: string }>({
|
|
|
+ category_id: '',
|
|
|
+ category_no: '',
|
|
|
+ category_name: '',
|
|
|
+ category_desc: '',
|
|
|
+ parent_field: '0',
|
|
|
+ category_level: ''
|
|
|
+})
|
|
|
+
|
|
|
+const categoryRules: FormRules = {
|
|
|
+ category_name: [
|
|
|
+ { required: true, message: '请输入字典类型名称', trigger: 'blur' },
|
|
|
+ { max: 512, message: '字典类型名称不能超过512个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ category_no: [
|
|
|
+ { max: 512, message: '字典类型编号不能超过512个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ category_desc: [
|
|
|
+ { max: 512, message: '字典类型备注不能超过512个字符', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+// 字典项对话框
|
|
|
+const showItemDialog = ref(false)
|
|
|
+const editingItem = ref(false)
|
|
|
+const itemFormRef = ref<FormInstance>()
|
|
|
+const itemForm = reactive<DictItemForm & { dict_id?: number }>({
|
|
|
+ dict_id: undefined,
|
|
|
+ dict_name: '',
|
|
|
+ dict_value: '',
|
|
|
+ dict_desc: '',
|
|
|
+ category_id: '',
|
|
|
+ enable_flag: '1',
|
|
|
+ sort: 0
|
|
|
+})
|
|
|
+
|
|
|
+const itemRules: FormRules = {
|
|
|
+ dict_name: [
|
|
|
+ { required: true, message: '请输入字典名称', trigger: 'blur' },
|
|
|
+ { max: 512, message: '字典名称不能超过512个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ dict_value: [
|
|
|
+ { required: true, message: '请输入字典值', trigger: 'blur' },
|
|
|
+ { max: 512, message: '字典值不能超过512个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ dict_desc: [
|
|
|
+ { max: 512, message: '字典备注不能超过512个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ category_id: [
|
|
|
+ { required: true, message: '请选择字典类型', trigger: 'change' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+const submitting = ref(false)
|
|
|
+
|
|
|
+// 计算属性:用于选择父节点的树(排除当前编辑的节点)
|
|
|
+const categoryTreeForSelect = computed(() => {
|
|
|
+ if (!editingCategory.value) {
|
|
|
+ return [{ category_id: '0', category_name: '根节点', children: categoryTree.value }]
|
|
|
+ }
|
|
|
+ // 编辑时需要排除自己和子节点
|
|
|
+ const filterNode = (nodes: DictCategory[], excludeId: string): DictCategory[] => {
|
|
|
+ return nodes.filter(node => node.category_id !== excludeId).map(node => ({
|
|
|
+ ...node,
|
|
|
+ children: node.children ? filterNode(node.children, excludeId) : []
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ return [{
|
|
|
+ category_id: '0',
|
|
|
+ category_name: '根节点',
|
|
|
+ children: filterNode(categoryTree.value, categoryForm.category_id || '')
|
|
|
+ }]
|
|
|
+})
|
|
|
+
|
|
|
+// 加载字典类型树
|
|
|
+const loadCategoryTree = async () => {
|
|
|
+ treeLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await dictApi.getCategoryTree()
|
|
|
+ if (res.code === '000000') {
|
|
|
+ categoryTree.value = res.data || []
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '加载字典类型树失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载字典类型树失败:', error)
|
|
|
+ ElMessage.error('加载字典类型树失败')
|
|
|
+ } finally {
|
|
|
+ treeLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 树节点点击
|
|
|
+const handleTreeNodeClick = (data: DictCategory) => {
|
|
|
+ selectedCategory.value = data
|
|
|
+ currentPage.value = 1
|
|
|
+ searchKeyword.value = ''
|
|
|
+ searchEnableFlag.value = ''
|
|
|
+ loadItems()
|
|
|
+}
|
|
|
+
|
|
|
+// 加载字典项列表
|
|
|
+const loadItems = async () => {
|
|
|
+ if (!selectedCategory.value) return
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await dictApi.getItemList({
|
|
|
+ category_id: selectedCategory.value.category_id,
|
|
|
+ keyword: searchKeyword.value || undefined,
|
|
|
+ enable_flag: searchEnableFlag.value || undefined,
|
|
|
+ page: currentPage.value,
|
|
|
+ page_size: pageSize.value
|
|
|
+ })
|
|
|
+ if (res.code === '000000') {
|
|
|
+ items.value = res.data.list || []
|
|
|
+ total.value = res.data.total || 0
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '加载字典项列表失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载字典项列表失败:', error)
|
|
|
+ ElMessage.error('加载字典项列表失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 分页相关
|
|
|
+const handleSizeChange = (val: number) => {
|
|
|
+ pageSize.value = val
|
|
|
+ currentPage.value = 1
|
|
|
+ loadItems()
|
|
|
+}
|
|
|
+
|
|
|
+const handleCurrentChange = (val: number) => {
|
|
|
+ currentPage.value = val
|
|
|
+ loadItems()
|
|
|
+}
|
|
|
+
|
|
|
+// 选择变化
|
|
|
+const handleSelectionChange = (val: DictItem[]) => {
|
|
|
+ selectedItems.value = val
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 字典类型操作 ====================
|
|
|
+
|
|
|
+// 打开创建字典类型对话框
|
|
|
+const openCreateCategoryDialog = () => {
|
|
|
+ editingCategory.value = false
|
|
|
+ resetCategoryForm()
|
|
|
+ showCategoryDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 打开编辑字典类型对话框
|
|
|
+const openEditCategoryDialog = () => {
|
|
|
+ if (!selectedCategory.value) return
|
|
|
+
|
|
|
+ editingCategory.value = true
|
|
|
+ Object.assign(categoryForm, {
|
|
|
+ category_id: selectedCategory.value.category_id,
|
|
|
+ category_no: selectedCategory.value.category_no || '',
|
|
|
+ category_name: selectedCategory.value.category_name,
|
|
|
+ category_desc: selectedCategory.value.category_desc || '',
|
|
|
+ parent_field: selectedCategory.value.parent_field || '0',
|
|
|
+ category_level: selectedCategory.value.category_level || ''
|
|
|
+ })
|
|
|
+ showCategoryDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 重置字典类型表单
|
|
|
+const resetCategoryForm = () => {
|
|
|
+ categoryFormRef.value?.resetFields()
|
|
|
+ Object.assign(categoryForm, {
|
|
|
+ category_id: '',
|
|
|
+ category_no: '',
|
|
|
+ category_name: '',
|
|
|
+ category_desc: '',
|
|
|
+ parent_field: '0',
|
|
|
+ category_level: ''
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 提交字典类型表单
|
|
|
+const submitCategoryForm = async () => {
|
|
|
+ if (!categoryFormRef.value) return
|
|
|
+
|
|
|
+ await categoryFormRef.value.validate(async (valid) => {
|
|
|
+ if (!valid) return
|
|
|
+
|
|
|
+ submitting.value = true
|
|
|
+ try {
|
|
|
+ const data: DictCategoryForm = {
|
|
|
+ category_no: categoryForm.category_no || undefined,
|
|
|
+ category_name: categoryForm.category_name,
|
|
|
+ category_desc: categoryForm.category_desc || undefined,
|
|
|
+ parent_field: categoryForm.parent_field || '0',
|
|
|
+ category_level: categoryForm.category_level || undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ if (editingCategory.value && categoryForm.category_id) {
|
|
|
+ // 更新
|
|
|
+ const res = await dictApi.updateCategory(categoryForm.category_id, data)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success('字典类型更新成功')
|
|
|
+ showCategoryDialog.value = false
|
|
|
+ await loadCategoryTree()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '字典类型更新失败')
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 创建
|
|
|
+ const res = await dictApi.createCategory(data)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success('字典类型创建成功')
|
|
|
+ showCategoryDialog.value = false
|
|
|
+ await loadCategoryTree()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '字典类型创建失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('提交字典类型失败:', error)
|
|
|
+ ElMessage.error('操作失败')
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 删除字典类型确认
|
|
|
+const deleteCategoryConfirm = () => {
|
|
|
+ if (!selectedCategory.value) return
|
|
|
+
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确定要删除字典类型"${selectedCategory.value.category_name}"吗?`,
|
|
|
+ '删除确认',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ ).then(async () => {
|
|
|
+ await deleteCategory()
|
|
|
+ }).catch(() => {
|
|
|
+ // 取消删除
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 删除字典类型
|
|
|
+const deleteCategory = async () => {
|
|
|
+ if (!selectedCategory.value) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await dictApi.deleteCategory(selectedCategory.value.category_id)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success('字典类型删除成功')
|
|
|
+ selectedCategory.value = null
|
|
|
+ items.value = []
|
|
|
+ await loadCategoryTree()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '字典类型删除失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除字典类型失败:', error)
|
|
|
+ ElMessage.error('删除失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 字典项操作 ====================
|
|
|
+
|
|
|
+// 打开创建字典项对话框
|
|
|
+const openCreateItemDialog = () => {
|
|
|
+ if (!selectedCategory.value) {
|
|
|
+ ElMessage.warning('请先选择字典类型')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ editingItem.value = false
|
|
|
+ resetItemForm()
|
|
|
+ itemForm.category_id = selectedCategory.value.category_id
|
|
|
+ showItemDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 编辑字典项
|
|
|
+const editItem = (row: DictItem) => {
|
|
|
+ editingItem.value = true
|
|
|
+ Object.assign(itemForm, {
|
|
|
+ dict_id: row.dict_id,
|
|
|
+ dict_name: row.dict_name,
|
|
|
+ dict_value: row.dict_value,
|
|
|
+ dict_desc: row.dict_desc || '',
|
|
|
+ category_id: row.category_id,
|
|
|
+ enable_flag: row.enable_flag,
|
|
|
+ sort: row.sort || 0
|
|
|
+ })
|
|
|
+ showItemDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 重置字典项表单
|
|
|
+const resetItemForm = () => {
|
|
|
+ itemFormRef.value?.resetFields()
|
|
|
+ Object.assign(itemForm, {
|
|
|
+ dict_id: undefined,
|
|
|
+ dict_name: '',
|
|
|
+ dict_value: '',
|
|
|
+ dict_desc: '',
|
|
|
+ category_id: '',
|
|
|
+ enable_flag: '1',
|
|
|
+ sort: 0
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 提交字典项表单
|
|
|
+const submitItemForm = async () => {
|
|
|
+ if (!itemFormRef.value) return
|
|
|
+
|
|
|
+ await itemFormRef.value.validate(async (valid) => {
|
|
|
+ if (!valid) return
|
|
|
+
|
|
|
+ submitting.value = true
|
|
|
+ try {
|
|
|
+ const data: DictItemForm = {
|
|
|
+ dict_name: itemForm.dict_name,
|
|
|
+ dict_value: itemForm.dict_value,
|
|
|
+ dict_desc: itemForm.dict_desc || undefined,
|
|
|
+ category_id: itemForm.category_id,
|
|
|
+ enable_flag: itemForm.enable_flag,
|
|
|
+ sort: itemForm.sort
|
|
|
+ }
|
|
|
+
|
|
|
+ if (editingItem.value && itemForm.dict_id) {
|
|
|
+ // 更新
|
|
|
+ const res = await dictApi.updateItem(itemForm.dict_id, data)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success('字典项更新成功')
|
|
|
+ showItemDialog.value = false
|
|
|
+ await loadItems()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '字典项更新失败')
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 创建
|
|
|
+ const res = await dictApi.createItem(data)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success('字典项创建成功')
|
|
|
+ showItemDialog.value = false
|
|
|
+ await loadItems()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '字典项创建失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('提交字典项失败:', error)
|
|
|
+ ElMessage.error('操作失败')
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 删除字典项
|
|
|
+const deleteItem = (row: DictItem) => {
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确定要删除字典项"${row.dict_name}"吗?`,
|
|
|
+ '删除确认',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ ).then(async () => {
|
|
|
+ try {
|
|
|
+ const res = await dictApi.deleteItem(row.dict_id)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success('字典项删除成功')
|
|
|
+ await loadItems()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '字典项删除失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除字典项失败:', error)
|
|
|
+ ElMessage.error('删除失败')
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ // 取消删除
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 批量删除字典项
|
|
|
+const batchDeleteItems = () => {
|
|
|
+ if (selectedItems.value.length === 0) {
|
|
|
+ ElMessage.warning('请先选择要删除的字典项')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确定要删除选中的 ${selectedItems.value.length} 个字典项吗?`,
|
|
|
+ '批量删除确认',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ ).then(async () => {
|
|
|
+ try {
|
|
|
+ const dictIds = selectedItems.value.map(item => item.dict_id)
|
|
|
+ const res = await dictApi.batchDeleteItems(dictIds)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success(res.message || '批量删除成功')
|
|
|
+ selectedItems.value = []
|
|
|
+ await loadItems()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '批量删除失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('批量删除字典项失败:', error)
|
|
|
+ ElMessage.error('批量删除失败')
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ // 取消删除
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 切换字典项状态
|
|
|
+const handleStatusChange = async (row: DictItem) => {
|
|
|
+ try {
|
|
|
+ const res = await dictApi.toggleItemStatus(row.dict_id, row.enable_flag)
|
|
|
+ if (res.code === '000000') {
|
|
|
+ ElMessage.success(res.message || '状态更新成功')
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '状态更新失败')
|
|
|
+ // 恢复原状态
|
|
|
+ row.enable_flag = row.enable_flag === '1' ? '0' : '1'
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('切换字典项状态失败:', error)
|
|
|
+ ElMessage.error('状态更新失败')
|
|
|
+ // 恢复原状态
|
|
|
+ row.enable_flag = row.enable_flag === '1' ? '0' : '1'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化日期
|
|
|
+const formatDate = (dateStr: string | undefined) => {
|
|
|
+ if (!dateStr) return '-'
|
|
|
+ const date = new Date(dateStr)
|
|
|
+ return date.toLocaleString('zh-CN', {
|
|
|
+ year: 'numeric',
|
|
|
+ month: '2-digit',
|
|
|
+ day: '2-digit',
|
|
|
+ hour: '2-digit',
|
|
|
+ minute: '2-digit',
|
|
|
+ second: '2-digit'
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化
|
|
|
+onMounted(() => {
|
|
|
+ loadCategoryTree()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.dictionary-management {
|
|
|
+ padding: 20px;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.page-header {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-header h2 {
|
|
|
+ margin: 0 0 8px 0;
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.page-header p {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.content-container {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-panel {
|
|
|
+ width: 300px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-header {
|
|
|
+ padding: 16px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-header h3 {
|
|
|
+ margin: 0 0 12px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-actions .el-button {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.el-tree {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.list-panel {
|
|
|
+ flex: 1;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.list-header {
|
|
|
+ padding: 16px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.list-header h3 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.list-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.el-table {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-state {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination {
|
|
|
+ padding: 16px;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+</style>
|