fix: close platform admin final audit findings
This commit is contained in:
@@ -1,17 +1,138 @@
|
||||
<template><a-card :title="definition.title" :bordered="false"><template #extra><a-space><a-button @click="load">刷新</a-button><a-button v-if="canWrite" type="primary" @click="openCreate">新增</a-button></a-space></template><a-tree :data="tree" :loading="loading" :field-names="{ key: 'identity', title: 'name', children: 'children' }"><template #title="node"><a-space>{{ node.title }}<a-button v-if="canWrite" size="mini" @click.stop="openEdit(node)">编辑</a-button></a-space></template></a-tree></a-card><a-drawer :visible="formVisible" :title="editingIdentity ? '编辑' + definition.title : '新增' + definition.title" :width="480" @cancel="formVisible = false" @ok="save"><a-form :model="form" layout="vertical"><a-form-item v-for="field in definition.fields" :key="field.key" :label="field.label" :required="field.required"><a-input v-model="form[field.key]" /></a-form-item></a-form></a-drawer></template>
|
||||
<template>
|
||||
<a-card :title="definition.title" :bordered="false">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="load">刷新</a-button>
|
||||
<a-button v-if="canWrite" type="primary" @click="openCreate">新增</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-tree :data="tree" :loading="loading" :field-names="{ key: 'identity', title: 'name', children: 'children' }">
|
||||
<template #title="node">
|
||||
<a-space>
|
||||
{{ node.title }}
|
||||
<a-button v-if="canWrite" size="mini" @click.stop="openEdit(node)">编辑</a-button>
|
||||
<a-button v-if="canWrite" size="mini" status="danger" @click.stop="confirmArchive(node)">归档</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-card>
|
||||
<a-drawer :visible="formVisible" :title="editingIdentity ? `编辑${definition.title}` : `新增${definition.title}`" :width="480" @cancel="formVisible = false" @ok="save">
|
||||
<a-form :model="form" layout="vertical">
|
||||
<a-form-item v-for="field in definition.fields" :key="field.key" :label="field.label" :required="field.required">
|
||||
<a-input-number v-if="field.type === 'number'" v-model="form[field.key]" />
|
||||
<a-switch v-else-if="field.type === 'boolean'" v-model="form[field.key]" />
|
||||
<a-input v-else v-model="form[field.key]" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { Message, Modal } from '@arco-design/web-vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { resourceApi } from '@/api/resource';
|
||||
import { buildResourcePayload, isMissingField } from '@/api/resource-form';
|
||||
import type { ResourceUiDefinition } from '@/api/resources';
|
||||
type Node = Record<string, unknown> & { identity: string; parent_identity?: string; children: Node[] };
|
||||
const props = defineProps<{ definition: ResourceUiDefinition }>(); const loading = ref(false); const list = ref<Node[]>([]); const formVisible = ref(false); const editingIdentity = ref(''); const form = reactive<Record<string, string>>({});
|
||||
|
||||
type Node = Record<string, unknown> & {
|
||||
identity: string;
|
||||
parent_identity?: string;
|
||||
children: Node[];
|
||||
};
|
||||
const props = defineProps<{ definition: ResourceUiDefinition }>();
|
||||
const loading = ref(false);
|
||||
const list = ref<Node[]>([]);
|
||||
const formVisible = ref(false);
|
||||
const editingIdentity = ref('');
|
||||
const form = reactive<Record<string, any>>({});
|
||||
const canWrite = computed(() => props.definition.mode === 'writable');
|
||||
const tree = computed(() => { const byIdentity = new Map<string, Node>(); const roots: Node[] = []; list.value.forEach((item) => byIdentity.set(item.identity, { ...item, children: [] })); byIdentity.forEach((item) => { const parent = item.parent_identity ? byIdentity.get(item.parent_identity) : undefined; if (parent) parent.children.push(item); else roots.push(item); }); return roots; });
|
||||
function reset(data?: Node) { props.definition.fields.forEach((field) => { form[field.key] = data?.[field.key] == null ? '' : String(data[field.key]); }); }
|
||||
function openCreate() { editingIdentity.value = ''; reset(); formVisible.value = true; }
|
||||
function openEdit(node: Node) { editingIdentity.value = node.identity; reset(node); formVisible.value = true; }
|
||||
async function save() { if (props.definition.fields.some((field) => field.required && !form[field.key])) { Message.warning('请填写必填字段'); return; } try { if (editingIdentity.value) await resourceApi.update(props.definition.resource, editingIdentity.value, form); else await resourceApi.create(props.definition.resource, form); formVisible.value = false; await load(); } catch (error) { Message.error((error as Error).message); } }
|
||||
async function load() { loading.value = true; try { list.value = (await resourceApi.list<Node>(props.definition.resource, 1, 500)).list; } catch (error) { Message.error((error as Error).message); } finally { loading.value = false; } }
|
||||
const tree = computed(() => {
|
||||
const byIdentity = new Map<string, Node>();
|
||||
const roots: Node[] = [];
|
||||
for (const item of list.value)
|
||||
byIdentity.set(item.identity, { ...item, children: [] });
|
||||
for (const item of byIdentity.values()) {
|
||||
const parent = item.parent_identity
|
||||
? byIdentity.get(item.parent_identity)
|
||||
: undefined;
|
||||
if (parent) parent.children.push(item);
|
||||
else roots.push(item);
|
||||
}
|
||||
return roots;
|
||||
});
|
||||
|
||||
function reset(data?: Node) {
|
||||
for (const field of props.definition.fields) {
|
||||
const value = data?.[field.key];
|
||||
form[field.key] = value == null ? undefined : value;
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editingIdentity.value = '';
|
||||
reset();
|
||||
formVisible.value = true;
|
||||
}
|
||||
|
||||
function openEdit(node: Node) {
|
||||
editingIdentity.value = node.identity;
|
||||
reset(node);
|
||||
formVisible.value = true;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (
|
||||
props.definition.fields.some(
|
||||
(field) => field.required && isMissingField(form[field.key]),
|
||||
)
|
||||
) {
|
||||
Message.warning('请填写必填字段');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = buildResourcePayload(props.definition.fields, form);
|
||||
if (editingIdentity.value)
|
||||
await resourceApi.update(
|
||||
props.definition.resource,
|
||||
editingIdentity.value,
|
||||
payload,
|
||||
);
|
||||
else await resourceApi.create(props.definition.resource, payload);
|
||||
formVisible.value = false;
|
||||
await load();
|
||||
} catch (error) {
|
||||
Message.error((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmArchive(node: Node) {
|
||||
Modal.warning({
|
||||
title: '确认归档',
|
||||
content: `归档“${String(node.name ?? node.identity)}”后,其历史数据仍会保留。`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await resourceApi.archive(props.definition.resource, node.identity);
|
||||
Message.success('已归档');
|
||||
await load();
|
||||
} catch (error) {
|
||||
Message.error((error as Error).message);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
list.value = (
|
||||
await resourceApi.list<Node>(props.definition.resource, 1, 500)
|
||||
).list;
|
||||
} catch (error) {
|
||||
Message.error((error as Error).message);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user