修复配送端合同与用户管理问题

This commit is contained in:
czl231
2026-08-31 20:49:51 +08:00
parent 9bbe25fa79
commit 9a23ab2f5a
13 changed files with 290 additions and 12 deletions

View File

@@ -74,6 +74,9 @@ const listPage = read('src/views/shared/ResourceListPage.vue');
assert(!listPage.includes('title="ID"'), '标准列表仍暴露数据库自增 ID');
assert(listPage.includes('ProtectedAvatarThumbnail'), '账户列表尚未接入受控头像缩略图');
assert(listPage.includes('avatarLoader.reset()'), '列表刷新未清理头像缓存和请求');
assert(listPage.includes('field.listRelationNameOnly && fieldValue(field, record)'), '关系列表字段尚未按气站端显示业务名称');
assert(listPage.includes('relationListName(field, record)'), '关系列表字段缺少服务端派生名称读取');
assert(listPage.includes('@open="openRelation(field, record)"'), '关系列表名称缺少关联详情入口');
const avatarLoader = read('src/views/shared/protected-list-avatar-loader.ts');
assert(avatarLoader.includes('MAX_CONCURRENT_REQUESTS = 6'), '头像请求并发上限未与 5173 对齐');
assert(avatarLoader.includes("new Set(['staff_account', 'user_account'])"), '头像列表资源白名单不正确');

View File

@@ -1,8 +1,14 @@
<!-- 功能描述业务名称展示关联记录并保留唯一标识的查看与复制能力版本v1.0.0 -->
<!-- 功能描述关系名称为主展示支持进入关联详情并复制完整唯一标识版本v1.1.0 -->
<template>
<div class="relation-name-text">
<a-tooltip :content="`${identityLabel}${identity}`">
<span class="relation-name">{{ displayName }}</span>
<button
v-if="clickable"
class="relation-name relation-link"
type="button"
@click.stop="emit('open')"
>{{ displayName }}</button>
<span v-else class="relation-name">{{ displayName }}</span>
</a-tooltip>
<a-tooltip :content="`复制${identityLabel}`">
<button class="copy-button" type="button" :aria-label="`复制${identityLabel} ${identity}`" @click.stop="copyIdentity">
@@ -17,14 +23,20 @@ import { Message } from '@arco-design/web-vue';
import { IconCopy } from '@arco-design/web-vue/es/icon';
import { computed } from 'vue';
const props = defineProps<{ name: string; identity: string; identityLabel: string }>();
const props = defineProps<{
name: string;
identity: string;
identityLabel?: string;
clickable?: boolean;
}>();
const emit = defineEmits<{ open: [] }>();
const displayName = computed(() => props.name || props.identity);
/** 复制完整唯一标识,并给出明确的操作反馈。 */
async function copyIdentity() {
try {
await navigator.clipboard.writeText(props.identity);
Message.success(`${props.identityLabel}已复制`);
Message.success(`${props.identityLabel ?? '唯一标识'}已复制`);
} catch {
Message.error('复制失败,请从悬浮提示中复制');
}
@@ -34,6 +46,7 @@ async function copyIdentity() {
<style scoped>
.relation-name-text { display: flex; gap: 6px; align-items: center; min-width: 0; }
.relation-name { min-width: 0; overflow: hidden; color: var(--color-text-1); text-overflow: ellipsis; white-space: nowrap; }
.relation-link { padding: 0; color: rgb(var(--primary-6)); font: inherit; text-align: left; background: transparent; border: 0; cursor: pointer; }
.copy-button { display: inline-flex; flex: 0 0 auto; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; color: rgb(var(--primary-6)); font-size: 14px; background: transparent; border: 0; border-radius: 4px; cursor: pointer; }
.copy-button:hover, .copy-button:focus-visible { background: var(--color-fill-2); outline: none; }
</style>

View File

@@ -1,4 +1,4 @@
<!-- 功能描述展示配送点标准资源列表并导航到独立记录页版本v1.2.0 -->
<!-- 功能描述展示配送点标准资源列表并导航到独立记录页版本v1.3.0 -->
<template>
<a-card :title="listTitle" :bordered="false">
<template v-if="isCredentialList" #extra>
@@ -56,8 +56,8 @@
:title="field.listLabel ?? field.label"
:width="columnWidth(field)"
:align="isProtectedListAvatarField(definition.name, field.key) ? 'center' : 'left'"
:ellipsis="!isProtectedListAvatarField(definition.name, field.key)"
:tooltip="!isProtectedListAvatarField(definition.name, field.key)"
:ellipsis="!isProtectedListAvatarField(definition.name, field.key) && !field.listRelationNameOnly"
:tooltip="!isProtectedListAvatarField(definition.name, field.key) && !field.listRelationNameOnly"
>
<template #cell="{ record }">
<ProtectedAvatarThumbnail
@@ -68,6 +68,14 @@
:refresh-key="avatarRefreshKey"
:loader="avatarLoader"
/>
<RelationNameText
v-else-if="field.listRelationNameOnly && fieldValue(field, record)"
:name="relationListName(field, record)"
:identity="fieldValue(field, record)"
:identity-label="field.listLabel ?? field.label"
:clickable="Boolean(field.relation)"
@open="openRelation(field, record)"
/>
<a-button
v-else-if="field.type === 'contract-file' && record.has_attachment"
size="mini"
@@ -147,6 +155,7 @@ import type { ResourceRow } from '@/api/resource-record-form';
import { resourceFieldLabel, type ResourceField, type ResourceUiDefinition } from '@/api/resources';
import IdentityText from '@/components/IdentityText.vue';
import ProtectedAvatarThumbnail from './ProtectedAvatarThumbnail.vue';
import RelationNameText from './RelationNameText.vue';
import {
createProtectedListAvatarLoader,
isProtectedListAvatarField,
@@ -216,6 +225,7 @@ async function ensureCredentialContext() {
/** 返回列表列宽。 */
function columnWidth(field: ResourceField) {
if (isProtectedListAvatarField(props.definition.name, field.key)) return 72;
if (field.listRelationNameOnly) return 240;
if (field.type === 'contract-file') return 120;
if (field.type === 'datetime' || field.type === 'date') return 180;
if (field.type === 'money') return 160;
@@ -249,6 +259,28 @@ function fieldValue(field: ResourceField, row: ResourceRow) {
return String(row[field.key] ?? row[`${field.key}_masked`] ?? '');
}
/** 优先读取服务端在当前合同范围内返回的关联业务名称。 */
function relationListName(field: ResourceField, row: ResourceRow) {
const displayKey = field.key.endsWith('_identity')
? `${field.key.slice(0, -9)}_display_name`
: '';
return displayKey ? String(row[displayKey] ?? '').trim() : '';
}
/** 打开关系字段对应的独立详情页。 */
function openRelation(field: ResourceField, row: ResourceRow) {
if (!field.relation) return;
const target = router.getRoutes().find(
(item) => item.meta.resource === field.relation && item.meta.recordMode === 'detail',
);
if (!target?.name) return;
return router.push({
name: target.name,
params: { identity: fieldValue(field, row) },
query: { return_to: route.fullPath },
});
}
/** 优先使用后端受控派生字段展示列表摘要,空值使用字段业务文案。 */
function displayListField(field: ResourceField, row: ResourceRow) {
if (field.listDisplayKey) {