Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
problem_rectification
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
problem_rectification
Commits
fa6182cc
Commit
fa6182cc
authored
Aug 26, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: align view permissions and add notifications
parent
ba8d2186
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
628 additions
and
105 deletions
+628
-105
.umirc.ts
.umirc.ts
+5
-0
xinyuan-design-system.md
docs/xinyuan-design-system.md
+3
-0
AppIcons.jsx
src/components/AppIcons.jsx
+2
-0
DashboardSidebar.jsx
src/components/DashboardSidebar.jsx
+8
-2
index.jsx
src/components/record-workbench/index.jsx
+23
-1
application.js
src/config/application.js
+2
-0
SubjectViewContent.jsx
...ages/RecordDetailDrawer/components/SubjectViewContent.jsx
+3
-3
index.jsx
src/pages/RecordDetailDrawer/index.jsx
+8
-8
Layout.jsx
src/pages/problem_rectification/Layout.jsx
+50
-1
archiveWorkbenchService.js
.../problem_rectification/archive/archiveWorkbenchService.js
+32
-22
index.jsx
src/pages/problem_rectification/list/index.jsx
+79
-11
NotificationListPage.jsx
...blem_rectification/notifications/NotificationListPage.jsx
+0
-0
notificationService.js
...roblem_rectification/notifications/notificationService.js
+180
-0
problem-rectification.business-flow.md
...blem_rectification/problem-rectification.business-flow.md
+0
-0
problemRectificationListService.js
.../problem_rectification/problemRectificationListService.js
+30
-17
record-api.md
src/services/record-api.md
+46
-0
subjectsDefinition.md
src/services/subjectsDefinition.md
+23
-0
subjectView.js
src/utils/subjectView.js
+134
-40
No files found.
.umirc.ts
View file @
fa6182cc
...
...
@@ -11,6 +11,7 @@ const ROUTE_PATHS = {
businessDashboard
:
'/integrity_risk_table/business_dashboard'
,
problemRectification
:
'/problem_rectification'
,
issueRectificationArchive
:
'/issue_rectification_archive'
,
notifications
:
'/notifications'
,
problemRectificationForm
:
'/problem_rectification_form'
,
problemRectificationFlowTable
:
'/problem_rectification_flow_table'
,
}
as
const
;
...
...
@@ -56,6 +57,10 @@ export default defineConfig({
path
:
ROUTE_PATHS
.
issueRectificationArchive
,
component
:
'./problem_rectification/archive/index'
,
},
{
path
:
ROUTE_PATHS
.
notifications
,
component
:
'./problem_rectification/notifications/NotificationListPage'
,
},
{
path
:
ROUTE_PATHS
.
problemRectificationForm
,
component
:
'./problem_rectification/form/index'
,
...
...
docs/xinyuan-design-system.md
View file @
fa6182cc
...
...
@@ -106,6 +106,9 @@ Surface 用于表达页面内的包含关系,Overlay 用于表达浮动关系
## 6. DataGrid 与分页
-
所有业务列表使用 HeroUI Pro DataGrid,原生承载选择、排序、列宽、固定列和空状态。
-
Subject View 的
`settings.shortTitle = true`
统一解释为仅隐藏
`title`
展示列;共享列解析器必须保留标题数据在查询、排序、权限和详情上下文中的作用。标题列承载的业务信息与入口必须通过
`RecordWorkbench.contextColumns`
迁移到独立、可见的上下文列,并继续在固定操作列保留关键入口;禁止因隐藏标题而同时移除明细、详情、状态查看或行操作能力。上下文列不属于
`settings.columns`
业务字段,不参与数据查询、排序或字段权限。
-
`type = "detail"`
的视图使用同一
`shortTitle`
语义:为
`true`
时隐藏详情弹框 Header 中的记录标题和字段区中的
`title`
字段;功能标题必须始终保留作为 Modal 的可访问名称。存在多个详情视图时,以当前激活视图为准并随页签切换即时更新。
-
Subject Grid 权限必须与历史多维表格一致:
`viewAuth`
只决定视图可见性,字段
`auth`
只决定编辑能力;无
`viewAuth`
的视图和无字段
`auth`
的字段对普通用户默认拒绝。系统管理员和当前业务所属历史应用的“管理”角色可旁路两类授权,但
`disableColumns`
对所有账号始终只读。授权数据只从当前历史应用对应的
`user.applications[]`
项读取,不得跨应用合并角色、用户组或权限。由 Grid 进入详情弹框时,Grid 视图是唯一字段编辑权限来源,Detail 视图不得再次叠加字段权限。
-
选择列使用
`showSelectionCheckboxes`
,不得手写选择列或在 Cell 内嵌旧 Checkbox。
-
固定列、最小列宽和横向滚动是数据可读性行为,不通过 CSS 改写 DataGrid 表头、行高、边框、背景或 Hover。
-
DataGrid 无论列数多少都占满列表可用宽度。非宽表使用 HeroUI Pro 原生列尺寸:选择列和操作列保持固定,第一业务主列使用
`1fr`
吸收剩余空间,表格内容保持
`min-width: 100%`
;只有真实最小列宽超过视口时才使用像素列宽和内部横向滚动,不允许出现表面全宽但正文只停在左侧的空白断层。
...
...
src/components/AppIcons.jsx
View file @
fa6182cc
import
BankOutlined
from
'@ant-design/icons/BankOutlined'
;
import
BarChartOutlined
from
'@ant-design/icons/BarChartOutlined'
;
import
AppstoreOutlined
from
'@ant-design/icons/AppstoreOutlined'
;
import
BellOutlined
from
'@ant-design/icons/BellOutlined'
;
import
CalendarOutlined
from
'@ant-design/icons/CalendarOutlined'
;
import
CheckOutlined
from
'@ant-design/icons/CheckOutlined'
;
import
CheckCircleOutlined
from
'@ant-design/icons/CheckCircleOutlined'
;
...
...
@@ -62,6 +63,7 @@ export const Bank = withSize(BankOutlined);
export
const
Archive
=
withSize
(
InboxOutlined
);
export
const
AppGrid
=
withSize
(
AppstoreOutlined
);
export
const
BarChart
=
withSize
(
BarChartOutlined
);
export
const
Bell
=
withSize
(
BellOutlined
);
export
const
Calendar
=
withSize
(
CalendarOutlined
);
export
const
Check
=
withSize
(
CheckOutlined
);
export
const
ChevronDown
=
withSize
(
DownOutlined
);
...
...
src/components/DashboardSidebar.jsx
View file @
fa6182cc
...
...
@@ -3,6 +3,7 @@ import xyLogoWhite from '@/assets/xy-logo-white.png';
import
{
APPLICATION_DISPLAY_NAME
}
from
'@/config/application'
;
import
{
Sidebar
}
from
'@heroui-pro/react/sidebar'
;
import
{
Avatar
,
Button
,
Dropdown
,
Label
,
Tooltip
}
from
'@heroui/react'
;
import
{
Heading
}
from
'react-aria-components'
;
import
{
AppGrid
,
LogOut
}
from
'./AppIcons'
;
function
getUserDisplay
(
userInfo
)
{
...
...
@@ -132,6 +133,11 @@ function SidebarContents({
<
Icon
size=
{
16
}
/>
</
Sidebar
.
MenuIcon
>
<
Sidebar
.
MenuLabel
>
{
item
.
name
}
</
Sidebar
.
MenuLabel
>
{
Number
(
item
.
count
)
>
0
?
(
<
Sidebar
.
MenuChip
>
{
Number
(
item
.
count
)
>
99
?
'99+'
:
Number
(
item
.
count
)
}
</
Sidebar
.
MenuChip
>
)
:
null
}
</
Sidebar
.
MenuItem
>
);
})
}
...
...
@@ -154,9 +160,9 @@ export default function DashboardSidebar(props) {
<
SidebarContents
{
...
props
}
/>
</
Sidebar
>
<
Sidebar
.
Mobile
className=
"app-sidebar-glass rc-mobile-sidebar"
>
<
h2
slot=
"title"
className=
"sr-only"
>
<
Heading
slot=
"title"
className=
"sr-only"
>
{
APPLICATION_DISPLAY_NAME
}
导航
</
h2
>
</
Heading
>
<
SidebarContents
{
...
props
}
/>
</
Sidebar
.
Mobile
>
</>
...
...
src/components/record-workbench/index.jsx
View file @
fa6182cc
...
...
@@ -459,6 +459,7 @@ export default function RecordWorkbench({
onQuery
,
title
,
columns
=
EMPTY_ARRAY
,
contextColumns
=
EMPTY_ARRAY
,
columnGroups
=
EMPTY_ARRAY
,
records
=
EMPTY_ARRAY
,
selectedIds
=
EMPTY_ARRAY
,
...
...
@@ -780,6 +781,27 @@ export default function RecordWorkbench({
},
]
: []),
...contextColumns.map((column, columnIndex) => ({
key: column.key,
title: column.title,
width: column.width || 280,
minWidth: column.minWidth || column.width || 240,
pin: column.pin,
isPrimary: column.isPrimary ?? columnIndex === 0,
render: (row) => {
if (row.isExpandedRecord || row.isExpandedPanel) {
return null;
}
return column.render?.({
record: row.record,
expandable: row.expandable,
expanded: row.expanded,
toggleExpanded: () => toggleExpandedRow(row.key),
openDetail,
});
},
})),
...stickyColumns.orderedColumns.map((column, columnIndex) => {
const stickyMeta = stickyColumns.stickyMeta[column.field];
const columnGroupId = columnGroupMeta.fieldToGroup[column.field];
...
...
@@ -808,7 +830,7 @@ export default function RecordWorkbench({
: stickyMeta?.side === 'right'
? 'end'
: undefined,
isPrimary: columnIndex === 0,
isPrimary: co
ntextColumns.length === 0 && co
lumnIndex === 0,
render: (row) => {
if (row.isExpandedPanel) {
return columnIndex === 0 ? row.expandedContent : null;
...
...
src/config/application.js
View file @
fa6182cc
export
const
APPLICATION_DISPLAY_NAME
=
'问题整改'
;
export
const
APPLICATION_BASE_PATH
=
'/problem_rectification_new/'
;
export
const
AUTHORIZATION_APPLICATION_NAME
=
'integrity_risk_manager'
;
export
const
THEME_STORAGE_KEY
=
'xinyuan_color_theme'
;
export
const
ROUTE_TITLES
=
Object
.
freeze
({
...
...
@@ -7,6 +8,7 @@ export const ROUTE_TITLES = Object.freeze({
'/integrity_risk_table/business_dashboard'
:
'整改分析'
,
'/problem_rectification'
:
'问题整改'
,
'/issue_rectification_archive'
:
'往期材料归档'
,
'/notifications'
:
'通知消息'
,
'/problem_rectification_form'
:
'整改处理'
,
'/problem_rectification_flow_table'
:
'整改流程'
,
});
src/pages/RecordDetailDrawer/components/SubjectViewContent.jsx
View file @
fa6182cc
...
...
@@ -87,9 +87,9 @@ export default function SubjectViewContent({
const
fieldType
=
field
.
fieldDef
?.
type
||
'text'
;
const
fieldLabel
=
getFieldLabel
(
field
);
const
isFullWidth
=
FULL_WIDTH_FIELD_TYPES
.
has
(
fieldType
);
const
fieldDisabled
=
isFieldDisabled
?.(
field
.
field
,
view
)
||
isFieldDisabledByView
(
field
.
field
,
view
);
const
fieldDisabled
=
isFieldDisabled
?
isFieldDisabled
(
field
.
field
,
view
)
:
isFieldDisabledByView
(
field
.
field
,
view
);
return
(
<
div
...
...
src/pages/RecordDetailDrawer/index.jsx
View file @
fa6182cc
...
...
@@ -20,9 +20,8 @@ import {
buildViewColumns
,
checkViewCondition
,
getFieldValue
,
isFieldDisabledByView
,
isViewAccessible
,
setFieldValue
,
shouldHideViewTitleColumn
,
}
from
'@/utils/subjectView'
;
const
EMPTY_ARRAY
=
[];
...
...
@@ -121,7 +120,6 @@ const RecordDetailDrawer = (props, ref) => {
const
filtered
=
(
views
||
[])
.
filter
((
view
)
=>
view
.
type
===
'detail'
)
.
filter
((
view
)
=>
(
filterView
.
length
===
0
?
true
:
filterView
.
includes
(
view
.
name
)))
.
filter
((
view
)
=>
isViewAccessible
(
view
))
.
filter
((
view
)
=>
checkViewCondition
(
view
.
settings
?.
visabled
,
remoteRecord
));
if
(
filtered
.
length
>
0
)
{
...
...
@@ -169,6 +167,7 @@ const RecordDetailDrawer = (props, ref) => {
);
const
activeView
=
detailViews
.
find
((
view
)
=>
view
.
name
===
activeTabKey
)
||
detailViews
[
0
];
const
hideRecordTitle
=
shouldHideViewTitleColumn
(
activeView
);
const
isRecordLocked
=
remoteRecord
?.
metadata
?.
is_locked
===
true
;
const
effectiveReadonly
=
readonly
||
isRecordLocked
;
const
disabledFieldSet
=
useMemo
(
...
...
@@ -180,8 +179,7 @@ const RecordDetailDrawer = (props, ref) => {
);
const
isDrawerFieldDisabled
=
(
fieldPath
,
currentView
)
=>
disabledFieldSet
.
has
(
fieldPath
)
||
isFieldDisabled
(
fieldPath
,
currentView
)
||
isFieldDisabledByView
(
fieldPath
,
authorizationView
);
isFieldDisabled
(
fieldPath
,
authorizationView
||
currentView
);
const
hasEditableFields
=
Boolean
(
editableRecord
)
&&
...
...
@@ -396,9 +394,11 @@ const RecordDetailDrawer = (props, ref) => {
<
Modal
.
Header
className=
"pr-16"
>
<
Modal
.
Heading
>
{
title
||
'问题整改详情'
}
</
Modal
.
Heading
>
<
div
className=
"truncate text-sm text-muted"
title=
{
recordTitle
}
>
{
loading
?
'正在读取记录信息'
:
recordTitle
}
</
div
>
{
!
hideRecordTitle
?
(
<
div
className=
"truncate text-sm text-muted"
title=
{
recordTitle
}
>
{
loading
?
'正在读取记录信息'
:
recordTitle
}
</
div
>
)
:
null
}
</
Modal
.
Header
>
{
detailViews
.
length
>
1
?
(
...
...
src/pages/problem_rectification/Layout.jsx
View file @
fa6182cc
...
...
@@ -6,10 +6,15 @@ import AppShell from '@/components/AppShell';
import
{
Archive
,
BarChart
,
Bell
,
CheckCircleIcon
,
Project
,
}
from
'@/components/AppIcons'
;
import
ApplicationSwitcherDialog
from
'@/pages/problem_rectification/ApplicationSwitcherDialog'
;
import
{
loadUnreadNotificationCount
,
NOTIFICATION_STATE_EVENT
,
}
from
'@/pages/problem_rectification/notifications/notificationService'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
SiteContext
}
from
'@/wrapper/Site'
;
import
{
useContext
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
...
...
@@ -40,6 +45,12 @@ const MENU_GROUPS = [
path
:
'/issue_rectification_archive'
,
icon
:
Archive
,
},
{
name
:
'通知消息'
,
path
:
'/notifications'
,
icon
:
Bell
,
showsNotificationCount
:
true
,
},
],
},
];
...
...
@@ -50,6 +61,21 @@ export default function ProblemRectificationLayout() {
const
siteInfo
=
useContext
(
SiteContext
);
const
[
sidebarOpen
,
setSidebarOpen
]
=
useState
(
true
);
const
[
isApplicationDialogOpen
,
setIsApplicationDialogOpen
]
=
useState
(
false
);
const
[
unreadNotificationCount
,
setUnreadNotificationCount
]
=
useState
(
0
);
const
menuGroups
=
useMemo
(
()
=>
MENU_GROUPS
.
map
((
group
)
=>
({
...
group
,
items
:
group
.
items
.
map
((
item
)
=>
({
...
item
,
count
:
item
.
showsNotificationCount
?
unreadNotificationCount
:
undefined
,
})),
})),
[
unreadNotificationCount
],
);
const
currentTitle
=
useMemo
(
()
=>
ROUTE_TITLES
[
location
.
pathname
]
||
APPLICATION_DISPLAY_NAME
,
...
...
@@ -60,10 +86,33 @@ export default function ProblemRectificationLayout() {
document
.
title
=
`
${
currentTitle
}
-
${
APPLICATION_DISPLAY_NAME
}
`
;
},
[
currentTitle
]);
useEffect
(()
=>
{
let
cancelled
=
false
;
const
refreshUnreadCount
=
async
()
=>
{
try
{
const
count
=
await
loadUnreadNotificationCount
();
if
(
!
cancelled
)
setUnreadNotificationCount
(
count
);
}
catch
(
error
)
{
if
(
!
cancelled
)
setUnreadNotificationCount
(
0
);
}
};
refreshUnreadCount
();
window
.
addEventListener
(
NOTIFICATION_STATE_EVENT
,
refreshUnreadCount
);
const
timer
=
window
.
setInterval
(
refreshUnreadCount
,
60000
);
return
()
=>
{
cancelled
=
true
;
window
.
removeEventListener
(
NOTIFICATION_STATE_EVENT
,
refreshUnreadCount
);
window
.
clearInterval
(
timer
);
};
},
[
location
.
pathname
]);
return
(
<>
<
AppShell
menuGroups=
{
MENU_GROUPS
}
menuGroups=
{
menuGroups
}
onLogout=
{
userInfo
?.
logout
}
onOpenApplicationSwitcher=
{
()
=>
setIsApplicationDialogOpen
(
true
)
}
open=
{
sidebarOpen
}
...
...
src/pages/problem_rectification/archive/archiveWorkbenchService.js
View file @
fa6182cc
import
{
countRecords
,
getSubjectView
,
loadRecord
,
loadSubject
,
queryRecords
}
from
'@/services/DataService'
;
import
{
buildViewColumns
,
buildViewSearchColumns
,
checkViewCondition
,
formatFieldValue
,
getFieldValue
,
...
...
@@ -29,17 +30,20 @@ export function normalizeArchiveBatchRecord(record) {
}
export
async
function
loadArchiveWorkbenchResources
()
{
const
[
archiveRecordsResult
,
archiveSubject
,
archiveViews
]
=
await
Promise
.
all
([
queryRecords
({
subject
:
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
,
page
:
0
,
page_size
:
1000
,
sort
:
'-updated_at -created_at'
,
mode
:
'full'
,
}),
const
[
archiveSubject
,
archiveViews
]
=
await
Promise
.
all
([
loadSubject
(
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
),
getSubjectView
(
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
),
]);
const
accessibleViews
=
buildArchiveGridViews
(
archiveViews
||
[]);
const
archiveRecordsResult
=
accessibleViews
.
length
>
0
?
await
queryRecords
({
subject
:
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
,
page
:
0
,
page_size
:
1000
,
sort
:
'-updated_at -created_at'
,
mode
:
'full'
,
})
:
{
records
:
[]
};
const
records
=
(
archiveRecordsResult
?.
records
||
[]).
map
(
normalizeArchiveBatchRecord
);
return
{
...
...
@@ -50,31 +54,37 @@ export async function loadArchiveWorkbenchResources() {
}
export
function
buildArchiveGridViews
(
views
=
[])
{
const
gridViews
=
views
.
filter
((
view
)
=>
view
.
type
===
'grid'
)
.
filter
((
view
)
=>
isViewAccessible
(
view
));
const
configuredGridViews
=
views
.
filter
((
view
)
=>
view
.
type
===
'grid'
);
const
gridViews
=
configuredGridViews
.
filter
((
view
)
=>
isViewAccessible
(
view
));
if
(
gridViews
.
length
>
0
)
{
if
(
gridViews
.
length
>
0
||
configuredGridViews
.
length
>
0
)
{
return
gridViews
;
}
return
[
{
title
:
'全部批次'
,
type
:
'grid'
,
name
:
'all'
,
settings
:
{},
},
];
const
fallbackView
=
{
title
:
'全部批次'
,
type
:
'grid'
,
name
:
'all'
,
settings
:
{},
};
return
isViewAccessible
(
fallbackView
)
?
[
fallbackView
]
:
[];
}
export
function
buildArchiveColumns
(
view
,
subject
)
{
return
buildViewColumns
(
view
,
subject
)
.
filter
((
column
)
=>
column
.
field
!==
'title'
||
!
column
.
hide
)
;
return
buildViewColumns
(
view
,
subject
);
}
export
function
buildArchiveConfiguredView
(
records
,
filters
,
gridView
,
subject
)
{
if
(
!
gridView
)
{
return
{
filteredRecords
:
[],
columns
:
[],
};
}
const
keyword
=
normalizeText
(
filters
?.
keyword
).
toLowerCase
();
const
columns
=
buildArchiveColumns
(
gridView
,
subject
);
const
searchColumns
=
buildViewSearchColumns
(
gridView
,
subject
);
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
const
rawRecord
=
record
.
raw
||
record
;
...
...
@@ -98,7 +108,7 @@ export function buildArchiveConfiguredView(records, filters, gridView, subject)
return
true
;
}
const
searchText
=
c
olumns
const
searchText
=
searchC
olumns
.
map
((
column
)
=>
formatFieldValue
(
getFieldValue
(
rawRecord
,
column
.
field
),
column
.
fieldDef
))
.
join
(
' '
)
.
toLowerCase
();
...
...
src/pages/problem_rectification/list/index.jsx
View file @
fa6182cc
...
...
@@ -51,6 +51,7 @@ import {
getFieldValue
,
getSelectOptions
,
normalizeText
,
shouldHideViewTitleColumn
,
}
from
'@/utils/subjectView'
;
import
{
DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE
,
...
...
@@ -895,6 +896,14 @@ export default function ProblemRectificationListPage() {
const reloadList = useCallback(
async (overrides = {}) => {
if (!subject || !activeGridView) {
setRecords([]);
setGridViewCounts({});
setPagination({
page: 0,
pageSize,
totalRecords: 0,
totalPages: 1,
});
return;
}
...
...
@@ -1628,7 +1637,7 @@ export default function ProblemRectificationListPage() {
await reloadList();
setSuccessMessage(
isDepartmentFlow
? `
推送成功,已生成
$
{
departmentAssignments
.
length
}
条部门整改明细,可从
记录标题右侧
展开查看。
`
? `
推送成功,已生成
$
{
departmentAssignments
.
length
}
条部门整改明细,可从
问题动态或标题列
展开查看。
`
: `
推送成功,已将
$
{
selectedRecords
.
length
}
条问题的
$
{
Array
.
from
(
batchDepartmentItemsByRecordId
.
values
()).
flat
().
length
}
条部门整改明细发送给填写人。
`,
);
} catch (requestError) {
...
...
@@ -1732,6 +1741,63 @@ export default function ProblemRectificationListPage() {
}}
title="问题清单"
columns={configuredView.columns || []}
contextColumns={
shouldHideViewTitleColumn(activeGridView)
? [
{
key: 'problem-context',
title: '问题动态',
width: 320,
minWidth: 320,
pin: 'start',
render: ({ record, expandable, expanded, toggleExpanded }) => {
const delayRequestDisplay = resolveDelayRequestDisplay(record);
return (
<div className="flex flex-wrap items-center gap-2">
{expandable ? (
<HeroButton
className="text-accent"
aria-expanded={expanded}
size="sm"
variant="ghost"
onPress={toggleExpanded}
>
<ChevronRightIcon
data-slot="icon"
className={clsx('transition-transform', expanded && 'rotate-90')}
/>
{expanded
? '收起问题明细'
: `
$
{
record
.
departmentItems
.
length
}
条问题明细
`}
</HeroButton>
) : (
<span className="text-sm text-muted">暂无问题明细</span>
)}
{delayRequestDisplay ? (
<HeroButton
size="sm"
variant={delayRequestDisplay.tone === 'rose' ? 'danger-soft' : 'secondary'}
title={delayRequestDisplay.title}
aria-label={`
查看
$
{
record
.
title
||
record
.
projectName
||
'当前问题'
}
的延期申请详情
`}
onPress={() => setDelayDetailRecord(record)}
>
<CalendarDaysIcon className="size-3.5 shrink-0" aria-hidden="true" />
{delayRequestDisplay.text}
</HeroButton>
) : (
<span className="text-sm text-muted">未发起延期</span>
)}
{record.isLocked ? <StatusPill text="已锁定" tone="amber" /> : null}
</div>
);
},
},
]
: []
}
columnGroups={activeGridView?.settings?.groups || []}
records={filteredRecords}
selectedIds={selectedIds}
...
...
@@ -1744,7 +1810,7 @@ export default function ProblemRectificationListPage() {
buildDepartmentDetailRecord(record, item, workflowUserLabels),
)
)}
toolbarActions={[
toolbarActions={
activeGridView ?
[
{
key: 'export',
label: '导出',
...
...
@@ -1766,8 +1832,8 @@ export default function ProblemRectificationListPage() {
tone: 'primary',
onClick: () => setShowCreateModal(true),
},
]}
bulkActions={[
]
: []
}
bulkActions={
activeGridView ?
[
{
key: 'push',
label: '推送整改',
...
...
@@ -1794,8 +1860,8 @@ export default function ProblemRectificationListPage() {
disabled: () => selectedIds.length === 0 || hasLockedSelection || actionLoading,
onClick: () => handleDelete(selectedRecords),
},
]}
rowActions={[
]
: []
}
rowActions={
activeGridView ?
[
{
key: 'view-detail',
label: '查看详情',
...
...
@@ -1843,8 +1909,8 @@ export default function ProblemRectificationListPage() {
disabled: ({ record }) => actionLoading || record.isLocked,
onClick: ({ record }) => handleDelete([record]),
},
]}
detailPanel={{
]
: []
}
detailPanel={
activeGridView ?
{
getRecordName: (record) => record.name || record.id,
onDirtyClose: reloadList,
render: ({ open, recordName, onClose, onDataChange }) => (
...
...
@@ -1875,15 +1941,17 @@ export default function ProblemRectificationListPage() {
useAuditMaterialCenter
/>
),
}}
}
: null
}
states={{
loading: loading || recordsLoading,
error,
success: '',
loadingTitle: '正在加载问题整改列表',
loadingDescription: '正在读取对象结构、视图配置和记录数据,请稍候。',
emptyTitle: '当前视图暂无数据',
emptyDescription: '可以切换其他视图,或调整检查报告出具月份、严重程度、整改评级、主责部门和重点关注筛选。',
emptyTitle: activeGridView ? '当前视图暂无数据' : '暂无可访问的视图',
emptyDescription: activeGridView
? '可以切换其他视图,或调整检查报告出具月份、严重程度、整改评级、主责部门和重点关注筛选。'
: '当前账号未获得问题整改 Grid 视图的查看权限。',
}}
pagination={{
page,
...
...
src/pages/problem_rectification/notifications/NotificationListPage.jsx
0 → 100644
View file @
fa6182cc
This diff is collapsed.
Click to expand it.
src/pages/problem_rectification/notifications/notificationService.js
0 → 100644
View file @
fa6182cc
import
{
exacteActionFunction
,
queryUserNotice
,
readUserNotice
,
unReadNoticeCount
,
}
from
'@/services/DataService'
;
export
const
NOTIFICATION_PAGE_SIZE
=
10
;
export
const
NOTIFICATION_STATE_EVENT
=
'problem-rectification:notification-state'
;
function
toArray
(
value
)
{
return
Array
.
isArray
(
value
)
?
value
:
[];
}
function
toId
(
value
)
{
if
(
value
&&
typeof
value
===
'object'
)
{
return
String
(
value
.
_id
||
value
.
id
||
value
.
name
||
''
);
}
return
String
(
value
||
''
);
}
function
escapeRegExp
(
value
)
{
return
String
(
value
||
''
).
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&'
);
}
export
async
function
loadNotificationPage
({
page
=
0
,
pageSize
=
NOTIFICATION_PAGE_SIZE
,
category
=
'message'
,
readStatus
=
'all'
,
keyword
=
''
,
}
=
{})
{
const
filter
=
{
category
};
const
normalizedKeyword
=
String
(
keyword
||
''
).
trim
();
if
(
normalizedKeyword
)
{
filter
.
content
=
{
$regex
:
escapeRegExp
(
normalizedKeyword
),
$options
:
'i'
,
};
}
const
response
=
await
queryUserNotice
(
page
,
pageSize
,
filter
,
readStatus
===
'unread'
?
'unread'
:
null
,
);
return
{
list
:
toArray
(
response
?.
list
),
page
:
Number
(
response
?.
current_page
??
page
)
||
0
,
pageSize
:
Number
(
response
?.
page_size
??
pageSize
)
||
pageSize
,
total
:
Number
(
response
?.
total_notices
??
response
?.
total
??
0
)
||
0
,
totalPages
:
Number
(
response
?.
total_pages
??
0
)
||
0
,
};
}
export
async
function
loadUnreadNotificationCount
()
{
const
response
=
await
unReadNoticeCount
();
return
Math
.
max
(
0
,
Number
(
response
?.
total_notices
??
response
?.
count
??
response
?.
total
??
response
??
0
,
)
||
0
,
);
}
export
async
function
markNotificationRead
(
name
)
{
if
(
!
name
)
{
throw
new
Error
(
'通知缺少已读标识'
);
}
await
readUserNotice
(
name
);
notifyNotificationStateChanged
();
}
export
async
function
markAllNotificationsRead
()
{
await
readUserNotice
();
notifyNotificationStateChanged
();
}
export
async
function
executeNotificationAction
(
notification
,
action
)
{
const
actionName
=
typeof
action
?.
action
===
'string'
?
action
.
action
:
action
?.
action
?.
name
||
action
?.
name
;
const
methodName
=
action
?.
method_name
||
action
?.
methodName
;
if
(
!
actionName
||
!
methodName
)
{
throw
new
Error
(
'该待办操作缺少执行配置'
);
}
const
result
=
await
exacteActionFunction
(
actionName
,
methodName
,
{
notice_id
:
notification
?.
_id
,
});
notifyNotificationStateChanged
();
return
result
;
}
export
function
isNotificationRead
(
notification
,
userId
)
{
const
normalizedUserId
=
toId
(
userId
);
if
(
!
normalizedUserId
)
return
false
;
return
toArray
(
notification
?.
data
?.
logs
).
some
(
(
log
)
=>
toId
(
log
?.
user_id
)
===
normalizedUserId
,
);
}
export
function
getNotificationSender
(
notification
)
{
return
(
notification
?.
from
?.
display_name
||
notification
?.
from
?.
username
||
notification
?.
created_by
?.
display_name
||
notification
?.
created_by
?.
username
||
'系统'
);
}
export
function
getNotificationContent
(
notification
)
{
return
String
(
notification
?.
content
||
notification
?.
content_compile
||
''
).
trim
();
}
function
trimTarget
(
target
)
{
return
String
(
target
||
''
).
replace
(
/
[
),.;!?,。;!?)
]
+$/
u
,
''
);
}
export
function
extractNotificationTarget
(
notification
)
{
const
configuredTarget
=
notification
?.
settings
?.
url
||
notification
?.
settings
?.
link
||
notification
?.
data
?.
url
||
notification
?.
data
?.
link
;
if
(
configuredTarget
)
return
trimTarget
(
configuredTarget
);
const
content
=
getNotificationContent
(
notification
);
const
match
=
content
.
match
(
/https
?
:
\/\/[^\s
<>"'
]
+|
\/
problem_rectification_new
\/[^\s
<>"'
]
+|
\/
problem_rectification
(?:
_flow_table|_form
)(?:\?[^\s
<>"'
]
*
)?
/
,
);
return
trimTarget
(
match
?.[
0
]);
}
export
function
resolveNotificationTarget
(
target
)
{
const
normalizedTarget
=
trimTarget
(
target
);
if
(
!
normalizedTarget
)
return
null
;
if
(
/^https
?
:
\/\/
/i
.
test
(
normalizedTarget
))
{
try
{
const
url
=
new
URL
(
normalizedTarget
);
if
(
url
.
pathname
.
startsWith
(
'/problem_rectification_new/'
))
{
return
resolveNotificationTarget
(
`
${
url
.
pathname
}${
url
.
search
}${
url
.
hash
}
`
);
}
return
{
href
:
url
.
toString
(),
external
:
true
};
}
catch
(
error
)
{
return
null
;
}
}
const
appPrefix
=
'/problem_rectification_new'
;
if
(
!
normalizedTarget
.
startsWith
(
`
${
appPrefix
}
/`
))
{
return
normalizedTarget
.
startsWith
(
'/'
)
?
{
href
:
normalizedTarget
,
external
:
true
}
:
null
;
}
return
{
href
:
normalizedTarget
.
slice
(
appPrefix
.
length
),
external
:
false
,
};
}
export
function
notifyNotificationStateChanged
()
{
if
(
typeof
window
!==
'undefined'
)
{
window
.
dispatchEvent
(
new
CustomEvent
(
NOTIFICATION_STATE_EVENT
));
}
}
src/pages/problem_rectification/problem-rectification.business-flow.md
View file @
fa6182cc
This diff is collapsed.
Click to expand it.
src/pages/problem_rectification/problemRectificationListService.js
View file @
fa6182cc
...
...
@@ -13,6 +13,7 @@ import {
import
{
calculateProblemSeverityLevel
}
from
'./problemSeverity'
;
import
{
buildViewColumns
,
buildViewSearchColumns
,
checkViewCondition
,
emptyValueForField
,
formatFieldValue
,
...
...
@@ -1144,28 +1145,24 @@ export async function loadProblemRectificationViewCountMap({ filters = {}, views
}
export
function
buildGridViews
(
views
=
[])
{
const
gridViews
=
views
.
filter
((
view
)
=>
view
.
type
===
'grid'
)
.
filter
((
view
)
=>
isViewAccessible
(
view
));
const
configuredGridViews
=
views
.
filter
((
view
)
=>
view
.
type
===
'grid'
);
const
gridViews
=
configuredGridViews
.
filter
((
view
)
=>
isViewAccessible
(
view
));
if
(
gridViews
.
length
>
0
)
{
if
(
gridViews
.
length
>
0
||
configuredGridViews
.
length
>
0
)
{
return
gridViews
;
}
return
[
{
title
:
'全部'
,
type
:
'grid'
,
name
:
'all'
,
settings
:
{},
},
];
const
fallbackView
=
{
title
:
'全部'
,
type
:
'grid'
,
name
:
'all'
,
settings
:
{},
};
return
isViewAccessible
(
fallbackView
)
?
[
fallbackView
]
:
[];
}
export
function
buildViewColumnsForList
(
view
,
subject
)
{
return
buildViewColumns
(
view
,
subject
).
filter
(
(
column
)
=>
column
.
field
!==
'title'
||
!
column
.
hide
,
);
return
buildViewColumns
(
view
,
subject
);
}
export
function
getRecordCellText
(
record
,
column
)
{
...
...
@@ -1180,6 +1177,21 @@ export function getRecordCellText(record, column) {
}
export
function
buildProblemRectificationConfiguredListView
(
records
,
filters
,
gridView
,
subject
)
{
if
(
!
gridView
)
{
return
{
filteredRecords
:
[],
columns
:
[],
progressOptions
:
[],
auditStatusOptions
:
[],
severityOptions
:
[],
overallRatingOptions
:
[],
primaryResponsibleDepartmentOptions
:
[],
problemFromOptions
:
[],
rootCauseOptions
:
[],
focusOptions
:
[],
};
}
const
nextKeyword
=
filters
?.
keyword
||
''
;
const
nextInspectionReportMonth
=
normalizeText
(
filters
?.
inspectionReportMonth
);
const
nextProgress
=
filters
?.
progress
||
''
;
...
...
@@ -1192,7 +1204,8 @@ export function buildProblemRectificationConfiguredListView(records, filters, gr
const
nextFocusFlag
=
filters
?.
focusFlag
||
''
;
const
columns
=
buildViewColumnsForList
(
gridView
,
subject
);
const
searchableFields
=
columns
.
map
((
column
)
=>
column
.
field
);
const
searchColumns
=
buildViewSearchColumns
(
gridView
,
subject
);
const
searchableFields
=
searchColumns
.
map
((
column
)
=>
column
.
field
);
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
if
(
...
...
@@ -1296,7 +1309,7 @@ export function buildProblemRectificationConfiguredListView(records, filters, gr
const
keyword
=
normalizeText
(
nextKeyword
).
toLowerCase
();
return
searchableFields
.
map
((
fieldPath
)
=>
normalizeText
(
formatFieldValue
(
getFieldValue
(
rawRecord
,
fieldPath
),
c
olumns
.
find
((
column
)
=>
column
.
field
===
fieldPath
)?.
fieldDef
)))
.
map
((
fieldPath
)
=>
normalizeText
(
formatFieldValue
(
getFieldValue
(
rawRecord
,
fieldPath
),
searchC
olumns
.
find
((
column
)
=>
column
.
field
===
fieldPath
)?.
fieldDef
)))
.
join
(
' '
)
.
toLowerCase
()
.
includes
(
keyword
);
...
...
src/services/record-api.md
View file @
fa6182cc
...
...
@@ -331,3 +331,49 @@ Query:`calculated=true|false`
3.
所有待建档材料创建成功后,再调用
`PUT /record/:name`
更新问题记录的
`metadata.rectification_proof_materials`
,单值写对象,多值写对象数组。
4.
任一材料创建失败时不更新问题记录;一批材料中已创建成功的条目和“材料已创建、问题更新失败”的条目都在当前编辑会话中保留建档标记,重试保存只处理尚未建档的条目,不得重复创建。
5.
用户从草稿逐项删除本地附件后,该附件不再参与材料建档;取消详情编辑不创建
`am_material`
。已经上传但未建档的二进制文件遵循上传服务自身的清理策略。
## 6. 当前用户通知接口
通知中心使用平台通知接口,不使用 Record API,也不包裹
`metadata`
。所有接口均要求当前登录用户身份。
### 6.1 分页查询
调用
`POST /notice/customer/query`
:
```
json
{
"page"
:
0
,
"page_size"
:
10
,
"filter"
:
{
"category"
:
"message"
,
"content"
:
{
"$regex"
:
"整改"
,
"$options"
:
"i"
}
},
"status"
:
"unread"
}
```
-
`category`
为
`message`
或
`todo`
。
-
内容搜索通过
`filter.content`
正则完成,前端必须转义用户输入中的正则特殊字符。
-
查询全部通知时
`status`
传
`null`
;查询未读时传
`unread`
,不得仅对当前页做前端未读过滤。
-
响应包含
`list`
、
`current_page`
、
`page_size`
、
`total_notices`
和
`total_pages`
。
### 6.2 已读与未读数量
-
`GET /notice/customer/count`
:返回当前用户的未读通知数量,侧边栏角标和“全部已读”按钮以此为准。
-
`PUT /notice/customer/read`
,请求体
`{ "name": "通知 name" }`
:标记单条通知已读。
-
`PUT /notice/customer/read`
,请求体
`{}`
:标记当前用户全部通知已读。
### 6.3 待办动作
调用
`PUT /notice/customer/:action/action/:fname`
,请求体固定传递通知记录 ID:
```
json
{
"notice_id"
:
"通知 _id"
}
```
其中
`:action`
、
`:fname`
和按钮标签均来自通知的
`actions`
配置,前端不得硬编码业务动作名称。执行成功后重新查询通知列表和未读数量;失败时保留当前列表并显示错误提示。
src/services/subjectsDefinition.md
View file @
fa6182cc
...
...
@@ -524,3 +524,26 @@ type Record = {
]
}
```
---
## 4. 当前用户通知对象
通知是平台
`/notice/customer/*`
接口返回的领域对象,不属于 Subject Record,因此不使用
`metadata`
封装。前端按以下结构消费:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
|
`_id`
| string | 通知记录 ID,执行待办动作时作为
`notice_id`
|
|
`name`
| string | 通知业务标识,标记单条已读时使用 |
|
`title`
| string | 通知标题 |
|
`category`
|
`message \| todo`
| 消息或待办分类 |
|
`content`
| string | 通知正文;前端按纯文本安全渲染 |
|
`content_compile`
| string | 编译后正文,仅在
`content`
为空时作为降级内容 |
|
`sent_at`
| ISO 8601 string | 发送时间 |
|
`from`
| object | 发送人,优先读取
`display_name`
,其次
`username`
|
|
`created_by`
| object | 创建人;发送人缺失时作为降级来源 |
|
`data.logs`
| array | 用户已读日志;
`user_id`
与当前用户
`_id`
匹配表示已读 |
|
`settings`
| object | 可选通知设置,可包含业务链接 |
|
`actions`
| array | 待办操作配置,包含按钮标签、动作名和方法名 |
通知内部业务链接可以位于
`settings.url`
、
`settings.link`
、
`data.url`
、
`data.link`
或正文中。指向
`/problem_rectification_new/*`
的站内地址进入 Umi 路由前应去除应用 base 前缀;其他应用和外部 HTTP(S) 地址使用新窗口打开。
src/utils/subjectView.js
View file @
fa6182cc
import
dayjs
from
'dayjs'
;
import
{
AUTHORIZATION_APPLICATION_NAME
}
from
'@/config/application'
;
export
function
normalizeText
(
value
)
{
return
String
(
value
??
''
).
trim
();
...
...
@@ -34,62 +35,106 @@ export function getCurrentUserFromStorage() {
}
}
function
collectUserGroups
(
user
)
{
const
directGroups
=
toArray
(
user
?.
groups
)
.
flatMap
((
group
)
=>
[
group
?.
_id
,
group
?.
name
,
group
?.
display_name
,
group
])
function
collectIdentityValues
(
values
)
{
return
toArray
(
values
)
.
flatMap
((
value
)
=>
{
if
(
value
&&
typeof
value
===
'object'
)
{
return
[
value
.
_id
,
value
.
name
,
value
.
display_name
,
value
.
displayName
,
value
.
username
,
];
}
return
value
;
})
.
map
(
normalizeText
)
.
filter
(
Boolean
);
const
appGroups
=
toArray
(
user
?.
applications
).
flatMap
((
app
)
=>
app
?.
groups
||
[]);
return
[...
directGroups
,
...
appGroups
].
map
(
normalizeText
).
filter
(
Boolean
);
}
function
collectUserRoles
(
user
)
{
const
directRoles
=
toArray
(
user
?.
roles
);
const
appRoles
=
toArray
(
user
?.
applications
).
flatMap
((
app
)
=>
app
?.
roles
||
[]);
return
[...
directRoles
,
...
appRoles
].
map
(
normalizeText
).
filter
(
Boolean
)
;
function
getUserApplication
(
user
,
applicationName
=
AUTHORIZATION_APPLICATION_NAME
)
{
return
toArray
(
user
?.
applications
).
find
(
(
application
)
=>
normalizeText
(
application
?.
name
)
===
normalizeText
(
applicationName
),
)
||
null
;
}
function
collectUserPermissions
(
user
)
{
const
directPermissions
=
toArray
(
user
?.
permissions
);
const
appPermissions
=
toArray
(
user
?.
applications
).
flatMap
((
app
)
=>
app
?.
permissions
||
[]);
return
[...
directPermissions
,
...
appPermissions
].
map
(
normalizeText
).
filter
(
Boolean
);
function
collectApplicationAuthorization
(
user
,
applicationName
)
{
const
application
=
getUserApplication
(
user
,
applicationName
);
return
{
groups
:
collectIdentityValues
(
application
?.
groups
),
roles
:
collectIdentityValues
(
application
?.
roles
),
permissions
:
collectIdentityValues
(
application
?.
permissions
),
};
}
function
intersects
(
left
,
right
)
{
const
rightSet
=
new
Set
(
toArray
(
right
).
map
(
normalizeText
).
filter
(
Boolean
));
return
toArray
(
left
).
some
((
item
)
=>
rightSet
.
has
(
normalizeText
(
item
)));
const
rightSet
=
new
Set
(
collectIdentityValues
(
right
));
return
collectIdentityValues
(
left
).
some
((
item
)
=>
rightSet
.
has
(
item
));
}
function
getMultidimensionalTableAuthRule
(
authRule
)
{
return
[
'group'
,
'role'
,
'permission'
].
includes
(
authRule
?.
type
)
?
authRule
:
'管理'
;
}
export
function
hasAuthAccess
(
authRule
,
user
=
getCurrentUserFromStorage
())
{
if
(
!
authRule
)
{
export
function
isSystemAdministrator
(
user
=
getCurrentUserFromStorage
())
{
return
user
?.
type
===
'admin'
||
user
?.
type
===
'system'
;
}
export
function
hasApplicationManagementAccess
(
user
=
getCurrentUserFromStorage
(),
applicationName
=
AUTHORIZATION_APPLICATION_NAME
,
)
{
if
(
isSystemAdministrator
(
user
))
{
return
true
;
}
const
{
roles
}
=
collectApplicationAuthorization
(
user
,
applicationName
);
return
intersects
(
roles
,
[
'管理'
]);
}
export
function
hasAuthAccess
(
authRule
,
user
=
getCurrentUserFromStorage
(),
applicationName
=
AUTHORIZATION_APPLICATION_NAME
,
)
{
if
(
!
user
)
{
return
false
;
}
if
(
user
.
type
===
'admin'
||
user
.
type
===
'system'
)
{
if
(
hasApplicationManagementAccess
(
user
,
applicationName
)
)
{
return
true
;
}
if
(
authRule
.
type
===
'group'
)
{
return
intersects
(
collectUserGroups
(
user
),
authRule
.
groups
);
const
{
groups
,
roles
,
permissions
}
=
collectApplicationAuthorization
(
user
,
applicationName
,
);
if
(
typeof
authRule
===
'string'
)
{
return
intersects
(
roles
,
[
authRule
]);
}
if
(
authRule
?.
type
===
'group'
)
{
return
intersects
(
groups
,
authRule
.
groups
);
}
if
(
authRule
.
type
===
'role'
)
{
return
intersects
(
collectUserRoles
(
user
)
,
authRule
.
roles
);
if
(
authRule
?
.
type
===
'role'
)
{
return
intersects
(
roles
,
authRule
.
roles
);
}
if
(
authRule
.
type
===
'permission'
)
{
return
intersects
(
collectUserPermissions
(
user
)
,
authRule
.
permissions
);
if
(
authRule
?
.
type
===
'permission'
)
{
return
intersects
(
permissions
,
authRule
.
permissions
);
}
if
(
authRule
.
type
===
'username'
)
{
return
intersects
([
user
.
username
,
user
.
display_name
,
user
.
_id
],
authRule
.
usernames
);
if
(
authRule
?
.
type
===
'username'
)
{
return
intersects
([
user
.
username
],
authRule
.
usernames
);
}
return
intersects
(
collectUserRoles
(
user
),
[
'管理'
])
;
return
false
;
}
export
function
getFieldValue
(
record
,
fieldPath
)
{
...
...
@@ -237,12 +282,21 @@ export function normalizeRecordLabel(value) {
return
normalizeText
(
value
);
}
export
function
shouldHideViewTitleColumn
(
view
)
{
return
view
?.
settings
?.
shortTitle
===
true
;
}
export
function
buildViewColumns
(
view
,
subject
,
{
includeHidden
=
false
}
=
{})
{
const
configuredColumns
=
view
?.
settings
?.
columns
||
[];
const
hideTitleColumn
=
!
includeHidden
&&
shouldHideViewTitleColumn
(
view
);
if
(
configuredColumns
.
length
>
0
)
{
return
configuredColumns
.
filter
((
column
)
=>
includeHidden
||
!
column
.
hide
)
.
filter
(
(
column
)
=>
(
includeHidden
||
!
column
.
hide
)
&&
(
!
hideTitleColumn
||
column
.
field
!==
'title'
),
)
.
map
((
column
)
=>
{
const
fieldDef
=
getFieldDefinition
(
column
.
field
,
subject
);
return
{
...
...
@@ -255,12 +309,14 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
}
const
defaultColumns
=
[];
defaultColumns
.
push
({
field
:
'title'
,
width
:
180
,
label
:
'标题'
,
fieldDef
:
{
name
:
'title'
,
label
:
'标题'
,
type
:
'text'
},
});
if
(
!
hideTitleColumn
)
{
defaultColumns
.
push
({
field
:
'title'
,
width
:
180
,
label
:
'标题'
,
fieldDef
:
{
name
:
'title'
,
label
:
'标题'
,
type
:
'text'
},
});
}
toArray
(
subject
?.
fields
).
forEach
((
field
)
=>
{
defaultColumns
.
push
({
...
...
@@ -274,6 +330,23 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
return
defaultColumns
;
}
export
function
buildViewSearchColumns
(
view
,
subject
)
{
const
displayColumns
=
buildViewColumns
(
view
,
subject
);
if
(
displayColumns
.
some
((
column
)
=>
column
.
field
===
'title'
))
{
return
displayColumns
;
}
return
[
{
field
:
'title'
,
width
:
180
,
label
:
'标题'
,
fieldDef
:
getFieldDefinition
(
'title'
,
subject
),
},
...
displayColumns
,
];
}
function
compareRuleValue
(
recordValue
,
comparator
,
expectedValue
)
{
const
expectedValues
=
toArray
(
expectedValue
).
map
(
normalizeText
);
const
recordValues
=
toArray
(
recordValue
).
map
((
item
)
=>
{
...
...
@@ -332,18 +405,39 @@ export function checkViewCondition(condition, record) {
:
results
.
every
(
Boolean
);
}
export
function
isViewAccessible
(
view
,
user
=
getCurrentUserFromStorage
())
{
return
hasAuthAccess
(
view
?.
settings
?.
viewAuth
,
user
);
}
export
function
isFieldDisabledByView
(
fieldPath
,
view
,
user
=
getCurrentUserFromStorage
())
{
export
function
isViewAccessible
(
view
,
user
=
getCurrentUserFromStorage
(),
applicationName
=
AUTHORIZATION_APPLICATION_NAME
,
)
{
return
hasAuthAccess
(
getMultidimensionalTableAuthRule
(
view
?.
settings
?.
viewAuth
),
user
,
applicationName
,
);
}
export
function
isFieldDisabledByView
(
fieldPath
,
view
,
user
=
getCurrentUserFromStorage
(),
applicationName
=
AUTHORIZATION_APPLICATION_NAME
,
)
{
const
disabledColumn
=
toArray
(
view
?.
settings
?.
disableColumns
).
some
((
column
)
=>
column
.
field
===
fieldPath
);
if
(
disabledColumn
)
{
return
true
;
}
if
(
hasApplicationManagementAccess
(
user
,
applicationName
))
{
return
false
;
}
const
fieldAuthRule
=
toArray
(
view
?.
settings
?.
auth
).
find
((
rule
)
=>
rule
.
field
===
fieldPath
);
return
fieldAuthRule
?
!
hasAuthAccess
(
fieldAuthRule
,
user
)
:
false
;
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