修复气站用户配送点归属展示

This commit is contained in:
czl231
2026-08-18 20:52:15 +08:00
parent fd2a2b38e3
commit 7c95971a45
5 changed files with 62 additions and 7 deletions

View File

@@ -11,19 +11,30 @@ import (
"gorm.io/gorm"
)
// gasUserListItem 在用户主档上附加当前气站服务关系中的配送点归属。
type gasUserListItem struct {
models.UserAccount
DeliveryBasicID uint64 `gorm:"column:delivery_basic_id" json:"delivery_basic_id"`
}
// scopedUserList 将用户列表与当前气站唯一有效服务关系绑定,避免返回跨站归属。
func scopedUserList(databaseService *gorm.DB, gasBasicID uint64) *gorm.DB {
return common.ActiveRecords(databaseService.Model(&models.UserAccount{})).
Select("user_account.*, user_service_relation.delivery_basic_id AS delivery_basic_id").
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = user_account.id AND user_service_relation.status <> ?", common.StatusArchived).
Where("user_service_relation.gas_basic_id = ?", gasBasicID)
}
func ListUser(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
page, size := common.PageSize(ctx)
var list []models.UserAccount
var list []gasUserListItem
var total int64
query := common.ApplyKeywordFilter(ctx,
common.ActiveRecords(impl.DBService.Model(&models.UserAccount{})).
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = user_account.id AND user_service_relation.status <> ?",
common.StatusArchived).
Where("user_service_relation.gas_basic_id = ?", station.ID),
scopedUserList(impl.DBService, station.ID),
&models.UserAccount{})
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)

View File

@@ -0,0 +1,32 @@
package gas
import (
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// TestScopedUserListIncludesDeliveryRelation 验证用户列表带出当前气站服务关系的配送点主键。
func TestScopedUserListIncludesDeliveryRelation(t *testing.T) {
connection, _, err := sqlmock.New()
if err != nil {
t.Fatalf("create SQL mock: %v", err)
}
t.Cleanup(func() { _ = connection.Close() })
databaseService, err := gorm.Open(postgres.New(postgres.Config{Conn: connection}), &gorm.Config{DryRun: true})
if err != nil {
t.Fatalf("open GORM database: %v", err)
}
statement := scopedUserList(databaseService, 42).
Find(&[]gasUserListItem{}).Statement.SQL.String()
if !strings.Contains(statement, "user_service_relation.delivery_basic_id AS delivery_basic_id") {
t.Fatalf("user list must select delivery relation, got: %s", statement)
}
if !strings.Contains(statement, "user_service_relation.gas_basic_id =") {
t.Fatalf("user list must keep gas scope, got: %s", statement)
}
}

View File

@@ -14,6 +14,7 @@
- 复用 5173 的账户摘要组件和头像组合式逻辑,在新建、详情、编辑页显示头像选择与预览。
- 启用工作人员与用户列表的受保护头像缩略图,替代头像存储路径文本。
- 用户账户列表从当前气站服务关系带出配送点归属,修复配送点唯一标识显示为空。
- 从基础信息表单移除头像文本输入框,保存时仅在选择新头像或恢复默认头像后提交头像字段。
- 新增工作人员、用户头像读取路由,查询始终附加当前气站或服务关系范围。
- 将更新请求的头像字段改为可选指针,未修改头像时不更新数据库列。
@@ -22,6 +23,7 @@
- `/staff/add` 顶部显示账户摘要与头像选择区,布局和交互参考 5173。
- 工作人员与用户列表显示 32 像素圆形头像;无头像或加载失败时回退默认头像。
- 用户账户列表以“所属配送点”为列名并显示配送点业务名称,唯一标识仅用于悬停查看和复制。
- 工作人员与用户的详情、编辑页可安全读取现有头像。
- 普通资料编辑不会误清空头像;主动恢复默认头像仍会提交空值。
@@ -30,7 +32,10 @@
- `frontend/gas_admin/src/views/resource/ResourceRecordPage.vue`:启用头像摘要、过滤文本字段、接入上传载荷与未保存检测。
- `frontend/gas_admin/src/api/avatar.ts`:使用气站 API 前缀读取受保护头像。
- `frontend/gas_admin/src/views/shared/protected-list-avatar-loader.ts`:开放工作人员与用户缩略图渲染。
- `frontend/gas_admin/src/api/resources.ts`:用户账户的配送点关系列表以业务名称为主展示,并保留唯一标识复制能力。
- `frontend/gas_admin/scripts/check-resource-pages.mjs`:新增用户账户配送点名称展示的静态回归检查。
- `backend/api/internal/logic/gas/staff.go``user.go`:新增范围受限头像读取并保护头像更新语义。
- `backend/api/internal/logic/gas/user_test.go`:校验用户列表携带配送点关系且保留气站范围。
- `backend/api/internal/routers/gas_business.go``gas_test.go`:注册并校验头像路由。
## 验证结果

View File

@@ -1,6 +1,6 @@
/**
* 功能:静态检查气站管理端全部标准资源是否具备独立记录页配置。
* 版本v1.2.0
* 版本v1.3.0
*/
import { readFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
@@ -83,6 +83,13 @@ for (const marker of [
if (!listSource.includes('recordEditReason(record)')) {
fail('合同列表缺少编辑禁用原因提示');
}
if (
!resourceSource.includes(
"relation('delivery_basic_identity', '/delivery_basic', true, { label: '所属配送点', listLabel: '所属配送点', listRelationNameOnly: true, showIdentityCopy: true })",
)
) {
fail('用户账户列表未按文档以所属配送点名称为主展示');
}
const createCount = listResources.filter((item) => createModes.has(item.mode)).length;
console.log(

View File

@@ -513,7 +513,7 @@ const gasOverrides: ResourceUiDefinition[] = [
{ name: '重置密码', resource: '/staff_account/:identity/password', method: 'PUT', fields: [f('password', { required: true })] },
]),
define('staff_credential', '工作人员资质', 'writable', [relation('staff_account_identity', '/staff_account', true, { staffRelation: { roles: 'context', lockPrefilled: true, showIdentityCopy: true } }), f('credential_type', { required: true }), f('credential_no'), f('expired_at')]),
define('user_account', '用户账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('name', { required: true }), f('phone'), f('avatar'), f('real_name'), relation('delivery_basic_identity', '/delivery_basic', true)], 'list', [
define('user_account', '用户账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('name', { required: true }), f('phone'), f('avatar'), f('real_name'), relation('delivery_basic_identity', '/delivery_basic', true, { label: '所属配送点', listLabel: '所属配送点', listRelationNameOnly: true, showIdentityCopy: true })], 'list', [
{ name: '重置密码', resource: '/user_account/:identity/password', method: 'PUT', fields: [f('password', { required: true })] },
]),
define('user_address', '用户地址', 'writable', [relation('user_account_identity', '/user_account', true), f('address', { required: true }), f('longitude'), f('latitude'), f('is_default')]),