修复气站合同用户权限与服务关系边界
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 功能:静态检查气站管理端全部标准资源是否具备独立记录页配置。
|
||||
* 版本:v1.4.0
|
||||
* 版本:v1.5.0
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -12,7 +12,10 @@ const routeSource = readFileSync(resolve(root, 'src/router/routes/modules/platfo
|
||||
const routeBuilderSource = readFileSync(resolve(root, 'src/router/routes/modules/resource-route-builder.ts'), 'utf8');
|
||||
const ruleSource = readFileSync(resolve(root, 'src/api/resource-page-rules.ts'), 'utf8');
|
||||
const listSource = readFileSync(resolve(root, 'src/views/shared/CrudListPage.vue'), 'utf8');
|
||||
const listFieldSource = readFileSync(resolve(root, 'src/views/shared/resource-list-field-display.ts'), 'utf8');
|
||||
const detailSource = readFileSync(resolve(root, 'src/views/resource/ResourceDetailContent.vue'), 'utf8');
|
||||
const recordSource = readFileSync(resolve(root, 'src/views/resource/ResourceRecordPage.vue'), 'utf8');
|
||||
const relationSource = readFileSync(resolve(root, 'src/views/resource/use-resource-relations.ts'), 'utf8');
|
||||
const displaySource = readFileSync(resolve(root, 'src/api/resource-display.ts'), 'utf8');
|
||||
const resourceSource = readFileSync(resolve(root, 'src/api/resources.ts'), 'utf8');
|
||||
const treeResources = new Set();
|
||||
@@ -96,6 +99,29 @@ if (
|
||||
) {
|
||||
fail('用户账户详情未恢复主档与配送点关系上下文');
|
||||
}
|
||||
if (
|
||||
!listFieldSource.includes('const serverName = relationDisplayName(field, row)') ||
|
||||
!relationSource.includes('if (relationDisplayName(field, values)) return []')
|
||||
) {
|
||||
fail('合同列表未优先使用服务端范围内的签约用户名称');
|
||||
}
|
||||
if (
|
||||
!ruleSource.includes('row.user_service_active === false') ||
|
||||
!recordSource.includes("action.name !== '终止合同'")
|
||||
) {
|
||||
fail('签约用户转出后未限制合同编辑和业务动作');
|
||||
}
|
||||
if (
|
||||
!ruleSource.includes('row.contract_readonly === true') ||
|
||||
!recordSource.includes('record.value.contract_readonly === true') ||
|
||||
!recordSource.includes('record.contract_readonly !== true') ||
|
||||
!recordSource.includes("definition.value.name === 'user_account'") ||
|
||||
!listSource.includes("contract_identity: String(row.identity ?? '')") ||
|
||||
!displaySource.includes("contract_readonly: '账户权限'") ||
|
||||
!displaySource.includes("return '仅查看当前合同关联资料'")
|
||||
) {
|
||||
fail('合同范围用户详情未恢复配送点上下文或未进入完整只读模式');
|
||||
}
|
||||
|
||||
const createCount = listResources.filter((item) => createModes.has(item.mode)).length;
|
||||
console.log(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 功能:统一资源列表与详情页面的字段名称、关系、金额、状态和时间展示。
|
||||
* 版本:v1.8.0
|
||||
* 版本:v1.9.0
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
import type { RecordPageMode, ResourceRow } from './resource-page-rules';
|
||||
@@ -36,6 +36,8 @@ const aliases: Record<string, string> = {
|
||||
delivery_basic_display_name: '配送点',
|
||||
staff_account_display_name: '工作人员',
|
||||
user_account_display_name: '用户',
|
||||
user_service_active: '签约用户仍属当前气站',
|
||||
contract_readonly: '账户权限',
|
||||
creator_display_name: '创建方',
|
||||
contract_display_name: '配送合同',
|
||||
assigner_name: '分配人',
|
||||
@@ -375,6 +377,8 @@ export function displayRawValue(key: string, value: unknown) {
|
||||
if (key === 'staff_account_identity' && (value == null || value === ''))
|
||||
return '暂未分配';
|
||||
if (value == null || value === '') return '-';
|
||||
if (key === 'contract_readonly' && value === true)
|
||||
return '仅查看当前合同关联资料';
|
||||
if (typeof value === 'boolean') return value ? '是' : '否';
|
||||
if (key === 'status') return recordStatusLabel(Number(value));
|
||||
if (key === 'contract_status') return contractStatusLabel(Number(value));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 功能:定义标准资源在新建、详情和编辑页面中的字段能力与动态编辑限制。
|
||||
* 版本:v1.0.0
|
||||
* 版本:v1.1.0
|
||||
*/
|
||||
import type { ResourceField, ResourceUiDefinition } from './resources';
|
||||
|
||||
@@ -325,7 +325,14 @@ export function editBlockedReason(
|
||||
context: ResourcePageContext,
|
||||
) {
|
||||
if (!definition.canEdit) return '当前资源不支持编辑';
|
||||
if (
|
||||
definition.name === 'user_account' &&
|
||||
row.contract_readonly === true
|
||||
)
|
||||
return '该用户仅因本站合同可见,不能在当前气站编辑';
|
||||
if (definition.name === 'gasorder_contract') {
|
||||
if (row.user_service_active === false)
|
||||
return '签约用户已不属于当前气站,只允许查看或终止合同';
|
||||
if (Number(row.contract_status) !== 0)
|
||||
return '只有草稿状态的合同可以编辑';
|
||||
if (![0, 1].includes(Number(row.status)))
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* 功能:封装标准资源列表、详情、新建、编辑和业务动作请求。
|
||||
* 版本:v1.1.0
|
||||
*/
|
||||
import { request, type PageResult } from './http';
|
||||
|
||||
/** 所有标准 CRUD 资源共享的调用方法。 */
|
||||
@@ -6,7 +10,10 @@ export const resourceApi = {
|
||||
const query = new URLSearchParams({ page: String(page), size: String(size), ...filters });
|
||||
return request<PageResult<T>>(`${resource}?${query.toString()}`);
|
||||
},
|
||||
detail: <T>(resource: string, identity: string) => request<T>(`${resource}/${identity}`),
|
||||
detail: <T>(resource: string, identity: string, filters: Record<string, string> = {}) => {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
return request<T>(`${resource}/${identity}${query ? `?${query}` : ''}`);
|
||||
},
|
||||
create: <T>(resource: string, data: Record<string, unknown>) => request<T>(resource, { method: 'POST', body: JSON.stringify(data) }),
|
||||
update: <T>(resource: string, identity: string, data: Record<string, unknown>) => request<T>(`${resource}/${identity}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
updateStatus: (resource: string, identity: string, status: number) => request<{ updated: boolean }>(`${resource}/${identity}/status`, { method: 'PATCH', body: JSON.stringify({ status }) }),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!--
|
||||
功能:承载气站管理端全部标准资源的新建、详情和编辑独立页面。
|
||||
版本:v1.5.0
|
||||
版本:v1.7.0
|
||||
-->
|
||||
<template>
|
||||
<div class="record-page">
|
||||
@@ -75,7 +75,7 @@
|
||||
</a-space>
|
||||
</a-card>
|
||||
<ResourceWalletSummary
|
||||
v-if="definition.walletOwnerType"
|
||||
v-if="definition.walletOwnerType && record.contract_readonly !== true"
|
||||
:wallet="wallet"
|
||||
@view="viewWallet"
|
||||
/>
|
||||
@@ -400,9 +400,20 @@ const canEditRecord = computed(
|
||||
const visibleActions = computed(() =>
|
||||
(definition.value.detailActions ?? []).filter(
|
||||
(action) =>
|
||||
!action.visibleFor ||
|
||||
action.visibleFor.values.includes(
|
||||
record.value[action.visibleFor.field] as string | number,
|
||||
!(
|
||||
definition.value.name === 'user_account' &&
|
||||
record.value.contract_readonly === true
|
||||
) &&
|
||||
!(
|
||||
definition.value.name === 'gasorder_contract' &&
|
||||
record.value.user_service_active === false &&
|
||||
action.name !== '终止合同'
|
||||
) &&
|
||||
(
|
||||
!action.visibleFor ||
|
||||
action.visibleFor.values.includes(
|
||||
record.value[action.visibleFor.field] as string | number,
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -454,6 +465,10 @@ async function initialize() {
|
||||
detail.value = await resourceApi.detail<ResourceRow>(
|
||||
definition.value.resource,
|
||||
identity.value,
|
||||
definition.value.name === 'user_account' &&
|
||||
typeof route.query.contract_identity === 'string'
|
||||
? { contract_identity: route.query.contract_identity }
|
||||
: {},
|
||||
);
|
||||
contractAttachment.load(
|
||||
definition.value.name === 'gasorder_contract'
|
||||
@@ -511,6 +526,7 @@ async function loadWallet() {
|
||||
if (
|
||||
!definition.value.walletOwnerType ||
|
||||
mode.value === 'create' ||
|
||||
record.value.contract_readonly === true ||
|
||||
!recordIdentity.value
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 功能:为资源表单和详情页加载、缓存并搜索关联资源选项。
|
||||
* 版本:v1.0.0
|
||||
* 版本:v1.1.0
|
||||
*/
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { onBeforeUnmount, reactive } from 'vue';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import type { ResourceField } from '@/api/resources';
|
||||
import type { ResourceRow } from '@/api/resource-page-rules';
|
||||
import { fieldRelationResource } from '@/api/resource-display';
|
||||
import { relationDisplayName } from '../shared/resource-list-field-display';
|
||||
import { createRelationRequestVersionGuard } from './resource-relation-linkage-policy';
|
||||
|
||||
export type ResourceRelationLoadOptions = {
|
||||
@@ -134,6 +135,8 @@ export function useResourceRelations(
|
||||
/** 补载表单或详情中已经保存的关系值,避免分页和筛选导致回显裸标识。 */
|
||||
async function ensureValues(fields: ResourceField[], values: ResourceRow) {
|
||||
const requests = fields.flatMap((field) => {
|
||||
// 服务端已在当前业务记录范围内返回名称时,不再越权补载全局关联资源。
|
||||
if (relationDisplayName(field, values)) return [];
|
||||
const resource = fieldRelationResource(field, values);
|
||||
if (!resource) return [];
|
||||
const value = values[field.key];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!--
|
||||
功能:展示标准资源列表,并将新建、详情和编辑入口导航到独立页面。
|
||||
版本:v2.3.0
|
||||
版本:v2.5.0
|
||||
-->
|
||||
<template>
|
||||
<a-card :title="listTitle" :bordered="false">
|
||||
@@ -121,7 +121,7 @@
|
||||
<a-button v-if="definition.name === 'staff_account'" size="mini" @click="viewCredentials(record)">查看资质</a-button>
|
||||
<a-button size="mini" @click="openRecord('detail', record)">详情</a-button>
|
||||
<a-button
|
||||
v-if="definition.name === 'gasorder_contract' && Number(record.contract_status) === 0"
|
||||
v-if="definition.name === 'gasorder_contract' && Number(record.contract_status) === 0 && record.user_service_active !== false"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="bindContractProduct(record)"
|
||||
@@ -427,6 +427,14 @@ function openRelation(field: ResourceField, row: ResourceRow) {
|
||||
void router.push({
|
||||
name: target.name,
|
||||
params: { identity: identityFieldValue(field, row) },
|
||||
query:
|
||||
props.definition.name === 'gasorder_contract' &&
|
||||
field.key === 'user_account_identity'
|
||||
? {
|
||||
contract_identity: String(row.identity ?? ''),
|
||||
return_to: route.fullPath,
|
||||
}
|
||||
: { return_to: route.fullPath },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 功能描述:集中处理标准资源列表字段的关系名称、唯一标识和列宽展示。
|
||||
* 版本:v1.1.0
|
||||
* 版本:v1.2.0
|
||||
*/
|
||||
import { fieldRelationResource, optionLabel } from '@/api/resource-display';
|
||||
import type { ResourceRow } from '@/api/resource-page-rules';
|
||||
@@ -106,6 +106,8 @@ export function relationListName(
|
||||
row: ResourceRow,
|
||||
relationOptions: Record<string, ResourceRow[]>,
|
||||
) {
|
||||
const serverName = relationDisplayName(field, row);
|
||||
if (serverName) return serverName;
|
||||
const identity = identityFieldValue(field, row);
|
||||
const resource = fieldRelationResource(field, row);
|
||||
const match = (relationOptions[resource] ?? []).find(
|
||||
@@ -114,6 +116,14 @@ export function relationListName(
|
||||
return match ? optionLabel(match) : '名称加载失败';
|
||||
}
|
||||
|
||||
/** 读取服务端在当前业务记录权限范围内直接返回的关联名称。 */
|
||||
export function relationDisplayName(field: ResourceField, row: ResourceRow) {
|
||||
const key = field.key.endsWith('_identity')
|
||||
? `${field.key.slice(0, -9)}_display_name`
|
||||
: '';
|
||||
return key ? String(row[key] ?? '').trim() : '';
|
||||
}
|
||||
|
||||
/** 按字段类型返回标准列表列宽。 */
|
||||
export function resourceListColumnWidth(
|
||||
definitionName: string,
|
||||
|
||||
Reference in New Issue
Block a user