Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
R
risk_case_library
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
risk_case_library
Commits
06f0d7dd
Commit
06f0d7dd
authored
Jun 08, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update: 筛选优化
parent
7b7c281e
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
86 additions
and
65 deletions
+86
-65
audit-material-center.business-flow.md
...project_manager_xy/audit-material-center.business-flow.md
+35
-19
materialService.js
src/pages/project_manager_xy/materialService.js
+51
-46
index.jsx
src/pages/project_manager_xy/resource-booking/index.jsx
+0
-0
No files found.
src/pages/project_manager_xy/audit-material-center.business-flow.md
View file @
06f0d7dd
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
-
导入线下 Excel 维护的案例数据
-
导入线下 Excel 维护的案例数据
-
支持手动创建、编辑、删除案例
-
支持手动创建、编辑、删除案例
-
提供更直观的案例浏览体验
-
提供更直观的案例浏览体验
-
支持按
关键词、案例性质、案例来源、案例等级快速
检索典型案例
-
支持按
全文关键词、案例来源、案例类型、问题属性、分类标签、案例性质等多字段组合
检索典型案例
-
为培训、复盘、经验沉淀提供统一入口
-
为培训、复盘、经验沉淀提供统一入口
当前阶段的核心原则是:
当前阶段的核心原则是:
...
@@ -33,7 +33,7 @@
...
@@ -33,7 +33,7 @@
-
展示关键字词云与最近更新案例
-
展示关键字词云与最近更新案例
-
提供对库内内容密度和结构完整度的快速判断
-
提供对库内内容密度和结构完整度的快速判断
-
`案例管理`
-
`案例管理`
-
搜索、
筛选、浏览案例卡片
-
多字段
筛选、浏览案例卡片
-
手动创建案例
-
手动创建案例
-
Excel 批量导入案例
-
Excel 批量导入案例
-
查看案例详情并编辑
-
查看案例详情并编辑
...
@@ -101,7 +101,7 @@
...
@@ -101,7 +101,7 @@
1.
用户进入
`案例管理`
1.
用户进入
`案例管理`
2.
点击
`Excel 批量导出`
2.
点击
`Excel 批量导出`
3.
前端按当前
搜索词、案例性质与关键字
筛选条件查询全部案例
3.
前端按当前
全文搜索、案例性质、案例来源、案例类型、问题属性、分类标签等
筛选条件查询全部案例
4.
导出文件统一输出以下 11 列:
4.
导出文件统一输出以下 11 列:
-
`标题`
-
`标题`
-
`分类`
-
`分类`
...
@@ -115,25 +115,28 @@
...
@@ -115,25 +115,28 @@
-
`案例等级`
-
`案例等级`
-
`案例年份`
-
`案例年份`
5.
浏览器直接下载
`.xlsx`
文件
5.
浏览器直接下载
`.xlsx`
文件
6.
`metadata.keyword`
导出到
`关键字`
列时
恢复为逗号
拼接字符串,便于线下继续维护
6.
`metadata.keyword`
导出到
`关键字`
列时
统一使用英文逗号
`,`
拼接字符串,便于线下继续维护
### 4.4 浏览与检索案例
### 4.4 浏览与检索案例
1.
用户进入
`案例管理`
1.
用户进入
`案例管理`
2.
通过搜索框检索标题、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级
2.
通过全文搜索框检索标题、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级
3.
通过案例性质标签快速切换当前视图
3.
通过案例来源、案例类型、问题属性、分类标签输入框填写组合筛选条件,输入框仅录入文本条件,不提供下拉候选
4.
通过关键字输入框进一步缩小范围,前端同时兼容历史
`metadata.tag`
4.
点击
`查询`
后,前端将当前输入条件提交到
`POST /record/query`
做远程查询;仅输入文字不触发请求
5.
页面顶部提供
`Segment`
视图切换,可在“卡片视图 / 表格视图”之间切换
5.
通过案例性质标签快速切换当前视图
6.
卡片视图用于浏览案例摘要,点击卡片后以弹框方式查看并编辑详情
6.
分类标签筛选优先匹配
`metadata.tag`
,同时兼容多值
`metadata.keyword`
7.
表格视图展示标题、分类、标签、案例类型、关键字、来源、等级、年份、更新时间等字段
7.
页面不对当前页案例做本地二次搜索
8.
表格视图按页加载案例,支持选择每页 20、50、100 条,并显示页码、当前页范围和匹配总数
8.
页面顶部提供
`Segment`
视图切换,可在“卡片视图 / 表格视图”之间切换
9.
表格视图支持单选、全选当前页案例,并显示当前勾选数量
9.
卡片视图用于浏览案例摘要,点击卡片后以弹框方式查看并编辑详情
10.
表格视图展示标题、分类、标签、案例类型、关键字、来源、等级、年份、更新时间等字段
11.
表格视图按页加载案例,支持选择每页 20、50、100 条,并显示页码、当前页范围和匹配总数
12.
表格视图支持单选、全选当前页案例,并显示当前勾选数量
### 4.5 编辑案例
### 4.5 编辑案例
1.
用户在列表中打开某个案例详情
1.
用户在列表中打开某个案例详情
2.
修改标题、分类、标签、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级、案例年份
2.
修改标题、分类、标签、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级、案例年份
3.
关键字编辑区
支持逗号输入,并实时预览为关键字标签;保存时拆分为
`metadata.keyword`
字符串数组
3.
关键字编辑区
的输入框仅用于新增关键字,已有关键字以标签展示并支持逐个删除;保存时写入
`metadata.keyword`
字符串数组
4.
前端调用
`PUT /record/:name`
4.
前端调用
`PUT /record/:name`
5.
保存成功后刷新列表与统计数据
5.
保存成功后刷新列表与统计数据
...
@@ -181,15 +184,22 @@
...
@@ -181,15 +184,22 @@
-
空行直接跳过
-
空行直接跳过
-
`标题`
为空时,优先使用
`案例类型`
,再回退为
`案例详情`
摘要自动生成
`title`
-
`标题`
为空时,优先使用
`案例类型`
,再回退为
`案例详情`
摘要自动生成
`title`
-
普通文本字段统一按字符串保存
-
普通文本字段统一按字符串保存
-
`关键字`
是多值字段,导入时
将逗号拼接文本拆分为字符串数组保存
-
`关键字`
是多值字段,导入时
识别英文逗号
`,`
与中文逗号
`,`
,拆分、去空、去重后保存为字符串数组
-
`案例年份`
优先按数字写入,不可解析时保留原字符串
-
`案例年份`
优先按数字写入,不可解析时保留原字符串
-
导出模板固定为 11 列标准表头,便于线下统一维护
-
导出模板固定为 11 列标准表头,便于线下统一维护
### 5.3 筛选规则
### 5.3 筛选规则
-
案例性质筛选使用
`metadata.category`
-
案例性质筛选使用
`metadata.category`
-
关键字筛选优先使用多值数组
`metadata.keyword`
,同时兼容历史字符串型
`metadata.keyword`
与
`metadata.tag`
-
案例来源筛选使用
`metadata.case_source`
,支持模糊匹配
-
案例类型筛选使用
`metadata.case_type`
,支持模糊匹配
-
问题属性筛选使用
`metadata.case_attribute`
,支持模糊匹配
-
分类标签筛选优先匹配
`metadata.tag`
,同时兼容多值数组或历史字符串型
`metadata.keyword`
-
关键字检索优先使用多值数组
`metadata.keyword`
,同时兼容历史字符串型
`metadata.keyword`
与
`metadata.tag`
-
关键词检索同时覆盖
`title`
、
`metadata.label`
、
`metadata.case_type`
、
`metadata.case_attribute`
、
`metadata.case_details`
、
`metadata.case_warning`
、
`metadata.keyword`
、
`metadata.case_source`
、
`metadata.case_level`
、
`metadata.category`
、
`metadata.tag`
-
关键词检索同时覆盖
`title`
、
`metadata.label`
、
`metadata.case_type`
、
`metadata.case_attribute`
、
`metadata.case_details`
、
`metadata.case_warning`
、
`metadata.keyword`
、
`metadata.case_source`
、
`metadata.case_level`
、
`metadata.category`
、
`metadata.tag`
-
筛选统一走远程
`POST /record/query`
;页面不使用本地数据搜索替代远程结果
-
筛选条件输入后必须点击
`查询`
才发起请求,输入过程不自动请求
-
筛选输入使用普通文本输入框,不使用
`select`
、
`datalist`
或其它下拉候选控件
---
---
...
@@ -223,9 +233,14 @@
...
@@ -223,9 +233,14 @@
建议至少包含:
建议至少包含:
-
搜索框
-
全文
搜索框
-
案例性质切换
-
案例性质切换
-
关键字筛选
-
案例来源筛选
-
案例类型筛选
-
问题属性筛选
-
分类标签筛选
-
查询按钮
-
筛选项为普通文本输入框,不提供下拉框
-
`Segment`
视图切换(卡片 / 表格)
-
`Segment`
视图切换(卡片 / 表格)
-
手动新建按钮
-
手动新建按钮
-
Excel 导入按钮
-
Excel 导入按钮
...
@@ -251,7 +266,7 @@
...
@@ -251,7 +266,7 @@
-
案例详情全文
-
案例详情全文
-
案例性质
-
案例性质
-
关键字
-
关键字
-
关键字以标签形式展示;编辑时
保留逗号输入并即时预览拆分后的标签
-
关键字以标签形式展示;编辑时
输入框仅用于新增关键字,已有标签支持逐个删除
-
案例警示
-
案例警示
-
案例来源 / 案例等级 / 案例年份
-
案例来源 / 案例等级 / 案例年份
-
案例详情字数
-
案例详情字数
...
@@ -286,7 +301,8 @@
...
@@ -286,7 +301,8 @@
-
首屏只加载第一页,默认每页 20 条
-
首屏只加载第一页,默认每页 20 条
-
表格与卡片列表通过页码分页加载,不使用下滑自动加载
-
表格与卡片列表通过页码分页加载,不使用下滑自动加载
-
用户可选择每页 20、50、100 条;切换每页数量时从第一页重新加载
-
用户可选择每页 20、50、100 条;切换每页数量时从第一页重新加载
-
当用户切换分类或搜索条件时,从第一页重新加载
-
当用户点击
`查询`
、重置筛选或切换案例性质时,从第一页重新加载
-
搜索与筛选输入变化时不自动请求;点击
`查询`
后必须发起远程查询,不在当前页结果中做本地搜索
-
总数优先读取
`POST /record/query`
返回的
`total_records`
;接口未返回总数时再回退到
`count`
接口
-
总数优先读取
`POST /record/query`
返回的
`total_records`
;接口未返回总数时再回退到
`count`
接口
---
---
...
...
src/pages/project_manager_xy/materialService.js
View file @
06f0d7dd
...
@@ -281,7 +281,7 @@ function createSummary(content = '', maxLength = 140) {
...
@@ -281,7 +281,7 @@ function createSummary(content = '', maxLength = 140) {
export
function
normalizeKeywordList
(
value
)
{
export
function
normalizeKeywordList
(
value
)
{
const
sourceValues
=
(
Array
.
isArray
(
value
)
?
value
:
[
value
]).
flatMap
((
item
)
=>
const
sourceValues
=
(
Array
.
isArray
(
value
)
?
value
:
[
value
]).
flatMap
((
item
)
=>
String
(
item
||
''
).
split
(
/
[
,,
]
/
),
String
(
item
||
''
).
split
(
/
[
,,
;;
\n
]
/
),
);
);
const
seenKeywords
=
new
Set
();
const
seenKeywords
=
new
Set
();
...
@@ -300,7 +300,7 @@ export function normalizeKeywordList(value) {
...
@@ -300,7 +300,7 @@ export function normalizeKeywordList(value) {
}
}
export
function
formatKeywordList
(
value
)
{
export
function
formatKeywordList
(
value
)
{
return
normalizeKeywordList
(
value
).
join
(
',
'
);
return
normalizeKeywordList
(
value
).
join
(
','
);
}
}
export
function
formatDateTime
(
value
)
{
export
function
formatDateTime
(
value
)
{
...
@@ -365,6 +365,20 @@ function buildKeywordFilter(keyword = '') {
...
@@ -365,6 +365,20 @@ function buildKeywordFilter(keyword = '') {
};
};
}
}
function
buildMetadataTextFilter
(
fieldName
,
value
=
''
)
{
const
normalizedValue
=
String
(
value
||
''
).
trim
();
if
(
!
normalizedValue
)
{
return
null
;
}
return
{
[
`metadata.
${
fieldName
}
`
]:
{
$regex
:
escapeRegExp
(
normalizedValue
),
$options
:
'i'
,
},
};
}
function
buildRecordFilter
(
filters
=
{})
{
function
buildRecordFilter
(
filters
=
{})
{
const
clauses
=
[];
const
clauses
=
[];
const
keywordFilter
=
buildKeywordFilter
(
filters
.
keyword
);
const
keywordFilter
=
buildKeywordFilter
(
filters
.
keyword
);
...
@@ -380,14 +394,28 @@ function buildRecordFilter(filters = {}) {
...
@@ -380,14 +394,28 @@ function buildRecordFilter(filters = {}) {
}
}
if
(
filters
.
tag
)
{
if
(
filters
.
tag
)
{
clauses
.
push
({
const
tagRegex
=
{
'metadata.tag'
:
{
$regex
:
escapeRegExp
(
String
(
filters
.
tag
).
trim
()),
$regex
:
escapeRegExp
(
String
(
filters
.
tag
).
trim
()),
$options
:
'i'
,
$options
:
'i'
,
},
};
clauses
.
push
({
$or
:
[{
'metadata.tag'
:
tagRegex
},
{
'metadata.keyword'
:
tagRegex
}],
});
});
}
}
[
[
'case_source'
,
filters
.
caseSource
],
[
'case_type'
,
filters
.
caseType
],
[
'case_attribute'
,
filters
.
caseAttribute
],
].
forEach
(([
fieldName
,
value
])
=>
{
const
fieldFilter
=
buildMetadataTextFilter
(
fieldName
,
value
);
if
(
fieldFilter
)
{
clauses
.
push
(
fieldFilter
);
}
});
if
(
clauses
.
length
===
0
)
{
if
(
clauses
.
length
===
0
)
{
return
{};
return
{};
}
}
...
@@ -405,7 +433,10 @@ function hasActiveFilters(filters = {}) {
...
@@ -405,7 +433,10 @@ function hasActiveFilters(filters = {}) {
return
Boolean
(
return
Boolean
(
String
(
filters
.
keyword
||
''
).
trim
()
||
String
(
filters
.
keyword
||
''
).
trim
()
||
String
(
filters
.
category
||
''
).
trim
()
||
String
(
filters
.
category
||
''
).
trim
()
||
String
(
filters
.
tag
||
''
).
trim
(),
String
(
filters
.
tag
||
''
).
trim
()
||
String
(
filters
.
caseSource
||
''
).
trim
()
||
String
(
filters
.
caseType
||
''
).
trim
()
||
String
(
filters
.
caseAttribute
||
''
).
trim
(),
);
);
}
}
...
@@ -461,36 +492,6 @@ export function normalizeCaseRecord(record = {}) {
...
@@ -461,36 +492,6 @@ export function normalizeCaseRecord(record = {}) {
};
};
}
}
function
applyClientFilters
(
cases
,
filters
=
{})
{
const
keyword
=
String
(
filters
.
keyword
||
''
).
trim
().
toLowerCase
();
const
category
=
String
(
filters
.
category
||
''
).
trim
();
const
tag
=
String
(
filters
.
tag
||
''
).
trim
().
toLowerCase
();
return
cases
.
filter
((
item
)
=>
{
const
matchesKeyword
=
!
keyword
||
[
item
.
label
,
item
.
caseType
,
item
.
caseAttribute
,
item
.
caseDetails
,
item
.
caseWarning
,
item
.
keyword
,
item
.
caseSource
,
item
.
caseLevel
,
item
.
category
,
item
.
tag
,
]
.
filter
(
Boolean
)
.
some
((
value
)
=>
String
(
value
).
toLowerCase
().
includes
(
keyword
));
const
matchesCategory
=
!
category
||
item
.
category
===
category
;
const
matchesTag
=
!
tag
||
String
(
item
.
tag
||
''
).
toLowerCase
().
includes
(
tag
);
return
matchesKeyword
&&
matchesCategory
&&
matchesTag
;
});
}
export
function
buildCaseDashboard
(
cases
=
[],
options
=
{})
{
export
function
buildCaseDashboard
(
cases
=
[],
options
=
{})
{
const
totalCount
=
Number
.
isFinite
(
options
.
totalCount
)
const
totalCount
=
Number
.
isFinite
(
options
.
totalCount
)
?
options
.
totalCount
?
options
.
totalCount
...
@@ -572,8 +573,6 @@ export function buildCaseDashboard(cases = [], options = {}) {
...
@@ -572,8 +573,6 @@ export function buildCaseDashboard(cases = [], options = {}) {
caseSourceGroups
,
caseSourceGroups
,
recentCases
,
recentCases
,
attentionCases
,
attentionCases
,
categorySuggestions
:
categoryGroups
.
map
((
item
)
=>
item
.
label
),
tagSuggestions
:
tagGroups
.
map
((
item
)
=>
item
.
label
),
};
};
}
}
...
@@ -620,10 +619,7 @@ export async function loadCasePage(filters = {}) {
...
@@ -620,10 +619,7 @@ export async function loadCasePage(filters = {}) {
page
===
0
&&
!
hasActiveFilters
(
filters
)
&&
total
===
0
&&
cases
.
length
===
0
;
page
===
0
&&
!
hasActiveFilters
(
filters
)
&&
total
===
0
&&
cases
.
length
===
0
;
if
(
shouldUseDemo
)
{
if
(
shouldUseDemo
)
{
const
demoCases
=
applyClientFilters
(
const
demoCases
=
DEMO_CASES
.
map
((
item
)
=>
normalizeCaseRecord
(
item
));
DEMO_CASES
.
map
((
item
)
=>
normalizeCaseRecord
(
item
)),
filters
,
);
const
start
=
page
*
pageSize
;
const
start
=
page
*
pageSize
;
const
pageCases
=
demoCases
.
slice
(
start
,
start
+
pageSize
);
const
pageCases
=
demoCases
.
slice
(
start
,
start
+
pageSize
);
const
demoTotal
=
demoCases
.
length
;
const
demoTotal
=
demoCases
.
length
;
...
@@ -662,10 +658,19 @@ export async function loadCasePage(filters = {}) {
...
@@ -662,10 +658,19 @@ export async function loadCasePage(filters = {}) {
total
=
0
;
total
=
0
;
}
}
const
demoCases
=
applyClientFilters
(
if
(
hasActiveFilters
(
filters
))
{
DEMO_CASES
.
map
((
item
)
=>
normalizeCaseRecord
(
item
)),
return
{
filters
,
cases
:
[],
);
total
:
0
,
page
,
pageSize
,
hasMore
:
false
,
isDemoMode
:
false
,
error
:
error
?.
message
||
'远程案例查询失败,请稍后重试。'
,
};
}
const
demoCases
=
DEMO_CASES
.
map
((
item
)
=>
normalizeCaseRecord
(
item
));
const
start
=
page
*
pageSize
;
const
start
=
page
*
pageSize
;
const
pageCases
=
demoCases
.
slice
(
start
,
start
+
pageSize
);
const
pageCases
=
demoCases
.
slice
(
start
,
start
+
pageSize
);
...
...
src/pages/project_manager_xy/resource-booking/index.jsx
View file @
06f0d7dd
This diff is collapsed.
Click to expand it.
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