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
a2e249fe
Commit
a2e249fe
authored
Aug 25, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: integrate audit material center in problem detail
parent
ceba014b
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
653 additions
and
18 deletions
+653
-18
SubjectViewContent.jsx
...ages/RecordDetailDrawer/components/SubjectViewContent.jsx
+4
-0
TailwindEditableField.jsx
...s/RecordDetailDrawer/components/TailwindEditableField.jsx
+23
-0
index.jsx
src/pages/RecordDetailDrawer/index.jsx
+47
-0
ProblemRectificationMaterialField.jsx
...fication/components/ProblemRectificationMaterialField.jsx
+304
-0
index.jsx
src/pages/problem_rectification/list/index.jsx
+1
-0
problem-rectification.business-flow.md
...blem_rectification/problem-rectification.business-flow.md
+17
-2
problemRectificationMaterialService.js
...blem_rectification/problemRectificationMaterialService.js
+159
-0
record-api.md
src/services/record-api.md
+64
-2
subjectsDefinition.md
src/services/subjectsDefinition.md
+34
-14
No files found.
src/pages/RecordDetailDrawer/components/SubjectViewContent.jsx
View file @
a2e249fe
...
@@ -53,10 +53,12 @@ export default function SubjectViewContent({
...
@@ -53,10 +53,12 @@ export default function SubjectViewContent({
editSessionId
,
editSessionId
,
mode
=
'view'
,
mode
=
'view'
,
onFieldChange
,
onFieldChange
,
onPendingLocalMaterialChange
,
onFieldValidation
,
onFieldValidation
,
readonly
,
readonly
,
isFieldDisabled
,
isFieldDisabled
,
minimal
=
false
,
minimal
=
false
,
useAuditMaterialCenter
=
false
,
})
{
})
{
const
displayFields
=
buildViewColumns
(
view
,
subject
);
const
displayFields
=
buildViewColumns
(
view
,
subject
);
const
contentFields
=
minimal
const
contentFields
=
minimal
...
@@ -119,8 +121,10 @@ export default function SubjectViewContent({
...
@@ -119,8 +121,10 @@ export default function SubjectViewContent({
)
)
}
}
onValidationChange=
{
onFieldValidation
}
onValidationChange=
{
onFieldValidation
}
onPendingLocalMaterialChange=
{
onPendingLocalMaterialChange
}
readonly=
{
readonly
}
readonly=
{
readonly
}
disabled=
{
fieldDisabled
}
disabled=
{
fieldDisabled
}
useAuditMaterialCenter=
{
useAuditMaterialCenter
}
/>
/>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
src/pages/RecordDetailDrawer/components/TailwindEditableField.jsx
View file @
a2e249fe
...
@@ -15,6 +15,7 @@ import FileUploadField from '@/components/FileUploadField';
...
@@ -15,6 +15,7 @@ import FileUploadField from '@/components/FileUploadField';
import
{
queryUsers
,
uploadFile
}
from
'@/services/DataService'
;
import
{
queryUsers
,
uploadFile
}
from
'@/services/DataService'
;
import
ReferenceSearchSelect
from
'@/components/ReferenceSearchSelect'
;
import
ReferenceSearchSelect
from
'@/components/ReferenceSearchSelect'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
ProblemRectificationMaterialField
from
'@/pages/problem_rectification/components/ProblemRectificationMaterialField'
;
import
ReferenceRecordsTable
from
'./ReferenceRecordsTable'
;
import
ReferenceRecordsTable
from
'./ReferenceRecordsTable'
;
import
{
import
{
dateInputValue
,
dateInputValue
,
...
@@ -303,7 +304,9 @@ function ControlledFieldEditor({
...
@@ -303,7 +304,9 @@ function ControlledFieldEditor({
fieldVariant
,
fieldVariant
,
placeholder
,
placeholder
,
onChange
,
onChange
,
onPendingLocalMaterialChange
,
onValidationChange
,
onValidationChange
,
useAuditMaterialCenter
,
})
{
})
{
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
uploading
,
setUploading
]
=
useState
(
false
);
const
[
uploading
,
setUploading
]
=
useState
(
false
);
...
@@ -388,6 +391,22 @@ function ControlledFieldEditor({
...
@@ -388,6 +391,22 @@ function ControlledFieldEditor({
}
}
if
(
fieldType
===
'file'
)
{
if
(
fieldType
===
'file'
)
{
if
(
useAuditMaterialCenter
&&
fieldPath
===
'metadata.rectification_proof_materials'
)
{
return
(
<
ProblemRectificationMaterialField
editSessionId=
{
editSessionId
}
fieldPath=
{
fieldPath
}
onChange=
{
onChange
}
onPendingLocalMaterialChange=
{
onPendingLocalMaterialChange
}
onValidationChange=
{
onValidationChange
}
value=
{
value
}
/>
);
}
return
(
return
(
<
Card
variant=
"secondary"
>
<
Card
variant=
"secondary"
>
<
Card
.
Content
className=
"space-y-3"
>
<
Card
.
Content
className=
"space-y-3"
>
...
@@ -518,7 +537,9 @@ export default function TailwindEditableField({
...
@@ -518,7 +537,9 @@ export default function TailwindEditableField({
placeholder
,
placeholder
,
onSave
,
onSave
,
onChange
,
onChange
,
onPendingLocalMaterialChange
,
onValidationChange
,
onValidationChange
,
useAuditMaterialCenter
=
false
,
mode
=
'inline'
,
mode
=
'inline'
,
readonly
=
false
,
readonly
=
false
,
disabled
=
false
,
disabled
=
false
,
...
@@ -559,7 +580,9 @@ export default function TailwindEditableField({
...
@@ -559,7 +580,9 @@ export default function TailwindEditableField({
fieldVariant=
{
fieldVariant
}
fieldVariant=
{
fieldVariant
}
placeholder=
{
textPlaceholder
}
placeholder=
{
textPlaceholder
}
onChange=
{
onChange
}
onChange=
{
onChange
}
onPendingLocalMaterialChange=
{
onPendingLocalMaterialChange
}
onValidationChange=
{
onValidationChange
}
onValidationChange=
{
onValidationChange
}
useAuditMaterialCenter=
{
useAuditMaterialCenter
}
/>
/>
);
);
}
}
...
...
src/pages/RecordDetailDrawer/index.jsx
View file @
a2e249fe
...
@@ -4,6 +4,7 @@ import { Close } from '@/components/AppIcons';
...
@@ -4,6 +4,7 @@ import { Close } 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
,
{
useCallback
,
useCallback
,
useContext
,
forwardRef
,
forwardRef
,
useEffect
,
useEffect
,
useImperativeHandle
,
useImperativeHandle
,
...
@@ -13,6 +14,8 @@ import React, {
...
@@ -13,6 +14,8 @@ import React, {
}
from
'react'
;
}
from
'react'
;
import
{
useSubjectRecord
}
from
'./hooks/useSubjectRecord'
;
import
{
useSubjectRecord
}
from
'./hooks/useSubjectRecord'
;
import
SubjectViewContent
from
'./components/SubjectViewContent'
;
import
SubjectViewContent
from
'./components/SubjectViewContent'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
createProblemRectificationMaterial
}
from
'@/pages/problem_rectification/problemRectificationMaterialService'
;
import
{
import
{
buildViewColumns
,
buildViewColumns
,
checkViewCondition
,
checkViewCondition
,
...
@@ -58,8 +61,11 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -58,8 +61,11 @@ const RecordDetailDrawer = (props, ref) => {
disabledFields
=
EMPTY_ARRAY
,
disabledFields
=
EMPTY_ARRAY
,
authorizationView
=
null
,
authorizationView
=
null
,
deriveFieldValues
,
deriveFieldValues
,
useAuditMaterialCenter
=
false
,
}
=
props
;
}
=
props
;
const
currentUser
=
useContext
(
UserContext
);
const
[
refreshKey
,
setRefreshKey
]
=
useState
(()
=>
Date
.
now
());
const
[
refreshKey
,
setRefreshKey
]
=
useState
(()
=>
Date
.
now
());
const
[
isFullscreen
,
setIsFullscreen
]
=
useState
(
false
);
const
[
isFullscreen
,
setIsFullscreen
]
=
useState
(
false
);
const
[
activeTabKey
,
setActiveTabKey
]
=
useState
();
const
[
activeTabKey
,
setActiveTabKey
]
=
useState
();
...
@@ -70,6 +76,7 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -70,6 +76,7 @@ const RecordDetailDrawer = (props, ref) => {
const
[
validationErrors
,
setValidationErrors
]
=
useState
({});
const
[
validationErrors
,
setValidationErrors
]
=
useState
({});
const
[
saveError
,
setSaveError
]
=
useState
(
''
);
const
[
saveError
,
setSaveError
]
=
useState
(
''
);
const
editSessionRef
=
useRef
(
0
);
const
editSessionRef
=
useRef
(
0
);
const
pendingLocalMaterialsRef
=
useRef
({});
const
{
const
{
editableRecord
,
editableRecord
,
...
@@ -100,6 +107,7 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -100,6 +107,7 @@ const RecordDetailDrawer = (props, ref) => {
setDirtyFields
({});
setDirtyFields
({});
setValidationErrors
({});
setValidationErrors
({});
setSaveError
(
''
);
setSaveError
(
''
);
pendingLocalMaterialsRef
.
current
=
{};
editSessionRef
.
current
+=
1
;
editSessionRef
.
current
+=
1
;
},
[
open
,
recordName
]);
},
[
open
,
recordName
]);
...
@@ -200,6 +208,7 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -200,6 +208,7 @@ const RecordDetailDrawer = (props, ref) => {
setDirtyFields
({});
setDirtyFields
({});
setValidationErrors
({});
setValidationErrors
({});
setSaveError
(
''
);
setSaveError
(
''
);
pendingLocalMaterialsRef
.
current
=
{};
};
};
const
handleRequestClose
=
()
=>
{
const
handleRequestClose
=
()
=>
{
...
@@ -215,6 +224,7 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -215,6 +224,7 @@ const RecordDetailDrawer = (props, ref) => {
setDirtyFields
({});
setDirtyFields
({});
setValidationErrors
({});
setValidationErrors
({});
setSaveError
(
''
);
setSaveError
(
''
);
pendingLocalMaterialsRef
.
current
=
{};
setIsEditing
(
true
);
setIsEditing
(
true
);
};
};
...
@@ -257,6 +267,21 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -257,6 +267,21 @@ const RecordDetailDrawer = (props, ref) => {
});
});
},
[]);
},
[]);
const
handlePendingLocalMaterialChange
=
useCallback
(
(
fieldPath
,
pendingMaterial
,
editSessionId
)
=>
{
if
(
editSessionId
!==
editSessionRef
.
current
)
return
;
const
nextPendingMaterials
=
{
...
pendingLocalMaterialsRef
.
current
};
if
(
pendingMaterial
)
{
nextPendingMaterials
[
fieldPath
]
=
pendingMaterial
;
}
else
{
delete
nextPendingMaterials
[
fieldPath
];
}
pendingLocalMaterialsRef
.
current
=
nextPendingMaterials
;
},
[],
);
const
handleSave
=
async
()
=>
{
const
handleSave
=
async
()
=>
{
if
(
!
hasDirtyFields
||
hasValidationErrors
||
isSaving
)
return
;
if
(
!
hasDirtyFields
||
hasValidationErrors
||
isSaving
)
return
;
...
@@ -264,10 +289,30 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -264,10 +289,30 @@ const RecordDetailDrawer = (props, ref) => {
setSaveError
(
''
);
setSaveError
(
''
);
try
{
try
{
const
pendingMaterials
=
Object
.
entries
(
pendingLocalMaterialsRef
.
current
)
.
filter
(([
fieldPath
])
=>
dirtyFields
[
fieldPath
]);
for
(
const
[
fieldPath
,
pendingMaterial
]
of
pendingMaterials
)
{
if
(
pendingMaterial
.
materialRecordId
)
continue
;
const
createdMaterial
=
await
createProblemRectificationMaterial
({
fileAsset
:
pendingMaterial
.
fileAsset
,
currentUser
,
});
pendingLocalMaterialsRef
.
current
=
{
...
pendingLocalMaterialsRef
.
current
,
[
fieldPath
]:
{
...
pendingMaterial
,
materialRecordId
:
createdMaterial
.
id
,
},
};
}
await
handleFieldsUpdate
(
Object
.
values
(
dirtyFields
));
await
handleFieldsUpdate
(
Object
.
values
(
dirtyFields
));
setIsEditing
(
false
);
setIsEditing
(
false
);
setDirtyFields
({});
setDirtyFields
({});
setValidationErrors
({});
setValidationErrors
({});
pendingLocalMaterialsRef
.
current
=
{};
onDataChange
?.(
true
);
onDataChange
?.(
true
);
}
catch
(
saveFailure
)
{
}
catch
(
saveFailure
)
{
setSaveError
(
saveFailure
?.
message
||
'保存失败,请稍后重试。'
);
setSaveError
(
saveFailure
?.
message
||
'保存失败,请稍后重试。'
);
...
@@ -389,9 +434,11 @@ const RecordDetailDrawer = (props, ref) => {
...
@@ -389,9 +434,11 @@ const RecordDetailDrawer = (props, ref) => {
mode=
{
isEditing
?
'edit'
:
'view'
}
mode=
{
isEditing
?
'edit'
:
'view'
}
onFieldChange=
{
handleDraftFieldChange
}
onFieldChange=
{
handleDraftFieldChange
}
onFieldValidation=
{
handleFieldValidation
}
onFieldValidation=
{
handleFieldValidation
}
onPendingLocalMaterialChange=
{
handlePendingLocalMaterialChange
}
readonly=
{
effectiveReadonly
}
readonly=
{
effectiveReadonly
}
isFieldDisabled=
{
isDrawerFieldDisabled
}
isFieldDisabled=
{
isDrawerFieldDisabled
}
minimal=
{
minimalContent
}
minimal=
{
minimalContent
}
useAuditMaterialCenter=
{
useAuditMaterialCenter
}
/>
/>
</
div
>
</
div
>
)
}
)
}
...
...
src/pages/problem_rectification/components/ProblemRectificationMaterialField.jsx
0 → 100644
View file @
a2e249fe
import
{
ArrowPathIcon
,
ChevronDown
,
Close
,
Delete
,
FIELD_SUFFIX_ICON_STYLE
,
Search
,
}
from
'@/components/AppIcons'
;
import
FileUploadField
from
'@/components/FileUploadField'
;
import
{
queryAuditMaterials
,
uploadProblemRectificationFile
,
}
from
'../problemRectificationMaterialService'
;
import
{
Alert
,
Autocomplete
,
Button
,
Label
,
ListBox
,
SearchField
,
Spinner
,
Tabs
,
Tooltip
,
}
from
'@heroui/react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
const
MATERIAL_CENTER_TAB
=
'material-center'
;
const
LOCAL_UPLOAD_TAB
=
'local-upload'
;
function
formatMaterialDescription
(
material
)
{
return
[
material
.
fileAsset
?.
name
,
material
.
archiveCategory
,
material
.
archiveYear
,
].
filter
(
Boolean
).
join
(
' · '
);
}
export
default
function
ProblemRectificationMaterialField
({
editSessionId
,
fieldPath
,
onChange
,
onPendingLocalMaterialChange
,
onValidationChange
,
value
,
})
{
const
[
activeTab
,
setActiveTab
]
=
useState
(
MATERIAL_CENTER_TAB
);
const
[
keyword
,
setKeyword
]
=
useState
(
''
);
const
[
debouncedKeyword
,
setDebouncedKeyword
]
=
useState
(
''
);
const
[
materials
,
setMaterials
]
=
useState
([]);
const
[
selectedMaterialId
,
setSelectedMaterialId
]
=
useState
(
null
);
const
[
materialsLoading
,
setMaterialsLoading
]
=
useState
(
false
);
const
[
materialsError
,
setMaterialsError
]
=
useState
(
''
);
const
[
retryKey
,
setRetryKey
]
=
useState
(
0
);
const
[
uploading
,
setUploading
]
=
useState
(
false
);
const
[
uploadError
,
setUploadError
]
=
useState
(
''
);
const
[
localFileAsset
,
setLocalFileAsset
]
=
useState
(
null
);
useEffect
(()
=>
{
setActiveTab
(
MATERIAL_CENTER_TAB
);
setKeyword
(
''
);
setDebouncedKeyword
(
''
);
setSelectedMaterialId
(
null
);
setMaterialsError
(
''
);
setUploadError
(
''
);
setLocalFileAsset
(
null
);
},
[
editSessionId
]);
useEffect
(()
=>
{
const
timeoutId
=
window
.
setTimeout
(()
=>
{
setDebouncedKeyword
(
keyword
);
},
300
);
return
()
=>
window
.
clearTimeout
(
timeoutId
);
},
[
keyword
]);
useEffect
(()
=>
{
if
(
activeTab
!==
MATERIAL_CENTER_TAB
)
return
undefined
;
let
cancelled
=
false
;
setMaterialsLoading
(
true
);
setMaterialsError
(
''
);
queryAuditMaterials
(
debouncedKeyword
)
.
then
((
nextMaterials
)
=>
{
if
(
!
cancelled
)
setMaterials
(
nextMaterials
);
})
.
catch
((
error
)
=>
{
if
(
!
cancelled
)
{
setMaterials
([]);
setMaterialsError
(
error
?.
message
||
'审计材料加载失败,请稍后重试。'
);
}
})
.
finally
(()
=>
{
if
(
!
cancelled
)
setMaterialsLoading
(
false
);
});
return
()
=>
{
cancelled
=
true
;
};
},
[
activeTab
,
debouncedKeyword
,
retryKey
]);
const
selectedMaterial
=
useMemo
(
()
=>
materials
.
find
((
material
)
=>
material
.
id
===
selectedMaterialId
),
[
materials
,
selectedMaterialId
],
);
const
selectedValueText
=
selectedMaterial
?.
materialTitle
||
value
?.
name
||
'请选择审计材料'
;
const
handleMaterialSelection
=
(
key
)
=>
{
const
materialId
=
key
?
String
(
key
)
:
''
;
const
material
=
materials
.
find
((
item
)
=>
String
(
item
.
id
)
===
materialId
);
if
(
!
material
?.
fileAsset
?.
url
)
return
;
setSelectedMaterialId
(
material
.
id
);
setLocalFileAsset
(
null
);
setUploadError
(
''
);
onPendingLocalMaterialChange
?.(
fieldPath
,
null
,
editSessionId
);
onChange
?.({
name
:
material
.
fileAsset
.
name
,
type
:
material
.
fileAsset
.
type
||
'application/octet-stream'
,
size
:
Number
(
material
.
fileAsset
.
size
||
0
),
url
:
material
.
fileAsset
.
url
,
});
};
const
handleLocalUpload
=
async
(
files
)
=>
{
const
file
=
files
?.[
0
];
if
(
!
file
)
return
;
setUploading
(
true
);
setUploadError
(
''
);
onValidationChange
?.(
fieldPath
,
'附件正在上传,请稍候。'
,
editSessionId
);
try
{
const
fileAsset
=
await
uploadProblemRectificationFile
(
file
);
setLocalFileAsset
(
fileAsset
);
setSelectedMaterialId
(
null
);
onPendingLocalMaterialChange
?.(
fieldPath
,
{
fileAsset
,
materialRecordId
:
null
},
editSessionId
,
);
onChange
?.(
fileAsset
);
}
catch
(
error
)
{
setUploadError
(
error
?.
message
||
'附件上传失败,请稍后重试。'
);
}
finally
{
setUploading
(
false
);
onValidationChange
?.(
fieldPath
,
''
,
editSessionId
);
}
};
const
handleRemove
=
()
=>
{
setSelectedMaterialId
(
null
);
setLocalFileAsset
(
null
);
setUploadError
(
''
);
onPendingLocalMaterialChange
?.(
fieldPath
,
null
,
editSessionId
);
onChange
?.(
null
);
};
return
(
<
div
className=
"space-y-4"
>
{
value
?.
name
||
value
?.
url
?
(
<
div
className=
"flex min-w-0 items-center justify-between gap-3"
>
<
div
className=
"min-w-0"
>
<
div
className=
"truncate text-sm font-medium text-foreground"
title=
{
value
.
name
||
value
.
url
}
>
{
value
.
name
||
value
.
url
}
</
div
>
<
div
className=
"mt-1 text-xs text-muted"
>
{
localFileAsset
?
'本地文件,保存后同步至审计材料中心'
:
'已选择附件'
}
</
div
>
</
div
>
<
Tooltip
>
<
Button
aria
-
label=
"删除当前附件"
isIconOnly
size=
"sm"
variant=
"danger-soft"
onPress=
{
handleRemove
}
>
<
Delete
size=
{
16
}
/>
</
Button
>
<
Tooltip
.
Content
>
删除当前附件
</
Tooltip
.
Content
>
</
Tooltip
>
</
div
>
)
:
(
<
span
className=
"text-sm text-muted"
>
未上传
</
span
>
)
}
<
Tabs
aria
-
label=
"附件来源"
className=
"w-fit max-w-full min-w-0"
selectedKey=
{
activeTab
}
variant=
"secondary"
onSelectionChange=
{
(
key
)
=>
setActiveTab
(
String
(
key
))
}
>
<
Tabs
.
ListContainer
className=
"w-fit max-w-full min-w-0"
>
<
Tabs
.
List
className=
"w-max min-w-max"
>
<
Tabs
.
Tab
className=
"w-auto min-w-max flex-none whitespace-nowrap"
id=
{
MATERIAL_CENTER_TAB
}
>
材料中心
<
Tabs
.
Indicator
/>
</
Tabs
.
Tab
>
<
Tabs
.
Tab
className=
"w-auto min-w-max flex-none whitespace-nowrap"
id=
{
LOCAL_UPLOAD_TAB
}
>
本地上传
<
Tabs
.
Indicator
/>
</
Tabs
.
Tab
>
</
Tabs
.
List
>
</
Tabs
.
ListContainer
>
<
Tabs
.
Panel
className=
"pt-3"
id=
{
MATERIAL_CENTER_TAB
}
>
<
div
className=
"space-y-3"
>
<
Autocomplete
aria
-
label=
"选择审计材料"
fullWidth
selectedKey=
{
selectedMaterialId
}
selectionMode=
"single"
variant=
"secondary"
onSelectionChange=
{
handleMaterialSelection
}
>
<
Autocomplete
.
Trigger
>
<
Autocomplete
.
Value
>
<
span
className=
"block min-w-0 truncate"
title=
{
selectedValueText
}
>
{
selectedValueText
}
</
span
>
</
Autocomplete
.
Value
>
<
Autocomplete
.
Indicator
>
<
ChevronDown
size=
{
16
}
style=
{
FIELD_SUFFIX_ICON_STYLE
}
/>
</
Autocomplete
.
Indicator
>
</
Autocomplete
.
Trigger
>
<
Autocomplete
.
Popover
placement=
"bottom start"
>
<
Autocomplete
.
Filter
filter=
{
()
=>
true
}
inputValue=
{
keyword
}
onInputChange=
{
setKeyword
}
>
<
SearchField
aria
-
label=
"搜索审计材料"
fullWidth
variant=
"secondary"
>
<
SearchField
.
Group
>
<
SearchField
.
SearchIcon
><
Search
size=
{
16
}
/></
SearchField
.
SearchIcon
>
<
SearchField
.
Input
autoFocus
placeholder=
"搜索材料标题或文件名"
/>
<
SearchField
.
ClearButton
aria
-
label=
"清空搜索"
><
Close
size=
{
14
}
/></
SearchField
.
ClearButton
>
</
SearchField
.
Group
>
</
SearchField
>
<
ListBox
aria
-
label=
"审计材料列表"
items=
{
materialsLoading
||
materialsError
?
[]
:
materials
}
renderEmptyState=
{
()
=>
(
<
div
className=
"grid min-h-24 place-items-center px-4 py-5 text-sm text-muted"
>
{
materialsLoading
?
<
Spinner
size=
"sm"
/>
:
materialsError
||
'没有找到可选择的审计材料'
}
</
div
>
)
}
>
{
(
material
)
=>
(
<
ListBox
.
Item
id=
{
material
.
id
}
textValue=
{
`${material.materialTitle} ${material.fileAsset?.name || ''}`
}
>
<
span
className=
"min-w-0"
>
<
Label
className=
"block truncate"
title=
{
material
.
materialTitle
}
>
{
material
.
materialTitle
}
</
Label
>
<
span
className=
"mt-0.5 block truncate text-xs text-muted"
title=
{
formatMaterialDescription
(
material
)
}
>
{
formatMaterialDescription
(
material
)
}
</
span
>
</
span
>
<
ListBox
.
ItemIndicator
/>
</
ListBox
.
Item
>
)
}
</
ListBox
>
</
Autocomplete
.
Filter
>
</
Autocomplete
.
Popover
>
</
Autocomplete
>
{
materialsError
?
(
<
Alert
status=
"danger"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
materialsError
}
</
Alert
.
Description
>
</
Alert
.
Content
>
<
Button
size=
"sm"
variant=
"danger-soft"
onPress=
{
()
=>
setRetryKey
((
key
)
=>
key
+
1
)
}
>
<
ArrowPathIcon
size=
{
14
}
/>
重试
</
Button
>
</
Alert
>
)
:
null
}
</
div
>
</
Tabs
.
Panel
>
<
Tabs
.
Panel
className=
"pt-3"
id=
{
LOCAL_UPLOAD_TAB
}
>
<
div
className=
"space-y-3"
>
<
FileUploadField
description=
"上传后先保留为详情草稿,保存详情时同步写入审计材料中心。"
fileName=
{
localFileAsset
?.
name
||
''
}
isDisabled=
{
uploading
}
triggerLabel=
{
uploading
?
'上传中...'
:
'选择本地文件'
}
onFilesChange=
{
handleLocalUpload
}
/>
{
uploadError
?
(
<
Alert
status=
"danger"
>
<
Alert
.
Content
><
Alert
.
Description
>
{
uploadError
}
</
Alert
.
Description
></
Alert
.
Content
>
</
Alert
>
)
:
null
}
</
div
>
</
Tabs
.
Panel
>
</
Tabs
>
</
div
>
);
}
src/pages/problem_rectification/list/index.jsx
View file @
a2e249fe
...
@@ -1872,6 +1872,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1872,6 +1872,7 @@ export default function ProblemRectificationListPage() {
}}
}}
preferredViewName={activeGridView?.title || activeGridView?.name}
preferredViewName={activeGridView?.title || activeGridView?.name}
authorizationView={activeGridView}
authorizationView={activeGridView}
useAuditMaterialCenter
/>
/>
),
),
}}
}}
...
...
src/pages/problem_rectification/problem-rectification.business-flow.md
View file @
a2e249fe
...
@@ -142,7 +142,13 @@
...
@@ -142,7 +142,13 @@
-
`problem_rectification_archive_records`
:归档后的问题快照明细
-
`problem_rectification_archive_records`
:归档后的问题快照明细
-
`issue_rectification_archive`
:一次归档批次主单
-
`issue_rectification_archive`
:一次归档批次主单
### 4.3 流程辅助对象
### 4.3 审计材料共享对象
-
`am_material`
:审计材料中心共享主档。问题清单详情可查询已有主档;从本地上传证明材料并保存详情时同步创建主档
问题记录仍在
`metadata.rectification_proof_materials`
保存
`{name,type,size,url}`
文件快照,不保存材料主档
`_id`
,不建立自动联动关系。
### 4.4 流程辅助对象
-
`message_reference_id`
(信息关联id):旧应用中用于承载“推送填写、复核、最终审核”状态的消息流程主单
-
`message_reference_id`
(信息关联id):旧应用中用于承载“推送填写、复核、最终审核”状态的消息流程主单
...
@@ -745,9 +751,18 @@
...
@@ -745,9 +751,18 @@
-
使用抽屉或对话框,而不是表格内直接改单元格
-
使用抽屉或对话框,而不是表格内直接改单元格
-
基础信息与整改信息分区展示
-
基础信息与整改信息分区展示
-
附件
上传单独区域展示
-
附件
在单独区域展示,编辑时默认进入“材料中心”,也可切换到“本地上传”
-
状态、责任人、计划时间等字段用结构化控件维护
-
状态、责任人、计划时间等字段用结构化控件维护
整改证明材料交互限定在问题清单详情弹框,不改变整改处理页、整改流程表格和归档详情:
1.
“材料中心”首次读取当前用户可访问的最近 20 条
`am_material`
,支持按材料标题或文件名远程搜索;选中后仅把
`metadata.file_asset`
快照写入详情草稿。
2.
“本地上传”选择文件后立即调用
`/upload`
,上传期间禁止保存;成功后将文件对象写入详情草稿,并提示保存后同步到材料中心。
3.
用户点击详情“保存”时,先创建
`am_material`
,写入
`source_app = issue_rectification`
、当前用户
`_id`
和 ISO 上传时间,再更新
`problem_rectification.metadata.rectification_proof_materials`
。
4.
用户取消编辑时不创建材料主档;选择已有材料不重复上传、不创建新主档。
5.
材料创建失败时保留编辑草稿且不更新问题;材料创建成功但问题更新失败时保留已建档标记,再次保存只重试问题更新,避免重复材料。
6.
清空或替换问题附件不删除材料中心主档或底层文件;已有材料后续修改或软删除不联动历史问题快照。
建议字段分组:
建议字段分组:
-
基础信息
-
基础信息
...
...
src/pages/problem_rectification/problemRectificationMaterialService.js
0 → 100644
View file @
a2e249fe
import
dayjs
from
'dayjs'
;
import
{
createRecord
,
queryRecords
,
uploadFile
}
from
'@/services/DataService'
;
export
const
AUDIT_MATERIAL_SUBJECT
=
'am_material'
;
export
const
ISSUE_RECTIFICATION_SOURCE_APP
=
'issue_rectification'
;
const
MATERIAL_PAGE_SIZE
=
20
;
function
extractRecords
(
response
)
{
const
candidates
=
[
response
,
response
?.
data
,
response
?.
data
?.
list
,
response
?.
data
?.
records
,
response
?.
list
,
response
?.
records
,
response
?.
items
,
response
?.
result
,
response
?.
result
?.
records
,
];
return
candidates
.
find
((
candidate
)
=>
Array
.
isArray
(
candidate
))
||
[];
}
function
escapeRegExp
(
value
=
''
)
{
return
String
(
value
).
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&'
);
}
function
inferMaterialTitle
(
fileName
=
''
)
{
return
String
(
fileName
||
''
).
replace
(
/
\.[^
.
]
+$/
,
''
).
trim
()
||
'未命名材料'
;
}
function
normalizeFileAsset
(
uploadResult
,
file
)
{
const
candidates
=
[
uploadResult
?.
file
,
uploadResult
?.
data
?.
file
,
uploadResult
?.
data
,
uploadResult
?.
result
?.
file
,
uploadResult
?.
result
,
uploadResult
,
];
for
(
const
candidate
of
candidates
)
{
if
(
!
candidate
||
typeof
candidate
!==
'object'
)
continue
;
const
url
=
candidate
.
url
||
candidate
.
path
||
candidate
.
location
||
candidate
?.
data
?.
url
||
''
;
if
(
!
url
)
continue
;
return
{
name
:
candidate
.
name
||
candidate
.
filename
||
file
?.
name
||
'未命名文件'
,
type
:
candidate
.
type
||
candidate
.
mimetype
||
file
?.
type
||
'application/octet-stream'
,
size
:
Number
(
candidate
.
size
||
file
?.
size
||
0
),
url
,
};
}
throw
new
Error
(
'文件上传成功,但未获取到可用的文件地址。'
);
}
function
normalizeMaterial
(
record
=
{})
{
const
metadata
=
record
?.
metadata
||
{};
const
fileAsset
=
metadata
.
file_asset
||
{};
return
{
id
:
record
.
_id
||
record
.
id
||
record
.
name
||
''
,
materialTitle
:
metadata
.
material_title
||
record
.
title
||
fileAsset
.
name
||
'未命名材料'
,
fileAsset
,
archiveCategory
:
metadata
.
archive_category
||
''
,
archiveYear
:
metadata
.
archive_year
||
''
,
sourceApp
:
metadata
.
source_app
||
''
,
uploadedAt
:
metadata
.
uploaded_at
||
record
.
created_at
||
''
,
};
}
export
async
function
queryAuditMaterials
(
keyword
=
''
)
{
const
normalizedKeyword
=
String
(
keyword
||
''
).
trim
();
const
escapedKeyword
=
escapeRegExp
(
normalizedKeyword
);
const
filter
=
escapedKeyword
?
{
$or
:
[
{
'metadata.material_title'
:
{
$regex
:
escapedKeyword
,
$options
:
'i'
,
},
},
{
'metadata.file_asset.name'
:
{
$regex
:
escapedKeyword
,
$options
:
'i'
,
},
},
],
}
:
{};
const
response
=
await
queryRecords
({
subject
:
AUDIT_MATERIAL_SUBJECT
,
page
:
0
,
page_size
:
MATERIAL_PAGE_SIZE
,
filter
,
sort
:
'-updated_at -created_at'
,
mode
:
'full'
,
});
return
extractRecords
(
response
)
.
map
(
normalizeMaterial
)
.
filter
((
material
)
=>
material
.
id
&&
material
.
fileAsset
?.
url
);
}
export
async
function
uploadProblemRectificationFile
(
file
)
{
if
(
!
file
)
{
throw
new
Error
(
'请选择要上传的本地文件。'
);
}
const
uploadResult
=
await
uploadFile
(
file
);
return
normalizeFileAsset
(
uploadResult
,
file
);
}
export
async
function
createProblemRectificationMaterial
({
fileAsset
,
currentUser
})
{
if
(
!
fileAsset
?.
url
)
{
throw
new
Error
(
'缺少有效的文件地址,无法写入审计材料中心。'
);
}
const
uploaderId
=
currentUser
?.
_id
||
currentUser
?.
id
||
''
;
if
(
!
uploaderId
)
{
throw
new
Error
(
'未获取到当前登录用户,无法写入审计材料中心。'
);
}
const
materialTitle
=
inferMaterialTitle
(
fileAsset
.
name
);
const
response
=
await
createRecord
({
subject
:
AUDIT_MATERIAL_SUBJECT
,
title
:
materialTitle
,
content_type
:
'text'
,
metadata
:
{
material_title
:
materialTitle
,
file_asset
:
{
name
:
fileAsset
.
name
,
type
:
fileAsset
.
type
||
'application/octet-stream'
,
size
:
Number
(
fileAsset
.
size
||
0
),
url
:
fileAsset
.
url
,
},
source_app
:
ISSUE_RECTIFICATION_SOURCE_APP
,
uploader
:
uploaderId
,
uploaded_at
:
dayjs
().
toISOString
(),
},
});
const
createdRecord
=
response
?.
data
||
response
?.
result
||
response
;
return
{
id
:
createdRecord
?.
_id
||
createdRecord
?.
id
||
createdRecord
?.
name
||
true
,
record
:
createdRecord
,
};
}
src/services/record-api.md
View file @
a2e249fe
# Record 接口文档
# Record 接口文档
**最后更新**
:2026 年
3 月
**最后更新**
:2026 年
8 月
**基于代码实现整理**
**基于代码实现整理**
参考文件:
参考文件:
-
`src/routers/record.js`
-
`src/routers/record.js`
...
@@ -267,5 +267,67 @@ Query:`calculated=true|false`
...
@@ -267,5 +267,67 @@ Query:`calculated=true|false`
6.
**软删除 vs 物理删除**
:默认软删除(status=removed),需显式
`force`
才物理删除。
6.
**软删除 vs 物理删除**
:默认软删除(status=removed),需显式
`force`
才物理删除。
7.
**导入接口**
:支持 name 自动转 id,但仅限同一 subject 内查找。
7.
**导入接口**
:支持 name 自动转 id,但仅限同一 subject 内查找。
如需补充特定 subject 的 metadata 字段规范、文件上传流程、权限点详细对应表等,请继续提供信息,我可以进一步扩展对应章节。
## 5. 问题详情与审计材料中心接入
问题清单详情弹框中的
`metadata.rectification_proof_materials`
支持从统一材料池选择已有文件,或上传本地文件后同步建档。整改处理页、整改流程表格和归档详情不使用本节流程。
### 5.1 查询已有材料
调用
`POST /record/query`
,按当前用户的 Record 访问权限返回有效材料:
```
json
{
"subject"
:
"am_material"
,
"page"
:
0
,
"page_size"
:
20
,
"filter"
:
{
"$or"
:
[
{
"metadata.material_title"
:
{
"$regex"
:
"整改说明"
,
"$options"
:
"i"
}
},
{
"metadata.file_asset.name"
:
{
"$regex"
:
"整改说明"
,
"$options"
:
"i"
}
}
]
},
"sort"
:
"-updated_at -created_at"
,
"mode"
:
"full"
}
```
```
搜索关键词必须先转义正则特殊字符。选中材料后,只将
`metadata.file_asset`
的
`{name,type,size,url}`
快照写入问题详情草稿,不保存
`am_material`
`_id`
。
### 5.2 本地上传并写入材料中心
1.
选择文件后立即调用
`POST /upload`
,将返回结果规范化为
`{name,type,size,url}`
并保留在详情草稿。
2.
用户点击详情“保存”时调用
`POST /record`
创建材料主档:
```
json
{
"subject"
:
"am_material"
,
"title"
:
"整改说明"
,
"content_type"
:
"text"
,
"metadata"
:
{
"material_title"
:
"整改说明"
,
"file_asset"
:
{
"name"
:
"整改说明.pdf"
,
"type"
:
"application/pdf"
,
"size"
:
204800
,
"url"
:
"https://example.com/files/proof.pdf"
},
"source_app"
:
"issue_rectification"
,
"uploader"
:
"69b12a47e92b1de77c94a9f5"
,
"uploaded_at"
:
"2026-08-25T08:30:00.000Z"
}
}
```
3.
材料主档创建成功后,再调用
`PUT /record/:name`
更新问题记录的
`metadata.rectification_proof_materials`
。
4.
材料创建失败时不更新问题记录;材料创建成功但问题更新失败时,当前编辑会话记住已创建的材料,重试保存不得重复创建。
5.
取消详情编辑不创建
`am_material`
;已经上传但未建档的二进制文件遵循上传服务自身的清理策略。
src/services/subjectsDefinition.md
View file @
a2e249fe
...
@@ -4,10 +4,10 @@
...
@@ -4,10 +4,10 @@
本文件定义当前项目使用的 Subject(业务对象)字段协议,供前端页面、服务层、动态表单与 AI 代码生成使用。
本文件定义当前项目使用的 Subject(业务对象)字段协议,供前端页面、服务层、动态表单与 AI 代码生成使用。
当前项目
当前涉及两个业务模块
:
当前项目
涉及以下业务对象
:
-
`pm_project_task`
:项目管理
-
`problem_rectification`
:问题整改
-
`problem_rectification`
:问题整改
-
`am_material`
:审计材料中心共享主档,仅供问题详情查询和本地附件同步建档
---
---
...
@@ -187,6 +187,7 @@ type Record = {
...
@@ -187,6 +187,7 @@ type Record = {
-
新增驾驶舱扩展字段当前在线上 Subject 中大多仍为
`text`
类型,前端工作台按标准值引导录入,用于支持后续严重程度、问题分类、问题原因和整改质效分析
-
新增驾驶舱扩展字段当前在线上 Subject 中大多仍为
`text`
类型,前端工作台按标准值引导录入,用于支持后续严重程度、问题分类、问题原因和整改质效分析
-
其余
`select`
类型的可选值必须以线上 Subject 配置中的
`settings.options`
为准,禁止在页面内硬编码
-
其余
`select`
类型的可选值必须以线上 Subject 配置中的
`settings.options`
为准,禁止在页面内硬编码
-
`problem_discovery_situation`
曾在历史文档和归档对象中出现,但本次主对象字段定义未包含;新页面只应在 Subject View 或线上字段配置存在时展示,不能作为
`problem_rectification`
固定字段依赖
-
`problem_discovery_situation`
曾在历史文档和归档对象中出现,但本次主对象字段定义未包含;新页面只应在 Subject View 或线上字段配置存在时展示,不能作为
`problem_rectification`
固定字段依赖
-
问题清单详情弹框中的
`rectification_proof_materials`
支持从
`am_material.metadata.file_asset`
复制文件快照,或上传本地文件后创建
`am_material`
。问题字段仍只保存
`{name,type,size,url}`
,不保存材料主档
`_id`
,材料后续修改或软删除不反向联动问题记录
-
`department_rectification_items`
用于新转发流程的部门级整改明细。无论单条还是多条推送,每个问题都按责任部门生成明细;单条问题即使只有 1 个责任部门,也生成 1 条明细。已推送过的问题再次推送时,必须保留原部门明细记录和
`item_id`
,只在本次参与推送的原明细行上更新填写人、审批人、流程状态和待办 ID;未参与本次推送的原明细行保持不变,不因弹窗删除部门配置而从父记录移除。转发后
`rectification_progress`
、
`rectification_situation`
、
`the_time_of_rectification`
、
`rectification_proof_materials`
、
`audit_status`
、
`final_audit_status`
以明细为事实来源,父字段仅做兼容汇总展示;整改进度、审批状态和最终审批状态按部门明细中最落后的状态汇总,例如“整改中”优先于“已整改”,“未审批”优先于“已审批”
-
`department_rectification_items`
用于新转发流程的部门级整改明细。无论单条还是多条推送,每个问题都按责任部门生成明细;单条问题即使只有 1 个责任部门,也生成 1 条明细。已推送过的问题再次推送时,必须保留原部门明细记录和
`item_id`
,只在本次参与推送的原明细行上更新填写人、审批人、流程状态和待办 ID;未参与本次推送的原明细行保持不变,不因弹窗删除部门配置而从父记录移除。转发后
`rectification_progress`
、
`rectification_situation`
、
`the_time_of_rectification`
、
`rectification_proof_materials`
、
`audit_status`
、
`final_audit_status`
以明细为事实来源,父字段仅做兼容汇总展示;整改进度、审批状态和最终审批状态按部门明细中最落后的状态汇总,例如“整改中”优先于“已整改”,“未审批”优先于“已审批”
-
`problem_rectification_delay_request`
为历史问题级单次延期申请嵌套对象,与
`department_rectification_items`
平级;新发起延期默认按部门写入
`problem_rectification_delay_request_items`
-
`problem_rectification_delay_request`
为历史问题级单次延期申请嵌套对象,与
`department_rectification_items`
平级;新发起延期默认按部门写入
`problem_rectification_delay_request_items`
-
`problem_rectification_delay_request_items`
为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID
-
`problem_rectification_delay_request_items`
为部门级延期申请明细数组;每个责任部门 1 条延期申请,独立保存填写人、一级审批人、最终审批人、申请完成时间、延期事由、状态和待办 ID
...
@@ -290,7 +291,26 @@ type Record = {
...
@@ -290,7 +291,26 @@ type Record = {
}
}
```
```
## 3.2 问题整改归档明细
## 3.2 审计材料中心共享主档
-
**subject**
:
`am_material`
本项目只使用该对象完成问题详情的材料查询和本地附件同步建档,不修改线上 Subject 配置。
| 字段标签 | 字段别名 | 字段类型 | 是否必填 | 问题详情写入规则 |
| --- | --- | --- | --- | --- |
| 材料标题 |
`material_title`
| text | 是 | 默认取文件名去后缀 |
| 文件对象 |
`file_asset`
| file | 是 |
`{name,type,size,url}`
|
| 来源应用 |
`source_app`
| text | 否 | 固定写入
`issue_rectification`
|
| 上传人 |
`uploader`
| user | 是 | 当前登录用户
`_id`
|
| 上传时间 |
`uploaded_at`
| date | 是 | ISO 8601 字符串 |
| 来源系统 |
`source_system`
| select | 否 | 问题详情不写入 |
| 材料分类 |
`archive_category`
| select | 否 | 问题详情不写入 |
| 所属周期/年份 |
`archive_year`
| text | 否 | 问题详情不写入 |
问题详情选择已有材料时读取
`material_title`
与
`file_asset`
;搜索范围为
`metadata.material_title`
和
`metadata.file_asset.name`
。列表只展示当前登录用户通过 Record 权限可读取且
`file_asset.url`
有效的记录。
## 3.3 问题整改归档明细
-
**subject**
:
`problem_rectification_archive_records`
-
**subject**
:
`problem_rectification_archive_records`
...
@@ -319,14 +339,14 @@ type Record = {
...
@@ -319,14 +339,14 @@ type Record = {
| 检查项目名称 |
`project_name`
| text | 否 | 否 | 否 |
| 检查项目名称 |
`project_name`
| text | 否 | 否 | 否 |
| 整改证明材料 |
`rectification_proof_materials`
| file | 否 | 否 | 否 |
| 整改证明材料 |
`rectification_proof_materials`
| file | 否 | 否 | 否 |
### 3.
2
.1 归档写入约定
### 3.
3
.1 归档写入约定
-
归档对象存的是快照,不再要求保留原来的
`select`
选项结构
-
归档对象存的是快照,不再要求保留原来的
`select`
选项结构
-
旧应用归档时会把
`responsible_department`
转为逗号拼接文本
-
旧应用归档时会把
`responsible_department`
转为逗号拼接文本
-
归档记录仍需保留
`fill_in_the_people`
、
`review_people`
的用户
`_id[]`
-
归档记录仍需保留
`fill_in_the_people`
、
`review_people`
的用户
`_id[]`
-
`the_time_of_rectification`
继续按 ISO 字符串写入
-
`the_time_of_rectification`
继续按 ISO 字符串写入
## 3.
3
问题整改归档批次
## 3.
4
问题整改归档批次
-
**subject**
:
`issue_rectification_archive`
-
**subject**
:
`issue_rectification_archive`
...
@@ -336,7 +356,7 @@ type Record = {
...
@@ -336,7 +356,7 @@ type Record = {
| --- | --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- | --- |
| 归档明细记录 |
`reference_records`
| reference | 否 | 是 | 是 |
| 归档明细记录 |
`reference_records`
| reference | 否 | 是 | 是 |
### 3.
3
.1 metadata 示例
### 3.
4
.1 metadata 示例
```
json
```
json
{
{
...
@@ -347,7 +367,7 @@ type Record = {
...
@@ -347,7 +367,7 @@ type Record = {
}
}
```
```
## 3.
4
信息关联id(问题整改流程消息主单)
## 3.
5
信息关联id(问题整改流程消息主单)
-
**subject**
:
`message_reference_id`
-
**subject**
:
`message_reference_id`
-
**对象名称**
: 信息关联id
-
**对象名称**
: 信息关联id
...
@@ -373,7 +393,7 @@ type Record = {
...
@@ -373,7 +393,7 @@ type Record = {
| 审核状态 |
`auditor_status`
| select | 否 | 否 | 否 |
| 审核状态 |
`auditor_status`
| select | 否 | 否 | 否 |
| 发送人状态 |
`sender_status`
| select | 否 | 否 | 否 |
| 发送人状态 |
`sender_status`
| select | 否 | 否 | 否 |
### 3.
4
.1 当前流程写入约定
### 3.
5
.1 当前流程写入约定
-
`restriction_key`
默认写
`_id`
-
`restriction_key`
默认写
`_id`
-
`restriction_value`
保存被推送整改记录的
`_id[]`
-
`restriction_value`
保存被推送整改记录的
`_id[]`
...
@@ -388,7 +408,7 @@ type Record = {
...
@@ -388,7 +408,7 @@ type Record = {
-
`sender_status`
:当前项目统一使用
`未完成`
、
`已完成`
-
`sender_status`
:当前项目统一使用
`未完成`
、
`已完成`
-
当批量流程复核或最终审核驳回时,流程主单
`editor_status`
重置为
`未完成`
并回写新的
`progress_id_editor`
,同一批次的全部问题共同重新进入填写阶段
-
当批量流程复核或最终审核驳回时,流程主单
`editor_status`
重置为
`未完成`
并回写新的
`progress_id_editor`
,同一批次的全部问题共同重新进入填写阶段
## 3.
5
项目管理
## 3.
6
项目管理
-
**subject**
:
`pm_project_task`
-
**subject**
:
`pm_project_task`
...
@@ -414,7 +434,7 @@ type Record = {
...
@@ -414,7 +434,7 @@ type Record = {
| 延期原因 |
`delay_reason`
| text | 否 | 否 | 否 |
| 延期原因 |
`delay_reason`
| text | 否 | 否 | 否 |
| 周进度记录 |
`weekly_updates`
| object | 否 | 是 | 否 |
| 周进度记录 |
`weekly_updates`
| object | 否 | 是 | 否 |
### 3.
5
.1 `project_status` 选项
### 3.
6
.1 `project_status` 选项
| 显示值 | 存储值 |
| 显示值 | 存储值 |
| --- | --- |
| --- | --- |
...
@@ -425,7 +445,7 @@ type Record = {
...
@@ -425,7 +445,7 @@ type Record = {
| 受阻 |
`blocked`
|
| 受阻 |
`blocked`
|
| 已取消 |
`cancelled`
|
| 已取消 |
`cancelled`
|
### 3.
5
.2 `priority` 建议选项
### 3.
6
.2 `priority` 建议选项
| 显示值 | 存储值 |
| 显示值 | 存储值 |
| --- | --- |
| --- | --- |
...
@@ -433,7 +453,7 @@ type Record = {
...
@@ -433,7 +453,7 @@ type Record = {
| 中 |
`medium`
|
| 中 |
`medium`
|
| 低 |
`low`
|
| 低 |
`low`
|
### 3.
5
.3 `result_status` 建议选项
### 3.
6
.3 `result_status` 建议选项
| 显示值 | 存储值 |
| 显示值 | 存储值 |
| --- | --- |
| --- | --- |
...
@@ -444,7 +464,7 @@ type Record = {
...
@@ -444,7 +464,7 @@ type Record = {
| 准时完成 |
`completed_on_time`
|
| 准时完成 |
`completed_on_time`
|
| 超时完成 |
`completed_overdue`
|
| 超时完成 |
`completed_overdue`
|
### 3.
5
.4 `weekly_updates` 子结构建议
### 3.
6
.4 `weekly_updates` 子结构建议
`weekly_updates`
为对象数组,建议每项至少包含以下字段:
`weekly_updates`
为对象数组,建议每项至少包含以下字段:
...
@@ -465,7 +485,7 @@ type Record = {
...
@@ -465,7 +485,7 @@ type Record = {
-
周进度记录通过“新增本周进度 / 编辑周进度”弹窗录入
-
周进度记录通过“新增本周进度 / 编辑周进度”弹窗录入
-
列表页或项目详情只展示最近一条摘要,不直接暴露底层 JSON
-
列表页或项目详情只展示最近一条摘要,不直接暴露底层 JSON
### 3.
5
.5 metadata 示例
### 3.
6
.5 metadata 示例
```
json
```
json
{
{
...
...
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