Files
Ticket/web/src/features/system/dicts/index.vue
T
2026-05-22 15:37:45 +08:00

238 lines
6.5 KiB
Vue

<template>
<div class="page-shell">
<div class="grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-2">
<section class="page-card table-fill">
<div class="page-toolbar">
<span class="text-sm font-semibold text-slate-700">字典类型</span>
<n-button size="small" type="primary" @click="openTypeCreate">新增类型</n-button>
</div>
<div class="card-body card-body-fill table-fill">
<n-data-table
flex-height
:columns="typeColumns"
:data="typeRows"
:pagination="typePagination"
remote
@update:page="onTypePage"
@update:page-size="onTypePageSize"
/>
</div>
</section>
<section class="page-card table-fill">
<div class="page-toolbar">
<span class="text-sm font-semibold text-slate-700">字典项</span>
<n-button size="small" type="primary" :disabled="!selectedTypeId" @click="openItemCreate"
>新增字典项</n-button
>
</div>
<div class="card-body card-body-fill table-fill">
<n-data-table
flex-height
:columns="itemColumns"
:data="itemRows"
:pagination="itemPagination"
remote
@update:page="onItemPage"
@update:page-size="onItemPageSize"
/>
</div>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, h, reactive, ref, watch } from 'vue'
import { NButton, NPopconfirm, NTag, NSpace, useMessage, type DataTableColumns } from 'naive-ui'
import { useMutation, useQuery } from '@tanstack/vue-query'
import {
createDictItemApi,
createDictTypeApi,
deleteDictItemApi,
deleteDictTypeApi,
listDictItemsApi,
listDictTypesApi
} from '@/api/system/dict'
import type { DictItem, DictTypeItem } from '@/types/system/dict'
import { statusLabel, statusTagType } from '@/utils/display'
import { renderPagePrefix } from '@/utils/pagination'
const message = useMessage()
const selectedTypeId = ref<string>('')
const typeQuery = reactive({ page: 1, pageSize: 10, keyword: '' })
const itemQuery = reactive({ page: 1, pageSize: 10 })
const typesReq = useQuery({
queryKey: computed(() => ['dict-types', { ...typeQuery }]),
queryFn: () => listDictTypesApi({ ...typeQuery, keyword: typeQuery.keyword || undefined })
})
const itemsReq = useQuery({
queryKey: computed(() => ['dict-items', selectedTypeId.value, { ...itemQuery }]),
queryFn: () => listDictItemsApi({ ...itemQuery, typeId: selectedTypeId.value || undefined })
})
const typeRows = computed(() => typesReq.data.value?.items ?? [])
const itemRows = computed(() => itemsReq.data.value?.items ?? [])
const typePagination = computed(() => ({
page: typeQuery.page,
pageSize: typeQuery.pageSize,
itemCount: typesReq.data.value?.total ?? 0,
showSizePicker: true,
pageSizes: [10, 20],
prefix: renderPagePrefix
}))
const itemPagination = computed(() => ({
page: itemQuery.page,
pageSize: itemQuery.pageSize,
itemCount: itemsReq.data.value?.total ?? 0,
showSizePicker: true,
pageSizes: [10, 20],
prefix: renderPagePrefix
}))
watch(
typeRows,
(v) => {
if (!selectedTypeId.value && v.length > 0) selectedTypeId.value = v[0].id
},
{ immediate: true }
)
const createTypeMutation = useMutation({
mutationFn: () =>
createDictTypeApi({
code: `TYPE_${Date.now()}`,
name: `新类型${Date.now().toString().slice(-4)}`,
status: 'ENABLED'
}),
onSuccess: async () => {
message.success('新增类型成功')
await typesReq.refetch()
}
})
const createItemMutation = useMutation({
mutationFn: () =>
createDictItemApi({
typeId: selectedTypeId.value,
label: `新项${Date.now().toString().slice(-4)}`,
value: `${Date.now()}`,
sort: 0,
status: 'ENABLED'
}),
onSuccess: async () => {
message.success('新增字典项成功')
await itemsReq.refetch()
}
})
const deleteTypeMutation = useMutation({
mutationFn: deleteDictTypeApi,
onSuccess: async () => {
message.success('删除成功')
selectedTypeId.value = ''
await typesReq.refetch()
await itemsReq.refetch()
}
})
const deleteItemMutation = useMutation({
mutationFn: deleteDictItemApi,
onSuccess: async () => {
message.success('删除成功')
await itemsReq.refetch()
}
})
function openTypeCreate() {
createTypeMutation.mutate()
}
function openItemCreate() {
if (!selectedTypeId.value) return
createItemMutation.mutate()
}
function onTypePage(p: number) {
typeQuery.page = p
}
function onTypePageSize(s: number) {
typeQuery.pageSize = s
typeQuery.page = 1
}
function onItemPage(p: number) {
itemQuery.page = p
}
function onItemPageSize(s: number) {
itemQuery.pageSize = s
itemQuery.page = 1
}
const typeColumns = computed<DataTableColumns<DictTypeItem>>(() => [
{ title: '编码', key: 'code' },
{
title: '名称',
key: 'name',
render: (r) => h('a', { onClick: () => (selectedTypeId.value = r.id) }, r.name)
},
{
title: '状态',
key: 'status',
render: (r) =>
h(
NTag,
{ size: 'small', type: statusTagType(r.status) },
{ default: () => statusLabel(r.status) }
)
},
{
title: '操作',
key: 'actions',
render: (r) =>
h(
NPopconfirm,
{ onPositiveClick: () => deleteTypeMutation.mutate(r.id) },
{
trigger: () =>
h(NButton, { size: 'tiny', tertiary: true, type: 'error' }, { default: () => '删除' }),
default: () => '确认删除?'
}
)
}
])
const itemColumns = computed<DataTableColumns<DictItem>>(() => [
{ title: '标签', key: 'label' },
{ title: '值', key: 'value' },
{
title: '颜色',
key: 'color',
render: (r) =>
r.color
? h(NTag, { size: 'small', color: { color: r.color } }, { default: () => r.color })
: '-'
},
{
title: '状态',
key: 'status',
render: (r) =>
h(
NTag,
{ size: 'small', type: statusTagType(r.status) },
{ default: () => statusLabel(r.status) }
)
},
{
title: '操作',
key: 'actions',
render: (r) =>
h(NSpace, { size: 6 }, () => [
h(
NPopconfirm,
{ onPositiveClick: () => deleteItemMutation.mutate(r.id) },
{
trigger: () =>
h(
NButton,
{ size: 'tiny', tertiary: true, type: 'error' },
{ default: () => '删除' }
),
default: () => '确认删除?'
}
)
])
}
])
</script>