2026-08-11 00:49:41 +08:00
|
|
|
|
<!--
|
|
|
|
|
|
功能:承载资源详情页中的审核、流转、归属调整和菜单分配等短业务操作。
|
|
|
|
|
|
版本: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"
|
2026-08-11 14:28:13 +08:00
|
|
|
|
@search-relation="searchRelation"
|
2026-08-11 00:49:41 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</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';
|
2026-08-11 14:28:13 +08:00
|
|
|
|
import type { DetailAction, ResourceField } from '@/api/resources';
|
2026-08-11 00:49:41 +08:00
|
|
|
|
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',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-08-11 14:28:13 +08:00
|
|
|
|
/** 业务动作字段不启用记录页联动,只沿用普通关联搜索。 */
|
|
|
|
|
|
function searchRelation(field: ResourceField, keyword: string) {
|
2026-08-11 19:15:53 +08:00
|
|
|
|
relations.searchField(field, keyword);
|
2026-08-11 14:28:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 00:49:41 +08:00
|
|
|
|
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>
|