Commit 72f883d9 authored by Andy-bubu's avatar Andy-bubu

fix: align application and detail permissions

parent fd8348b3
...@@ -140,8 +140,9 @@ CSS,并用显式 `@source` 仅扫描入口、组件、页面和 wrapper;`src ...@@ -140,8 +140,9 @@ CSS,并用显式 `@source` 仅扫描入口、组件、页面和 wrapper;`src
```text ```text
UserContext UserContext
-> user.applications[name = audit_project_manager] -> user.applications[name = audit_project_manager_new]
-> Grid viewAuth / field auth(Detail View 只负责编排与 shortTitle) -> Grid viewAuth
-> active Detail auth/disableColumns(未配置时整体回退 active Grid)
Navbar notification badge Navbar notification badge
-> platform notice APIs -> /notifications -> base-aware internal navigation -> platform notice APIs -> /notifications -> base-aware internal navigation
...@@ -154,9 +155,11 @@ Editable file field ...@@ -154,9 +155,11 @@ Editable file field
``` ```
Subject/View 资源必须先加载并完成 Grid View 授权过滤;只有存在可访问 View 才允许查询记录。 Subject/View 资源必须先加载并完成 Grid View 授权过滤;只有存在可访问 View 才允许查询记录。
`RecordDetailDrawer` 接收激活 Grid View 作为唯一 `authorizationView`,激活 Detail View 只决定字段 `RecordDetailDrawer` 接收激活 Grid View 作为 `authorizationView`,并通过
布局、条件和记录标题是否显示。通知与材料服务只封装既有接口参数,不拥有 Shell、记录更新或 `GET /subject/:subjectName/view/:viewName` 获取完整 Detail 配置。当前 Detail 有任一有效
业务状态流。 `auth/disableColumns` 时使用 Detail 整套权限;完全未配置时才整体回退 Grid。Detail `columns`
只决定展示范围和顺序,权限来源不会合并。通知与材料服务只封装既有接口参数,不拥有 Shell、
记录更新或业务状态流。
通知路由解析以 `ROUTE_TITLES` 的真实注册路径为本应用站内白名单,并只剥离 通知路由解析以 `ROUTE_TITLES` 的真实注册路径为本应用站内白名单,并只剥离
`/audit_project_manager_new` 自身 base;不能把其它应用的裸路径交给当前 Umi Router。 `/audit_project_manager_new` 自身 base;不能把其它应用的裸路径交给当前 Umi Router。
......
...@@ -97,7 +97,8 @@ ...@@ -97,7 +97,8 @@
- [x] Grid `shortTitle` 按激活 View 生效,title 仍参与搜索且业务入口保留 - [x] Grid `shortTitle` 按激活 View 生效,title 仍参与搜索且业务入口保留
- [x] Detail `shortTitle` 随激活 Detail View 即时更新 - [x] Detail `shortTitle` 随激活 Detail View 即时更新
- [x] 权限只读取 `audit_project_manager` 应用项,缺省拒绝且管理旁路正确 - [x] 权限只读取稳定应用 `audit_project_manager_new`,不合并旧入口或其它应用授权
- [x] Detail 完整配置优先,未配置时整体回退 Grid,权限不合并且编辑按钮只检查当前 Detail
- [x] 无可访问 Grid View 时不查询记录、不建 fallback、不显示新增/批量/行操作 - [x] 无可访问 Grid View 时不查询记录、不建 fallback、不显示新增/批量/行操作
- [x] 只有一个可访问 Grid View 时隐藏 Tabs 轨道 - [x] 只有一个可访问 Grid View 时隐藏 Tabs 轨道
- [x] `/notifications`、Navbar Badge、消息/待办、全部/未读和动态操作完成 - [x] `/notifications`、Navbar Badge、消息/待办、全部/未读和动态操作完成
......
...@@ -97,8 +97,11 @@ Portal、错误/空/加载、键盘 focus 和移动响应式。合入前运行 ` ...@@ -97,8 +97,11 @@ Portal、错误/空/加载、键盘 focus 和移动响应式。合入前运行 `
- `settings.shortTitle === true` 只隐藏当前 View 的 `title` 展示;title 数据、关键词搜索、排序、 - `settings.shortTitle === true` 只隐藏当前 View 的 `title` 展示;title 数据、关键词搜索、排序、
保存与详情入口不得丢失。Detail 标题显示只跟随当前激活 Detail View。 保存与详情入口不得丢失。Detail 标题显示只跟随当前激活 Detail View。
- View/字段权限只能读取正式应用 `audit_project_manager` 在 `user.applications[]` 中的授权项。 - View/字段权限只能读取当前稳定应用 `audit_project_manager_new` 在 `user.applications[]` 中的授权项。
系统管理员及该应用“管理”角色可旁路;普通用户缺失配置时默认拒绝,`disableColumns` 不旁路。 系统管理员及该应用“管理”角色可旁路;普通用户缺失配置时默认拒绝,`disableColumns` 不旁路。
- Grid `viewAuth` 只控制 Tab 可见性。详情字段权限优先使用当前完整 Detail View 的整套
`auth/disableColumns`;两项都没有有效配置或不存在实际 Detail 时,整体回退当前 Grid,禁止
合并或逐字段回退。编辑按钮只检查当前 Detail 展示字段。
- Navbar 通知使用 HeroUI `Badge.Anchor + Badge + Button + Tooltip`,位于主题按钮左侧;通知页面 - Navbar 通知使用 HeroUI `Badge.Anchor + Badge + Button + Tooltip`,位于主题按钮左侧;通知页面
使用原平台接口,正文按纯文本呈现,站内路径剥离 `/audit_project_manager_new` 后交给 Umi。 使用原平台接口,正文按纯文本呈现,站内路径剥离 `/audit_project_manager_new` 后交给 Umi。
- editable file 字段统一提供“材料中心 / 本地上传”。选择材料只写文件快照;本地文件先上传到 - editable file 字段统一提供“材料中心 / 本地上传”。选择材料只写文件快照;本地文件先上传到
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
- 分支:`codex/ui-ux-xinyuan-migration` - 分支:`codex/ui-ux-xinyuan-migration`
- 唯一实现基准:`problem_rectification` - 唯一实现基准:`problem_rectification`
- 应用基路径:`/audit_project_manager_new/` - 应用基路径:`/audit_project_manager_new/`
- 本轮启动命令:`npm run dev -- --port 8000` - 本轮启动命令:`npm run dev -- --port 8003`(Umi 检测端口占用后选择 `8007`)
- 目标验收地址:`http://127.0.0.1:8000/audit_project_manager_new/` - 目标验收地址:`http://127.0.0.1:8007/audit_project_manager_new/`
- 基准验收地址:`http://127.0.0.1:8001/problem_rectification_new/` - 基准验收地址:`http://127.0.0.1:8001/problem_rectification_new/`
迁移前已启动未修改项目并记录业务基线。原登录页为蓝色营销式左右分栏,Shell、表格、 迁移前已启动未修改项目并记录业务基线。原登录页为蓝色营销式左右分栏,Shell、表格、
...@@ -263,18 +263,19 @@ HeroUI Context。`@heroui/styles@3.2.4` 的两个 pnpm peer 虚拟目录仅分 ...@@ -263,18 +263,19 @@ HeroUI Context。`@heroui/styles@3.2.4` 的两个 pnpm peer 虚拟目录仅分
- 功能白名单判定:本应用为“审计项目管理”,属于完整功能应用,实施 Grid/Detail - 功能白名单判定:本应用为“审计项目管理”,属于完整功能应用,实施 Grid/Detail
`shortTitle`、视图权限、消息通知、文件与审计材料中心五项功能。 `shortTitle`、视图权限、消息通知、文件与审计材料中心五项功能。
- 运行标识:Subject 为 `audit_project`,正式应用授权名与材料 `source_app` 为 - 运行标识:Subject 为 `audit_project`,当前稳定应用标识、应用授权名与材料 `source_app` 均为
`audit_project_manager`;Umi 路由 base 为 `/audit_project_manager_new/`。站点配置同时存在 `audit_project_manager_new`,Umi 路由 base 由该标识生成。站点配置同时保留旧入口
演示入口 `audit_project_manager_new`,但它不作为权限或材料来源标识。 `audit_project_manager`,但当前应用不得读取或合并旧入口授权。
- 登录样本:Andy 的 `type` 为 `admin`、`applications` 为空,只能验证系统管理员旁路;普通 - 登录样本:Andy 的 `type` 为 `admin`、`applications` 为空,只能验证系统管理员旁路;普通
用户必须仅从 `user.applications[]` 中名称为 `audit_project_manager` 的项目读取 group、role 用户必须仅从 `user.applications[]` 中名称为 `audit_project_manager_new` 的项目读取 group、role
和 permission,禁止合并顶层或其他应用授权。 和 permission,禁止合并顶层或其他应用授权。
- View 现状:七个 Grid View 均配置 `shortTitle: true`;“计划”额外配置 group - View 现状:七个 Grid View 均配置 `shortTitle: true`;“计划”额外配置 group
`viewAuth` 与字段 `auth`。页面此前通过常量无条件隐藏 `title`,搜索也随之丢失 title; `viewAuth` 与字段 `auth`。页面此前通过常量无条件隐藏 `title`,搜索也随之丢失 title;
View 权限缺省允许、无权时创建 fallback,并在鉴权前查询数据,均不符合基准。 View 权限缺省允许、无权时创建 fallback,并在鉴权前查询数据,均不符合基准。
- Detail 现状:详情视图未统一配置 `shortTitle`;抽屉始终显示记录标题,字段权限同时受 Detail - Detail 原始实现只读取 View 摘要,并合并 Detail 与 Grid 字段权限,编辑按钮还会遍历其它 Detail。
View 影响。目标行为应随当前激活 Detail View 即时切换标题显示,但字段权限只读取激活 Grid 现已逐个读取完整 View 配置;当前 Detail 存在有效 `auth/disableColumns` 时整套优先,两项都未
View,`disableColumns` 永远只读。 配置或不存在实际 Detail 时才整体回退激活 Grid。`disableColumns` 永远只读,编辑按钮只检查
当前 Detail 的展示字段。
- 通知现状:原 `DataService` 已提供 `queryUserNotice`、`readUserNotice`、 - 通知现状:原 `DataService` 已提供 `queryUserNotice`、`readUserNotice`、
`unReadNoticeCount` 和 `exacteActionFunction`,但没有独立页面、路由或 Navbar 入口。 `unReadNoticeCount` 和 `exacteActionFunction`,但没有独立页面、路由或 Navbar 入口。
- 文件现状:editable file 字段只执行 `/upload` 并立即写入详情草稿,没有材料中心选择,也没有 - 文件现状:editable file 字段只执行 `/upload` 并立即写入详情草稿,没有材料中心选择,也没有
...@@ -393,6 +394,27 @@ rg -n 'hp_[A-Za-z0-9]+' . --glob '!node_modules/**' --glob '!dist/**' --glob '!. ...@@ -393,6 +394,27 @@ rg -n 'hp_[A-Za-z0-9]+' . --glob '!node_modules/**' --glob '!dist/**' --glob '!.
返回 200,标题正确,页面级横向溢出均为 0,捕获结果为 `0 errors / 0 warnings`;此前为验证 返回 200,标题正确,页面级横向溢出均为 0,捕获结果为 `0 errors / 0 warnings`;此前为验证
材料失败重试而故意模拟的 500 日志未计入本次干净结果。 材料失败重试而故意模拟的 500 日志未计入本次干净结果。
### 2026-08-26 权限归属与 Detail 优先级修复
- 以问题整改提交 `c047ec4` 和 `6763c26` 为代码基准复核。此前目标应用把当前
`/audit_project_manager_new/` 入口的 View 权限与材料来源错误绑定到旧
`audit_project_manager`。现新增稳定 `APPLICATION_NAME = "audit_project_manager_new"`,并由它
统一生成 base、应用切换当前项、授权应用名和材料 `source_app`;旧入口及其它应用的
groups/roles/permissions 不再参与当前应用权限。
- 详情 Hook 此前只使用 `/subject/:subjectName/view` 返回的摘要。现按摘要中的 View name 调用
`GET /subject/:subjectName/view/:viewName` 取得完整配置,并在单个请求失败时仅回退该 View 摘要,
不影响其它 View 加载。
- 当前 Detail 存在任一有效 `auth` 或 `disableColumns` 时使用 Detail 整套字段权限;两项都没有
配置或不存在实际 Detail 时才整体使用激活 Grid。两套权限不合并,Detail 已展示但未配置 auth
的字段对普通用户只读;`hasEditableFields` 仅检查当前 Detail 展示字段,切换 Tab 后立即更新。
- `$playwright-cli` 专项 fixture 只存在浏览器会话,未请求真实写接口。普通 Detail 授权用户在
自带权限 Detail 可编辑、切到无配置 Detail 后因缺少 Grid 字段授权变为只读;普通 Grid 授权
用户在前者不能逐字段借用 Grid,切到后者后才可编辑。完整 Detail endpoint 均实际发起请求。
- 只有旧 `audit_project_manager` 与其它应用“管理”角色的用户显示“暂无可访问的视图”,
`/record/query` 为 0;当前 `audit_project_manager_new` 的“管理”角色和系统管理员旁路正常。
管理员编辑态下普通授权字段显示输入框,`disableColumns` 字段仍为只读文本。页面横向溢出为 0,
控制台为 `0 errors / 0 warnings`。
### 2026-08-24 DataGrid 长文本边界复核 ### 2026-08-24 DataGrid 长文本边界复核
- 全局三个 HeroUI Pro DataGrid 消费入口均复用 `DataTable`:主业务列表、动态表单数据表和 - 全局三个 HeroUI Pro DataGrid 消费入口均复用 `DataTable`:主业务列表、动态表单数据表和
......
import '@/tailwind.css'; import '@/tailwind.css';
import { defineApp } from 'umi'; import { defineApp } from 'umi';
import { APPLICATION_BASE_PATH, THEME_STORAGE_KEY } from '@/config/application'; import {
APPLICATION_BASE_PATH,
APPLICATION_NAME,
THEME_STORAGE_KEY,
} from '@/config/application';
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
const storedTheme = window.localStorage?.getItem?.(THEME_STORAGE_KEY); const storedTheme = window.localStorage?.getItem?.(THEME_STORAGE_KEY);
...@@ -28,7 +32,7 @@ if ( ...@@ -28,7 +32,7 @@ if (
export async function getInitialState() { export async function getInitialState() {
return { return {
name: 'audit_project_manager', name: APPLICATION_NAME,
}; };
} }
......
export const APPLICATION_DISPLAY_NAME = '审计项目管理'; export const APPLICATION_DISPLAY_NAME = '审计项目管理';
export const APPLICATION_BASE_PATH = '/audit_project_manager_new/'; export const APPLICATION_NAME = 'audit_project_manager_new';
export const AUTHORIZATION_APPLICATION_NAME = 'audit_project_manager'; export const APPLICATION_BASE_PATH = `/${APPLICATION_NAME}/`;
export const SOURCE_APPLICATION_NAME = 'audit_project_manager'; export const AUTHORIZATION_APPLICATION_NAME = APPLICATION_NAME;
export const SOURCE_APPLICATION_NAME = APPLICATION_NAME;
export const THEME_STORAGE_KEY = 'xinyuan_color_theme'; export const THEME_STORAGE_KEY = 'xinyuan_color_theme';
export const ROUTE_TITLES = Object.freeze({ export const ROUTE_TITLES = Object.freeze({
......
...@@ -92,9 +92,9 @@ export default function SubjectViewContent({ ...@@ -92,9 +92,9 @@ export default function SubjectViewContent({
mode === 'view' && mode === 'view' &&
!minimal && !minimal &&
(fieldType === 'form' || isReferenceFieldType(fieldType)); (fieldType === 'form' || isReferenceFieldType(fieldType));
const fieldDisabled = const fieldDisabled = isFieldDisabled
isFieldDisabled?.(field.field, view) || ? isFieldDisabled(field.field, view)
isFieldDisabledByView(field.field, view); : isFieldDisabledByView(field.field, view);
const fieldContent = ( const fieldContent = (
<> <>
......
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { import {
getSubjectView, getSubjectView,
getSubjectViewDetail,
loadRecord, loadRecord,
loadSubject, loadSubject,
updateRecord, updateRecord,
...@@ -97,9 +98,27 @@ export function useSubjectRecord( ...@@ -97,9 +98,27 @@ export function useSubjectRecord(
try { try {
const record = await loadRecord(recordName); const record = await loadRecord(recordName);
const resolvedSubjectName = subjectName || record?.__t || record?.subject?.name || record?.subject; const resolvedSubjectName = subjectName || record?.__t || record?.subject?.name || record?.subject;
const [subjectData, viewsData] = resolvedSubjectName const [subjectData, viewSummaries] = resolvedSubjectName
? await Promise.all([loadSubject(resolvedSubjectName), getSubjectView(resolvedSubjectName)]) ? await Promise.all([loadSubject(resolvedSubjectName), getSubjectView(resolvedSubjectName)])
: [null, []]; : [null, []];
const viewsData = await Promise.all(
(Array.isArray(viewSummaries) ? viewSummaries : []).map(async (view) => {
if (!view?.name || !resolvedSubjectName) {
return view;
}
try {
const detail = await getSubjectViewDetail(resolvedSubjectName, view.name);
return {
...view,
...detail,
settings: detail?.settings || view?.settings || {},
};
} catch (error) {
return view;
}
}),
);
if (!cancelled && requestIdRef.current === requestId) { if (!cancelled && requestIdRef.current === requestId) {
setRemoteRecord(record); setRemoteRecord(record);
......
...@@ -18,7 +18,7 @@ import { ...@@ -18,7 +18,7 @@ import {
buildViewColumns, buildViewColumns,
checkViewCondition, checkViewCondition,
getFieldValue, getFieldValue,
isFieldDisabledByView, hasFieldAuthorizationConfiguration,
setFieldValue, setFieldValue,
shouldHideViewTitleColumn, shouldHideViewTitleColumn,
} from '@/utils/subjectView'; } from '@/utils/subjectView';
...@@ -265,10 +265,16 @@ const RecordDetailDrawer = (props, ref) => { ...@@ -265,10 +265,16 @@ const RecordDetailDrawer = (props, ref) => {
), ),
[disabledFields], [disabledFields],
); );
const isDrawerFieldDisabled = (fieldPath, currentView) => const isDrawerFieldDisabled = (fieldPath, currentView) => {
disabledFieldSet.has(fieldPath) || const effectiveAuthorizationView = hasFieldAuthorizationConfiguration(currentView)
isFieldDisabled(fieldPath, currentView) || ? currentView
isFieldDisabledByView(fieldPath, authorizationView); : authorizationView || currentView;
return (
disabledFieldSet.has(fieldPath) ||
isFieldDisabled(fieldPath, effectiveAuthorizationView)
);
};
const refreshRecord = ({ selectLastTab = false } = {}) => { const refreshRecord = ({ selectLastTab = false } = {}) => {
if (selectLastTab || autoSwitchToLastTab) { if (selectLastTab || autoSwitchToLastTab) {
...@@ -319,12 +325,10 @@ const RecordDetailDrawer = (props, ref) => { ...@@ -319,12 +325,10 @@ const RecordDetailDrawer = (props, ref) => {
!error && !error &&
!effectiveReadonly && !effectiveReadonly &&
!minimalContent && !minimalContent &&
enabledDetailViews.some((view) => buildViewColumns(activeView, subject).some(
buildViewColumns(view, subject).some( (field) =>
(field) => !NON_EDITABLE_FIELD_TYPES.has(field.fieldDef?.type) &&
!NON_EDITABLE_FIELD_TYPES.has(field.fieldDef?.type) && !isDrawerFieldDisabled(field.field, activeView),
!isDrawerFieldDisabled(field.field, view),
),
); );
const hasDirtyFields = Object.keys(dirtyFields).length > 0; const hasDirtyFields = Object.keys(dirtyFields).length > 0;
const hasValidationErrors = Object.values(validationErrors).some(Boolean); const hasValidationErrors = Object.values(validationErrors).some(Boolean);
......
...@@ -2,6 +2,7 @@ import xinyuanLogo from '@/assets/xy-logo-red.png'; ...@@ -2,6 +2,7 @@ import xinyuanLogo from '@/assets/xy-logo-red.png';
import { import {
APPLICATION_BASE_PATH, APPLICATION_BASE_PATH,
APPLICATION_DISPLAY_NAME, APPLICATION_DISPLAY_NAME,
APPLICATION_NAME,
} from '@/config/application'; } from '@/config/application';
import { getApplications } from '@/services/DataService'; import { getApplications } from '@/services/DataService';
import { ExclamationTriangleIcon } from '@/components/AppIcons'; import { ExclamationTriangleIcon } from '@/components/AppIcons';
...@@ -18,7 +19,7 @@ import { ...@@ -18,7 +19,7 @@ import {
} from '@heroui/react'; } from '@heroui/react';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
const CURRENT_APPLICATION_NAME = APPLICATION_BASE_PATH.replace(/^\/+|\/+$/g, ''); const CURRENT_APPLICATION_NAME = APPLICATION_NAME;
function unwrapApplications(response) { function unwrapApplications(response) {
const candidates = [ const candidates = [
......
...@@ -126,12 +126,14 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template` ...@@ -126,12 +126,14 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template`
- 登录页首次打开时调用 `GET /auth/captcha` 获取验证码图片和 token;登录请求在既有账号、 - 登录页首次打开时调用 `GET /auth/captcha` 获取验证码图片和 token;登录请求在既有账号、
加密密码与 `type=password` 基础上提交 `captcha` 和 `token`。用户可点击验证码刷新;登录 加密密码与 `type=password` 基础上提交 `captcha` 和 `token`。用户可点击验证码刷新;登录
失败时清空验证码输入并重新获取验证码,成功后的 token 保存和 redirect 流程保持不变。 失败时清空验证码输入并重新获取验证码,成功后的 token 保存和 redirect 流程保持不变。
- Grid View 的 `settings.viewAuth` 控制视图可见性,当前激活 Grid View 的 `settings.auth` 控制 - Grid View 的 `settings.viewAuth` 只控制视图可见性。详情打开时逐个读取完整 Detail 配置;当前
详情字段编辑权限,`settings.disableColumns` 对所有用户始终只读。授权只读取 Detail 存在任一有效 `settings.auth` 或 `settings.disableColumns` 时,整套使用 Detail 权限,
`user.applications[]` 中 `name = "audit_project_manager"` 的应用项;系统管理员、系统账号及该 两项都未配置或不存在实际 Detail 时才整体回退当前 Grid,禁止合并或逐字段回退。Detail
应用“管理”角色可旁路 View/字段 auth,但不能旁路 `disableColumns`。普通用户缺少配置时默认 `columns` 只控制字段展示范围和顺序,编辑按钮只检查当前 Detail 展示字段。授权只读取
拒绝;没有可访问 Grid View 时不查询记录、不创建兜底视图,也不展示新增、批量和行操作;只有 `user.applications[]` 中 `name = "audit_project_manager_new"` 的应用项;系统管理员、系统账号及
一个可访问 View 时隐藏 Tabs 轨道。 该应用“管理”角色可旁路 View/字段 auth,但不能旁路 `disableColumns`。普通用户在选定权限来源
中缺少字段 `auth` 时只读;没有可访问 Grid View 时不查询记录、不创建兜底视图,也不展示新增、
批量和行操作;只有一个可访问 View 时隐藏 Tabs 轨道。
- 历史快照列表只读,不允许新建、复制或删除。 - 历史快照列表只读,不允许新建、复制或删除。
- 批量归档未返回任何历史记录 ID 时按失败处理,并保留原审计项目数据。 - 批量归档未返回任何历史记录 ID 时按失败处理,并保留原审计项目数据。
...@@ -152,6 +154,6 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template` ...@@ -152,6 +154,6 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template`
远程搜索;选择后只把 `{name,type,size,url}` 快照写入业务草稿。 远程搜索;选择后只把 `{name,type,size,url}` 快照写入业务草稿。
- 本地文件先调用 `/upload` 并进入当前详情草稿。取消编辑不创建材料主档;单值替换,多值追加并 - 本地文件先调用 `/upload` 并进入当前详情草稿。取消编辑不创建材料主档;单值替换,多值追加并
按 URL 去重,移除或清空业务附件不删除材料中心主档或底层文件。 按 URL 去重,移除或清空业务附件不删除材料中心主档或底层文件。
- 用户点击详情“保存”后,系统才用 `source_app = "audit_project_manager"`、当前用户、字段快照和 - 用户点击详情“保存”后,系统才用 `source_app = "audit_project_manager_new"`、当前用户、字段快照和
ISO 时间逐个创建 `am_material`。全部建档成功后才调用既有业务记录更新回调;部分失败会在当前 ISO 时间逐个创建 `am_material`。全部建档成功后才调用既有业务记录更新回调;部分失败会在当前
编辑会话保留已建档标记,重试只创建尚未成功的材料,避免重复主档。 编辑会话保留已建档标记,重试只创建尚未成功的材料,避免重复主档。
...@@ -92,8 +92,8 @@ function intersects(left, right) { ...@@ -92,8 +92,8 @@ function intersects(left, right) {
return collectIdentityValues(left).some((item) => rightSet.has(item)); return collectIdentityValues(left).some((item) => rightSet.has(item));
} }
function getAuthorizationRule(authRule) { function getMultidimensionalTableAuthRule(authRule) {
return ['group', 'role', 'permission', 'username'].includes(authRule?.type) return ['group', 'role', 'permission'].includes(authRule?.type)
? authRule ? authRule
: '管理'; : '管理';
} }
...@@ -413,7 +413,19 @@ export function isViewAccessible( ...@@ -413,7 +413,19 @@ export function isViewAccessible(
user = getCurrentUserFromStorage(), user = getCurrentUserFromStorage(),
applicationName = AUTHORIZATION_APPLICATION_NAME, applicationName = AUTHORIZATION_APPLICATION_NAME,
) { ) {
return hasAuthAccess(getAuthorizationRule(view?.settings?.viewAuth), user, applicationName); return hasAuthAccess(
getMultidimensionalTableAuthRule(view?.settings?.viewAuth),
user,
applicationName,
);
}
export function hasFieldAuthorizationConfiguration(view) {
const settings = view?.settings;
return (
toArray(settings?.auth).some((rule) => normalizeText(rule?.field)) ||
toArray(settings?.disableColumns).some((column) => normalizeText(column?.field))
);
} }
export function isFieldDisabledByView( export function isFieldDisabledByView(
...@@ -432,7 +444,11 @@ export function isFieldDisabledByView( ...@@ -432,7 +444,11 @@ export function isFieldDisabledByView(
} }
const fieldAuthRule = toArray(view?.settings?.auth).find((rule) => rule.field === fieldPath); const fieldAuthRule = toArray(view?.settings?.auth).find((rule) => rule.field === fieldPath);
return !hasAuthAccess(getAuthorizationRule(fieldAuthRule), user, applicationName); return !hasAuthAccess(
getMultidimensionalTableAuthRule(fieldAuthRule),
user,
applicationName,
);
} }
export function recordMatchesRestrictions(record, restrictions = []) { export function recordMatchesRestrictions(record, restrictions = []) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment