feat: 标准资源使用独立页面并优化详情布局

This commit is contained in:
czl231
2026-08-11 00:49:41 +08:00
parent 7242048abf
commit 13daa996a2
39 changed files with 3826 additions and 2106 deletions

View File

@@ -0,0 +1,164 @@
<!--
功能承载资源详情页中的审核流转归属调整和菜单分配等短业务操作
版本v1.0.0
-->
<template>
<a-modal
:visible="visible"
:title="action?.name"
:width="680"
:ok-loading="submitting"
:ok-button-props="{ status: action?.danger ? 'danger' : 'normal' }"
@cancel="close"
@ok="submit"
>
<a-alert v-if="action?.danger" class="action-alert" type="error" show-icon>
这是高风险操作可能改变业务状态且无法直接撤销请确认目标记录和填写内容准确
</a-alert>
<a-alert v-if="isOwnershipAction" class="action-alert" type="warning" show-icon>
智能气阀只能有一个当前归属选择新归属前请清空原归属操作会写入审计记录
</a-alert>
<a-form :model="form" layout="vertical">
<ResourceFieldForm
v-model="form"
:fields="action?.fields ?? []"
:required-keys="requiredKeys"
:relation-options="relations.options"
:relation-loading="relations.loading"
@search-relation="relations.search"
/>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import { computed, reactive, ref, watch } from 'vue';
import { buildResourcePayload, isMissingField } from '@/api/resource-form';
import { resourceApi } from '@/api/resource';
import { platformApi } from '@/api/platform';
import type { DetailAction } from '@/api/resources';
import type { ResourceRow } from '@/api/resource-page-rules';
import ResourceFieldForm from './ResourceFieldForm.vue';
import { useResourceRelations } from './use-resource-relations';
const props = defineProps<{
visible: boolean;
action?: DetailAction;
identity: string;
record: ResourceRow;
}>();
const emit = defineEmits<{
'update:visible': [value: boolean];
completed: [];
}>();
const form = reactive<Record<string, any>>({});
const submitting = ref(false);
const relations = useResourceRelations();
const requiredKeys = computed(() =>
(props.action?.fields ?? [])
.filter((field) => field.required)
.map((field) => field.key),
);
const isOwnershipAction = computed(
() => props.action?.resource === '/product_info/:identity',
);
const ownershipKeys = [
'warehouse_identity',
'gas_basic_identity',
'delivery_basic_identity',
'user_account_identity',
];
watch(
() => [props.visible, props.action?.name] as const,
async ([visible]) => {
if (!visible || !props.action) return;
for (const field of props.action.fields ?? []) {
form[field.key] = isOwnershipAction.value
? props.record[field.key]
: undefined;
}
await relations.preload(props.action.fields ?? []);
if (props.action.resource.includes('/menu')) {
try {
const [menus, assigned] = await Promise.all([
platformApi.listMenu(),
platformApi.listRoleMenuIdentities(props.identity),
]);
relations.options['/platform_menu'] = menus.list;
form.menu_identities = assigned.menu_identities;
} catch (error) {
Message.error((error as Error).message);
close();
}
}
},
);
function close() {
emit('update:visible', false);
}
async function submit() {
const action = props.action;
if (!action) return;
if (
(action.fields ?? []).some(
(field) => field.required && isMissingField(form[field.key]),
)
) {
Message.warning('请填写必填字段');
return;
}
const invalidPassword = (action.fields ?? []).find(
(field) =>
field.type === 'password' &&
!isMissingField(form[field.key]) &&
String(form[field.key]).length < 6,
);
if (invalidPassword) {
Message.warning(`${invalidPassword.label}长度不能少于 6 位`);
return;
}
if (isOwnershipAction.value) {
const selected = ownershipKeys.filter((key) => !isMissingField(form[key]));
if (selected.length > 1) {
Message.warning('智能气阀只能选择一个当前归属');
return;
}
}
submitting.value = true;
try {
const payload = buildResourcePayload(action.fields ?? [], form, 'create');
if (isOwnershipAction.value) {
for (const key of ownershipKeys) payload[key] = String(form[key] ?? '');
}
if (action.resource.includes('/menu')) {
await platformApi.replaceRoleMenus(
props.identity,
payload.menu_identities as string[],
);
} else {
await resourceApi.action(
action.resource.replace(':identity', props.identity),
action.method ?? 'POST',
payload,
);
}
Message.success('操作成功');
close();
emit('completed');
} catch (error) {
Message.error((error as Error).message);
} finally {
submitting.value = false;
}
}
</script>
<style scoped lang="less">
.action-alert {
margin-bottom: 16px;
}
</style>