vue2-client 1.20.34 → 1.20.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/base-client/components/common/XMarkdownSectionExtractor/DemoXMarkdownSectionExtractor.vue +42 -1
- package/src/base-client/components/common/XMarkdownSectionExtractor/XMarkdownSectionExtractor.vue +37 -1
- package/src/base-client/components/common/XMarkdownSectionExtractor/bingli.md +53 -72
- package/src/base-client/components/common/XMarkdownSectionExtractor/markdownSectionExtractor.js +155 -38
package/package.json
CHANGED
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
<button @click="loadAndExtract">加载内容并提取</button>
|
|
14
14
|
<button @click="getSelected">获取选中数据</button>
|
|
15
15
|
<button @click="clearSections">清空</button>
|
|
16
|
+
<button @click="testExtractByKeywords">测试 extractByKeywords</button>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div v-if="keywordResult" class="keyword-result">
|
|
20
|
+
<h3>extractByKeywords 结果:</h3>
|
|
21
|
+
<div v-for="(item, idx) in keywordResult" :key="idx" class="result-item">
|
|
22
|
+
<strong>{{ Object.keys(item)[0] }}:</strong>{{ Object.values(item)[0] }}
|
|
23
|
+
</div>
|
|
16
24
|
</div>
|
|
17
25
|
</div>
|
|
18
26
|
</template>
|
|
@@ -27,7 +35,8 @@ export default {
|
|
|
27
35
|
data () {
|
|
28
36
|
return {
|
|
29
37
|
selectedItems: [],
|
|
30
|
-
sampleMarkdown: markdownContent
|
|
38
|
+
sampleMarkdown: markdownContent,
|
|
39
|
+
keywordResult: null
|
|
31
40
|
}
|
|
32
41
|
},
|
|
33
42
|
methods: {
|
|
@@ -51,6 +60,15 @@ export default {
|
|
|
51
60
|
},
|
|
52
61
|
clearSections () {
|
|
53
62
|
this.$refs.extractor.clearRenderedSections()
|
|
63
|
+
},
|
|
64
|
+
testExtractByKeywords () {
|
|
65
|
+
const result = this.$refs.extractor.extractByKeywords(
|
|
66
|
+
this.sampleMarkdown,
|
|
67
|
+
['主诉', '现病史', '既往史', '体格检查', '专科检查', '辅助检查']
|
|
68
|
+
)
|
|
69
|
+
this.keywordResult = result
|
|
70
|
+
// eslint-disable-next-line no-console
|
|
71
|
+
console.log('extractByKeywords result:', result)
|
|
54
72
|
}
|
|
55
73
|
}
|
|
56
74
|
}
|
|
@@ -75,4 +93,27 @@ export default {
|
|
|
75
93
|
}
|
|
76
94
|
}
|
|
77
95
|
}
|
|
96
|
+
|
|
97
|
+
.keyword-result {
|
|
98
|
+
margin-top: 16px;
|
|
99
|
+
padding: 12px;
|
|
100
|
+
background: #f5f5f5;
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
|
|
103
|
+
h3 {
|
|
104
|
+
margin: 0 0 12px 0;
|
|
105
|
+
font-size: 14px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.result-item {
|
|
109
|
+
margin-bottom: 8px;
|
|
110
|
+
font-size: 13px;
|
|
111
|
+
line-height: 1.5;
|
|
112
|
+
word-break: break-all;
|
|
113
|
+
|
|
114
|
+
strong {
|
|
115
|
+
color: #1890ff;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
78
119
|
</style>
|
package/src/base-client/components/common/XMarkdownSectionExtractor/XMarkdownSectionExtractor.vue
CHANGED
|
@@ -49,7 +49,8 @@ import {
|
|
|
49
49
|
extractSectionsFromMarkdown,
|
|
50
50
|
flattenSectionsToItemsArray,
|
|
51
51
|
flattenSectionsToItemsByKey,
|
|
52
|
-
convertToFormat
|
|
52
|
+
convertToFormat,
|
|
53
|
+
extractByKeywords
|
|
53
54
|
} from './markdownSectionExtractor'
|
|
54
55
|
|
|
55
56
|
export default {
|
|
@@ -224,6 +225,7 @@ export default {
|
|
|
224
225
|
return []
|
|
225
226
|
}
|
|
226
227
|
const sections = extractSectionsFromMarkdown(text, config)
|
|
228
|
+
console.log('[extractAs] sections:', JSON.stringify(sections, null, 2))
|
|
227
229
|
if (parseType === 'flat') {
|
|
228
230
|
return flattenSectionsToItemsArray(sections)
|
|
229
231
|
}
|
|
@@ -236,6 +238,7 @@ export default {
|
|
|
236
238
|
}))
|
|
237
239
|
: []
|
|
238
240
|
}))
|
|
241
|
+
console.log('[extractAs] result:', JSON.stringify(result, null, 2))
|
|
239
242
|
// 创建新数组引用以确保 Vue 响应式更新
|
|
240
243
|
const newSections = [...this.renderedSections, ...result]
|
|
241
244
|
this.renderedSections = newSections
|
|
@@ -304,6 +307,39 @@ export default {
|
|
|
304
307
|
this.$nextTick(() => {
|
|
305
308
|
this.$emit('sections-rendered', this.renderedSections)
|
|
306
309
|
})
|
|
310
|
+
},
|
|
311
|
+
/**
|
|
312
|
+
* 根据关键字提取 markdown 中各区块的内容
|
|
313
|
+
*
|
|
314
|
+
* 用途:从一份 markdown 病历中按「主诉、现病史、既往史」等标题快速提取对应的区块内容。
|
|
315
|
+
* 返回格式为数组,每个元素是一个以关键字为 key 的对象。
|
|
316
|
+
*
|
|
317
|
+
* 规则:
|
|
318
|
+
* - 每个关键字会找到对应标题(默认 2 级标题即 ## 开头),提取从该标题到下一个同级或更高等级标题之前的内容
|
|
319
|
+
* - 若文档中没有匹配的关键字,则内容为空字符串
|
|
320
|
+
* - 只返回有匹配的关键字,未匹配的不返回
|
|
321
|
+
*
|
|
322
|
+
* @param {string} markdownText - markdown 格式的字符串
|
|
323
|
+
* @param {string[]} keywords - 关键字数组,如 ['主诉', '现病史', '既往史']
|
|
324
|
+
* @param {Object} [options] - 可选配置
|
|
325
|
+
* @param {number} [options.headingLevel=2] - 标题级别,默认 2(对应 ##),可设为 3(对应 ###)等
|
|
326
|
+
* @param {boolean} [options.trimContent=true] - 是否去除内容首尾空白,默认为是
|
|
327
|
+
* @param {boolean} [options.stripMarkdown=true] - 是否去除 markdown 格式符号(如 **、-、引用等),默认为是
|
|
328
|
+
* @returns {Array<Object>} - 数组,每个元素以关键字为 key,如 [{ "主诉": "反复左额部..." }, { "现病史": "患者女性..." }]
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* const result = this.extractByKeywords(markdownString, ['主诉', '现病史', '既往史'])
|
|
332
|
+
* // result = [
|
|
333
|
+
* // { "主诉": "反复左额部搏动性疼痛2年余,加重伴呕吐1天。" },
|
|
334
|
+
* // { "现病史": "患者女性,2年前无明显诱因出现左侧额部头痛..." }
|
|
335
|
+
* // ]
|
|
336
|
+
*/
|
|
337
|
+
extractByKeywords (markdownText, keywords, options) {
|
|
338
|
+
if (typeof markdownText !== 'string') {
|
|
339
|
+
console.warn('[XMarkdownSectionExtractor] extractByKeywords requires a string as first argument')
|
|
340
|
+
return []
|
|
341
|
+
}
|
|
342
|
+
return extractByKeywords(markdownText, keywords, options)
|
|
307
343
|
}
|
|
308
344
|
}
|
|
309
345
|
}
|
|
@@ -1,72 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
###
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- 立即行**视野检查和视神经功能评估**,因IIH可导致永久性视力损害。
|
|
55
|
-
|
|
56
|
-
4. **诊断标准参考ICHD-3**:
|
|
57
|
-
- 若符合**颅内压升高、正常脑脊液成分、无占位或静脉窦血栓等病因**,可诊断为**特发性颅内高压(IIH)**。
|
|
58
|
-
|
|
59
|
-
5. **治疗建议**:
|
|
60
|
-
- **一线药物治疗**:**乙酰唑胺**(通过减少脑脊液生成降低颅压),起始剂量通常为250 mg 每日2-3次,根据耐受性调整。
|
|
61
|
-
- **体重管理**:若存在超重或肥胖,**减重5%-10%**可显著改善症状 ^[2]^。
|
|
62
|
-
- **严重或进行性视力损害**:需考虑**视神经鞘减压术**或**脑脊液分流术**。
|
|
63
|
-
- **静脉窦血栓**:若MRV提示血栓,需抗凝治疗。
|
|
64
|
-
|
|
65
|
-
6. **监测与随访**:
|
|
66
|
-
- 定期监测**视力、视野及颅内压变化**,避免不可逆视力丧失。
|
|
67
|
-
|
|
68
|
-
**关键点**:
|
|
69
|
-
|
|
70
|
-
- **视乳头水肿是IIH的典型体征**,但需排除其他继发性病因。
|
|
71
|
-
- **CSF开放压是诊断IIH的核心指标** ^[1]^。
|
|
72
|
-
- **及时干预可防止永久性视力损害**。
|
|
1
|
+
# 病例分析
|
|
2
|
+
|
|
3
|
+
## 主诉
|
|
4
|
+
|
|
5
|
+
反复左额部搏动性疼痛2年余,加重伴呕吐1天。
|
|
6
|
+
|
|
7
|
+
## 现病史
|
|
8
|
+
|
|
9
|
+
患者女性,2年前无明显诱因出现左侧额部头痛,呈阵发性搏动性疼痛,每次持续12小时至3天,平均每月发作1-2次,疼痛程度为VAS 8分,发作时常伴畏光、畏声,严重时伴有恶心、呕吐。头痛在日常活动后加重,吹风、受凉及工作压力大时易诱发。
|
|
10
|
+
|
|
11
|
+
1天前再次发作头痛,性质与以往不同,转为**全头胀痛**,疼痛程度加重至VAS 10分,**平躺时加重,站立时减轻**,且在咳嗽时明显加重。自服布洛芬2粒后症状无缓解,且持续加重,伴恶心、呕吐及视物模糊。
|
|
12
|
+
|
|
13
|
+
值得注意的是,此次头痛具有**体位相关性**,**站立时减轻、平卧时加重**,提示可能与颅内压变化相关。此外,咳嗽时头痛加重,提示颅内压波动或脑脊液动力学异常。近期存在**用力排便**等增加腹压的行为史,可能为脑脊液漏的诱因之一。同时,患者自述**近期体重明显增加**,需考虑是否存在内分泌或代谢异常,如甲状腺功能减退、库欣综合征等,但尚无其他典型症状支持。
|
|
14
|
+
|
|
15
|
+
## 既往史
|
|
16
|
+
|
|
17
|
+
否认肿瘤史、高血压、脑动脉瘤、脑动静脉畸形;无家族性脑血管病史;否认近期头面部感染或头部创伤;2个月前停经,末次月经为2025年1月3日;否认药物过敏及手术史。
|
|
18
|
+
|
|
19
|
+
## 体格检查
|
|
20
|
+
|
|
21
|
+
- 血压:133/75 mmHg
|
|
22
|
+
- 脉搏:82次/分
|
|
23
|
+
- 体温:36.4°C
|
|
24
|
+
- 神志:清楚,焦虑面容
|
|
25
|
+
|
|
26
|
+
专科检查:
|
|
27
|
+
|
|
28
|
+
- **双侧视乳头水肿**,眼球活动正常,其余脑神经查体未见明显异常
|
|
29
|
+
- **颈强3指**,克氏征和布氏征阴性
|
|
30
|
+
- 四肢运动、感觉检查未见异常
|
|
31
|
+
|
|
32
|
+
## 辅助检查
|
|
33
|
+
|
|
34
|
+
目前尚未进行影像学或脑脊液检查。根据临床表现,需高度怀疑**自发性低颅压综合征(SIH)**,尤其是其典型特征如**体位性头痛(站立减轻、平卧加重)**、**咳嗽加重**、**视乳头水肿**及**颈部僵硬**等。
|
|
35
|
+
|
|
36
|
+
### 脑脊液检查要点
|
|
37
|
+
|
|
38
|
+
文献显示,**并非所有SIH患者脑脊液压力均低于正常**。一项118例的回顾性研究显示,**79.6%的患者脑脊液压力在60-250 mmH₂O之间**,甚至**1.9%的患者压力高于250 mmH₂O** ^[7]^。此外,**病程越短,脑脊液压力越低,随病程延长压力可逐渐回升至正常范围** ^[7]^。
|
|
39
|
+
|
|
40
|
+
**脑脊液蛋白水平在57.5%的患者中超过45 mg/dl**,**红细胞计数在50.6%的患者中超过8.0×10⁶/L**,可能与硬膜外静脉丛扩张损伤有关 ^[7]^。
|
|
41
|
+
|
|
42
|
+
### 进一步检查建议
|
|
43
|
+
|
|
44
|
+
应尽快完善:
|
|
45
|
+
|
|
46
|
+
1. **头颅MRI增强扫描** — 评估是否存在硬脑膜强化、硬膜下积液、静脉窦增宽等SIH典型表现
|
|
47
|
+
2. **脊髓影像学检查**(如脊髓水成像或CT脊髓造影) — 寻找脑脊液漏点
|
|
48
|
+
|
|
49
|
+
若确诊,**靶向硬膜外血贴(EBP)治疗**可能为有效干预手段。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
建议进一步查阅自发性低颅压综合征的诊断标准及脑脊液动力学异常在体重增加背景下的潜在内分泌关联的最新临床指南。
|
package/src/base-client/components/common/XMarkdownSectionExtractor/markdownSectionExtractor.js
CHANGED
|
@@ -129,9 +129,13 @@ function buildHierarchicalItems (sectionLines) {
|
|
|
129
129
|
if (!Array.isArray(sectionLines) || sectionLines.length === 0) return []
|
|
130
130
|
|
|
131
131
|
const result = []
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
const
|
|
132
|
+
// 匹配数字标题:"1. xxx" 或 "**1. xxx**"(行首必须是数字或 **)
|
|
133
|
+
// 使用捕获组:组1=数字(普通), 组2=内容(普通),组3=数字(加粗), 组4=内容(加粗)
|
|
134
|
+
const numberLineRegex = /^(?:(\d+)\.\s+(.+)|\*\*(\d+)\.\s+(.+)\*\*)$/
|
|
135
|
+
// 匹配 bullet 标题:"- **xxx**"(行首是 -)
|
|
136
|
+
const bulletTitleRegex = /^-\s+\*\*(.+)\*\*$/
|
|
137
|
+
// 匹配 bullet 内容:"- xxx"(行首是 -,后面不是 **)
|
|
138
|
+
const bulletRegex = /^-\s+([^*].*)$/
|
|
135
139
|
|
|
136
140
|
let currentTitle = null
|
|
137
141
|
let currentBullets = []
|
|
@@ -156,16 +160,38 @@ function buildHierarchicalItems (sectionLines) {
|
|
|
156
160
|
// 跳过空行
|
|
157
161
|
if (!trimmed.trim()) continue
|
|
158
162
|
|
|
163
|
+
// 调试日志
|
|
164
|
+
// console.log('Processing line:', JSON.stringify(trimmed))
|
|
165
|
+
|
|
166
|
+
// 匹配数字标题 "1. xxx" 或 "**1. xxx**"
|
|
159
167
|
const numMatch = numberLineRegex.exec(trimmed)
|
|
160
168
|
if (numMatch) {
|
|
169
|
+
// 组1=数字(普通), 组2=内容(普通),组3=数字(加粗), 组4=内容(加粗)
|
|
170
|
+
flushCurrent()
|
|
171
|
+
currentTitle = numMatch[2] || numMatch[4] || ''
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 匹配 bullet 标题 "- **标题**"
|
|
176
|
+
const bulletTitleMatch = bulletTitleRegex.exec(trimmed)
|
|
177
|
+
if (bulletTitleMatch) {
|
|
178
|
+
// console.log('Matched bullet title:', bulletTitleMatch[1])
|
|
161
179
|
flushCurrent()
|
|
162
|
-
currentTitle =
|
|
180
|
+
currentTitle = bulletTitleMatch[1] || ''
|
|
163
181
|
continue
|
|
164
182
|
}
|
|
165
183
|
|
|
184
|
+
// 匹配 bullet 内容 "- xxx"
|
|
166
185
|
const bulletMatch = bulletRegex.exec(trimmed)
|
|
167
186
|
if (bulletMatch) {
|
|
187
|
+
// console.log('Matched bullet content:', bulletMatch[1])
|
|
168
188
|
currentBullets.push(bulletMatch[1] || '')
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 处理缩进的普通文本(无前缀),作为内容
|
|
193
|
+
if (/^\s{2,}/.test(trimmed) && currentTitle !== null) {
|
|
194
|
+
currentBullets.push(trimmed)
|
|
169
195
|
}
|
|
170
196
|
}
|
|
171
197
|
|
|
@@ -197,73 +223,72 @@ function buildStructuredItems (sectionLines) {
|
|
|
197
223
|
if (!Array.isArray(sectionLines) || sectionLines.length === 0) return []
|
|
198
224
|
|
|
199
225
|
const items = []
|
|
200
|
-
//
|
|
201
|
-
const numberLineRegex =
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
const
|
|
226
|
+
// 匹配数字标题:"1. xxx" 或 "**1. xxx**"
|
|
227
|
+
const numberLineRegex = /^(?:(\d+)\.\s+(.+)|\*\*(\d+)\.\s+(.+)\*\*)$/
|
|
228
|
+
// 匹配 bullet 标题:"- **xxx**"
|
|
229
|
+
const bulletTitleRegex = /^-\s+\*\*(.+)\*\*$/
|
|
230
|
+
// 匹配 bullet 内容:"- xxx"(不包含 ** 的)
|
|
231
|
+
const bulletRegex = /^-\s+([^*].*)$/
|
|
232
|
+
|
|
233
|
+
// 先收集所有标题行(数字或 bullet)
|
|
234
|
+
const titleIndexes = []
|
|
206
235
|
sectionLines.forEach((line, idx) => {
|
|
207
236
|
if (numberLineRegex.test(line || '')) {
|
|
208
|
-
|
|
237
|
+
titleIndexes.push({ idx, type: 'number' })
|
|
238
|
+
} else if (bulletTitleRegex.test(line || '')) {
|
|
239
|
+
titleIndexes.push({ idx, type: 'bullet' })
|
|
209
240
|
}
|
|
210
241
|
})
|
|
211
242
|
|
|
212
|
-
if (
|
|
213
|
-
// 没有数字列表时,整体作为一个 item 返回
|
|
243
|
+
if (titleIndexes.length === 0) {
|
|
214
244
|
const joined = sectionLines.join(' ').trim()
|
|
215
245
|
if (!joined) return []
|
|
216
246
|
const plain = stripMarkdownEmphasis(joined)
|
|
217
|
-
return [{
|
|
218
|
-
id: 1,
|
|
219
|
-
name: plain
|
|
220
|
-
}]
|
|
247
|
+
return [{ id: 1, name: plain }]
|
|
221
248
|
}
|
|
222
249
|
|
|
223
|
-
|
|
250
|
+
titleIndexes.forEach((titleInfo, idx) => {
|
|
251
|
+
const startIdx = titleInfo.idx
|
|
224
252
|
const line = sectionLines[startIdx] || ''
|
|
225
|
-
const m = numberLineRegex.exec(line)
|
|
226
|
-
if (!m) return
|
|
227
253
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
254
|
+
let titleText = ''
|
|
255
|
+
if (titleInfo.type === 'number') {
|
|
256
|
+
const m = numberLineRegex.exec(line)
|
|
257
|
+
// numMatch[2] 是 "1. xxx" 的内容,numMatch[4] 是 "**1. xxx**" 的内容
|
|
258
|
+
titleText = m ? (m[2] || m[4] || '') : ''
|
|
259
|
+
} else {
|
|
260
|
+
const m = bulletTitleRegex.exec(line)
|
|
261
|
+
titleText = m ? (m[1] || '') : ''
|
|
262
|
+
}
|
|
232
263
|
|
|
233
|
-
// 尝试向后看,收集紧跟的 bullet 行
|
|
234
264
|
const bullets = []
|
|
235
|
-
|
|
265
|
+
const nextIdx = titleIndexes[idx + 1] ? titleIndexes[idx + 1].idx : sectionLines.length
|
|
266
|
+
|
|
267
|
+
for (let i = startIdx + 1; i < nextIdx; i++) {
|
|
236
268
|
const l = sectionLines[i] || ''
|
|
237
269
|
if (!l.trim()) continue
|
|
238
|
-
// 遇到下一个数字条目则终止
|
|
239
|
-
if (numberLineRegex.test(l)) break
|
|
240
270
|
const bm = bulletRegex.exec(l)
|
|
241
271
|
if (bm) {
|
|
242
272
|
bullets.push(bm[1] || '')
|
|
243
|
-
} else {
|
|
244
|
-
|
|
273
|
+
} else if (/^\s{2,}/.test(l)) {
|
|
274
|
+
bullets.push(l.trim())
|
|
245
275
|
}
|
|
246
276
|
}
|
|
247
277
|
|
|
248
|
-
|
|
249
|
-
|
|
278
|
+
let candidateText = titleText
|
|
279
|
+
if (bullets.length > 0) {
|
|
250
280
|
candidateText = bullets[0]
|
|
251
281
|
}
|
|
252
282
|
|
|
253
|
-
// 截取中文全角冒号前面的部分
|
|
254
283
|
const colonIndex = candidateText.indexOf(':')
|
|
255
284
|
if (colonIndex > 0) {
|
|
256
285
|
candidateText = candidateText.slice(0, colonIndex)
|
|
257
286
|
}
|
|
258
287
|
|
|
259
288
|
const name = stripMarkdownEmphasis(candidateText).trim()
|
|
260
|
-
|
|
261
289
|
if (!name) return
|
|
262
290
|
|
|
263
|
-
items.push({
|
|
264
|
-
id: items.length + 1,
|
|
265
|
-
name
|
|
266
|
-
})
|
|
291
|
+
items.push({ id: items.length + 1, name })
|
|
267
292
|
})
|
|
268
293
|
|
|
269
294
|
return items
|
|
@@ -532,3 +557,95 @@ export function convertToXListDefault (sections, options = {}) {
|
|
|
532
557
|
export function convertToXListCard (sections, options = {}) {
|
|
533
558
|
return convertToFormat(sections, OutputFormat.XLIST_CARD, options)
|
|
534
559
|
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* 去除 markdown 格式符号,保留纯文本
|
|
563
|
+
* - 去掉 ** 和 * 强调标记
|
|
564
|
+
* - 去掉 - bullet 列表标记(行首的 "- ")
|
|
565
|
+
* - 去掉 ^[数字]^ 引用标记
|
|
566
|
+
* - 去掉 [文字](url) 链接格式,保留文字
|
|
567
|
+
*
|
|
568
|
+
* @param {string} text
|
|
569
|
+
* @returns {string}
|
|
570
|
+
*/
|
|
571
|
+
function stripMarkdownFormat (text) {
|
|
572
|
+
if (!text || typeof text !== 'string') return ''
|
|
573
|
+
return text
|
|
574
|
+
// 去掉 ^[数字]^ 引用格式
|
|
575
|
+
.replace(/\^\[(\d+)\]\^/g, '')
|
|
576
|
+
// 去掉加粗 **xxx**
|
|
577
|
+
.replace(/\*\*(.+?)\*\*/g, '$1')
|
|
578
|
+
// 去掉斜体 *xxx*
|
|
579
|
+
.replace(/\*(.+?)\*/g, '$1')
|
|
580
|
+
// 去掉行首的 "- "(保留后面的内容)
|
|
581
|
+
.replace(/^- /gm, '')
|
|
582
|
+
// 去掉 [文字](url) 链接格式,保留文字
|
|
583
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
584
|
+
// 清理多余的空行(保留最多一个空行)
|
|
585
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* 根据关键字提取 markdown 中各区块的内容
|
|
590
|
+
*
|
|
591
|
+
* 用途:从一份 markdown 病历中按「主诉、现病史、既往史」等标题快速提取对应的区块内容。
|
|
592
|
+
* 返回格式为数组,每个元素是一个以关键字为 key 的对象。
|
|
593
|
+
*
|
|
594
|
+
* 规则:
|
|
595
|
+
* - 每个关键字会找到对应标题(默认 2 级标题即 ## 开头),提取从该标题到下一个同级或更高等级标题之前的内容
|
|
596
|
+
* - 若文档中没有匹配的关键字,则该关键字对应的内容为空字符串
|
|
597
|
+
* - 只返回有匹配的关键字,未匹配的不返回
|
|
598
|
+
*
|
|
599
|
+
* @param {string} markdownText - markdown 格式的字符串
|
|
600
|
+
* @param {string[]} keywords - 关键字数组,如 ['主诉', '现病史', '既往史']
|
|
601
|
+
* @param {Object} [options] - 可选配置
|
|
602
|
+
* @param {number} [options.headingLevel=2] - 标题级别,默认 2(对应 ##),可设为 3(对应 ###)等
|
|
603
|
+
* @param {boolean} [options.trimContent=true] - 是否去除内容首尾空白,默认为是
|
|
604
|
+
* @param {boolean} [options.stripMarkdown=true] - 是否去除 markdown 格式符号(如 **、-、引用等),默认为是
|
|
605
|
+
* @returns {Array<Object>} - 数组,每个元素以关键字为 key,如 [{ "主诉": "反复左额部..." }, { "现病史": "患者女性..." }]
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* const md = `## 主诉\n反复左额部搏动性疼痛2年余,加重伴呕吐1天。\n\n## 现病史\n患者女性,2年前无明显诱因出现左侧额部头痛...`
|
|
609
|
+
* const result = extractByKeywords(md, ['主诉', '现病史', '既往史'])
|
|
610
|
+
* // result = [
|
|
611
|
+
* // { "主诉": "反复左额部搏动性疼痛2年余,加重伴呕吐1天。" },
|
|
612
|
+
* // { "现病史": "患者女性,2年前无明显诱因出现左侧额部头痛..." }
|
|
613
|
+
* // ]
|
|
614
|
+
*/
|
|
615
|
+
export function extractByKeywords (markdownText, keywords, options = {}) {
|
|
616
|
+
if (!markdownText || typeof markdownText !== 'string') return []
|
|
617
|
+
if (!Array.isArray(keywords) || keywords.length === 0) return []
|
|
618
|
+
|
|
619
|
+
const {
|
|
620
|
+
headingLevel = 2,
|
|
621
|
+
trimContent = true,
|
|
622
|
+
stripMarkdown = true
|
|
623
|
+
} = options
|
|
624
|
+
|
|
625
|
+
const lines = markdownText.split(/\r?\n/)
|
|
626
|
+
const results = []
|
|
627
|
+
|
|
628
|
+
keywords.forEach(keyword => {
|
|
629
|
+
const cfg = {
|
|
630
|
+
key: `kw_${keyword}`,
|
|
631
|
+
label: String(keyword).trim(),
|
|
632
|
+
headingLevel
|
|
633
|
+
}
|
|
634
|
+
const headingIndex = findHeadingIndex(lines, cfg)
|
|
635
|
+
|
|
636
|
+
let content = ''
|
|
637
|
+
if (headingIndex !== -1) {
|
|
638
|
+
const range = findSectionRange(lines, headingIndex)
|
|
639
|
+
if (range.start <= range.end) {
|
|
640
|
+
const sectionLines = lines.slice(range.start, range.end + 1)
|
|
641
|
+
content = sectionLines.join('\n')
|
|
642
|
+
if (trimContent) content = content.trim()
|
|
643
|
+
if (stripMarkdown) content = stripMarkdownFormat(content)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
results.push({ [keyword]: content })
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
return results
|
|
651
|
+
}
|