Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
internal_regulation_manager_new
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
internal_regulation_manager_new
Commits
3e6db798
Commit
3e6db798
authored
Oct 08, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 统一操作反馈与失败重试并优化拓扑节点详情
parent
ba3d23a8
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
422 additions
and
96 deletions
+422
-96
xinyuan-design-system.md
docs/xinyuan-design-system.md
+6
-0
manifest.json
public/manifest.json
+1
-1
app.js
src/app.js
+7
-1
AppFeedback.jsx
src/components/AppFeedback.jsx
+42
-0
AppShell.jsx
src/components/AppShell.jsx
+0
-2
RequestFailure.jsx
src/components/RequestFailure.jsx
+18
-0
feedback.css
src/design-system/feedback.css
+11
-0
useFeedbackState.js
src/hooks/useFeedbackState.js
+38
-0
Login.jsx
src/pages/Login.jsx
+3
-10
ArchiveList.jsx
src/pages/internal_regulation_manager/ArchiveList.jsx
+19
-4
GraphNodeDetails.jsx
src/pages/internal_regulation_manager/GraphNodeDetails.jsx
+138
-0
InternalRegulationDetail.jsx
.../internal_regulation_manager/InternalRegulationDetail.jsx
+0
-0
InternalRegulationManager.jsx
...internal_regulation_manager/InternalRegulationManager.jsx
+7
-3
RegulationGraph.jsx
src/pages/internal_regulation_manager/RegulationGraph.jsx
+0
-0
RegulationList.jsx
src/pages/internal_regulation_manager/RegulationList.jsx
+13
-7
RevisionDetail.jsx
src/pages/internal_regulation_manager/RevisionDetail.jsx
+9
-5
RevisionList.jsx
src/pages/internal_regulation_manager/RevisionList.jsx
+0
-0
internal-regulation.business-flow.md
...l_regulation_manager/internal-regulation.business-flow.md
+0
-0
ApplicationSwitcherDialog.jsx
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
+4
-10
ForwardApprovals.jsx
src/pages/project_manager_xy/ForwardApprovals.jsx
+17
-7
ModelBatchData.jsx
src/pages/project_manager_xy/ModelBatchData.jsx
+12
-7
ModelBatches.jsx
src/pages/project_manager_xy/ModelBatches.jsx
+13
-7
ModelMonitorShared.jsx
src/pages/project_manager_xy/ModelMonitorShared.jsx
+11
-1
ModelOverview.jsx
src/pages/project_manager_xy/ModelOverview.jsx
+11
-5
Models.jsx
src/pages/project_manager_xy/Models.jsx
+18
-10
model-monitor.business-flow.md
src/pages/project_manager_xy/model-monitor.business-flow.md
+8
-0
NotificationListPage.jsx
...project_manager_xy/notifications/NotificationListPage.jsx
+7
-15
notifications.business-flow.md
...t_manager_xy/notifications/notifications.business-flow.md
+6
-0
tailwind.css
src/tailwind.css
+1
-0
Site.jsx
src/wrapper/Site.jsx
+2
-1
No files found.
docs/xinyuan-design-system.md
View file @
3e6db798
...
@@ -53,3 +53,9 @@ AppShell 采用低饱和深藏蓝侧栏与中性灰白内容画布。`theme.css`
...
@@ -53,3 +53,9 @@ AppShell 采用低饱和深藏蓝侧栏与中性灰白内容画布。`theme.css`
-
Reference 写入仍只保存 ID 或现有页面协议对象;枚举来自
`settings.options`
。
-
Reference 写入仍只保存 ID 或现有页面协议对象;枚举来自
`settings.options`
。
-
DatePicker 保持既有 service 转 ISO 流程;动态 Subject 继续使用现有
`loadSubject`
/subject definition。
-
DatePicker 保持既有 service 转 ISO 流程;动态 Subject 继续使用现有
`loadSubject`
/subject definition。
-
UI 迁移不得改变 API、路由、权限、校验、保存回调、请求时机、分页口径或审批状态。
-
UI 迁移不得改变 API、路由、权限、校验、保存回调、请求时机、分页口径或审批状态。
## 操作反馈浮层
-
全应用统一由根部
`AppFeedback`
挂载 HeroUI
`Toast.Provider`
和官方 compound 组件,登录和 AppShell 共用一个 Provider;接口成功/失败/降级警告不再渲染页面横条。
-
成功默认 4 秒、错误和警告默认 8 秒后消失;关闭只改变提示展示。详细加载错误通过 Toast 展示,失败空状态使用 HeroUI Pro EmptyState,并提供读取重试。
-
操作提示沿用问题整改
`bfc5862`
:关闭按钮直接组合 HeroUI
`Button`
(
`ghost`
、
`sm`
、
`isIconOnly`
),通过官方
`slot="close"`
接入关闭行为,使用 16px
`AppIcons.Close`
,位于正文右侧并参与正常布局。当前提示关闭图标始终可见,后排堆叠提示隐藏关闭按钮,避免误关闭;移除旧关闭按钮的边框、绝对定位和正文右侧占位。
`src/design-system/feedback.css`
只保留层级 300、安全区顶部和后排关闭按钮可见性约定;颜色、圆角、阴影、Hover 与焦点样式使用 HeroUI 默认主题。
public/manifest.json
View file @
3e6db798
{
{
"name"
:
"internal_regulation_manager_new"
,
"name"
:
"internal_regulation_manager_new"
,
"version"
:
"1.0.
2
"
,
"version"
:
"1.0.
4
"
,
"build"
:
"abc"
,
"build"
:
"abc"
,
"description"
:
"内部制度管理"
,
"description"
:
"内部制度管理"
,
"date"
:
"2025-07-14 19:27:20"
,
"date"
:
"2025-07-14 19:27:20"
,
...
...
src/app.js
View file @
3e6db798
import
'@/global.less'
;
import
'@/global.less'
;
import
'@/tailwind.css'
;
import
'@/tailwind.css'
;
import
{
defineApp
}
from
'umi'
;
import
{
defineApp
}
from
'umi'
;
import
{
createElement
}
from
'react'
;
import
AppFeedback
from
'@/components/AppFeedback'
;
if
(
typeof
document
!==
'undefined'
)
{
if
(
typeof
document
!==
'undefined'
)
{
document
.
documentElement
.
classList
.
remove
(
'dark'
);
document
.
documentElement
.
classList
.
remove
(
'dark'
);
...
@@ -15,4 +17,8 @@ export async function getInitialState() {
...
@@ -15,4 +17,8 @@ export async function getInitialState() {
};
};
}
}
export
default
defineApp
({});
export
function
rootContainer
(
container
)
{
return
createElement
(
AppFeedback
,
null
,
container
);
}
export
default
defineApp
({
rootContainer
});
src/components/AppFeedback.jsx
0 → 100644
View file @
3e6db798
import
{
Button
,
Toast
}
from
'@heroui/react'
;
import
{
Close
}
from
'@/components/AppIcons'
;
export
default
function
AppFeedback
({
children
})
{
return
(
<>
{
children
}
<
Toast
.
Provider
aria
-
label=
"操作提示"
className=
"app-feedback-region"
maxVisibleToasts=
{
3
}
placement=
"top"
width=
"min(30rem, calc(100vw - 2rem))"
>
{
({
toast
})
=>
(
<
Toast
className=
"app-feedback-toast gap-2"
toast=
{
toast
}
variant=
{
toast
.
content
.
variant
}
>
<
Toast
.
Indicator
variant=
{
toast
.
content
.
variant
}
/>
<
Toast
.
Content
className=
"min-w-0"
>
<
Toast
.
Title
>
{
toast
.
content
.
title
}
</
Toast
.
Title
>
{
toast
.
content
.
description
?
(
<
Toast
.
Description
className=
"max-h-[40dvh] overflow-y-auto whitespace-pre-wrap break-words text-foreground"
>
{
toast
.
content
.
description
}
</
Toast
.
Description
>
)
:
null
}
{
toast
.
content
.
actionProps
?
<
Toast
.
ActionButton
{
...
toast
.
content
.
actionProps
}
/>
:
null
}
</
Toast
.
Content
>
<
Button
aria
-
label=
"关闭提示"
className=
"app-feedback-close shrink-0 self-start"
isIconOnly
size=
"sm"
slot=
"close"
variant=
"ghost"
>
<
Close
size=
{
16
}
/>
</
Button
>
</
Toast
>
)
}
</
Toast
.
Provider
>
</>
);
}
src/components/AppShell.jsx
View file @
3e6db798
import
{
Sidebar
}
from
'@heroui-pro/react/sidebar'
;
import
{
Sidebar
}
from
'@heroui-pro/react/sidebar'
;
import
{
Toast
}
from
'@heroui/react'
;
import
{
history
}
from
'umi'
;
import
{
history
}
from
'umi'
;
import
DashboardNavbar
from
'./DashboardNavbar'
;
import
DashboardNavbar
from
'./DashboardNavbar'
;
import
DashboardSidebar
from
'./DashboardSidebar'
;
import
DashboardSidebar
from
'./DashboardSidebar'
;
...
@@ -34,7 +33,6 @@ export default function AppShell({
...
@@ -34,7 +33,6 @@ export default function AppShell({
<
div
className=
"rc-app-page"
>
{
children
}
</
div
>
<
div
className=
"rc-app-page"
>
{
children
}
</
div
>
</
div
>
</
div
>
</
Sidebar
.
Main
>
</
Sidebar
.
Main
>
<
Toast
.
Provider
maxVisibleToasts=
{
3
}
placement=
"top"
/>
</
Sidebar
.
Provider
>
</
Sidebar
.
Provider
>
);
);
}
}
src/components/RequestFailure.jsx
0 → 100644
View file @
3e6db798
import
{
Button
}
from
'@heroui/react'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
export
default
function
RequestFailure
({
title
=
'加载失败'
,
onRetry
})
{
return
(
<
EmptyState
className=
"min-h-48"
size=
"md"
>
<
EmptyState
.
Header
>
<
EmptyState
.
Title
>
{
title
}
</
EmptyState
.
Title
>
<
EmptyState
.
Description
>
详细错误见操作提示,请重新加载。
</
EmptyState
.
Description
>
</
EmptyState
.
Header
>
{
onRetry
?
(
<
EmptyState
.
Content
>
<
Button
variant=
"secondary"
onPress=
{
onRetry
}
>
重新加载
</
Button
>
</
EmptyState
.
Content
>
)
:
null
}
</
EmptyState
>
);
}
src/design-system/feedback.css
0 → 100644
View file @
3e6db798
/* Keep operation feedback above Modal and the mobile sidebar, outside page scrolling. */
.app-feedback-region.toast-region
{
z-index
:
300
;
top
:
max
(
1rem
,
env
(
safe-area-inset-top
));
}
/* Only the front toast is interactive; its ghost close button stays visible without hover. */
.app-feedback-toast
:not
([
data-frontmost
=
"true"
])
.app-feedback-close
{
visibility
:
hidden
;
pointer-events
:
none
;
}
src/hooks/useFeedbackState.js
0 → 100644
View file @
3e6db798
import
{
toast
}
from
'@heroui/react'
;
import
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
'react'
;
const
FEEDBACK_OPTIONS
=
{
error
:
{
title
:
'操作失败'
,
variant
:
'danger'
,
timeout
:
8000
},
success
:
{
title
:
'操作成功'
,
variant
:
'success'
,
timeout
:
4000
},
warning
:
{
title
:
'请注意'
,
variant
:
'warning'
,
timeout
:
8000
},
};
// Dismissing feedback must not clear the request's failure state or the form draft.
export
default
function
useFeedbackState
(
tone
=
'error'
)
{
const
[
message
,
setMessage
]
=
useState
(
''
);
const
toastKeyRef
=
useRef
(
null
);
const
isMountedRef
=
useRef
(
true
);
const
updateMessage
=
useCallback
((
nextMessage
)
=>
{
if
(
!
isMountedRef
.
current
)
return
;
if
(
toastKeyRef
.
current
)
toast
.
close
(
toastKeyRef
.
current
);
toastKeyRef
.
current
=
null
;
setMessage
(
nextMessage
);
if
(
!
nextMessage
)
return
;
const
{
title
,
variant
,
timeout
}
=
FEEDBACK_OPTIONS
[
tone
]
||
FEEDBACK_OPTIONS
.
error
;
// Every attempt reports feedback, including identical errors on a retry.
toastKeyRef
.
current
=
toast
(
title
,
{
description
:
nextMessage
,
variant
,
timeout
});
},
[
tone
]);
useEffect
(()
=>
{
isMountedRef
.
current
=
true
;
return
()
=>
{
isMountedRef
.
current
=
false
;
if
(
toastKeyRef
.
current
)
toast
.
close
(
toastKeyRef
.
current
);
toastKeyRef
.
current
=
null
;
};
},
[]);
return
[
message
,
updateMessage
];
}
src/pages/Login.jsx
View file @
3e6db798
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
xinyuanLogo
from
'@/assets/xy-logo-red.png'
;
import
xinyuanLogo
from
'@/assets/xy-logo-red.png'
;
import
xinyuanLogoWhite
from
'@/assets/xy-logo-white.png'
;
import
xinyuanLogoWhite
from
'@/assets/xy-logo-white.png'
;
import
{
APP_DISPLAY_NAME
}
from
'@/constants/app'
;
import
{
APP_DISPLAY_NAME
}
from
'@/constants/app'
;
import
authService
from
'@/services/authService'
;
import
authService
from
'@/services/authService'
;
import
{
getCaptcha
,
userLogin
}
from
'@/services/DataService'
;
import
{
getCaptcha
,
userLogin
}
from
'@/services/DataService'
;
import
{
SiteContext
}
from
'@/wrapper/Site'
;
import
{
SiteContext
}
from
'@/wrapper/Site'
;
import
{
Alert
,
Button
,
Card
,
Input
,
Label
,
TextField
}
from
'@heroui/react'
;
import
{
Button
,
Card
,
Input
,
Label
,
TextField
}
from
'@heroui/react'
;
import
CryptoJS
from
'crypto-js'
;
import
CryptoJS
from
'crypto-js'
;
import
{
useContext
,
useEffect
,
useState
}
from
'react'
;
import
{
useContext
,
useEffect
,
useState
}
from
'react'
;
import
{
history
,
useSearchParams
}
from
'umi'
;
import
{
history
,
useSearchParams
}
from
'umi'
;
...
@@ -26,7 +27,7 @@ export default function LoginPage() {
...
@@ -26,7 +27,7 @@ export default function LoginPage() {
const
[
captchaData
,
setCaptchaData
]
=
useState
(
null
);
const
[
captchaData
,
setCaptchaData
]
=
useState
(
null
);
const
[
captchaLoading
,
setCaptchaLoading
]
=
useState
(
false
);
const
[
captchaLoading
,
setCaptchaLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
'
'
);
const
[
,
setError
]
=
useFeedbackState
(
'error
'
);
const
redirect
=
searchParams
.
get
(
'redirect'
)
||
'/'
;
const
redirect
=
searchParams
.
get
(
'redirect'
)
||
'/'
;
const
refreshCaptcha
=
async
()
=>
{
const
refreshCaptcha
=
async
()
=>
{
...
@@ -171,14 +172,6 @@ export default function LoginPage() {
...
@@ -171,14 +172,6 @@ export default function LoginPage() {
</
div
>
</
div
>
</
div
>
</
div
>
{
error
?
(
<
Alert
className=
"login-error"
status=
"danger"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
)
:
null
}
<
Button
<
Button
className=
"login-submit-button"
className=
"login-submit-button"
fullWidth
fullWidth
...
...
src/pages/internal_regulation_manager/ArchiveList.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
Button
,
Input
,
Label
,
Spinner
,
TextField
}
from
'@heroui/react'
;
import
{
Button
,
Input
,
Label
,
Spinner
,
TextField
}
from
'@heroui/react'
;
import
{
Download
,
Search
}
from
'@/components/AppIcons'
;
import
{
Download
,
Search
}
from
'@/components/AppIcons'
;
import
ListPagination
from
'@/components/ListPagination'
;
import
ListPagination
from
'@/components/ListPagination'
;
...
@@ -18,16 +20,29 @@ export default function ArchiveList() {
...
@@ -18,16 +20,29 @@ export default function ArchiveList() {
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
totalPages
,
setTotalPages
]
=
useState
(
1
);
const
[
totalPages
,
setTotalPages
]
=
useState
(
1
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[,
setOptionsError
]
=
useFeedbackState
(
'warning'
);
const
refresh
=
async
()
=>
{
const
refresh
=
async
()
=>
{
try
{
try
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
const
result
=
await
loadArchivedRegulations
({
archive
,
keyword
,
page
,
pageSize
});
const
result
=
await
loadArchivedRegulations
({
archive
,
keyword
,
page
,
pageSize
});
setRecords
(
result
.
records
);
setTotal
(
result
.
total
);
setTotalPages
(
result
.
totalPages
);
setError
(
''
);
setRecords
(
result
.
records
);
setTotal
(
result
.
total
);
setTotalPages
(
result
.
totalPages
);
setError
(
''
);
}
catch
(
loadError
)
{
setError
(
loadError
?.
message
||
'归档历史加载失败。'
);
}
finally
{
setLoading
(
false
);
}
}
catch
(
loadError
)
{
set
LoadFailed
(
true
);
set
Error
(
loadError
?.
message
||
'归档历史加载失败。'
);
}
finally
{
setLoading
(
false
);
}
};
};
useEffect
(()
=>
{
loadArchiveOptions
().
then
((
values
)
=>
setArchiveOptions
(
values
.
map
((
value
)
=>
({
value
,
label
:
value
})))).
catch
(()
=>
setArchiveOptions
([]));
},
[]);
const
refreshOptions
=
async
()
=>
{
try
{
const
values
=
await
loadArchiveOptions
();
setArchiveOptions
(
values
.
map
((
value
)
=>
({
value
,
label
:
value
})));
setOptionsError
(
''
);
}
catch
(
requestError
)
{
setOptionsError
(
requestError
?.
message
||
'归档批次列表加载失败。'
);
}
};
useEffect
(()
=>
{
refreshOptions
();
},
[]);
const
changePageSize
=
(
nextPageSize
)
=>
{
setPageSize
(
nextPageSize
);
setPage
(
0
);
};
const
changePageSize
=
(
nextPageSize
)
=>
{
setPageSize
(
nextPageSize
);
setPage
(
0
);
};
useEffect
(()
=>
{
refresh
();
},
[
archive
,
keyword
,
page
,
pageSize
]);
useEffect
(()
=>
{
refresh
();
},
[
archive
,
keyword
,
page
,
pageSize
]);
...
@@ -53,5 +68,5 @@ export default function ArchiveList() {
...
@@ -53,5 +68,5 @@ export default function ArchiveList() {
}
catch
(
exportError
)
{
setError
(
exportError
?.
message
||
'导出失败。'
);
}
}
catch
(
exportError
)
{
setError
(
exportError
?.
message
||
'导出失败。'
);
}
};
};
return
<
div
className=
"space-y-4"
><
div
className=
"flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between"
><
div
className=
"flex flex-col gap-3 sm:flex-row sm:items-end"
><
XinyuanAutocomplete
className=
"w-full sm:w-56"
label=
"归档批次"
options=
{
[{
value
:
''
,
label
:
'全部归档'
},
...
archiveOptions
]
}
selectedKey=
{
archive
}
onSelectionChange=
{
(
value
)
=>
{
setArchive
(
String
(
value
||
''
));
setPage
(
0
);
}
}
/><
div
className=
"flex w-full items-end gap-2 sm:w-72"
><
TextField
className=
"min-w-0 flex-1"
fullWidth
value=
{
searchText
}
onChange=
{
setSearchText
}
onKeyDown=
{
(
event
)
=>
{
if
(
event
.
key
===
'Enter'
)
{
event
.
preventDefault
();
submitSearch
();
}
}
}
><
Label
className=
"sr-only"
>
搜索归档制度
</
Label
><
div
className=
"relative"
><
Search
className=
"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted"
size=
{
16
}
/><
Input
className=
"pl-9"
fullWidth
placeholder=
"搜索制度名称"
variant=
"secondary"
/></
div
></
TextField
><
Button
isDisabled=
{
loading
}
variant=
"secondary"
onPress=
{
submitSearch
}
>
查询
</
Button
></
div
></
div
><
div
className=
"flex items-center gap-3"
><
span
className=
"text-sm text-muted"
>
归档记录只读展示,不会修改现行制度
</
span
><
Button
variant=
"tertiary"
onPress=
{
exportList
}
><
Download
size=
{
16
}
/>
批量下载
</
Button
></
div
></
div
>
{
error
?
<
div
className=
"rounded-xl border border-danger/30 bg-danger/5 p-3 text-sm text-danger"
>
{
error
}
</
div
>
:
null
}{
loading
?
<
div
className=
"flex min-h-48 items-center justify-center"
><
Spinner
label=
"正在加载归档记录..."
/></
div
>
:
records
.
length
===
0
?
<
div
className=
"rounded-2xl border border-dashed border-separator p-10 text-center text-sm text-muted"
>
暂无归档记录
</
div
>
:
<
div
className=
"overflow-x-auto rounded-2xl border border-separator"
><
table
className=
"w-full min-w-[900px] text-left text-sm"
><
thead
className=
"bg-surface-secondary text-muted"
><
tr
>
{
[
'制度名称'
,
'时效性'
,
'效力位阶'
,
'审批主体'
,
'制定部门'
,
'发文日期'
,
'归档批次'
,
'操作'
].
map
((
title
)
=>
<
th
className=
"px-4 py-3 font-medium"
key=
{
title
}
>
{
title
}
</
th
>)
}
</
tr
></
thead
><
tbody
className=
"divide-y divide-separator"
>
{
records
.
map
((
record
)
=>
{
const
metadata
=
record
.
metadata
||
{};
return
<
tr
key=
{
record
.
_id
||
record
.
name
}
><
td
className=
"px-4 py-3"
><
button
className=
"font-medium text-accent hover:underline"
onClick=
{
()
=>
history
.
push
(
`/internal_regulation/${metadata.origin_regulation_id || record.name}`
)
}
>
{
metadata
.
regulation_name
||
record
.
title
||
'-'
}
</
button
></
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
effective_status
||
'-'
}
</
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
level
||
'-'
}
</
td
><
td
className=
"max-w-48 truncate px-4 py-3"
>
{
Array
.
isArray
(
metadata
.
approve
)
?
metadata
.
approve
.
join
(
'、'
)
:
metadata
.
approve
||
'-'
}
</
td
><
td
className=
"max-w-48 truncate px-4 py-3"
>
{
Array
.
isArray
(
metadata
.
departments
)
?
metadata
.
departments
.
join
(
'、'
)
:
metadata
.
departments
||
'-'
}
</
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
public_date
?
new
Date
(
metadata
.
public_date
).
toLocaleDateString
(
'zh-CN'
)
:
'-'
}
</
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
archive
||
'-'
}
</
td
><
td
className=
"px-4 py-3"
><
Button
size=
"sm"
variant=
"tertiary"
onPress=
{
()
=>
history
.
push
(
`/internal_regulation/${metadata.origin_regulation_id || record.name}`
)
}
>
查看详情
</
Button
></
td
></
tr
>;
})
}
</
tbody
></
table
></
div
>
}
<
ListPagination
isDisabled=
{
loading
}
page=
{
page
}
pageSize=
{
pageSize
}
total=
{
total
}
totalPages=
{
totalPages
}
onPageChange=
{
setPage
}
onPageSizeChange=
{
changePageSize
}
></
ListPagination
></
div
>;
return
<
div
className=
"space-y-4"
><
div
className=
"flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between"
><
div
className=
"flex flex-col gap-3 sm:flex-row sm:items-end"
><
XinyuanAutocomplete
className=
"w-full sm:w-56"
label=
"归档批次"
options=
{
[{
value
:
''
,
label
:
'全部归档'
},
...
archiveOptions
]
}
selectedKey=
{
archive
}
onSelectionChange=
{
(
value
)
=>
{
setArchive
(
String
(
value
||
''
));
setPage
(
0
);
}
}
/><
div
className=
"flex w-full items-end gap-2 sm:w-72"
><
TextField
className=
"min-w-0 flex-1"
fullWidth
value=
{
searchText
}
onChange=
{
setSearchText
}
onKeyDown=
{
(
event
)
=>
{
if
(
event
.
key
===
'Enter'
)
{
event
.
preventDefault
();
submitSearch
();
}
}
}
><
Label
className=
"sr-only"
>
搜索归档制度
</
Label
><
div
className=
"relative"
><
Search
className=
"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted"
size=
{
16
}
/><
Input
className=
"pl-9"
fullWidth
placeholder=
"搜索制度名称"
variant=
"secondary"
/></
div
></
TextField
><
Button
isDisabled=
{
loading
}
variant=
"secondary"
onPress=
{
submitSearch
}
>
查询
</
Button
></
div
></
div
><
div
className=
"flex items-center gap-3"
><
span
className=
"text-sm text-muted"
>
归档记录只读展示,不会修改现行制度
</
span
><
Button
variant=
"tertiary"
onPress=
{
exportList
}
><
Download
size=
{
16
}
/>
批量下载
</
Button
></
div
></
div
>
{
loading
?
<
div
className=
"flex min-h-48 items-center justify-center"
><
Spinner
label=
"正在加载归档记录..."
/></
div
>
:
loadFailed
?
<
RequestFailure
title=
"归档历史加载失败"
onRetry=
{
refresh
}
/>
:
records
.
length
===
0
?
<
div
className=
"rounded-2xl border border-dashed border-separator p-10 text-center text-sm text-muted"
>
暂无归档记录
</
div
>
:
<
div
className=
"overflow-x-auto rounded-2xl border border-separator"
><
table
className=
"w-full min-w-[900px] text-left text-sm"
><
thead
className=
"bg-surface-secondary text-muted"
><
tr
>
{
[
'制度名称'
,
'时效性'
,
'效力位阶'
,
'审批主体'
,
'制定部门'
,
'发文日期'
,
'归档批次'
,
'操作'
].
map
((
title
)
=>
<
th
className=
"px-4 py-3 font-medium"
key=
{
title
}
>
{
title
}
</
th
>)
}
</
tr
></
thead
><
tbody
className=
"divide-y divide-separator"
>
{
records
.
map
((
record
)
=>
{
const
metadata
=
record
.
metadata
||
{};
return
<
tr
key=
{
record
.
_id
||
record
.
name
}
><
td
className=
"px-4 py-3"
><
button
className=
"font-medium text-accent hover:underline"
onClick=
{
()
=>
history
.
push
(
`/internal_regulation/${metadata.origin_regulation_id || record.name}`
)
}
>
{
metadata
.
regulation_name
||
record
.
title
||
'-'
}
</
button
></
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
effective_status
||
'-'
}
</
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
level
||
'-'
}
</
td
><
td
className=
"max-w-48 truncate px-4 py-3"
>
{
Array
.
isArray
(
metadata
.
approve
)
?
metadata
.
approve
.
join
(
'、'
)
:
metadata
.
approve
||
'-'
}
</
td
><
td
className=
"max-w-48 truncate px-4 py-3"
>
{
Array
.
isArray
(
metadata
.
departments
)
?
metadata
.
departments
.
join
(
'、'
)
:
metadata
.
departments
||
'-'
}
</
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
public_date
?
new
Date
(
metadata
.
public_date
).
toLocaleDateString
(
'zh-CN'
)
:
'-'
}
</
td
><
td
className=
"px-4 py-3"
>
{
metadata
.
archive
||
'-'
}
</
td
><
td
className=
"px-4 py-3"
><
Button
size=
"sm"
variant=
"tertiary"
onPress=
{
()
=>
history
.
push
(
`/internal_regulation/${metadata.origin_regulation_id || record.name}`
)
}
>
查看详情
</
Button
></
td
></
tr
>;
})
}
</
tbody
></
table
></
div
>
}
<
ListPagination
isDisabled=
{
loading
||
loadFailed
}
page=
{
page
}
pageSize=
{
pageSize
}
total=
{
total
}
totalPages=
{
totalPages
}
onPageChange=
{
setPage
}
onPageSizeChange=
{
changePageSize
}
></
ListPagination
></
div
>;
}
}
src/pages/internal_regulation_manager/GraphNodeDetails.jsx
0 → 100644
View file @
3e6db798
import
{
Button
,
Chip
,
Input
,
Label
,
Tabs
,
TextArea
,
TextField
}
from
'@heroui/react'
;
import
{
ChevronDown
,
ChevronRight
,
Close
,
Delete
,
OpenExternal
,
PlusIcon
,
Reload
}
from
'@/components/AppIcons'
;
function
NodePath
({
items
,
emptyText
})
{
if
(
!
items
.
length
)
return
<
p
className=
"py-3 text-sm text-muted"
>
{
emptyText
}
</
p
>;
return
(
<
ol
className=
"space-y-2"
>
{
items
.
map
((
item
,
index
)
=>
(
<
li
className=
"flex items-start gap-2 text-sm leading-6"
key=
{
item
.
id
}
>
<
span
className=
"grid size-6 shrink-0 place-items-center rounded-md bg-surface-secondary text-xs text-muted"
>
{
index
+
1
}
</
span
>
<
span
className=
"min-w-0 break-words"
>
{
item
.
title
}
</
span
>
</
li
>
))
}
</
ol
>
);
}
export
default
function
GraphNodeDetails
({
node
,
typeLabel
,
status
,
isCollapsed
,
isArchived
,
isSaving
,
hasDescendants
,
onClearSelection
,
onFieldChange
,
onToggleCollapsed
,
onAddNode
,
onDeleteNode
,
onRestoreNode
,
onOpenRegulation
,
})
{
const
ancestors
=
node
.
ancestors
||
[];
const
historyRegulations
=
node
.
historyRegulation
||
[];
const
relatedId
=
node
.
relatedRegulation
||
node
.
relatedReviseRegulation
;
const
isMutationDisabled
=
isArchived
||
isSaving
;
return
(
<
div
className=
"flex min-h-0 flex-1 flex-col bg-surface"
>
<
header
className=
"flex shrink-0 items-start gap-3 border-b border-separator p-4"
>
<
div
className=
"min-w-0 flex-1"
>
<
div
className=
"mb-2 flex flex-wrap items-center gap-2"
>
<
span
className=
"text-xs text-muted"
>
{
typeLabel
}
</
span
>
<
Chip
aria
-
label=
{
`节点状态:${status.label}`
}
color=
{
status
.
color
}
size=
"sm"
variant=
"soft"
><
Chip
.
Label
>
{
status
.
label
}
</
Chip
.
Label
></
Chip
>
{
node
.
removed
?
<
Chip
color=
"danger"
size=
"sm"
variant=
"soft"
><
Chip
.
Label
>
已删除
</
Chip
.
Label
></
Chip
>
:
null
}
</
div
>
<
h2
className=
"break-words text-base font-semibold leading-6"
>
{
node
.
title
}
</
h2
>
</
div
>
<
Button
isIconOnly
aria
-
label=
"清除选择"
className=
"shrink-0 rounded-lg"
size=
"sm"
variant=
"ghost"
onPress=
{
onClearSelection
}
><
Close
size=
{
16
}
/></
Button
>
</
header
>
<
div
className=
"min-h-0 flex-1 overflow-y-auto overscroll-contain p-4"
aria
-
label=
"节点详情内容"
>
<
div
className=
"space-y-5"
>
<
section
aria
-
label=
"基本信息"
className=
"space-y-3"
>
<
h3
className=
"text-sm font-semibold"
>
基本信息
</
h3
>
<
TextField
fullWidth
isReadOnly=
{
isArchived
}
isDisabled=
{
isSaving
}
value=
{
node
.
title
}
onChange=
{
(
value
)
=>
onFieldChange
(
'title'
,
value
)
}
>
<
Label
className=
"text-sm text-muted"
>
标题
</
Label
>
<
Input
className=
"min-h-10 rounded-lg"
fullWidth
variant=
"secondary"
/>
</
TextField
>
<
TextField
fullWidth
isReadOnly=
{
isArchived
}
isDisabled=
{
isSaving
}
value=
{
node
.
content
||
''
}
onChange=
{
(
value
)
=>
onFieldChange
(
'content'
,
value
)
}
>
<
Label
className=
"text-sm text-muted"
>
备注
</
Label
>
<
TextArea
className=
"min-h-20 max-h-48 resize-y rounded-lg"
fullWidth
placeholder=
"补充节点说明"
rows=
{
3
}
variant=
"secondary"
/>
</
TextField
>
</
section
>
<
section
aria
-
label=
"节点操作"
className=
"space-y-3 border-t border-separator pt-4"
>
<
h3
className=
"text-sm font-semibold"
>
节点操作
</
h3
>
<
div
className=
"grid grid-cols-2 gap-2"
>
<
Button
className=
"min-h-10 min-w-0 gap-1.5 rounded-lg px-2 text-sm"
fullWidth
variant=
"tertiary"
onPress=
{
onToggleCollapsed
}
>
{
isCollapsed
?
<
ChevronRight
size=
{
15
}
/>
:
<
ChevronDown
size=
{
15
}
/>
}{
isCollapsed
?
'展开子节点'
:
'折叠子节点'
}
</
Button
>
<
Button
className=
"min-h-10 min-w-0 gap-1.5 rounded-lg px-2 text-sm"
fullWidth
isDisabled=
{
isMutationDisabled
||
node
.
removed
}
variant=
"tertiary"
onPress=
{
onAddNode
}
>
<
PlusIcon
size=
{
15
}
/>
添加流程节点
</
Button
>
</
div
>
{
relatedId
?
(
<
Button
className=
"min-h-10 justify-between rounded-lg px-3 text-sm"
fullWidth
variant=
"outline"
onPress=
{
()
=>
onOpenRegulation
(
relatedId
)
}
>
查看关联制度
<
OpenExternal
size=
{
15
}
/>
</
Button
>
)
:
null
}
</
section
>
<
section
aria
-
label=
"关联信息"
className=
"space-y-3 border-t border-separator pt-4"
>
<
h3
className=
"text-sm font-semibold"
>
关联信息
</
h3
>
<
Tabs
className=
"min-w-0 gap-3"
defaultSelectedKey=
"process"
variant=
"secondary"
>
<
Tabs
.
ListContainer
className=
"w-full min-w-0"
>
<
Tabs
.
List
aria
-
label=
"节点关联信息"
style=
{
{
display
:
'grid'
,
gridTemplateColumns
:
'repeat(3, minmax(0, 1fr))'
,
width
:
'100%'
}
}
>
<
Tabs
.
Tab
className=
"min-w-0 px-1"
id=
"process"
>
流程路径
<
Tabs
.
Indicator
/></
Tabs
.
Tab
>
<
Tabs
.
Tab
className=
"min-w-0 px-1"
id=
"regulation"
>
制度路径
<
Tabs
.
Indicator
/></
Tabs
.
Tab
>
<
Tabs
.
Tab
className=
"min-w-0 px-1"
id=
"history"
>
历史版本
<
Tabs
.
Indicator
/></
Tabs
.
Tab
>
</
Tabs
.
List
>
</
Tabs
.
ListContainer
>
<
Tabs
.
Panel
style=
{
{
marginTop
:
0
,
padding
:
0
}
}
id=
"process"
>
<
NodePath
items=
{
ancestors
.
filter
((
item
)
=>
item
.
type
===
'groupNode'
)
}
emptyText=
"暂无流程路径"
/>
</
Tabs
.
Panel
>
<
Tabs
.
Panel
style=
{
{
marginTop
:
0
,
padding
:
0
}
}
id=
"regulation"
>
<
NodePath
items=
{
ancestors
.
filter
((
item
)
=>
item
.
type
===
'regulationNode'
)
}
emptyText=
"暂无制度路径"
/>
</
Tabs
.
Panel
>
<
Tabs
.
Panel
style=
{
{
marginTop
:
0
,
padding
:
0
}
}
id=
"history"
>
<
div
className=
"space-y-1"
>
{
historyRegulations
.
map
((
item
)
=>
(
<
Button
className=
"h-auto min-h-10 justify-start whitespace-normal rounded-lg px-2 py-2 text-left text-sm text-accent"
fullWidth
key=
{
item
.
_id
||
item
.
id
||
item
.
name
}
variant=
"ghost"
onPress=
{
()
=>
onOpenRegulation
(
item
.
_id
||
item
.
name
)
}
>
{
item
.
metadata
?.
regulation_name
||
item
.
title
}
</
Button
>
))
}
{
!
historyRegulations
.
length
?
<
p
className=
"py-3 text-sm text-muted"
>
暂无历史版本
</
p
>
:
null
}
</
div
>
</
Tabs
.
Panel
>
</
Tabs
>
</
section
>
</
div
>
</
div
>
<
footer
className=
"shrink-0 space-y-2 border-t border-separator bg-background p-4"
>
{
isArchived
?
<
p
className=
"text-xs text-muted"
>
归档版本仅供查看,无法保存修改。
</
p
>
:
null
}
{
node
.
removed
?
(
<>
<
Button
className=
"min-h-10 rounded-lg"
fullWidth
isDisabled=
{
isMutationDisabled
}
variant=
"secondary"
onPress=
{
onRestoreNode
}
>
<
Reload
size=
{
16
}
/>
{
hasDescendants
?
'恢复节点及子节点'
:
'恢复节点'
}
</
Button
>
<
p
className=
"text-xs leading-5 text-muted"
>
恢复后点击顶部“保存”使修改生效。
</
p
>
</>
)
:
(
<>
<
Button
aria
-
keyshortcuts=
"Delete Backspace"
className=
"min-h-10 rounded-lg"
fullWidth
isDisabled=
{
isMutationDisabled
}
variant=
"danger-soft"
onPress=
{
onDeleteNode
}
>
<
Delete
size=
{
16
}
/>
删除节点
</
Button
>
<
p
className=
"text-xs leading-5 text-muted"
>
{
hasDescendants
?
'同时移除所有子节点,可在“删除节点”列表恢复。'
:
'快捷键 Delete(Mac ⌫),可在“删除节点”列表恢复。'
}
</
p
>
</>
)
}
</
footer
>
</
div
>
);
}
src/pages/internal_regulation_manager/InternalRegulationDetail.jsx
View file @
3e6db798
This diff is collapsed.
Click to expand it.
src/pages/internal_regulation_manager/InternalRegulationManager.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
Card
,
Spinner
,
Tabs
}
from
'@heroui/react'
;
import
{
Card
,
Spinner
,
Tabs
}
from
'@heroui/react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useSearchParams
}
from
'umi'
;
import
{
useSearchParams
}
from
'umi'
;
...
@@ -18,7 +20,9 @@ export default function InternalRegulationManager() {
...
@@ -18,7 +20,9 @@ export default function InternalRegulationManager() {
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
options
,
setOptions
]
=
useState
({});
const
[
options
,
setOptions
]
=
useState
({});
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useFeedbackState
(
'error'
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
...
@@ -38,7 +42,7 @@ export default function InternalRegulationManager() {
...
@@ -38,7 +42,7 @@ export default function InternalRegulationManager() {
}
}
})();
})();
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[]);
},
[
reloadKey
,
setError
]);
const
changeTab
=
(
key
)
=>
{
const
changeTab
=
(
key
)
=>
{
const
value
=
String
(
key
);
const
value
=
String
(
key
);
...
@@ -52,7 +56,7 @@ export default function InternalRegulationManager() {
...
@@ -52,7 +56,7 @@ export default function InternalRegulationManager() {
const
content
=
useMemo
(()
=>
{
const
content
=
useMemo
(()
=>
{
if
(
loading
)
return
<
div
className=
"flex min-h-72 items-center justify-center"
><
Spinner
label=
"正在加载制度配置..."
/></
div
>;
if
(
loading
)
return
<
div
className=
"flex min-h-72 items-center justify-center"
><
Spinner
label=
"正在加载制度配置..."
/></
div
>;
if
(
error
)
return
<
div
className=
"rounded-2xl border border-danger/30 bg-danger/5 p-6 text-sm text-danger"
>
{
error
}
</
div
>;
if
(
error
)
return
<
RequestFailure
title=
"制度配置加载失败"
onRetry=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
/
>;
if
(
activeTab
===
'process'
)
return
<
RevisionList
options=
{
options
}
/>;
if
(
activeTab
===
'process'
)
return
<
RevisionList
options=
{
options
}
/>;
if
(
activeTab
===
'archive'
)
return
<
ArchiveList
options=
{
options
}
/>;
if
(
activeTab
===
'archive'
)
return
<
ArchiveList
options=
{
options
}
/>;
return
<
RegulationList
subject=
{
subject
}
options=
{
options
}
/>;
return
<
RegulationList
subject=
{
subject
}
options=
{
options
}
/>;
...
...
src/pages/internal_regulation_manager/RegulationGraph.jsx
View file @
3e6db798
This diff is collapsed.
Click to expand it.
src/pages/internal_regulation_manager/RegulationList.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
Button
,
Chip
,
Input
,
Label
,
Spinner
,
TextArea
,
TextField
,
toast
}
from
'@heroui/react'
;
import
{
Button
,
Chip
,
Input
,
Label
,
Spinner
,
TextArea
,
TextField
,
toast
}
from
'@heroui/react'
;
import
{
Close
,
Download
,
Edit
,
Eye
,
FileText
,
PlusIcon
,
Search
}
from
'@/components/AppIcons'
;
import
{
Close
,
Download
,
Edit
,
Eye
,
FileText
,
PlusIcon
,
Search
}
from
'@/components/AppIcons'
;
import
ListPagination
from
'@/components/ListPagination'
;
import
ListPagination
from
'@/components/ListPagination'
;
...
@@ -41,7 +43,8 @@ export default function RegulationList({ options = {} }) {
...
@@ -41,7 +43,8 @@ export default function RegulationList({ options = {} }) {
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
totalPages
,
setTotalPages
]
=
useState
(
1
);
const
[
totalPages
,
setTotalPages
]
=
useState
(
1
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
dialogOpen
,
setDialogOpen
]
=
useState
(
false
);
const
[
dialogOpen
,
setDialogOpen
]
=
useState
(
false
);
const
[
form
,
setForm
]
=
useState
(
INITIAL_FORM
);
const
[
form
,
setForm
]
=
useState
(
INITIAL_FORM
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
...
@@ -52,6 +55,8 @@ export default function RegulationList({ options = {} }) {
...
@@ -52,6 +55,8 @@ export default function RegulationList({ options = {} }) {
const
refresh
=
async
()
=>
{
const
refresh
=
async
()
=>
{
try
{
try
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
const
activeFilters
=
Object
.
fromEntries
(
Object
.
entries
(
filters
).
filter
(([,
value
])
=>
value
).
map
(([
key
,
value
])
=>
[
`metadata.
${
key
}
`
,
[
'departments'
,
'approve'
].
includes
(
key
)
?
{
$in
:
[
value
]
}
:
value
]));
const
activeFilters
=
Object
.
fromEntries
(
Object
.
entries
(
filters
).
filter
(([,
value
])
=>
value
).
map
(([
key
,
value
])
=>
[
`metadata.
${
key
}
`
,
[
'departments'
,
'approve'
].
includes
(
key
)
?
{
$in
:
[
value
]
}
:
value
]));
const
result
=
await
loadRegulations
({
page
,
pageSize
,
keyword
,
filters
:
activeFilters
});
const
result
=
await
loadRegulations
({
page
,
pageSize
,
keyword
,
filters
:
activeFilters
});
setRecords
(
result
.
records
);
setRecords
(
result
.
records
);
...
@@ -59,6 +64,7 @@ export default function RegulationList({ options = {} }) {
...
@@ -59,6 +64,7 @@ export default function RegulationList({ options = {} }) {
setTotalPages
(
result
.
totalPages
);
setTotalPages
(
result
.
totalPages
);
setError
(
''
);
setError
(
''
);
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
setLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'制度目录加载失败。'
);
setError
(
loadError
?.
message
||
'制度目录加载失败。'
);
}
finally
{
}
finally
{
setLoading
(
false
);
setLoading
(
false
);
...
@@ -110,7 +116,7 @@ export default function RegulationList({ options = {} }) {
...
@@ -110,7 +116,7 @@ export default function RegulationList({ options = {} }) {
setSaving
(
true
);
setSaving
(
true
);
await
createRevision
(
form
);
await
createRevision
(
form
);
setDialogOpen
(
false
);
setDialogOpen
(
false
);
toast
.
success
(
'制度创建成功'
);
toast
.
success
(
'制度创建成功'
,
{
timeout
:
4000
}
);
await
refresh
();
await
refresh
();
}
catch
(
saveError
)
{
}
catch
(
saveError
)
{
setError
(
saveError
?.
message
||
'保存失败,请检查字段后重试。'
);
setError
(
saveError
?.
message
||
'保存失败,请检查字段后重试。'
);
...
@@ -137,7 +143,7 @@ export default function RegulationList({ options = {} }) {
...
@@ -137,7 +143,7 @@ export default function RegulationList({ options = {} }) {
origin_regulation
:
record
.
_id
,
origin_regulation
:
record
.
_id
,
});
});
setConfirmState
({
open
:
false
,
type
:
''
,
record
:
null
});
setConfirmState
({
open
:
false
,
type
:
''
,
record
:
null
});
toast
.
success
(
type
===
'修订'
?
'修订制度创建成功'
:
'废除制度创建成功'
);
toast
.
success
(
type
===
'修订'
?
'修订制度创建成功'
:
'废除制度创建成功'
,
{
timeout
:
4000
}
);
await
refresh
();
await
refresh
();
}
catch
(
saveError
)
{
}
catch
(
saveError
)
{
setError
(
saveError
?.
message
||
'保存失败,请检查字段后重试。'
);
setError
(
saveError
?.
message
||
'保存失败,请检查字段后重试。'
);
...
@@ -157,7 +163,7 @@ export default function RegulationList({ options = {} }) {
...
@@ -157,7 +163,7 @@ export default function RegulationList({ options = {} }) {
setSaving
(
true
);
setSaving
(
true
);
await
updateRegulationComment
(
record
,
commentState
.
comment
);
await
updateRegulationComment
(
record
,
commentState
.
comment
);
setCommentState
({
open
:
false
,
record
:
null
,
comment
:
''
});
setCommentState
({
open
:
false
,
record
:
null
,
comment
:
''
});
toast
.
success
(
'已成功添加备注'
);
toast
.
success
(
'已成功添加备注'
,
{
timeout
:
4000
}
);
await
refresh
();
await
refresh
();
}
catch
(
saveError
)
{
}
catch
(
saveError
)
{
setError
(
saveError
?.
message
||
'备注保存失败,请稍后重试。'
);
setError
(
saveError
?.
message
||
'备注保存失败,请稍后重试。'
);
...
@@ -184,8 +190,8 @@ export default function RegulationList({ options = {} }) {
...
@@ -184,8 +190,8 @@ export default function RegulationList({ options = {} }) {
<
XinyuanAutocomplete
className=
"w-52"
label=
"制定部门"
options=
{
[{
value
:
''
,
label
:
'全部'
},
...
departmentOptions
]
}
selectedKey=
{
filters
.
departments
}
onSelectionChange=
{
(
value
)
=>
{
setFilters
((
current
)
=>
({
...
current
,
departments
:
String
(
value
||
''
)
}));
setPage
(
0
);
}
}
/>
<
XinyuanAutocomplete
className=
"w-52"
label=
"制定部门"
options=
{
[{
value
:
''
,
label
:
'全部'
},
...
departmentOptions
]
}
selectedKey=
{
filters
.
departments
}
onSelectionChange=
{
(
value
)
=>
{
setFilters
((
current
)
=>
({
...
current
,
departments
:
String
(
value
||
''
)
}));
setPage
(
0
);
}
}
/>
<
XinyuanAutocomplete
className=
"w-52"
label=
"审批主体"
options=
{
[{
value
:
''
,
label
:
'全部'
},
...
approveOptions
]
}
selectedKey=
{
filters
.
approve
}
onSelectionChange=
{
(
value
)
=>
{
setFilters
((
current
)
=>
({
...
current
,
approve
:
String
(
value
||
''
)
}));
setPage
(
0
);
}
}
/>
<
XinyuanAutocomplete
className=
"w-52"
label=
"审批主体"
options=
{
[{
value
:
''
,
label
:
'全部'
},
...
approveOptions
]
}
selectedKey=
{
filters
.
approve
}
onSelectionChange=
{
(
value
)
=>
{
setFilters
((
current
)
=>
({
...
current
,
approve
:
String
(
value
||
''
)
}));
setPage
(
0
);
}
}
/>
</
div
>
</
div
>
{
error
?
<
div
className=
"rounded-xl border border-danger/30 bg-danger/5 p-3 text-sm text-danger"
>
{
error
}
</
div
>
:
null
}
{
loading
?
<
div
className=
"flex min-h-48 items-center justify-center"
><
Spinner
label=
"正在加载制度..."
/></
div
>
:
records
.
length
===
0
?
<
div
className=
"rounded-2xl border border-dashed border-separator p-10 text-center text-sm text-muted"
>
暂无制度记录
</
div
>
:
(
{
loading
?
<
div
className=
"flex min-h-48 items-center justify-center"
><
Spinner
label=
"正在加载制度..."
/></
div
>
:
loadFailed
?
<
RequestFailure
title=
"制度目录加载失败"
onRetry=
{
refresh
}
/>
:
records
.
length
===
0
?
<
div
className=
"rounded-2xl border border-dashed border-separator p-10 text-center text-sm text-muted"
>
暂无制度记录
</
div
>
:
(
<
div
className=
"overflow-x-auto rounded-2xl border border-separator"
>
<
div
className=
"overflow-x-auto rounded-2xl border border-separator"
>
<
table
className=
"w-full min-w-[1040px] text-left text-sm"
>
<
table
className=
"w-full min-w-[1040px] text-left text-sm"
>
<
thead
className=
"bg-surface-secondary text-muted"
><
tr
>
{
[
'制度名称'
,
'修订状态'
,
'时效性'
,
'效力位阶'
,
'制定部门'
,
'审批主体'
,
'发文日期'
,
'备注'
,
'操作'
].
map
((
title
)
=>
<
th
className=
"px-4 py-3 font-medium"
key=
{
title
}
>
{
title
}
</
th
>)
}
</
tr
></
thead
>
<
thead
className=
"bg-surface-secondary text-muted"
><
tr
>
{
[
'制度名称'
,
'修订状态'
,
'时效性'
,
'效力位阶'
,
'制定部门'
,
'审批主体'
,
'发文日期'
,
'备注'
,
'操作'
].
map
((
title
)
=>
<
th
className=
"px-4 py-3 font-medium"
key=
{
title
}
>
{
title
}
</
th
>)
}
</
tr
></
thead
>
...
@@ -209,7 +215,7 @@ export default function RegulationList({ options = {} }) {
...
@@ -209,7 +215,7 @@ export default function RegulationList({ options = {} }) {
</
table
>
</
table
>
</
div
>
</
div
>
)
}
)
}
<
ListPagination
isDisabled=
{
loading
}
page=
{
page
}
pageSize=
{
pageSize
}
total=
{
total
}
totalPages=
{
totalPages
}
onPageChange=
{
setPage
}
onPageSizeChange=
{
changePageSize
}
/>
<
ListPagination
isDisabled=
{
loading
||
loadFailed
}
page=
{
page
}
pageSize=
{
pageSize
}
total=
{
total
}
totalPages=
{
totalPages
}
onPageChange=
{
setPage
}
onPageSizeChange=
{
changePageSize
}
/>
<
XinyuanModal
isOpen=
{
dialogOpen
}
onOpenChange=
{
setDialogOpen
}
size=
"2xl"
title=
"创建修订计划"
footer=
{
<
div
className=
"flex justify-end gap-2"
><
Button
variant=
"tertiary"
onPress=
{
()
=>
setDialogOpen
(
false
)
}
>
取消
</
Button
><
Button
isDisabled=
{
saving
||
!
form
.
regulation_name
.
trim
()
}
variant=
"primary"
onPress=
{
submit
}
>
{
saving
?
'保存中...'
:
'保存'
}
</
Button
></
div
>
}
>
<
XinyuanModal
isOpen=
{
dialogOpen
}
onOpenChange=
{
setDialogOpen
}
size=
"2xl"
title=
"创建修订计划"
footer=
{
<
div
className=
"flex justify-end gap-2"
><
Button
variant=
"tertiary"
onPress=
{
()
=>
setDialogOpen
(
false
)
}
>
取消
</
Button
><
Button
isDisabled=
{
saving
||
!
form
.
regulation_name
.
trim
()
}
variant=
"primary"
onPress=
{
submit
}
>
{
saving
?
'保存中...'
:
'保存'
}
</
Button
></
div
>
}
>
<
div
className=
"grid gap-4 sm:grid-cols-2"
>
<
div
className=
"grid gap-4 sm:grid-cols-2"
>
<
TextField
className=
"sm:col-span-2"
fullWidth
isRequired
value=
{
form
.
regulation_name
}
onChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
regulation_name
:
value
}))
}
><
Label
>
制度名称
</
Label
><
Input
fullWidth
placeholder=
"请输入制度名称"
variant=
"secondary"
/></
TextField
>
<
TextField
className=
"sm:col-span-2"
fullWidth
isRequired
value=
{
form
.
regulation_name
}
onChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
regulation_name
:
value
}))
}
><
Label
>
制度名称
</
Label
><
Input
fullWidth
placeholder=
"请输入制度名称"
variant=
"secondary"
/></
TextField
>
...
...
src/pages/internal_regulation_manager/RevisionDetail.jsx
View file @
3e6db798
import
{
Button
,
Card
,
Input
,
Label
,
Spinner
,
TextArea
,
TextField
}
from
'@heroui/react'
;
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
Button
,
Card
,
Input
,
Label
,
Spinner
,
TextArea
,
TextField
,
toast
}
from
'@heroui/react'
;
import
{
Back
}
from
'@/components/AppIcons'
;
import
{
Back
}
from
'@/components/AppIcons'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
{
useEffect
,
useState
}
from
'react'
;
import
{
useEffect
,
useState
}
from
'react'
;
...
@@ -12,11 +14,13 @@ export default function RevisionDetail() {
...
@@ -12,11 +14,13 @@ export default function RevisionDetail() {
const
[
form
,
setForm
]
=
useState
({});
const
[
form
,
setForm
]
=
useState
({});
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
useEffect
(()
=>
{
Promise
.
all
([
loadRecord
(
name
),
loadSubjectOptions
(
'internal_regulation'
)]).
then
(([
loaded
,
loadedOptions
])
=>
{
setRecord
(
loaded
);
setForm
(
loaded
?.
metadata
||
{});
setOptions
(
loadedOptions
.
options
);
}).
catch
((
loadError
)
=>
setError
(
loadError
?.
message
||
'修订详情加载失败。'
)).
finally
(()
=>
setLoading
(
false
));
},
[
name
]);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
save
=
async
()
=>
{
try
{
setSaving
(
true
);
await
updateRevision
(
record
.
name
||
name
,
form
);
history
.
push
(
'/internal_regulation_manager?tab=process'
);
}
catch
(
saveError
)
{
setError
(
saveError
?.
message
||
'保存失败。'
);
}
finally
{
setSaving
(
false
);
}
};
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
useEffect
(()
=>
{
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
Promise
.
all
([
loadRecord
(
name
),
loadSubjectOptions
(
'internal_regulation'
)]).
then
(([
loaded
,
loadedOptions
])
=>
{
setRecord
(
loaded
);
setForm
(
loaded
?.
metadata
||
{});
setOptions
(
loadedOptions
.
options
);
}).
catch
((
loadError
)
=>
{
setLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'修订详情加载失败。'
);
}).
finally
(()
=>
setLoading
(
false
));
},
[
name
,
reloadKey
,
setError
]);
const
save
=
async
()
=>
{
try
{
setSaving
(
true
);
await
updateRevision
(
record
.
name
||
name
,
form
);
toast
.
success
(
'修订计划已保存。'
,
{
timeout
:
4000
});
history
.
push
(
'/internal_regulation_manager?tab=process'
);
}
catch
(
saveError
)
{
setError
(
saveError
?.
message
||
'保存失败。'
);
}
finally
{
setSaving
(
false
);
}
};
if
(
loading
)
return
<
div
className=
"flex min-h-72 items-center justify-center"
><
Spinner
label=
"正在加载修订详情..."
/></
div
>;
if
(
loading
)
return
<
div
className=
"flex min-h-72 items-center justify-center"
><
Spinner
label=
"正在加载修订详情..."
/></
div
>;
if
(
error
||
!
record
)
return
<
div
className=
"space-y-4"
><
Button
variant=
"tertiary"
onPress=
{
()
=>
history
.
back
()
}
><
Back
size=
{
16
}
/>
返回
</
Button
><
div
className=
"rounded-2xl border border-danger/30 bg-danger/5 p-6 text-sm text-danger"
>
{
error
||
'修订计划不存在。'
}
</
div
></
div
>;
if
(
loadFailed
||
!
record
)
return
<
div
className=
"space-y-4"
><
Button
variant=
"tertiary"
onPress=
{
()
=>
history
.
back
()
}
><
Back
size=
{
16
}
/>
返回
</
Button
><
RequestFailure
title=
{
loadFailed
?
'修订详情加载失败'
:
'修订计划不存在'
}
onRetry=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
/
></
div
>;
const
selectOptions
=
(
field
)
=>
(
options
[
field
]
||
[]).
map
((
item
)
=>
({
value
:
item
.
value
??
item
.
label
??
item
,
label
:
item
.
label
??
item
.
value
??
item
}));
const
selectOptions
=
(
field
)
=>
(
options
[
field
]
||
[]).
map
((
item
)
=>
({
value
:
item
.
value
??
item
.
label
??
item
,
label
:
item
.
label
??
item
.
value
??
item
}));
return
<
div
className=
"mx-auto max-w-4xl space-y-5"
><
div
className=
"flex items-center justify-between"
><
Button
variant=
"tertiary"
onPress=
{
()
=>
history
.
push
(
'/internal_regulation_manager?tab=process'
)
}
><
Back
size=
{
16
}
/>
返回修订计划
</
Button
><
Button
isDisabled=
{
saving
||
!
form
.
regulation_name
}
variant=
"primary"
onPress=
{
save
}
>
{
saving
?
'保存中...'
:
'保存'
}
</
Button
></
div
><
Card
className=
"rc-card-surface"
><
Card
.
Header
><
Card
.
Title
>
编辑修订计划
</
Card
.
Title
></
Card
.
Header
><
Card
.
Content
><
div
className=
"grid gap-4 sm:grid-cols-2"
><
TextField
className=
"sm:col-span-2"
fullWidth
isRequired
value=
{
form
.
regulation_name
||
''
}
onChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
regulation_name
:
value
}))
}
><
Label
>
制度名称
</
Label
><
Input
fullWidth
variant=
"secondary"
/></
TextField
><
XinyuanAutocomplete
label=
"修订类型"
options=
{
[
'新增'
,
'修订'
,
'废止'
].
map
((
value
)
=>
({
value
,
label
:
value
}))
}
selectedKey=
{
form
.
type
||
''
}
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
type
:
String
(
value
)
}))
}
/><
XinyuanAutocomplete
label=
"效力位阶"
options=
{
REGULATION_LEVELS
.
map
((
value
)
=>
({
value
,
label
:
value
}))
}
selectedKey=
{
form
.
level
||
''
}
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
level
:
String
(
value
)
}))
}
/><
XinyuanAutocomplete
label=
"审批主体"
options=
{
selectOptions
(
'approve'
)
}
selectedKeys=
{
new
Set
(
form
.
approve
||
[])
}
selectionMode=
"multiple"
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
approve
:
value
===
'all'
?
selectOptions
(
'approve'
).
map
((
item
)
=>
item
.
value
)
:
Array
.
from
(
value
||
[])
}))
}
/><
XinyuanAutocomplete
label=
"制定部门"
options=
{
selectOptions
(
'departments'
)
}
selectedKeys=
{
new
Set
(
form
.
departments
||
[])
}
selectionMode=
"multiple"
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
departments
:
value
===
'all'
?
selectOptions
(
'departments'
).
map
((
item
)
=>
item
.
value
)
:
Array
.
from
(
value
||
[])
}))
}
/><
TextField
className=
"sm:col-span-2"
fullWidth
value=
{
form
.
comment
||
''
}
onChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
comment
:
value
}))
}
><
Label
>
备注
</
Label
><
TextArea
fullWidth
rows=
{
6
}
variant=
"secondary"
/></
TextField
></
div
></
Card
.
Content
></
Card
></
div
>;
return
<
div
className=
"mx-auto max-w-4xl space-y-5"
><
div
className=
"flex items-center justify-between"
><
Button
variant=
"tertiary"
onPress=
{
()
=>
history
.
push
(
'/internal_regulation_manager?tab=process'
)
}
><
Back
size=
{
16
}
/>
返回修订计划
</
Button
><
Button
isDisabled=
{
saving
||
!
form
.
regulation_name
}
variant=
"primary"
onPress=
{
save
}
>
{
saving
?
'保存中...'
:
'保存'
}
</
Button
></
div
><
Card
className=
"rc-card-surface"
><
Card
.
Header
><
Card
.
Title
>
编辑修订计划
</
Card
.
Title
></
Card
.
Header
><
Card
.
Content
><
div
className=
"grid gap-4 sm:grid-cols-2"
><
TextField
className=
"sm:col-span-2"
fullWidth
isRequired
value=
{
form
.
regulation_name
||
''
}
onChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
regulation_name
:
value
}))
}
><
Label
>
制度名称
</
Label
><
Input
fullWidth
variant=
"secondary"
/></
TextField
><
XinyuanAutocomplete
label=
"修订类型"
options=
{
[
'新增'
,
'修订'
,
'废止'
].
map
((
value
)
=>
({
value
,
label
:
value
}))
}
selectedKey=
{
form
.
type
||
''
}
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
type
:
String
(
value
)
}))
}
/><
XinyuanAutocomplete
label=
"效力位阶"
options=
{
REGULATION_LEVELS
.
map
((
value
)
=>
({
value
,
label
:
value
}))
}
selectedKey=
{
form
.
level
||
''
}
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
level
:
String
(
value
)
}))
}
/><
XinyuanAutocomplete
label=
"审批主体"
options=
{
selectOptions
(
'approve'
)
}
selectedKeys=
{
new
Set
(
form
.
approve
||
[])
}
selectionMode=
"multiple"
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
approve
:
value
===
'all'
?
selectOptions
(
'approve'
).
map
((
item
)
=>
item
.
value
)
:
Array
.
from
(
value
||
[])
}))
}
/><
XinyuanAutocomplete
label=
"制定部门"
options=
{
selectOptions
(
'departments'
)
}
selectedKeys=
{
new
Set
(
form
.
departments
||
[])
}
selectionMode=
"multiple"
onSelectionChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
departments
:
value
===
'all'
?
selectOptions
(
'departments'
).
map
((
item
)
=>
item
.
value
)
:
Array
.
from
(
value
||
[])
}))
}
/><
TextField
className=
"sm:col-span-2"
fullWidth
value=
{
form
.
comment
||
''
}
onChange=
{
(
value
)
=>
setForm
((
current
)
=>
({
...
current
,
comment
:
value
}))
}
><
Label
>
备注
</
Label
><
TextArea
fullWidth
rows=
{
6
}
variant=
"secondary"
/></
TextField
></
div
></
Card
.
Content
></
Card
></
div
>;
}
}
src/pages/internal_regulation_manager/RevisionList.jsx
View file @
3e6db798
This diff is collapsed.
Click to expand it.
src/pages/internal_regulation_manager/internal-regulation.business-flow.md
View file @
3e6db798
This diff is collapsed.
Click to expand it.
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
View file @
3e6db798
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
xinyuanLogo
from
'@/assets/xy-logo-red.png'
;
import
xinyuanLogo
from
'@/assets/xy-logo-red.png'
;
import
{
APP_DISPLAY_NAME
,
APP_ROUTE_NAME
}
from
'@/constants/app'
;
import
{
APP_DISPLAY_NAME
,
APP_ROUTE_NAME
}
from
'@/constants/app'
;
import
{
getApplications
}
from
'@/services/DataService'
;
import
{
getApplications
}
from
'@/services/DataService'
;
import
{
ExclamationTriangleIcon
,
MagnifyingGlassIcon
}
from
'@/components/AppIcons'
;
import
{
MagnifyingGlassIcon
}
from
'@/components/AppIcons'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
ItemCard
}
from
'@heroui-pro/react/item-card'
;
import
{
ItemCard
}
from
'@heroui-pro/react/item-card'
;
import
{
ItemCardGroup
}
from
'@heroui-pro/react/item-card-group'
;
import
{
ItemCardGroup
}
from
'@heroui-pro/react/item-card-group'
;
import
{
import
{
Alert
,
Avatar
,
Avatar
,
Chip
,
Chip
,
Modal
,
Modal
,
...
@@ -312,7 +312,7 @@ export default function ApplicationSwitcherDialog({
...
@@ -312,7 +312,7 @@ export default function ApplicationSwitcherDialog({
const
[
status
,
setStatus
]
=
useState
(
const
[
status
,
setStatus
]
=
useState
(
fallbackApplications
.
length
>
0
?
'success'
:
'idle'
,
fallbackApplications
.
length
>
0
?
'success'
:
'idle'
,
);
);
const
[
errorMessage
,
setErrorMessage
]
=
useState
(
'
'
);
const
[
,
setErrorMessage
]
=
useFeedbackState
(
'warning
'
);
const
[
applicationSearch
,
setApplicationSearch
]
=
useState
(
''
);
const
[
applicationSearch
,
setApplicationSearch
]
=
useState
(
''
);
const
currentApplicationName
=
useMemo
(()
=>
{
const
currentApplicationName
=
useMemo
(()
=>
{
return
applications
.
find
(
isCurrentApplication
)?.
name
||
CURRENT_APPLICATION_NAME
;
return
applications
.
find
(
isCurrentApplication
)?.
name
||
CURRENT_APPLICATION_NAME
;
...
@@ -352,6 +352,7 @@ export default function ApplicationSwitcherDialog({
...
@@ -352,6 +352,7 @@ export default function ApplicationSwitcherDialog({
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
isOpen
)
{
if
(
!
isOpen
)
{
setApplicationSearch
(
''
);
setApplicationSearch
(
''
);
setErrorMessage
(
''
);
return
;
return
;
}
}
...
@@ -442,13 +443,6 @@ export default function ApplicationSwitcherDialog({
...
@@ -442,13 +443,6 @@ export default function ApplicationSwitcherDialog({
offset=
{
12
}
offset=
{
12
}
size=
{
24
}
size=
{
24
}
>
>
{
status
===
'error'
&&
errorMessage
?
(
<
Alert
className=
"mb-4"
status=
"warning"
>
<
Alert
.
Indicator
><
ExclamationTriangleIcon
size=
{
20
}
/></
Alert
.
Indicator
>
<
Alert
.
Content
><
Alert
.
Description
>
{
errorMessage
}
</
Alert
.
Description
></
Alert
.
Content
>
</
Alert
>
)
:
null
}
{
status
===
'loading'
&&
applications
.
length
===
0
?
(
{
status
===
'loading'
&&
applications
.
length
===
0
?
(
<
LoadingSkeleton
/>
<
LoadingSkeleton
/>
)
:
filteredApplicationSections
.
length
>
0
?
(
)
:
filteredApplicationSections
.
length
>
0
?
(
...
...
src/pages/project_manager_xy/ForwardApprovals.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
{
Eye
}
from
'@/components/AppIcons'
;
import
{
Eye
}
from
'@/components/AppIcons'
;
...
@@ -91,9 +93,12 @@ export default function ForwardApprovals() {
...
@@ -91,9 +93,12 @@ export default function ForwardApprovals() {
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
editLoading
,
setEditLoading
]
=
useState
(
false
);
const
[
editLoading
,
setEditLoading
]
=
useState
(
false
);
const
[
editLoadFailed
,
setEditLoadFailed
]
=
useState
(
false
);
const
[
editSaving
,
setEditSaving
]
=
useState
(
false
);
const
[
editSaving
,
setEditSaving
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
notice
,
setNotice
]
=
useState
(
''
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
const
[,
setNotice
]
=
useFeedbackState
(
'success'
);
const
[
activeRecordId
,
setActiveRecordId
]
=
useState
(
''
);
const
[
activeRecordId
,
setActiveRecordId
]
=
useState
(
''
);
const
[
isDetailDialogOpen
,
setIsDetailDialogOpen
]
=
useState
(
false
);
const
[
isDetailDialogOpen
,
setIsDetailDialogOpen
]
=
useState
(
false
);
const
[
isEditDialogOpen
,
setIsEditDialogOpen
]
=
useState
(
false
);
const
[
isEditDialogOpen
,
setIsEditDialogOpen
]
=
useState
(
false
);
...
@@ -107,6 +112,8 @@ export default function ForwardApprovals() {
...
@@ -107,6 +112,8 @@ export default function ForwardApprovals() {
(
async
()
=>
{
(
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
try
{
try
{
await
ensureModelMonitorSubjects
();
await
ensureModelMonitorSubjects
();
const
result
=
await
loadForwardRecords
();
const
result
=
await
loadForwardRecords
();
...
@@ -122,6 +129,7 @@ export default function ForwardApprovals() {
...
@@ -122,6 +129,7 @@ export default function ForwardApprovals() {
}
}
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
if
(
!
cancelled
)
{
if
(
!
cancelled
)
{
setLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'转发审批列表加载失败'
);
setError
(
loadError
?.
message
||
'转发审批列表加载失败'
);
}
}
}
finally
{
}
finally
{
...
@@ -134,7 +142,7 @@ export default function ForwardApprovals() {
...
@@ -134,7 +142,7 @@ export default function ForwardApprovals() {
return
()
=>
{
return
()
=>
{
cancelled
=
true
;
cancelled
=
true
;
};
};
},
[
currentUserId
]);
},
[
currentUserId
,
reloadKey
,
setError
]);
const
editableInvolvedCount
=
useMemo
(
const
editableInvolvedCount
=
useMemo
(
()
=>
()
=>
...
@@ -212,6 +220,7 @@ export default function ForwardApprovals() {
...
@@ -212,6 +220,7 @@ export default function ForwardApprovals() {
setEditingRecord
(
record
);
setEditingRecord
(
record
);
setIsEditDialogOpen
(
true
);
setIsEditDialogOpen
(
true
);
setEditLoading
(
true
);
setEditLoading
(
true
);
setEditLoadFailed
(
false
);
setError
(
''
);
setError
(
''
);
try
{
try
{
...
@@ -220,8 +229,8 @@ export default function ForwardApprovals() {
...
@@ -220,8 +229,8 @@ export default function ForwardApprovals() {
setEditableRecords
(
result
.
records
);
setEditableRecords
(
result
.
records
);
setActiveEditableRecordId
(
result
.
records
[
0
]?.
id
||
''
);
setActiveEditableRecordId
(
result
.
records
[
0
]?.
id
||
''
);
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
setEditLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'填写数据加载失败'
);
setError
(
loadError
?.
message
||
'填写数据加载失败'
);
setEditingRecord
(
null
);
setEditableSubjectDefinition
(
null
);
setEditableSubjectDefinition
(
null
);
setEditableRecords
([]);
setEditableRecords
([]);
setActiveEditableRecordId
(
''
);
setActiveEditableRecordId
(
''
);
...
@@ -457,8 +466,7 @@ export default function ForwardApprovals() {
...
@@ -457,8 +466,7 @@ export default function ForwardApprovals() {
/>
/>
{
loading
?
<
NoticeBanner
>
正在加载转发审批记录...
</
NoticeBanner
>
:
null
}
{
loading
?
<
NoticeBanner
>
正在加载转发审批记录...
</
NoticeBanner
>
:
null
}
{
!
loading
&&
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
loadFailed
?
<
RequestFailure
title=
"转发审批列表加载失败"
onRetry=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
/>
:
null
}
{
!
error
&&
notice
?
<
NoticeBanner
tone=
"success"
>
{
notice
}
</
NoticeBanner
>
:
null
}
<
div
className=
"min-w-0 max-w-full"
>
<
div
className=
"min-w-0 max-w-full"
>
<
Tabs
className=
"w-fit max-w-full min-w-0"
selectedKey=
{
activeFilter
}
variant=
"primary"
onSelectionChange=
{
(
key
)
=>
{
setActiveFilter
(
String
(
key
));
setPage
(
0
);
}
}
>
<
Tabs
className=
"w-fit max-w-full min-w-0"
selectedKey=
{
activeFilter
}
variant=
"primary"
onSelectionChange=
{
(
key
)
=>
{
setActiveFilter
(
String
(
key
));
setPage
(
0
);
}
}
>
...
@@ -477,7 +485,7 @@ export default function ForwardApprovals() {
...
@@ -477,7 +485,7 @@ export default function ForwardApprovals() {
{
editableInvolvedCount
>
0
?
<
NoticeBanner
>
你当前有
{
editableInvolvedCount
}
条待填写记录。
</
NoticeBanner
>
:
null
}
{
editableInvolvedCount
>
0
?
<
NoticeBanner
>
你当前有
{
editableInvolvedCount
}
条待填写记录。
</
NoticeBanner
>
:
null
}
<
section
className=
"min-w-0"
>
<
section
className=
"min-w-0"
hidden=
{
loading
||
loadFailed
}
>
<
SectionHeader
title=
"转发记录列表"
/>
<
SectionHeader
title=
"转发记录列表"
/>
<
DataGrid
<
DataGrid
aria
-
label=
"转发审批列表"
aria
-
label=
"转发审批列表"
...
@@ -573,6 +581,8 @@ export default function ForwardApprovals() {
...
@@ -573,6 +581,8 @@ export default function ForwardApprovals() {
>
>
{
editLoading
?
(
{
editLoading
?
(
<
NoticeBanner
>
正在加载可填写数据...
</
NoticeBanner
>
<
NoticeBanner
>
正在加载可填写数据...
</
NoticeBanner
>
)
:
editLoadFailed
?
(
<
RequestFailure
title=
"填写数据加载失败"
onRetry=
{
()
=>
openEditDialog
(
editingRecord
)
}
/>
)
:
editableRecords
.
length
>
0
&&
editableSubjectDefinition
?
(
)
:
editableRecords
.
length
>
0
&&
editableSubjectDefinition
?
(
<
div
className=
"grid gap-5 xl:grid-cols-[22rem_minmax(0,1fr)]"
>
<
div
className=
"grid gap-5 xl:grid-cols-[22rem_minmax(0,1fr)]"
>
<
Surface
className=
"rounded-2xl p-4"
variant=
"secondary"
>
<
Surface
className=
"rounded-2xl p-4"
variant=
"secondary"
>
...
...
src/pages/project_manager_xy/ModelBatchData.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
{
import
{
...
@@ -58,8 +60,10 @@ export default function ModelBatchData() {
...
@@ -58,8 +60,10 @@ export default function ModelBatchData() {
const
[
pageSize
,
setPageSize
]
=
useState
(
DEFAULT_PAGE_SIZE
);
const
[
pageSize
,
setPageSize
]
=
useState
(
DEFAULT_PAGE_SIZE
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
notice
,
setNotice
]
=
useState
(
''
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
const
[,
setNotice
]
=
useFeedbackState
(
'success'
);
const
[
isHiddenDialogOpen
,
setIsHiddenDialogOpen
]
=
useState
(
false
);
const
[
isHiddenDialogOpen
,
setIsHiddenDialogOpen
]
=
useState
(
false
);
const
[
isForwardDialogOpen
,
setIsForwardDialogOpen
]
=
useState
(
false
);
const
[
isForwardDialogOpen
,
setIsForwardDialogOpen
]
=
useState
(
false
);
const
[
forwardForm
,
setForwardForm
]
=
useState
(
createEmptyForwardDraft
);
const
[
forwardForm
,
setForwardForm
]
=
useState
(
createEmptyForwardDraft
);
...
@@ -82,6 +86,8 @@ export default function ModelBatchData() {
...
@@ -82,6 +86,8 @@ export default function ModelBatchData() {
let
cancelled
=
false
;
let
cancelled
=
false
;
(
async
()
=>
{
(
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
try
{
try
{
await
ensureModelMonitorSubjects
();
await
ensureModelMonitorSubjects
();
const
[
nextModel
,
nextBatch
]
=
await
Promise
.
all
([
loadModel
(
modelId
),
loadModelBatch
(
batchId
)]);
const
[
nextModel
,
nextBatch
]
=
await
Promise
.
all
([
loadModel
(
modelId
),
loadModelBatch
(
batchId
)]);
...
@@ -98,13 +104,13 @@ export default function ModelBatchData() {
...
@@ -98,13 +104,13 @@ export default function ModelBatchData() {
setForwardItems
(
itemList
);
setForwardItems
(
itemList
);
}
}
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
if
(
!
cancelled
)
setError
(
loadError
?.
message
||
'模型批次数据加载失败。'
);
if
(
!
cancelled
)
{
setLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'模型批次数据加载失败。'
);
}
}
finally
{
}
finally
{
if
(
!
cancelled
)
setLoading
(
false
);
if
(
!
cancelled
)
setLoading
(
false
);
}
}
})();
})();
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[
batchId
,
modelId
]);
},
[
batchId
,
modelId
,
reloadKey
,
setError
]);
const
forwardStatusIndex
=
useMemo
(()
=>
buildForwardStatusIndex
(
forwards
),
[
forwards
]);
const
forwardStatusIndex
=
useMemo
(()
=>
buildForwardStatusIndex
(
forwards
),
[
forwards
]);
const
forwardItemByRecordId
=
useMemo
(()
=>
buildForwardItemIndex
(
forwardItems
),
[
forwardItems
]);
const
forwardItemByRecordId
=
useMemo
(()
=>
buildForwardItemIndex
(
forwardItems
),
[
forwardItems
]);
...
@@ -225,11 +231,10 @@ export default function ModelBatchData() {
...
@@ -225,11 +231,10 @@ export default function ModelBatchData() {
<
div
className=
"space-y-5"
>
<
div
className=
"space-y-5"
>
<
PageHeader
eyebrow=
{
batch
?.
batchCode
||
'模型批次'
}
title=
{
batch
?.
batchName
||
'批次数据'
}
/>
<
PageHeader
eyebrow=
{
batch
?.
batchCode
||
'模型批次'
}
title=
{
batch
?.
batchName
||
'批次数据'
}
/>
{
loading
?
<
NoticeBanner
>
正在加载批次数据和转发状态...
</
NoticeBanner
>
:
null
}
{
loading
?
<
NoticeBanner
>
正在加载批次数据和转发状态...
</
NoticeBanner
>
:
null
}
{
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
loadFailed
?
<
RequestFailure
title=
"模型批次数据加载失败"
onRetry=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
/>
:
null
}
{
!
error
&&
notice
?
<
NoticeBanner
tone=
"success"
>
{
notice
}
</
NoticeBanner
>
:
null
}
{
batch
?.
syncStatus
===
'partial'
||
batch
?.
syncStatus
===
'failed'
?
<
NoticeBanner
tone=
"error"
>
最近一次同步未完整成功:
{
batch
.
errorMessage
||
'该批次不可重试,请联系管理员排查。'
}
</
NoticeBanner
>
:
null
}
{
batch
?.
syncStatus
===
'partial'
||
batch
?.
syncStatus
===
'failed'
?
<
NoticeBanner
tone=
"error"
>
最近一次同步未完整成功:
{
batch
.
errorMessage
||
'该批次不可重试,请联系管理员排查。'
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
batch
?
(
{
!
loading
&&
!
loadFailed
&&
batch
?
(
<
section
className=
"min-w-0 space-y-4"
>
<
section
className=
"min-w-0 space-y-4"
>
<
SectionHeader
<
SectionHeader
title=
"批次数据"
title=
"批次数据"
...
...
src/pages/project_manager_xy/ModelBatches.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
...
@@ -35,7 +37,9 @@ export default function ModelBatches() {
...
@@ -35,7 +37,9 @@ export default function ModelBatches() {
const
[
page
,
setPage
]
=
useState
(
0
);
const
[
page
,
setPage
]
=
useState
(
0
);
const
[
pageSize
,
setPageSize
]
=
useState
(
20
);
const
[
pageSize
,
setPageSize
]
=
useState
(
20
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
const
[
isSyncDialogOpen
,
setIsSyncDialogOpen
]
=
useState
(
false
);
const
[
isSyncDialogOpen
,
setIsSyncDialogOpen
]
=
useState
(
false
);
const
[
sourceModel
,
setSourceModel
]
=
useState
(
null
);
const
[
sourceModel
,
setSourceModel
]
=
useState
(
null
);
const
[
sourceBatches
,
setSourceBatches
]
=
useState
([]);
const
[
sourceBatches
,
setSourceBatches
]
=
useState
([]);
...
@@ -54,6 +58,8 @@ export default function ModelBatches() {
...
@@ -54,6 +58,8 @@ export default function ModelBatches() {
let
cancelled
=
false
;
let
cancelled
=
false
;
(
async
()
=>
{
(
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
try
{
try
{
await
ensureModelMonitorSubjects
();
await
ensureModelMonitorSubjects
();
const
nextModel
=
await
loadModel
(
modelId
);
const
nextModel
=
await
loadModel
(
modelId
);
...
@@ -63,13 +69,13 @@ export default function ModelBatches() {
...
@@ -63,13 +69,13 @@ export default function ModelBatches() {
setBatches
(
batchList
);
setBatches
(
batchList
);
}
}
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
if
(
!
cancelled
)
setError
(
loadError
?.
message
||
'模型批次加载失败。'
);
if
(
!
cancelled
)
{
setLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'模型批次加载失败。'
);
}
}
finally
{
}
finally
{
if
(
!
cancelled
)
setLoading
(
false
);
if
(
!
cancelled
)
setLoading
(
false
);
}
}
})();
})();
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[
modelId
]);
},
[
modelId
,
reloadKey
,
setError
]);
const
filteredBatches
=
useMemo
(()
=>
{
const
filteredBatches
=
useMemo
(()
=>
{
const
search
=
keyword
.
trim
().
toLowerCase
();
const
search
=
keyword
.
trim
().
toLowerCase
();
...
@@ -115,7 +121,7 @@ export default function ModelBatches() {
...
@@ -115,7 +121,7 @@ export default function ModelBatches() {
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
const
message
=
loadError
?.
message
||
'外部批次列表加载失败。'
;
const
message
=
loadError
?.
message
||
'外部批次列表加载失败。'
;
if
(
message
===
'外部模型列表中已找不到当前模型。'
)
{
if
(
message
===
'外部模型列表中已找不到当前模型。'
)
{
toast
.
info
(
message
);
toast
.
info
(
message
,
{
timeout
:
8000
}
);
setError
(
''
);
setError
(
''
);
}
else
{
}
else
{
setError
(
message
);
setError
(
message
);
...
@@ -137,7 +143,7 @@ export default function ModelBatches() {
...
@@ -137,7 +143,7 @@ export default function ModelBatches() {
try
{
try
{
const
result
=
await
synchronizeModelBatch
(
sourceModel
,
sourceBatch
,
userInfo
,
setProgress
);
const
result
=
await
synchronizeModelBatch
(
sourceModel
,
sourceBatch
,
userInfo
,
setProgress
);
await
refresh
(
result
.
model
);
await
refresh
(
result
.
model
);
toast
.
success
(
`批次“
${
sourceBatch
.
batch_name
}
”同步完成:新增
${
result
.
insertedCount
}
条。`
);
toast
.
success
(
`批次“
${
sourceBatch
.
batch_name
}
”同步完成:新增
${
result
.
insertedCount
}
条。`
,
{
timeout
:
4000
}
);
setError
(
''
);
setError
(
''
);
setIsSyncDialogOpen
(
false
);
setIsSyncDialogOpen
(
false
);
}
catch
(
syncError
)
{
}
catch
(
syncError
)
{
...
@@ -152,9 +158,9 @@ export default function ModelBatches() {
...
@@ -152,9 +158,9 @@ export default function ModelBatches() {
<
div
className=
"space-y-5"
>
<
div
className=
"space-y-5"
>
<
PageHeader
eyebrow=
{
model
?.
modelCode
||
'模型'
}
title=
{
model
?.
modelName
||
'模型批次'
}
/>
<
PageHeader
eyebrow=
{
model
?.
modelCode
||
'模型'
}
title=
{
model
?.
modelName
||
'模型批次'
}
/>
{
loading
?
<
NoticeBanner
>
正在加载模型批次...
</
NoticeBanner
>
:
null
}
{
loading
?
<
NoticeBanner
>
正在加载模型批次...
</
NoticeBanner
>
:
null
}
{
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
loadFailed
?
<
RequestFailure
title=
"模型批次加载失败"
onRetry=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
/
>
:
null
}
{
!
loading
&&
model
?
(
{
!
loading
&&
!
loadFailed
&&
model
?
(
<
section
className=
"min-w-0 space-y-4"
>
<
section
className=
"min-w-0 space-y-4"
>
<
div
className=
"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
>
<
div
className=
"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
>
<
div
className=
"min-w-0 flex-1 sm:max-w-96"
>
<
div
className=
"min-w-0 flex-1 sm:max-w-96"
>
...
...
src/pages/project_manager_xy/ModelMonitorShared.jsx
View file @
3e6db798
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
import
{
Archive
,
Archive
,
Back
,
Back
,
...
@@ -183,15 +184,23 @@ export function UserSelectField({ value, onChange, label, required = false, plac
...
@@ -183,15 +184,23 @@ export function UserSelectField({ value, onChange, label, required = false, plac
const
[
query
,
setQuery
]
=
useState
(
value
?.
label
||
''
);
const
[
query
,
setQuery
]
=
useState
(
value
?.
label
||
''
);
const
[
options
,
setOptions
]
=
useState
([]);
const
[
options
,
setOptions
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useFeedbackState
(
'error'
);
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
setLoading
(
true
);
setLoading
(
true
);
setError
(
''
);
searchUsers
(
query
)
searchUsers
(
query
)
.
then
((
result
)
=>
{
if
(
!
cancelled
)
setOptions
(
result
);
})
.
then
((
result
)
=>
{
if
(
!
cancelled
)
setOptions
(
result
);
})
.
catch
((
requestError
)
=>
{
if
(
!
cancelled
)
{
setOptions
([]);
setError
(
requestError
?.
message
||
'用户列表加载失败,请重新搜索。'
);
}
})
.
finally
(()
=>
{
if
(
!
cancelled
)
setLoading
(
false
);
});
.
finally
(()
=>
{
if
(
!
cancelled
)
setLoading
(
false
);
});
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[
query
]);
},
[
query
,
setError
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
value
?.
label
&&
value
.
label
!==
query
)
setQuery
(
value
.
label
);
if
(
value
?.
label
&&
value
.
label
!==
query
)
setQuery
(
value
.
label
);
...
@@ -203,6 +212,7 @@ export function UserSelectField({ value, onChange, label, required = false, plac
...
@@ -203,6 +212,7 @@ export function UserSelectField({ value, onChange, label, required = false, plac
:
options
;
:
options
;
return
<
XinyuanAutocomplete
return
<
XinyuanAutocomplete
emptyText=
{
loading
?
'正在加载...'
:
'没有匹配用户'
}
emptyText=
{
loading
?
'正在加载...'
:
'没有匹配用户'
}
errorText=
{
error
}
inputValue=
{
query
}
inputValue=
{
query
}
isLoading=
{
loading
}
isLoading=
{
loading
}
isRequired=
{
required
}
isRequired=
{
required
}
...
...
src/pages/project_manager_xy/ModelOverview.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
import
{
AppGrid
,
AppGrid
,
Clock
,
Clock
,
...
@@ -32,12 +34,16 @@ export default function ModelOverview() {
...
@@ -32,12 +34,16 @@ export default function ModelOverview() {
const
[
batches
,
setBatches
]
=
useState
([]);
const
[
batches
,
setBatches
]
=
useState
([]);
const
[
forwards
,
setForwards
]
=
useState
([]);
const
[
forwards
,
setForwards
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
(
async
()
=>
{
(
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
try
{
try
{
await
ensureModelMonitorSubjects
();
await
ensureModelMonitorSubjects
();
const
[
modelList
,
batchList
,
forwardList
]
=
await
Promise
.
all
([
const
[
modelList
,
batchList
,
forwardList
]
=
await
Promise
.
all
([
...
@@ -52,13 +58,13 @@ export default function ModelOverview() {
...
@@ -52,13 +58,13 @@ export default function ModelOverview() {
setError
(
''
);
setError
(
''
);
}
}
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
if
(
!
cancelled
)
setError
(
loadError
?.
message
||
'模型总览加载失败。'
);
if
(
!
cancelled
)
{
setLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'模型总览加载失败。'
);
}
}
finally
{
}
finally
{
if
(
!
cancelled
)
setLoading
(
false
);
if
(
!
cancelled
)
setLoading
(
false
);
}
}
})();
})();
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[]);
},
[
reloadKey
,
setError
]);
const
totalRecords
=
useMemo
(
const
totalRecords
=
useMemo
(
()
=>
batches
.
reduce
((
total
,
batch
)
=>
total
+
batch
.
totalRecordCount
,
0
),
()
=>
batches
.
reduce
((
total
,
batch
)
=>
total
+
batch
.
totalRecordCount
,
0
),
...
@@ -123,9 +129,9 @@ export default function ModelOverview() {
...
@@ -123,9 +129,9 @@ export default function ModelOverview() {
/>
/>
{
loading
?
<
NoticeBanner
>
正在初始化模型监控对象并加载概览...
</
NoticeBanner
>
:
null
}
{
loading
?
<
NoticeBanner
>
正在初始化模型监控对象并加载概览...
</
NoticeBanner
>
:
null
}
{
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
loadFailed
?
<
RequestFailure
title=
"模型总览加载失败"
onRetry=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
/
>
:
null
}
{
!
loading
&&
!
error
?
(
{
!
loading
&&
!
loadFailed
?
(
<>
<>
<
div
className=
"rc-kpi-grid"
data
-
count=
"4"
>
<
div
className=
"rc-kpi-grid"
data
-
count=
"4"
>
{
metrics
.
map
(({
icon
:
Icon
,
iconTone
,
label
,
value
})
=>
(
{
metrics
.
map
(({
icon
:
Icon
,
iconTone
,
label
,
value
})
=>
(
...
...
src/pages/project_manager_xy/Models.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
...
@@ -144,8 +146,11 @@ export default function Models() {
...
@@ -144,8 +146,11 @@ export default function Models() {
const
[
page
,
setPage
]
=
useState
(
0
);
const
[
page
,
setPage
]
=
useState
(
0
);
const
[
pageSize
,
setPageSize
]
=
useState
(
DEFAULT_PAGE_SIZE
);
const
[
pageSize
,
setPageSize
]
=
useState
(
DEFAULT_PAGE_SIZE
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
notice
,
setNotice
]
=
useState
(
''
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
const
[,
setNotice
]
=
useFeedbackState
(
'success'
);
const
[,
setWarning
]
=
useFeedbackState
(
'warning'
);
const
[
isGroupDialogOpen
,
setIsGroupDialogOpen
]
=
useState
(
false
);
const
[
isGroupDialogOpen
,
setIsGroupDialogOpen
]
=
useState
(
false
);
const
[
isDeleteGroupDialogOpen
,
setIsDeleteGroupDialogOpen
]
=
useState
(
false
);
const
[
isDeleteGroupDialogOpen
,
setIsDeleteGroupDialogOpen
]
=
useState
(
false
);
...
@@ -164,7 +169,7 @@ export default function Models() {
...
@@ -164,7 +169,7 @@ export default function Models() {
const
[
syncLoading
,
setSyncLoading
]
=
useState
(
false
);
const
[
syncLoading
,
setSyncLoading
]
=
useState
(
false
);
const
[
syncProgress
,
setSyncProgress
]
=
useState
({
completed
:
0
,
total
:
0
});
const
[
syncProgress
,
setSyncProgress
]
=
useState
({
completed
:
0
,
total
:
0
});
const
[
syncResult
,
setSyncResult
]
=
useState
(
null
);
const
[
syncResult
,
setSyncResult
]
=
useState
(
null
);
const
[
syncDialogError
,
setSyncDialogError
]
=
use
State
(
'
'
);
const
[
syncDialogError
,
setSyncDialogError
]
=
use
FeedbackState
(
'error
'
);
async
function
refresh
()
{
async
function
refresh
()
{
const
[
modelList
,
groupList
]
=
await
Promise
.
all
([
loadModels
(),
loadModelGroups
()]);
const
[
modelList
,
groupList
]
=
await
Promise
.
all
([
loadModels
(),
loadModelGroups
()]);
...
@@ -176,17 +181,19 @@ export default function Models() {
...
@@ -176,17 +181,19 @@ export default function Models() {
let
cancelled
=
false
;
let
cancelled
=
false
;
(
async
()
=>
{
(
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
try
{
try
{
await
ensureModelMonitorSubjects
();
await
ensureModelMonitorSubjects
();
if
(
!
cancelled
)
await
refresh
();
if
(
!
cancelled
)
await
refresh
();
}
catch
(
loadError
)
{
}
catch
(
loadError
)
{
if
(
!
cancelled
)
setError
(
loadError
?.
message
||
'模型列表初始化失败。'
);
if
(
!
cancelled
)
{
setLoadFailed
(
true
);
setError
(
loadError
?.
message
||
'模型列表初始化失败。'
);
}
}
finally
{
}
finally
{
if
(
!
cancelled
)
setLoading
(
false
);
if
(
!
cancelled
)
setLoading
(
false
);
}
}
})();
})();
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[]);
},
[
reloadKey
,
setError
]);
const
groupTree
=
useMemo
(()
=>
buildGroupTree
(
groups
),
[
groups
]);
const
groupTree
=
useMemo
(()
=>
buildGroupTree
(
groups
),
[
groups
]);
const
groupOptions
=
useMemo
(()
=>
flattenGroupOptions
(
groupTree
),
[
groupTree
]);
const
groupOptions
=
useMemo
(()
=>
flattenGroupOptions
(
groupTree
),
[
groupTree
]);
...
@@ -440,7 +447,9 @@ export default function Models() {
...
@@ -440,7 +447,9 @@ export default function Models() {
failedRecordCount
,
failedRecordCount
,
failures
,
failures
,
});
});
setNotice
(
`已完成
${
completedCodes
.
length
}
/
${
selectedEntries
.
length
}
个批次同步,共新增
${
insertedCount
}
条数据。`
);
const
summary
=
`已完成
${
selectedEntries
.
length
-
failures
.
length
}
/
${
selectedEntries
.
length
}
个批次同步,共新增
${
insertedCount
}
条数据。`
;
if
(
failures
.
length
)
setWarning
(
`
${
summary
}
${
failures
.
length
}
个批次出现异常,请查看同步结果。`
);
else
setNotice
(
summary
);
setError
(
''
);
setError
(
''
);
const
[,
localBatchList
]
=
await
Promise
.
all
([
refresh
(),
loadModelBatches
()]);
const
[,
localBatchList
]
=
await
Promise
.
all
([
refresh
(),
loadModelBatches
()]);
const
nextSyncedCodes
=
localBatchList
.
map
((
batch
)
=>
String
(
batch
.
batchCode
));
const
nextSyncedCodes
=
localBatchList
.
map
((
batch
)
=>
String
(
batch
.
batchCode
));
...
@@ -458,10 +467,9 @@ export default function Models() {
...
@@ -458,10 +467,9 @@ export default function Models() {
<
PageHeader
eyebrow=
"模型监控"
title=
"全部模型"
/>
<
PageHeader
eyebrow=
"模型监控"
title=
"全部模型"
/>
<
div
className=
"space-y-8"
>
<
div
className=
"space-y-8"
>
{
loading
?
<
NoticeBanner
>
正在初始化模型对象并加载列表...
</
NoticeBanner
>
:
null
}
{
loading
?
<
NoticeBanner
>
正在初始化模型对象并加载列表...
</
NoticeBanner
>
:
null
}
{
error
?
<
NoticeBanner
tone=
"error"
>
{
error
}
</
NoticeBanner
>
:
null
}
{
!
loading
&&
loadFailed
?
<
RequestFailure
title=
"模型列表加载失败"
onRetry=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
/>
:
null
}
{
!
error
&&
notice
?
<
NoticeBanner
tone=
"success"
>
{
notice
}
</
NoticeBanner
>
:
null
}
{
!
loading
?
(
{
!
loading
&&
!
loadFailed
?
(
<>
<>
<
div
className=
"grid min-w-0 gap-5 lg:grid-cols-[15rem_minmax(0,1fr)] lg:items-start"
>
<
div
className=
"grid min-w-0 gap-5 lg:grid-cols-[15rem_minmax(0,1fr)] lg:items-start"
>
<
Surface
className=
"min-w-0 rounded-3xl p-4"
variant=
"default"
>
<
Surface
className=
"min-w-0 rounded-3xl p-4"
variant=
"default"
>
...
@@ -591,7 +599,7 @@ export default function Models() {
...
@@ -591,7 +599,7 @@ export default function Models() {
<
div
className=
"space-y-5"
>
<
div
className=
"space-y-5"
>
<
Stepper
aria
-
label=
"同步步骤"
className=
"mx-auto max-w-4xl"
currentStep=
{
syncStep
-
1
}
size=
"sm"
>
{
[
'加载批次'
,
'选择批次'
,
'执行同步'
].
map
((
label
)
=>
<
Stepper
.
Step
key=
{
label
}
><
Stepper
.
Indicator
/><
Stepper
.
Content
><
Stepper
.
Title
>
{
label
}
</
Stepper
.
Title
></
Stepper
.
Content
><
Stepper
.
Separator
/></
Stepper
.
Step
>)
}
</
Stepper
>
<
Stepper
aria
-
label=
"同步步骤"
className=
"mx-auto max-w-4xl"
currentStep=
{
syncStep
-
1
}
size=
"sm"
>
{
[
'加载批次'
,
'选择批次'
,
'执行同步'
].
map
((
label
)
=>
<
Stepper
.
Step
key=
{
label
}
><
Stepper
.
Indicator
/><
Stepper
.
Content
><
Stepper
.
Title
>
{
label
}
</
Stepper
.
Title
></
Stepper
.
Content
><
Stepper
.
Separator
/></
Stepper
.
Step
>)
}
</
Stepper
>
{
syncDialogError
?
<
NoticeBanner
tone=
"error"
>
{
syncDialogError
}
</
NoticeBanner
>
:
null
}
{
syncDialogError
?
<
RequestFailure
title=
{
syncStep
===
1
?
'来源模型与批次加载失败'
:
'同步结果读取失败'
}
onRetry=
{
syncStep
===
1
?
openSyncDialog
:
undefined
}
/
>
:
null
}
{
syncStep
===
1
&&
syncLoading
?
<
NoticeBanner
>
正在加载全部来源模型、批次和本地同步状态...
</
NoticeBanner
>
:
null
}
{
syncStep
===
1
&&
syncLoading
?
<
NoticeBanner
>
正在加载全部来源模型、批次和本地同步状态...
</
NoticeBanner
>
:
null
}
{
syncStep
===
2
?
(
{
syncStep
===
2
?
(
...
...
src/pages/project_manager_xy/model-monitor.business-flow.md
View file @
3e6db798
...
@@ -182,3 +182,11 @@ MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql`
...
@@ -182,3 +182,11 @@ MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql`
-
批量同步模型批次弹窗使用紧凑步骤条、年份筛选与全选工具行;年份下按来源模型分段展示批次,同一模型的批次集中在同一列表区,并在分段标题展示模型名称、编码和批次数量。各分段使用 HeroUI Pro ListView 原生多选,切换年份继续保留其它年份已选项,已同步批次继续禁用,选择和同步口径不变。
-
批量同步模型批次弹窗使用紧凑步骤条、年份筛选与全选工具行;年份下按来源模型分段展示批次,同一模型的批次集中在同一列表区,并在分段标题展示模型名称、编码和批次数量。各分段使用 HeroUI Pro ListView 原生多选,切换年份继续保留其它年份已选项,已同步批次继续禁用,选择和同步口径不变。
-
查看态详情统一放在单一 secondary Surface;编辑态字段直接位于 Modal Overlay,字段必填、枚举来源、主键禁用和完成填写校验保持原规则。
-
查看态详情统一放在单一 secondary Surface;编辑态字段直接位于 Modal Overlay,字段必填、枚举来源、主键禁用和完成填写校验保持原规则。
-
Loading、错误和空状态由 HeroUI/HeroUI Pro 语义组件展示;不得生成测试记录或用伪造数值填充空页面。
-
Loading、错误和空状态由 HeroUI/HeroUI Pro 语义组件展示;不得生成测试记录或用伪造数值填充空页面。
## 8. 操作提示与失败重试
-
功能入口:模型总览、模型分组/归档信息、批次同步、批次数据隐藏/恢复、转发提交、填写保存、完成填写和两级审批,以及弹窗中的用户查询。
-
成功、失败和降级警告共用应用根部 HeroUI Toast,位于视口顶部且高于业务弹窗和侧栏,当前提示使用正文右侧的小号透明关闭按钮,点击或键盘激活通过
`close`
slot 关闭,后排提示隐藏关闭按钮;成功 4 秒、错误/警告 8 秒自动消失,悬停或聚焦暂停计时,同一错误重试时重新弹出。
-
关闭提示不改变同步/填写/审批状态,也不清空选择或草稿。保存失败保留原页面和弹窗;用户搜索失败保留字段级错误并可重新搜索。请求写入协议与
`metadata`
字段保持不变。
-
总览、模型列表、批次、批次数据、转发审批和填写数据加载失败时显示失败空状态及读取重试;重试只读取当前页面资源,不重复同步、转发或审批。批量同步来源加载失败可重新读取,同步结果刷新失败仍保留结果供查看。
-
批量同步出现部分失败时使用警告浮层并保留逐批结果;已经创建的批次继续禁止重复同步。历史同步失败摘要、同步进度、待填写数量属于业务状态,继续在原位置展示。
src/pages/project_manager_xy/notifications/NotificationListPage.jsx
View file @
3e6db798
import
RequestFailure
from
'@/components/RequestFailure'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
Bell
,
Close
,
OpenExternal
,
Search
}
from
'@/components/AppIcons'
;
import
{
Bell
,
Close
,
OpenExternal
,
Search
}
from
'@/components/AppIcons'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
import
{
Alert
,
Avatar
,
Avatar
,
Button
,
Button
,
Card
,
Card
,
...
@@ -200,7 +201,7 @@ export default function NotificationListPage() {
...
@@ -200,7 +201,7 @@ export default function NotificationListPage() {
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
unreadTotal
,
setUnreadTotal
]
=
useState
(
0
);
const
[
unreadTotal
,
setUnreadTotal
]
=
useState
(
0
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
use
State
(
'
'
);
const
[
error
,
setError
]
=
use
FeedbackState
(
'error
'
);
const
[
busyKey
,
setBusyKey
]
=
useState
(
''
);
const
[
busyKey
,
setBusyKey
]
=
useState
(
''
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
...
@@ -264,12 +265,12 @@ export default function NotificationListPage() {
...
@@ -264,12 +265,12 @@ export default function NotificationListPage() {
setBusyKey
(
key
);
setBusyKey
(
key
);
try
{
try
{
await
operation
();
await
operation
();
toast
.
success
(
successMessage
,
{
timeout
:
3
000
});
toast
.
success
(
successMessage
,
{
timeout
:
4
000
});
refresh
();
refresh
();
return
true
;
return
true
;
}
catch
(
operationError
)
{
}
catch
(
operationError
)
{
toast
.
danger
(
operationError
?.
message
||
'操作失败,请稍后重试'
,
{
toast
.
danger
(
operationError
?.
message
||
'操作失败,请稍后重试'
,
{
timeout
:
4
000
,
timeout
:
8
000
,
});
});
return
false
;
return
false
;
}
finally
{
}
finally
{
...
@@ -319,7 +320,7 @@ export default function NotificationListPage() {
...
@@ -319,7 +320,7 @@ export default function NotificationListPage() {
refresh
();
refresh
();
}
catch
(
operationError
)
{
}
catch
(
operationError
)
{
toast
.
danger
(
operationError
?.
message
||
'无法打开通知关联流程'
,
{
toast
.
danger
(
operationError
?.
message
||
'无法打开通知关联流程'
,
{
timeout
:
4
000
,
timeout
:
8
000
,
});
});
}
finally
{
}
finally
{
setBusyKey
(
''
);
setBusyKey
(
''
);
...
@@ -403,16 +404,7 @@ export default function NotificationListPage() {
...
@@ -403,16 +404,7 @@ export default function NotificationListPage() {
</
Tabs
>
</
Tabs
>
</
div
>
</
div
>
{
error
?
(
{
!
loading
&&
error
?
<
RequestFailure
title=
"通知加载失败"
onRetry=
{
refresh
}
/>
:
null
}
<
Alert
status=
"danger"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
</
Alert
.
Content
>
<
Button
size=
"sm"
variant=
"ghost"
onPress=
{
refresh
}
>
重试
</
Button
>
</
Alert
>
)
:
null
}
{
loading
&&
notifications
.
length
===
0
?
(
{
loading
&&
notifications
.
length
===
0
?
(
<
div
className=
"flex min-h-64 items-center justify-center"
aria
-
label=
"通知加载中"
>
<
div
className=
"flex min-h-64 items-center justify-center"
aria
-
label=
"通知加载中"
>
...
...
src/pages/project_manager_xy/notifications/notifications.business-flow.md
View file @
3e6db798
...
@@ -35,3 +35,9 @@
...
@@ -35,3 +35,9 @@
-
接口 401/403、网络失败或动态 action 配置缺失时不得伪造成功状态或未读数量。
-
接口 401/403、网络失败或动态 action 配置缺失时不得伪造成功状态或未读数量。
-
通知中心不读取或修改模型监控 Subject、Record、审批字段及用户
`applications`
授权结构。
-
通知中心不读取或修改模型监控 Subject、Record、审批字段及用户
`applications`
授权结构。
-
本应用不在五项完整功能白名单中,不实现
`shortTitle`
、Subject View 权限或审计材料中心文件工作流。
-
本应用不在五项完整功能白名单中,不实现
`shortTitle`
、Subject View 权限或审计材料中心文件工作流。
## 操作提示与失败重试
-
通知查询、已读操作、待办动作和关联流程打开失败均使用根部 HeroUI Toast,弹窗和移动侧栏上方可见;成功 4 秒、失败 8 秒自动消失,当前提示正文右侧的透明关闭图标始终可见,点击或键盘激活通过
`close`
slot 关闭,后排提示隐藏关闭按钮;悬停或聚焦暂停计时。
-
列表加载失败保留“通知加载失败”空状态与“重新加载”,重试保留当前分类、已读状态、关键词及页码,只重新读取通知。
-
关闭提示不会把请求失败改为成功,不修改通知已读/待办状态或未读数量,不重复执行待办动作或打开关联链接;接口协议保持不变。
src/tailwind.css
View file @
3e6db798
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
@import
"./design-system/theme.css"
;
@import
"./design-system/theme.css"
;
@import
"./design-system/tokens.css"
;
@import
"./design-system/tokens.css"
;
@import
"./design-system/shell.css"
;
@import
"./design-system/shell.css"
;
@import
"./design-system/feedback.css"
;
@source
"./*.{js,jsx,ts,tsx}"
;
@source
"./*.{js,jsx,ts,tsx}"
;
@source
"./components/**/*.{js,jsx,ts,tsx}"
;
@source
"./components/**/*.{js,jsx,ts,tsx}"
;
...
...
src/wrapper/Site.jsx
View file @
3e6db798
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
getSystemInfo
}
from
'@/services/DataService'
;
import
{
getSystemInfo
}
from
'@/services/DataService'
;
import
dayjs
from
'dayjs'
;
import
dayjs
from
'dayjs'
;
import
customParseFormat
from
'dayjs/plugin/customParseFormat'
;
import
customParseFormat
from
'dayjs/plugin/customParseFormat'
;
...
@@ -18,7 +19,7 @@ export default function SiteWrapper() {
...
@@ -18,7 +19,7 @@ export default function SiteWrapper() {
const
[
siteInfo
,
setSiteInfo
]
=
useState
(
null
);
const
[
siteInfo
,
setSiteInfo
]
=
useState
(
null
);
const
[
metadata
,
setMetadata
]
=
useState
(
null
);
const
[
metadata
,
setMetadata
]
=
useState
(
null
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loadError
,
setLoadError
]
=
use
State
(
'
'
);
const
[
loadError
,
setLoadError
]
=
use
FeedbackState
(
'warning
'
);
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
...
...
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