You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
9.1 KiB

<script setup lang="ts">
import PageTitle from '@/components/PageTitle.vue'
import { ref } from 'vue'
import { usePageLoad } from '@/composables/usePageLoad'
import {
fetchDictTypes,
createDictType,
updateDictType,
deleteDictType,
fetchDictItems,
createDictItem,
updateDictItem,
deleteDictItem,
} from '@/api/admin/dict'
import type { DictTypeRow, DictItemRow } from '@/api/types'
import { enabledStatusClass } from '@/utils/admin-list'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const types = ref<DictTypeRow[]>([])
const meta = ref({ current_page: 1, per_page: 20, total: 0 })
const page = ref(1)
const keyword = ref('')
const itemsDialog = ref(false)
const currentType = ref<DictTypeRow | null>(null)
const items = ref<DictItemRow[]>([])
const itemsLoading = ref(false)
const typeDialog = ref(false)
const editingType = ref<DictTypeRow | null>(null)
const typeForm = ref({
code: '',
name: '',
remark: '',
status: 1,
sort: 0,
})
async function loadTypes() {
loading.value = true
try {
const res = await fetchDictTypes({
page: page.value,
page_size: meta.value.per_page,
keyword: keyword.value || undefined,
})
types.value = res.items
meta.value = res.meta
} finally {
loading.value = false
}
}
function openCreateType() {
editingType.value = null
typeForm.value = { code: '', name: '', remark: '', status: 1, sort: 0 }
typeDialog.value = true
}
function openEditType(row: DictTypeRow) {
editingType.value = row
typeForm.value = {
code: row.code,
name: row.name,
remark: row.remark || '',
status: row.status,
sort: row.sort,
}
typeDialog.value = true
}
async function saveType() {
if (editingType.value) {
await updateDictType(editingType.value.id, typeForm.value)
} else {
await createDictType(typeForm.value)
}
ElMessage.success('已保存')
typeDialog.value = false
await loadTypes()
}
async function removeType(row: DictTypeRow) {
await ElMessageBox.confirm(`确定删除字典类型「${row.name}」及其全部字典项?`, '提示', {
type: 'warning',
})
await deleteDictType(row.id)
ElMessage.success('已删除')
await loadTypes()
}
async function openItems(row: DictTypeRow) {
currentType.value = row
itemsDialog.value = true
itemsLoading.value = true
try {
items.value = await fetchDictItems(row.id)
} finally {
itemsLoading.value = false
}
}
const itemFormOpen = ref(false)
const editingItem = ref<DictItemRow | null>(null)
const itemForm = ref({
label: '',
value: '',
sort: 0,
status: 1,
})
function openCreateItem() {
editingItem.value = null
itemForm.value = { label: '', value: '', sort: 0, status: 1 }
itemFormOpen.value = true
}
function openEditItem(row: DictItemRow) {
editingItem.value = row
itemForm.value = {
label: row.label,
value: row.value,
sort: row.sort,
status: row.status,
}
itemFormOpen.value = true
}
async function saveItem() {
if (!currentType.value) return
if (editingItem.value) {
await updateDictItem(currentType.value.id, editingItem.value.id, itemForm.value)
} else {
await createDictItem(currentType.value.id, itemForm.value)
}
ElMessage.success('已保存')
itemFormOpen.value = false
items.value = await fetchDictItems(currentType.value.id)
}
async function removeItem(row: DictItemRow) {
if (!currentType.value) return
await ElMessageBox.confirm(`删除字典项「${row.label}」?`, '提示', { type: 'warning' })
await deleteDictItem(currentType.value.id, row.id)
ElMessage.success('已删除')
items.value = await fetchDictItems(currentType.value.id)
}
function searchTypes() {
page.value = 1
void loadTypes()
}
function resetFilters() {
keyword.value = ''
page.value = 1
void loadTypes()
}
usePageLoad(loadTypes)
</script>
<template>
<div class="list-page">
<div class="page-header">
<PageTitle />
<el-button type="primary" size="small" class="btn-create" @click="openCreateType"></el-button>
</div>
<el-card shadow="never" class="admin-list-card">
<div class="list-filter-bar">
<el-input
v-model="keyword"
placeholder="编码/名称"
clearable
class="filter-search"
@keyup.enter="searchTypes"
/>
<el-button type="primary" @click="searchTypes">搜索</el-button>
<el-button @click="resetFilters">重置</el-button>
</div>
<el-table v-loading="loading" :data="types" row-key="id">
<el-table-column prop="code" label="编码" width="160" />
<el-table-column prop="name" label="名称" width="180" />
<el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip />
<el-table-column prop="sort" label="排序" width="80" align="center" />
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<span class="status-badge" :class="enabledStatusClass(row.status)">
{{ row.status === 1 ? '启用' : '停用' }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<div class="table-row-actions">
<el-button class="btn-action-brand" @click="openItems(row)">字典项</el-button>
<el-button class="btn-action-secondary" @click="openEditType(row)">编辑</el-button>
<el-button class="btn-action-secondary" @click="removeType(row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="list-pager">
<el-pagination
v-model:current-page="page"
layout="total, prev, pager, next"
:total="meta.total"
:page-size="meta.per_page"
@current-change="loadTypes"
/>
</div>
</el-card>
</div>
<el-dialog v-model="typeDialog" :title="editingType ? '编辑类型' : '新增类型'" width="480px">
<el-form label-width="88px">
<el-form-item label="编码" v-if="!editingType">
<el-input v-model="typeForm.code" />
</el-form-item>
<el-form-item label="编码" v-else>
<el-input v-model="typeForm.code" disabled />
</el-form-item>
<el-form-item label="名称">
<el-input v-model="typeForm.name" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="typeForm.remark" type="textarea" :rows="2" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="typeForm.sort" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="typeForm.status" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="typeDialog = false">取消</el-button>
<el-button type="primary" @click="saveType">保存</el-button>
</template>
</el-dialog>
<el-dialog
v-model="itemsDialog"
:title="currentType ? `字典项 · ${currentType.name}` : '字典项'"
width="720px"
destroy-on-close
>
<div class="items-toolbar">
<el-button type="primary" size="small" @click="openCreateItem">新增字典项</el-button>
</div>
<el-table v-loading="itemsLoading" :data="items" row-key="id" style="margin-top: 8px">
<el-table-column prop="label" label="展示" width="160" />
<el-table-column prop="value" label="值" width="160" />
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column label="状态" width="90">
<template #default="{ row }">
<span class="status-badge" :class="enabledStatusClass(row.status)">
{{ row.status === 1 ? '启用' : '停用' }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template #default="{ row }">
<div class="table-row-actions">
<el-button class="btn-action-secondary" @click="openEditItem(row)">编辑</el-button>
<el-button class="btn-action-secondary" @click="removeItem(row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog v-model="itemFormOpen" :title="editingItem ? '编辑字典项' : '新增字典项'" width="420px">
<el-form label-width="72px">
<el-form-item label="展示">
<el-input v-model="itemForm.label" />
</el-form-item>
<el-form-item label="值">
<el-input v-model="itemForm.value" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="itemForm.sort" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="itemForm.status" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="itemFormOpen = false">取消</el-button>
<el-button type="primary" @click="saveItem"></el-button>
</template>
</el-dialog>
</template>
<style scoped>
.items-toolbar {
display: flex;
justify-content: flex-end;
}
</style>