完善基础数据

This commit is contained in:
yanpeng200212
2026-10-08 15:44:07 +08:00
parent 8430ba0452
commit 2ffb83830c
95 changed files with 37759 additions and 5 deletions
+335
View File
@@ -0,0 +1,335 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';
import request from '../../../utils/request';
import { ElMessage, ElMessageBox } from 'element-plus';
const allData = ref([]);
const treeData = ref([]);
const loading = ref(false);
const selectedPath = ref('');
const treeRef = ref(null);
const query = reactive({ keyword: '', pageIndex: 1, pageSize: 20 });
const pagedList = ref([]);
const filteredTotal = ref(0);
const dataTypeOptions = ['string', 'int', 'decimal', 'bool', 'json', 'array'];
const dialogVisible = ref(false);
const dialogTitle = ref('新增配置项');
const isEdit = ref(false);
const editId = ref(null);
const form = reactive({
category: '',
keyName: '',
value: '',
dataType: 'string',
description: '',
isActive: true
});
// 按命名空间(反斜杠分层)构建树,并统计各节点(含子级)配置项数量
function buildTree(list) {
const roots = [];
const map = {};
const paths = [...new Set(list.map((x) => (x.category || '').trim()).filter(Boolean))].sort();
paths.forEach((path) => {
const parts = path.split('\\');
let cur = '';
parts.forEach((part, i) => {
cur = i === 0 ? part : `${cur}\\${part}`;
if (!map[cur]) {
const node = { id: cur, label: part, children: [] };
map[cur] = node;
if (i === 0) roots.push(node);
else map[parts.slice(0, i).join('\\')].children.push(node);
}
});
});
const countOf = (path) =>
list.filter((x) => x.category === path || (x.category || '').startsWith(`${path}\\`)).length;
Object.values(map).forEach((node) => {
node.label = `${node.label} (${countOf(node.id)})`;
});
return roots;
}
function applyFilter() {
let data = allData.value;
if (selectedPath.value) {
data = data.filter(
(x) => x.category === selectedPath.value || (x.category || '').startsWith(`${selectedPath.value}\\`)
);
}
const kw = (query.keyword || '').trim();
if (kw) {
data = data.filter(
(x) =>
(x.keyName || '').includes(kw) ||
(x.value || '').includes(kw) ||
(x.description || '').includes(kw) ||
(x.category || '').includes(kw)
);
}
filteredTotal.value = data.length;
const start = (query.pageIndex - 1) * query.pageSize;
pagedList.value = data.slice(start, start + query.pageSize);
}
async function loadAll() {
loading.value = true;
try {
const res = await request.get('/sys/registry');
allData.value = res.data || [];
treeData.value = buildTree(allData.value);
applyFilter();
} finally {
loading.value = false;
}
}
function handleNodeClick(node) {
selectedPath.value = node.id;
query.pageIndex = 1;
applyFilter();
}
function handleClearNode() {
selectedPath.value = '';
treeRef.value?.setCurrentKey(null);
query.pageIndex = 1;
applyFilter();
}
function handleSearch() {
query.pageIndex = 1;
applyFilter();
}
function handlePageChange(page) {
query.pageIndex = page;
applyFilter();
}
function openCreate() {
isEdit.value = false;
dialogTitle.value = '新增配置项';
Object.assign(form, {
category: selectedPath.value || '',
keyName: '',
value: '',
dataType: 'string',
description: '',
isActive: true
});
dialogVisible.value = true;
}
function openEdit(row) {
isEdit.value = true;
editId.value = row.id;
dialogTitle.value = '修改配置项';
Object.assign(form, {
category: row.category,
keyName: row.keyName,
value: row.value,
dataType: row.dataType || 'string',
description: row.description || '',
isActive: row.isActive
});
dialogVisible.value = true;
}
async function handleSave() {
if (!form.category) {
ElMessage.warning('请填写命名空间');
return;
}
if (!form.keyName) {
ElMessage.warning('请填写键名');
return;
}
const payload = {
category: form.category.trim(),
keyName: form.keyName.trim(),
value: form.value ?? '',
dataType: form.dataType,
description: form.description || '',
isActive: form.isActive
};
try {
if (isEdit.value) {
await request.put(`/sys/registry/${editId.value}`, payload);
ElMessage.success('修改成功');
} else {
await request.post('/sys/registry', payload);
ElMessage.success('保存成功');
}
dialogVisible.value = false;
loadAll();
} catch (e) {}
}
async function handleDelete(row) {
await ElMessageBox.confirm(`确定删除配置项「${row.keyName}」吗?`, '提示', { type: 'warning' });
try {
await request.delete(`/sys/registry/${row.id}`);
ElMessage.success('删除成功');
loadAll();
} catch (e) {}
}
const currentPathLabel = computed(() => selectedPath.value || '全部配置项');
onMounted(loadAll);
</script>
<template>
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>注册表</span>
<el-button type="primary" @click="openCreate">新增配置项</el-button>
</div>
</template>
<div class="registry-body">
<div class="tree-panel">
<div class="tree-title">
<span>命名空间</span>
<el-button size="small" link type="primary" @click="handleClearNode">全部</el-button>
</div>
<el-tree
ref="treeRef"
:data="treeData"
node-key="id"
highlight-current
default-expand-all
:props="{ label: 'label', children: 'children' }"
@node-click="handleNodeClick"
/>
</div>
<div class="table-panel">
<div class="search-bar">
<el-input
v-model="query.keyword"
placeholder="键名 / 值 / 描述 / 命名空间"
clearable
style="width: 260px"
@keyup.enter="handleSearch"
@clear="handleSearch"
/>
<el-button type="primary" @click="handleSearch">查询</el-button>
<span class="path-tip">当前:{{ currentPathLabel }}</span>
</div>
<el-table v-loading="loading" :data="pagedList" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="keyName" label="键名" min-width="150" />
<el-table-column prop="value" label="值" min-width="220" show-overflow-tooltip />
<el-table-column prop="dataType" label="数据类型" width="100" />
<el-table-column prop="description" label="描述" min-width="140" show-overflow-tooltip />
<el-table-column label="是否启用" width="90">
<template #default="{ row }">
<el-tag :type="row.isActive ? 'success' : 'info'">{{ row.isActive ? '启用' : '禁用' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="category" label="命名空间" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button size="small" type="primary" link @click="openEdit(row)">编辑</el-button>
<el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="query.pageIndex"
:page-size="query.pageSize"
:total="filteredTotal"
layout="total, prev, pager, next"
class="pager"
@current-change="handlePageChange"
/>
</div>
</div>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px">
<el-form :model="form" label-width="100px">
<el-form-item label="命名空间" required>
<el-input v-model="form.category" placeholder="如:基础数据\工种" />
</el-form-item>
<el-form-item label="键名" required>
<el-input v-model="form.keyName" placeholder="如:业务类型" />
</el-form-item>
<el-form-item label="值">
<el-input v-model="form.value" type="textarea" :rows="3" placeholder="数组类型存逗号分隔字符串;json 类型存 JSON 文本" />
</el-form-item>
<el-form-item label="数据类型">
<el-select v-model="form.dataType" style="width: 100%">
<el-option v-for="t in dataTypeOptions" :key="t" :label="t" :value="t" />
</el-select>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" placeholder="配置说明" />
</el-form-item>
<el-form-item label="是否启用">
<el-switch v-model="form.isActive" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">确定</el-button>
</template>
</el-dialog>
</el-card>
</template>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.registry-body {
display: flex;
gap: 12px;
}
.tree-panel {
width: 240px;
flex-shrink: 0;
border-right: 1px solid var(--el-border-color-lighter);
padding-right: 8px;
max-height: 620px;
overflow: auto;
}
.tree-title {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
margin-bottom: 8px;
}
.table-panel {
flex: 1;
min-width: 0;
}
.search-bar {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 12px;
}
.path-tip {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.pager {
margin-top: 12px;
justify-content: flex-end;
}
</style>