完善基础数据
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user