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
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>
|