Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
A
audit_project_manager
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
audit_project_manager
Commits
72f883d9
Commit
72f883d9
authored
Aug 26, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: align application and detail permissions
parent
fd8348b3
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
121 additions
and
45 deletions
+121
-45
ui-architecture.md
docs/ui-architecture.md
+8
-5
ui-migration-checklist.md
docs/ui-migration-checklist.md
+2
-1
xinyuan-design-system.md
docs/xinyuan-design-system.md
+4
-1
xinyuan-ui-audit.md
docs/xinyuan-ui-audit.md
+31
-9
app.js
src/app.js
+6
-2
application.js
src/config/application.js
+4
-3
SubjectViewContent.jsx
...ages/RecordDetailDrawer/components/SubjectViewContent.jsx
+3
-3
useSubjectRecord.js
src/pages/RecordDetailDrawer/hooks/useSubjectRecord.js
+20
-1
index.jsx
src/pages/RecordDetailDrawer/index.jsx
+12
-8
ApplicationSwitcherDialog.jsx
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
+2
-1
project-management.business-flow.md
...es/project_manager_xy/project-management.business-flow.md
+9
-7
subjectView.js
src/utils/subjectView.js
+20
-4
No files found.
docs/ui-architecture.md
View file @
72f883d9
...
...
@@ -140,8 +140,9 @@ CSS,并用显式 `@source` 仅扫描入口、组件、页面和 wrapper;`src
```
text
UserContext
-> user.applications[name = audit_project_manager]
-> Grid viewAuth / field auth(Detail View 只负责编排与 shortTitle)
-> user.applications[name = audit_project_manager_new]
-> Grid viewAuth
-> active Detail auth/disableColumns(未配置时整体回退 active Grid)
Navbar notification badge
-> platform notice APIs -> /notifications -> base-aware internal navigation
...
...
@@ -154,9 +155,11 @@ Editable file field
```
Subject/View 资源必须先加载并完成 Grid View 授权过滤;只有存在可访问 View 才允许查询记录。
`RecordDetailDrawer`
接收激活 Grid View 作为唯一
`authorizationView`
,激活 Detail View 只决定字段
布局、条件和记录标题是否显示。通知与材料服务只封装既有接口参数,不拥有 Shell、记录更新或
业务状态流。
`RecordDetailDrawer`
接收激活 Grid View 作为
`authorizationView`
,并通过
`GET /subject/:subjectName/view/:viewName`
获取完整 Detail 配置。当前 Detail 有任一有效
`auth/disableColumns`
时使用 Detail 整套权限;完全未配置时才整体回退 Grid。Detail
`columns`
只决定展示范围和顺序,权限来源不会合并。通知与材料服务只封装既有接口参数,不拥有 Shell、
记录更新或业务状态流。
通知路由解析以
`ROUTE_TITLES`
的真实注册路径为本应用站内白名单,并只剥离
`/audit_project_manager_new`
自身 base;不能把其它应用的裸路径交给当前 Umi Router。
...
...
docs/ui-migration-checklist.md
View file @
72f883d9
...
...
@@ -97,7 +97,8 @@
-
[
x
]
Grid
`shortTitle`
按激活 View 生效,title 仍参与搜索且业务入口保留
-
[
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 时隐藏 Tabs 轨道
-
[
x
]
`/notifications`
、Navbar Badge、消息/待办、全部/未读和动态操作完成
...
...
docs/xinyuan-design-system.md
View file @
72f883d9
...
...
@@ -97,8 +97,11 @@ Portal、错误/空/加载、键盘 focus 和移动响应式。合入前运行 `
-
`settings.shortTitle === true`
只隐藏当前 View 的
`title`
展示;title 数据、关键词搜索、排序、
保存与详情入口不得丢失。Detail 标题显示只跟随当前激活 Detail View。
-
View/字段权限只能读取
正式应用
`audit_project_manager
`
在
`user.applications[]`
中的授权项。
-
View/字段权限只能读取
当前稳定应用
`audit_project_manager_new
`
在
`user.applications[]`
中的授权项。
系统管理员及该应用“管理”角色可旁路;普通用户缺失配置时默认拒绝,
`disableColumns`
不旁路。
-
Grid
`viewAuth`
只控制 Tab 可见性。详情字段权限优先使用当前完整 Detail View 的整套
`auth/disableColumns`
;两项都没有有效配置或不存在实际 Detail 时,整体回退当前 Grid,禁止
合并或逐字段回退。编辑按钮只检查当前 Detail 展示字段。
-
Navbar 通知使用 HeroUI
`Badge.Anchor + Badge + Button + Tooltip`
,位于主题按钮左侧;通知页面
使用原平台接口,正文按纯文本呈现,站内路径剥离
`/audit_project_manager_new`
后交给 Umi。
-
editable file 字段统一提供“材料中心 / 本地上传”。选择材料只写文件快照;本地文件先上传到
...
...
docs/xinyuan-ui-audit.md
View file @
72f883d9
...
...
@@ -7,8 +7,8 @@
-
分支:
`codex/ui-ux-xinyuan-migration`
-
唯一实现基准:
`problem_rectification`
-
应用基路径:
`/audit_project_manager_new/`
-
本轮启动命令:
`npm run dev -- --port 800
0`
-
目标验收地址:
`http://127.0.0.1:800
0
/audit_project_manager_new/`
-
本轮启动命令:
`npm run dev -- --port 800
3`
(Umi 检测端口占用后选择
`8007`
)
-
目标验收地址:
`http://127.0.0.1:800
7
/audit_project_manager_new/`
-
基准验收地址:
`http://127.0.0.1:8001/problem_rectification_new/`
迁移前已启动未修改项目并记录业务基线。原登录页为蓝色营销式左右分栏,Shell、表格、
...
...
@@ -263,18 +263,19 @@ HeroUI Context。`@heroui/styles@3.2.4` 的两个 pnpm peer 虚拟目录仅分
-
功能白名单判定:本应用为“审计项目管理”,属于完整功能应用,实施 Grid/Detail
`shortTitle`
、视图权限、消息通知、文件与审计材料中心五项功能。
-
运行标识:Subject 为
`audit_project`
,
正式应用授权名与材料
`source_app`
为
`audit_project_manager
`
;Umi 路由 base 为
`/audit_project_manager_new/`
。站点配置同时存在
演示入口
`audit_project_manager_new`
,但它不作为权限或材料来源标识
。
-
运行标识:Subject 为
`audit_project`
,
当前稳定应用标识、应用授权名与材料
`source_app`
均
为
`audit_project_manager
_new`
,Umi 路由 base 由该标识生成。站点配置同时保留旧入口
`audit_project_manager`
,但当前应用不得读取或合并旧入口授权
。
-
登录样本:Andy 的
`type`
为
`admin`
、
`applications`
为空,只能验证系统管理员旁路;普通
用户必须仅从
`user.applications[]`
中名称为
`audit_project_manager`
的项目读取 group、role
用户必须仅从
`user.applications[]`
中名称为
`audit_project_manager
_new
`
的项目读取 group、role
和 permission,禁止合并顶层或其他应用授权。
-
View 现状:七个 Grid View 均配置
`shortTitle: true`
;“计划”额外配置 group
`viewAuth`
与字段
`auth`
。页面此前通过常量无条件隐藏
`title`
,搜索也随之丢失 title;
View 权限缺省允许、无权时创建 fallback,并在鉴权前查询数据,均不符合基准。
-
Detail 现状:详情视图未统一配置
`shortTitle`
;抽屉始终显示记录标题,字段权限同时受 Detail
View 影响。目标行为应随当前激活 Detail View 即时切换标题显示,但字段权限只读取激活 Grid
View,
`disableColumns`
永远只读。
-
Detail 原始实现只读取 View 摘要,并合并 Detail 与 Grid 字段权限,编辑按钮还会遍历其它 Detail。
现已逐个读取完整 View 配置;当前 Detail 存在有效
`auth/disableColumns`
时整套优先,两项都未
配置或不存在实际 Detail 时才整体回退激活 Grid。
`disableColumns`
永远只读,编辑按钮只检查
当前 Detail 的展示字段。
-
通知现状:原
`DataService`
已提供
`queryUserNotice`
、
`readUserNotice`
、
`unReadNoticeCount`
和
`exacteActionFunction`
,但没有独立页面、路由或 Navbar 入口。
-
文件现状:editable file 字段只执行
`/upload`
并立即写入详情草稿,没有材料中心选择,也没有
...
...
@@ -393,6 +394,27 @@ rg -n 'hp_[A-Za-z0-9]+' . --glob '!node_modules/**' --glob '!dist/**' --glob '!.
返回 200,标题正确,页面级横向溢出均为 0,捕获结果为
`0 errors / 0 warnings`
;此前为验证
材料失败重试而故意模拟的 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 长文本边界复核
-
全局三个 HeroUI Pro DataGrid 消费入口均复用
`DataTable`
:主业务列表、动态表单数据表和
...
...
src/app.js
View file @
72f883d9
import
'@/tailwind.css'
;
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'
)
{
const
storedTheme
=
window
.
localStorage
?.
getItem
?.(
THEME_STORAGE_KEY
);
...
...
@@ -28,7 +32,7 @@ if (
export
async
function
getInitialState
()
{
return
{
name
:
'audit_project_manager'
,
name
:
APPLICATION_NAME
,
};
}
...
...
src/config/application.js
View file @
72f883d9
export
const
APPLICATION_DISPLAY_NAME
=
'审计项目管理'
;
export
const
APPLICATION_BASE_PATH
=
'/audit_project_manager_new/'
;
export
const
AUTHORIZATION_APPLICATION_NAME
=
'audit_project_manager'
;
export
const
SOURCE_APPLICATION_NAME
=
'audit_project_manager'
;
export
const
APPLICATION_NAME
=
'audit_project_manager_new'
;
export
const
APPLICATION_BASE_PATH
=
`/
${
APPLICATION_NAME
}
/`
;
export
const
AUTHORIZATION_APPLICATION_NAME
=
APPLICATION_NAME
;
export
const
SOURCE_APPLICATION_NAME
=
APPLICATION_NAME
;
export
const
THEME_STORAGE_KEY
=
'xinyuan_color_theme'
;
export
const
ROUTE_TITLES
=
Object
.
freeze
({
...
...
src/pages/RecordDetailDrawer/components/SubjectViewContent.jsx
View file @
72f883d9
...
...
@@ -92,9 +92,9 @@ export default function SubjectViewContent({
mode
===
'view'
&&
!
minimal
&&
(
fieldType
===
'form'
||
isReferenceFieldType
(
fieldType
));
const
fieldDisabled
=
isFieldDisabled
?.(
field
.
field
,
view
)
||
isFieldDisabledByView
(
field
.
field
,
view
);
const
fieldDisabled
=
isFieldDisabled
?
isFieldDisabled
(
field
.
field
,
view
)
:
isFieldDisabledByView
(
field
.
field
,
view
);
const
fieldContent
=
(
<>
...
...
src/pages/RecordDetailDrawer/hooks/useSubjectRecord.js
View file @
72f883d9
import
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
{
getSubjectView
,
getSubjectViewDetail
,
loadRecord
,
loadSubject
,
updateRecord
,
...
...
@@ -97,9 +98,27 @@ export function useSubjectRecord(
try {
const record = await loadRecord(recordName);
const resolvedSubjectName = subjectName || record?.__t || record?.subject?.name || record?.subject;
const [subjectData, view
sData
] = resolvedSubjectName
const [subjectData, view
Summaries
] = resolvedSubjectName
? await Promise.all([loadSubject(resolvedSubjectName), getSubjectView(resolvedSubjectName)])
: [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) {
setRemoteRecord(record);
...
...
src/pages/RecordDetailDrawer/index.jsx
View file @
72f883d9
...
...
@@ -18,7 +18,7 @@ import {
buildViewColumns
,
checkViewCondition
,
getFieldValue
,
isFieldDisabledByView
,
hasFieldAuthorizationConfiguration
,
setFieldValue
,
shouldHideViewTitleColumn
,
}
from
'@/utils/subjectView'
;
...
...
@@ -265,10 +265,16 @@ const RecordDetailDrawer = (props, ref) => {
),
[
disabledFields
],
);
const
isDrawerFieldDisabled
=
(
fieldPath
,
currentView
)
=>
const
isDrawerFieldDisabled
=
(
fieldPath
,
currentView
)
=>
{
const
effectiveAuthorizationView
=
hasFieldAuthorizationConfiguration
(
currentView
)
?
currentView
:
authorizationView
||
currentView
;
return
(
disabledFieldSet
.
has
(
fieldPath
)
||
isFieldDisabled
(
fieldPath
,
currentView
)
||
isFieldDisabledByView
(
fieldPath
,
authorizationView
);
isFieldDisabled
(
fieldPath
,
effectiveAuthorizationView
)
);
};
const
refreshRecord
=
({
selectLastTab
=
false
}
=
{})
=>
{
if
(
selectLastTab
||
autoSwitchToLastTab
)
{
...
...
@@ -319,12 +325,10 @@ const RecordDetailDrawer = (props, ref) => {
!
error
&&
!
effectiveReadonly
&&
!
minimalContent
&&
enabledDetailViews
.
some
((
view
)
=>
buildViewColumns
(
view
,
subject
).
some
(
buildViewColumns
(
activeView
,
subject
).
some
(
(
field
)
=>
!
NON_EDITABLE_FIELD_TYPES
.
has
(
field
.
fieldDef
?.
type
)
&&
!
isDrawerFieldDisabled
(
field
.
field
,
view
),
),
!
isDrawerFieldDisabled
(
field
.
field
,
activeView
),
);
const
hasDirtyFields
=
Object
.
keys
(
dirtyFields
).
length
>
0
;
const
hasValidationErrors
=
Object
.
values
(
validationErrors
).
some
(
Boolean
);
...
...
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
View file @
72f883d9
...
...
@@ -2,6 +2,7 @@ import xinyuanLogo from '@/assets/xy-logo-red.png';
import
{
APPLICATION_BASE_PATH
,
APPLICATION_DISPLAY_NAME
,
APPLICATION_NAME
,
}
from
'@/config/application'
;
import
{
getApplications
}
from
'@/services/DataService'
;
import
{
ExclamationTriangleIcon
}
from
'@/components/AppIcons'
;
...
...
@@ -18,7 +19,7 @@ import {
}
from
'@heroui/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
)
{
const
candidates
=
[
...
...
src/pages/project_manager_xy/project-management.business-flow.md
View file @
72f883d9
...
...
@@ -126,12 +126,14 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template`
-
登录页首次打开时调用
`GET /auth/captcha`
获取验证码图片和 token;登录请求在既有账号、
加密密码与
`type=password`
基础上提交
`captcha`
和
`token`
。用户可点击验证码刷新;登录
失败时清空验证码输入并重新获取验证码,成功后的 token 保存和 redirect 流程保持不变。
-
Grid View 的
`settings.viewAuth`
控制视图可见性,当前激活 Grid View 的
`settings.auth`
控制
详情字段编辑权限,
`settings.disableColumns`
对所有用户始终只读。授权只读取
`user.applications[]`
中
`name = "audit_project_manager"`
的应用项;系统管理员、系统账号及该
应用“管理”角色可旁路 View/字段 auth,但不能旁路
`disableColumns`
。普通用户缺少配置时默认
拒绝;没有可访问 Grid View 时不查询记录、不创建兜底视图,也不展示新增、批量和行操作;只有
一个可访问 View 时隐藏 Tabs 轨道。
-
Grid View 的
`settings.viewAuth`
只控制视图可见性。详情打开时逐个读取完整 Detail 配置;当前
Detail 存在任一有效
`settings.auth`
或
`settings.disableColumns`
时,整套使用 Detail 权限,
两项都未配置或不存在实际 Detail 时才整体回退当前 Grid,禁止合并或逐字段回退。Detail
`columns`
只控制字段展示范围和顺序,编辑按钮只检查当前 Detail 展示字段。授权只读取
`user.applications[]`
中
`name = "audit_project_manager_new"`
的应用项;系统管理员、系统账号及
该应用“管理”角色可旁路 View/字段 auth,但不能旁路
`disableColumns`
。普通用户在选定权限来源
中缺少字段
`auth`
时只读;没有可访问 Grid View 时不查询记录、不创建兜底视图,也不展示新增、
批量和行操作;只有一个可访问 View 时隐藏 Tabs 轨道。
-
历史快照列表只读,不允许新建、复制或删除。
-
批量归档未返回任何历史记录 ID 时按失败处理,并保留原审计项目数据。
...
...
@@ -152,6 +154,6 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template`
远程搜索;选择后只把
`{name,type,size,url}`
快照写入业务草稿。
-
本地文件先调用
`/upload`
并进入当前详情草稿。取消编辑不创建材料主档;单值替换,多值追加并
按 URL 去重,移除或清空业务附件不删除材料中心主档或底层文件。
-
用户点击详情“保存”后,系统才用
`source_app = "audit_project_manager"`
、当前用户、字段快照和
-
用户点击详情“保存”后,系统才用
`source_app = "audit_project_manager
_new
"`
、当前用户、字段快照和
ISO 时间逐个创建
`am_material`
。全部建档成功后才调用既有业务记录更新回调;部分失败会在当前
编辑会话保留已建档标记,重试只创建尚未成功的材料,避免重复主档。
src/utils/subjectView.js
View file @
72f883d9
...
...
@@ -92,8 +92,8 @@ function intersects(left, right) {
return
collectIdentityValues
(
left
).
some
((
item
)
=>
rightSet
.
has
(
item
));
}
function
get
Authorization
Rule
(
authRule
)
{
return
[
'group'
,
'role'
,
'permission'
,
'username'
].
includes
(
authRule
?.
type
)
function
get
MultidimensionalTableAuth
Rule
(
authRule
)
{
return
[
'group'
,
'role'
,
'permission'
].
includes
(
authRule
?.
type
)
?
authRule
:
'管理'
;
}
...
...
@@ -413,7 +413,19 @@ export function isViewAccessible(
user
=
getCurrentUserFromStorage
(),
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
(
...
...
@@ -432,7 +444,11 @@ export function isFieldDisabledByView(
}
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
=
[])
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment