vue2-client 1.22.2 → 1.22.3

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.
Files changed (170) hide show
  1. package/.claude/settings.local.json +30 -30
  2. package/.env.his +19 -19
  3. package/.eslintrc.js +74 -74
  4. package/.history/.eslintrc_20260521171150.js +74 -0
  5. package/.history/.eslintrc_20260521171213.js +74 -0
  6. package/.history/src/base-client/components/common/HIS/HAddNativeForm/HAddNativeForm_20260601154443.vue +726 -0
  7. package/.history/src/base-client/components/common/HIS/HAddNativeForm/HAddNativeForm_20260601154700.vue +478 -0
  8. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260512175435.vue +706 -0
  9. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260512175450.vue +694 -0
  10. package/.history/src/base-client/components/common/HIS/HButtons/HButtons_20260611152602.vue +755 -0
  11. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260513145941.vue +524 -0
  12. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260513153133.vue +731 -0
  13. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260513160316.vue +525 -0
  14. package/.history/src/base-client/components/common/HIS/HForm/HForm_20260601144150.vue +1046 -0
  15. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260310142713.vue +512 -0
  16. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260310145118.vue +511 -0
  17. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260311094834.vue +696 -0
  18. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260320143028.vue +693 -0
  19. package/.history/src/base-client/components/common/HIS/HFormTable/HFormTable_20260409101450.vue +677 -0
  20. package/.history/src/base-client/components/common/HIS/HTab/HTab_20260508164645.vue +758 -0
  21. package/.history/src/base-client/components/common/HIS/HTab/HTab_20260508164714.vue +693 -0
  22. package/.history/src/base-client/components/common/HIS/HTab/HTab_20260508171651.vue +716 -0
  23. package/.history/src/base-client/components/common/HIS/HTab/HTab_20260509133717.vue +695 -0
  24. package/.history/src/base-client/components/common/HIS/HTab/HTab_20260509171115.vue +664 -0
  25. package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513140637.vue +1455 -0
  26. package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513140935.vue +1441 -0
  27. package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513150818.vue +1441 -0
  28. package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513153119.vue +1442 -0
  29. package/.history/src/base-client/components/common/XAddNativeForm/XAddNativeForm_20260513153126.vue +1486 -0
  30. package/.history/src/base-client/components/common/XForm/XFormItem_20260513140854.vue +1607 -0
  31. package/.history/src/base-client/components/common/XMarkdownViewer/XMarkdownViewer_20260519140403.vue +643 -0
  32. package/.history/src/base-client/components/common/XMarkdownViewer/XMarkdownViewer_20260519140829.vue +628 -0
  33. package/.history/src/base-client/components/common/XMarkdownViewer/demo_20260519142824.vue +104 -0
  34. package/.history/src/base-client/components/common/XMarkdownViewer/demo_20260519143155.vue +102 -0
  35. package/.history/src/base-client/components/common/XReportGrid/XReport_20260309171231.vue +1241 -0
  36. package/.history/src/base-client/components/common/XReportGrid/XReport_20260309171441.vue +1223 -0
  37. package/.history/src/base-client/components/his/HAi/HAi_20260612174826.vue +472 -0
  38. package/.history/src/base-client/components/his/HAi/HAi_20260612175839.vue +538 -0
  39. package/.history/src/base-client/components/his/HAi/HAi_20260615103331.vue +650 -0
  40. package/.history/src/base-client/components/his/XHDescriptions/XHDescriptions_20260424134504.vue +1469 -0
  41. package/.history/src/base-client/components/his/XSidebar/XSidebar_20260610171133.vue +788 -0
  42. package/.history/src/base-client/components/his/XSidebar/XSidebar_20260610171151.vue +780 -0
  43. package/.history/src/base-client/components/his/XTransfer/XTransfer_20260511170841.vue +585 -0
  44. package/.history/src/base-client/components/his/XTransfer/XTransfer_20260511171138.vue +787 -0
  45. package/.history/src/base-client/components/his/XTransfer/XTransfer_20260512141830.vue +739 -0
  46. package/.history/src/components/STable/index_20260409155138.js +806 -0
  47. package/.history/src/components/STable/index_20260409155218.js +814 -0
  48. package/.history/src/expression/core/Expression_20260305164427.js +1371 -0
  49. package/.history/src/expression/core/Expression_20260305170258.js +1358 -0
  50. package/.history/src/expression/core/Program_20260305111830.js +944 -0
  51. package/.history/src/expression/core/Program_20260305112041.js +931 -0
  52. package/.history/src/logic/LogicRunner_20260304154306.js +170 -0
  53. package/.history/src/logic/LogicRunner_20260304155553.js +112 -0
  54. package/.history/src/logic/LogicRunner_20260305105834.js +112 -0
  55. package/.history/src/logic/LogicRunner_20260305112718.js +129 -0
  56. package/.history/src/logic/LogicRunner_20260305182436.js +133 -0
  57. package/.history/src/logic/LogicRunner_20260306151301.js +213 -0
  58. package/.history/src/logic/LogicRunner_20260306152419.js +213 -0
  59. package/.history/src/logic/plugins/common/DateTools_20260305154159.js +61 -0
  60. package/.history/src/logic/plugins/common/DateTools_20260305154217.js +44 -0
  61. package/.history/src/logic/plugins/common/DateTools_20260305161014.js +44 -0
  62. package/.history/src/logic/plugins/common/HttpTools_20260305164352.js +80 -0
  63. package/.history/src/logic/plugins/common/HttpTools_20260305170258.js +75 -0
  64. package/.history/src/logic/plugins/common/HttpTools_20260305171634.js +75 -0
  65. package/.history/src/logic/plugins/common/HttpTools_20260306152419.js +72 -0
  66. package/.history/src/services/api/restTools_20260427142149.js +245 -0
  67. package/.history/src/services/api/restTools_20260427142853.js +230 -0
  68. package/.history/src/services/api/restTools_20260519135558.js +230 -0
  69. package/.history/src/services/api/restTools_20260519140825.js +230 -0
  70. package/.history/src/services/api/restTools_20260519151223.js +230 -0
  71. package/.history/src/utils/indexedDB_20260306150918.js +593 -0
  72. package/.history/src/utils/indexedDB_20260306151301.js +586 -0
  73. package/.idea/af-vue2-client.iml +9 -0
  74. package/.idea/codeStyles/Project.xml +62 -0
  75. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  76. package/.idea/misc.xml +6 -0
  77. package/.idea/modules.xml +1 -1
  78. package/Components.md +60 -60
  79. package/index.js +31 -31
  80. package/jest-transform-stub.js +8 -8
  81. package/jest.setup.js +7 -7
  82. package/package.json +1 -1
  83. package/preview-input-box.html +180 -0
  84. package/src/assets/img/querySlotDemo.svg +15 -15
  85. package/src/base-client/components/common/AmapMarker/AmapPointRendering.vue +120 -120
  86. package/src/base-client/components/common/CitySelect/index.js +3 -3
  87. package/src/base-client/components/common/CitySelect/index.md +109 -109
  88. package/src/base-client/components/common/FormGroupEdit/index.js +3 -3
  89. package/src/base-client/components/common/FormGroupEdit/index.md +43 -43
  90. package/src/base-client/components/common/HIS/HButtons/HButtons.vue +55 -1
  91. package/src/base-client/components/common/HIS/HForm/HForm.vue +1186 -1186
  92. package/src/base-client/components/common/HIS/HTab/HTab.vue +88 -1
  93. package/src/base-client/components/common/JSONToTree/jsontotree.vue +271 -271
  94. package/src/base-client/components/common/PersonSetting/index.js +3 -3
  95. package/src/base-client/components/common/Tree/index.js +2 -2
  96. package/src/base-client/components/common/Upload/index.js +3 -3
  97. package/src/base-client/components/common/XAddNativeForm/index.md +146 -146
  98. package/src/base-client/components/common/XAddReport/XAddReport.vue +16 -1
  99. package/src/base-client/components/common/XCard/XCard.vue +64 -64
  100. package/src/base-client/components/common/XDataDrawer/XDataDrawer.vue +180 -180
  101. package/src/base-client/components/common/XDataDrawer/index.js +3 -3
  102. package/src/base-client/components/common/XDataDrawer/index.md +41 -41
  103. package/src/base-client/components/common/XDescriptions/index.js +3 -3
  104. package/src/base-client/components/common/XDescriptions/index.md +382 -382
  105. package/src/base-client/components/common/XForm/index.md +178 -178
  106. package/src/base-client/components/common/XInput/XInput.vue +32 -1
  107. package/src/base-client/components/common/XInspectionDetailDrawer/index.vue +1 -1
  108. package/src/base-client/components/common/XMarkdownViewer/demo.vue +102 -102
  109. package/src/base-client/components/common/XStepView/XStepView.vue +252 -252
  110. package/src/base-client/components/common/XStepView/index.js +3 -3
  111. package/src/base-client/components/common/XStepView/index.md +31 -31
  112. package/src/base-client/components/common/XTable/index.md +255 -255
  113. package/src/base-client/components/his/HAi/HAi.vue +1177 -436
  114. package/src/base-client/components/his/XList/XList.vue +337 -58
  115. package/src/base-client/components/his/XSidebar/XSidebar.vue +36 -12
  116. package/src/base-client/components/his/XTransfer/index.md +327 -327
  117. package/src/base-client/components/system/DictionaryDetailsView/DictionaryDetailsView.vue +232 -232
  118. package/src/base-client/plugins/Config.js +19 -19
  119. package/src/base-client/plugins/tabs-page-plugin.js +39 -39
  120. package/src/components/Charts/Bar.vue +62 -62
  121. package/src/components/Charts/ChartCard.vue +134 -134
  122. package/src/components/Charts/Liquid.vue +67 -67
  123. package/src/components/Charts/MiniArea.vue +39 -39
  124. package/src/components/Charts/MiniBar.vue +39 -39
  125. package/src/components/Charts/MiniProgress.vue +75 -75
  126. package/src/components/Charts/MiniSmoothArea.vue +40 -40
  127. package/src/components/Charts/Radar.vue +68 -68
  128. package/src/components/Charts/RankList.vue +77 -77
  129. package/src/components/Charts/TagCloud.vue +113 -113
  130. package/src/components/Charts/TransferBar.vue +64 -64
  131. package/src/components/Charts/Trend.vue +82 -82
  132. package/src/components/Charts/chart.less +12 -12
  133. package/src/components/Charts/smooth.area.less +13 -13
  134. package/src/components/NumberInfo/NumberInfo.vue +54 -54
  135. package/src/components/NumberInfo/index.js +3 -3
  136. package/src/components/NumberInfo/index.less +54 -54
  137. package/src/components/NumberInfo/index.md +43 -43
  138. package/src/components/STable/index.js +953 -953
  139. package/src/components/card/ChartCard.vue +79 -79
  140. package/src/components/chart/Bar.vue +60 -60
  141. package/src/components/chart/MiniArea.vue +67 -67
  142. package/src/components/chart/MiniBar.vue +59 -59
  143. package/src/components/chart/MiniProgress.vue +57 -57
  144. package/src/components/chart/Radar.vue +80 -80
  145. package/src/components/chart/RankingList.vue +60 -60
  146. package/src/components/chart/Trend.vue +79 -79
  147. package/src/components/chart/index.less +9 -9
  148. package/src/components/checkbox/ColorCheckbox.vue +157 -157
  149. package/src/components/input/IInput.vue +66 -66
  150. package/src/components/menu/SideMenu.vue +75 -75
  151. package/src/components/menu/menu.js +273 -273
  152. package/src/components/tool/AStepItem.vue +60 -60
  153. package/src/layouts/CommonLayout.vue +56 -56
  154. package/src/lib.js +1 -1
  155. package/src/mock/extend/index.js +84 -84
  156. package/src/mock/goods/index.js +108 -108
  157. package/src/pages/dashboard/workplace/WorkPlace.vue +141 -141
  158. package/src/pages/system/dictionary/index.vue +44 -44
  159. package/src/pages/system/monitor/loginInfor/index.vue +37 -37
  160. package/src/pages/system/monitor/operLog/index.vue +37 -37
  161. package/src/services/api/cas.js +79 -79
  162. package/src/store/modules/setting.js +119 -119
  163. package/src/utils/errorCode.js +6 -6
  164. package//350/277/201/347/247/273/346/227/245/345/277/227.md +15 -15
  165. package/.idea/MarsCodeWorkspaceAppSettings.xml +0 -7
  166. package/.idea/google-java-format.xml +0 -6
  167. package/.idea/inspectionProfiles/Project_Default.xml +0 -24
  168. package/.idea/jsLinters/eslint.xml +0 -6
  169. package/.idea/vue2-client.iml +0 -12
  170. package/.vscode/settings.json +0 -28
@@ -0,0 +1,643 @@
1
+ <template>
2
+ <div class="x-markdown-viewer-wrapper">
3
+ <div
4
+ v-if="showControlButton"
5
+ class="stream-control-bar"
6
+ >
7
+ <a-button
8
+ class="stream-btn"
9
+ :class="buttonClass"
10
+ @click="handleButtonClick"
11
+ >
12
+ <a-icon :type="getButtonIcon()" />
13
+ <span class="btn-text">{{ getButtonText() }}</span>
14
+ </a-button>
15
+ </div>
16
+ <div ref="containerRef" class="x-markdown-viewer">
17
+ <template v-for="(seg, idx) in segments">
18
+ <div
19
+ v-if="seg.type === 'md'"
20
+ :key="`md-${idx}`"
21
+ class="markdown-body"
22
+ v-html="renderMd(seg.content)"
23
+ />
24
+ <XList
25
+ v-else-if="seg.type === 'component' && seg.componentType === 'x-list'"
26
+ :key="`xlist-${idx}`"
27
+ :queryParamsName="seg.configName"
28
+ :initialData="seg.data"
29
+ />
30
+ </template>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script>
36
+ import { marked } from 'marked'
37
+ import { parseMarkdownWithJsonBlocks } from './parseMarkdownWithJsonBlocks'
38
+ import XList from '@vue2-client/base-client/components/his/XList/XList.vue'
39
+ import { startEventStreamPOST } from '@vue2-client/services/api/restTools'
40
+ import { getConfigByName } from '@vue2-client/services/api/common'
41
+ import moment from 'moment/moment'
42
+
43
+ /**
44
+ * HTML 转义,防止 XSS
45
+ */
46
+ function escapeHtml (s) {
47
+ if (s == null) return ''
48
+ return String(s)
49
+ .replace(/&/g, '&amp;')
50
+ .replace(/</g, '&lt;')
51
+ .replace(/>/g, '&gt;')
52
+ .replace(/"/g, '&quot;')
53
+ .replace(/'/g, '&#39;')
54
+ }
55
+
56
+ /**
57
+ * 标准化 markdown 输入,支持多种格式
58
+ */
59
+ function normalizeMdInput (v, onWarn) {
60
+ if (typeof v === 'string') return v === '[object Object]' ? '' : v
61
+ if (v && typeof v === 'object') {
62
+ if (typeof v.default === 'string') return v.default
63
+ if (typeof v.content === 'string') return v.content
64
+ if (typeof onWarn === 'function') onWarn(v)
65
+ return ''
66
+ }
67
+ const s = v == null ? '' : String(v)
68
+ return s === '[object Object]' ? '' : s
69
+ }
70
+
71
+ /**
72
+ * 判断是否为 Blob/File 对象
73
+ */
74
+ function isBlobLike (v) {
75
+ if (!v || typeof v !== 'object') return false
76
+ return typeof v.size === 'number' && typeof v.type === 'string' && (typeof v.text === 'function' || typeof FileReader !== 'undefined')
77
+ }
78
+
79
+ /**
80
+ * 将 Blob 读取为文本
81
+ */
82
+ function readBlobAsText (blob) {
83
+ if (blob && typeof blob.text === 'function') return blob.text()
84
+ return new Promise((resolve, reject) => {
85
+ try {
86
+ const reader = new FileReader()
87
+ reader.onload = () => resolve(reader.result == null ? '' : String(reader.result))
88
+ reader.onerror = () => reject(reader.error || new Error('read file error'))
89
+ reader.readAsText(blob)
90
+ } catch (e) {
91
+ reject(e)
92
+ }
93
+ })
94
+ }
95
+
96
+ export default {
97
+ name: 'XMarkdownViewer',
98
+ components: { XList },
99
+ props: {
100
+ /**
101
+ * Markdown 文本(推荐使用 markdown;value 用于兼容 v-model/旧写法)
102
+ */
103
+ markdown: {
104
+ type: [String, Object],
105
+ default: ''
106
+ },
107
+ value: {
108
+ type: [String, Object],
109
+ default: ''
110
+ },
111
+ /**
112
+ * 是否允许 Markdown 中的原始 HTML 被解析渲染
113
+ */
114
+ allowHtml: {
115
+ type: Boolean,
116
+ default: false
117
+ },
118
+ /**
119
+ * marked 解析参数覆盖
120
+ */
121
+ markedOptions: {
122
+ type: Object,
123
+ default: () => ({})
124
+ },
125
+ /**
126
+ * 纯数组 JSON 代码块时默认的 x-list 配置名
127
+ */
128
+ defaultXListConfigName: {
129
+ type: String,
130
+ default: ''
131
+ },
132
+ /**
133
+ * 是否显示控制按钮
134
+ */
135
+ showControlButton: {
136
+ type: Boolean,
137
+ default: true
138
+ },
139
+ /**
140
+ * 按钮配置名称(用于从配置中心获取按钮文字等配置)
141
+ */
142
+ queryParamsName: {
143
+ type: String,
144
+ default: ''
145
+ },
146
+ /**
147
+ * 服务名
148
+ */
149
+ serviceName: {
150
+ type: String,
151
+ default: process.env.VUE_APP_SYSTEM_NAME
152
+ }
153
+ },
154
+ emits: {
155
+ 'start-stream': null,
156
+ 'stop-stream': null,
157
+ 'configEnd': null
158
+ },
159
+ data () {
160
+ return {
161
+ /**
162
+ * 通过实例方法 receiveMarkdown() 写入的 md 内容
163
+ */
164
+ internalMarkdown: null,
165
+ /**
166
+ * 容器 DOM 引用
167
+ */
168
+ containerRef: null,
169
+ /**
170
+ * 是否正在流式输出
171
+ */
172
+ isStreaming: false,
173
+ /**
174
+ * 流是否已结束
175
+ */
176
+ streamEnded: false,
177
+ /**
178
+ * 按钮配置(从配置中心加载,无默认值)
179
+ */
180
+ buttonConfig: {},
181
+ /**
182
+ * 环境变量
183
+ */
184
+ env: process.env.NODE_ENV || 'production',
185
+ /**
186
+ * 是否始终显示按钮
187
+ */
188
+ showButtonInitially: true,
189
+ /**
190
+ * 配置是否已加载完成
191
+ */
192
+ // eslint-disable-next-line vue/no-reserved-keys
193
+ configEndEmitted: false,
194
+ /**
195
+ * 配置结束 Promise
196
+ */
197
+ // eslint-disable-next-line vue/no-reserved-keys
198
+ configEndPromise: null,
199
+ /**
200
+ * 配置结束 resolve 函数
201
+ */
202
+ // eslint-disable-next-line vue/no-reserved-keys
203
+ configEndResolve: null
204
+ }
205
+ },
206
+ /**
207
+ * 监听按钮配置名称变化,自动加载配置
208
+ */
209
+ watch: {
210
+ queryParamsName: {
211
+ immediate: true,
212
+ handler (newVal) {
213
+ this.loadButtonConfig(newVal)
214
+ },
215
+ deep: true
216
+ }
217
+ },
218
+ computed: {
219
+ buttonClass() {
220
+ return {
221
+ 'is-streaming': this.isStreaming
222
+ }
223
+ },
224
+ normalizedMd () {
225
+ if (this.internalMarkdown !== null && this.internalMarkdown !== undefined) {
226
+ return normalizeMdInput(this.internalMarkdown, v => {
227
+ console.warn('[XMarkdownViewer] internalMarkdown 期望是字符串,但收到对象:', v)
228
+ })
229
+ }
230
+ const v = (this.markdown != null && this.markdown !== '') ? this.markdown : (this.value || '')
231
+ return normalizeMdInput(v, vv => {
232
+ console.warn('[XMarkdownViewer] markdown/value 期望是字符串,但收到对象:', vv)
233
+ })
234
+ },
235
+ segments () {
236
+ return parseMarkdownWithJsonBlocks(this.normalizedMd, {
237
+ defaultXListConfigName: this.defaultXListConfigName || undefined
238
+ })
239
+ }
240
+ },
241
+ methods: {
242
+ /**
243
+ * 初始化按钮配置
244
+ */
245
+ initButtonConfig(parameter) {
246
+ this.configEndEmitted = false
247
+ this.configEndPromise = new Promise(resolve => { this.configEndResolve = resolve })
248
+ if (!parameter) {
249
+ this._emitConfigEnd()
250
+ return
251
+ }
252
+ this.loadButtonConfig(parameter)
253
+ },
254
+ /**
255
+ * 配置加载结束后发出事件
256
+ */
257
+ _emitConfigEnd () {
258
+ if (this.configEndEmitted) return
259
+ this.configEndEmitted = true
260
+ if (this.configEndResolve) {
261
+ this.configEndResolve()
262
+ this.configEndResolve = null
263
+ }
264
+ this.$emit('configEnd')
265
+ },
266
+ /**
267
+ * 等待配置结束的异步方法
268
+ */
269
+ waitConfigEnd () {
270
+ if (this.configEndEmitted) return Promise.resolve()
271
+ return this.configEndPromise || Promise.resolve()
272
+ },
273
+ /**
274
+ * 通过组件实例接收 md 文件
275
+ */
276
+ async receiveMarkdown (file) {
277
+ if (!isBlobLike(file)) {
278
+ console.warn('[XMarkdownViewer] receiveMarkdown 仅支持 File/Blob,但收到:', file)
279
+ this.internalMarkdown = ''
280
+ return ''
281
+ }
282
+ const text = await readBlobAsText(file)
283
+ this.internalMarkdown = text
284
+ return text
285
+ },
286
+ /**
287
+ * 接收字符串格式的 markdown 内容
288
+ */
289
+ receiveMarkdownString (markdownText) {
290
+ if (typeof markdownText !== 'string') {
291
+ console.warn('[XMarkdownViewer] receiveMarkdownString 期望接收字符串,但收到:', markdownText)
292
+ this.internalMarkdown = ''
293
+ return ''
294
+ }
295
+ this.internalMarkdown = markdownText
296
+ return markdownText
297
+ },
298
+ /**
299
+ * receiveMarkdown 的别名
300
+ */
301
+ async receiveFile (file) {
302
+ return await this.receiveMarkdown(file)
303
+ },
304
+ /**
305
+ * 清除通过 receiveMarkdown() 写入的内容
306
+ */
307
+ clearReceivedMarkdown () {
308
+ this.internalMarkdown = null
309
+ },
310
+ /**
311
+ * 渲染 Markdown
312
+ */
313
+ renderMd (text) {
314
+ const s = this.allowHtml ? (text || '') : escapeHtml(text || '')
315
+ try {
316
+ return marked.parse(s, {
317
+ breaks: true,
318
+ gfm: true,
319
+ headerIds: false,
320
+ mangle: false,
321
+ ...this.markedOptions
322
+ })
323
+ } catch (e) {
324
+ console.error('[XMarkdownViewer] Markdown 渲染错误:', e)
325
+ return s
326
+ }
327
+ },
328
+ /**
329
+ * 调用 AI 流式接口
330
+ */
331
+ callAiStream (userMessage, options = {}) {
332
+ const {
333
+ chatId = this._aiChatId || (this._aiChatId = moment().format('YYYYMMDDHHmmss')),
334
+ model = '',
335
+ prompt = '',
336
+ useContext = true,
337
+ onChunk,
338
+ onEnd,
339
+ onError
340
+ } = options
341
+
342
+ let currentContent = ''
343
+ let stopConnection = null
344
+
345
+ this.isStreaming = true
346
+ this.streamEnded = false
347
+
348
+ stopConnection = startEventStreamPOST(
349
+ '/his-web/api/af-his/ai/chat/stream',
350
+ {
351
+ userMessage,
352
+ chatId,
353
+ model,
354
+ prompt,
355
+ useContext
356
+ },
357
+ {},
358
+ (data, type) => {
359
+ if (type === 'additionalInfo' || type === 'sourceInfo') {
360
+ return
361
+ }
362
+ let textChunk = ''
363
+ if (typeof data === 'object' && data !== null) {
364
+ const choices = data.choices
365
+ if (Array.isArray(choices) && choices.length > 0) {
366
+ const delta = choices[0].delta
367
+ if (delta && typeof delta.content === 'string') {
368
+ textChunk = delta.content
369
+ }
370
+ }
371
+ } else if (typeof data === 'string') {
372
+ textChunk = data
373
+ }
374
+
375
+ if (textChunk) {
376
+ // 检测后端是否发送了累积内容(而非增量)导致重复
377
+ // 策略:如果新内容以当前内容开头,且长度差异明显(ratio > 1.5),认为是累积内容
378
+ if (currentContent.length > 0 && textChunk.startsWith(currentContent)) {
379
+ const ratio = textChunk.length / currentContent.length
380
+ if (ratio > 1.5) {
381
+ // 后端发送了累积内容,只取新增部分
382
+ const newPart = textChunk.slice(currentContent.length)
383
+ currentContent += newPart
384
+ } else {
385
+ // 正常增量更新,直接追加
386
+ currentContent += textChunk
387
+ }
388
+ } else {
389
+ // 正常情况或无法判断时直接追加
390
+ currentContent += textChunk
391
+ }
392
+ // 同步更新 internalMarkdown 以触发视图更新
393
+ this.internalMarkdown = currentContent
394
+ onChunk?.(textChunk, currentContent)
395
+ this.scrollToBottom()
396
+ }
397
+ },
398
+ (error) => {
399
+ this.isStreaming = false
400
+ this.streamEnded = true
401
+ onError?.(error.message || '请求失败')
402
+ },
403
+ () => {
404
+ this.isStreaming = false
405
+ this.streamEnded = true
406
+ onEnd?.(currentContent)
407
+ this.scrollToBottom()
408
+ }
409
+ )
410
+
411
+ this._currentStreamConnection = {
412
+ stop: () => {
413
+ stopConnection?.()
414
+ },
415
+ getContent: () => currentContent
416
+ }
417
+
418
+ return this._currentStreamConnection
419
+ },
420
+ /**
421
+ * 停止当前的流式请求
422
+ */
423
+ stopStream() {
424
+ this._currentStreamConnection?.stop()
425
+ this.isStreaming = false
426
+ this.streamEnded = true
427
+ },
428
+ /**
429
+ * 处理按钮点击
430
+ */
431
+ handleButtonClick() {
432
+ if (this.buttonConfig.eventDisabled) {
433
+ return
434
+ }
435
+
436
+ if (this.isStreaming) {
437
+ this.stopStream()
438
+ this.$emit('stop-stream')
439
+ } else {
440
+ this.streamEnded = false
441
+ const startEvent = this.buttonConfig.startClickEventName || 'start-stream'
442
+ console.log('[XMarkdownViewer] 触发开始事件:', startEvent)
443
+ this.$emit(startEvent)
444
+ }
445
+ },
446
+ /**
447
+ * 获取当前累积的内容
448
+ */
449
+ getStreamContent() {
450
+ return this._currentStreamConnection?.getContent() || ''
451
+ },
452
+ /**
453
+ * 滚动到底部
454
+ */
455
+ scrollToBottom() {
456
+ if (this.$refs.containerRef) {
457
+ this.$nextTick(() => {
458
+ const container = this.$refs.containerRef
459
+ container.scrollTop = container.scrollHeight
460
+ })
461
+ }
462
+ },
463
+ /**
464
+ * 重置流状态
465
+ */
466
+ resetStreamState() {
467
+ this.isStreaming = false
468
+ this.streamEnded = false
469
+ },
470
+ /**
471
+ * 加载按钮配置
472
+ */
473
+ loadButtonConfig(configName) {
474
+ if (!configName) {
475
+ console.warn('[XMarkdownViewer] queryParamsName 为空,跳过加载')
476
+ this._emitConfigEnd()
477
+ return
478
+ }
479
+ console.log('[XMarkdownViewer] 开始加载按钮配置:', configName)
480
+
481
+ // 优先使用传入的 serviceName,否则使用 'af-his' 确保获取到正确的配置
482
+ const serviceName = this.serviceName || 'af-his'
483
+ console.log('[XMarkdownViewer] 使用服务名:', serviceName)
484
+
485
+ getConfigByName(configName, serviceName, (res) => {
486
+ console.log('[XMarkdownViewer] 配置加载回调, res:', res)
487
+ if (!res) {
488
+ console.warn('[XMarkdownViewer] 按钮配置未找到:', configName)
489
+ this._emitConfigEnd()
490
+ return
491
+ }
492
+ console.log('[XMarkdownViewer] 应用配置:', res)
493
+ this.buttonConfig = {
494
+ ...res
495
+ }
496
+ if (typeof res.showButtonInitially === 'boolean') {
497
+ this.showButtonInitially = res.showButtonInitially
498
+ }
499
+ console.log('[XMarkdownViewer] buttonConfig 已更新:', this.buttonConfig)
500
+ this._emitConfigEnd()
501
+ }, this.env === 'dev')
502
+ },
503
+ /**
504
+ * 获取按钮图标
505
+ */
506
+ getButtonIcon() {
507
+ if (this.isStreaming) {
508
+ return this.buttonConfig.stopIcon || 'pause-circle'
509
+ } else if (this.streamEnded) {
510
+ return this.buttonConfig.stoppedIcon || 'check-circle'
511
+ } else {
512
+ return this.buttonConfig.startIcon || 'play-circle'
513
+ }
514
+ },
515
+ /**
516
+ * 获取按钮文字
517
+ */
518
+ getButtonText() {
519
+ if (this.isStreaming) {
520
+ return this.buttonConfig.stopText || '停止'
521
+ } else if (this.streamEnded) {
522
+ return this.buttonConfig.startText || '开始'
523
+ } else {
524
+ return this.buttonConfig.startText || '开始'
525
+ }
526
+ }
527
+ },
528
+ mounted() {
529
+ this.containerRef = this.$refs.containerRef
530
+ }
531
+ }
532
+ </script>
533
+
534
+ <style lang="less" scoped>
535
+ .x-markdown-viewer-wrapper {
536
+ width: 100%;
537
+ display: flex;
538
+ flex-direction: column;
539
+ opacity: 1;
540
+ background: #FFFFFF;
541
+ box-sizing: border-box;
542
+ border: 1px solid #E5E9F0;
543
+ padding: 5px;
544
+
545
+ .stream-control-bar {
546
+ display: flex;
547
+ justify-content: flex-end;
548
+ padding: 5px;
549
+ margin-bottom: 5px;
550
+ border-bottom: 1px solid #e8e8e8;
551
+
552
+ .stream-btn {
553
+ display: inline-flex;
554
+ align-items: center;
555
+ justify-content: center;
556
+ gap: 6px;
557
+ width: 110px;
558
+ height: 32px;
559
+ padding: 0;
560
+ font-family: "Source Han Sans";
561
+ font-size: 16px;
562
+ font-weight: normal;
563
+ letter-spacing: 0em;
564
+ line-height: normal;
565
+ font-feature-settings: "kern" on;
566
+ color: #5D5C5C;
567
+ opacity: 1;
568
+ background: #FFFFFF;
569
+ border: 1px solid #9499A0;
570
+ border-radius: 6px;
571
+ box-sizing: border-box;
572
+ margin-right: 5px;
573
+
574
+ &.is-streaming {
575
+ background: #fff2f0;
576
+ border-color: #ff4d4f;
577
+ color: #ff4d4f;
578
+
579
+ &:hover {
580
+ background: #fff1f0;
581
+ border-color: #ff7875;
582
+ color: #ff7875;
583
+ }
584
+ }
585
+
586
+ }
587
+ }
588
+
589
+ .x-markdown-viewer {
590
+ width: 100%;
591
+ max-height: 350px;
592
+ overflow-y: auto;
593
+ overflow-x: hidden;
594
+ }
595
+ }
596
+
597
+ .markdown-body {
598
+ font-family: "Source Han Sans";
599
+ font-size: 16px;
600
+ font-weight: normal;
601
+ line-height: normal;
602
+ text-align: justify;
603
+ letter-spacing: 0em;
604
+ font-feature-settings: "kern" on;
605
+ color: #313131;
606
+ word-break: break-word;
607
+ }
608
+
609
+ .markdown-body h1,
610
+ .markdown-body h2,
611
+ .markdown-body h3,
612
+ .markdown-body h4,
613
+ .markdown-body h5,
614
+ .markdown-body h6 {
615
+ margin-top: 16px;
616
+ margin-bottom: 8px;
617
+ font-weight: 600;
618
+ }
619
+
620
+ .markdown-body p {
621
+ margin: 0 0 12px;
622
+ }
623
+
624
+ .markdown-body ul,
625
+ .markdown-body ol {
626
+ padding-left: 2em;
627
+ margin: 0 0 12px;
628
+ }
629
+
630
+ .markdown-body pre {
631
+ background-color: #f6f8fa;
632
+ border-radius: 6px;
633
+ padding: 12px;
634
+ overflow: auto;
635
+ }
636
+
637
+ .markdown-body code {
638
+ background-color: #f6f8fa;
639
+ border-radius: 3px;
640
+ padding: 0.2em 0.4em;
641
+ font-size: 85%;
642
+ }
643
+ </style>