Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
project_manager_xy
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
project_manager_xy
Commits
c1615014
Commit
c1615014
authored
Jul 14, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 部分字段改成选项,优化 ui
parent
12c2303f
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
240 additions
and
268 deletions
+240
-268
Login.jsx
src/pages/Login.jsx
+2
-2
Layout.jsx
src/pages/project_manager_xy/Layout.jsx
+1
-4
ProjectBookingWorkspace.jsx
src/pages/project_manager_xy/ProjectBookingWorkspace.jsx
+103
-68
project-management.business-flow.md
...es/project_manager_xy/project-management.business-flow.md
+13
-14
projectTaskService.js
src/pages/project_manager_xy/projectTaskService.js
+18
-0
index.jsx
src/pages/project_manager_xy/resource-stat/index.jsx
+78
-170
subjectsDefinition.md
src/services/subjectsDefinition.md
+24
-9
Site.jsx
src/wrapper/Site.jsx
+1
-1
No files found.
src/pages/Login.jsx
View file @
c1615014
...
...
@@ -26,7 +26,7 @@ export default function LoginPage() {
const
redirect
=
searchParams
.
get
(
'redirect'
)
||
'/'
;
const
siteTitle
=
site
?.
title
||
site
?.
name
||
'
项目管理系统
'
;
const
siteTitle
=
site
?.
title
||
site
?.
name
||
'
部门任务管理
'
;
const
siteSubtitle
=
site
?.
subtitle
||
'项目下发、成员协作、甘特排期与个人日历统一协同'
;
...
...
@@ -141,7 +141,7 @@ export default function LoginPage() {
Sign In
</
p
>
<
h2
className=
"mt-3 text-3xl font-semibold text-slate-900"
>
登录
项目管理系统
登录
部门任务管理
</
h2
>
<
p
className=
"mt-3 text-sm leading-6 text-slate-500"
>
参考 `streams-admin` 登录页改造,已接入验证码校验流程。
...
...
src/pages/project_manager_xy/Layout.jsx
View file @
c1615014
...
...
@@ -14,7 +14,7 @@ import clsx from 'clsx';
import
{
useContext
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
Link
,
Outlet
,
useLocation
}
from
'umi'
;
const
APP_NAME
=
'
项目管理系统
'
;
const
APP_NAME
=
'
部门任务管理
'
;
const
menuGroups
=
[
{
...
...
@@ -272,9 +272,6 @@ export default function ProjectManagerLayout() {
<
h1
className=
"truncate text-sm font-semibold tracking-[0.02em] text-white/95"
>
{
APP_NAME
}
</
h1
>
<
div
className=
"mt-1 text-xs text-zinc-400"
>
项目资源管理工作台
</
div
>
</
div
>
{
applicationSwitcherButton
}
</>
...
...
src/pages/project_manager_xy/ProjectBookingWorkspace.jsx
View file @
c1615014
import
clsx
from
'clsx'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
BIG_CATEGORY_OPTIONS
,
buildImportTaskPayload
,
buildTaskPayload
,
OCCURRENCE_FREQUENCY_OPTIONS
,
...
...
@@ -1679,8 +1680,8 @@ function WeeklyUpdateEditorDialog({
</
div
>
<
div
className=
"grid gap-4 px-6 py-5 md:grid-cols-2"
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
本周开始日期
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
本周开始日期
</
span
>
<
input
type=
"date"
value=
{
value
.
week_start_date
}
...
...
@@ -1689,8 +1690,8 @@ function WeeklyUpdateEditorDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
本周结束日期
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
本周结束日期
</
span
>
<
input
type=
"date"
value=
{
value
.
week_end_date
}
...
...
@@ -1699,8 +1700,8 @@ function WeeklyUpdateEditorDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600 md:col-span-2"
>
<
span
className=
"
font-medium
"
>
本周完成内容
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600 md:col-span-2"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
本周完成内容
</
span
>
<
textarea
value=
{
value
.
weekly_summary
}
onChange=
{
(
event
)
=>
onChange
(
'weekly_summary'
,
event
.
target
.
value
)
}
...
...
@@ -1708,8 +1709,8 @@ function WeeklyUpdateEditorDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
更新后累计进度
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
更新后累计进度
</
span
>
<
input
type=
"number"
min=
"0"
...
...
@@ -1722,8 +1723,8 @@ function WeeklyUpdateEditorDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
记录日期
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
记录日期
</
span
>
<
input
type=
"date"
value=
{
value
.
updated_at
}
...
...
@@ -1732,8 +1733,8 @@ function WeeklyUpdateEditorDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
当前阻塞事项
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
当前阻塞事项
</
span
>
<
textarea
value=
{
value
.
blockers
}
onChange=
{
(
event
)
=>
onChange
(
'blockers'
,
event
.
target
.
value
)
}
...
...
@@ -1741,8 +1742,8 @@ function WeeklyUpdateEditorDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
下周计划
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
下周计划
</
span
>
<
textarea
value=
{
value
.
next_week_plan
}
onChange=
{
(
event
)
=>
onChange
(
'next_week_plan'
,
event
.
target
.
value
)
}
...
...
@@ -2082,6 +2083,12 @@ function ProjectTaskDialog({
?
formValues
.
weekly_updates
:
[];
const
priorityOptions
=
fieldOptions
?.
priorityOptions
||
[];
const
bigCategoryOptions
=
fieldOptions
?.
bigCategoryOptions
?.
length
>
0
?
fieldOptions
.
bigCategoryOptions
:
BIG_CATEGORY_OPTIONS
;
const
taskSourceOptions
=
fieldOptions
?.
taskSourceOptions
||
[];
const
taskNatureOptions
=
fieldOptions
?.
taskNatureOptions
||
[];
const
projectStatusOptions
=
fieldOptions
?.
projectStatusOptions
?.
length
>
0
?
fieldOptions
.
projectStatusOptions
...
...
@@ -2097,7 +2104,7 @@ function ProjectTaskDialog({
<>
<
div
className=
"fixed inset-0 z-50 flex items-center justify-center bg-slate-950/45 px-4 py-6"
>
<
div
className=
"max-h-[92vh] w-full max-w-5xl overflow-hidden rounded-[28px] bg-white shadow-[0_32px_100px_rgba(15,23,42,0.28)]"
>
<
div
className=
"flex items-center justify-between border-b border-zinc-200 px-
6 py-5
"
>
<
div
className=
"flex items-center justify-between border-b border-zinc-200 px-
7 py-6
"
>
<
div
>
<
div
className=
"text-xl font-semibold text-zinc-900"
>
{
task
?
'编辑项目'
:
'新建项目'
}
...
...
@@ -2140,11 +2147,11 @@ function ProjectTaskDialog({
event
.
preventDefault
();
onSubmit
(
formValues
);
}
}
className=
"max-h-[calc(92vh-
84px)] overflow-auto px-6 py-5
"
className=
"max-h-[calc(92vh-
92px)] overflow-auto px-7 pb-6 pt-7
"
>
<
div
className=
"grid gap-
4
md:grid-cols-2"
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
项目名称
</
span
>
<
div
className=
"grid gap-
x-4 gap-y-5
md:grid-cols-2"
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
项目名称
</
span
>
<
input
value=
{
formValues
.
project_name
}
onChange=
{
(
event
)
=>
updateField
(
'project_name'
,
event
.
target
.
value
)
}
...
...
@@ -2153,17 +2160,24 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
大类
</
span
>
<
inpu
t
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
大类
</
span
>
<
selec
t
value=
{
formValues
.
big_category
}
onChange=
{
(
event
)
=>
updateField
(
'big_category'
,
event
.
target
.
value
)
}
className=
"h-11 w-full rounded-xl border border-zinc-200 px-3 outline-none transition focus:border-rose-300"
/>
className=
"h-11 w-full rounded-xl border border-zinc-200 bg-white px-3 outline-none transition focus:border-rose-300"
>
<
option
value=
""
>
请选择大类
</
option
>
{
bigCategoryOptions
.
map
((
option
)
=>
(
<
option
key=
{
option
.
value
}
value=
{
option
.
value
}
>
{
option
.
label
}
</
option
>
))
}
</
select
>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
项目
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
项目
</
span
>
<
input
value=
{
formValues
.
project
}
onChange=
{
(
event
)
=>
updateField
(
'project'
,
event
.
target
.
value
)
}
...
...
@@ -2171,8 +2185,8 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
项目负责人
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
项目负责人
</
span
>
<
UserMultiSearchSelect
value=
{
formValues
.
project_owner
}
onChange=
{
(
value
)
=>
updateField
(
'project_owner'
,
value
)
}
...
...
@@ -2180,8 +2194,8 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
项目协作人
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
项目协作人
</
span
>
<
UserMultiSearchSelect
value=
{
formValues
.
project_collaborator
}
onChange=
{
(
value
)
=>
updateField
(
'project_collaborator'
,
value
)
}
...
...
@@ -2189,26 +2203,44 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
任务来源
</
span
>
<
inpu
t
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
任务来源
</
span
>
<
selec
t
value=
{
formValues
.
task_source
}
onChange=
{
(
event
)
=>
updateField
(
'task_source'
,
event
.
target
.
value
)
}
className=
"h-11 w-full rounded-xl border border-zinc-200 px-3 outline-none transition focus:border-rose-300"
/>
className=
"h-11 w-full rounded-xl border border-zinc-200 bg-white px-3 outline-none transition focus:border-rose-300"
>
<
option
value=
""
>
{
taskSourceOptions
.
length
>
0
?
'请选择任务来源'
:
'请先配置任务来源选项'
}
</
option
>
{
taskSourceOptions
.
map
((
option
)
=>
(
<
option
key=
{
option
.
value
}
value=
{
option
.
value
}
>
{
option
.
label
}
</
option
>
))
}
</
select
>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
任务性质
</
span
>
<
inpu
t
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
任务性质
</
span
>
<
selec
t
value=
{
formValues
.
task_nature
}
onChange=
{
(
event
)
=>
updateField
(
'task_nature'
,
event
.
target
.
value
)
}
className=
"h-11 w-full rounded-xl border border-zinc-200 px-3 outline-none transition focus:border-rose-300"
/>
className=
"h-11 w-full rounded-xl border border-zinc-200 bg-white px-3 outline-none transition focus:border-rose-300"
>
<
option
value=
""
>
{
taskNatureOptions
.
length
>
0
?
'请选择任务性质'
:
'请先配置任务性质选项'
}
</
option
>
{
taskNatureOptions
.
map
((
option
)
=>
(
<
option
key=
{
option
.
value
}
value=
{
option
.
value
}
>
{
option
.
label
}
</
option
>
))
}
</
select
>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
发生频率
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
发生频率
</
span
>
<
select
value=
{
formValues
.
occurrence_frequency
}
onChange=
{
(
event
)
=>
updateField
(
'occurrence_frequency'
,
event
.
target
.
value
)
}
...
...
@@ -2223,8 +2255,8 @@ function ProjectTaskDialog({
</
select
>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
优先级
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
优先级
</
span
>
<
select
value=
{
formValues
.
priority
}
onChange=
{
(
event
)
=>
updateField
(
'priority'
,
event
.
target
.
value
)
}
...
...
@@ -2239,8 +2271,8 @@ function ProjectTaskDialog({
</
select
>
</
label
>
<
div
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
是否属于年度审计计划
</
span
>
<
div
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
是否属于年度审计计划
</
span
>
<
div
className=
"rounded-2xl border border-zinc-200 bg-white px-3 py-3"
>
<
label
className=
"flex items-start gap-3"
>
<
input
...
...
@@ -2258,8 +2290,8 @@ function ProjectTaskDialog({
</
div
>
</
div
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
计划开始日期
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
计划开始日期
</
span
>
<
input
type=
"date"
value=
{
formValues
.
planned_start_date
}
...
...
@@ -2269,8 +2301,8 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
计划结束日期
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
计划结束日期
</
span
>
<
input
type=
"date"
value=
{
formValues
.
planned_end_date
}
...
...
@@ -2280,8 +2312,8 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
实际开始日期
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
实际开始日期
</
span
>
<
input
type=
"date"
value=
{
formValues
.
actual_start_date
}
...
...
@@ -2290,8 +2322,8 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
实际完成日期
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
实际完成日期
</
span
>
<
input
type=
"date"
value=
{
formValues
.
actual_finish_date
}
...
...
@@ -2301,9 +2333,9 @@ function ProjectTaskDialog({
</
label
>
</
div
>
<
div
className=
"mt-
5 grid gap-4
md:grid-cols-2"
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
项目状态
</
span
>
<
div
className=
"mt-
6 grid gap-x-4 gap-y-5
md:grid-cols-2"
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
项目状态
</
span
>
<
select
value=
{
formValues
.
project_status
}
onChange=
{
(
event
)
=>
updateField
(
'project_status'
,
event
.
target
.
value
)
}
...
...
@@ -2321,8 +2353,8 @@ function ProjectTaskDialog({
</
div
>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
完成进度
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
完成进度
</
span
>
<
input
type=
"number"
min=
"0"
...
...
@@ -2334,9 +2366,9 @@ function ProjectTaskDialog({
</
label
>
</
div
>
<
div
className=
"mt-
5 grid gap-4
md:grid-cols-2"
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
项目说明
</
span
>
<
div
className=
"mt-
6 grid gap-x-4 gap-y-5
md:grid-cols-2"
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
项目说明
</
span
>
<
textarea
value=
{
formValues
.
description
}
onChange=
{
(
event
)
=>
updateField
(
'description'
,
event
.
target
.
value
)
}
...
...
@@ -2344,8 +2376,8 @@ function ProjectTaskDialog({
/>
</
label
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
备注
</
span
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
备注
</
span
>
<
textarea
value=
{
formValues
.
remark
}
onChange=
{
(
event
)
=>
updateField
(
'remark'
,
event
.
target
.
value
)
}
...
...
@@ -2354,9 +2386,9 @@ function ProjectTaskDialog({
</
label
>
</
div
>
<
div
className=
"mt-
5 grid gap-4
md:grid-cols-2"
>
<
label
className=
"
space-y-2
text-sm text-zinc-600"
>
<
span
className=
"
font-medium
"
>
延期原因
</
span
>
<
div
className=
"mt-
6 grid gap-x-4 gap-y-5
md:grid-cols-2"
>
<
label
className=
"
block space-y-2.5
text-sm text-zinc-600"
>
<
span
className=
"
block font-medium leading-5 text-zinc-700
"
>
延期原因
</
span
>
<
textarea
value=
{
formValues
.
delay_reason
}
onChange=
{
(
event
)
=>
updateField
(
'delay_reason'
,
event
.
target
.
value
)
}
...
...
@@ -2365,7 +2397,7 @@ function ProjectTaskDialog({
</
label
>
</
div
>
<
div
className=
"mt-
5
rounded-2xl border border-zinc-200/90 bg-zinc-50/60 p-4"
>
<
div
className=
"mt-
6
rounded-2xl border border-zinc-200/90 bg-zinc-50/60 p-4"
>
<
div
className=
"flex flex-wrap items-start justify-between gap-3"
>
<
div
className=
"text-sm font-medium text-zinc-900"
>
周进度记录
</
div
>
<
button
...
...
@@ -2551,6 +2583,9 @@ export default function ProjectBookingWorkspace({ currentUsername }) {
const
[
importLoading
,
setImportLoading
]
=
useState
(
false
);
const
[
importFileName
,
setImportFileName
]
=
useState
(
''
);
const
[
fieldOptions
,
setFieldOptions
]
=
useState
({
bigCategoryOptions
:
BIG_CATEGORY_OPTIONS
,
taskSourceOptions
:
[],
taskNatureOptions
:
[],
projectStatusOptions
:
PROJECT_STATUS_OPTIONS
,
priorityOptions
:
[],
occurrenceFrequencyOptions
:
OCCURRENCE_FREQUENCY_OPTIONS
,
...
...
src/pages/project_manager_xy/project-management.business-flow.md
View file @
c1615014
...
...
@@ -35,12 +35,12 @@
关键字段:
-
`project_name`
:项目名称
-
`big_category`
:大类
-
`big_category`
:大类
,选择项为部门管理、纪检工作、审计工作
-
`project`
:项目归属或项目分类补充字段
-
`project_owner`
:项目负责人,直接关联系统用户,多值
-
`project_collaborator`
:项目协作人,直接关联系统用户,多值
-
`task_source`
:任务来源
-
`task_nature`
:任务性质
-
`task_source`
:任务来源
,选择项由 Subject 字段配置
`settings.options`
提供
-
`task_nature`
:任务性质
,选择项由 Subject 字段配置
`settings.options`
提供
-
`occurrence_frequency`
:发生频率
-
`planned_start_date`
/
`planned_end_date`
:计划时间
-
`actual_start_date`
/
`actual_finish_date`
:实际时间
...
...
@@ -59,7 +59,7 @@
1.
领导创建一条
`pm_project_task`
2.
填写项目名称、计划开始日期、计划结束日期
3.
按需要
补充大类、项目、任务来源、任务性质、发生频率
3.
按需要
选择大类、任务来源、任务性质、发生频率,并补充项目
4.
按需勾选是否属于年度审计计划
`is_annual_audit_plan`
5.
选择一个或多个项目负责人
`project_owner`
6.
选择一个或多个项目协作人
`project_collaborator`
...
...
@@ -128,28 +128,25 @@
-
结果状态分布柱状图
-
进度区间分布柱状图
-
成员负载 Top N 横向柱状图
-
维度概览区保留列表化统计:
-
发生频率
-
大类
-
任务来源
-
“维度概览”区块当前意义不明确,资源统计看板暂不展示该模块;发生频率、大类、任务来源等维度待统计口径明确后再补充为独立图表或筛选项
-
风险清单区展示:
-
即将到期项目
-
超时项目清单
-
风险清单区位于成员负载明细下方,采用独立全宽区块展示,避免作为右侧纵列造成页面左右高度失衡
-
建议补充统计维度:
-
项目状态分布
-
结果状态分布(按时间规则实时派生)
-
发生频率分布
-
大类分布
-
任务来源分布
-
负责人/协作人负载分布
-
即将到期项目
-
超时项目清单
-
统计口径以
`pm_project_task.metadata`
的当前字段为准,优先展示:
-
`project_status`
-
`occurrence_frequency`
-
`big_category`
-
`task_source`
-
`completion_percent`
-
`project_owner`
-
`project_collaborator`
-
`planned_end_date`
-
`actual_finish_date`
### 5.1 甘特图
...
...
@@ -229,6 +226,8 @@ const result = await queryUsers(0, 20, {
## 6.3 Excel 批量导入口径
-
Excel 模板采用业务列头:
`大类`
、
`项目`
、
`是否属于年度审计计划`
、
`发生频率`
、
`任务名称`
、
`任务来源`
、
`任务性质`
、
`计划启动时间`
、
`计划完成时间`
、
`时间进度`
、
`任务进度`
、
`任务状态`
、
`主办人`
、
`协办人1`
-
`大类`
导入到
`metadata.big_category`
,当前仅支持
`部门管理`
、
`纪检工作`
、
`审计工作`
-
`任务来源`
/
`任务性质`
导入到
`metadata.task_source`
/
`metadata.task_nature`
,应填写对应 Subject 字段
`settings.options`
中配置的存储值
-
`任务名称`
导入到
`metadata.project_name`
-
`是否属于年度审计计划`
导入到
`metadata.is_annual_audit_plan`
,支持
`是/否`
、
`true/false`
、
`1/0`
-
`主办人`
导入到
`metadata.project_owner`
,
`协办人1`
导入到
`metadata.project_collaborator`
;支持填写系统用户
`display_name`
或
`username`
,前端会先按
`username/display_name`
的
`$regex`
条件调用
`queryUsers`
查询,并最终保存为用户
`_id`
数组
...
...
src/pages/project_manager_xy/projectTaskService.js
View file @
c1615014
...
...
@@ -28,6 +28,12 @@ export const OCCURRENCE_FREQUENCY_OPTIONS = [
{
label
:
'低'
,
value
:
'1'
},
];
export
const
BIG_CATEGORY_OPTIONS
=
[
{
label
:
'部门管理'
,
value
:
'部门管理'
},
{
label
:
'纪检工作'
,
value
:
'纪检工作'
},
{
label
:
'审计工作'
,
value
:
'审计工作'
},
];
export
const
PRIORITY_OPTIONS
=
[
{
label
:
'高'
,
value
:
'high'
},
{
label
:
'中'
,
value
:
'medium'
},
...
...
@@ -331,11 +337,20 @@ function extractSubjectField(subject, fieldName) {
export
async
function
loadProjectTaskFieldOptions
()
{
try
{
const
subject
=
await
loadSubject
(
PROJECT_TASK_SUBJECT
);
const
bigCategoryField
=
extractSubjectField
(
subject
,
'big_category'
);
const
taskSourceField
=
extractSubjectField
(
subject
,
'task_source'
);
const
taskNatureField
=
extractSubjectField
(
subject
,
'task_nature'
);
const
projectStatusField
=
extractSubjectField
(
subject
,
'project_status'
);
const
priorityField
=
extractSubjectField
(
subject
,
'priority'
);
const
occurrenceFrequencyField
=
extractSubjectField
(
subject
,
'occurrence_frequency'
);
return
{
bigCategoryOptions
:
normalizeSelectOptions
(
bigCategoryField
?.
settings
?.
options
).
length
>
0
?
normalizeSelectOptions
(
bigCategoryField
?.
settings
?.
options
)
:
BIG_CATEGORY_OPTIONS
,
taskSourceOptions
:
normalizeSelectOptions
(
taskSourceField
?.
settings
?.
options
),
taskNatureOptions
:
normalizeSelectOptions
(
taskNatureField
?.
settings
?.
options
),
projectStatusOptions
:
normalizeSelectOptions
(
projectStatusField
?.
settings
?.
options
).
length
>
0
?
normalizeSelectOptions
(
projectStatusField
?.
settings
?.
options
)
...
...
@@ -348,6 +363,9 @@ export async function loadProjectTaskFieldOptions() {
};
}
catch
(
error
)
{
return
{
bigCategoryOptions
:
BIG_CATEGORY_OPTIONS
,
taskSourceOptions
:
[],
taskNatureOptions
:
[],
projectStatusOptions
:
PROJECT_STATUS_OPTIONS
,
priorityOptions
:
[],
occurrenceFrequencyOptions
:
OCCURRENCE_FREQUENCY_OPTIONS
,
...
...
src/pages/project_manager_xy/resource-stat/index.jsx
View file @
c1615014
...
...
@@ -4,7 +4,6 @@ import { useContext, useEffect, useMemo, useState } from 'react';
import
ReactECharts
from
'echarts-for-react'
;
import
{
formatDateLabel
,
OCCURRENCE_FREQUENCY_LABEL_MAP
,
PROJECT_STATUS_LABEL_MAP
,
RESULT_STATUS_LABEL_MAP
,
}
from
'../projectTaskService'
;
...
...
@@ -297,28 +296,6 @@ function ChartPanel({ title, description, option, hasData }) {
);
}
function
DimensionList
({
items
,
emptyText
})
{
if
(
items
.
length
===
0
)
{
return
<
div
className=
"text-sm text-zinc-400"
>
{
emptyText
}
</
div
>;
}
return
(
<
div
className=
"space-y-3"
>
{
items
.
map
((
item
)
=>
(
<
div
key=
{
item
.
key
}
className=
"rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-4"
>
<
div
className=
"flex items-center justify-between gap-4"
>
<
div
className=
"min-w-0 text-sm font-medium text-zinc-800"
>
{
item
.
label
}
</
div
>
<
div
className=
"shrink-0 text-lg font-semibold text-zinc-950"
>
{
item
.
count
}
</
div
>
</
div
>
</
div
>
))
}
</
div
>
);
}
export
default
function
ResourceStatPage
({
currentUsername
=
''
})
{
const
userInfo
=
useContext
(
UserContext
);
const
effectiveCurrentUsername
=
currentUsername
||
userInfo
?.
username
||
userInfo
?.
_id
||
''
;
...
...
@@ -400,48 +377,6 @@ export default function ResourceStatPage({ currentUsername = '' }) {
}))
.
sort
((
left
,
right
)
=>
right
.
count
-
left
.
count
);
const
frequencyGroups
=
Object
.
entries
(
tasks
.
reduce
((
accumulator
,
task
)
=>
{
const
key
=
task
.
occurrenceFrequency
||
'unset'
;
accumulator
[
key
]
=
(
accumulator
[
key
]
||
0
)
+
1
;
return
accumulator
;
},
{}),
)
.
map
(([
key
,
count
])
=>
({
key
,
label
:
key
===
'unset'
?
'未设置'
:
OCCURRENCE_FREQUENCY_LABEL_MAP
[
key
]
||
key
,
count
,
}))
.
sort
((
left
,
right
)
=>
right
.
count
-
left
.
count
);
const
categoryGroups
=
Object
.
entries
(
tasks
.
reduce
((
accumulator
,
task
)
=>
{
const
key
=
task
.
bigCategory
||
'unset'
;
accumulator
[
key
]
=
(
accumulator
[
key
]
||
0
)
+
1
;
return
accumulator
;
},
{}),
)
.
map
(([
key
,
count
])
=>
({
key
,
label
:
key
===
'unset'
?
'未设置'
:
key
,
count
,
}))
.
sort
((
left
,
right
)
=>
right
.
count
-
left
.
count
);
const
taskSourceGroups
=
Object
.
entries
(
tasks
.
reduce
((
accumulator
,
task
)
=>
{
const
key
=
task
.
taskSource
||
'unset'
;
accumulator
[
key
]
=
(
accumulator
[
key
]
||
0
)
+
1
;
return
accumulator
;
},
{}),
)
.
map
(([
key
,
count
])
=>
({
key
,
label
:
key
===
'unset'
?
'未设置'
:
key
,
count
,
}))
.
sort
((
left
,
right
)
=>
right
.
count
-
left
.
count
);
const
progressBuckets
=
PROGRESS_BUCKETS
.
map
((
bucket
)
=>
({
...
bucket
,
count
:
tasks
.
filter
((
task
)
=>
{
...
...
@@ -495,9 +430,6 @@ export default function ResourceStatPage({ currentUsername = '' }) {
currentUserTaskCount
,
statusGroups
,
resultStatusGroups
,
frequencyGroups
,
categoryGroups
,
taskSourceGroups
,
progressBuckets
,
memberLoad
,
upcoming
,
...
...
@@ -635,7 +567,7 @@ export default function ResourceStatPage({ currentUsername = '' }) {
/>
</
div
>
<
div
className=
"grid gap-6 xl:grid-cols-
[1fr_1fr_0.9fr]
"
>
<
div
className=
"grid gap-6 xl:grid-cols-
2
"
>
<
ChartPanel
title=
"进度区间分布"
description=
"按 `completion_percent` 分桶,快速查看项目推进层级。"
...
...
@@ -648,113 +580,89 @@ export default function ResourceStatPage({ currentUsername = '' }) {
option=
{
memberLoadOption
}
hasData=
{
dashboard
.
memberLoad
.
length
>
0
}
/>
<
SectionCard
title=
"维度概览"
description=
"保留发生频率、大类、任务来源三类核心统计维度。"
bodyClassName=
"space-y-5"
>
<
div
>
<
div
className=
"mb-3 text-sm font-medium text-zinc-500"
>
发生频率
</
div
>
<
DimensionList
items=
{
dashboard
.
frequencyGroups
.
slice
(
0
,
3
)
}
emptyText=
"暂无发生频率统计数据"
/>
</
div
>
<
div
>
<
div
className=
"mb-3 text-sm font-medium text-zinc-500"
>
大类
</
div
>
<
DimensionList
items=
{
dashboard
.
categoryGroups
.
slice
(
0
,
3
)
}
emptyText=
"暂无大类统计数据"
/>
</
div
>
<
div
>
<
div
className=
"mb-3 text-sm font-medium text-zinc-500"
>
任务来源
</
div
>
<
DimensionList
items=
{
dashboard
.
taskSourceGroups
.
slice
(
0
,
3
)
}
emptyText=
"暂无任务来源统计数据"
/>
</
div
>
</
SectionCard
>
</
div
>
<
div
className=
"grid gap-6 xl:grid-cols-[1.1fr_0.9fr]"
>
<
SectionCard
title=
"成员负载明细"
description=
"展示每位成员的关联项目、进行中项目和超时项目数量。"
bodyClassName=
"px-0 py-0"
>
<
div
className=
"overflow-hidden rounded-[28px]"
>
<
div
className=
"grid grid-cols-[1.4fr_0.8fr_0.8fr_0.8fr] bg-zinc-50 text-sm font-medium text-zinc-500"
>
<
div
className=
"px-5 py-3"
>
成员
</
div
>
<
div
className=
"px-5 py-3 text-center"
>
参与项目
</
div
>
<
div
className=
"px-5 py-3 text-center"
>
进行中
</
div
>
<
div
className=
"px-5 py-3 text-center"
>
超时
</
div
>
</
div
>
{
dashboard
.
memberLoad
.
length
>
0
?
(
dashboard
.
memberLoad
.
map
((
member
)
=>
(
<
div
key=
{
member
.
id
}
className=
"grid grid-cols-[1.4fr_0.8fr_0.8fr_0.8fr] border-t border-zinc-100 text-sm text-zinc-700"
>
<
div
className=
"px-5 py-3.5 font-medium text-zinc-950"
>
{
member
.
label
}
</
div
>
<
div
className=
"px-5 py-3.5 text-center"
>
{
member
.
totalProjects
}
</
div
>
<
div
className=
"px-5 py-3.5 text-center"
>
{
member
.
inProgressProjects
}
</
div
>
<
div
className=
"px-5 py-3.5 text-center text-rose-600"
>
{
member
.
overdueProjects
}
</
div
>
<
SectionCard
title=
"成员负载明细"
description=
"展示每位成员的关联项目、进行中项目和超时项目数量。"
bodyClassName=
"px-0 py-0"
>
<
div
className=
"overflow-hidden rounded-[28px]"
>
<
div
className=
"grid grid-cols-[1.4fr_0.8fr_0.8fr_0.8fr] bg-zinc-50 text-sm font-medium text-zinc-500"
>
<
div
className=
"px-5 py-3"
>
成员
</
div
>
<
div
className=
"px-5 py-3 text-center"
>
参与项目
</
div
>
<
div
className=
"px-5 py-3 text-center"
>
进行中
</
div
>
<
div
className=
"px-5 py-3 text-center"
>
超时
</
div
>
</
div
>
{
dashboard
.
memberLoad
.
length
>
0
?
(
dashboard
.
memberLoad
.
map
((
member
)
=>
(
<
div
key=
{
member
.
id
}
className=
"grid grid-cols-[1.4fr_0.8fr_0.8fr_0.8fr] border-t border-zinc-100 text-sm text-zinc-700"
>
<
div
className=
"px-5 py-3.5 font-medium text-zinc-950"
>
{
member
.
label
}
</
div
>
<
div
className=
"px-5 py-3.5 text-center"
>
{
member
.
totalProjects
}
</
div
>
<
div
className=
"px-5 py-3.5 text-center"
>
{
member
.
inProgressProjects
}
</
div
>
<
div
className=
"px-5 py-3.5 text-center text-rose-600"
>
{
member
.
overdueProjects
}
</
div
>
))
)
:
(
<
div
className=
"px-5 py-12 text-center text-sm text-zinc-400"
>
暂无成员负载数据
</
div
>
)
}
</
div
>
</
SectionCard
>
<
div
className=
"space-y-6"
>
<
SectionCard
title=
"即将到期"
description=
"按计划完成日期升序展示,便于提前提醒负责人。"
>
<
div
className=
"space-y-3"
>
{
dashboard
.
upcoming
.
length
>
0
?
(
dashboard
.
upcoming
.
map
((
task
)
=>
(
<
div
key=
{
task
.
_id
||
task
.
id
||
task
.
name
}
className=
"rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-4"
>
<
div
className=
"text-sm font-semibold text-zinc-950"
>
{
task
.
projectName
}
</
div
>
<
div
className=
"mt-1 text-xs leading-5 text-zinc-500"
>
截止
{
formatDateLabel
(
task
.
plannedEndDate
)
}
·
{
task
.
projectStatusLabel
}
·
{
' '
}
{
task
.
resultStatusLabel
}
·
{
formatPercent
(
task
.
progressPercent
)
}
</
div
>
</
div
>
))
)
:
(
<
div
className=
"text-sm text-zinc-400"
>
暂无到期项目
</
div
>
)
}
</
div
>
</
SectionCard
>
<
SectionCard
title=
"超时清单"
description=
"按超时天数倒序展示,帮助工作台聚焦风险项目。"
>
<
div
className=
"space-y-3"
>
{
dashboard
.
overdueList
.
length
>
0
?
(
dashboard
.
overdueList
.
map
((
task
)
=>
(
<
div
key=
{
task
.
_id
||
task
.
id
||
task
.
name
}
className=
"rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4"
>
<
div
className=
"text-sm font-semibold text-zinc-950"
>
{
task
.
projectName
}
</
div
>
<
div
className=
"mt-1 text-xs leading-5 text-rose-700"
>
超时
{
task
.
overtimeDays
}
天 ·
{
task
.
projectStatusLabel
}
·
{
' '
}
{
task
.
resultStatusLabel
}
</
div
>
</
div
>
))
)
:
(
<
div
className=
"text-sm text-zinc-400"
>
当前没有超时项目
</
div
>
)
}
))
)
:
(
<
div
className=
"px-5 py-12 text-center text-sm text-zinc-400"
>
暂无成员负载数据
</
div
>
</
SectionCard
>
)
}
</
div
>
</
div
>
</
SectionCard
>
<
SectionCard
title=
"即将到期"
description=
"按计划完成日期升序展示,便于提前提醒负责人。"
>
<
div
className=
"grid gap-3 md:grid-cols-2 xl:grid-cols-3"
>
{
dashboard
.
upcoming
.
length
>
0
?
(
dashboard
.
upcoming
.
map
((
task
)
=>
(
<
div
key=
{
task
.
_id
||
task
.
id
||
task
.
name
}
className=
"rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-4"
>
<
div
className=
"text-sm font-semibold text-zinc-950"
>
{
task
.
projectName
}
</
div
>
<
div
className=
"mt-1 text-xs leading-5 text-zinc-500"
>
截止
{
formatDateLabel
(
task
.
plannedEndDate
)
}
·
{
task
.
projectStatusLabel
}
·
{
' '
}
{
task
.
resultStatusLabel
}
·
{
formatPercent
(
task
.
progressPercent
)
}
</
div
>
</
div
>
))
)
:
(
<
div
className=
"text-sm text-zinc-400"
>
暂无到期项目
</
div
>
)
}
</
div
>
</
SectionCard
>
<
SectionCard
title=
"超时清单"
description=
"按超时天数倒序展示,帮助工作台聚焦风险项目。"
>
<
div
className=
"grid gap-3 md:grid-cols-2 xl:grid-cols-3"
>
{
dashboard
.
overdueList
.
length
>
0
?
(
dashboard
.
overdueList
.
map
((
task
)
=>
(
<
div
key=
{
task
.
_id
||
task
.
id
||
task
.
name
}
className=
"rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4"
>
<
div
className=
"text-sm font-semibold text-zinc-950"
>
{
task
.
projectName
}
</
div
>
<
div
className=
"mt-1 text-xs leading-5 text-rose-700"
>
超时
{
task
.
overtimeDays
}
天 ·
{
task
.
projectStatusLabel
}
·
{
' '
}
{
task
.
resultStatusLabel
}
</
div
>
</
div
>
))
)
:
(
<
div
className=
"text-sm text-zinc-400"
>
当前没有超时项目
</
div
>
)
}
</
div
>
</
SectionCard
>
</
div
>
);
}
src/services/subjectsDefinition.md
View file @
c1615014
...
...
@@ -89,12 +89,12 @@ type Record = {
| 字段标签 | 字段别名 | 字段类型 | 是否主键 | 是否多值 | 是否必填 |
| --- | --- | --- | --- | --- | --- |
| 项目名称 |
`project_name`
| text | 否 | 否 | 是 |
| 大类 |
`big_category`
|
tex
t | 否 | 否 | 否 |
| 大类 |
`big_category`
|
selec
t | 否 | 否 | 否 |
| 项目 |
`project`
| text | 否 | 否 | 否 |
| 项目负责人 |
`project_owner`
| user | 否 | 是 | 否 |
| 项目协作人 |
`project_collaborator`
| user | 否 | 是 | 否 |
| 任务来源 |
`task_source`
|
tex
t | 否 | 否 | 否 |
| 任务性质 |
`task_nature`
|
tex
t | 否 | 否 | 否 |
| 任务来源 |
`task_source`
|
selec
t | 否 | 否 | 否 |
| 任务性质 |
`task_nature`
|
selec
t | 否 | 否 | 否 |
| 发生频率 |
`occurrence_frequency`
| select | 否 | 否 | 否 |
| 计划开始日期 |
`planned_start_date`
| date | 否 | 否 | 是 |
| 计划结束日期 |
`planned_end_date`
| date | 否 | 否 | 是 |
...
...
@@ -109,7 +109,22 @@ type Record = {
| 周进度记录 |
`weekly_updates`
| object | 否 | 否 | 否 |
| 是否属于年度审计计划 |
`is_annual_audit_plan`
| boolean | 否 | 否 | 否 |
### 3.1.1 `project_status` 选项
### 3.1.1 `big_category` 选项
| 显示值 | 存储值 |
| --- | --- |
| 部门管理 |
`部门管理`
|
| 纪检工作 |
`纪检工作`
|
| 审计工作 |
`审计工作`
|
### 3.1.2 `task_source` / `task_nature` 配置说明
-
`task_source`
与
`task_nature`
为
`select`
类型
-
选项必须由 Subject 字段配置中的
`settings.options`
提供
-
前端表单只读取配置项生成下拉,不在代码中硬编码选项
-
Excel 导入时应填写对应选项的存储值
### 3.1.3 `project_status` 选项
| 显示值 | 存储值 |
| --- | --- |
...
...
@@ -119,7 +134,7 @@ type Record = {
| 受阻 |
`blocked`
|
| 已取消 |
`cancelled`
|
### 3.1.
2
`occurrence_frequency` 选项
### 3.1.
4
`occurrence_frequency` 选项
| 显示值 | 存储值 |
| --- | --- |
...
...
@@ -127,13 +142,13 @@ type Record = {
| 中 |
`2`
|
| 低 |
`1`
|
### 3.1.
3
`priority` 配置说明
### 3.1.
5
`priority` 配置说明
-
当前最新结构中,
`priority`
为
`select`
类型
-
但字段定义内暂未提供
`settings.options`
-
前端生成表单时不得硬编码选项,需等对象配置或接口返回的
`settings.options`
### 3.1.
4
`weekly_updates` 子结构建议
### 3.1.
6
`weekly_updates` 子结构建议
`weekly_updates`
当前字段类型为
`object`
。如业务上继续沿用“周进度记录集合”方案,建议对象内维护
`items`
数组,每项至少包含以下字段:
...
...
@@ -155,12 +170,12 @@ type Record = {
-
列表页或项目详情只展示最近一条摘要,不直接暴露底层 JSON
-
若后端最终确认
`weekly_updates`
直接存数组,可再将本节从“对象内
`items`
数组”调整为“字段直接存数组”
### 3.1.
5
metadata 示例
### 3.1.
7
metadata 示例
```
json
{
"project_name"
:
"某客户 2026 年专项项目"
,
"big_category"
:
"审计"
,
"big_category"
:
"审计
工作
"
,
"project"
:
"2026 年专项项目"
,
"project_owner"
:
[
"69b12a47e92b1de77c94a9f6"
...
...
src/wrapper/Site.jsx
View file @
c1615014
...
...
@@ -42,7 +42,7 @@ export default function SiteWrapper() {
setLoadError
(
error
?.
message
||
'加载站点配置失败'
);
setSiteInfo
({
name
:
'
项目管理系统
'
,
name
:
'
部门任务管理
'
,
});
}
finally
{
if
(
!
cancelled
)
{
...
...
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