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
513706c5
Commit
513706c5
authored
Oct 08, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: make operation feedback visible and dismissible
parent
3c5cc2ab
Show whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
295 additions
and
171 deletions
+295
-171
xinyuan-design-system.md
docs/xinyuan-design-system.md
+6
-0
app.js
src/app.js
+7
-1
AppFeedback.jsx
src/components/AppFeedback.jsx
+32
-0
AppShell.jsx
src/components/AppShell.jsx
+0
-2
ReferenceSearchSelect.jsx
src/components/ReferenceSearchSelect.jsx
+2
-1
UserSearchSelect.jsx
src/components/UserSearchSelect.jsx
+10
-3
index.jsx
src/components/record-workbench/index.jsx
+13
-19
feedback.css
src/design-system/feedback.css
+20
-0
useFeedbackState.js
src/hooks/useFeedbackState.js
+38
-0
Login.jsx
src/pages/Login.jsx
+3
-10
ReferenceRecordsTable.jsx
...s/RecordDetailDrawer/components/ReferenceRecordsTable.jsx
+9
-9
TailwindEditableField.jsx
...s/RecordDetailDrawer/components/TailwindEditableField.jsx
+22
-5
useSubjectRecord.js
src/pages/RecordDetailDrawer/hooks/useSubjectRecord.js
+2
-1
index.jsx
src/pages/RecordDetailDrawer/index.jsx
+12
-11
ApplicationSwitcherDialog.jsx
...pages/problem_rectification/ApplicationSwitcherDialog.jsx
+4
-10
ArchiveSnapshotItems.jsx
...es/problem_rectification/archive/ArchiveSnapshotItems.jsx
+22
-9
index.jsx
src/pages/problem_rectification/archive/index.jsx
+7
-4
ProblemRectificationMaterialField.jsx
...fication/components/ProblemRectificationMaterialField.jsx
+5
-15
index.jsx
src/pages/problem_rectification/dashboard/index.jsx
+19
-11
index.jsx
src/pages/problem_rectification/flow-table/index.jsx
+13
-17
index.jsx
src/pages/problem_rectification/form/index.jsx
+10
-8
index.jsx
src/pages/problem_rectification/list/index.jsx
+12
-21
NotificationListPage.jsx
...blem_rectification/notifications/NotificationListPage.jsx
+14
-13
problem-rectification.business-flow.md
...blem_rectification/problem-rectification.business-flow.md
+10
-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 @
513706c5
...
@@ -31,6 +31,12 @@
...
@@ -31,6 +31,12 @@
-
共享组件只能组合 HeroUI/HeroUI Pro 的官方 compound API,不得复刻旧组件 props。
-
共享组件只能组合 HeroUI/HeroUI Pro 的官方 compound API,不得复刻旧组件 props。
-
官方组件暂不覆盖的场景应优先组合 HeroUI/HeroUI Pro compound API,并在本设计系统中补充经过评审的约定;不得以 Catalyst 或可见原生控件作为降级方案。
-
官方组件暂不覆盖的场景应优先组合 HeroUI/HeroUI Pro compound API,并在本设计系统中补充经过评审的约定;不得以 Catalyst 或可见原生控件作为降级方案。
### 1.1 操作反馈浮层
接口成功、失败及降级警告统一使用根部
`AppFeedback`
的 HeroUI
`Toast.Provider`
和官方 compound 组件;登录、独立流程页和 AppShell 共用一个 Provider,业务页面不再渲染接口反馈横条。成功默认 4 秒、错误/警告默认 8 秒后消失,关闭不改变请求结果或表单草稿。
`src/design-system/feedback.css`
是操作反馈的窄范围交互约定,沿用模型分析应用
`f167d24`
:仅调整 Toast 层级、安全区位置、长文布局及关闭按钮的可见性/点击范围。浮层层级 300,高于业务 Modal 和移动侧栏;关闭按钮无需悬停即可操作。颜色、边框、圆角、阴影与 Hover 继续使用 HeroUI 默认主题,不在页面 CSS 或
`tokens.css`
中覆盖。
## 2. 主题权威
## 2. 主题权威
-
`src/design-system/theme.css`
是浅色、暗色、品牌色、语义色、圆角和字体的唯一主题权威,以 HeroUI 官方主题工具导出值为基础,并在文件内固化批准的 L1/L2/Overlay 映射。Shell 配色以本项目 2026-09-28 调整为准;升级 HeroUI 时重新导出基础值、复核三项语义映射与 Shell Token。
-
`src/design-system/theme.css`
是浅色、暗色、品牌色、语义色、圆角和字体的唯一主题权威,以 HeroUI 官方主题工具导出值为基础,并在文件内固化批准的 L1/L2/Overlay 映射。Shell 配色以本项目 2026-09-28 调整为准;升级 HeroUI 时重新导出基础值、复核三项语义映射与 Shell Token。
...
...
src/app.js
View file @
513706c5
import
'@/tailwind.css'
;
import
'@/tailwind.css'
;
import
{
defineApp
}
from
'umi'
;
import
{
defineApp
}
from
'umi'
;
import
{
createElement
}
from
'react'
;
import
AppFeedback
from
'@/components/AppFeedback'
;
import
{
import
{
APPLICATION_BASE_PATH
,
APPLICATION_BASE_PATH
,
APPLICATION_NAME
,
APPLICATION_NAME
,
...
@@ -40,4 +42,8 @@ export async function getInitialState() {
...
@@ -40,4 +42,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 @
513706c5
import
{
Toast
}
from
'@heroui/react'
;
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"
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
>
<
Toast
.
CloseButton
aria
-
label=
"关闭提示"
className=
"app-feedback-close"
/>
</
Toast
>
)
}
</
Toast
.
Provider
>
</>
);
}
src/components/AppShell.jsx
View file @
513706c5
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'
;
...
@@ -32,7 +31,6 @@ export default function AppShell({
...
@@ -32,7 +31,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/ReferenceSearchSelect.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
{
queryRecords
}
from
'@/services/DataService'
;
import
{
queryRecords
}
from
'@/services/DataService'
;
import
{
Button
,
Label
}
from
'@heroui/react'
;
import
{
Button
,
Label
}
from
'@heroui/react'
;
...
@@ -334,7 +335,7 @@ export default function ReferenceSearchSelect({
...
@@ -334,7 +335,7 @@ export default function ReferenceSearchSelect({
const
[
options
,
setOptions
]
=
useState
([]);
const
[
options
,
setOptions
]
=
useState
([]);
const
[
selectedById
,
setSelectedById
]
=
useState
({});
const
[
selectedById
,
setSelectedById
]
=
useState
({});
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
use
State
(
'
'
);
const
[
error
,
setError
]
=
use
FeedbackState
(
'error
'
);
const
[
active
,
setActive
]
=
useState
(
false
);
const
[
active
,
setActive
]
=
useState
(
false
);
const
lastSearchRequestId
=
useRef
(
0
);
const
lastSearchRequestId
=
useRef
(
0
);
...
...
src/components/UserSearchSelect.jsx
View file @
513706c5
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
Label
}
from
'@heroui/react'
;
import
{
Label
}
from
'@heroui/react'
;
import
{
loadUser
,
queryUsers
}
from
'@/services/DataService'
;
import
{
loadUser
,
queryUsers
}
from
'@/services/DataService'
;
import
{
normalizeUserLabel
,
toArray
}
from
'@/utils/subjectView'
;
import
{
normalizeUserLabel
,
toArray
}
from
'@/utils/subjectView'
;
...
@@ -21,11 +22,13 @@ export default function UserSearchSelect({
...
@@ -21,11 +22,13 @@ export default function UserSearchSelect({
const
[
users
,
setUsers
]
=
useState
([]);
const
[
users
,
setUsers
]
=
useState
([]);
const
[
profiles
,
setProfiles
]
=
useState
({});
const
[
profiles
,
setProfiles
]
=
useState
({});
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useFeedbackState
();
const
selectedIds
=
useMemo
(()
=>
toArray
(
value
).
map
(
getUserId
).
filter
(
Boolean
),
[
value
]);
const
selectedIds
=
useMemo
(()
=>
toArray
(
value
).
map
(
getUserId
).
filter
(
Boolean
),
[
value
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
setLoading
(
true
);
setLoading
(
true
);
setError
(
''
);
const
text
=
keyword
.
trim
();
const
text
=
keyword
.
trim
();
queryUsers
(
0
,
30
,
text
?
{
queryUsers
(
0
,
30
,
text
?
{
$or
:
[
$or
:
[
...
@@ -42,14 +45,17 @@ export default function UserSearchSelect({
...
@@ -42,14 +45,17 @@ export default function UserSearchSelect({
...
Object
.
fromEntries
(
nextUsers
.
map
((
user
)
=>
[
getUserId
(
user
),
user
])),
...
Object
.
fromEntries
(
nextUsers
.
map
((
user
)
=>
[
getUserId
(
user
),
user
])),
}));
}));
})
})
.
catch
(()
=>
{
.
catch
((
requestError
)
=>
{
if
(
!
cancelled
)
setUsers
([]);
if
(
!
cancelled
)
{
setUsers
([]);
setError
(
requestError
?.
message
||
'用户列表加载失败,请重新搜索。'
);
}
})
})
.
finally
(()
=>
{
.
finally
(()
=>
{
if
(
!
cancelled
)
setLoading
(
false
);
if
(
!
cancelled
)
setLoading
(
false
);
});
});
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[
keyword
]);
},
[
keyword
,
setError
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
const
missing
=
selectedIds
.
filter
((
id
)
=>
!
profiles
[
id
]);
const
missing
=
selectedIds
.
filter
((
id
)
=>
!
profiles
[
id
]);
...
@@ -82,6 +88,7 @@ export default function UserSearchSelect({
...
@@ -82,6 +88,7 @@ export default function UserSearchSelect({
<
XinyuanAutocomplete
<
XinyuanAutocomplete
aria
-
label=
{
ariaLabel
}
aria
-
label=
{
ariaLabel
}
inputValue=
{
keyword
}
inputValue=
{
keyword
}
errorText=
{
error
}
isDisabled=
{
disabled
}
isDisabled=
{
disabled
}
isClearable=
{
selectedIds
.
length
>
0
}
isClearable=
{
selectedIds
.
length
>
0
}
isLoading=
{
loading
}
isLoading=
{
loading
}
...
...
src/components/record-workbench/index.jsx
View file @
513706c5
...
@@ -4,6 +4,7 @@ import {
...
@@ -4,6 +4,7 @@ import {
DataTableText
,
DataTableText
,
}
from
'@/components/DataTable'
;
}
from
'@/components/DataTable'
;
import
{
KPI
}
from
'@heroui-pro/react/kpi'
;
import
{
KPI
}
from
'@heroui-pro/react/kpi'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
import
{
TableActionButton
,
TableActionButton
,
TableActionGroup
,
TableActionGroup
,
...
@@ -28,7 +29,6 @@ import TableFieldValue from '@/components/TableFieldValue';
...
@@ -28,7 +29,6 @@ import TableFieldValue from '@/components/TableFieldValue';
import
{
formatFieldValue
,
getFieldValue
,
normalizeText
}
from
'@/utils/subjectView'
;
import
{
formatFieldValue
,
getFieldValue
,
normalizeText
}
from
'@/utils/subjectView'
;
import
{
formatTableFieldValue
,
getTableFieldDisplayConfig
}
from
'@/utils/tableFieldDisplay'
;
import
{
formatTableFieldValue
,
getTableFieldDisplayConfig
}
from
'@/utils/tableFieldDisplay'
;
import
{
import
{
Alert
,
Button
,
Button
,
Disclosure
,
Disclosure
,
Input
,
Input
,
...
@@ -574,7 +574,7 @@ export default function RecordWorkbench({
...
@@ -574,7 +574,7 @@ export default function RecordWorkbench({
const
[
expandedRowIds
,
setExpandedRowIds
]
=
useState
({});
const
[
expandedRowIds
,
setExpandedRowIds
]
=
useState
({});
const
filterFieldVariant
=
'secondary'
;
const
filterFieldVariant
=
'secondary'
;
const
loading
=
Boolean
(
states
?.
loading
);
const
loading
=
Boolean
(
states
?.
loading
);
const
error
=
states
?.
error
||
''
;
const
loadFailed
=
Boolean
(
states
?.
loadFailed
)
;
const
totalRecords
=
pagination
?.
totalRecords
??
records
.
length
;
const
totalRecords
=
pagination
?.
totalRecords
??
records
.
length
;
const
presentationColumns
=
useMemo
(
const
presentationColumns
=
useMemo
(
()
=>
()
=>
...
@@ -1283,25 +1283,19 @@ export default function RecordWorkbench({
...
@@ -1283,25 +1283,19 @@ export default function RecordWorkbench({
) : null}
) : null}
</div>
</div>
{error ? (
{!loading && loadFailed ? (
<Alert status="danger">
<EmptyState className="min-h-48" size="md">
<Alert.Content><Alert.Description>{error}</Alert.Description></Alert.Content>
<EmptyState.Header>
</Alert>
<EmptyState.Title>{title || '列表'}加载失败</EmptyState.Title>
<EmptyState.Description>详细错误见操作提示,请重新加载。</EmptyState.Description>
</EmptyState.Header>
<EmptyState.Footer>
<Button variant="secondary" onPress={states.onRetry || onQuery}>重新加载</Button>
</EmptyState.Footer>
</EmptyState>
) : null}
) : null}
{states?.warning ? (
<div ref={tableScrollRef} className={!loading && loadFailed ? 'hidden' : tableContentClass}>
<Alert status="warning">
<Alert.Content><Alert.Description>{states.warning}</Alert.Description></Alert.Content>
</Alert>
) : null}
{states?.success ? (
<Alert status="success">
<Alert.Content><Alert.Description>{states.success}</Alert.Description></Alert.Content>
</Alert>
) : null}
<div ref={tableScrollRef} className={tableContentClass}>
<DataTable
<DataTable
ariaLabel={title || '业务数据列表'}
ariaLabel={title || '业务数据列表'}
columns={dataGridColumns}
columns={dataGridColumns}
...
...
src/design-system/feedback.css
0 → 100644
View file @
513706c5
/* 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
));
}
.app-feedback-toast.toast
{
gap
:
0.5rem
;
padding-inline-end
:
3.5rem
;
}
/* HeroUI hides this button until hover by default; touch and keyboard users need it visible. */
.app-feedback-toast
[
data-frontmost
=
"true"
]
.app-feedback-close
{
top
:
0.25rem
;
right
:
0.25rem
;
width
:
2.75rem
;
height
:
2.75rem
;
opacity
:
1
;
pointer-events
:
auto
;
}
src/hooks/useFeedbackState.js
0 → 100644
View file @
513706c5
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 @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
xyLogoRed
from
'@/assets/xy-logo-red.png'
;
import
xyLogoRed
from
'@/assets/xy-logo-red.png'
;
import
xyLogoWhite
from
'@/assets/xy-logo-white.png'
;
import
xyLogoWhite
from
'@/assets/xy-logo-white.png'
;
import
{
import
{
...
@@ -7,7 +8,7 @@ import authService from '@/services/authService';
...
@@ -7,7 +8,7 @@ import authService from '@/services/authService';
import
{
normalizeRedirect
}
from
'@/utils/authRedirect'
;
import
{
normalizeRedirect
}
from
'@/utils/authRedirect'
;
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
=
normalizeRedirect
(
searchParams
.
get
(
'redirect'
));
const
redirect
=
normalizeRedirect
(
searchParams
.
get
(
'redirect'
));
const
refreshCaptcha
=
async
()
=>
{
const
refreshCaptcha
=
async
()
=>
{
...
@@ -180,14 +181,6 @@ export default function LoginPage() {
...
@@ -180,14 +181,6 @@ export default function LoginPage() {
</
div
>
</
div
>
</
div
>
</
div
>
{
error
?
(
<
Alert
status=
"danger"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
)
:
null
}
<
Button
<
Button
fullWidth
fullWidth
className=
"login-submit-button"
className=
"login-submit-button"
...
...
src/pages/RecordDetailDrawer/components/ReferenceRecordsTable.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
DataTable
,
DataTableEmptyState
}
from
'@/components/DataTable'
;
import
{
DataTable
,
DataTableEmptyState
}
from
'@/components/DataTable'
;
import
{
Alert
,
Spinner
}
from
'@heroui/react'
;
import
{
Alert
,
Button
,
Spinner
}
from
'@heroui/react'
;
import
{
loadSubject
,
queryRecords
}
from
'@/services/DataService'
;
import
{
loadSubject
,
queryRecords
}
from
'@/services/DataService'
;
import
{
import
{
formatDateText
,
formatDateText
,
...
@@ -131,7 +132,8 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
...
@@ -131,7 +132,8 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useFeedbackState
(
'error'
);
const
[
retryKey
,
setRetryKey
]
=
useState
(
0
);
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
...
@@ -195,7 +197,7 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
...
@@ -195,7 +197,7 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
return
()
=>
{
return
()
=>
{
cancelled
=
true
;
cancelled
=
true
;
};
};
},
[
pageSize
,
populatedRecords
,
referenceIds
,
subjectName
]);
},
[
pageSize
,
populatedRecords
,
referenceIds
,
subjectName
,
retryKey
,
setError
]);
if
(
referenceIds
.
length
===
0
)
{
if
(
referenceIds
.
length
===
0
)
{
return
(
return
(
...
@@ -232,15 +234,13 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
...
@@ -232,15 +234,13 @@ export default function ReferenceRecordsTable({ fieldDef, value, pageSize = 1000
return
(
return
(
<
div
className=
"space-y-2"
>
<
div
className=
"space-y-2"
>
{
error
?
(
{
error
?
(
<
Alert
status=
"warning"
>
<
Button
size=
"sm"
variant=
"secondary"
onPress=
{
()
=>
setRetryKey
((
key
)
=>
key
+
1
)
}
>
<
Alert
.
Content
>
重新加载关联明细
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
</
Button
>
</
Alert
.
Content
>
</
Alert
>
)
:
null
}
)
:
null
}
<
ReferenceRecordsFrame
action=
{
action
}
count=
{
records
.
length
}
>
<
ReferenceRecordsFrame
action=
{
action
}
count=
{
records
.
length
}
>
{
records
.
length
===
0
?
(
{
records
.
length
===
0
?
(
<
DataTableEmptyState
title=
"暂无可展示的关联明细"
/>
<
DataTableEmptyState
title=
{
error
?
'关联明细加载失败,详细错误见操作提示'
:
'暂无可展示的关联明细'
}
/>
)
:
(
)
:
(
<
DataTable
<
DataTable
ariaLabel=
"关联记录"
ariaLabel=
"关联记录"
...
...
src/pages/RecordDetailDrawer/components/TailwindEditableField.jsx
View file @
513706c5
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
clsx
from
'clsx'
;
import
clsx
from
'clsx'
;
import
{
import
{
Alert
,
Alert
,
...
@@ -168,6 +169,8 @@ function FieldValue({ value, fieldDef }) {
...
@@ -168,6 +169,8 @@ function FieldValue({ value, fieldDef }) {
function
UserSelector
({
value
,
onChange
,
multiple
,
variant
})
{
function
UserSelector
({
value
,
onChange
,
multiple
,
variant
})
{
const
[
keyword
,
setKeyword
]
=
useState
(
''
);
const
[
keyword
,
setKeyword
]
=
useState
(
''
);
const
[
options
,
setOptions
]
=
useState
([]);
const
[
options
,
setOptions
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useFeedbackState
();
const
selectedIds
=
useMemo
(
const
selectedIds
=
useMemo
(
()
=>
toArray
(
value
).
map
((
item
)
=>
item
?.
_id
||
item
).
filter
(
Boolean
),
()
=>
toArray
(
value
).
map
((
item
)
=>
item
?.
_id
||
item
).
filter
(
Boolean
),
[
value
],
[
value
],
...
@@ -175,6 +178,8 @@ function UserSelector({ value, onChange, multiple, variant }) {
...
@@ -175,6 +178,8 @@ function UserSelector({ value, onChange, multiple, variant }) {
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
setLoading
(
true
);
setError
(
''
);
const
loadOptions
=
async
()
=>
{
const
loadOptions
=
async
()
=>
{
const
filter
=
keyword
.
trim
()
const
filter
=
keyword
.
trim
()
...
@@ -194,7 +199,10 @@ function UserSelector({ value, onChange, multiple, variant }) {
...
@@ -194,7 +199,10 @@ function UserSelector({ value, onChange, multiple, variant }) {
}
catch
(
error
)
{
}
catch
(
error
)
{
if
(
!
cancelled
)
{
if
(
!
cancelled
)
{
setOptions
([]);
setOptions
([]);
setError
(
error
?.
message
||
'用户列表加载失败,请重新搜索。'
);
}
}
}
finally
{
if
(
!
cancelled
)
setLoading
(
false
);
}
}
};
};
...
@@ -203,7 +211,7 @@ function UserSelector({ value, onChange, multiple, variant }) {
...
@@ -203,7 +211,7 @@ function UserSelector({ value, onChange, multiple, variant }) {
return
()
=>
{
return
()
=>
{
cancelled
=
true
;
cancelled
=
true
;
};
};
},
[
keyword
]);
},
[
keyword
,
setError
]);
const
handleSelectionChange
=
(
keys
)
=>
{
const
handleSelectionChange
=
(
keys
)
=>
{
if
(
!
multiple
)
{
if
(
!
multiple
)
{
...
@@ -234,8 +242,10 @@ function UserSelector({ value, onChange, multiple, variant }) {
...
@@ -234,8 +242,10 @@ function UserSelector({ value, onChange, multiple, variant }) {
{
...
selectionProps
}
{
...
selectionProps
}
aria
-
label=
"用户"
aria
-
label=
"用户"
emptyText=
"暂无用户"
emptyText=
"暂无用户"
errorText=
{
error
}
inputValue=
{
keyword
}
inputValue=
{
keyword
}
isClearable=
{
selectedIds
.
length
>
0
}
isClearable=
{
selectedIds
.
length
>
0
}
isLoading=
{
loading
}
options=
{
autocompleteOptions
}
options=
{
autocompleteOptions
}
placeholder=
"请选择用户"
placeholder=
"请选择用户"
searchPlaceholder=
"搜索用户名或显示名"
searchPlaceholder=
"搜索用户名或显示名"
...
@@ -321,6 +331,7 @@ function ControlledFieldEditor({
...
@@ -321,6 +331,7 @@ function ControlledFieldEditor({
useAuditMaterialCenter
,
useAuditMaterialCenter
,
})
{
})
{
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setRequestError
]
=
useFeedbackState
();
const
[
uploading
,
setUploading
]
=
useState
(
false
);
const
[
uploading
,
setUploading
]
=
useState
(
false
);
const
fieldType
=
fieldDef
?.
type
||
'text'
;
const
fieldType
=
fieldDef
?.
type
||
'text'
;
const
isDateField
=
isDateFieldType
(
fieldType
);
const
isDateField
=
isDateFieldType
(
fieldType
);
...
@@ -333,6 +344,7 @@ function ControlledFieldEditor({
...
@@ -333,6 +344,7 @@ function ControlledFieldEditor({
setUploading
(
true
);
setUploading
(
true
);
setError
(
''
);
setError
(
''
);
setRequestError
(
''
);
onValidationChange
?.(
fieldPath
,
'附件正在上传,请稍候。'
,
editSessionId
);
onValidationChange
?.(
fieldPath
,
'附件正在上传,请稍候。'
,
editSessionId
);
try
{
try
{
...
@@ -340,7 +352,7 @@ function ControlledFieldEditor({
...
@@ -340,7 +352,7 @@ function ControlledFieldEditor({
const
fileValue
=
uploaded
?.
file
||
uploaded
?.
data
||
uploaded
;
const
fileValue
=
uploaded
?.
file
||
uploaded
?.
data
||
uploaded
;
onChange
?.(
fieldDef
?.
multiple
?
[...
toArray
(
value
),
fileValue
]
:
fileValue
);
onChange
?.(
fieldDef
?.
multiple
?
[...
toArray
(
value
),
fileValue
]
:
fileValue
);
}
catch
(
uploadError
)
{
}
catch
(
uploadError
)
{
setError
(
uploadError
?.
message
||
'附件上传失败,请稍后重试。'
);
set
Request
Error
(
uploadError
?.
message
||
'附件上传失败,请稍后重试。'
);
}
finally
{
}
finally
{
setUploading
(
false
);
setUploading
(
false
);
onValidationChange
?.(
fieldPath
,
''
,
editSessionId
);
onValidationChange
?.(
fieldPath
,
''
,
editSessionId
);
...
@@ -560,6 +572,7 @@ export default function TailwindEditableField({
...
@@ -560,6 +572,7 @@ export default function TailwindEditableField({
const
[
editing
,
setEditing
]
=
useState
(
false
);
const
[
editing
,
setEditing
]
=
useState
(
false
);
const
[
draft
,
setDraft
]
=
useState
(
value
);
const
[
draft
,
setDraft
]
=
useState
(
value
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setRequestError
]
=
useFeedbackState
();
const
[
uploading
,
setUploading
]
=
useState
(
false
);
const
[
uploading
,
setUploading
]
=
useState
(
false
);
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -602,6 +615,7 @@ export default function TailwindEditableField({
...
@@ -602,6 +615,7 @@ export default function TailwindEditableField({
const
save
=
async
(
nextValue
=
draft
)
=>
{
const
save
=
async
(
nextValue
=
draft
)
=>
{
setError
(
''
);
setError
(
''
);
setRequestError
(
''
);
if
(
fieldType
===
'object'
&&
typeof
nextValue
===
'string'
&&
nextValue
.
trim
())
{
if
(
fieldType
===
'object'
&&
typeof
nextValue
===
'string'
&&
nextValue
.
trim
())
{
try
{
try
{
...
@@ -616,7 +630,7 @@ export default function TailwindEditableField({
...
@@ -616,7 +630,7 @@ export default function TailwindEditableField({
await
onSave
(
asSaveValue
(
nextValue
,
fieldDef
));
await
onSave
(
asSaveValue
(
nextValue
,
fieldDef
));
setEditing
(
false
);
setEditing
(
false
);
}
catch
(
saveError
)
{
}
catch
(
saveError
)
{
setError
(
saveError
?.
message
||
'保存失败,请稍后重试。'
);
set
Request
Error
(
saveError
?.
message
||
'保存失败,请稍后重试。'
);
}
}
};
};
...
@@ -629,6 +643,7 @@ export default function TailwindEditableField({
...
@@ -629,6 +643,7 @@ export default function TailwindEditableField({
const
cancel
=
()
=>
{
const
cancel
=
()
=>
{
setDraft
(
value
);
setDraft
(
value
);
setError
(
''
);
setError
(
''
);
setRequestError
(
''
);
setEditing
(
false
);
setEditing
(
false
);
};
};
...
@@ -640,6 +655,7 @@ export default function TailwindEditableField({
...
@@ -640,6 +655,7 @@ export default function TailwindEditableField({
setUploading
(
true
);
setUploading
(
true
);
setError
(
''
);
setError
(
''
);
setRequestError
(
''
);
try
{
try
{
const
uploaded
=
await
uploadFile
(
file
);
const
uploaded
=
await
uploadFile
(
file
);
...
@@ -649,7 +665,7 @@ export default function TailwindEditableField({
...
@@ -649,7 +665,7 @@ export default function TailwindEditableField({
await
onSave
(
nextValue
);
await
onSave
(
nextValue
);
setEditing
(
false
);
setEditing
(
false
);
}
catch
(
uploadError
)
{
}
catch
(
uploadError
)
{
setError
(
uploadError
?.
message
||
'附件上传失败,请稍后重试。'
);
set
Request
Error
(
uploadError
?.
message
||
'附件上传失败,请稍后重试。'
);
}
finally
{
}
finally
{
setUploading
(
false
);
setUploading
(
false
);
}
}
...
@@ -657,6 +673,7 @@ export default function TailwindEditableField({
...
@@ -657,6 +673,7 @@ export default function TailwindEditableField({
const
handleRemoveFile
=
async
(
indexToRemove
)
=>
{
const
handleRemoveFile
=
async
(
indexToRemove
)
=>
{
setError
(
''
);
setError
(
''
);
setRequestError
(
''
);
const
nextValue
=
fieldDef
?.
multiple
const
nextValue
=
fieldDef
?.
multiple
?
toArray
(
draft
).
filter
((
_
,
index
)
=>
index
!==
indexToRemove
)
?
toArray
(
draft
).
filter
((
_
,
index
)
=>
index
!==
indexToRemove
)
...
@@ -670,7 +687,7 @@ export default function TailwindEditableField({
...
@@ -670,7 +687,7 @@ export default function TailwindEditableField({
}
}
}
catch
(
saveError
)
{
}
catch
(
saveError
)
{
setDraft
(
draft
);
setDraft
(
draft
);
setError
(
saveError
?.
message
||
'删除附件失败,请稍后重试。'
);
set
Request
Error
(
saveError
?.
message
||
'删除附件失败,请稍后重试。'
);
}
}
};
};
...
...
src/pages/RecordDetailDrawer/hooks/useSubjectRecord.js
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
{
import
{
getSubjectView
,
getSubjectView
,
...
@@ -75,7 +76,7 @@ export function useSubjectRecord(
...
@@ -75,7 +76,7 @@ export function useSubjectRecord(
const [subject, setSubject] = useState(null);
const [subject, setSubject] = useState(null);
const [views, setViews] = useState([]);
const [views, setViews] = useState([]);
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = use
State('
');
const [error, setError] = use
FeedbackState('error
');
const requestIdRef = useRef(0);
const requestIdRef = useRef(0);
useEffect(() => {
useEffect(() => {
...
...
src/pages/RecordDetailDrawer/index.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
Close
,
Fullscreen
,
FullscreenExit
}
from
'@/components/AppIcons'
;
import
{
Close
,
Fullscreen
,
FullscreenExit
}
from
'@/components/AppIcons'
;
import
{
Alert
,
Button
,
Modal
,
Spinner
,
Tabs
,
Tooltip
}
from
'@heroui/react'
;
import
{
Alert
,
Button
,
Modal
,
Spinner
,
Tabs
,
Tooltip
}
from
'@heroui/react'
;
import
React
,
{
import
React
,
{
...
@@ -72,7 +74,7 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -72,7 +74,7 @@ const RecordDetailDrawer = (props, ref) => {
const
[
draftRecord
,
setDraftRecord
]
=
useState
(
null
);
const
[
draftRecord
,
setDraftRecord
]
=
useState
(
null
);
const
[
dirtyFields
,
setDirtyFields
]
=
useState
({});
const
[
dirtyFields
,
setDirtyFields
]
=
useState
({});
const
[
validationErrors
,
setValidationErrors
]
=
useState
({});
const
[
validationErrors
,
setValidationErrors
]
=
useState
({});
const
[
saveError
,
setSaveError
]
=
useState
(
'
'
);
const
[
,
setSaveError
]
=
useFeedbackState
(
'error
'
);
const
editSessionRef
=
useRef
(
0
);
const
editSessionRef
=
useRef
(
0
);
const
pendingLocalMaterialsRef
=
useRef
({});
const
pendingLocalMaterialsRef
=
useRef
({});
...
@@ -434,18 +436,17 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -434,18 +436,17 @@ const RecordDetailDrawer = (props, ref) => {
<
Spinner
size=
"sm"
/>
<
Spinner
size=
"sm"
/>
</
div
>
</
div
>
)
:
error
?
(
)
:
error
?
(
<
Alert
status=
"danger"
>
<
EmptyState
className=
"min-h-48"
size=
"md"
>
<
Alert
.
Content
><
Alert
.
Description
>
{
error
}
</
Alert
.
Description
></
Alert
.
Content
>
<
EmptyState
.
Header
>
</
Alert
>
<
EmptyState
.
Title
>
详情加载失败
</
EmptyState
.
Title
>
<
EmptyState
.
Description
>
详细错误见操作提示,请重新加载。
</
EmptyState
.
Description
>
</
EmptyState
.
Header
>
<
EmptyState
.
Footer
>
<
Button
variant=
"secondary"
onPress=
{
()
=>
setRefreshKey
((
key
)
=>
key
+
1
)
}
>
重新加载
</
Button
>
</
EmptyState
.
Footer
>
</
EmptyState
>
)
:
(
)
:
(
<
div
>
<
div
>
{
saveError
?
(
<
Alert
className=
"mb-4"
status=
"danger"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
saveError
}
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
)
:
null
}
{
isRecordLocked
?
(
{
isRecordLocked
?
(
<
Alert
status=
"warning"
>
<
Alert
status=
"warning"
>
<
Alert
.
Content
>
<
Alert
.
Content
>
...
...
src/pages/problem_rectification/ApplicationSwitcherDialog.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
xinyuanLogo
from
'@/assets/xy-logo-red.png'
;
import
xinyuanLogo
from
'@/assets/xy-logo-red.png'
;
import
{
import
{
APPLICATION_DISPLAY_NAME
,
APPLICATION_DISPLAY_NAME
,
...
@@ -5,12 +6,11 @@ import {
...
@@ -5,12 +6,11 @@ import {
}
from
'@/config/application'
;
}
from
'@/config/application'
;
import
{
getApplications
}
from
'@/services/DataService'
;
import
{
getApplications
}
from
'@/services/DataService'
;
import
{
EmptyStateSurface
}
from
'@/components/DataTable'
;
import
{
EmptyStateSurface
}
from
'@/components/DataTable'
;
import
{
ExclamationTriangleIcon
,
Search
as
SearchIcon
}
from
'@/components/AppIcons'
;
import
{
Search
as
SearchIcon
}
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
,
...
@@ -285,7 +285,7 @@ export default function ApplicationSwitcherDialog({
...
@@ -285,7 +285,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
;
...
@@ -319,6 +319,7 @@ export default function ApplicationSwitcherDialog({
...
@@ -319,6 +319,7 @@ export default function ApplicationSwitcherDialog({
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
open
)
{
if
(
!
open
)
{
setApplicationSearch
(
''
);
setApplicationSearch
(
''
);
setErrorMessage
(
''
);
return
;
return
;
}
}
...
@@ -398,13 +399,6 @@ export default function ApplicationSwitcherDialog({
...
@@ -398,13 +399,6 @@ export default function ApplicationSwitcherDialog({
</
Modal
.
Header
>
</
Modal
.
Header
>
<
Modal
.
Body
className=
"flex min-h-0 overflow-hidden"
style=
{
{
marginTop
:
'0.75rem'
}
}
>
<
Modal
.
Body
className=
"flex min-h-0 overflow-hidden"
style=
{
{
marginTop
:
'0.75rem'
}
}
>
<
ScrollShadow
className=
"h-full min-h-0 flex-1 overflow-y-auto pe-1 pb-1"
hideScrollBar=
{
false
}
offset=
{
12
}
size=
{
24
}
>
<
ScrollShadow
className=
"h-full min-h-0 flex-1 overflow-y-auto pe-1 pb-1"
hideScrollBar=
{
false
}
offset=
{
12
}
size=
{
24
}
>
{
status
===
'error'
&&
errorMessage
?
(
<
Alert
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/problem_rectification/archive/ArchiveSnapshotItems.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
Alert
,
Button
,
Link
,
Modal
,
Spinner
,
Surface
,
Tooltip
}
from
'@heroui/react'
;
import
{
Button
,
Link
,
Modal
,
Spinner
,
Surface
,
Tooltip
}
from
'@heroui/react'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
Close
,
Eye
,
Fullscreen
,
FullscreenExit
}
from
'@/components/AppIcons'
;
import
{
Close
,
Eye
,
Fullscreen
,
FullscreenExit
}
from
'@/components/AppIcons'
;
import
{
formatFieldValue
,
toArray
}
from
'@/utils/subjectView'
;
import
{
formatFieldValue
,
toArray
}
from
'@/utils/subjectView'
;
import
{
loadArchiveSnapshotItemSubject
}
from
'./archiveWorkbenchService'
;
import
{
loadArchiveSnapshotItemSubject
}
from
'./archiveWorkbenchService'
;
...
@@ -36,7 +38,7 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
...
@@ -36,7 +38,7 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
const
[
isFullscreen
,
setIsFullscreen
]
=
useState
(
false
);
const
[
isFullscreen
,
setIsFullscreen
]
=
useState
(
false
);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
use
State
(
'
'
);
const
[
error
,
setError
]
=
use
FeedbackState
(
'error
'
);
const
[
retryKey
,
setRetryKey
]
=
useState
(
0
);
const
[
retryKey
,
setRetryKey
]
=
useState
(
0
);
const
snapshot
=
useMemo
(()
=>
{
const
snapshot
=
useMemo
(()
=>
{
try
{
try
{
...
@@ -47,7 +49,15 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
...
@@ -47,7 +49,15 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
},
[
value
]);
},
[
value
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
isOpen
||
snapshot
.
error
||
snapshot
.
items
.
length
===
0
)
return
;
if
(
!
isOpen
)
{
setError
(
''
);
return
;
}
if
(
snapshot
.
error
)
{
setError
(
snapshot
.
error
);
return
;
}
if
(
snapshot
.
items
.
length
===
0
)
return
;
let
cancelled
=
false
;
let
cancelled
=
false
;
setLoading
(
true
);
setLoading
(
true
);
setError
(
''
);
setError
(
''
);
...
@@ -63,7 +73,7 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
...
@@ -63,7 +73,7 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
if
(
!
cancelled
)
setLoading
(
false
);
if
(
!
cancelled
)
setLoading
(
false
);
});
});
return
()
=>
{
cancelled
=
true
;
};
return
()
=>
{
cancelled
=
true
;
};
},
[
fieldName
,
isOpen
,
retryKey
,
snapshot
]);
},
[
fieldName
,
isOpen
,
retryKey
,
snapshot
,
setError
]);
if
(
!
snapshot
.
error
&&
snapshot
.
items
.
length
===
0
)
{
if
(
!
snapshot
.
error
&&
snapshot
.
items
.
length
===
0
)
{
return
<
span
className=
"text-sm text-muted"
>
暂无
{
configuration
.
label
}
</
span
>;
return
<
span
className=
"text-sm text-muted"
>
暂无
{
configuration
.
label
}
</
span
>;
...
@@ -119,16 +129,19 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
...
@@ -119,16 +129,19 @@ export default function ArchiveSnapshotItems({ fieldName, value, recordTitle })
</
Modal
.
Header
>
</
Modal
.
Header
>
<
Modal
.
Body
>
<
Modal
.
Body
>
{
snapshot
.
error
||
error
?
(
{
snapshot
.
error
||
error
?
(
<
div
className=
"space-y-3"
>
<
EmptyState
className=
"min-h-32"
size=
"md"
>
<
Alert
status=
"danger"
>
<
EmptyState
.
Header
>
<
Alert
.
Content
><
Alert
.
Description
>
{
snapshot
.
error
||
error
}
</
Alert
.
Description
></
Alert
.
Content
>
<
EmptyState
.
Title
>
{
snapshot
.
error
?
'归档快照数据异常'
:
'归档明细字段加载失败'
}
</
EmptyState
.
Title
>
</
Alert
>
<
EmptyState
.
Description
>
详细错误见操作提示。
</
EmptyState
.
Description
>
</
EmptyState
.
Header
>
{
!
snapshot
.
error
?
(
{
!
snapshot
.
error
?
(
<
EmptyState
.
Footer
>
<
Button
size=
"sm"
variant=
"secondary"
onPress=
{
()
=>
setRetryKey
((
current
)
=>
current
+
1
)
}
>
<
Button
size=
"sm"
variant=
"secondary"
onPress=
{
()
=>
setRetryKey
((
current
)
=>
current
+
1
)
}
>
重试
重试
</
Button
>
</
Button
>
</
EmptyState
.
Footer
>
)
:
null
}
)
:
null
}
</
div
>
</
EmptyState
>
)
:
loading
||
!
subject
?
(
)
:
loading
||
!
subject
?
(
<
div
className=
"flex min-h-32 items-center justify-center"
role=
"status"
aria
-
label=
"正在加载归档明细字段"
>
<
div
className=
"flex min-h-32 items-center justify-center"
role=
"status"
aria
-
label=
"正在加载归档明细字段"
>
<
Spinner
size=
"sm"
/>
<
Spinner
size=
"sm"
/>
...
...
src/pages/problem_rectification/archive/index.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
TableFieldValue
from
'@/components/TableFieldValue'
;
import
TableFieldValue
from
'@/components/TableFieldValue'
;
import
{
getTableFieldDisplayConfig
}
from
'@/utils/tableFieldDisplay'
;
import
{
getTableFieldDisplayConfig
}
from
'@/utils/tableFieldDisplay'
;
...
@@ -21,9 +22,10 @@ export default function ProblemRectificationArchivePage() {
...
@@ -21,9 +22,10 @@ export default function ProblemRectificationArchivePage() {
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
views
,
setViews
]
=
useState
([]);
const
[
views
,
setViews
]
=
useState
([]);
const
[
tableDisplayConfig
,
setTableDisplayConfig
]
=
useState
({});
const
[
tableDisplayConfig
,
setTableDisplayConfig
]
=
useState
({});
const
[
tableDisplayConfigError
,
setTableDisplayConfigError
]
=
useState
(
'
'
);
const
[
,
setTableDisplayConfigError
]
=
useFeedbackState
(
'warning
'
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
filters
,
setFilters
]
=
useState
({
const
[
filters
,
setFilters
]
=
useState
({
keyword
:
''
,
keyword
:
''
,
});
});
...
@@ -34,6 +36,7 @@ export default function ProblemRectificationArchivePage() {
...
@@ -34,6 +36,7 @@ export default function ProblemRectificationArchivePage() {
const
reloadList
=
useCallback
(
async
()
=>
{
const
reloadList
=
useCallback
(
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
setError
(
''
);
try
{
try
{
...
@@ -44,6 +47,7 @@ export default function ProblemRectificationArchivePage() {
...
@@ -44,6 +47,7 @@ export default function ProblemRectificationArchivePage() {
setTableDisplayConfig
(
resources
.
displayConfig
||
{});
setTableDisplayConfig
(
resources
.
displayConfig
||
{});
setTableDisplayConfigError
(
resources
.
displayConfigError
||
''
);
setTableDisplayConfigError
(
resources
.
displayConfigError
||
''
);
}
catch
(
requestError
)
{
}
catch
(
requestError
)
{
setLoadFailed
(
true
);
setRecords
([]);
setRecords
([]);
setSubject
(
null
);
setSubject
(
null
);
setViews
([]);
setViews
([]);
...
@@ -193,8 +197,7 @@ export default function ProblemRectificationArchivePage() {
...
@@ -193,8 +197,7 @@ export default function ProblemRectificationArchivePage() {
}
}
}
}
states=
{
{
states=
{
{
loading
,
loading
,
error
,
loadFailed
,
warning
:
tableDisplayConfigError
,
loadingTitle
:
'正在加载归档批次'
,
loadingTitle
:
'正在加载归档批次'
,
loadingDescription
:
'正在读取归档对象、视图配置和批次记录,请稍候。'
,
loadingDescription
:
'正在读取归档对象、视图配置和批次记录,请稍候。'
,
emptyTitle
:
'暂无归档批次'
,
emptyTitle
:
'暂无归档批次'
,
...
...
src/pages/problem_rectification/components/ProblemRectificationMaterialField.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
import
{
ArrowPathIcon
,
ArrowPathIcon
,
Delete
,
Delete
,
...
@@ -9,7 +10,6 @@ import {
...
@@ -9,7 +10,6 @@ import {
uploadProblemRectificationFile
,
uploadProblemRectificationFile
,
}
from
'../problemRectificationMaterialService'
;
}
from
'../problemRectificationMaterialService'
;
import
{
import
{
Alert
,
Button
,
Button
,
Label
,
Label
,
Tabs
,
Tabs
,
...
@@ -60,10 +60,10 @@ export default function ProblemRectificationMaterialField({
...
@@ -60,10 +60,10 @@ export default function ProblemRectificationMaterialField({
const
[
debouncedKeyword
,
setDebouncedKeyword
]
=
useState
(
''
);
const
[
debouncedKeyword
,
setDebouncedKeyword
]
=
useState
(
''
);
const
[
materials
,
setMaterials
]
=
useState
([]);
const
[
materials
,
setMaterials
]
=
useState
([]);
const
[
materialsLoading
,
setMaterialsLoading
]
=
useState
(
false
);
const
[
materialsLoading
,
setMaterialsLoading
]
=
useState
(
false
);
const
[
materialsError
,
setMaterialsError
]
=
use
State
(
'
'
);
const
[
materialsError
,
setMaterialsError
]
=
use
FeedbackState
(
'error
'
);
const
[
retryKey
,
setRetryKey
]
=
useState
(
0
);
const
[
retryKey
,
setRetryKey
]
=
useState
(
0
);
const
[
uploading
,
setUploading
]
=
useState
(
false
);
const
[
uploading
,
setUploading
]
=
useState
(
false
);
const
[
uploadError
,
setUploadError
]
=
useState
(
'
'
);
const
[
,
setUploadError
]
=
useFeedbackState
(
'error
'
);
const
[
localFileKeys
,
setLocalFileKeys
]
=
useState
(()
=>
new
Set
());
const
[
localFileKeys
,
setLocalFileKeys
]
=
useState
(()
=>
new
Set
());
const
fileValues
=
useMemo
(
const
fileValues
=
useMemo
(
()
=>
(
multiple
?
toFileArray
(
value
)
:
toFileArray
(
value
).
slice
(
0
,
1
)),
()
=>
(
multiple
?
toFileArray
(
value
)
:
toFileArray
(
value
).
slice
(
0
,
1
)),
...
@@ -322,15 +322,10 @@ export default function ProblemRectificationMaterialField({
...
@@ -322,15 +322,10 @@ export default function ProblemRectificationMaterialField({
/>
/>
{materialsError ? (
{materialsError ? (
<Alert status="danger">
<Button size="sm" variant="secondary" onPress={() => setRetryKey((key) => key + 1)}>
<Alert.Content>
<Alert.Description>{materialsError}</Alert.Description>
</Alert.Content>
<Button size="sm" variant="danger-soft" onPress={() => setRetryKey((key) => key + 1)}>
<ArrowPathIcon size={14} />
<ArrowPathIcon size={14} />
重试
重新加载材料
</Button>
</Button>
</Alert>
) : null}
) : null}
</div>
</div>
</Tabs.Panel>
</Tabs.Panel>
...
@@ -346,11 +341,6 @@ export default function ProblemRectificationMaterialField({
...
@@ -346,11 +341,6 @@ export default function ProblemRectificationMaterialField({
triggerLabel={uploading ? '上传中...' : '选择本地文件'}
triggerLabel={uploading ? '上传中...' : '选择本地文件'}
onFilesChange={handleLocalUpload}
onFilesChange={handleLocalUpload}
/>
/>
{uploadError ? (
<Alert status="danger">
<Alert.Content><Alert.Description>{uploadError}</Alert.Description></Alert.Content>
</Alert>
) : null}
</div>
</div>
</Tabs.Panel>
</Tabs.Panel>
</Tabs>
</Tabs>
...
...
src/pages/problem_rectification/dashboard/index.jsx
View file @
513706c5
import
{
DataTable
}
from
'@/components/DataTable'
;
import
{
DataTable
}
from
'@/components/DataTable'
;
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
import
{
createResolvedXinyuanPieChartColorMap
,
createResolvedXinyuanPieChartColorMap
,
getXinyuanPieChartColor
,
getXinyuanPieChartColor
,
...
@@ -17,7 +18,6 @@ import {
...
@@ -17,7 +18,6 @@ import {
ExclamationTriangleIcon
,
ExclamationTriangleIcon
,
}
from
'@/components/AppIcons'
;
}
from
'@/components/AppIcons'
;
import
{
import
{
Alert
,
Button
,
Button
,
Card
,
Card
,
Chip
,
Chip
,
...
@@ -456,24 +456,28 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
...
@@ -456,24 +456,28 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
isEmpty
:
false
,
isEmpty
:
false
,
});
});
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
reloadKey
,
setReloadKey
]
=
useState
(
0
);
const
[,
setError
]
=
useFeedbackState
();
useEffect
(()
=>
{
useEffect
(()
=>
{
let
cancelled
=
false
;
let
cancelled
=
false
;
(
async
()
=>
{
(
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
setError
(
''
);
const
result
=
await
loadProblemRectificationDashboardData
();
const
result
=
await
loadProblemRectificationDashboardData
();
if
(
cancelled
)
{
if
(
cancelled
)
{
return
;
return
;
}
}
setDashboardData
(
result
);
setDashboardData
(
result
);
setError
(
result
.
error
||
''
);
setLoading
(
false
);
setLoading
(
false
);
})();
})();
return
()
=>
{
return
()
=>
{
cancelled
=
true
;
cancelled
=
true
;
};
};
},
[]);
},
[
reloadKey
,
setError
]);
const
groups
=
dashboardData
.
groups
||
[];
const
groups
=
dashboardData
.
groups
||
[];
const
yearOptions
=
dashboardData
.
yearOptions
||
[];
const
yearOptions
=
dashboardData
.
yearOptions
||
[];
...
@@ -585,14 +589,6 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
...
@@ -585,14 +589,6 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
</
div
>
</
div
>
</
div
>
</
div
>
{
dashboardData
.
error
?
(
<
Alert
status=
"warning"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
dashboardData
.
error
}
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
)
:
null
}
{
loading
?
(
{
loading
?
(
<
div
className=
"flex min-h-48 items-center justify-center gap-2"
role=
"status"
>
<
div
className=
"flex min-h-48 items-center justify-center gap-2"
role=
"status"
>
<
Spinner
size=
"sm"
/>
<
Spinner
size=
"sm"
/>
...
@@ -600,7 +596,19 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
...
@@ -600,7 +596,19 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
</
div
>
</
div
>
)
:
null
}
)
:
null
}
{
!
loading
&&
dashboardData
.
isEmpty
?
(
{
!
loading
&&
dashboardData
.
error
?
(
<
EmptyState
className=
"min-h-48"
size=
"md"
>
<
EmptyState
.
Header
>
<
EmptyState
.
Title
>
{
dashboardTitle
}
加载失败
</
EmptyState
.
Title
>
<
EmptyState
.
Description
>
详细错误见操作提示,请重新加载。
</
EmptyState
.
Description
>
</
EmptyState
.
Header
>
<
EmptyState
.
Footer
>
<
Button
variant=
"secondary"
onPress=
{
()
=>
setReloadKey
((
key
)
=>
key
+
1
)
}
>
重新加载
</
Button
>
</
EmptyState
.
Footer
>
</
EmptyState
>
)
:
null
}
{
!
loading
&&
!
dashboardData
.
error
&&
dashboardData
.
isEmpty
?
(
<
EmptyBlock
title=
"暂无问题整改数据"
/>
<
EmptyBlock
title=
"暂无问题整改数据"
/>
)
:
null
}
)
:
null
}
...
...
src/pages/problem_rectification/flow-table/index.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
import
{
ArrowPathIcon
,
ArrowPathIcon
,
CalendarDaysIcon
,
CalendarDaysIcon
,
...
@@ -21,7 +22,7 @@ import {
...
@@ -21,7 +22,7 @@ import {
import
{
normalizeText
,
toArray
}
from
'@/utils/subjectView'
;
import
{
normalizeText
,
toArray
}
from
'@/utils/subjectView'
;
import
clsx
from
'clsx'
;
import
clsx
from
'clsx'
;
import
dayjs
from
'dayjs'
;
import
dayjs
from
'dayjs'
;
import
{
Alert
,
Button
,
Card
,
Chip
,
Spinner
,
TextArea
,
Toast
,
Tooltip
,
toast
}
from
'@heroui/react'
;
import
{
Button
,
Card
,
Chip
,
Spinner
,
TextArea
,
Tooltip
}
from
'@heroui/react'
;
import
{
useContext
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useContext
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useSearchParams
}
from
'umi'
;
import
{
useSearchParams
}
from
'umi'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
...
@@ -416,8 +417,9 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -416,8 +417,9 @@ export default function ProblemRectificationFlowTablePage() {
const
scopedRecordId
=
searchParams
.
get
(
'record'
)
||
''
;
const
scopedRecordId
=
searchParams
.
get
(
'record'
)
||
''
;
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
submittingKey
,
setSubmittingKey
]
=
useState
(
''
);
const
[
submittingKey
,
setSubmittingKey
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
success
,
setSuccess
]
=
useState
(
''
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[,
setSuccess
]
=
useFeedbackState
(
'success'
);
const
[
messageRecord
,
setMessageRecord
]
=
useState
(
null
);
const
[
messageRecord
,
setMessageRecord
]
=
useState
(
null
);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
userLabels
,
setUserLabels
]
=
useState
({});
const
[
userLabels
,
setUserLabels
]
=
useState
({});
...
@@ -463,12 +465,14 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -463,12 +465,14 @@ export default function ProblemRectificationFlowTablePage() {
const
loadFlow
=
async
()
=>
{
const
loadFlow
=
async
()
=>
{
if
(
!
messageId
)
{
if
(
!
messageId
)
{
setLoadFailed
(
true
);
setError
(
'缺少流程 ID。'
);
setError
(
'缺少流程 ID。'
);
setLoading
(
false
);
setLoading
(
false
);
return
;
return
;
}
}
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
setError
(
''
);
try
{
try
{
...
@@ -481,6 +485,7 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -481,6 +485,7 @@ export default function ProblemRectificationFlowTablePage() {
setRecords
(
relatedRecords
.
filter
(
Boolean
));
setRecords
(
relatedRecords
.
filter
(
Boolean
));
setDrafts
({});
setDrafts
({});
}
catch
(
requestError
)
{
}
catch
(
requestError
)
{
setLoadFailed
(
true
);
setError
(
requestError
?.
message
||
'流程表格加载失败,请稍后重试。'
);
setError
(
requestError
?.
message
||
'流程表格加载失败,请稍后重试。'
);
setMessageRecord
(
null
);
setMessageRecord
(
null
);
setRecords
([]);
setRecords
([]);
...
@@ -1778,12 +1783,6 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -1778,12 +1783,6 @@ export default function ProblemRectificationFlowTablePage() {
},
},
];
];
useEffect
(()
=>
{
if
(
!
success
)
return
;
toast
.
success
(
success
,
{
timeout
:
4000
});
setSuccess
(
''
);
},
[
success
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
document
.
title
=
'整改流程 - 问题整改'
;
document
.
title
=
'整改流程 - 问题整改'
;
},
[]);
},
[]);
...
@@ -1835,13 +1834,6 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -1835,13 +1834,6 @@ export default function ProblemRectificationFlowTablePage() {
</
Card
.
Content
>
</
Card
.
Content
>
</
Card
>
</
Card
>
{
error
?
(
<
Alert
status=
"danger"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
)
:
null
}
<
section
className=
"rc-list-stack"
>
<
section
className=
"rc-list-stack"
>
<
div
className=
"rc-page-toolbar"
>
<
div
className=
"rc-page-toolbar"
>
<
div
className=
"flex min-w-0 items-center gap-3"
>
<
div
className=
"flex min-w-0 items-center gap-3"
>
...
@@ -1861,6 +1853,11 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -1861,6 +1853,11 @@ export default function ProblemRectificationFlowTablePage() {
<
div
className=
"flex min-h-56 items-center justify-center"
role=
"status"
aria
-
label=
"正在加载流程表格"
>
<
div
className=
"flex min-h-56 items-center justify-center"
role=
"status"
aria
-
label=
"正在加载流程表格"
>
<
Spinner
size=
"md"
/>
<
Spinner
size=
"md"
/>
</
div
>
</
div
>
)
:
loadFailed
?
(
<
div
className=
"space-y-3"
>
<
DataTableEmptyState
title=
"流程表格加载失败,详细错误见操作提示"
/>
<
Button
variant=
"secondary"
onPress=
{
loadFlow
}
>
重新加载
</
Button
>
</
div
>
)
:
rows
.
length
===
0
?
(
)
:
rows
.
length
===
0
?
(
<
DataTableEmptyState
title=
{
isDelayFlow
?
'当前流程没有可处理的延期申请'
:
'当前流程没有可处理的整改明细'
}
/>
<
DataTableEmptyState
title=
{
isDelayFlow
?
'当前流程没有可处理的延期申请'
:
'当前流程没有可处理的整改明细'
}
/>
)
:
(
)
:
(
...
@@ -1877,7 +1874,6 @@ export default function ProblemRectificationFlowTablePage() {
...
@@ -1877,7 +1874,6 @@ export default function ProblemRectificationFlowTablePage() {
)
}
)
}
</
section
>
</
section
>
</
div
>
</
div
>
<
Toast
.
Provider
maxVisibleToasts=
{
3
}
placement=
"top"
/>
</
main
>
</
main
>
);
);
}
}
src/pages/problem_rectification/form/index.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
import
{
CheckCircleIcon
,
CheckCircleIcon
,
ClipboardDocumentListIcon
,
ClipboardDocumentListIcon
,
...
@@ -130,13 +131,14 @@ function StatusTag({ value }) {
...
@@ -130,13 +131,14 @@ function StatusTag({ value }) {
);
);
}
}
function
EmptyState
({
title
,
description
})
{
function
EmptyState
({
title
,
description
,
action
})
{
return
(
return
(
<
ProEmptyState
className=
"min-h-64"
size=
"md"
>
<
ProEmptyState
className=
"min-h-64"
size=
"md"
>
<
ProEmptyState
.
Header
>
<
ProEmptyState
.
Header
>
<
ProEmptyState
.
Title
>
{
title
}
</
ProEmptyState
.
Title
>
<
ProEmptyState
.
Title
>
{
title
}
</
ProEmptyState
.
Title
>
{
description
?
<
ProEmptyState
.
Description
>
{
description
}
</
ProEmptyState
.
Description
>
:
null
}
{
description
?
<
ProEmptyState
.
Description
>
{
description
}
</
ProEmptyState
.
Description
>
:
null
}
</
ProEmptyState
.
Header
>
</
ProEmptyState
.
Header
>
{
action
?
<
ProEmptyState
.
Footer
>
{
action
}
</
ProEmptyState
.
Footer
>
:
null
}
</
ProEmptyState
>
</
ProEmptyState
>
);
);
}
}
...
@@ -163,12 +165,14 @@ export default function ProblemRectificationFormPage() {
...
@@ -163,12 +165,14 @@ export default function ProblemRectificationFormPage() {
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
submitting
,
setSubmitting
]
=
useState
(
false
);
const
[
submitting
,
setSubmitting
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[,
setError
]
=
useFeedbackState
(
'error'
);
const
[
loadFailed
,
setLoadFailed
]
=
useState
(
false
);
const
[
flowData
,
setFlowData
]
=
useState
(
null
);
const
[
flowData
,
setFlowData
]
=
useState
(
null
);
const
[
selectedRecord
,
setSelectedRecord
]
=
useState
(
null
);
const
[
selectedRecord
,
setSelectedRecord
]
=
useState
(
null
);
const
reload
=
async
()
=>
{
const
reload
=
async
()
=>
{
if
(
!
messageId
)
{
if
(
!
messageId
)
{
setLoadFailed
(
true
);
setFlowData
(
null
);
setFlowData
(
null
);
setLoading
(
false
);
setLoading
(
false
);
setError
(
'缺少流程 ID,无法加载整改流程。'
);
setError
(
'缺少流程 ID,无法加载整改流程。'
);
...
@@ -176,12 +180,14 @@ export default function ProblemRectificationFormPage() {
...
@@ -176,12 +180,14 @@ export default function ProblemRectificationFormPage() {
}
}
setLoading
(
true
);
setLoading
(
true
);
setLoadFailed
(
false
);
setError
(
''
);
setError
(
''
);
try
{
try
{
const
result
=
await
loadProblemRectificationFlow
(
messageId
,
userInfo
);
const
result
=
await
loadProblemRectificationFlow
(
messageId
,
userInfo
);
setFlowData
(
result
);
setFlowData
(
result
);
}
catch
(
requestError
)
{
}
catch
(
requestError
)
{
setLoadFailed
(
true
);
setFlowData
(
null
);
setFlowData
(
null
);
setError
(
requestError
?.
message
||
'整改流程加载失败,请稍后重试。'
);
setError
(
requestError
?.
message
||
'整改流程加载失败,请稍后重试。'
);
}
finally
{
}
finally
{
...
@@ -380,12 +386,8 @@ export default function ProblemRectificationFormPage() {
...
@@ -380,12 +386,8 @@ export default function ProblemRectificationFormPage() {
<
div
className=
"grid min-h-64 place-items-center"
role=
"status"
aria
-
label=
"正在加载整改流程"
>
<
div
className=
"grid min-h-64 place-items-center"
role=
"status"
aria
-
label=
"正在加载整改流程"
>
<
Spinner
/>
<
Spinner
/>
</
div
>
</
div
>
)
:
error
?
(
)
:
loadFailed
?
(
<
Alert
status=
"danger"
>
<
EmptyState
title=
"整改流程加载失败"
description=
"详细错误见操作提示,请重新加载。"
action=
{
<
Button
variant=
"secondary"
onPress=
{
reload
}
>
重新加载
</
Button
>
}
/>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
)
:
!
messageRecord
?
(
)
:
!
messageRecord
?
(
<
EmptyState
title=
"未找到流程数据"
description=
"请确认消息链接中的 id 是否正确,或检查流程主单是否已存在。"
/>
<
EmptyState
title=
"未找到流程数据"
description=
"请确认消息链接中的 id 是否正确,或检查流程主单是否已存在。"
/>
)
:
(
)
:
(
...
...
src/pages/problem_rectification/list/index.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
FormDatePicker
from
'@/components/FormDatePicker'
;
import
FormDatePicker
from
'@/components/FormDatePicker'
;
import
ResourceFilterMultiSelect
from
'@/components/ResourceFilterMultiSelect'
;
import
ResourceFilterMultiSelect
from
'@/components/ResourceFilterMultiSelect'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
...
@@ -49,7 +50,6 @@ import {
...
@@ -49,7 +50,6 @@ import {
import
clsx
from
'clsx'
;
import
clsx
from
'clsx'
;
import
dayjs
from
'dayjs'
;
import
dayjs
from
'dayjs'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
'react'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
'react'
;
import
{
toast
}
from
'@heroui/react'
;
import
{
import
{
formatFieldValue
,
formatFieldValue
,
getFieldNameFromPath
,
getFieldNameFromPath
,
...
@@ -795,10 +795,11 @@ export default function ProblemRectificationListPage() {
...
@@ -795,10 +795,11 @@ export default function ProblemRectificationListPage() {
const [subject, setSubject] = useState(null);
const [subject, setSubject] = useState(null);
const [views, setViews] = useState([]);
const [views, setViews] = useState([]);
const [tableDisplayConfig, setTableDisplayConfig] = useState({});
const [tableDisplayConfig, setTableDisplayConfig] = useState({});
const [
tableDisplayConfigError, setTableDisplayConfigError] = useState('
');
const [
, setTableDisplayConfigError] = useFeedbackState('warning
');
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(true);
const [recordsLoading, setRecordsLoading] = useState(false);
const [recordsLoading, setRecordsLoading] = useState(false);
const [error, setError] = useState('');
const [, setError] = useFeedbackState('error');
const [loadFailed, setLoadFailed] = useState(false);
const [actionLoading, setActionLoading] = useState(false);
const [actionLoading, setActionLoading] = useState(false);
const [filters, setFilters] = useState({});
const [filters, setFilters] = useState({});
const [pendingFilters, setPendingFilters] = useState({});
const [pendingFilters, setPendingFilters] = useState({});
...
@@ -812,13 +813,13 @@ export default function ProblemRectificationListPage() {
...
@@ -812,13 +813,13 @@ export default function ProblemRectificationListPage() {
const [cloneTargetRecord, setCloneTargetRecord] = useState(null);
const [cloneTargetRecord, setCloneTargetRecord] = useState(null);
const [deleteTargetRecords, setDeleteTargetRecords] = useState([]);
const [deleteTargetRecords, setDeleteTargetRecords] = useState([]);
const [archiveTargetRecords, setArchiveTargetRecords] = useState([]);
const [archiveTargetRecords, setArchiveTargetRecords] = useState([]);
const [
archiveError, setArchiveError] = useState('
');
const [
, setArchiveError] = useFeedbackState('error
');
const [formValues, setFormValues] = useState({});
const [formValues, setFormValues] = useState({});
const [fillerUsers, setFillerUsers] = useState([]);
const [fillerUsers, setFillerUsers] = useState([]);
const [auditorUsers, setAuditorUsers] = useState([]);
const [auditorUsers, setAuditorUsers] = useState([]);
const [departmentAssignments, setDepartmentAssignments] = useState([]);
const [departmentAssignments, setDepartmentAssignments] = useState([]);
const [delayDepartmentAssignments, setDelayDepartmentAssignments] = useState([]);
const [delayDepartmentAssignments, setDelayDepartmentAssignments] = useState([]);
const [
successMessage, setSuccessMessage] = useState('
');
const [
, setSuccessMessage] = useFeedbackState('success
');
const [page, setPage] = useState(0);
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE);
const [pageSize, setPageSize] = useState(DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE);
const [pagination, setPagination] = useState({
const [pagination, setPagination] = useState({
...
@@ -844,6 +845,7 @@ export default function ProblemRectificationListPage() {
...
@@ -844,6 +845,7 @@ export default function ProblemRectificationListPage() {
const reloadResources = useCallback(async () => {
const reloadResources = useCallback(async () => {
setLoading(true);
setLoading(true);
setLoadFailed(false);
setError('');
setError('');
setSuccessMessage('');
setSuccessMessage('');
...
@@ -854,6 +856,7 @@ export default function ProblemRectificationListPage() {
...
@@ -854,6 +856,7 @@ export default function ProblemRectificationListPage() {
setTableDisplayConfig(resources.displayConfig || {});
setTableDisplayConfig(resources.displayConfig || {});
setTableDisplayConfigError(resources.displayConfigError || '');
setTableDisplayConfigError(resources.displayConfigError || '');
} catch (requestError) {
} catch (requestError) {
setLoadFailed(true);
setRecords([]);
setRecords([]);
setSubject(null);
setSubject(null);
setViews([]);
setViews([]);
...
@@ -869,12 +872,6 @@ export default function ProblemRectificationListPage() {
...
@@ -869,12 +872,6 @@ export default function ProblemRectificationListPage() {
reloadResources();
reloadResources();
}, [reloadResources]);
}, [reloadResources]);
useEffect(() => {
if (!successMessage) return;
toast.success(successMessage, { timeout: 4000 });
setSuccessMessage('');
}, [successMessage]);
useEffect(() => {
useEffect(() => {
const userIds = Array.from(
const userIds = Array.from(
new Set(
new Set(
...
@@ -1022,6 +1019,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1022,6 +1019,7 @@ export default function ProblemRectificationListPage() {
const countCache = viewCountCacheRef.current;
const countCache = viewCountCacheRef.current;
setRecordsLoading(true);
setRecordsLoading(true);
setLoadFailed(false);
setError('');
setError('');
if (!overrides.keepSuccessMessage) {
if (!overrides.keepSuccessMessage) {
setSuccessMessage('');
setSuccessMessage('');
...
@@ -1074,6 +1072,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1074,6 +1072,7 @@ export default function ProblemRectificationListPage() {
return;
return;
}
}
setRecords([]);
setRecords([]);
setLoadFailed(true);
setGridViewCounts({});
setGridViewCounts({});
setPagination({
setPagination({
page: nextPage,
page: nextPage,
...
@@ -2081,9 +2080,8 @@ export default function ProblemRectificationListPage() {
...
@@ -2081,9 +2080,8 @@ export default function ProblemRectificationListPage() {
} : null}
} : null}
states={{
states={{
loading: loading || recordsLoading,
loading: loading || recordsLoading,
error,
loadFailed,
warning: tableDisplayConfigError,
onRetry: reloadResources,
success: '',
loadingTitle: '正在加载问题整改列表',
loadingTitle: '正在加载问题整改列表',
loadingDescription: '正在读取对象结构、视图配置和记录数据,请稍候。',
loadingDescription: '正在读取对象结构、视图配置和记录数据,请稍候。',
emptyTitle: activeGridView ? '当前视图暂无数据' : '暂无可访问的视图',
emptyTitle: activeGridView ? '当前视图暂无数据' : '暂无可访问的视图',
...
@@ -2686,13 +2684,6 @@ export default function ProblemRectificationListPage() {
...
@@ -2686,13 +2684,6 @@ export default function ProblemRectificationListPage() {
</>
</>
}
}
>
>
{archiveError ? (
<Alert status="danger">
<Alert.Content>
<Alert.Description>{archiveError}</Alert.Description>
</Alert.Content>
</Alert>
) : null}
<Alert status="warning">
<Alert status="warning">
<Alert.Content>
<Alert.Content>
<Alert.Description>
<Alert.Description>
...
...
src/pages/problem_rectification/notifications/NotificationListPage.jsx
View file @
513706c5
import
useFeedbackState
from
'@/hooks/useFeedbackState'
;
import
{
Bell
,
Close
,
MagnifyingGlassIcon
,
OpenExternal
}
from
'@/components/AppIcons'
;
import
{
Bell
,
Close
,
MagnifyingGlassIcon
,
OpenExternal
}
from
'@/components/AppIcons'
;
import
{
DataTablePagination
}
from
'@/components/DataTable'
;
import
{
DataTablePagination
}
from
'@/components/DataTable'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
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
,
...
@@ -150,7 +150,7 @@ export default function NotificationListPage() {
...
@@ -150,7 +150,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
);
...
@@ -214,12 +214,12 @@ export default function NotificationListPage() {
...
@@ -214,12 +214,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
{
...
@@ -272,7 +272,7 @@ export default function NotificationListPage() {
...
@@ -272,7 +272,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
(
''
);
...
@@ -356,14 +356,15 @@ export default function NotificationListPage() {
...
@@ -356,14 +356,15 @@ export default function NotificationListPage() {
</
div
>
</
div
>
{
error
?
(
{
error
?
(
<
Alert
status=
"danger"
>
<
EmptyState
className=
"min-h-48"
size=
"md"
>
<
Alert
.
Content
>
<
EmptyState
.
Header
>
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
<
EmptyState
.
Title
>
通知加载失败
</
EmptyState
.
Title
>
</
Alert
.
Content
>
<
EmptyState
.
Description
>
详细错误见操作提示,请重新加载。
</
EmptyState
.
Description
>
<
Button
size=
"sm"
variant=
"ghost"
onPress=
{
refresh
}
>
</
EmptyState
.
Header
>
重试
<
EmptyState
.
Footer
>
</
Button
>
<
Button
variant=
"secondary"
onPress=
{
refresh
}
>
重试
</
Button
>
</
Alert
>
</
EmptyState
.
Footer
>
</
EmptyState
>
)
:
null
}
)
:
null
}
{
loading
&&
notifications
.
length
===
0
?
(
{
loading
&&
notifications
.
length
===
0
?
(
...
...
src/pages/problem_rectification/problem-rectification.business-flow.md
View file @
513706c5
...
@@ -151,6 +151,16 @@
...
@@ -151,6 +151,16 @@
---
---
### 操作提示与失败重试
-
功能入口:登录、站点配置、应用切换、问题清单及归档批次、驾驶舱/整改分析、详情编辑、证明材料上传与搜索、整改/延期流程表格、旧外链流程页和通知中心。
-
查询、保存、复制、删除、锁定/解锁、推送、归档、上传及审批的接口成功/失败反馈统一显示 HeroUI Toast,应用配置/应用列表降级使用 warning。根部统一挂载,Portal 在视口顶部呈现,层级高于业务 Modal、确认框和移动侧栏;弹窗打开或页面滚动时也可看到并关闭,不抢走表单焦点。
-
成功提示默认 4 秒、错误和警告默认 8 秒后消失,悬停或键盘聚焦暂停计时;关闭按钮始终可见。相同错误再次发生时重新弹出,同一来源的新提示替换旧提示;清空反馈或离开对应页面时清理该来源提示。
-
提示关闭仅影响展示,不把失败改为成功,不清空草稿、选中记录或附件,不发起保存、推送、审批或归档。表单必填/JSON 校验、锁定说明和删除/归档确认说明继续保留在对应控件或确认框中。
-
问题清单、归档批次、驾驶舱/整改分析、详情、通知和流程页加载失败时保留失败空状态及“重新加载/重试”,详细原因由浮层展示,避免误显示“暂无数据”。重试只读取当前页面资源,不重新执行写入操作;通知继续按当前分类、已读状态、关键词和页码重试。
-
人员、关联记录和材料查询失败时弹出提示,选择器保留字段级加载错误;关联明细和材料保留读取重试入口。详情保存或附件上传失败时保留当前编辑内容,可修正后再次提交;批量附件部分失败保留已成功的附件并提示失败项。
-
归档快照字段读取失败时保留明细弹窗和重试入口,历史 JSON 损坏时保留“查看异常数据”及异常空状态,详细原因由操作提示展示。关闭提示不回写
`metadata`
、不更改
`is_locked`
、审批状态、分页筛选或归档快照。
## 4. 核心业务对象
## 4. 核心业务对象
### 4.1 主对象
### 4.1 主对象
...
...
src/tailwind.css
View file @
513706c5
...
@@ -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 @
513706c5
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'
;
...
@@ -20,7 +21,7 @@ export default function SiteWrapper() {
...
@@ -20,7 +21,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
(
'error
'
);
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