Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
integrity_risk_prevention
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
integrity_risk_prevention
Commits
fbbe3f5a
Commit
fbbe3f5a
authored
Sep 08, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: support department item editing and risk workflow
parent
6f750b02
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
526 additions
and
65 deletions
+526
-65
AGENTS.md
AGENTS.md
+17
-16
ui-architecture.md
docs/ui-architecture.md
+1
-1
ui-migration-checklist.md
docs/ui-migration-checklist.md
+1
-1
xinyuan-design-system.md
docs/xinyuan-design-system.md
+1
-1
xinyuan-ui-audit.md
docs/xinyuan-ui-audit.md
+7
-5
DynamicRecordDialog.jsx
src/components/DynamicRecordDialog.jsx
+26
-5
SubjectRecordWorkbench.jsx
src/components/SubjectRecordWorkbench.jsx
+10
-0
index.jsx
src/components/record-workbench/index.jsx
+51
-17
ReferenceRecordsTable.jsx
...s/RecordDetailDrawer/components/ReferenceRecordsTable.jsx
+3
-2
SubjectViewContent.jsx
...ages/RecordDetailDrawer/components/SubjectViewContent.jsx
+2
-2
TailwindEditableField.jsx
...s/RecordDetailDrawer/components/TailwindEditableField.jsx
+14
-1
useSubjectRecord.js
src/pages/RecordDetailDrawer/hooks/useSubjectRecord.js
+5
-2
index.jsx
src/pages/RecordDetailDrawer/index.jsx
+1
-1
ControlMeasureFormPage.jsx
...ages/integrity_risk_prevention/ControlMeasureFormPage.jsx
+60
-0
DepartmentControlItemDialog.jsx
...integrity_risk_prevention/DepartmentControlItemDialog.jsx
+13
-6
DepartmentControlMeasureFlow.jsx
...ntegrity_risk_prevention/DepartmentControlMeasureFlow.jsx
+45
-1
DepartmentPushDialog.jsx
src/pages/integrity_risk_prevention/DepartmentPushDialog.jsx
+1
-1
IntegrityRiskService.js
src/pages/integrity_risk_prevention/IntegrityRiskService.js
+0
-0
IntegrityRiskWorkbenchPage.jsx
.../integrity_risk_prevention/IntegrityRiskWorkbenchPage.jsx
+129
-0
integrity-risk-prevention.business-flow.md
...isk_prevention/integrity-risk-prevention.business-flow.md
+0
-0
DataService.js
src/services/DataService.js
+8
-3
SubjectWorkbenchService.js
src/services/SubjectWorkbenchService.js
+2
-0
subjectView.js
src/utils/subjectView.js
+129
-0
No files found.
AGENTS.md
View file @
fbbe3f5a
...
@@ -21,12 +21,13 @@
...
@@ -21,12 +21,13 @@
-
**常量**
:使用全大写下划线 (SNAKE_CASE)。
-
**常量**
:使用全大写下划线 (SNAKE_CASE)。
-
**技术栈偏好**
:
-
**技术栈偏好**
:
-
优先使用 React Hooks 和函数组件。
-
优先使用 React Hooks 和函数组件。
-
样式处理:
**
优先使用 Tailwind Plus / Catalyst UI Kit(见 `src/catalyst-ui-kit`)+ Tailwind CSS
**
。
-
样式处理:
**
统一使用 HeroUI 与 HeroUI Pro 的主题、Token 和组件样式,配合 Tailwind CSS 做布局
**
。
-
交互组件:
**优先使用 He
adless UI**
(已在 Catalyst 组件内封装,除非缺组件才直接用
`@headlessui/react`
)
。
-
交互组件:
**优先使用 He
roUI / HeroUI Pro 的 compound API**
,由组件自身提供焦点管理、键盘导航、Portal 和可访问性行为
。
-
组件选择优先级(从高到低):
-
组件选择优先级(从高到低):
1.
`src/catalyst-ui-kit/javascript/*`
(Button/Input/Dialog/Table/Combobox/SidebarLayout/...)
1.
`@heroui/react`
(Button/Input/Modal/Autocomplete/Table 等 OSS 组件)
2.
基于 Catalyst 样式规范的自定义组件(仅在 Kit 无现成组件时)
2.
`@heroui-pro/react/*`
(DataGrid/Sidebar/Navbar/DropZone/KPI/EmptyState 等 Pro 组件)
3.
原生 HTML + 自写样式(尽量避免)
3.
基于 HeroUI 语义 Token 的自定义组合组件(仅在 HeroUI/HeroUI Pro 无现成组件时)
4.
原生 HTML + 自写样式(尽量避免)
## 4. 接口请求规范
## 4. 接口请求规范
...
@@ -39,12 +40,12 @@
...
@@ -39,12 +40,12 @@
-
如果
`record-api.md`
或
`subjectsDefinition.md`
中的定义不清晰,请务必询问我,不要随意猜测字段名。
-
如果
`record-api.md`
或
`subjectsDefinition.md`
中的定义不清晰,请务必询问我,不要随意猜测字段名。
-
每次生成代码后,简要说明你引用了哪个 API 和哪些数据对象。
-
每次生成代码后,简要说明你引用了哪个 API 和哪些数据对象。
## 6. UI 规范(
Tailwind Plus / Headless UI
强制约束)
## 6. UI 规范(
HeroUI / HeroUI Pro
强制约束)
-
**全项目 UI 统一**
:默认使用
`
src/catalyst-ui-kit`
中的 Catalyst 组件与样式体系,避免引入其它 UI 框架
。
-
**全项目 UI 统一**
:默认使用
`
@heroui/react`
与
`@heroui-pro/react`
的组件和样式体系,禁止新增旧版 UI 框架或其它平行 UI 框架依赖
。
-
**He
adless UI 优先**
:弹窗/下拉/选择器等交互优先用 Headless UI(直接或通过 Catalyst 组件)
。
-
**He
roUI compound API 优先**
:弹窗、下拉、选择器、标签组等交互直接使用 HeroUI/HeroUI Pro 官方 compound API,不访问内部 slot/BEM 实现页面私有外观
。
-
**一致性**
:表单控件优先
用 Catalyst 的
`Input/Select/Checkbox/Textarea/Fieldset`
,表格用
`Table`
,对话框用
`
Dialog`
。
-
**一致性**
:表单控件优先
使用 HeroUI
`Input/TextArea/Checkbox/DatePicker/Autocomplete`
,表格优先使用 HeroUI Pro
`DataGrid`
,对话框使用 HeroUI
`Modal/Alert
Dialog`
。
-
**可访问性**
:移动端侧栏/弹层必须使用 He
adless UI 的 Dialog 等可访问性组件(focus trap / esc 关闭等)
。
-
**可访问性**
:移动端侧栏/弹层必须使用 He
roUI Pro
`Sheet/Sidebar`
、HeroUI
`Modal`
等组件提供的 focus trap、Esc 关闭、Portal 和键盘导航能力
。
# AI 开发执行规范
# AI 开发执行规范
...
@@ -54,12 +55,12 @@
...
@@ -54,12 +55,12 @@
| 字段类型 (Type) | UI 组件 (Component) | 交互逻辑 / 备注 |
| 字段类型 (Type) | UI 组件 (Component) | 交互逻辑 / 备注 |
| :-- | :-- | :-- |
| :-- | :-- | :-- |
|
**文本 (String)**
|
Catalyst
`Input`
| 标准文本输入 |
|
**文本 (String)**
|
HeroUI
`Input`
| 标准文本输入 |
|
**引用 (Reference)**
|
`ReferenceSearchSelect`
(He
adless UI Combobox
) | 必须通过 API 获取列表,选中后仅存储
`_id`
|
|
**引用 (Reference)**
|
`ReferenceSearchSelect`
(He
roUI
`Autocomplete`
compound API
) | 必须通过 API 获取列表,选中后仅存储
`_id`
|
|
**数字 (Number)**
|
Catalyst
`Input`
(
`type="number"`
) | 仅允许输入数字 |
|
**数字 (Number)**
|
HeroUI
`Input`
(
`type="number"`
) | 仅允许输入数字 |
|
**日期 (Date)**
|
Catalyst
`Input`
(
`type="date"`
/
`datetime-local`
)
| 提交时格式化为
`ISO 8601`
字符串 |
|
**日期 (Date)**
|
HeroUI
`DatePicker`
/
`DateField`
| 提交时格式化为
`ISO 8601`
字符串 |
|
**布尔 (Boolean)**
|
Catalyst
`Checkbox`
| 映射为 true/false |
|
**布尔 (Boolean)**
|
HeroUI
`Checkbox`
| 映射为 true/false |
|
**JSON**
|
Catalyst
`Texta
rea`
| 需要包含 JSON 校验逻辑 |
|
**JSON**
|
HeroUI
`TextA
rea`
| 需要包含 JSON 校验逻辑 |
|
**子对象 (Subject)**
|
**动态表单递归**
|
**核心逻辑:**
必须先调用
`loadSubject(subjectName)`
接口读取该子对象的字段定义,然后递归应用本映射表生成子表单。 |
|
**子对象 (Subject)**
|
**动态表单递归**
|
**核心逻辑:**
必须先调用
`loadSubject(subjectName)`
接口读取该子对象的字段定义,然后递归应用本映射表生成子表单。 |
---
---
...
...
docs/ui-architecture.md
View file @
fbbe3f5a
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
1.
业务控制层:现有 services、hooks、路由、权限、查询参数、分页口径和保存回调保持不变。
1.
业务控制层:现有 services、hooks、路由、权限、查询参数、分页口径和保存回调保持不变。
2.
页面编排层:页面只组合业务数据与 HeroUI 原生组件,
`className`
仅用于布局。
2.
页面编排层:页面只组合业务数据与 HeroUI 原生组件,
`className`
仅用于布局。
3.
共享组合层:
`AppShell`
、
`DataTable`
、
`XinyuanAutocomplete`
、
`XinyuanModal`
等均是 HeroUI-native 的业务无关组合,不兼容
旧 Catalyst
props。
3.
共享组合层:
`AppShell`
、
`DataTable`
、
`XinyuanAutocomplete`
、
`XinyuanModal`
等均是 HeroUI-native 的业务无关组合,不兼容
已废弃的旧 UI 适配层
props。
4.
主题层:HeroUI styles -> HeroUI Pro CSS -> 权威
`theme.css`
-> 批准的
`tokens.css`
-> Shell 布局样式。
4.
主题层:HeroUI styles -> HeroUI Pro CSS -> 权威
`theme.css`
-> 批准的
`tokens.css`
-> Shell 布局样式。
## 依赖边界
## 依赖边界
...
...
docs/ui-migration-checklist.md
View file @
fbbe3f5a
...
@@ -20,7 +20,7 @@
...
@@ -20,7 +20,7 @@
-
[
x
]
弹窗查看态为单一 L2 字段组且字段无框,编辑态移除字段组 Surface;只读字段不使用禁用输入框模拟。
-
[
x
]
弹窗查看态为单一 L2 字段组且字段无框,编辑态移除字段组 Surface;只读字段不使用禁用输入框模拟。
-
[
x
]
所有动态表单使用 HeroUI compound 控件。
-
[
x
]
所有动态表单使用 HeroUI compound 控件。
-
[
x
]
所有上传使用 HeroUI Pro DropZone。
-
[
x
]
所有上传使用 HeroUI Pro DropZone。
-
[
x
]
活动
Catalyst/Headless UI 引用为零
。
-
[
x
]
活动
业务源码统一使用 HeroUI/HeroUI Pro,未引入旧版 UI 框架或其它平行 UI 框架
。
-
[
x
]
活动原生 select/date/file 控件为零(DropZone 内部 input 为不可见技术输入)。
-
[
x
]
活动原生 select/date/file 控件为零(DropZone 内部 input 为不可见技术输入)。
-
[
x
]
页面私有视觉 CSS 扫描为零;页面 className 仅承担布局、间距和响应式。
-
[
x
]
页面私有视觉 CSS 扫描为零;页面 className 仅承担布局、间距和响应式。
-
[
x
]
全局复扫移除业务 JSX 的 Surface/KPI/DataGrid 圆角覆盖与旧 Disclosure BEM class。
-
[
x
]
全局复扫移除业务 JSX 的 Surface/KPI/DataGrid 圆角覆盖与旧 Disclosure BEM class。
...
...
docs/xinyuan-design-system.md
View file @
fbbe3f5a
...
@@ -13,7 +13,7 @@
...
@@ -13,7 +13,7 @@
-
页面
`className`
只处理布局、宽度、间距、对齐和响应式。
-
页面
`className`
只处理布局、宽度、间距、对齐和响应式。
-
组件外观由 HeroUI variant/size/compound API 与权威主题负责。
-
组件外观由 HeroUI variant/size/compound API 与权威主题负责。
-
不引入
Catalyst、Headless UI
、兼容旧 props 的 HeroUI 包装层或可见原生选择/日期/文件控件。
-
不引入
旧版 UI 框架
、兼容旧 props 的 HeroUI 包装层或可见原生选择/日期/文件控件。
-
列表使用 HeroUI Pro DataGrid、ActionBar、EmptyState 和 HeroUI Pagination。
-
列表使用 HeroUI Pro DataGrid、ActionBar、EmptyState 和 HeroUI Pagination。
-
筛选使用 HeroUI Surface、SearchField、Autocomplete、DatePicker 和 Disclosure。
-
筛选使用 HeroUI Surface、SearchField、Autocomplete、DatePicker 和 Disclosure。
-
受控 Autocomplete 的已选值必须可逆操作:单选使用官方
`Autocomplete.ClearButton`
清除当前值;多选在 Trigger 内使用官方
`TagGroup + Tag + Tag.RemoveButton`
将每个值显示为独立 Tag 并支持单项删除,同时保留官方清除入口清空集合;下拉中的官方
`ListBox.Item`
再次点选也可移除单项。Tag、清除按钮和选中态都从同一受控
`selectedKeys`
派生,不得合并为逗号文本、使用页面自制标签或维护第二套选择状态。已选 TagGroup 使用 HeroUI
`default`
variant,Tag 使用官方
`accent-soft-hover`
/
`accent-soft-foreground`
语义 Token,继承鑫元品牌辅助色并与字段背景拉开层级,避免纯白
`surface`
。
-
受控 Autocomplete 的已选值必须可逆操作:单选使用官方
`Autocomplete.ClearButton`
清除当前值;多选在 Trigger 内使用官方
`TagGroup + Tag + Tag.RemoveButton`
将每个值显示为独立 Tag 并支持单项删除,同时保留官方清除入口清空集合;下拉中的官方
`ListBox.Item`
再次点选也可移除单项。Tag、清除按钮和选中态都从同一受控
`selectedKeys`
派生,不得合并为逗号文本、使用页面自制标签或维护第二套选择状态。已选 TagGroup 使用 HeroUI
`default`
variant,Tag 使用官方
`accent-soft-hover`
/
`accent-soft-foreground`
语义 Token,继承鑫元品牌辅助色并与字段背景拉开层级,避免纯白
`surface`
。
...
...
docs/xinyuan-ui-audit.md
View file @
fbbe3f5a
...
@@ -30,11 +30,13 @@
...
@@ -30,11 +30,13 @@
|
`/integrity_risk_control_archive`
| 历史归档 | 只读行为不变 | 已验收 |
|
`/integrity_risk_control_archive`
| 历史归档 | 只读行为不变 | 已验收 |
|
`/control_measure_form`
| 部门填报与审核 | 保留流程状态、驳回与审核回调 | 已验收 |
|
`/control_measure_form`
| 部门填报与审核 | 保留流程状态、驳回与审核回调 | 已验收 |
##
旧 UI 清单
##
迁移前旧 UI 清单(历史基线)
-
Shell:Catalyst Sidebar/Navbar/Avatar/Button。
以下内容仅记录 HeroUI/HeroUI Pro 迁移前的实现状态,用于审计对照,不代表当前技术栈或新增代码规范。
-
页面:Catalyst Button、Dialog、Table、Checkbox、Input、Select、Textarea。
-
Portal:Headless UI Dialog、Combobox、Listbox。
-
Shell:旧版 Sidebar/Navbar/Avatar/Button 组件。
-
页面:旧版 Button、Dialog、Table、Checkbox、Input、Select、Textarea 组件。
-
Portal:旧版 Dialog、Combobox、Listbox 组件。
-
列表:自制 Table、选择列、分页、空状态和工具栏。
-
列表:自制 Table、选择列、分页、空状态和工具栏。
-
图表:未发现活动业务图表、ECharts 私有主题或自制 Tooltip;源码没有
`PieChart`
/
`BarChart`
/
`ComposedChart`
/
`ChartTooltip`
活动页面。
`recharts`
仍是原始依赖并保留在锁文件中,但没有活动 import,不制造测试数据。
-
图表:未发现活动业务图表、ECharts 私有主题或自制 Tooltip;源码没有
`PieChart`
/
`BarChart`
/
`ComposedChart`
/
`ChartTooltip`
活动页面。
`recharts`
仍是原始依赖并保留在锁文件中,但没有活动 import,不制造测试数据。
-
饼图 / 环形图专项审计:当前仍无真实 Pie/Donut 调用点,未新增伪造图表数据或页面。已新增
`--xinyuan-pie-chart-*`
/
`--rc-pie-chart-*`
专用 Token、稳定分类 key 冲突消解映射和
`applyXinyuanPieChartPalette`
共享适配器;未来接入时不得由页面直接传色或改动普通图表色板。
-
饼图 / 环形图专项审计:当前仍无真实 Pie/Donut 调用点,未新增伪造图表数据或页面。已新增
`--xinyuan-pie-chart-*`
/
`--rc-pie-chart-*`
专用 Token、稳定分类 key 冲突消解映射和
`applyXinyuanPieChartPalette`
共享适配器;未来接入时不得由页面直接传色或改动普通图表色板。
...
@@ -180,4 +182,4 @@
...
@@ -180,4 +182,4 @@
-
`shortTitle`
:现有
`buildViewColumns`
未按严格布尔值隐藏
`title`
,详情 Header 和字段区也未跟随当前激活 Detail View 隐藏记录标题;关键词搜索目前仅使用可见列,隐藏标题后会丢失标题检索语义。
-
`shortTitle`
:现有
`buildViewColumns`
未按严格布尔值隐藏
`title`
,详情 Header 和字段区也未跟随当前激活 Detail View 隐藏记录标题;关键词搜索目前仅使用可见列,隐藏标题后会丢失标题检索语义。
-
通知:目标 DataService 已保留平台
`queryUserNotice`
、
`readUserNotice`
、
`unReadNoticeCount`
和动态 action 接口,但目标应用尚无
`/notifications`
路由、通知页面/service 或 Navbar Badge;Sidebar 当前没有通知入口,符合要求。
-
通知:目标 DataService 已保留平台
`queryUserNotice`
、
`readUserNotice`
、
`unReadNoticeCount`
和动态 action 接口,但目标应用尚无
`/notifications`
路由、通知页面/service 或 Navbar Badge;Sidebar 当前没有通知入口,符合要求。
-
文件字段:运行时 Subject 协议支持
`file`
单值对象和
`multiple=true`
数组;现有详情仅直接上传业务附件。目标详情曾残留问题整改材料服务引用,必须改为廉洁风险防控自己的
`source_app`
,并保持
`{name,type,size,url}`
快照协议、延迟建档和重试去重语义。
-
文件字段:运行时 Subject 协议支持
`file`
单值对象和
`multiple=true`
数组;现有详情仅直接上传业务附件。目标详情曾残留问题整改材料服务引用,必须改为廉洁风险防控自己的
`source_app`
,并保持
`{name,type,size,url}`
快照协议、延迟建档和重试去重语义。
-
UI 边界:活动业务源码使用直接 HeroUI/HeroUI Pro 组合;DataGrid、Modal、DropZone、Autocomplete、Badge/Tooltip 的新增实现继续保持原生 compound API,不引入
Catalyst、Headless UI
、旧适配器或页面私有皮肤。
-
UI 边界:活动业务源码使用直接 HeroUI/HeroUI Pro 组合;DataGrid、Modal、DropZone、Autocomplete、Badge/Tooltip 的新增实现继续保持原生 compound API,不引入
旧版 UI 框架
、旧适配器或页面私有皮肤。
src/components/DynamicRecordDialog.jsx
View file @
fbbe3f5a
...
@@ -22,6 +22,8 @@ import {
...
@@ -22,6 +22,8 @@ import {
isReferenceFieldType
,
isReferenceFieldType
,
getViewRestrictionDefaults
,
getViewRestrictionDefaults
,
normalizeValueForSubmit
,
normalizeValueForSubmit
,
isFormulaFieldType
,
resolveFieldValue
,
shouldHideViewTitleColumn
,
shouldHideViewTitleColumn
,
toArray
,
toArray
,
}
from
'@/utils/subjectView'
;
}
from
'@/utils/subjectView'
;
...
@@ -223,7 +225,9 @@ function NestedSubjectFields({ fieldDef, value, onChange, disabled, depth }) {
...
@@ -223,7 +225,9 @@ function NestedSubjectFields({ fieldDef, value, onChange, disabled, depth }) {
<
DynamicField
<
DynamicField
key=
{
nestedField
.
name
}
key=
{
nestedField
.
name
}
fieldDef=
{
nestedField
}
fieldDef=
{
nestedField
}
value=
{
item
?.[
nestedField
.
name
]
}
value=
{
isFormulaFieldType
(
nestedField
.
type
)
?
resolveFieldValue
(
item
,
nestedField
.
name
,
nestedField
)
:
item
?.[
nestedField
.
name
]
}
disabled=
{
disabled
}
disabled=
{
disabled
}
onChange=
{
(
nextValue
)
=>
handleFieldChange
(
nestedField
.
name
,
nextValue
)
}
onChange=
{
(
nextValue
)
=>
handleFieldChange
(
nestedField
.
name
,
nextValue
)
}
depth=
{
depth
+
1
}
depth=
{
depth
+
1
}
...
@@ -294,7 +298,16 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept
...
@@ -294,7 +298,16 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept
const
fieldPlaceholder
=
getFieldPlaceholder
(
fieldDef
);
const
fieldPlaceholder
=
getFieldPlaceholder
(
fieldDef
);
let
control
=
null
;
let
control
=
null
;
if
(
type
===
'boolean'
)
{
if
(
isFormulaFieldType
(
type
))
{
control
=
(
<
Input
isReadOnly
value=
{
value
??
''
}
placeholder=
"保存后由公式自动计算"
variant=
"secondary"
/>
);
}
else
if
(
type
===
'boolean'
)
{
control
=
(
control
=
(
<
Checkbox
<
Checkbox
isSelected=
{
Boolean
(
value
)
}
isSelected=
{
Boolean
(
value
)
}
...
@@ -422,6 +435,10 @@ export async function prepareValuesForSubmit(subject, values, path = '') {
...
@@ -422,6 +435,10 @@ export async function prepareValuesForSubmit(subject, values, path = '') {
const
fieldLabel
=
`
${
path
}${
getFieldDisplayLabel
(
field
)}
`
;
const
fieldLabel
=
`
${
path
}${
getFieldDisplayLabel
(
field
)}
`
;
const
fieldType
=
String
(
field
.
type
||
'text'
).
toLowerCase
();
const
fieldType
=
String
(
field
.
type
||
'text'
).
toLowerCase
();
if
(
isFormulaFieldType
(
fieldType
))
{
continue
;
}
if
(
isRequiredField
(
field
)
&&
isEmptyValue
(
value
))
{
if
(
isRequiredField
(
field
)
&&
isEmptyValue
(
value
))
{
errors
.
push
(
`
${
fieldLabel
}
为必填项`
);
errors
.
push
(
`
${
fieldLabel
}
为必填项`
);
}
}
...
@@ -598,15 +615,19 @@ export default function DynamicRecordDialog({
...
@@ -598,15 +615,19 @@ export default function DynamicRecordDialog({
/>
/>
</
TextField
>
:
null
}
</
TextField
>
:
null
}
{
dialogFields
.
map
((
field
)
=>
{
{
dialogFields
.
map
((
field
)
=>
{
const
fieldType
=
String
(
field
.
type
||
''
).
toLowerCase
();
const
fieldValue
=
isFormulaFieldType
(
fieldType
)
?
resolveFieldValue
(
values
,
field
.
name
,
field
)
:
values
[
field
.
name
];
const
isFullWidth
=
[
'long_text'
,
'rich_text'
,
'object'
,
'form'
,
'subject'
,
'file'
].
includes
(
const
isFullWidth
=
[
'long_text'
,
'rich_text'
,
'object'
,
'form'
,
'subject'
,
'file'
].
includes
(
String
(
field
.
type
||
''
).
toLowerCase
()
,
fieldType
,
);
);
return
(
return
(
<
div
className=
{
isFullWidth
?
'md:col-span-2'
:
undefined
}
key=
{
field
.
name
}
>
<
div
className=
{
isFullWidth
?
'md:col-span-2'
:
undefined
}
key=
{
field
.
name
}
>
<
DynamicField
<
DynamicField
fieldDef=
{
field
}
fieldDef=
{
field
}
value=
{
values
[
field
.
name
]
}
value=
{
fieldValue
}
disabled=
{
saving
||
(
editing
&&
isPrimaryField
(
field
))
||
(
disabled=
{
saving
||
isFormulaFieldType
(
fieldType
)
||
(
editing
&&
isPrimaryField
(
field
))
||
(
!
editing
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
restrictionDefaults
.
metadata
,
field
.
name
)
!
editing
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
restrictionDefaults
.
metadata
,
field
.
name
)
)
}
)
}
onChange=
{
(
nextValue
)
=>
setValues
((
current
)
=>
({
onChange=
{
(
nextValue
)
=>
setValues
((
current
)
=>
({
...
...
src/components/SubjectRecordWorkbench.jsx
View file @
fbbe3f5a
...
@@ -161,6 +161,11 @@ function normalizeActionContext(context, helpers) {
...
@@ -161,6 +161,11 @@ function normalizeActionContext(context, helpers) {
...
context
,
...
context
,
selectedRecords
:
(
context
.
selectedRecords
||
[]).
map
((
record
)
=>
record
.
raw
||
record
),
selectedRecords
:
(
context
.
selectedRecords
||
[]).
map
((
record
)
=>
record
.
raw
||
record
),
record
:
context
.
record
?
context
.
record
.
raw
||
context
.
record
:
undefined
,
record
:
context
.
record
?
context
.
record
.
raw
||
context
.
record
:
undefined
,
parentRecord
:
context
.
parentRecord
?
context
.
parentRecord
.
raw
||
context
.
parentRecord
:
undefined
,
isExpandedRecord
:
Boolean
(
context
.
isExpandedRecord
),
isExpandedPanel
:
Boolean
(
context
.
isExpandedPanel
),
...
helpers
,
...
helpers
,
};
};
}
}
...
@@ -179,6 +184,7 @@ export default function SubjectRecordWorkbench({
...
@@ -179,6 +184,7 @@ export default function SubjectRecordWorkbench({
toolbarActions
=
[],
toolbarActions
=
[],
bulkActions
=
[],
bulkActions
=
[],
rowActions
=
[],
rowActions
=
[],
expandedRowActions
=
[],
compactBulkActions
=
false
,
compactBulkActions
=
false
,
hiddenColumnFields
=
[],
hiddenColumnFields
=
[],
detailDrawerProps
=
{},
detailDrawerProps
=
{},
...
@@ -474,6 +480,9 @@ export default function SubjectRecordWorkbench({
...
@@ -474,6 +480,9 @@ export default function SubjectRecordWorkbench({
}]
}]
:
[]),
:
[]),
]
:
[];
]
:
[];
const
builtExpandedRowActions
=
hasAccessibleGridView
?
expandedRowActions
.
map
(
wrapAction
)
:
[];
if
(
loading
&&
!
hasAccessibleGridView
)
{
if
(
loading
&&
!
hasAccessibleGridView
)
{
return
(
return
(
...
@@ -534,6 +543,7 @@ export default function SubjectRecordWorkbench({
...
@@ -534,6 +543,7 @@ export default function SubjectRecordWorkbench({
toolbarActions=
{
builtToolbarActions
}
toolbarActions=
{
builtToolbarActions
}
bulkActions=
{
builtBulkActions
}
bulkActions=
{
builtBulkActions
}
rowActions=
{
builtRowActions
}
rowActions=
{
builtRowActions
}
expandedRowActions=
{
builtExpandedRowActions
}
isRowExpandable=
{
isRowExpandable
}
isRowExpandable=
{
isRowExpandable
}
getExpandedRecordRows=
{
getExpandedRecordRows
?
resolvedExpandedRecordRows
:
undefined
}
getExpandedRecordRows=
{
getExpandedRecordRows
?
resolvedExpandedRecordRows
:
undefined
}
detailPanel=
{
{
detailPanel=
{
{
...
...
src/components/record-workbench/index.jsx
View file @
fbbe3f5a
...
@@ -27,7 +27,12 @@ import FilterSelect from '@/components/FilterSelect';
...
@@ -27,7 +27,12 @@ import FilterSelect from '@/components/FilterSelect';
import
FormDatePicker
from
'@/components/FormDatePicker'
;
import
FormDatePicker
from
'@/components/FormDatePicker'
;
import
ReferenceSearchSelect
from
'@/pages/RecordDetailDrawer/components/ReferenceSearchSelect'
;
import
ReferenceSearchSelect
from
'@/pages/RecordDetailDrawer/components/ReferenceSearchSelect'
;
import
UserSearchSelect
from
'@/components/UserSearchSelect'
;
import
UserSearchSelect
from
'@/components/UserSearchSelect'
;
import
{
formatFieldValue
,
getFieldValue
,
normalizeText
}
from
'@/utils/subjectView'
;
import
{
formatFieldValue
,
getFieldValue
,
normalizeText
,
resolveFieldValue
,
}
from
'@/utils/subjectView'
;
import
{
import
{
Alert
,
Alert
,
Avatar
,
Avatar
,
...
@@ -323,7 +328,7 @@ function getFilterPlaceholder(field) {
...
@@ -323,7 +328,7 @@ function getFilterPlaceholder(field) {
function
defaultRenderCell
(
record
,
column
)
{
function
defaultRenderCell
(
record
,
column
)
{
const
sourceRecord
=
record
?.
raw
||
record
;
const
sourceRecord
=
record
?.
raw
||
record
;
const
value
=
getFieldValue
(
sourceRecord
,
column
.
field
);
const
value
=
resolveFieldValue
(
sourceRecord
,
column
.
field
,
column
.
fieldDef
);
const
text
=
formatFieldValue
(
value
,
column
.
fieldDef
)
||
'-'
;
const
text
=
formatFieldValue
(
value
,
column
.
fieldDef
)
||
'-'
;
if
(
column
.
fieldDef
?.
type
===
'user'
)
{
if
(
column
.
fieldDef
?.
type
===
'user'
)
{
...
@@ -542,6 +547,7 @@ export default function RecordWorkbench({
...
@@ -542,6 +547,7 @@ export default function RecordWorkbench({
toolbarActions
=
[],
toolbarActions
=
[],
bulkActions
=
[],
bulkActions
=
[],
rowActions
=
[],
rowActions
=
[],
expandedRowActions
=
[],
detailPanel
=
null
,
detailPanel
=
null
,
states
=
{},
states
=
{},
renderCell
,
renderCell
,
...
@@ -605,21 +611,25 @@ export default function RecordWorkbench({
...
@@ -605,21 +611,25 @@ export default function RecordWorkbench({
[
records
,
getRecordId
],
[
records
,
getRecordId
],
);
);
const
resolvedActionColumnWidth
=
useMemo
(
const
resolvedActionColumnWidth
=
useMemo
(
()
=>
resolveActionColumnWidth
(
rowActions
,
actionColumnWidth
),
()
=>
resolveActionColumnWidth
(
[
rowActions
,
actionColumnWidth
],
rowActions
.
length
>=
expandedRowActions
.
length
?
rowActions
:
expandedRowActions
,
actionColumnWidth
,
),
[
rowActions
,
expandedRowActions
,
actionColumnWidth
],
);
);
const
hasRowActions
=
rowActions
.
length
>
0
||
expandedRowActions
.
length
>
0
;
const
stickyColumns
=
useMemo
(
const
stickyColumns
=
useMemo
(
()
=>
()
=>
buildStickyColumnMeta
(
buildStickyColumnMeta
(
orderedVisibleColumns
,
orderedVisibleColumns
,
selectable
,
selectable
,
rowActions
.
length
>
0
,
hasRowActions
,
resolvedActionColumnWidth
,
resolvedActionColumnWidth
,
),
),
[
[
orderedVisibleColumns
,
orderedVisibleColumns
,
selectable
,
selectable
,
rowActions
.
length
,
hasRowActions
,
resolvedActionColumnWidth
,
resolvedActionColumnWidth
,
],
],
);
);
...
@@ -801,6 +811,12 @@ export default function RecordWorkbench({
...
@@ -801,6 +811,12 @@ export default function RecordWorkbench({
});
});
if (Array.isArray(expandedRecords)) {
if (Array.isArray(expandedRecords)) {
expandedRecords.forEach((expandedRecord, index) => {
expandedRecords.forEach((expandedRecord, index) => {
const expandedRowContext = {
...rowContext,
record: expandedRecord,
parentRecord: record,
isExpandedRecord: true,
};
dataGridRows.push({
dataGridRows.push({
key:
key:
expandedRecord?.key ||
expandedRecord?.key ||
...
@@ -811,7 +827,7 @@ export default function RecordWorkbench({
...
@@ -811,7 +827,7 @@ export default function RecordWorkbench({
parentRecord: record,
parentRecord: record,
expandable: false,
expandable: false,
expanded: false,
expanded: false,
rowContext,
rowContext
: expandedRowContext
,
isExpandedRecord: true,
isExpandedRecord: true,
});
});
});
});
...
@@ -821,7 +837,11 @@ export default function RecordWorkbench({
...
@@ -821,7 +837,11 @@ export default function RecordWorkbench({
key: `
$
{
recordId
}
-
expanded
`,
key: `
$
{
recordId
}
-
expanded
`,
record,
record,
parentRecord: record,
parentRecord: record,
rowContext,
rowContext: {
...rowContext,
parentRecord: record,
isExpandedPanel: true,
},
isExpandedPanel: true,
isExpandedPanel: true,
expandedContent: renderExpandedRow({ record, rowContext }),
expandedContent: renderExpandedRow({ record, rowContext }),
});
});
...
@@ -914,13 +934,21 @@ export default function RecordWorkbench({
...
@@ -914,13 +934,21 @@ export default function RecordWorkbench({
}
}
if (columnIndex === 0 && detailPanel) {
if (columnIndex === 0 && detailPanel) {
const value = getFieldValue(row.record?.raw || row.record, column.field);
const value = resolveFieldValue(
row.record?.raw || row.record,
column.field,
column.fieldDef,
);
const text = formatFieldValue(value, column.fieldDef) || '-';
const text = formatFieldValue(value, column.fieldDef) || '-';
return (
return (
<DataTablePrimaryAction
<DataTablePrimaryAction
className="h-auto w-full min-w-0 justify-start px-0 text-left"
className="h-auto w-full min-w-0 justify-start px-0 text-left"
text={text}
text={text}
onPress={() => row.rowContext.openDetail(row.record)}
onPress={() => row.rowContext.openDetail(
row.isExpandedRecord || row.isExpandedPanel
? row.parentRecord
: row.record,
)}
/>
/>
);
);
}
}
...
@@ -929,7 +957,7 @@ export default function RecordWorkbench({
...
@@ -929,7 +957,7 @@ export default function RecordWorkbench({
},
},
};
};
}),
}),
...(
rowActions.length > 0
...(
hasRowActions
? [
? [
{
{
key: 'actions',
key: 'actions',
...
@@ -939,10 +967,10 @@ export default function RecordWorkbench({
...
@@ -939,10 +967,10 @@ export default function RecordWorkbench({
pin: 'end',
pin: 'end',
isActions: true,
isActions: true,
render: (row) => {
render: (row) => {
if (row.isExpandedRecord || row.isExpandedPanel) {
const actions = row.isExpandedRecord || row.isExpandedPanel
return null;
? expandedRowActions
}
: rowActions;
const visibleRowActions =
rowA
ctions.filter(
const visibleRowActions =
a
ctions.filter(
(action) => !isActionHidden(action, row.rowContext),
(action) => !isActionHidden(action, row.rowContext),
);
);
return (
return (
...
@@ -961,7 +989,13 @@ export default function RecordWorkbench({
...
@@ -961,7 +989,13 @@ export default function RecordWorkbench({
]
]
: []),
: []),
];
];
const disabledDataGridRowKeys = dataGridRows
// Expanded rows can expose their own actions (for example, editing a nested
// department item). Do not disable the whole row in that case: HeroUI
// DataGrid's disabledKeys disables descendant buttons as well as selection.
// Selection is still filtered to parent record keys by the workbench.
const disabledDataGridRowKeys = expandedRowActions.length > 0
? []
: dataGridRows
.filter((row) => row.isExpandedRecord || row.isExpandedPanel)
.filter((row) => row.isExpandedRecord || row.isExpandedPanel)
.map((row) => String(row.key));
.map((row) => String(row.key));
const toolbarSearchField = filterSchema.find((field) => field.icon === 'search');
const toolbarSearchField = filterSchema.find((field) => field.icon === 'search');
...
@@ -1229,7 +1263,7 @@ export default function RecordWorkbench({
...
@@ -1229,7 +1263,7 @@ export default function RecordWorkbench({
selectable
selectable
? (keys) =>
? (keys) =>
onSelectionChange?.(
onSelectionChange?.(
keys.filter((key) => currentPageRecordIds.includes(
key
)),
keys.filter((key) => currentPageRecordIds.includes(
String(key)
)),
)
)
: undefined
: undefined
}
}
...
...
src/pages/RecordDetailDrawer/components/ReferenceRecordsTable.jsx
View file @
fbbe3f5a
...
@@ -10,9 +10,9 @@ import { loadSubject, queryRecords } from '@/services/DataService';
...
@@ -10,9 +10,9 @@ import { loadSubject, queryRecords } from '@/services/DataService';
import
{
import
{
formatDateText
,
formatDateText
,
formatFieldValue
,
formatFieldValue
,
getFieldValue
,
getReferenceSubjectName
,
getReferenceSubjectName
,
normalizeRecordId
,
normalizeRecordId
,
resolveFieldValue
,
toArray
,
toArray
,
}
from
'@/utils/subjectView'
;
}
from
'@/utils/subjectView'
;
...
@@ -165,6 +165,7 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
...
@@ -165,6 +165,7 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
_id
:
{
$in
:
referenceIds
},
_id
:
{
$in
:
referenceIds
},
},
},
mode
:
'full'
,
mode
:
'full'
,
calculated
:
true
,
})
})
:
Promise
.
resolve
({
records
:
populatedRecords
}),
:
Promise
.
resolve
({
records
:
populatedRecords
}),
]);
]);
...
@@ -250,7 +251,7 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
...
@@ -250,7 +251,7 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
isPrimary
:
index
===
0
,
isPrimary
:
index
===
0
,
render
:
(
record
)
=>
(
render
:
(
record
)
=>
(
<
CellValue
<
CellValue
value=
{
getFieldValue
(
record
,
column
.
field
)
}
value=
{
resolveFieldValue
(
record
,
column
.
field
,
column
.
fieldDef
)
}
field=
{
column
}
field=
{
column
}
/>
/>
),
),
...
...
src/pages/RecordDetailDrawer/components/SubjectViewContent.jsx
View file @
fbbe3f5a
...
@@ -5,7 +5,7 @@ import {
...
@@ -5,7 +5,7 @@ import {
buildViewColumns
,
buildViewColumns
,
getFieldDisplayLabel
,
getFieldDisplayLabel
,
getFieldPlaceholder
,
getFieldPlaceholder
,
get
FieldValue
,
resolve
FieldValue
,
isFieldDisabledByView
,
isFieldDisabledByView
,
}
from
'@/utils/subjectView'
;
}
from
'@/utils/subjectView'
;
...
@@ -45,7 +45,7 @@ export default function SubjectViewContent({
...
@@ -45,7 +45,7 @@ export default function SubjectViewContent({
className=
"grid min-w-0 grid-cols-1 gap-y-2"
className=
"grid min-w-0 grid-cols-1 gap-y-2"
>
>
{
contentFields
.
map
((
field
)
=>
{
{
contentFields
.
map
((
field
)
=>
{
const
value
=
getFieldValue
(
editableRecord
,
field
.
field
);
const
value
=
resolveFieldValue
(
editableRecord
,
field
.
field
,
field
.
fieldDef
);
const
fieldLabel
=
getFieldDisplayLabel
(
field
.
fieldDef
,
field
.
field
);
const
fieldLabel
=
getFieldDisplayLabel
(
field
.
fieldDef
,
field
.
field
);
const
fieldDisabled
=
isFieldDisabled
const
fieldDisabled
=
isFieldDisabled
?
isFieldDisabled
(
field
.
field
,
view
)
?
isFieldDisabled
(
field
.
field
,
view
)
...
...
src/pages/RecordDetailDrawer/components/TailwindEditableField.jsx
View file @
fbbe3f5a
...
@@ -23,6 +23,7 @@ import {
...
@@ -23,6 +23,7 @@ import {
getFieldDisplayLabel
,
getFieldDisplayLabel
,
getFieldPlaceholder
,
getFieldPlaceholder
,
getSelectOptions
,
getSelectOptions
,
isFormulaFieldType
,
isDateFieldType
,
isDateFieldType
,
isReferenceFieldType
,
isReferenceFieldType
,
getReferenceSubjectName
,
getReferenceSubjectName
,
...
@@ -252,6 +253,17 @@ function ControlledFieldEditor({
...
@@ -252,6 +253,17 @@ function ControlledFieldEditor({
}
}
};
};
if
(
isFormulaFieldType
(
fieldType
))
{
return
(
<
Input
isReadOnly
value=
{
value
??
''
}
placeholder=
"保存后由公式自动计算"
variant=
{
fieldVariant
}
/>
);
}
if
(
fieldType
===
'boolean'
)
{
if
(
fieldType
===
'boolean'
)
{
return
(
return
(
<
Checkbox
isSelected=
{
Boolean
(
value
)
}
onChange=
{
(
nextValue
)
=>
onChange
?.(
nextValue
)
}
>
<
Checkbox
isSelected=
{
Boolean
(
value
)
}
onChange=
{
(
nextValue
)
=>
onChange
?.(
nextValue
)
}
>
...
@@ -433,12 +445,13 @@ export default function TailwindEditableField({
...
@@ -433,12 +445,13 @@ export default function TailwindEditableField({
const
canEdit
=
!
readonly
&&
!
disabled
;
const
canEdit
=
!
readonly
&&
!
disabled
;
const
fieldType
=
fieldDef
?.
type
||
'text'
;
const
fieldType
=
fieldDef
?.
type
||
'text'
;
const
isFormula
=
isFormulaFieldType
(
fieldType
);
const
isDateField
=
isDateFieldType
(
fieldType
);
const
isDateField
=
isDateFieldType
(
fieldType
);
const
isLongText
=
[
'long_text'
,
'rich_text'
].
includes
(
fieldType
);
const
isLongText
=
[
'long_text'
,
'rich_text'
].
includes
(
fieldType
);
const
isJsonLike
=
[
'object'
,
'form'
,
'subject'
].
includes
(
fieldType
);
const
isJsonLike
=
[
'object'
,
'form'
,
'subject'
].
includes
(
fieldType
);
const
textPlaceholder
=
getTextPlaceholder
(
placeholder
,
fieldDef
);
const
textPlaceholder
=
getTextPlaceholder
(
placeholder
,
fieldDef
);
if
(
mode
===
'view'
||
(
mode
===
'edit'
&&
(
readonly
||
disabled
)))
{
if
(
mode
===
'view'
||
isFormula
||
(
mode
===
'edit'
&&
(
readonly
||
disabled
)))
{
return
<
ReadonlyFieldValue
value=
{
value
}
fieldDef=
{
fieldDef
}
/>;
return
<
ReadonlyFieldValue
value=
{
value
}
fieldDef=
{
fieldDef
}
/>;
}
}
...
...
src/pages/RecordDetailDrawer/hooks/useSubjectRecord.js
View file @
fbbe3f5a
...
@@ -96,7 +96,7 @@ export function useSubjectRecord(
...
@@ -96,7 +96,7 @@ export function useSubjectRecord(
setError('');
setError('');
try {
try {
const record = await loadRecord(recordName);
const record = await loadRecord(recordName
, true
);
const resolvedSubjectName = subjectName || record?.__t || record?.subject?.name || record?.subject;
const resolvedSubjectName = subjectName || record?.__t || record?.subject?.name || record?.subject;
const [subjectData, viewSummaries] = resolvedSubjectName
const [subjectData, viewSummaries] = resolvedSubjectName
? await Promise.all([loadSubject(resolvedSubjectName), getSubjectView(resolvedSubjectName)])
? await Promise.all([loadSubject(resolvedSubjectName), getSubjectView(resolvedSubjectName)])
...
@@ -151,7 +151,10 @@ export function useSubjectRecord(
...
@@ -151,7 +151,10 @@ export function useSubjectRecord(
return;
return;
}
}
const normalizedChanges = changes.filter((change) => change?.fieldPath);
const normalizedChanges = changes.filter((change) => (
change?.fieldPath
&& String(change.fieldDef?.type || '').toLowerCase() !== 'formula'
));
if (normalizedChanges.length === 0) {
if (normalizedChanges.length === 0) {
return;
return;
}
}
...
...
src/pages/RecordDetailDrawer/index.jsx
View file @
fbbe3f5a
...
@@ -23,7 +23,7 @@ import {
...
@@ -23,7 +23,7 @@ import {
}
from
'@/utils/subjectView'
;
}
from
'@/utils/subjectView'
;
const
EMPTY_ARRAY
=
[];
const
EMPTY_ARRAY
=
[];
const
NON_EDITABLE_FIELD_TYPES
=
new
Set
([
'form'
,
'subject'
]);
const
NON_EDITABLE_FIELD_TYPES
=
new
Set
([
'form'
,
'subject'
,
'formula'
]);
function
cloneRecord
(
record
)
{
function
cloneRecord
(
record
)
{
return
record
?
JSON
.
parse
(
JSON
.
stringify
(
record
))
:
null
;
return
record
?
JSON
.
parse
(
JSON
.
stringify
(
record
))
:
null
;
...
...
src/pages/integrity_risk_prevention/ControlMeasureFormPage.jsx
View file @
fbbe3f5a
...
@@ -16,6 +16,7 @@ import {
...
@@ -16,6 +16,7 @@ import {
normalizeIds
,
normalizeIds
,
rejectControlMeasureFlow
,
rejectControlMeasureFlow
,
submitControlMeasureFlow
,
submitControlMeasureFlow
,
withdrawControlMeasureFlow
,
}
from
'./IntegrityRiskService'
;
}
from
'./IntegrityRiskService'
;
function
normalizeRecord
(
record
)
{
function
normalizeRecord
(
record
)
{
...
@@ -58,6 +59,34 @@ function RejectConfirmDialog({ type, loading, onClose, onConfirm }) {
...
@@ -58,6 +59,34 @@ function RejectConfirmDialog({ type, loading, onClose, onConfirm }) {
);
);
}
}
function
WithdrawConfirmDialog
({
open
,
loading
,
onClose
,
onConfirm
})
{
return
(
<
XinyuanModal
isOpen=
{
open
}
onOpenChange=
{
(
isOpen
)
=>
{
if
(
!
isOpen
&&
!
loading
)
onClose
?.();
}
}
size=
"sm"
title=
"确认撤回审批"
description=
"撤回后审批人的 OA 待办会完成,表单恢复为填报人可填写状态。"
footer=
{
(
<>
<
Button
isDisabled=
{
loading
}
variant=
"secondary"
onPress=
{
onClose
}
>
取消
</
Button
>
<
Button
isDisabled=
{
loading
}
isPending=
{
loading
}
onPress=
{
onConfirm
}
>
{
loading
?
'撤回中...'
:
'确认撤回'
}
</
Button
>
</>
)
}
>
<
Alert
status=
"warning"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
已填写的数据会保留,审批人尚未处理时才可以撤回。
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
</
XinyuanModal
>
);
}
export
default
function
ControlMeasureFormPage
()
{
export
default
function
ControlMeasureFormPage
()
{
const
location
=
useLocation
();
const
location
=
useLocation
();
const
userInfo
=
useUserInfo
();
const
userInfo
=
useUserInfo
();
...
@@ -67,6 +96,7 @@ export default function ControlMeasureFormPage() {
...
@@ -67,6 +96,7 @@ export default function ControlMeasureFormPage() {
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
success
,
setSuccess
]
=
useState
(
''
);
const
[
success
,
setSuccess
]
=
useState
(
''
);
const
[
rejectType
,
setRejectType
]
=
useState
(
''
);
const
[
rejectType
,
setRejectType
]
=
useState
(
''
);
const
[
withdrawOpen
,
setWithdrawOpen
]
=
useState
(
false
);
const
messageId
=
useMemo
(
const
messageId
=
useMemo
(
()
=>
new
URLSearchParams
(
location
.
search
).
get
(
'id'
)
||
''
,
()
=>
new
URLSearchParams
(
location
.
search
).
get
(
'id'
)
||
''
,
...
@@ -158,6 +188,21 @@ export default function ControlMeasureFormPage() {
...
@@ -158,6 +188,21 @@ export default function ControlMeasureFormPage() {
});
});
}
}
if
(
isEditor
&&
editorStatus
===
FLOW_STATUS
.
complete
&&
auditorStatus
===
FLOW_STATUS
.
pending
)
{
toolbarActions
.
push
({
key
:
'withdraw'
,
label
:
actionLoading
===
'withdraw'
?
'撤回中...'
:
'撤回审批'
,
variant
:
'secondary'
,
tone
:
'warning'
,
disabled
:
Boolean
(
actionLoading
)
||
records
.
length
===
0
,
onClick
:
()
=>
setWithdrawOpen
(
true
),
});
}
if
(
if
(
isAuditor
isAuditor
&&
editorStatus
===
FLOW_STATUS
.
complete
&&
editorStatus
===
FLOW_STATUS
.
complete
...
@@ -228,6 +273,15 @@ export default function ControlMeasureFormPage() {
...
@@ -228,6 +273,15 @@ export default function ControlMeasureFormPage() {
if
(
succeeded
)
setRejectType
(
''
);
if
(
succeeded
)
setRejectType
(
''
);
};
};
const
confirmWithdraw
=
async
()
=>
{
const
succeeded
=
await
runFlowAction
(
'withdraw'
,
()
=>
withdrawControlMeasureFlow
(
messageRecord
,
userInfo
,
returnUrl
),
'已撤回审批,表单已恢复为可填写状态。'
,
);
if
(
succeeded
)
setWithdrawOpen
(
false
);
};
if
(
workspace
?.
isDepartmentFlow
)
{
if
(
workspace
?.
isDepartmentFlow
)
{
return
(
return
(
<
DepartmentControlMeasureFlow
<
DepartmentControlMeasureFlow
...
@@ -290,6 +344,12 @@ export default function ControlMeasureFormPage() {
...
@@ -290,6 +344,12 @@ export default function ControlMeasureFormPage() {
onClose=
{
()
=>
setRejectType
(
''
)
}
onClose=
{
()
=>
setRejectType
(
''
)
}
onConfirm=
{
confirmReject
}
onConfirm=
{
confirmReject
}
/>
/>
<
WithdrawConfirmDialog
open=
{
withdrawOpen
}
loading=
{
actionLoading
===
'withdraw'
}
onClose=
{
()
=>
setWithdrawOpen
(
false
)
}
onConfirm=
{
confirmWithdraw
}
/>
</
main
>
</
main
>
);
);
}
}
src/pages/integrity_risk_prevention/DepartmentControlItemDialog.jsx
View file @
fbbe3f5a
...
@@ -2,7 +2,11 @@ import { Alert, Button, Surface } from '@heroui/react';
...
@@ -2,7 +2,11 @@ import { Alert, Button, Surface } from '@heroui/react';
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
{
DynamicField
,
prepareValuesForSubmit
}
from
'@/components/DynamicRecordDialog'
;
import
{
DynamicField
,
prepareValuesForSubmit
}
from
'@/components/DynamicRecordDialog'
;
import
TailwindEditableField
from
'@/pages/RecordDetailDrawer/components/TailwindEditableField'
;
import
TailwindEditableField
from
'@/pages/RecordDetailDrawer/components/TailwindEditableField'
;
import
{
getFieldDisplayLabel
}
from
'@/utils/subjectView'
;
import
{
getFieldDisplayLabel
,
isFormulaFieldType
,
resolveFieldValue
,
}
from
'@/utils/subjectView'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
const
HIDDEN_FIELD_NAMES
=
new
Set
([
const
HIDDEN_FIELD_NAMES
=
new
Set
([
...
@@ -50,8 +54,7 @@ export default function DepartmentControlItemDialog({
...
@@ -50,8 +54,7 @@ export default function DepartmentControlItemDialog({
const
fields
=
useMemo
(
const
fields
=
useMemo
(
()
=>
(
subject
?.
fields
||
[]).
filter
((
field
)
=>
()
=>
(
subject
?.
fields
||
[]).
filter
((
field
)
=>
!
HIDDEN_FIELD_NAMES
.
has
(
field
.
name
)
!
HIDDEN_FIELD_NAMES
.
has
(
field
.
name
)),
&&
String
(
field
.
type
||
''
).
toLowerCase
()
!==
'formula'
),
[
subject
],
[
subject
],
);
);
...
@@ -74,7 +77,11 @@ export default function DepartmentControlItemDialog({
...
@@ -74,7 +77,11 @@ export default function DepartmentControlItemDialog({
<
div
className=
"mt-1.5 text-foreground"
>
{
item
?.
business_unit
||
'暂无内容'
}
</
div
>
<
div
className=
"mt-1.5 text-foreground"
>
{
item
?.
business_unit
||
'暂无内容'
}
</
div
>
</
div
>
</
div
>
{
fields
.
map
((
field
)
=>
{
{
fields
.
map
((
field
)
=>
{
const
isFullWidth
=
[
'long_text'
,
'rich_text'
,
'subject'
,
'object'
,
'form'
].
includes
(
field
.
type
);
const
fieldType
=
String
(
field
.
type
||
''
).
toLowerCase
();
const
fieldValue
=
isFormulaFieldType
(
fieldType
)
?
resolveFieldValue
(
values
,
field
.
name
,
field
)
:
values
[
field
.
name
];
const
isFullWidth
=
[
'long_text'
,
'rich_text'
,
'subject'
,
'object'
,
'form'
].
includes
(
fieldType
);
return
(
return
(
<
div
<
div
...
@@ -93,14 +100,14 @@ export default function DepartmentControlItemDialog({
...
@@ -93,14 +100,14 @@ export default function DepartmentControlItemDialog({
fieldDef=
{
field
}
fieldDef=
{
field
}
mode=
"view"
mode=
"view"
readonly
readonly
value=
{
values
[
field
.
name
]
}
value=
{
fieldValue
}
/>
/>
</
div
>
</
div
>
</>
</>
)
:
(
)
:
(
<
DynamicField
<
DynamicField
fieldDef=
{
field
}
fieldDef=
{
field
}
value=
{
values
[
field
.
name
]
}
value=
{
fieldValue
}
disabled=
{
loading
}
disabled=
{
loading
}
onChange=
{
(
nextValue
)
=>
setValues
((
current
)
=>
({
onChange=
{
(
nextValue
)
=>
setValues
((
current
)
=>
({
...
current
,
...
current
,
...
...
src/pages/integrity_risk_prevention/DepartmentControlMeasureFlow.jsx
View file @
fbbe3f5a
...
@@ -3,7 +3,7 @@ import XinyuanModal from '@/components/XinyuanModal';
...
@@ -3,7 +3,7 @@ import XinyuanModal from '@/components/XinyuanModal';
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
{
queryUsers
}
from
'@/services/DataService'
;
import
{
queryUsers
}
from
'@/services/DataService'
;
import
{
normalizeUserLabel
}
from
'@/utils/subjectView'
;
import
{
normalizeUserLabel
}
from
'@/utils/subjectView'
;
import
{
CheckCircleIcon
,
Close
,
Edit
,
Eye
,
Send
}
from
'@/components/AppIcons'
;
import
{
ArrowPathIcon
,
CheckCircleIcon
,
Close
,
Edit
,
Eye
,
Send
}
from
'@/components/AppIcons'
;
import
dayjs
from
'dayjs'
;
import
dayjs
from
'dayjs'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
DepartmentControlItemDialog
from
'./DepartmentControlItemDialog'
;
import
DepartmentControlItemDialog
from
'./DepartmentControlItemDialog'
;
...
@@ -17,6 +17,7 @@ import {
...
@@ -17,6 +17,7 @@ import {
rejectDepartmentControlItem
,
rejectDepartmentControlItem
,
saveDepartmentControlItem
,
saveDepartmentControlItem
,
submitDepartmentControlItem
,
submitDepartmentControlItem
,
withdrawDepartmentControlItem
,
}
from
'./IntegrityRiskService'
;
}
from
'./IntegrityRiskService'
;
const
COLUMNS
=
[
const
COLUMNS
=
[
...
@@ -42,6 +43,7 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
...
@@ -42,6 +43,7 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
success
,
setSuccess
]
=
useState
(
''
);
const
[
success
,
setSuccess
]
=
useState
(
''
);
const
[
rejectContext
,
setRejectContext
]
=
useState
(
null
);
const
[
rejectContext
,
setRejectContext
]
=
useState
(
null
);
const
[
withdrawContext
,
setWithdrawContext
]
=
useState
(
null
);
const
[
userLabels
,
setUserLabels
]
=
useState
({});
const
[
userLabels
,
setUserLabels
]
=
useState
({});
const
rows
=
useMemo
(
const
rows
=
useMemo
(
...
@@ -130,6 +132,14 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
...
@@ -130,6 +132,14 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
`
${
record
.
business_unit
}
部门明细已送审。`
,
`
${
record
.
business_unit
}
部门明细已送审。`
,
),
),
},
},
{
key
:
'withdraw-department-item'
,
label
:
'撤回审批'
,
icon
:
ArrowPathIcon
,
hidden
:
({
record
})
=>
!
getDepartmentItemPermissions
(
record
,
userInfo
).
canWithdraw
,
disabled
:
({
record
})
=>
Boolean
(
actionKey
)
||
!
record
.
auditor
,
onClick
:
({
record
})
=>
setWithdrawContext
(
record
),
},
{
{
key
:
'approve-department-item'
,
key
:
'approve-department-item'
,
label
:
'审批通过'
,
label
:
'审批通过'
,
...
@@ -204,6 +214,17 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
...
@@ -204,6 +214,17 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
if
(
succeeded
)
setRejectContext
(
null
);
if
(
succeeded
)
setRejectContext
(
null
);
};
};
const
confirmWithdraw
=
async
()
=>
{
const
row
=
withdrawContext
;
if
(
!
row
)
return
;
const
succeeded
=
await
runAction
(
`withdraw-
${
row
.
id
}
`
,
()
=>
withdrawDepartmentControlItem
(
actionArgs
(
row
)),
`
${
row
.
business_unit
}
部门明细已撤回审批,可继续填写。`
,
);
if
(
succeeded
)
setWithdrawContext
(
null
);
};
const
activePermissions
=
activeItem
const
activePermissions
=
activeItem
?
getDepartmentItemPermissions
(
activeItem
,
userInfo
)
?
getDepartmentItemPermissions
(
activeItem
,
userInfo
)
:
{
canEdit
:
false
};
:
{
canEdit
:
false
};
...
@@ -260,6 +281,29 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
...
@@ -260,6 +281,29 @@ export default function DepartmentControlMeasureFlow({ workspace, userInfo, onRe
</>
</>
)
}
)
}
/>
/>
<
XinyuanModal
isOpen=
{
Boolean
(
withdrawContext
)
}
onOpenChange=
{
(
isOpen
)
=>
{
if
(
!
isOpen
&&
!
actionKey
)
setWithdrawContext
(
null
);
}
}
size=
"sm"
title=
"确认撤回审批"
description=
{
`撤回“${withdrawContext?.business_unit || '-'}”部门明细的审批申请。`
}
footer=
{
(
<>
<
Button
isDisabled=
{
Boolean
(
actionKey
)
}
variant=
"secondary"
onPress=
{
()
=>
setWithdrawContext
(
null
)
}
>
取消
</
Button
>
<
Button
isDisabled=
{
Boolean
(
actionKey
)
}
isPending=
{
Boolean
(
actionKey
)
}
onPress=
{
confirmWithdraw
}
>
{
actionKey
?
'撤回中...'
:
'确认撤回'
}
</
Button
>
</>
)
}
>
<
div
className=
"space-y-2 text-sm text-muted"
>
<
p
>
撤回后将完成审批人的 OA 待办,已填写内容会保留。
</
p
>
<
p
>
当前部门明细会恢复为填报人可编辑状态,其他部门不受影响。
</
p
>
</
div
>
</
XinyuanModal
>
</
main
>
</
main
>
);
);
}
}
src/pages/integrity_risk_prevention/DepartmentPushDialog.jsx
View file @
fbbe3f5a
...
@@ -55,7 +55,7 @@ export default function DepartmentPushDialog({ open, targets, loading, onClose,
...
@@ -55,7 +55,7 @@ export default function DepartmentPushDialog({ open, targets, loading, onClose,
primary=
{
assignment
.
business_unit
}
primary=
{
assignment
.
business_unit
}
secondary=
{
showParentTitle
?
assignment
.
parent_title
:
'将生成 1 条部门明细'
}
secondary=
{
showParentTitle
?
assignment
.
parent_title
:
'将生成 1 条部门明细'
}
/>
/>
{
assignment
.
reuse_existing
?
<
Chip
className=
"mt-1"
color=
"warning"
size=
"sm"
>
沿用
未完成子项
</
Chip
>
:
null
}
{
assignment
.
reuse_existing
?
<
Chip
className=
"mt-1"
color=
"warning"
size=
"sm"
>
沿用
部门子项原始数据
</
Chip
>
:
null
}
</
div
>
</
div
>
),
),
},
},
...
...
src/pages/integrity_risk_prevention/IntegrityRiskService.js
View file @
fbbe3f5a
This diff is collapsed.
Click to expand it.
src/pages/integrity_risk_prevention/IntegrityRiskWorkbenchPage.jsx
View file @
fbbe3f5a
...
@@ -2,6 +2,7 @@ import { Button, Surface } from '@heroui/react';
...
@@ -2,6 +2,7 @@ import { Button, Surface } from '@heroui/react';
import
FileUploadField
from
'@/components/FileUploadField'
;
import
FileUploadField
from
'@/components/FileUploadField'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
SubjectRecordWorkbench
from
'@/components/SubjectRecordWorkbench'
;
import
SubjectRecordWorkbench
from
'@/components/SubjectRecordWorkbench'
;
import
DepartmentControlItemDialog
from
'./DepartmentControlItemDialog'
;
import
{
useUserInfo
}
from
'@/hooks/user'
;
import
{
useUserInfo
}
from
'@/hooks/user'
;
import
{
useMemo
,
useState
}
from
'react'
;
import
{
useMemo
,
useState
}
from
'react'
;
import
{
import
{
...
@@ -13,11 +14,15 @@ import {
...
@@ -13,11 +14,15 @@ import {
exportProcessTable
,
exportProcessTable
,
exportShowTable
,
exportShowTable
,
INTEGRITY_RISK_SUBJECTS
,
INTEGRITY_RISK_SUBJECTS
,
getDepartmentItemPermissions
,
loadDepartmentControlItemSubject
,
saveDepartmentControlItem
,
pushControlMeasureForm
,
pushControlMeasureForm
,
replaceMeasureDescriptionsFromWorkbook
,
replaceMeasureDescriptionsFromWorkbook
,
replaceShowTableFromWorkbook
,
replaceShowTableFromWorkbook
,
normalizeDepartmentControlItems
,
normalizeDepartmentControlItems
,
}
from
'./IntegrityRiskService'
;
}
from
'./IntegrityRiskService'
;
import
{
Edit
,
Eye
}
from
'@/components/AppIcons'
;
import
DepartmentPushDialog
from
'./DepartmentPushDialog'
;
import
DepartmentPushDialog
from
'./DepartmentPushDialog'
;
const
PAGE_CONFIG
=
{
const
PAGE_CONFIG
=
{
...
@@ -90,6 +95,10 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
...
@@ -90,6 +95,10 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
const
[
importContext
,
setImportContext
]
=
useState
(
null
);
const
[
importContext
,
setImportContext
]
=
useState
(
null
);
const
[
importFile
,
setImportFile
]
=
useState
(
null
);
const
[
importFile
,
setImportFile
]
=
useState
(
null
);
const
[
importType
,
setImportType
]
=
useState
(
''
);
const
[
importType
,
setImportType
]
=
useState
(
''
);
const
[
departmentSubject
,
setDepartmentSubject
]
=
useState
(
null
);
const
[
departmentEditor
,
setDepartmentEditor
]
=
useState
(
null
);
const
[
departmentEditorLoading
,
setDepartmentEditorLoading
]
=
useState
(
false
);
const
[
departmentEditorActionKey
,
setDepartmentEditorActionKey
]
=
useState
(
''
);
const
runAction
=
async
({
key
,
context
,
action
,
successMessage
,
reload
=
false
})
=>
{
const
runAction
=
async
({
key
,
context
,
action
,
successMessage
,
reload
=
false
})
=>
{
setBusyKey
(
key
);
setBusyKey
(
key
);
...
@@ -154,6 +163,17 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
...
@@ -154,6 +163,17 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
action
:
exportProcessTable
,
action
:
exportProcessTable
,
successMessage
:
(
count
)
=>
`已导出
${
count
}
条过程表记录。`
,
successMessage
:
(
count
)
=>
`已导出
${
count
}
条过程表记录。`
,
}),
}),
},
{
key
:
'export-process-measures'
,
label
:
busyKey
===
'export-process-measures'
?
'导出中...'
:
'导出防控措施'
,
variant
:
'secondary'
,
disabled
:
disabledWhileBusy
,
onClick
:
(
context
)
=>
runAction
({
key
:
'export-process-measures'
,
context
,
action
:
()
=>
exportPreventionMeasures
(
INTEGRITY_RISK_SUBJECTS
.
process
),
successMessage
:
(
count
)
=>
`已导出
${
count
}
条去重后的防控措施。`
,
}),
}],
}],
bulkActions
:
[
bulkActions
:
[
{
{
...
@@ -303,6 +323,105 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
...
@@ -303,6 +323,105 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
if
(
succeeded
)
setPushContext
(
null
);
if
(
succeeded
)
setPushContext
(
null
);
};
};
const
resolveDepartmentItemContext
=
(
context
)
=>
{
const
parentRecord
=
context
?.
parentRecord
?.
raw
||
context
?.
parentRecord
||
context
?.
record
?.
parentRecord
?.
raw
||
context
?.
record
?.
parentRecord
||
context
?.
record
?.
raw
||
context
?.
record
;
const
parentId
=
parentRecord
?.
_id
||
parentRecord
?.
id
||
parentRecord
?.
name
;
const
expandedRecord
=
context
?.
record
?.
raw
||
context
?.
record
;
const
itemId
=
expandedRecord
?.
metadata
?.
item_id
||
expandedRecord
?.
item_id
;
const
item
=
normalizeDepartmentControlItems
(
parentRecord
)
.
find
((
candidate
)
=>
candidate
.
item_id
===
itemId
);
return
{
parentRecord
,
parentId
,
item
};
};
const
openDepartmentItem
=
async
(
context
,
readonly
=
false
)
=>
{
const
{
parentId
,
item
}
=
resolveDepartmentItemContext
(
context
);
if
(
!
parentId
||
!
item
)
{
context
?.
setError
?.(
'找不到对应的部门子项,请刷新主界面后重试。'
);
return
;
}
const
permissions
=
getDepartmentItemPermissions
(
item
,
userInfo
);
if
(
!
readonly
&&
!
permissions
.
canEdit
)
{
context
?.
setError
?.(
'当前用户没有编辑该部门子项的权限。'
);
return
;
}
setDepartmentEditorLoading
(
true
);
context
?.
setError
?.(
''
);
try
{
const
subject
=
departmentSubject
||
await
loadDepartmentControlItemSubject
();
setDepartmentSubject
(
subject
);
setDepartmentEditor
({
item
,
parentId
,
readonly
:
readonly
||
!
permissions
.
canEdit
,
setError
:
context
?.
setError
,
setSuccess
:
context
?.
setSuccess
,
reload
:
context
?.
reload
,
});
}
catch
(
error
)
{
context
?.
setError
?.(
error
?.
message
||
'部门子项结构加载失败,请稍后重试。'
);
}
finally
{
setDepartmentEditorLoading
(
false
);
}
};
const
expandedRowActions
=
pageType
===
'process'
?
[
{
key
:
'edit-department-item'
,
label
:
'编辑子项'
,
icon
:
Edit
,
hidden
:
(
context
)
=>
{
const
{
item
}
=
resolveDepartmentItemContext
(
context
);
return
!
item
||
!
getDepartmentItemPermissions
(
item
,
userInfo
).
canEdit
;
},
disabled
:
()
=>
departmentEditorLoading
||
Boolean
(
departmentEditorActionKey
),
onClick
:
(
context
)
=>
openDepartmentItem
(
context
,
false
),
},
{
key
:
'view-department-item'
,
label
:
'查看子项'
,
icon
:
Eye
,
hidden
:
(
context
)
=>
{
const
{
item
}
=
resolveDepartmentItemContext
(
context
);
return
!
item
||
getDepartmentItemPermissions
(
item
,
userInfo
).
canEdit
;
},
disabled
:
()
=>
departmentEditorLoading
||
Boolean
(
departmentEditorActionKey
),
onClick
:
(
context
)
=>
openDepartmentItem
(
context
,
true
),
},
]
:
[];
const
closeDepartmentEditor
=
()
=>
{
if
(
departmentEditorActionKey
)
return
;
setDepartmentEditor
(
null
);
};
const
handleDepartmentItemSave
=
async
(
values
)
=>
{
if
(
!
departmentEditor
)
return
;
const
actionKey
=
`save-department-
${
departmentEditor
.
item
.
item_id
}
`
;
setDepartmentEditorActionKey
(
actionKey
);
departmentEditor
.
setError
?.(
''
);
departmentEditor
.
setSuccess
?.(
''
);
try
{
await
saveDepartmentControlItem
({
parentId
:
departmentEditor
.
parentId
,
itemId
:
departmentEditor
.
item
.
item_id
,
values
,
user
:
userInfo
,
});
departmentEditor
.
setSuccess
?.(
'部门子项已保存,主表数据已自动合并。'
);
await
departmentEditor
.
reload
?.({
keepFeedback
:
true
});
setDepartmentEditor
(
null
);
}
catch
(
error
)
{
departmentEditor
.
setError
?.(
error
?.
message
||
'部门子项保存失败,请稍后重试。'
);
}
finally
{
setDepartmentEditorActionKey
(
''
);
}
};
return
(
return
(
<>
<>
<
SubjectRecordWorkbench
<
SubjectRecordWorkbench
...
@@ -319,6 +438,7 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
...
@@ -319,6 +438,7 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
hiddenColumnFields=
{
pageConfig
.
hiddenColumnFields
||
[]
}
hiddenColumnFields=
{
pageConfig
.
hiddenColumnFields
||
[]
}
toolbarActions=
{
toolbarActions
}
toolbarActions=
{
toolbarActions
}
bulkActions=
{
bulkActions
}
bulkActions=
{
bulkActions
}
expandedRowActions=
{
expandedRowActions
}
detailDrawerProps=
{
{
detailDrawerProps=
{
{
title
:
`${pageConfig.title}详情`
,
title
:
`${pageConfig.title}详情`
,
readonly
:
pageConfig
.
readonly
,
readonly
:
pageConfig
.
readonly
,
...
@@ -357,6 +477,15 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
...
@@ -357,6 +477,15 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
onClose=
{
()
=>
setPushContext
(
null
)
}
onClose=
{
()
=>
setPushContext
(
null
)
}
onSubmit=
{
confirmPush
}
onSubmit=
{
confirmPush
}
/>
/>
<
DepartmentControlItemDialog
open=
{
Boolean
(
departmentEditor
)
}
item=
{
departmentEditor
?.
item
}
subject=
{
departmentSubject
}
readonly=
{
Boolean
(
departmentEditor
?.
readonly
)
}
loading=
{
Boolean
(
departmentEditorActionKey
)
}
onClose=
{
closeDepartmentEditor
}
onSave=
{
handleDepartmentItemSave
}
/>
<
ImportConfirmDialog
<
ImportConfirmDialog
open=
{
Boolean
(
importContext
)
}
open=
{
Boolean
(
importContext
)
}
file=
{
importFile
}
file=
{
importFile
}
...
...
src/pages/integrity_risk_prevention/integrity-risk-prevention.business-flow.md
View file @
fbbe3f5a
This diff is collapsed.
Click to expand it.
src/services/DataService.js
View file @
fbbe3f5a
...
@@ -167,8 +167,9 @@ function changeSearchIndex(params) {
...
@@ -167,8 +167,9 @@ function changeSearchIndex(params) {
});
});
}
}
function
loadRecord
(
name
)
{
function
loadRecord
(
name
,
calculated
=
true
)
{
return
request
(
`/record/
${
name
}
`
,
{
const
query
=
calculated
?
'?calculated=true'
:
''
;
return
request
(
`/record/
${
name
}${
query
}
`
,
{
method
:
'get'
,
method
:
'get'
,
});
});
}
}
...
@@ -219,6 +220,7 @@ function loadRecords(
...
@@ -219,6 +220,7 @@ function loadRecords(
keyword
,
keyword
,
full
,
full
,
select
,
select
,
calculated
:
true
,
},
},
});
});
}
}
...
@@ -226,7 +228,10 @@ function loadRecords(
...
@@ -226,7 +228,10 @@ function loadRecords(
function
queryRecords
(
query
)
{
function
queryRecords
(
query
)
{
return
request
(
'/record/query'
,
{
return
request
(
'/record/query'
,
{
method
:
'post'
,
method
:
'post'
,
data
:
query
,
data
:
{
...(
query
||
{}),
calculated
:
query
?.
calculated
??
true
,
},
});
});
}
}
...
...
src/services/SubjectWorkbenchService.js
View file @
fbbe3f5a
...
@@ -96,6 +96,7 @@ export async function queryAllSubjectRecords(subjectName, query = {}) {
...
@@ -96,6 +96,7 @@ export async function queryAllSubjectRecords(subjectName, query = {}) {
page
:
0
,
page
:
0
,
page_size
:
QUERY_PAGE_SIZE
,
page_size
:
QUERY_PAGE_SIZE
,
mode
:
'full'
,
mode
:
'full'
,
calculated
:
true
,
sort
:
'-updated_at -created_at'
,
sort
:
'-updated_at -created_at'
,
...
query
,
...
query
,
});
});
...
@@ -111,6 +112,7 @@ export async function queryAllSubjectRecords(subjectName, query = {}) {
...
@@ -111,6 +112,7 @@ export async function queryAllSubjectRecords(subjectName, query = {}) {
page
,
page
,
page_size
:
QUERY_PAGE_SIZE
,
page_size
:
QUERY_PAGE_SIZE
,
mode
:
'full'
,
mode
:
'full'
,
calculated
:
true
,
sort
:
'-updated_at -created_at'
,
sort
:
'-updated_at -created_at'
,
...
query
,
...
query
,
});
});
...
...
src/utils/subjectView.js
View file @
fbbe3f5a
...
@@ -185,6 +185,135 @@ export function getFieldValue(record, fieldPath) {
...
@@ -185,6 +185,135 @@ export function getFieldValue(record, fieldPath) {
return
fieldPath
.
split
(
'.'
).
reduce
((
current
,
key
)
=>
current
?.[
key
],
record
);
return
fieldPath
.
split
(
'.'
).
reduce
((
current
,
key
)
=>
current
?.[
key
],
record
);
}
}
function
isFormulaEmpty
(
value
)
{
return
value
===
undefined
||
value
===
null
||
value
===
''
;
}
function
splitFormulaArguments
(
value
)
{
const
parts
=
[];
let
current
=
''
;
let
depth
=
0
;
let
quote
=
''
;
for
(
const
character
of
String
(
value
||
''
))
{
if
(
quote
)
{
current
+=
character
;
if
(
character
===
quote
)
quote
=
''
;
continue
;
}
if
(
character
===
"'"
||
character
===
'"'
)
{
quote
=
character
;
current
+=
character
;
}
else
if
(
character
===
'('
)
{
depth
+=
1
;
current
+=
character
;
}
else
if
(
character
===
')'
)
{
depth
=
Math
.
max
(
0
,
depth
-
1
);
current
+=
character
;
}
else
if
(
character
===
','
&&
depth
===
0
)
{
parts
.
push
(
current
.
trim
());
current
=
''
;
}
else
{
current
+=
character
;
}
}
if
(
current
.
trim
()
||
parts
.
length
>
0
)
parts
.
push
(
current
.
trim
());
return
parts
;
}
function
unwrapFormulaCall
(
expression
,
functionName
)
{
const
match
=
String
(
expression
||
''
).
trim
().
match
(
new
RegExp
(
`^
${
functionName
}
\\s*\\((.*)\\)$`
,
'i'
),
);
return
match
?
match
[
1
]
:
null
;
}
function
formulaOperandValue
(
record
,
expression
)
{
const
source
=
String
(
expression
||
''
).
trim
();
if
(
!
source
)
return
undefined
;
const
quoted
=
source
.
match
(
/^
([
'"
])(
.*
)\1
$/
s
);
if
(
quoted
)
return
quoted
[
2
];
if
(
/^null$/i
.
test
(
source
))
return
null
;
if
(
/^true$/i
.
test
(
source
))
return
true
;
if
(
/^false$/i
.
test
(
source
))
return
false
;
if
(
/^-
?(?:\d
+
\.?\d
*|
\.\d
+
)
$/
.
test
(
source
))
return
Number
(
source
);
const
productArguments
=
unwrapFormulaCall
(
source
,
'PRODUCT'
);
if
(
productArguments
!==
null
)
{
const
values
=
splitFormulaArguments
(
productArguments
).
map
((
item
)
=>
formulaOperandValue
(
record
,
item
),
);
if
(
values
.
some
((
value
)
=>
isFormulaEmpty
(
value
)))
return
undefined
;
const
numbers
=
values
.
map
(
Number
);
if
(
numbers
.
some
((
value
)
=>
!
Number
.
isFinite
(
value
)))
return
undefined
;
return
numbers
.
reduce
((
result
,
value
)
=>
result
*
value
,
1
);
}
const
fieldReference
=
source
.
match
(
/^
\[([^\]]
+
)\]
$/
);
const
fieldName
=
fieldReference
?
fieldReference
[
1
].
trim
()
:
source
;
const
value
=
getFieldValue
(
record
,
fieldName
)
??
getFieldValue
(
record
,
`metadata.
${
fieldName
}
`
);
return
value
&&
typeof
value
===
'object'
&&
!
Array
.
isArray
(
value
)
?
value
.
value
??
value
.
_id
??
value
.
name
:
value
;
}
/**
* Evaluate the small, declarative formula grammar used by Subject fields.
* It intentionally supports only function calls and literals, never arbitrary
* JavaScript, so formulas from the API cannot execute code in the browser.
*/
export
function
evaluateFormula
(
formula
,
record
)
{
const
source
=
String
(
formula
||
''
).
trim
();
if
(
!
source
)
return
undefined
;
const
switchArguments
=
unwrapFormulaCall
(
source
,
'SWITCH'
);
if
(
switchArguments
!==
null
)
{
const
parts
=
splitFormulaArguments
(
switchArguments
);
if
(
parts
.
length
<
3
)
return
undefined
;
const
switchValue
=
formulaOperandValue
(
record
,
parts
[
0
]);
for
(
let
index
=
1
;
index
+
1
<
parts
.
length
;
index
+=
2
)
{
const
caseValue
=
formulaOperandValue
(
record
,
parts
[
index
]);
if
(
String
(
caseValue
)
===
String
(
switchValue
))
{
return
formulaOperandValue
(
record
,
parts
[
index
+
1
]);
}
}
return
parts
.
length
%
2
===
0
?
formulaOperandValue
(
record
,
parts
[
parts
.
length
-
1
])
:
undefined
;
}
return
formulaOperandValue
(
record
,
source
);
}
export
function
isFormulaFieldType
(
fieldType
)
{
return
normalizeText
(
fieldType
).
toLowerCase
()
===
'formula'
;
}
export
function
getFormulaExpression
(
fieldDef
)
{
return
fieldDef
?.
settings
?.
formula
||
fieldDef
?.
formula
||
''
;
}
export
function
resolveFieldValue
(
record
,
fieldPath
,
fieldDef
)
{
const
currentValue
=
getFieldValue
(
record
,
fieldPath
);
if
(
!
isFormulaFieldType
(
fieldDef
?.
type
))
return
currentValue
;
const
calculatedValue
=
evaluateFormula
(
getFormulaExpression
(
fieldDef
),
record
);
return
calculatedValue
===
undefined
?
currentValue
:
calculatedValue
;
}
export
function
applyFormulaFields
(
record
,
fields
=
[])
{
return
(
fields
||
[]).
reduce
((
nextRecord
,
field
)
=>
{
if
(
!
isFormulaFieldType
(
field
?.
type
))
return
nextRecord
;
const
fieldPath
=
getFieldPath
(
field
);
const
value
=
evaluateFormula
(
getFormulaExpression
(
field
),
nextRecord
);
return
value
===
undefined
?
nextRecord
:
setFieldValue
(
nextRecord
,
fieldPath
,
value
);
},
JSON
.
parse
(
JSON
.
stringify
(
record
||
{})));
}
export
function
setFieldValue
(
record
,
fieldPath
,
value
)
{
export
function
setFieldValue
(
record
,
fieldPath
,
value
)
{
const
nextRecord
=
JSON
.
parse
(
JSON
.
stringify
(
record
||
{}));
const
nextRecord
=
JSON
.
parse
(
JSON
.
stringify
(
record
||
{}));
const
keys
=
fieldPath
.
split
(
'.'
);
const
keys
=
fieldPath
.
split
(
'.'
);
...
...
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