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,650 @@
1
+ <template>
2
+ <div class="ai-consultation-outer">
3
+ <div class="ai-consultation-container">
4
+ <div class="chat-area">
5
+ <!-- 对话区域 -->
6
+ <div class="dialog-area">
7
+ <template v-for="(item, index) in displayItems">
8
+ <div
9
+ v-if="item.type === 'divider'"
10
+ :key="`divider-${index}`"
11
+ class="dialog-divider"
12
+ >
13
+ <span class="dialog-divider-text">{{ item.text }}</span>
14
+ </div>
15
+ <div
16
+ v-else
17
+ :key="`msg-${index}`"
18
+ :class="['dialog-message', item.type]"
19
+ >
20
+ <div class="message-text" v-html="renderMd(item.content)" />
21
+ <span v-if="isAiStreaming && index === displayItems.length - 1 && item.type === 'ai'" class="typing-cursor">▍</span>
22
+ </div>
23
+ </template>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="bottom-input-wrapper">
28
+ <div class="input-container">
29
+ <div class="input-textarea-area">
30
+ <a-textarea
31
+ ref="consultTextarea"
32
+ v-model="doctorInputText"
33
+ placeholder="医生补充信息或提问..."
34
+ class="bottom-input"
35
+ :rows="1"
36
+ resize="none"
37
+ @input="handleInput"
38
+ @keydown="handleKeydown"
39
+ ></a-textarea>
40
+ </div>
41
+
42
+ <input
43
+ ref="uploadInput"
44
+ type="file"
45
+ style="display: none"
46
+ @change="handleFileChange"
47
+ multiple
48
+ accept="image/*,.pdf,.doc,.docx"
49
+ />
50
+
51
+ <div class="input-controls">
52
+ <div class="input-controls-left">
53
+ <a-button class="upload-btn" @click="triggerUpload">
54
+ <a-icon type="plus" class="upload-icon" />
55
+ <span class="upload-text">上传(报告、图片)</span>
56
+ </a-button>
57
+ </div>
58
+
59
+ <div class="input-controls-right">
60
+ <span class="enter-tip">Enter</span>
61
+ <a-button
62
+ type="primary"
63
+ shape="circle"
64
+ class="send-btn"
65
+ @click="handleSendMessage"
66
+ :disabled="!doctorInputText.trim()"
67
+ >
68
+ <span class="send-icon">↑</span>
69
+ </a-button>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </template>
77
+
78
+ <script>
79
+ import { startEventStreamPOST } from '@vue2-client/services/api/restTools'
80
+ import { marked } from 'marked'
81
+ import moment from 'moment/moment'
82
+ import { mapState } from 'vuex'
83
+
84
+ export default {
85
+ name: 'AIConsultation',
86
+ data() {
87
+ return {
88
+ currentDialogId: null,
89
+ doctorInputText: '',
90
+ isPlaying: false,
91
+ isRecording: false,
92
+ patientComplaint: '',
93
+ doctorQuestions: [],
94
+ examinationOrder: null,
95
+ aiDiagnosis: null,
96
+ dialogMessages: [],
97
+ currentStreamConnection: null,
98
+ isAiStreaming: false,
99
+ resizeTimeout: null
100
+ }
101
+ },
102
+ computed: {
103
+ ...mapState('account', {currUser: 'user'}),
104
+ // 把 dialogMessages 与日期分隔符合并成最终渲染列表
105
+ displayItems() {
106
+ const items = []
107
+ let lastDateKey = ''
108
+ this.dialogMessages.forEach(msg => {
109
+ const dateKey = this.getDateKey(msg.timestamp)
110
+ if (dateKey && dateKey !== lastDateKey) {
111
+ items.push({ type: 'divider', text: this.getDateLabel(msg.timestamp) })
112
+ lastDateKey = dateKey
113
+ }
114
+ items.push(msg)
115
+ })
116
+ return items
117
+ }
118
+ },
119
+ mounted() {
120
+ this.initTestData()
121
+ this.$nextTick(() => {
122
+ this.autoResizeTextarea()
123
+ })
124
+ },
125
+ beforeDestroy() {
126
+ // 组件销毁时关闭流式连接
127
+ if (this.currentStreamConnection) {
128
+ this.currentStreamConnection()
129
+ this.currentStreamConnection = null
130
+ }
131
+ if (this.resizeTimeout) {
132
+ clearTimeout(this.resizeTimeout)
133
+ }
134
+ },
135
+ methods: {
136
+ autoResizeTextarea() {
137
+ this.$nextTick(() => {
138
+ const textarea = this.$refs.consultTextarea?.$el || this.$refs.consultTextarea
139
+ if (!textarea) return
140
+
141
+ textarea.style.height = 'auto'
142
+ const scrollHeight = textarea.scrollHeight
143
+ textarea.style.height = `${Math.min(Math.max(scrollHeight, 120), 250)}px`
144
+ })
145
+ },
146
+
147
+ handleInput() {
148
+ if (this.resizeTimeout) clearTimeout(this.resizeTimeout)
149
+ this.resizeTimeout = setTimeout(() => {
150
+ this.autoResizeTextarea()
151
+ }, 16)
152
+ },
153
+
154
+ renderMd(text) {
155
+ if (!text) return ''
156
+ try {
157
+ return marked.parse(text, { breaks: true, gfm: true, headerIds: false, mangle: false })
158
+ } catch {
159
+ return text
160
+ }
161
+ },
162
+
163
+ handleKeydown(event) {
164
+ if (event.key === 'Enter' && !event.shiftKey) {
165
+ event.preventDefault()
166
+ this.handleSendMessage()
167
+ }
168
+ },
169
+
170
+ initDialog() {
171
+ if (!this.currentDialogId) {
172
+ this.currentDialogId = moment().format('YYYYMMDDHHmmss')
173
+ }
174
+ },
175
+
176
+ clearDialog() {
177
+ this.currentDialogId = moment().format('YYYYMMDDHHmmss')
178
+ this.dialogMessages = []
179
+ },
180
+
181
+ handleSendMessage() {
182
+ if (!this.doctorInputText.trim()) return
183
+
184
+ // 确保有dialogId
185
+ if (!this.currentDialogId) {
186
+ this.initDialog()
187
+ }
188
+
189
+ const messageContent = this.doctorInputText.trim()
190
+ const doctorMessage = {
191
+ type: 'user',
192
+ content: messageContent,
193
+ time: new Date().toLocaleTimeString(),
194
+ timestamp: Date.now()
195
+ }
196
+ this.dialogMessages.push(doctorMessage)
197
+ this.scrollToBottom()
198
+
199
+ this.doctorInputText = ''
200
+ this.autoResizeTextarea()
201
+
202
+ const chatId = this.currentDialogId
203
+ let aiContent = ''
204
+ const aiMsgIndex = this.dialogMessages.length
205
+ this.dialogMessages.push({
206
+ type: 'ai',
207
+ content: '',
208
+ time: new Date().toLocaleTimeString(),
209
+ timestamp: Date.now()
210
+ })
211
+ this.scrollToBottom()
212
+
213
+ this.isAiStreaming = true
214
+
215
+ this.currentStreamConnection = startEventStreamPOST(
216
+ '/his-web/api/af-his/ai/chat/stream',
217
+ { userMessage: messageContent, chatId, model: '', prompt: '', useContext: true, user: this.currUser.operInfo },
218
+ {'Content-Type': 'application/json'},
219
+ (data, type) => {
220
+ if (type === 'additionalInfo' || type === 'sourceInfo') return
221
+
222
+ let textChunk = ''
223
+ if (typeof data === 'object' && data !== null) {
224
+ const choices = data.choices
225
+ if (Array.isArray(choices) && choices.length > 0) {
226
+ const delta = choices[0].delta
227
+
228
+ // 优先取 delta.content(标准 OpenAI 格式)
229
+ if (delta && typeof delta.content === 'string' && delta.content) {
230
+ textChunk = delta.content
231
+ }
232
+
233
+ // 兼容百川模型的 thinking 字段
234
+ if (!textChunk && delta && delta.thinking) {
235
+ const thinking = delta.thinking
236
+ // 取 summary 描述
237
+ if (thinking.summary && typeof thinking.summary === 'string') {
238
+ textChunk = `[思考中] ${thinking.summary}`
239
+ }
240
+ // 取 reasoning steps
241
+ if (Array.isArray(thinking.steps)) {
242
+ const activeStep = thinking.steps.find(s => s.status === 'in_progress')
243
+ if (activeStep && activeStep.label) {
244
+ textChunk = `[思考中] ${activeStep.label}`
245
+ }
246
+ }
247
+ }
248
+
249
+ // 检测模型拒答
250
+ const finishReason = choices[0].finish_reason
251
+ if (finishReason === 'refuse_answer') {
252
+ const refuseTip = '\n\n> ⚠️ 很抱歉,当前问题我无法回答,请尝试调整提问方式或联系管理员。'
253
+ if (!this.dialogMessages[aiMsgIndex].content.includes('无法回答')) {
254
+ this.dialogMessages[aiMsgIndex].content += refuseTip
255
+ this.isAiStreaming = false
256
+ this.scrollToBottom()
257
+ }
258
+ }
259
+ }
260
+ } else if (typeof data === 'string') {
261
+ textChunk = data
262
+ }
263
+
264
+ if (textChunk) {
265
+ aiContent += textChunk
266
+ this.dialogMessages[aiMsgIndex].content = aiContent
267
+ this.scrollToBottom()
268
+ }
269
+ },
270
+ (error) => {
271
+ this.isAiStreaming = false
272
+ this.dialogMessages[aiMsgIndex].content += `\n\n[错误: ${error.message || '请求失败'}]`
273
+ this.scrollToBottom()
274
+ },
275
+ () => {
276
+ this.isAiStreaming = false
277
+ this.scrollToBottom()
278
+ }
279
+ )
280
+ },
281
+
282
+ setPatientComplaint(complaint) {
283
+ this.patientComplaint = complaint
284
+ },
285
+
286
+ scrollToBottom(delay = 0) {
287
+ this.$nextTick(() => {
288
+ setTimeout(() => {
289
+ const chatArea = document.querySelector('.chat-area')
290
+ if (chatArea) {
291
+ chatArea.scrollTop = chatArea.scrollHeight
292
+ }
293
+ }, delay)
294
+ })
295
+ },
296
+
297
+ reset() {
298
+ if (this.currentStreamConnection) {
299
+ this.currentStreamConnection()
300
+ this.currentStreamConnection = null
301
+ }
302
+ this.isAiStreaming = false
303
+ this.patientComplaint = ''
304
+ this.doctorQuestions = []
305
+ this.examinationOrder = null
306
+ this.aiDiagnosis = null
307
+ this.doctorInputText = ''
308
+ this.isPlaying = false
309
+ this.isRecording = false
310
+ this.dialogMessages = []
311
+ this.$nextTick(() => {
312
+ this.autoResizeTextarea()
313
+ const chatArea = document.querySelector('.chat-area')
314
+ if (chatArea) {
315
+ chatArea.scrollTop = 0
316
+ }
317
+ })
318
+ },
319
+
320
+ triggerUpload() {
321
+ this.$refs.uploadInput.click()
322
+ },
323
+
324
+ handleFileChange(event) {
325
+ const files = Array.from(event.target.files || [])
326
+ if (!files.length) return
327
+
328
+ this.dialogMessages.push({
329
+ type: 'user',
330
+ content: `已上传文件:${files.map(f => f.name).join('、')}`,
331
+ time: new Date().toLocaleTimeString(),
332
+ timestamp: Date.now()
333
+ })
334
+ this.scrollToBottom()
335
+ event.target.value = ''
336
+ },
337
+
338
+ // 初始化测试数据:两天内容(昨天 + 今天),不调用接口
339
+ initTestData() {
340
+ const now = moment()
341
+ const today = now.clone().startOf('day')
342
+ const yesterday = today.clone().subtract(1, 'day')
343
+
344
+ this.dialogMessages = [
345
+ // 昨天 - 上午
346
+ {
347
+ type: 'user',
348
+ content: '患者男,65岁,主诉反复胸闷、心悸2年,加重伴乏力1周。既往有高血压病史10年,长期口服氨氯地平。',
349
+ time: '09:12',
350
+ timestamp: yesterday.clone().hour(9).minute(12).valueOf()
351
+ },
352
+ {
353
+ type: 'ai',
354
+ content: '**初步分析:**\n\n1. 患者老年男性,有高血压病史,症状提示可能存在 **冠心病、心律失常** 或 **心力衰竭**\n2. 建议完善以下检查:\n - 心电图(ECG)\n - 心脏彩超\n - 动态心电图(Holter)\n - 血脂、血糖、BNP\n\n3. 注意鉴别:\n - 甲状腺功能异常\n - 电解质紊乱\n - 药物影响',
355
+ time: '09:13',
356
+ timestamp: yesterday.clone().hour(9).minute(13).valueOf()
357
+ },
358
+ {
359
+ type: 'user',
360
+ content: '心电图提示:窦性心律,ST段压低,T波倒置。心脏彩超:左室舒张功能减退,EF 55%。',
361
+ time: '10:25',
362
+ timestamp: yesterday.clone().hour(10).minute(25).valueOf()
363
+ },
364
+ {
365
+ type: 'ai',
366
+ content: '**进一步分析:**\n\n根据检查结果:\n- ST段压低、T波倒置:考虑 **心肌缺血**\n- 左室舒张功能减退:提示 **舒张性心衰(HFrEF 临界)**\n- EF 55% 处于正常下限\n\n**建议:**\n1. 加用 **阿司匹林** 抗血小板治疗\n2. 加用 **他汀类** 调脂稳斑\n3. 考虑 **ACEI/ARB** 控制血压、逆转心肌重构\n4. 必要时行 **冠脉造影** 明确冠脉病变\n5. 密切监测血压、心率',
367
+ time: '10:27',
368
+ timestamp: yesterday.clone().hour(10).minute(27).valueOf()
369
+ },
370
+ // 昨天 - 下午
371
+ {
372
+ type: 'user',
373
+ content: '冠脉造影结果:LAD 中段狭窄 70%,LCX 远端狭窄 40%。',
374
+ time: '15:40',
375
+ timestamp: yesterday.clone().hour(15).minute(40).valueOf()
376
+ },
377
+ {
378
+ type: 'ai',
379
+ content: '**冠脉病变评估:**\n\n- **LAD 中段 70% 狭窄**:临界病变,建议 PCI 干预\n- **LCX 远端 40% 狭窄**:轻度,可药物保守治疗\n\n**治疗方案:**\n1. **LAD 行 PCI + 支架植入**\n2. 术后双联抗血小板治疗(阿司匹林 + 氯吡格雷)至少 12 个月\n3. 强化他汀治疗至 LDL < 1.4 mmol/L\n4. 控制血压 < 130/80 mmHg\n5. 心肺康复、规律运动',
380
+ time: '15:42',
381
+ timestamp: yesterday.clone().hour(15).minute(42).valueOf()
382
+ },
383
+ // 今天 - 上午
384
+ {
385
+ type: 'user',
386
+ content: 'PCI 术后第 1 天,患者诉胸闷较前明显好转,无胸痛,复查心电图未见明显 ST-T 改变。',
387
+ time: '08:30',
388
+ timestamp: today.clone().hour(8).minute(30).valueOf()
389
+ },
390
+ {
391
+ type: 'ai',
392
+ content: '**术后评估:**\n\n术后恢复良好,症状改善明显。\n\n**今日医嘱:**\n1. 继续双联抗血小板治疗\n2. 监测心电图、心肌酶变化\n3. 观察穿刺点有无出血、血肿\n4. 鼓励早期下床活动\n5. 出院前评估:\n - 心脏彩超复查\n - 24小时动态心电图\n - 出院带药及健康宣教',
393
+ time: '08:31',
394
+ timestamp: today.clone().hour(8).minute(31).valueOf()
395
+ },
396
+ {
397
+ type: 'user',
398
+ content: '好的,麻烦帮我整理一下完整的出院小结模板。',
399
+ time: '09:45',
400
+ timestamp: today.clone().hour(9).minute(45).valueOf()
401
+ }
402
+ ]
403
+ },
404
+
405
+ // 获取时间戳的日期键(YYYY-MM-DD)
406
+ getDateKey(timestamp) {
407
+ if (!timestamp) return ''
408
+ return moment(timestamp).format('YYYY-MM-DD')
409
+ },
410
+
411
+ // 解析日期键为本地日期对象
412
+ parseDateKey(dateKey) {
413
+ return moment(dateKey, 'YYYY-MM-DD')
414
+ },
415
+
416
+ // 把日期戳格式化为"今天 / 昨天 / 具体日期"
417
+ getDateLabel(timestamp) {
418
+ if (!timestamp) return ''
419
+ const m = moment(timestamp)
420
+ const today = moment().startOf('day')
421
+ const yesterday = today.clone().subtract(1, 'day')
422
+ const target = m.clone().startOf('day')
423
+
424
+ if (target.isSame(today, 'day')) return '今天'
425
+ if (target.isSame(yesterday, 'day')) return '昨天'
426
+ // 跨年显示年份
427
+ if (target.year() !== today.year()) {
428
+ return m.format('YYYY年M月D日')
429
+ }
430
+ return m.format('M月D日')
431
+ }
432
+ }
433
+ }
434
+ </script>
435
+
436
+ <style scoped>
437
+ .ai-consultation-outer {
438
+ box-sizing: border-box;
439
+ margin: 0;
440
+ padding: 0;
441
+ width: 100%;
442
+ height: 100%;
443
+ }
444
+
445
+ .ai-consultation-container {
446
+ width: 100%;
447
+ height: 100%;
448
+ display: flex;
449
+ flex-direction: column;
450
+ background: #FFFFFF;
451
+ border: 1px solid #E5E9F0;
452
+ }
453
+
454
+ /* 对话区域容器:初始高度 240px(无内容时),最大高度 690px(内容超出滚动) */
455
+ .chat-area {
456
+ flex: 0 1 auto;
457
+ overflow-y: auto;
458
+ padding: 16px;
459
+ background: #fff;
460
+ /* 初始高度:没有内容时也保留这块区域 */
461
+ min-height: 240px;
462
+ /* 最大高度:内容超过则出现滚动条 */
463
+ max-height: 690px;
464
+ box-sizing: border-box;
465
+ }
466
+
467
+ /* 消息列表:跟随 chat-area 高度 */
468
+ .dialog-area {
469
+ display: flex;
470
+ flex-direction: column;
471
+ min-height: 208px; /* 240(chat-area) - 16*2(padding) = 208 */
472
+ }
473
+
474
+ /* 日期分隔符 */
475
+ .dialog-divider {
476
+ display: flex;
477
+ align-items: center;
478
+ justify-content: center;
479
+ margin: 8px 0 16px;
480
+ }
481
+
482
+ .dialog-divider::before,
483
+ .dialog-divider::after {
484
+ content: '';
485
+ flex: 1;
486
+ height: 1px;
487
+ background: #E5E9F0;
488
+ }
489
+
490
+ .dialog-divider-text {
491
+ padding: 2px 12px;
492
+ font-size: 12px;
493
+ color: #8C8C8C;
494
+ background: #F5F7FA;
495
+ border-radius: 10px;
496
+ margin: 0 12px;
497
+ white-space: nowrap;
498
+ }
499
+
500
+ /* 底部输入框区域 */
501
+ .bottom-input-wrapper {
502
+ flex-shrink: 0;
503
+ padding: 12px 16px;
504
+ background: #fff;
505
+ }
506
+
507
+ .input-container {
508
+ width: 782px;
509
+ margin: 0 auto;
510
+ padding: 12px;
511
+ display: flex;
512
+ flex-direction: column;
513
+ border: 1px solid #E5E9F0;
514
+ border-radius: 6px;
515
+ background: #FFFFFF;
516
+ box-sizing: border-box;
517
+ }
518
+
519
+ .input-textarea-area {
520
+ flex: 1 1 auto;
521
+ min-height: 0;
522
+ margin-bottom: 8px;
523
+ }
524
+
525
+ .bottom-input {
526
+ width: 100%;
527
+ height: 100%;
528
+ border: none !important;
529
+ background: transparent;
530
+ padding: 0;
531
+ font-family: 'Source Han Sans', 'Microsoft YaHei', sans-serif;
532
+ font-size: 16px;
533
+ color: #313131;
534
+ resize: none;
535
+ outline: none;
536
+ box-shadow: none !important;
537
+ }
538
+
539
+ .bottom-input:focus,
540
+ .bottom-input:hover {
541
+ border: none !important;
542
+ box-shadow: none !important;
543
+ }
544
+
545
+ .bottom-input::placeholder {
546
+ color: #999;
547
+ }
548
+
549
+ .input-controls {
550
+ flex-shrink: 0;
551
+ display: flex;
552
+ align-items: center;
553
+ justify-content: space-between;
554
+ padding-top: 0;
555
+ border-top: none;
556
+ }
557
+
558
+ .input-controls-left {
559
+ display: flex;
560
+ align-items: center;
561
+ gap: 8px;
562
+ }
563
+
564
+ .input-controls-right {
565
+ display: flex;
566
+ align-items: center;
567
+ gap: 8px;
568
+ }
569
+
570
+ .enter-tip {
571
+ font-family: 'Inter', sans-serif;
572
+ font-size: 14px;
573
+ font-weight: 500;
574
+ color: #9E9E9E;
575
+ padding: 4px 8px;
576
+ }
577
+
578
+ .send-btn {
579
+ width: 30px;
580
+ height: 30px;
581
+ background: #0057FE;
582
+ border: none;
583
+ border-radius: 50%;
584
+ color: #fff;
585
+ font-size: 14px;
586
+ cursor: pointer;
587
+ display: flex;
588
+ align-items: center;
589
+ justify-content: center;
590
+ padding: 0;
591
+ }
592
+
593
+ .send-btn:hover {
594
+ background: #0040cc;
595
+ }
596
+
597
+ .send-icon {
598
+ font-size: 14px;
599
+ font-weight: bold;
600
+ }
601
+
602
+ /* 对话区域样式 */
603
+ .dialog-message {
604
+ display: flex;
605
+ margin-bottom: 16px;
606
+ align-items: flex-start;
607
+ }
608
+
609
+ .dialog-message:last-child {
610
+ margin-bottom: 0;
611
+ }
612
+
613
+ .dialog-message.ai {
614
+ flex-direction: row;
615
+ }
616
+
617
+ .dialog-message.user {
618
+ flex-direction: row-reverse;
619
+ }
620
+
621
+ .dialog-message.user .message-text {
622
+ text-align: right;
623
+ }
624
+
625
+ .message-text {
626
+ opacity: 1;
627
+ font-family: Source Han Sans;
628
+ font-size: 16px;
629
+ font-weight: normal;
630
+ line-height: normal;
631
+ text-align: justify;
632
+ letter-spacing: 0em;
633
+ font-feature-settings: "kern" on;
634
+ max-width: 100%;
635
+ color: #313131;
636
+ }
637
+
638
+ .typing-cursor {
639
+ display: inline-block;
640
+ margin-left: 2px;
641
+ color: #0057FE;
642
+ font-size: 18px;
643
+ animation: blink 1s step-end infinite;
644
+ }
645
+
646
+ @keyframes blink {
647
+ 0%, 100% { opacity: 1; }
648
+ 50% { opacity: 0; }
649
+ }
650
+ </style>