后台管理增加字典管理功能

This commit is contained in:
2025-12-24 15:58:17 +08:00
parent 31cc78038b
commit 9b9678b0b6
6 changed files with 565 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
import request from '@/utils/request'
import type {
DictionaryPageRequest,
DictionaryCreateRequest,
DictionaryUpdateRequest,
Dictionary
} from '@/types/dictionary'
import type { ApiResponse, PageResult } from '@/types/common'
// 分页查询字典
export function getDictionaryPage(params: DictionaryPageRequest) {
return request<ApiResponse<PageResult<Dictionary>>>({
url: '/dictionary/list',
method: 'get',
params
})
}
// 根据ID获取字典详情
export function getDictionaryById(id: string) {
return request<ApiResponse<Dictionary>>({
url: '/dictionary/detail',
method: 'get',
params: { id }
})
}
// 创建字典
export function createDictionary(data: DictionaryCreateRequest) {
return request<ApiResponse<Dictionary>>({
url: '/dictionary',
method: 'post',
data
})
}
// 更新字典
export function updateDictionary(data: DictionaryUpdateRequest) {
return request<ApiResponse<Dictionary>>({
url: '/dictionary',
method: 'put',
data
})
}
// 删除字典
export function deleteDictionary(id: string) {
return request<ApiResponse<void>>({
url: '/dictionary/delete',
method: 'delete',
params: { id }
})
}
// 根据字典类型查询字典集合
export function getDictionariesByType(dictType: string) {
return request<ApiResponse<Dictionary[]>>({
url: '/dictionary/byType',
method: 'get',
params: { dictType }
})
}
// 根据字典类型查询启用的字典集合
export function getEnabledDictionariesByType(dictType: string) {
return request<ApiResponse<Dictionary[]>>({
url: '/dictionary/enabledByType',
method: 'get',
params: { dictType }
})
}
+5
View File
@@ -27,6 +27,11 @@ export const menuConfig: MenuItem[] = [
title: 'AI配置管理',
icon: 'Setting'
},
{
path: '/dictionary',
title: '字典管理',
icon: 'Document'
},
{
path: '/tools',
title: '开发工具',
+27
View File
@@ -63,6 +63,33 @@ const routes: RouteRecordRaw[] = [
}
]
},
{
path: '/dictionary',
component: Layout,
redirect: '/dictionary/list',
meta: { title: '字典管理', icon: 'Document' },
children: [
{
path: 'list',
name: 'DictionaryList',
component: () => import('@/views/dictionary/DictionaryList.vue'),
meta: { title: '字典列表' }
},
{
path: 'create',
name: 'DictionaryCreate',
component: () => import('@/views/dictionary/DictionaryForm.vue'),
meta: { title: '创建字典', hidden: true }
},
{
path: 'edit/:id',
name: 'DictionaryEdit',
component: () => import('@/views/dictionary/DictionaryForm.vue'),
meta: { title: '编辑字典', hidden: true },
props: true
}
]
},
{
path: '/tools',
component: Layout,
+46
View File
@@ -0,0 +1,46 @@
// 字典相关类型定义
export interface Dictionary {
id: string
dictType: string
dictCode: string
dictName: string
dictValue?: string
sortOrder?: number
status: number
createBy?: string
updateBy?: string
remarks?: string
createTime?: string
updateTime?: string
}
export interface DictionaryPageRequest {
current: number
size: number
dictType?: string
dictCode?: string
dictName?: string
status?: number
}
export interface DictionaryCreateRequest {
dictType: string
dictCode: string
dictName: string
dictValue?: string
sortOrder?: number
status: number
remarks?: string
}
export interface DictionaryUpdateRequest {
id: string
dictType: string
dictCode: string
dictName: string
dictValue?: string
sortOrder?: number
status: number
remarks?: string
}
@@ -0,0 +1,173 @@
<template>
<div class="dictionary-form">
<h2 class="page-title">{{ isEdit ? '编辑字典' : '创建字典' }}</h2>
<el-card>
<el-form
:model="form"
:rules="rules"
ref="formRef"
label-width="120px"
style="max-width: 600px"
>
<el-form-item label="字典类型" prop="dictType">
<el-input
v-model="form.dictType"
:disabled="isEdit"
placeholder="请输入字典类型,如:city, constellation, mbti"
/>
</el-form-item>
<el-form-item label="字典编码" prop="dictCode">
<el-input
v-model="form.dictCode"
:disabled="isEdit"
placeholder="请输入字典编码"
/>
</el-form-item>
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="form.dictName" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item label="字典值" prop="dictValue">
<el-input v-model="form.dictValue" placeholder="请输入字典值" />
</el-form-item>
<el-form-item label="排序" prop="sortOrder">
<el-input-number
v-model="form.sortOrder"
:min="0"
:max="9999"
placeholder="请输入排序值"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注信息"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit" :loading="submitting">提交</el-button>
<el-button @click="handleCancel">取消</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { useRouter, useRoute } from 'vue-router'
import { getDictionaryById, createDictionary, updateDictionary } from '@/api/dictionary'
import type { DictionaryCreateRequest, DictionaryUpdateRequest } from '@/types/dictionary'
const router = useRouter()
const route = useRoute()
const formRef = ref()
const submitting = ref(false)
const form = reactive<DictionaryCreateRequest | DictionaryUpdateRequest>({
dictType: '',
dictCode: '',
dictName: '',
dictValue: '',
sortOrder: 0,
status: 1,
remarks: ''
})
const rules = {
dictType: [
{ required: true, message: '请输入字典类型', trigger: 'blur' },
{ min: 1, max: 50, message: '字典类型长度在1-50个字符之间', trigger: 'blur' }
],
dictCode: [
{ required: true, message: '请输入字典编码', trigger: 'blur' },
{ min: 1, max: 50, message: '字典编码长度在1-50个字符之间', trigger: 'blur' }
],
dictName: [
{ required: true, message: '请输入字典名称', trigger: 'blur' },
{ min: 1, max: 100, message: '字典名称长度在1-100个字符之间', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'change' }
]
}
const isEdit = computed(() => route.params.id !== undefined)
const fetchDetail = async () => {
if (isEdit.value) {
try {
const res = await getDictionaryById(route.params.id as string)
Object.assign(form, res.data)
} catch (error) {
console.error('获取字典详情失败:', error)
}
}
}
const handleSubmit = async () => {
if (!formRef.value) return
const valid = await formRef.value.validate().catch(() => false)
if (!valid) return
submitting.value = true
try {
if (isEdit.value) {
// 编辑模式
await updateDictionary(form as DictionaryUpdateRequest)
ElMessage.success('更新成功')
} else {
// 创建模式
await createDictionary(form as DictionaryCreateRequest)
ElMessage.success('创建成功')
}
router.push('/dictionary/list')
} catch (error) {
console.error(isEdit.value ? '更新失败:' : '创建失败:', error)
} finally {
submitting.value = false
}
}
const handleCancel = () => {
router.go(-1)
}
onMounted(() => {
if (isEdit.value) {
fetchDetail()
}
})
</script>
<style scoped lang="scss">
.dictionary-form {
.page-title {
margin-bottom: 20px;
font-size: 24px;
color: #333;
}
.el-card {
margin-bottom: 20px;
}
}
</style>
@@ -0,0 +1,243 @@
<template>
<div class="dictionary-list">
<h2 class="page-title">字典管理</h2>
<el-card class="search-card">
<el-form :model="searchForm" :inline="true">
<el-form-item label="字典类型">
<el-input v-model="searchForm.dictType" placeholder="请输入字典类型" clearable />
</el-form-item>
<el-form-item label="字典编码">
<el-input v-model="searchForm.dictCode" placeholder="请输入字典编码" clearable />
</el-form-item>
<el-form-item label="字典名称">
<el-input v-model="searchForm.dictName" placeholder="请输入字典名称" clearable />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<el-button type="primary" @click="handleCreate">新增字典</el-button>
</el-card>
<el-card class="table-card">
<template #header>
<span>字典列表</span>
</template>
<el-table :data="tableData" v-loading="loading" stripe>
<el-table-column prop="dictType" label="字典类型" />
<el-table-column prop="dictCode" label="字典编码" />
<el-table-column prop="dictName" label="字典名称" />
<el-table-column prop="dictValue" label="字典值" />
<el-table-column prop="sortOrder" label="排序" width="80" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column prop="updateTime" label="更新时间" width="160" />
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看</el-button>
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button
:type="row.status === 1 ? 'warning' : 'success'"
link
@click="handleToggleStatus(row)"
>
{{ row.status === 1 ? '禁用' : '启用' }}
</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.size"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="fetchData"
@current-change="fetchData"
class="pagination"
/>
</el-card>
<!-- 字典详情对话框 -->
<el-dialog v-model="detailVisible" title="字典详情" width="600px">
<el-descriptions :column="2" border v-if="currentDictionary">
<el-descriptions-item label="字典类型">{{ currentDictionary.dictType }}</el-descriptions-item>
<el-descriptions-item label="字典编码">{{ currentDictionary.dictCode }}</el-descriptions-item>
<el-descriptions-item label="字典名称">{{ currentDictionary.dictName }}</el-descriptions-item>
<el-descriptions-item label="字典值">{{ currentDictionary.dictValue || '-' }}</el-descriptions-item>
<el-descriptions-item label="排序">{{ currentDictionary.sortOrder }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="currentDictionary.status === 1 ? 'success' : 'danger'">
{{ currentDictionary.status === 1 ? '启用' : '禁用' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ currentDictionary.remarks || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ currentDictionary.createTime }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ currentDictionary.updateTime }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getDictionaryPage, deleteDictionary, updateDictionary } from '@/api/dictionary'
import type { Dictionary, DictionaryPageRequest } from '@/types/dictionary'
const loading = ref(false)
const detailVisible = ref(false)
const currentDictionary = ref<Dictionary | null>(null)
const searchForm = reactive<DictionaryPageRequest>({
current: 1,
size: 10
})
const pagination = reactive({
current: 1,
size: 10,
total: 0
})
const tableData = ref<Dictionary[]>([])
const fetchData = async () => {
loading.value = true
try {
const params = {
...searchForm,
current: pagination.current,
size: pagination.size
}
const res = await getDictionaryPage(params)
const pageData = res.data as any
tableData.value = pageData.records
pagination.total = pageData.total
} catch (error) {
console.error('获取字典列表失败:', error)
} finally {
loading.value = false
}
}
const handleSearch = () => {
pagination.current = 1
fetchData()
}
const handleReset = () => {
Object.assign(searchForm, {
current: 1,
size: 10,
dictType: '',
dictCode: '',
dictName: '',
status: undefined
})
fetchData()
}
const handleCreate = () => {
// 跳转到创建页面
window.location.hash = '#/dictionary/create'
}
const handleEdit = (row: Dictionary) => {
// 跳转到编辑页面
window.location.hash = `#/dictionary/edit/${row.id}`
}
const handleView = (row: Dictionary) => {
currentDictionary.value = row
detailVisible.value = true
}
const handleDelete = (row: Dictionary) => {
ElMessageBox.confirm('确定要删除该字典吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await deleteDictionary(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
console.error('删除失败:', error)
}
})
}
const handleToggleStatus = (row: Dictionary) => {
const action = row.status === 1 ? '禁用' : '启用'
ElMessageBox.confirm(`确定要${action}该字典吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await updateDictionary({
id: row.id,
dictType: row.dictType,
dictCode: row.dictCode,
dictName: row.dictName,
dictValue: row.dictValue,
sortOrder: row.sortOrder,
status: row.status === 1 ? 0 : 1,
remarks: row.remarks
})
ElMessage.success(`${action}成功`)
fetchData()
} catch (error) {
console.error(`${action}失败:`, error)
}
})
}
onMounted(() => {
fetchData()
})
</script>
<style scoped lang="scss">
.dictionary-list {
.page-title {
margin-bottom: 20px;
font-size: 24px;
color: #333;
}
.search-card {
margin-bottom: 20px;
.el-form {
margin-bottom: 20px;
}
}
.table-card {
.pagination {
margin-top: 20px;
justify-content: flex-end;
}
}
}
</style>