修复配送端合同与用户管理问题
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// 功能描述:实现配送点范围内的合同、合同气瓶和配送订单接口。
|
// 功能描述:实现配送点范围内的合同、合同气瓶和配送订单接口。
|
||||||
// 版本:v1.4.0。
|
// 版本:v1.4.1。
|
||||||
package delivery
|
package delivery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -74,7 +74,8 @@ func ListContract(ctx *gin.Context) {
|
|||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var list []platformgasorder.ContractPartyDisplay
|
// 空结果也初始化为空切片,确保公开响应保持 JSON 数组而不是 null。
|
||||||
|
list := make([]platformgasorder.ContractPartyDisplay, 0)
|
||||||
if err := query.Order("gasorder_contract.created_at desc").Offset((page - 1) * size).Limit(size).Scan(&list).Error; err != nil {
|
if err := query.Order("gasorder_contract.created_at desc").Offset((page - 1) * size).Limit(size).Scan(&list).Error; err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// 功能描述:验证配送合同附件列表脱敏与公开状态投影。
|
// 功能描述:验证配送合同附件列表脱敏与公开状态投影。
|
||||||
// 版本:v1.0.0。
|
// 版本:v1.1.0。
|
||||||
package delivery
|
package delivery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -44,3 +44,22 @@ func TestProtectDeliveryContractListResponse(t *testing.T) {
|
|||||||
t.Fatal("无附件的合同必须返回 has_attachment=false")
|
t.Fatal("无附件的合同必须返回 has_attachment=false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestProtectDeliveryContractListEmptyResponse 验证没有可用合同时仍返回空数组。
|
||||||
|
func TestProtectDeliveryContractListEmptyResponse(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||||
|
ctx.Request = httptest.NewRequest("GET", "/gasorder_contract?candidate=order", nil)
|
||||||
|
contracts := make([]models.GasorderContract, 0)
|
||||||
|
response, err := common.PublicResourceResponse(contracts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("构造空合同公开响应失败: %v", err)
|
||||||
|
}
|
||||||
|
protected, err := protectDeliveryContractListResponse(ctx, response, contracts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("空合同列表不应返回结构错误: %v", err)
|
||||||
|
}
|
||||||
|
if protected == nil || len(protected) != 0 {
|
||||||
|
t.Fatalf("空合同列表必须返回非 nil 空数组,实际值: %#v", protected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// 功能描述:实现配送点范围内的用户、服务关系和收货地址管理。
|
// 功能描述:实现配送点范围内的用户、服务关系和收货地址管理。
|
||||||
// 版本:v1.1.0。
|
// 版本:v1.2.0。
|
||||||
package delivery
|
package delivery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -12,9 +12,25 @@ import (
|
|||||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload"
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload"
|
||||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const deliveryUserUsernameConstraint = "idx_user_account_username"
|
||||||
|
|
||||||
|
var errDeliveryUserUsernameExists = errors.New("用户名已存在,请更换用户名")
|
||||||
|
|
||||||
|
// deliveryUserCreateError 仅转换配送点新建用户时的用户名唯一约束冲突。
|
||||||
|
// 参数:err 为用户和服务关系事务返回的错误。
|
||||||
|
// 返回值:用户名冲突返回中文提示,其他错误保持原样。
|
||||||
|
func deliveryUserCreateError(err error) error {
|
||||||
|
var postgresError *pgconn.PgError
|
||||||
|
if errors.As(err, &postgresError) && postgresError.Code == "23505" && postgresError.ConstraintName == deliveryUserUsernameConstraint {
|
||||||
|
return errDeliveryUserUsernameExists
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func userQuery(pointID uint64) *gorm.DB {
|
func userQuery(pointID uint64) *gorm.DB {
|
||||||
return common.ActiveRecords(db().Model(&models.UserAccount{})).
|
return common.ActiveRecords(db().Model(&models.UserAccount{})).
|
||||||
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = user_account.id AND user_service_relation.status <> ?",
|
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = user_account.id AND user_service_relation.status <> ?",
|
||||||
@@ -116,7 +132,7 @@ func CreateUser(ctx *gin.Context) {
|
|||||||
relation.UserAccountID = user.ID
|
relation.UserAccountID = user.ID
|
||||||
return tx.Create(&relation).Error
|
return tx.Create(&relation).Error
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
infra.Response.Error(ctx, err)
|
infra.Response.Error(ctx, deliveryUserCreateError(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.RespondCreatedResource(ctx, user)
|
common.RespondCreatedResource(ctx, user)
|
||||||
|
|||||||
33
backend/api/internal/logic/delivery/user_error_test.go
Normal file
33
backend/api/internal/logic/delivery/user_error_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// 功能描述:验证配送点新建用户的用户名冲突提示转换。
|
||||||
|
// 版本:v1.0.0。
|
||||||
|
package delivery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDeliveryUserCreateErrorUsernameConflict 验证用户名唯一约束冲突返回指定中文提示。
|
||||||
|
// 参数:t 为 Go 测试上下文。
|
||||||
|
// 返回值:无。
|
||||||
|
func TestDeliveryUserCreateErrorUsernameConflict(t *testing.T) {
|
||||||
|
databaseError := &pgconn.PgError{Code: "23505", ConstraintName: deliveryUserUsernameConstraint}
|
||||||
|
wrapped := fmt.Errorf("create delivery user: %w", databaseError)
|
||||||
|
|
||||||
|
if actual := deliveryUserCreateError(wrapped).Error(); actual != "用户名已存在,请更换用户名" {
|
||||||
|
t.Fatalf("用户名冲突提示不正确,实际为 %q", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDeliveryUserCreateErrorKeepsOtherErrors 验证其他错误不被本次局部映射改变。
|
||||||
|
// 参数:t 为 Go 测试上下文。
|
||||||
|
// 返回值:无。
|
||||||
|
func TestDeliveryUserCreateErrorKeepsOtherErrors(t *testing.T) {
|
||||||
|
original := errors.New("other error")
|
||||||
|
if actual := deliveryUserCreateError(original); !errors.Is(actual, original) {
|
||||||
|
t.Fatalf("其他错误应保持原样,实际为 %v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
29
docs/操作日志_配送点合同用户名称显示_20260831.md
Normal file
29
docs/操作日志_配送点合同用户名称显示_20260831.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 操作日志:配送点合同用户名称显示
|
||||||
|
|
||||||
|
操作时间:2026-08-31
|
||||||
|
操作类型:修改
|
||||||
|
影响模块:配送点合同列表
|
||||||
|
|
||||||
|
## 操作前状态
|
||||||
|
|
||||||
|
“用户账户”列直接显示系统唯一标识片段,与气站管理端的用户名称展示不一致。
|
||||||
|
|
||||||
|
## 具体操作
|
||||||
|
|
||||||
|
1. 关系列表字段优先读取服务端派生业务名称。
|
||||||
|
2. 复用名称、关联详情和唯一标识复制的组合展示。
|
||||||
|
3. 增加静态契约检查,防止回退为裸唯一标识。
|
||||||
|
|
||||||
|
## 操作后状态
|
||||||
|
|
||||||
|
配送点合同列表按气站端样式显示用户业务名称,并保留完整唯一标识的复制能力。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- `npm.cmd run type:check`:通过。
|
||||||
|
- `npm.cmd run resource-pages:check`:通过,详情 17、新建 9、编辑 5。
|
||||||
|
- `npm.cmd run build`:通过,2648 个模块完成生产构建。
|
||||||
|
|
||||||
|
## 风险评估
|
||||||
|
|
||||||
|
仅调整前端关系列表字段渲染,提交字段、后端响应和权限范围均不改变。
|
||||||
29
docs/操作日志_配送点用户名重复提示_20260831.md
Normal file
29
docs/操作日志_配送点用户名重复提示_20260831.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 操作日志:配送点用户名重复提示
|
||||||
|
|
||||||
|
操作时间:2026-08-31
|
||||||
|
操作类型:修改
|
||||||
|
影响模块:配送点用户管理—新建用户
|
||||||
|
|
||||||
|
## 操作前状态
|
||||||
|
|
||||||
|
重复用户名直接向页面返回 PostgreSQL 唯一约束错误及 SQLSTATE。
|
||||||
|
|
||||||
|
## 具体操作
|
||||||
|
|
||||||
|
1. 识别用户名约束 `idx_user_account_username` 的 `23505` 冲突。
|
||||||
|
2. 将该冲突转换为“用户名已存在,请更换用户名”。
|
||||||
|
3. 其他数据库错误保持原有处理,避免扩大本次变更范围。
|
||||||
|
|
||||||
|
## 操作后状态
|
||||||
|
|
||||||
|
重复创建同名用户时页面显示明确中文提示,不再显示该约束的数据库英文错误。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- `go test ./api/internal/logic/delivery`:通过。
|
||||||
|
- 已覆盖包装后的 PostgreSQL `23505` 用户名约束冲突。
|
||||||
|
- 已验证其他错误保持原样。
|
||||||
|
|
||||||
|
## 风险评估
|
||||||
|
|
||||||
|
仅改变一个已知唯一约束冲突的提示文本,不影响成功创建路径与其他错误类型。
|
||||||
30
docs/操作日志_配送点订单合同空列表修复_20260831.md
Normal file
30
docs/操作日志_配送点订单合同空列表修复_20260831.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 操作日志:配送点订单合同空列表修复
|
||||||
|
|
||||||
|
操作时间:2026-08-31
|
||||||
|
操作类型:修改
|
||||||
|
影响模块:配送点订单创建、配送合同候选列表
|
||||||
|
|
||||||
|
## 操作前状态
|
||||||
|
|
||||||
|
没有可履约合同时,合同查询结果为 nil 切片,公开响应序列化成 `null`,随后被列表结构校验拒绝。
|
||||||
|
|
||||||
|
## 具体操作
|
||||||
|
|
||||||
|
1. 将合同查询接收容器初始化为空切片。
|
||||||
|
2. 增加零条合同的回归测试,要求返回非 nil 空数组。
|
||||||
|
3. 保持接口路径、筛选条件和响应字段不变。
|
||||||
|
|
||||||
|
## 操作后状态
|
||||||
|
|
||||||
|
无可用合同时接口正常返回 `total: 0`、`list: []`,页面只显示无可用合同提示。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- `go test ./api/internal/logic/delivery`:通过。
|
||||||
|
- 新增空合同列表用例,确认公开响应为非 nil 空数组。
|
||||||
|
- 后端已重新构建并重启,12426 端口监听正常。
|
||||||
|
- 浏览器会话的原登录令牌已失效并跳转登录页,登录后页面验证待执行。
|
||||||
|
|
||||||
|
## 风险评估
|
||||||
|
|
||||||
|
变更仅影响零条记录的序列化形式;有数据列表和合同筛选逻辑不变,兼容风险低。
|
||||||
24
docs/项目文档_配送点合同用户名称显示_v1.0.md
Normal file
24
docs/项目文档_配送点合同用户名称显示_v1.0.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 项目文档:配送点合同用户名称显示 v1.0
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
配送点合同列表的“用户账户”列与气站管理端保持一致,以用户业务名称为主展示,不再把系统唯一标识作为主文本。
|
||||||
|
|
||||||
|
## 核心文件说明
|
||||||
|
|
||||||
|
- `frontend/delivery_admin/src/views/shared/ResourceListPage.vue`:渲染服务端返回的 `user_account_display_name`,并保留关联详情入口和唯一标识复制能力。
|
||||||
|
- `frontend/delivery_admin/src/views/shared/RelationNameText.vue`:支持可点击的关系名称展示。
|
||||||
|
- `frontend/delivery_admin/scripts/check-resource-pages.mjs`:增加关系名称展示的静态契约检查。
|
||||||
|
|
||||||
|
## 行为变化
|
||||||
|
|
||||||
|
- 修改前:合同列表显示用户账户唯一标识片段。
|
||||||
|
- 修改后:合同列表显示用户业务名称,例如 `ccc`、`xc`、`cyy`;名称可进入用户详情,旁边按钮可复制完整唯一标识。
|
||||||
|
|
||||||
|
## 接口兼容性
|
||||||
|
|
||||||
|
复用现有合同列表响应中的 `user_account_display_name`,不修改后端接口和数据库。
|
||||||
|
|
||||||
|
## 测试方法
|
||||||
|
|
||||||
|
在 `frontend/delivery_admin` 目录执行类型检查、资源页面契约检查和生产构建。
|
||||||
22
docs/项目文档_配送点用户名重复提示_v1.0.md
Normal file
22
docs/项目文档_配送点用户名重复提示_v1.0.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 项目文档:配送点用户名重复提示 v1.0
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
配送点管理端新建用户时,将用户名唯一约束冲突转换为可理解的中文提示。
|
||||||
|
|
||||||
|
## 核心文件说明
|
||||||
|
|
||||||
|
- `backend/api/internal/logic/delivery/user.go`:识别 `idx_user_account_username` 唯一约束冲突并返回“用户名已存在,请更换用户名”。
|
||||||
|
- `backend/api/internal/logic/delivery/user_error_test.go`:验证已知冲突提示与其他错误保持原样。
|
||||||
|
|
||||||
|
## 变更范围
|
||||||
|
|
||||||
|
仅修改配送点管理端新建用户的重复用户名提示,不修改数据库约束、用户创建流程、字段校验或其他错误处理。
|
||||||
|
|
||||||
|
## 测试方法
|
||||||
|
|
||||||
|
在 `backend` 目录执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
go test ./api/internal/logic/delivery
|
||||||
|
```
|
||||||
27
docs/项目文档_配送点订单合同空列表修复_v1.0.md
Normal file
27
docs/项目文档_配送点订单合同空列表修复_v1.0.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 项目文档:配送点订单合同空列表修复 v1.0
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
修复配送点管理端创建配送订单时,当前没有可履约合同时错误提示“配送合同列表响应结构无效”的问题。
|
||||||
|
|
||||||
|
## 核心文件说明
|
||||||
|
|
||||||
|
- `backend/api/internal/logic/delivery/order.go`:查询配送合同候选项;空结果初始化为非 nil 空切片,保证接口输出 `list: []`。
|
||||||
|
- `backend/api/internal/logic/delivery/order_contract_attachment_test.go`:覆盖合同空列表公开响应的回归测试。
|
||||||
|
|
||||||
|
## 行为变化
|
||||||
|
|
||||||
|
- 修复前:零条合同被序列化为 `null`,后端响应结构校验失败。
|
||||||
|
- 修复后:零条合同返回空数组,前端正常显示“暂无可下单的生效合同”。
|
||||||
|
|
||||||
|
## 接口兼容性
|
||||||
|
|
||||||
|
接口地址和字段保持不变,仅将异常的 `list: null` 规范为 `list: []`,兼容现有调用方。
|
||||||
|
|
||||||
|
## 测试方法
|
||||||
|
|
||||||
|
在 `backend` 目录执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
go test ./api/internal/logic/delivery
|
||||||
|
```
|
||||||
@@ -74,6 +74,9 @@ const listPage = read('src/views/shared/ResourceListPage.vue');
|
|||||||
assert(!listPage.includes('title="ID"'), '标准列表仍暴露数据库自增 ID');
|
assert(!listPage.includes('title="ID"'), '标准列表仍暴露数据库自增 ID');
|
||||||
assert(listPage.includes('ProtectedAvatarThumbnail'), '账户列表尚未接入受控头像缩略图');
|
assert(listPage.includes('ProtectedAvatarThumbnail'), '账户列表尚未接入受控头像缩略图');
|
||||||
assert(listPage.includes('avatarLoader.reset()'), '列表刷新未清理头像缓存和请求');
|
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');
|
const avatarLoader = read('src/views/shared/protected-list-avatar-loader.ts');
|
||||||
assert(avatarLoader.includes('MAX_CONCURRENT_REQUESTS = 6'), '头像请求并发上限未与 5173 对齐');
|
assert(avatarLoader.includes('MAX_CONCURRENT_REQUESTS = 6'), '头像请求并发上限未与 5173 对齐');
|
||||||
assert(avatarLoader.includes("new Set(['staff_account', 'user_account'])"), '头像列表资源白名单不正确');
|
assert(avatarLoader.includes("new Set(['staff_account', 'user_account'])"), '头像列表资源白名单不正确');
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<!-- 功能描述:以业务名称展示关联记录,并保留唯一标识的查看与复制能力。版本:v1.0.0。 -->
|
<!-- 功能描述:以关系名称为主展示,支持进入关联详情并复制完整唯一标识。版本:v1.1.0。 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="relation-name-text">
|
<div class="relation-name-text">
|
||||||
<a-tooltip :content="`${identityLabel}:${identity}`">
|
<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>
|
||||||
<a-tooltip :content="`复制${identityLabel}`">
|
<a-tooltip :content="`复制${identityLabel}`">
|
||||||
<button class="copy-button" type="button" :aria-label="`复制${identityLabel} ${identity}`" @click.stop="copyIdentity">
|
<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 { IconCopy } from '@arco-design/web-vue/es/icon';
|
||||||
import { computed } from 'vue';
|
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);
|
const displayName = computed(() => props.name || props.identity);
|
||||||
|
|
||||||
/** 复制完整唯一标识,并给出明确的操作反馈。 */
|
/** 复制完整唯一标识,并给出明确的操作反馈。 */
|
||||||
async function copyIdentity() {
|
async function copyIdentity() {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(props.identity);
|
await navigator.clipboard.writeText(props.identity);
|
||||||
Message.success(`${props.identityLabel}已复制`);
|
Message.success(`${props.identityLabel ?? '唯一标识'}已复制`);
|
||||||
} catch {
|
} catch {
|
||||||
Message.error('复制失败,请从悬浮提示中复制');
|
Message.error('复制失败,请从悬浮提示中复制');
|
||||||
}
|
}
|
||||||
@@ -34,6 +46,7 @@ async function copyIdentity() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.relation-name-text { display: flex; gap: 6px; align-items: center; min-width: 0; }
|
.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-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 { 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; }
|
.copy-button:hover, .copy-button:focus-visible { background: var(--color-fill-2); outline: none; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 功能描述:展示配送点标准资源列表并导航到独立记录页。版本:v1.2.0。 -->
|
<!-- 功能描述:展示配送点标准资源列表并导航到独立记录页。版本:v1.3.0。 -->
|
||||||
<template>
|
<template>
|
||||||
<a-card :title="listTitle" :bordered="false">
|
<a-card :title="listTitle" :bordered="false">
|
||||||
<template v-if="isCredentialList" #extra>
|
<template v-if="isCredentialList" #extra>
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
:title="field.listLabel ?? field.label"
|
:title="field.listLabel ?? field.label"
|
||||||
:width="columnWidth(field)"
|
:width="columnWidth(field)"
|
||||||
:align="isProtectedListAvatarField(definition.name, field.key) ? 'center' : 'left'"
|
:align="isProtectedListAvatarField(definition.name, field.key) ? 'center' : 'left'"
|
||||||
:ellipsis="!isProtectedListAvatarField(definition.name, field.key)"
|
:ellipsis="!isProtectedListAvatarField(definition.name, field.key) && !field.listRelationNameOnly"
|
||||||
:tooltip="!isProtectedListAvatarField(definition.name, field.key)"
|
:tooltip="!isProtectedListAvatarField(definition.name, field.key) && !field.listRelationNameOnly"
|
||||||
>
|
>
|
||||||
<template #cell="{ record }">
|
<template #cell="{ record }">
|
||||||
<ProtectedAvatarThumbnail
|
<ProtectedAvatarThumbnail
|
||||||
@@ -68,6 +68,14 @@
|
|||||||
:refresh-key="avatarRefreshKey"
|
:refresh-key="avatarRefreshKey"
|
||||||
:loader="avatarLoader"
|
: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
|
<a-button
|
||||||
v-else-if="field.type === 'contract-file' && record.has_attachment"
|
v-else-if="field.type === 'contract-file' && record.has_attachment"
|
||||||
size="mini"
|
size="mini"
|
||||||
@@ -147,6 +155,7 @@ import type { ResourceRow } from '@/api/resource-record-form';
|
|||||||
import { resourceFieldLabel, type ResourceField, type ResourceUiDefinition } from '@/api/resources';
|
import { resourceFieldLabel, type ResourceField, type ResourceUiDefinition } from '@/api/resources';
|
||||||
import IdentityText from '@/components/IdentityText.vue';
|
import IdentityText from '@/components/IdentityText.vue';
|
||||||
import ProtectedAvatarThumbnail from './ProtectedAvatarThumbnail.vue';
|
import ProtectedAvatarThumbnail from './ProtectedAvatarThumbnail.vue';
|
||||||
|
import RelationNameText from './RelationNameText.vue';
|
||||||
import {
|
import {
|
||||||
createProtectedListAvatarLoader,
|
createProtectedListAvatarLoader,
|
||||||
isProtectedListAvatarField,
|
isProtectedListAvatarField,
|
||||||
@@ -216,6 +225,7 @@ async function ensureCredentialContext() {
|
|||||||
/** 返回列表列宽。 */
|
/** 返回列表列宽。 */
|
||||||
function columnWidth(field: ResourceField) {
|
function columnWidth(field: ResourceField) {
|
||||||
if (isProtectedListAvatarField(props.definition.name, field.key)) return 72;
|
if (isProtectedListAvatarField(props.definition.name, field.key)) return 72;
|
||||||
|
if (field.listRelationNameOnly) return 240;
|
||||||
if (field.type === 'contract-file') return 120;
|
if (field.type === 'contract-file') return 120;
|
||||||
if (field.type === 'datetime' || field.type === 'date') return 180;
|
if (field.type === 'datetime' || field.type === 'date') return 180;
|
||||||
if (field.type === 'money') return 160;
|
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`] ?? '');
|
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) {
|
function displayListField(field: ResourceField, row: ResourceRow) {
|
||||||
if (field.listDisplayKey) {
|
if (field.listDisplayKey) {
|
||||||
|
|||||||
Reference in New Issue
Block a user