369 lines
9.8 KiB
Vue
369 lines
9.8 KiB
Vue
<template>
|
||
<div class="container-box">
|
||
<div class="header-bar">
|
||
<h2>字典管理</h2>
|
||
<div class="header-actions">
|
||
<el-button @click="refresh">
|
||
<el-icon><Refresh /></el-icon>
|
||
刷新
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<el-divider></el-divider>
|
||
|
||
<!-- 内容区域:根据当前视图切换 -->
|
||
<div v-if="currentView === 'types'">
|
||
<div class="tab-content">
|
||
<div class="toolbar">
|
||
<el-button type="primary" @click="handleAddType">
|
||
<el-icon><Plus /></el-icon>
|
||
添加字典类型
|
||
</el-button>
|
||
<div class="search-box">
|
||
<el-input
|
||
v-model="typeSearchKeyword"
|
||
placeholder="搜索字典类型名称或编码"
|
||
clearable
|
||
style="width: 300px"
|
||
@input="handleTypeSearch"
|
||
>
|
||
<template #prefix>
|
||
<el-icon><Search /></el-icon>
|
||
</template>
|
||
</el-input>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table
|
||
:data="dictTypes"
|
||
stripe
|
||
style="width: 100%"
|
||
v-loading="typeLoading"
|
||
>
|
||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||
<el-table-column prop="dict_name" label="字典名称" min-width="150" align="center" />
|
||
<el-table-column prop="dict_code" label="字典编码" min-width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag size="small">{{ row.dict_code }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<!-- <el-table-column prop="parent_id" label="父级ID" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<span v-if="row.parent_id > 0">{{ row.parent_id }}</span>
|
||
<span v-else style="color: #999">-</span>
|
||
</template>
|
||
</el-table-column> -->
|
||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="is_global" label="全局展示" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.is_global === 1 ? 'success' : 'warning'" size="small">
|
||
{{ row.is_global === 1 ? '是' : '否' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<!-- <el-table-column prop="sort" label="排序" width="80" align="center" /> -->
|
||
<!-- <el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip /> -->
|
||
<el-table-column prop="create_time" label="创建时间" width="180" align="center">
|
||
<template #default="{ row }">
|
||
{{ formatDate(row.create_time) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button size="small" type="primary" link @click="handleEditType(row)">
|
||
<el-icon><Edit /></el-icon>
|
||
编辑
|
||
</el-button>
|
||
<el-button size="small" type="primary" link @click="handleViewItems(row)">
|
||
<el-icon><List /></el-icon>
|
||
查看字典项
|
||
</el-button>
|
||
<el-button size="small" type="danger" link @click="handleDeleteType(row)">
|
||
<el-icon><Delete /></el-icon>
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-bar">
|
||
<el-pagination
|
||
:current-page="currentPage"
|
||
:page-size="pageSize"
|
||
:total="totalCount"
|
||
@current-change="handlePageChange"
|
||
layout="total, prev, pager, next"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else-if="currentView === 'items'">
|
||
<div class="tab-content">
|
||
<div class="toolbar">
|
||
<div>
|
||
<el-button @click="goBackToTypes">
|
||
返回
|
||
</el-button>
|
||
</div>
|
||
<div class="search-box">
|
||
<span v-if="selectedDictType">
|
||
当前字典类型:{{ selectedDictType.dict_name }}({{ selectedDictType.dict_code }})
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<DictItemList :selected-type-id="selectedDictTypeId || undefined" @refresh="refreshTypesSilently" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 字典类型编辑对话框 -->
|
||
<DictTypeEdit
|
||
v-model="typeDialogVisible"
|
||
:dict-type="currentDictType"
|
||
@success="handleTypeSuccess"
|
||
/>
|
||
|
||
<!-- 字典项独立页面内操作通过 DictItemList 完成 -->
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import {
|
||
Plus,
|
||
Edit,
|
||
Delete,
|
||
Refresh,
|
||
Search,
|
||
List,
|
||
} from '@element-plus/icons-vue'
|
||
import {
|
||
getDictTypes,
|
||
deleteDictType,
|
||
} from '@/api/dict'
|
||
import DictTypeEdit from './components/DictTypeEdit.vue'
|
||
import DictItemList from './components/DictItemList.vue'
|
||
|
||
const currentView = ref<'types' | 'items'>('types')
|
||
const typeLoading = ref(false)
|
||
|
||
// 字典类型相关
|
||
const dictTypes = ref<any[]>([])
|
||
const typeSearchKeyword = ref('')
|
||
const typeDialogVisible = ref(false)
|
||
const currentDictType = ref<any>(null)
|
||
|
||
// 分页相关
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(10)
|
||
const totalCount = ref(0)
|
||
|
||
const selectedDictTypeId = ref<number | null>(null)
|
||
const selectedDictType = computed(() => {
|
||
if (!selectedDictTypeId.value) {
|
||
return null
|
||
}
|
||
return dictTypes.value.find((type) => type.id === selectedDictTypeId.value) || null
|
||
})
|
||
|
||
// 格式化日期
|
||
function formatDate(dateStr: string | null | undefined): string {
|
||
if (!dateStr) return ''
|
||
try {
|
||
const date = new Date(dateStr)
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
})
|
||
} catch {
|
||
return dateStr as string
|
||
}
|
||
}
|
||
|
||
// 获取字典类型列表
|
||
async function fetchDictTypes() {
|
||
typeLoading.value = true
|
||
try {
|
||
const res = await getDictTypes({
|
||
page: currentPage.value,
|
||
pageSize: pageSize.value,
|
||
keyword: typeSearchKeyword.value
|
||
})
|
||
if (res.success === true && res.data) {
|
||
dictTypes.value = Array.isArray(res.data.list) ? res.data.list : (Array.isArray(res.data) ? res.data : [])
|
||
totalCount.value = res.data.total || res.data.length || 0
|
||
} else if (Array.isArray(res)) {
|
||
dictTypes.value = res
|
||
totalCount.value = res.length
|
||
} else {
|
||
dictTypes.value = []
|
||
totalCount.value = 0
|
||
ElMessage.error(res.message || '获取字典类型失败')
|
||
}
|
||
} catch (error: any) {
|
||
ElMessage.error(error.message || '获取字典类型失败')
|
||
dictTypes.value = []
|
||
totalCount.value = 0
|
||
} finally {
|
||
typeLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 字典类型搜索
|
||
function handleTypeSearch() {
|
||
currentPage.value = 1
|
||
fetchDictTypes()
|
||
}
|
||
|
||
// 分页切换
|
||
function handlePageChange(page: number) {
|
||
currentPage.value = page
|
||
fetchDictTypes()
|
||
}
|
||
|
||
// 添加字典类型
|
||
function handleAddType() {
|
||
currentDictType.value = null
|
||
typeDialogVisible.value = true
|
||
}
|
||
|
||
// 编辑字典类型
|
||
function handleEditType(row: any) {
|
||
currentDictType.value = { ...row }
|
||
typeDialogVisible.value = true
|
||
}
|
||
|
||
// 查看字典项
|
||
function handleViewItems(row: any) {
|
||
selectedDictTypeId.value = row.id
|
||
currentView.value = 'items'
|
||
}
|
||
|
||
// 删除字典类型
|
||
async function handleDeleteType(row: any) {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要删除字典类型「${row.dict_name}」吗?删除后不可恢复。`,
|
||
'警告',
|
||
{ type: 'warning' }
|
||
)
|
||
|
||
typeLoading.value = true
|
||
try {
|
||
const res = await deleteDictType(row.id)
|
||
if (res.success === true) {
|
||
ElMessage.success('删除成功')
|
||
fetchDictTypes()
|
||
} else {
|
||
ElMessage.error(res.message || '删除失败')
|
||
}
|
||
} catch (error: any) {
|
||
ElMessage.error(error.message || '删除失败')
|
||
} finally {
|
||
typeLoading.value = false
|
||
}
|
||
} catch {
|
||
// 取消删除
|
||
}
|
||
}
|
||
|
||
// 字典类型操作成功回调
|
||
function handleTypeSuccess() {
|
||
fetchDictTypes()
|
||
}
|
||
|
||
// 从字典项页返回
|
||
function goBackToTypes() {
|
||
currentView.value = 'types'
|
||
}
|
||
|
||
// 子页面刷新后,静默刷新类型列表
|
||
function refreshTypesSilently() {
|
||
fetchDictTypes()
|
||
}
|
||
|
||
// 刷新
|
||
async function refresh() {
|
||
await fetchDictTypes()
|
||
ElMessage.success('刷新成功')
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchDictTypes()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.container-box {
|
||
padding: 20px;
|
||
}
|
||
|
||
.header-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
|
||
h2 {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
}
|
||
|
||
.tab-content {
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-box {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.pagination-bar {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.color-display {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.color-dot {
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
border: 1px solid #e5e5e5;
|
||
display: inline-block;
|
||
}
|
||
}
|
||
</style>
|