vue2-client 1.16.51 → 1.16.52
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 +112 -112
- package/src/assets/img/paymentMethod/icon1.png +0 -0
- package/src/assets/img/paymentMethod/icon2.png +0 -0
- package/src/assets/img/paymentMethod/icon3.png +0 -0
- package/src/assets/img/paymentMethod/icon4.png +0 -0
- package/src/assets/img/paymentMethod/icon5.png +0 -0
- package/src/assets/img/paymentMethod/icon6.png +0 -0
- package/src/assets/svg/female.svg +1 -1
- package/src/assets/svg/male.svg +1 -1
- package/src/base-client/components/common/HIS/HButtons/HButtons.vue +380 -371
- package/src/base-client/components/common/HIS/HForm/HForm.vue +9 -0
- package/src/base-client/components/common/HIS/HFormGroup/HFormGroup.vue +120 -120
- package/src/base-client/components/common/HIS/HFormGroup/index.js +3 -3
- package/src/base-client/components/common/HIS/HFormTable/HFormTable.vue +379 -379
- package/src/base-client/components/common/HIS/HTab/HTab.vue +120 -31
- package/src/base-client/components/common/HIS/demo.vue +61 -61
- package/src/base-client/components/common/XCollapse/XCollapse.vue +461 -461
- package/src/base-client/components/common/XInput/XInput.vue +147 -147
- package/src/base-client/components/common/XReport/XReportHospitalizationDemo.vue +45 -0
- package/src/base-client/components/common/XReportGrid/XReportTrGroup.vue +824 -824
- package/src/base-client/components/common/XTab/XTab.vue +4 -0
- package/src/base-client/components/common/XTable/XTable.vue +1610 -1610
- package/src/base-client/components/common/XTimeline/XTimeline.vue +454 -454
- package/src/base-client/components/his/XCharge/testConfig.js +149 -0
- package/src/base-client/components/his/XHisEditor/XHisEditor.vue +705 -705
- package/src/base-client/components/his/XList/XList.vue +829 -829
- package/src/base-client/components/his/XTitle/XTitle.vue +46 -2
- package/src/base-client/components/his/threeTestOrders/editor.vue +113 -113
- package/src/pages/userInfoDetailManage/ExceptionRecordQuery/index.vue +45 -45
- package/src/router/async/router.map.js +132 -129
@@ -1,829 +1,829 @@
|
|
1
|
-
<template>
|
2
|
-
<!-- 列表卡片模式 listMode: card -->
|
3
|
-
<div
|
4
|
-
class="x-list-wrapper"
|
5
|
-
:class="wrapperClassObject"
|
6
|
-
v-if="listMode"
|
7
|
-
ref="listRef"
|
8
|
-
@scroll="handleInfiniteOnLoad">
|
9
|
-
<a-list
|
10
|
-
:grid="{ gutter: 16, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 4 }"
|
11
|
-
:data-source="localData">
|
12
|
-
<a-list-item slot="renderItem" slot-scope="item, index">
|
13
|
-
<div class="card-a-col" :class="{ 'selected-active': enableSelectRow && currentSelectedIndex === index }" :style="getCardStyle(currentTitleValue(index))" @click="handleCardClick(index)">
|
14
|
-
<a-row class="card-row">
|
15
|
-
<a-col class="id-a-col" :span="4" v-for="(detail,idx) in item.filter(d => d.label == label)" :key="idx">
|
16
|
-
{{ detail.value }}
|
17
|
-
<div class="gender-icon" v-if="getHasPatient(item) && getGender(item)">
|
18
|
-
<img :src="getGender(item) === '男' ? maleIcon : femaleIcon" :alt="getGender(item)" class="gender-img" />
|
19
|
-
</div>
|
20
|
-
</a-col>
|
21
|
-
<a-col :span="20" class="id-a-col-2">
|
22
|
-
<template v-for="(detail,idx) in item">
|
23
|
-
<div :key="`title_${idx}`" class="title-row" v-if="detail.type == 'title'">
|
24
|
-
<a-tooltip :title="detail.value" placement="topLeft">
|
25
|
-
<span class="describe-list-a-col" :class="{name: detail.bold}">{{ detail.value }}</span>
|
26
|
-
</a-tooltip>
|
27
|
-
<div class="title-actions" v-if="getTitleOptions && getTitleOptions.length">
|
28
|
-
<span class="title-select-label">{{ getDisplayTitleLabel(index) }}</span>
|
29
|
-
<a-dropdown placement="bottomRight" trigger="['click']">
|
30
|
-
<a class="arrow-btn" @click.prevent>
|
31
|
-
<a-icon type="down" />
|
32
|
-
</a>
|
33
|
-
<a-menu slot="overlay" :selectedKeys="[String(currentTitleValue(index))]" @click="info => handleTitleMenuClick(info, index)">
|
34
|
-
<a-menu-item v-for="opt in getTitleOptions" :key="String(opt && opt.value !== undefined ? opt.value : opt)">
|
35
|
-
{{ opt && opt.label !== undefined ? opt.label : opt }}
|
36
|
-
</a-menu-item>
|
37
|
-
</a-menu>
|
38
|
-
</a-dropdown>
|
39
|
-
</div>
|
40
|
-
</div>
|
41
|
-
<span :key="idx" class="component-a-col" v-else-if="getHasPatient(item) && detail.type == 'component' && detail.label != label">
|
42
|
-
<a-tooltip :title="detail.label" placement="topLeft">
|
43
|
-
<span class="label-text">{{ `${detail.label}:` }}</span>
|
44
|
-
</a-tooltip>
|
45
|
-
<component
|
46
|
-
:is="detail.slotType"
|
47
|
-
:key="idx"
|
48
|
-
:ref="`dynamicComponent_${ idx.slotType || idx}_${idx}`"
|
49
|
-
:serviceName="serviceName"
|
50
|
-
v-on="forwardAllEvents"
|
51
|
-
:queryParamsName="detail.value"
|
52
|
-
:countVisible="false"
|
53
|
-
/>
|
54
|
-
</span>
|
55
|
-
<span :key="idx" class="component-a-col" v-else-if="getHasPatient(item) && detail.type == 'date'">
|
56
|
-
<a-tooltip :title="detail.label" placement="topLeft">
|
57
|
-
<span class="label-text">{{ `${detail.label}:` }}</span>
|
58
|
-
</a-tooltip>
|
59
|
-
<a-date-picker @change="onChange" />
|
60
|
-
</span>
|
61
|
-
<a-tooltip :key="idx" :title="`${detail.label}:${detail.value}`" placement="topLeft" v-else-if="getHasPatient(item) && detail.label != label">
|
62
|
-
<span class="describe-list-a-col" :class="{name: detail.bold}">{{ `${detail.label}:${detail.value}` }}</span>
|
63
|
-
</a-tooltip>
|
64
|
-
</template>
|
65
|
-
<a-button
|
66
|
-
v-if="getHasPatient(item) && showCardButtons"
|
67
|
-
v-for="(btn, i) in buttonNames"
|
68
|
-
:key="i"
|
69
|
-
icon="search"
|
70
|
-
class="button-a-col"
|
71
|
-
@click.stop="click(item, index)">
|
72
|
-
{{ btn }}
|
73
|
-
</a-button>
|
74
|
-
</a-col>
|
75
|
-
</a-row>
|
76
|
-
</div>
|
77
|
-
</a-list-item>
|
78
|
-
<div v-if="loading" class="demo-loading-container">
|
79
|
-
<a-spin />
|
80
|
-
</div>
|
81
|
-
<div v-if="allLoaded">
|
82
|
-
<div class="demo-infinite-list-bottom">
|
83
|
-
已经显示全部数据
|
84
|
-
</div>
|
85
|
-
</div>
|
86
|
-
</a-list>
|
87
|
-
</div>
|
88
|
-
|
89
|
-
<!-- 默认标签模式 -->
|
90
|
-
<div class="list-wrapper x-list-wrapper" :class="wrapperClassObject" v-else>
|
91
|
-
<a-list size="large" :data-source="data" itemLayout="horizontal" class="list-container" ref="listRef">
|
92
|
-
<a-list-item
|
93
|
-
slot="renderItem"
|
94
|
-
slot-scope="item, index"
|
95
|
-
class="list-item"
|
96
|
-
@click="handleClick(index)"
|
97
|
-
@mouseenter="enableHoverOptions && handleMouseEnter(index)"
|
98
|
-
@mouseleave="handleMouseLeave"
|
99
|
-
:class="{ 'hover-active': enableHoverOptions && hoveredIndex === index, 'selected-active': enableSelectRow && currentSelectedIndex === index }">
|
100
|
-
<i
|
101
|
-
v-if="icon"
|
102
|
-
class="icon-menu"
|
103
|
-
:style="getIconStyle(item)">
|
104
|
-
</i>
|
105
|
-
<span
|
106
|
-
class="item-text">
|
107
|
-
{{ item.number }} {{ item.name }}
|
108
|
-
</span>
|
109
|
-
|
110
|
-
<div v-if="button" class="button-group">
|
111
|
-
<a-button
|
112
|
-
v-for="(name, idx) in buttonNames"
|
113
|
-
:key="idx"
|
114
|
-
type="link"
|
115
|
-
:class="['confirm-btn', buttonMode ? 'hover-btn' : '']"
|
116
|
-
@click.stop="click(index, idx)">
|
117
|
-
<span :class="{ 'hover-active': enableHoverOptions && hoveredIndex === index }">{{ name }}</span>
|
118
|
-
</a-button>
|
119
|
-
</div>
|
120
|
-
|
121
|
-
<!-- 悬浮选项框 -->
|
122
|
-
<div
|
123
|
-
v-show="enableHoverOptions && hoveredIndex === index"
|
124
|
-
class="hover-options"
|
125
|
-
@mouseenter="handleOptionsEnter"
|
126
|
-
@mouseleave="handleOptionsLeave">
|
127
|
-
<div class="hover-options-content">
|
128
|
-
<div
|
129
|
-
v-for="(Item, idx) in select_options"
|
130
|
-
:key="idx"
|
131
|
-
class="option-item"
|
132
|
-
@click="handleOptionClick(index, Item)">
|
133
|
-
{{ Item }}
|
134
|
-
</div>
|
135
|
-
</div>
|
136
|
-
</div>
|
137
|
-
</a-list-item>
|
138
|
-
</a-list>
|
139
|
-
</div>
|
140
|
-
</template>
|
141
|
-
|
142
|
-
<script>
|
143
|
-
|
144
|
-
import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
|
145
|
-
|
146
|
-
export default {
|
147
|
-
name: 'XList',
|
148
|
-
components: {
|
149
|
-
XReport: () => import('@vue2-client/base-client/components/common/XReport/XReport.vue'),
|
150
|
-
XButtons: () => import('@vue2-client/base-client/components/common/XButtons/XButtons.vue'),
|
151
|
-
XInput: () => import('@vue2-client/base-client/components/common/XInput/XInput.vue'),
|
152
|
-
XRadio: () => import('@vue2-client/base-client/components/his/XRadio/XRadio.vue'),
|
153
|
-
XTimeSelect: () => import('@vue2-client/base-client/components/his/XTimeSelect/XTimeSelect.vue'),
|
154
|
-
XCheckbox: () => import('@vue2-client/base-client/components/his/XCheckbox/XCheckbox.vue'),
|
155
|
-
XTitle: () => import('@vue2-client/base-client/components/his/XTitle/XTitle.vue'),
|
156
|
-
XSelect: () => import('@vue2-client/base-client/components/his/XSelect/XSelect.vue')
|
157
|
-
},
|
158
|
-
props: {
|
159
|
-
queryParamsName: {
|
160
|
-
type: Object,
|
161
|
-
default: null
|
162
|
-
},
|
163
|
-
fixedQueryForm: {
|
164
|
-
type: Object,
|
165
|
-
default: { condition: '1=1' }
|
166
|
-
},
|
167
|
-
enableHoverOptions: {
|
168
|
-
type: Boolean,
|
169
|
-
default: true
|
170
|
-
},
|
171
|
-
serviceName: {
|
172
|
-
type: String,
|
173
|
-
default: 'af-his'
|
174
|
-
},
|
175
|
-
// 点击是否触发选中(默认不通过点击选中,仅手动控制)
|
176
|
-
selectOnClick: {
|
177
|
-
type: Boolean,
|
178
|
-
default: false
|
179
|
-
},
|
180
|
-
// 受控选中索引;不传则内部维护
|
181
|
-
selectedIndex: {
|
182
|
-
type: Number,
|
183
|
-
default: undefined
|
184
|
-
},
|
185
|
-
// 卡片按钮显示控制(默认隐藏,可通过属性或配置开启)
|
186
|
-
showCardButtons: {
|
187
|
-
type: Boolean,
|
188
|
-
default: false
|
189
|
-
},
|
190
|
-
// 标题右侧下拉选项与受控值
|
191
|
-
titleSelectOptions: {
|
192
|
-
type: Array,
|
193
|
-
default: () => []
|
194
|
-
},
|
195
|
-
titleSelectValue: {
|
196
|
-
type: [String, Number],
|
197
|
-
default: undefined
|
198
|
-
}
|
199
|
-
},
|
200
|
-
data () {
|
201
|
-
return {
|
202
|
-
data: [], // 数据源
|
203
|
-
localData: [], // 本地数据源
|
204
|
-
loading: false, // 加载中
|
205
|
-
busy: false, // 繁忙状态
|
206
|
-
button: false,
|
207
|
-
icon: false,
|
208
|
-
buttonNames: [],
|
209
|
-
listMode: undefined, // 列表模式
|
210
|
-
buttonMode: true,
|
211
|
-
hoveredIndex: -1, // 当前悬浮的索引
|
212
|
-
isOptionsHovered: false, // 悬浮选项框是否悬浮
|
213
|
-
hoverTimer: null, // 悬浮选项框定时器
|
214
|
-
leaveTimer: null, // 离开选项框定时器
|
215
|
-
select_options: [], // 悬浮选项框
|
216
|
-
logicName: '',
|
217
|
-
nowPage: 0, // 当前页
|
218
|
-
pageSize: 12,
|
219
|
-
allLoaded: false,
|
220
|
-
label: 'id',
|
221
|
-
scrollTimer: null,
|
222
|
-
// 内部选中索引(非受控时使用)
|
223
|
-
localSelectedIndex: -1,
|
224
|
-
maleIcon: require('@vue2-client/assets/svg/male.svg'),
|
225
|
-
femaleIcon: require('@vue2-client/assets/svg/female.svg'),
|
226
|
-
// 下拉配置
|
227
|
-
internalTitleOptions: [],
|
228
|
-
titleValueByIndex: {},
|
229
|
-
// 选择请求的轻量防抖
|
230
|
-
selectDebounceTimer: null
|
231
|
-
}
|
232
|
-
},
|
233
|
-
inject: ['getComponentByName'],
|
234
|
-
|
235
|
-
created () {
|
236
|
-
this.getData(this.queryParamsName, this.fixedQueryForm)
|
237
|
-
},
|
238
|
-
mounted () {},
|
239
|
-
computed: {
|
240
|
-
// 参考 HForm 的 wrapperClassObject 规则,支持通过组件属性动态控制样式
|
241
|
-
wrapperClassObject () {
|
242
|
-
const a = this.$attrs || {}
|
243
|
-
const classes = {}
|
244
|
-
// 多个布尔型样式开关(存在且为真则生效)
|
245
|
-
const booleanStyleKeys = [
|
246
|
-
''
|
247
|
-
]
|
248
|
-
booleanStyleKeys.forEach(key => {
|
249
|
-
const val = a[key]
|
250
|
-
const truthy = val === true || val === '' || val === 'true'
|
251
|
-
if (truthy) classes[`x-list-${key}`] = true
|
252
|
-
})
|
253
|
-
return classes
|
254
|
-
},
|
255
|
-
// 标题下拉:优先使用外部 props,否则使用内部配置
|
256
|
-
getTitleOptions () {
|
257
|
-
if (Array.isArray(this.titleSelectOptions) && this.titleSelectOptions.length) return this.titleSelectOptions
|
258
|
-
return this.internalTitleOptions
|
259
|
-
},
|
260
|
-
currentTitleValue () {
|
261
|
-
return (index) => {
|
262
|
-
if (this.titleSelectValue !== undefined) return this.titleSelectValue
|
263
|
-
const local = this.titleValueByIndex[index]
|
264
|
-
if (local !== undefined) return local
|
265
|
-
// 二次回退:若模板标题提供了 titleRightValue,则用于首屏渲染颜色与文案
|
266
|
-
const row = Array.isArray(this.localData) ? this.localData[index] : null
|
267
|
-
if (Array.isArray(row)) {
|
268
|
-
const title = row.find(d => d && d.type === 'title')
|
269
|
-
if (title && title.titleRightValue !== undefined) return title.titleRightValue
|
270
|
-
}
|
271
|
-
// 最终回退:使用 options 的第一个
|
272
|
-
const def = this.internalTitleOptions && this.internalTitleOptions.length ? (this.internalTitleOptions[0].value !== undefined ? this.internalTitleOptions[0].value : this.internalTitleOptions[0]) : undefined
|
273
|
-
return def
|
274
|
-
}
|
275
|
-
},
|
276
|
-
// 选择控制:受控优先
|
277
|
-
currentSelectedIndex () {
|
278
|
-
return typeof this.selectedIndex === 'number' ? this.selectedIndex : this.localSelectedIndex
|
279
|
-
},
|
280
|
-
enableSelectRow () {
|
281
|
-
const a = this.$attrs || {}
|
282
|
-
const val = a.enableSelection
|
283
|
-
return val === true || val === '' || val === 'true'
|
284
|
-
},
|
285
|
-
forwardAllEvents () {
|
286
|
-
return {
|
287
|
-
// 监听所有事件并转发给父组件
|
288
|
-
'*': (eventName, ...payload) => {
|
289
|
-
this.$emit(eventName, ...payload)
|
290
|
-
}
|
291
|
-
}
|
292
|
-
}
|
293
|
-
},
|
294
|
-
methods: {
|
295
|
-
onChange (date, dateString) {
|
296
|
-
this.$emit('dateChange', date, dateString)
|
297
|
-
},
|
298
|
-
handleInfiniteOnLoad (event) {
|
299
|
-
if (this.busy || this.allLoaded) return // 防止重复加载
|
300
|
-
if (this.scrollTimer) clearTimeout(this.scrollTimer)
|
301
|
-
this.scrollTimer = setTimeout(() => {
|
302
|
-
const container = event.target
|
303
|
-
const scrollTop = container.scrollTop
|
304
|
-
const scrollHeight = container.scrollHeight
|
305
|
-
const clientHeight = container.clientHeight
|
306
|
-
const bottomOffset = 10
|
307
|
-
if (scrollTop + clientHeight >= scrollHeight - bottomOffset) {
|
308
|
-
this.busy = true
|
309
|
-
this.loading = true
|
310
|
-
this.nowPage = this.nowPage + this.pageSize
|
311
|
-
this.fixedQueryForm.condition = `Limit ${this.nowPage}, ${this.pageSize}`
|
312
|
-
runLogic(this.logicName, this.fixedQueryForm, 'af-his').then(async (res) => {
|
313
|
-
this.localData = [...this.localData, ...res]
|
314
|
-
if (res.length < this.pageSize) {
|
315
|
-
this.allLoaded = true
|
316
|
-
}
|
317
|
-
}).catch(e => {
|
318
|
-
this.$message.error(e.message)
|
319
|
-
}).finally(() => {
|
320
|
-
this.loading = false
|
321
|
-
this.busy = false
|
322
|
-
})
|
323
|
-
}
|
324
|
-
}, 100)
|
325
|
-
},
|
326
|
-
async getData (config, param) {
|
327
|
-
const that = this
|
328
|
-
getConfigByName(config, 'af-his', async (res) => {
|
329
|
-
that.listMode = await res.listMode == 'card'
|
330
|
-
that.logicName = await res.data
|
331
|
-
that.button = await res.button // 按钮
|
332
|
-
that.icon = await res.icon // 图标
|
333
|
-
that.label = await res.label // 标签
|
334
|
-
that.buttonNames = await res.buttonNames || []// 按钮文本
|
335
|
-
that.buttonMode = await res.buttonMode || false// 按钮模式
|
336
|
-
that.cardButtonsVisible = await res.cardButtonsVisible || false// 卡片按钮
|
337
|
-
// 标题下拉:从配置拿 options: [{label,value,color}]
|
338
|
-
if (Array.isArray(res.titleOptions)) {
|
339
|
-
this.internalTitleOptions = res.titleOptions
|
340
|
-
if (res.titleDefaultValue !== undefined && Array.isArray(this.localData)) {
|
341
|
-
// 初始化每个卡片的默认值
|
342
|
-
this.titleValueByIndex = {}
|
343
|
-
}
|
344
|
-
}
|
345
|
-
this.enableHoverOptions = await res.enableHoverOptions || false// 悬浮选项框
|
346
|
-
if (this.enableHoverOptions) {
|
347
|
-
this.select_options = await res.select_options
|
348
|
-
}
|
349
|
-
if (that.listMode) { param.condition = `Limit ${that.nowPage}, ${that.pageSize}` }
|
350
|
-
runLogic(res.data, param, 'af-his').then(result => {
|
351
|
-
that.data = result
|
352
|
-
if (that.nowPage === 0) { this.localData = result }
|
353
|
-
// 初始化每张卡片的 title 选中值:优先读取模板 titleRightValue,否则使用配置默认或第一项
|
354
|
-
if (Array.isArray(this.localData)) {
|
355
|
-
this.localData.forEach((row, idx) => {
|
356
|
-
if (this.titleValueByIndex[idx] !== undefined) return
|
357
|
-
if (Array.isArray(row)) {
|
358
|
-
const title = row.find(d => d && d.type === 'title')
|
359
|
-
if (title && title.titleRightValue !== undefined) {
|
360
|
-
this.$set(this.titleValueByIndex, idx, title.titleRightValue)
|
361
|
-
return
|
362
|
-
}
|
363
|
-
}
|
364
|
-
// fallback:配置默认或第一项
|
365
|
-
const def = (this.internalTitleOptions && this.internalTitleOptions.length)
|
366
|
-
? (this.internalTitleOptions[0].value !== undefined ? this.internalTitleOptions[0].value : this.internalTitleOptions[0])
|
367
|
-
: undefined
|
368
|
-
if (def !== undefined) this.$set(this.titleValueByIndex, idx, def)
|
369
|
-
})
|
370
|
-
}
|
371
|
-
})
|
372
|
-
})
|
373
|
-
},
|
374
|
-
// 提取性别:从数组数据中查找 label 含“性别”的项
|
375
|
-
getGender (item) {
|
376
|
-
if (!Array.isArray(item)) return null
|
377
|
-
const g = item.find(d => d && typeof d.label === 'string' && d.label.indexOf('性别') > -1)
|
378
|
-
const val = g && (g.value || g.text)
|
379
|
-
if (!val) return null
|
380
|
-
if (String(val).includes('男')) return '男'
|
381
|
-
if (String(val).includes('女')) return '女'
|
382
|
-
return null
|
383
|
-
},
|
384
|
-
// 读取标题项上的 hasPatient 布尔值
|
385
|
-
getHasPatient (item) {
|
386
|
-
if (!Array.isArray(item)) return true
|
387
|
-
const title = item.find(d => d && d.type === 'title')
|
388
|
-
if (!title || typeof title.hasPatient === 'undefined') return true
|
389
|
-
return !!title.hasPatient
|
390
|
-
},
|
391
|
-
// 标题右侧下拉选择
|
392
|
-
handleTitleSelectChange (val, index) {
|
393
|
-
// 重复值拦截:若值未变化则直接返回
|
394
|
-
const prev = this.normalizeValue(this.currentTitleValue(index))
|
395
|
-
const next = this.normalizeValue(val)
|
396
|
-
if (prev === next) return
|
397
|
-
|
398
|
-
this.$set(this.titleValueByIndex, index, val)
|
399
|
-
this.$emit('update:titleSelectValue', val)
|
400
|
-
this.$emit('titleSelectChange', { index, value: val })
|
401
|
-
// 变更后仅通知后端更新,不处理返回值;支持从标题项读取 extraParams 合并(如住院号)
|
402
|
-
const extra = this.getTitleExtraParams(index)
|
403
|
-
if (this.selectDebounceTimer) clearTimeout(this.selectDebounceTimer)
|
404
|
-
this.selectDebounceTimer = setTimeout(() => {
|
405
|
-
runLogic(this.logicName, Object.assign({ type: 'select', value: val }, extra), 'af-his').catch(() => {})
|
406
|
-
}, 200)
|
407
|
-
},
|
408
|
-
// 读取标题项中额外需要传递的参数(可选),例如 { inpatientNo: row.t_id }
|
409
|
-
getTitleExtraParams (index) {
|
410
|
-
const row = Array.isArray(this.localData) ? this.localData[index] : null
|
411
|
-
if (!Array.isArray(row)) return {}
|
412
|
-
const title = row.find(d => d && d.type === 'title') || {}
|
413
|
-
if (title && title.extraParams && typeof title.extraParams === 'object') return title.extraParams
|
414
|
-
return {}
|
415
|
-
},
|
416
|
-
handleTitleMenuClick ({ key }, index) {
|
417
|
-
const v = isNaN(Number(key)) ? key : Number(key)
|
418
|
-
this.handleTitleSelectChange(v, index)
|
419
|
-
},
|
420
|
-
// 根据当前选择项的 color 设置卡片背景色
|
421
|
-
normalizeValue (v) { return String(v) },
|
422
|
-
getCardStyle (val) {
|
423
|
-
const target = this.normalizeValue(val)
|
424
|
-
const opt = (this.getTitleOptions || []).find(o => this.normalizeValue(o && (o.value !== undefined ? o.value : o)) === target)
|
425
|
-
const color = opt && (opt.color || (opt.style && opt.style.background))
|
426
|
-
return color ? { backgroundColor: color } : {}
|
427
|
-
},
|
428
|
-
getTitleLabel (val) {
|
429
|
-
const target = this.normalizeValue(val)
|
430
|
-
const opt = (this.getTitleOptions || []).find(o => this.normalizeValue(o && (o.value !== undefined ? o.value : o)) === target)
|
431
|
-
return opt && (opt.label !== undefined ? opt.label : opt)
|
432
|
-
},
|
433
|
-
getDisplayTitleLabel (index) {
|
434
|
-
// 优先展示“当前选中值”的 label,未选中再回退到模板提供的 titleRightLabel
|
435
|
-
const current = this.getTitleLabel(this.currentTitleValue(index))
|
436
|
-
if (current) return current
|
437
|
-
const title = Array.isArray(this.localData && this.localData[index])
|
438
|
-
? this.localData[index].find(d => d && d.type === 'title')
|
439
|
-
: null
|
440
|
-
return title && title.titleRightLabel ? title.titleRightLabel : ''
|
441
|
-
},
|
442
|
-
// 点击列表项
|
443
|
-
handleClick (index) {
|
444
|
-
if (this.enableSelectRow && this.selectOnClick) this.setSelectedIndex(index)
|
445
|
-
this.$emit('listClick', this.data[index])
|
446
|
-
},
|
447
|
-
// 卡片模式点击卡片
|
448
|
-
handleCardClick (index) {
|
449
|
-
if (this.enableSelectRow && this.selectOnClick) this.setSelectedIndex(index)
|
450
|
-
this.$emit('listClick', this.localData[index])
|
451
|
-
},
|
452
|
-
// 外部可调用:设置选中索引
|
453
|
-
setSelectedIndex (index) {
|
454
|
-
const next = typeof index === 'number' ? index : -1
|
455
|
-
if (typeof this.selectedIndex === 'number') {
|
456
|
-
// 受控:仅派发事件
|
457
|
-
this.$emit('update:selectedIndex', next)
|
458
|
-
} else {
|
459
|
-
// 非受控:更新内部并派发事件
|
460
|
-
this.localSelectedIndex = next
|
461
|
-
this.$emit('update:selectedIndex', next)
|
462
|
-
}
|
463
|
-
},
|
464
|
-
// 外部可调用:清空选中
|
465
|
-
clearSelected () { this.setSelectedIndex(-1) },
|
466
|
-
refreshList (param) {
|
467
|
-
this.getData(this.queryParamsName, param)
|
468
|
-
},
|
469
|
-
click (index, buttonIndex) {
|
470
|
-
this.$emit('click', { data: this.data[index], name: this.buttonNames[buttonIndex] })
|
471
|
-
},
|
472
|
-
// 根据对象字段匹配选中(默认按 id 字段)
|
473
|
-
setSelectedById (id, field = 'id') {
|
474
|
-
const list = this.listMode ? this.localData : this.data
|
475
|
-
if (!Array.isArray(list)) return
|
476
|
-
const index = list.findIndex(item => {
|
477
|
-
if (Array.isArray(item)) {
|
478
|
-
const f = item.find(d => d && d.label === field)
|
479
|
-
return f && f.value === id
|
480
|
-
}
|
481
|
-
return item && item[field] === id
|
482
|
-
})
|
483
|
-
if (index >= 0) this.setSelectedIndex(index)
|
484
|
-
},
|
485
|
-
// 根据 label/value(卡片数组数据场景)匹配选中
|
486
|
-
setSelectedByLabelValue (label, value) {
|
487
|
-
const list = this.listMode ? this.localData : this.data
|
488
|
-
if (!Array.isArray(list)) return
|
489
|
-
const index = list.findIndex(item => Array.isArray(item) && item.some(d => d && d.label === label && d.value === value))
|
490
|
-
if (index >= 0) this.setSelectedIndex(index)
|
491
|
-
},
|
492
|
-
// 通用:传入谓词函数决定选中项
|
493
|
-
setSelectedBy (predicate) {
|
494
|
-
if (typeof predicate !== 'function') return
|
495
|
-
const list = this.listMode ? this.localData : this.data
|
496
|
-
if (!Array.isArray(list)) return
|
497
|
-
const index = list.findIndex(predicate)
|
498
|
-
if (index >= 0) this.setSelectedIndex(index)
|
499
|
-
},
|
500
|
-
getIconStyle (item) {
|
501
|
-
return item.picture
|
502
|
-
? { backgroundImage: `url(${item.picture})` }
|
503
|
-
: {}
|
504
|
-
},
|
505
|
-
filterData (par) {
|
506
|
-
runLogic(this.queryParamsName, par, 'af-his').then(res => {
|
507
|
-
this.data = res.data
|
508
|
-
})
|
509
|
-
},
|
510
|
-
// 鼠标进入列表项
|
511
|
-
handleMouseEnter (index) {
|
512
|
-
this.clearAllTimers()
|
513
|
-
this.hoveredIndex = index
|
514
|
-
this.isOptionsHovered = true
|
515
|
-
},
|
516
|
-
// 鼠标离开列表项
|
517
|
-
handleMouseLeave () {
|
518
|
-
this.clearAllTimers()
|
519
|
-
this.leaveTimer = setTimeout(() => {
|
520
|
-
this.isOptionsHovered = false
|
521
|
-
this.hoveredIndex = -1
|
522
|
-
}, 100)
|
523
|
-
},
|
524
|
-
// 鼠标进入悬浮选项框
|
525
|
-
handleOptionsEnter () {
|
526
|
-
this.clearAllTimers()
|
527
|
-
this.isOptionsHovered = true
|
528
|
-
},
|
529
|
-
// 鼠标离开悬浮选项框
|
530
|
-
handleOptionsLeave () {
|
531
|
-
this.clearAllTimers()
|
532
|
-
this.leaveTimer = setTimeout(() => {
|
533
|
-
this.isOptionsHovered = false
|
534
|
-
this.hoveredIndex = -1
|
535
|
-
}, 100)
|
536
|
-
},
|
537
|
-
// 清除所有定时器
|
538
|
-
clearAllTimers () {
|
539
|
-
if (this.hoverTimer) {
|
540
|
-
clearTimeout(this.hoverTimer)
|
541
|
-
this.hoverTimer = null
|
542
|
-
}
|
543
|
-
if (this.leaveTimer) {
|
544
|
-
clearTimeout(this.leaveTimer)
|
545
|
-
this.leaveTimer = null
|
546
|
-
}
|
547
|
-
},
|
548
|
-
// 选项框点击
|
549
|
-
handleOptionClick (index, action) {
|
550
|
-
this.$emit('optionClick', { data: this.data[index], action })
|
551
|
-
}
|
552
|
-
},
|
553
|
-
watch: {
|
554
|
-
// 同步受控值变化
|
555
|
-
selectedIndex (val) {
|
556
|
-
if (typeof val === 'number') {
|
557
|
-
// 允许受控值影响内部显示
|
558
|
-
this.localSelectedIndex = val
|
559
|
-
}
|
560
|
-
},
|
561
|
-
fixedQueryForm: {
|
562
|
-
deep: true,
|
563
|
-
handler (val) {
|
564
|
-
this.refreshList(val)
|
565
|
-
}
|
566
|
-
}
|
567
|
-
},
|
568
|
-
beforeDestroy () {
|
569
|
-
const ref = this.$refs.listRef
|
570
|
-
if (ref && ref.removeEventListener) {
|
571
|
-
ref.removeEventListener('scroll', this.handleInfiniteOnLoad)
|
572
|
-
}
|
573
|
-
this.clearAllTimers()
|
574
|
-
}
|
575
|
-
}
|
576
|
-
</script>
|
577
|
-
|
578
|
-
<style scoped>
|
579
|
-
.list-wrapper {
|
580
|
-
max-height: 240px;
|
581
|
-
overflow-y: auto;
|
582
|
-
padding-right: 2px;
|
583
|
-
}
|
584
|
-
|
585
|
-
.list-container {
|
586
|
-
width: 100%;
|
587
|
-
}
|
588
|
-
|
589
|
-
.list-item {
|
590
|
-
height: 35px;
|
591
|
-
border-radius: 6px;
|
592
|
-
background-color: #F4F4F4;
|
593
|
-
padding: 8px 15px;
|
594
|
-
font-size: 16px;
|
595
|
-
display: flex;
|
596
|
-
align-items: center;
|
597
|
-
width: 100%;
|
598
|
-
border: 1px solid #D9D9D9;
|
599
|
-
box-sizing: border-box;
|
600
|
-
margin-bottom: 8px !important;
|
601
|
-
position: relative;
|
602
|
-
transition: background-color 0.3s ease;
|
603
|
-
}
|
604
|
-
|
605
|
-
.icon-menu {
|
606
|
-
display: inline-block;
|
607
|
-
width: 20px;
|
608
|
-
height: 20px;
|
609
|
-
background-color: #ccc;
|
610
|
-
margin-right: 8px;
|
611
|
-
}
|
612
|
-
|
613
|
-
.item-text {
|
614
|
-
flex: 1;
|
615
|
-
}
|
616
|
-
|
617
|
-
.confirm-btn {
|
618
|
-
margin-left: auto;
|
619
|
-
padding: 0 8px;
|
620
|
-
}
|
621
|
-
|
622
|
-
.confirm-btn.hover-btn {
|
623
|
-
opacity: 0;
|
624
|
-
transition: opacity 0.3s ease;
|
625
|
-
}
|
626
|
-
|
627
|
-
.button-group {
|
628
|
-
display: flex;
|
629
|
-
gap: 2px; /* 按钮之间的间距 */
|
630
|
-
}
|
631
|
-
|
632
|
-
.list-item:hover .confirm-btn.hover-btn {
|
633
|
-
opacity: 1;
|
634
|
-
}
|
635
|
-
|
636
|
-
/* 自定义滚动条样式 */
|
637
|
-
.list-wrapper::-webkit-scrollbar {
|
638
|
-
width: 6px;
|
639
|
-
}
|
640
|
-
|
641
|
-
.list-wrapper::-webkit-scrollbar-thumb {
|
642
|
-
background-color: #d9d9d9;
|
643
|
-
border-radius: 3px;
|
644
|
-
}
|
645
|
-
|
646
|
-
.list-wrapper::-webkit-scrollbar-track {
|
647
|
-
background-color: #f0f0f0;
|
648
|
-
}
|
649
|
-
|
650
|
-
.hover-active {
|
651
|
-
color: white;
|
652
|
-
}
|
653
|
-
|
654
|
-
.list-item.hover-active {
|
655
|
-
background-color: rgb(0, 87, 254) !important;
|
656
|
-
color: white;
|
657
|
-
border: 1px solid black;
|
658
|
-
}
|
659
|
-
|
660
|
-
/* 选中态样式(通过 selectRow 开启) */
|
661
|
-
.selected-active { color: white; }
|
662
|
-
.list-item.selected-active {
|
663
|
-
background-color: #0057FE !important;
|
664
|
-
color: white;
|
665
|
-
border: none !important;
|
666
|
-
}
|
667
|
-
.list-item.selected-active .confirm-btn,
|
668
|
-
.list-item.selected-active .confirm-btn span,
|
669
|
-
.list-item.selected-active .ant-btn,
|
670
|
-
.list-item.selected-active .ant-btn > span { color: #ffffff !important; }
|
671
|
-
.card-a-col.selected-active {
|
672
|
-
background-color: #0057FE !important;
|
673
|
-
color: white;
|
674
|
-
border: none !important;
|
675
|
-
}
|
676
|
-
.card-a-col.selected-active .button-a-col,
|
677
|
-
.card-a-col.selected-active .button-a-col .ant-btn,
|
678
|
-
.card-a-col.selected-active .button-a-col .ant-btn > span { color: #ffffff !important; }
|
679
|
-
|
680
|
-
.hover-options {
|
681
|
-
position: absolute;
|
682
|
-
left: 0;
|
683
|
-
right: 0;
|
684
|
-
top: 100%;
|
685
|
-
background: white;
|
686
|
-
border: 1px solid #d9d9d9;
|
687
|
-
border-radius: 4px;
|
688
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
689
|
-
z-index: 1000;
|
690
|
-
margin-top: 4px;
|
691
|
-
width: 100%;
|
692
|
-
box-sizing: border-box;
|
693
|
-
pointer-events: auto;
|
694
|
-
}
|
695
|
-
|
696
|
-
.hover-options-content {
|
697
|
-
padding: 4px 0;
|
698
|
-
display: flex;
|
699
|
-
flex-direction: column;
|
700
|
-
width: 100%;
|
701
|
-
}
|
702
|
-
|
703
|
-
.option-item {
|
704
|
-
padding: 8px 12px;
|
705
|
-
cursor: pointer;
|
706
|
-
transition: all 0.3s ease;
|
707
|
-
color: #333;
|
708
|
-
font-size: 14px;
|
709
|
-
display: flex;
|
710
|
-
align-items: center;
|
711
|
-
}
|
712
|
-
|
713
|
-
.option-item:hover {
|
714
|
-
background-color: #f5f5f5;
|
715
|
-
color: #1890ff;
|
716
|
-
}
|
717
|
-
|
718
|
-
.option-item:active {
|
719
|
-
background-color: #e6f7ff;
|
720
|
-
}
|
721
|
-
|
722
|
-
.demo-loading-container {
|
723
|
-
position: absolute;
|
724
|
-
bottom: 40px;
|
725
|
-
width: 100%;
|
726
|
-
text-align: center;
|
727
|
-
}
|
728
|
-
|
729
|
-
.demo-infinite-container{
|
730
|
-
overflow-x: hidden;
|
731
|
-
overflow-y: auto;
|
732
|
-
height: 85vh;
|
733
|
-
}
|
734
|
-
.card-row{
|
735
|
-
height: 100%;
|
736
|
-
width: 100%;
|
737
|
-
border-radius: 6px;
|
738
|
-
padding: 6px;
|
739
|
-
}
|
740
|
-
.id-a-col{
|
741
|
-
font-size: 22px;
|
742
|
-
font-weight: 700;
|
743
|
-
display: flex;
|
744
|
-
flex-direction: column;
|
745
|
-
align-items: center;
|
746
|
-
justify-content: center;
|
747
|
-
gap: 4px;
|
748
|
-
}
|
749
|
-
.id-a-col-2{
|
750
|
-
padding: 8px;
|
751
|
-
height: 100%;
|
752
|
-
overflow: hidden;
|
753
|
-
display: flex;
|
754
|
-
flex-direction: column;
|
755
|
-
justify-content: space-between;
|
756
|
-
}
|
757
|
-
.describe-list-a-col{
|
758
|
-
display: block;
|
759
|
-
font-family: Source Han Sans;
|
760
|
-
font-size: 16px;
|
761
|
-
font-weight: normal;
|
762
|
-
line-height: normal;
|
763
|
-
overflow: hidden;
|
764
|
-
text-overflow: ellipsis;
|
765
|
-
white-space: nowrap;
|
766
|
-
max-width: 100%;
|
767
|
-
}
|
768
|
-
.name{
|
769
|
-
font-family: Source Han Sans;
|
770
|
-
font-size: 22px;
|
771
|
-
font-weight: bold;
|
772
|
-
line-height: normal;
|
773
|
-
}
|
774
|
-
.component-a-col{
|
775
|
-
display: flex;
|
776
|
-
align-items: center;
|
777
|
-
overflow: hidden;
|
778
|
-
}
|
779
|
-
.label-text{
|
780
|
-
overflow: hidden;
|
781
|
-
text-overflow: ellipsis;
|
782
|
-
white-space: nowrap;
|
783
|
-
max-width: 120px;
|
784
|
-
display: inline-block;
|
785
|
-
}
|
786
|
-
.gender-icon{
|
787
|
-
margin-top: 2px;
|
788
|
-
}
|
789
|
-
.gender-icon .male{ color: #2f54eb; font-size: 14px; }
|
790
|
-
.gender-icon .female{ color: #eb2f96; font-size: 14px; }
|
791
|
-
.gender-img{ width: 100%; height: 100%; opacity: 1; display: inline-block; }
|
792
|
-
.title-row{
|
793
|
-
display: flex;
|
794
|
-
align-items: center;
|
795
|
-
justify-content: space-between;
|
796
|
-
gap: 8px;
|
797
|
-
}
|
798
|
-
/* 标题文本占满剩余空间,确保箭头贴右 */
|
799
|
-
.title-row .describe-list-a-col{ flex: 1; }
|
800
|
-
.title-actions{ display: inline-flex; align-items: center; gap: 6px; }
|
801
|
-
.title-select-label{
|
802
|
-
font-family: Source Han Sans;
|
803
|
-
font-size: 16px;
|
804
|
-
font-weight: normal;
|
805
|
-
line-height: normal;
|
806
|
-
color: #595959;
|
807
|
-
}
|
808
|
-
.title-select{ min-width: 96px; }
|
809
|
-
.arrow-btn{ display: inline-flex; align-items: center; color: #8C8C8C; }
|
810
|
-
.arrow-btn .anticon{ font-size: 18px; }
|
811
|
-
/* 放大下拉箭头的 svg 尺寸以符合 UI(scoped 深度选择) */
|
812
|
-
::v-deep .arrow-btn .anticon svg{ width: 2em; height: 2em; }
|
813
|
-
.button-a-col{
|
814
|
-
position: absolute;
|
815
|
-
top: 245px;
|
816
|
-
left: 225px;
|
817
|
-
}
|
818
|
-
.card-a-col{
|
819
|
-
background-color: rgba(247, 249, 252);
|
820
|
-
height: 297px;
|
821
|
-
width: auto;
|
822
|
-
border-radius: 6px;
|
823
|
-
border: 1px solid #E5E9F0;
|
824
|
-
overflow: hidden;
|
825
|
-
position: relative;
|
826
|
-
box-sizing: border-box;
|
827
|
-
margin: 10px;
|
828
|
-
}
|
829
|
-
</style>
|
1
|
+
<template>
|
2
|
+
<!-- 列表卡片模式 listMode: card -->
|
3
|
+
<div
|
4
|
+
class="x-list-wrapper"
|
5
|
+
:class="wrapperClassObject"
|
6
|
+
v-if="listMode"
|
7
|
+
ref="listRef"
|
8
|
+
@scroll="handleInfiniteOnLoad">
|
9
|
+
<a-list
|
10
|
+
:grid="{ gutter: 16, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 4 }"
|
11
|
+
:data-source="localData">
|
12
|
+
<a-list-item slot="renderItem" slot-scope="item, index">
|
13
|
+
<div class="card-a-col" :class="{ 'selected-active': enableSelectRow && currentSelectedIndex === index }" :style="getCardStyle(currentTitleValue(index))" @click="handleCardClick(index)">
|
14
|
+
<a-row class="card-row">
|
15
|
+
<a-col class="id-a-col" :span="4" v-for="(detail,idx) in item.filter(d => d.label == label)" :key="idx">
|
16
|
+
{{ detail.value }}
|
17
|
+
<div class="gender-icon" v-if="getHasPatient(item) && getGender(item)">
|
18
|
+
<img :src="getGender(item) === '男' ? maleIcon : femaleIcon" :alt="getGender(item)" class="gender-img" />
|
19
|
+
</div>
|
20
|
+
</a-col>
|
21
|
+
<a-col :span="20" class="id-a-col-2">
|
22
|
+
<template v-for="(detail,idx) in item">
|
23
|
+
<div :key="`title_${idx}`" class="title-row" v-if="detail.type == 'title'">
|
24
|
+
<a-tooltip :title="detail.value" placement="topLeft">
|
25
|
+
<span class="describe-list-a-col" :class="{name: detail.bold}">{{ detail.value }}</span>
|
26
|
+
</a-tooltip>
|
27
|
+
<div class="title-actions" v-if="getTitleOptions && getTitleOptions.length">
|
28
|
+
<span class="title-select-label">{{ getDisplayTitleLabel(index) }}</span>
|
29
|
+
<a-dropdown placement="bottomRight" trigger="['click']">
|
30
|
+
<a class="arrow-btn" @click.prevent>
|
31
|
+
<a-icon type="down" />
|
32
|
+
</a>
|
33
|
+
<a-menu slot="overlay" :selectedKeys="[String(currentTitleValue(index))]" @click="info => handleTitleMenuClick(info, index)">
|
34
|
+
<a-menu-item v-for="opt in getTitleOptions" :key="String(opt && opt.value !== undefined ? opt.value : opt)">
|
35
|
+
{{ opt && opt.label !== undefined ? opt.label : opt }}
|
36
|
+
</a-menu-item>
|
37
|
+
</a-menu>
|
38
|
+
</a-dropdown>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
<span :key="idx" class="component-a-col" v-else-if="getHasPatient(item) && detail.type == 'component' && detail.label != label">
|
42
|
+
<a-tooltip :title="detail.label" placement="topLeft">
|
43
|
+
<span class="label-text">{{ `${detail.label}:` }}</span>
|
44
|
+
</a-tooltip>
|
45
|
+
<component
|
46
|
+
:is="detail.slotType"
|
47
|
+
:key="idx"
|
48
|
+
:ref="`dynamicComponent_${ idx.slotType || idx}_${idx}`"
|
49
|
+
:serviceName="serviceName"
|
50
|
+
v-on="forwardAllEvents"
|
51
|
+
:queryParamsName="detail.value"
|
52
|
+
:countVisible="false"
|
53
|
+
/>
|
54
|
+
</span>
|
55
|
+
<span :key="idx" class="component-a-col" v-else-if="getHasPatient(item) && detail.type == 'date'">
|
56
|
+
<a-tooltip :title="detail.label" placement="topLeft">
|
57
|
+
<span class="label-text">{{ `${detail.label}:` }}</span>
|
58
|
+
</a-tooltip>
|
59
|
+
<a-date-picker @change="onChange" />
|
60
|
+
</span>
|
61
|
+
<a-tooltip :key="idx" :title="`${detail.label}:${detail.value}`" placement="topLeft" v-else-if="getHasPatient(item) && detail.label != label">
|
62
|
+
<span class="describe-list-a-col" :class="{name: detail.bold}">{{ `${detail.label}:${detail.value}` }}</span>
|
63
|
+
</a-tooltip>
|
64
|
+
</template>
|
65
|
+
<a-button
|
66
|
+
v-if="getHasPatient(item) && showCardButtons"
|
67
|
+
v-for="(btn, i) in buttonNames"
|
68
|
+
:key="i"
|
69
|
+
icon="search"
|
70
|
+
class="button-a-col"
|
71
|
+
@click.stop="click(item, index)">
|
72
|
+
{{ btn }}
|
73
|
+
</a-button>
|
74
|
+
</a-col>
|
75
|
+
</a-row>
|
76
|
+
</div>
|
77
|
+
</a-list-item>
|
78
|
+
<div v-if="loading" class="demo-loading-container">
|
79
|
+
<a-spin />
|
80
|
+
</div>
|
81
|
+
<div v-if="allLoaded">
|
82
|
+
<div class="demo-infinite-list-bottom">
|
83
|
+
已经显示全部数据
|
84
|
+
</div>
|
85
|
+
</div>
|
86
|
+
</a-list>
|
87
|
+
</div>
|
88
|
+
|
89
|
+
<!-- 默认标签模式 -->
|
90
|
+
<div class="list-wrapper x-list-wrapper" :class="wrapperClassObject" v-else>
|
91
|
+
<a-list size="large" :data-source="data" itemLayout="horizontal" class="list-container" ref="listRef">
|
92
|
+
<a-list-item
|
93
|
+
slot="renderItem"
|
94
|
+
slot-scope="item, index"
|
95
|
+
class="list-item"
|
96
|
+
@click="handleClick(index)"
|
97
|
+
@mouseenter="enableHoverOptions && handleMouseEnter(index)"
|
98
|
+
@mouseleave="handleMouseLeave"
|
99
|
+
:class="{ 'hover-active': enableHoverOptions && hoveredIndex === index, 'selected-active': enableSelectRow && currentSelectedIndex === index }">
|
100
|
+
<i
|
101
|
+
v-if="icon"
|
102
|
+
class="icon-menu"
|
103
|
+
:style="getIconStyle(item)">
|
104
|
+
</i>
|
105
|
+
<span
|
106
|
+
class="item-text">
|
107
|
+
{{ item.number }} {{ item.name }}
|
108
|
+
</span>
|
109
|
+
|
110
|
+
<div v-if="button" class="button-group">
|
111
|
+
<a-button
|
112
|
+
v-for="(name, idx) in buttonNames"
|
113
|
+
:key="idx"
|
114
|
+
type="link"
|
115
|
+
:class="['confirm-btn', buttonMode ? 'hover-btn' : '']"
|
116
|
+
@click.stop="click(index, idx)">
|
117
|
+
<span :class="{ 'hover-active': enableHoverOptions && hoveredIndex === index }">{{ name }}</span>
|
118
|
+
</a-button>
|
119
|
+
</div>
|
120
|
+
|
121
|
+
<!-- 悬浮选项框 -->
|
122
|
+
<div
|
123
|
+
v-show="enableHoverOptions && hoveredIndex === index"
|
124
|
+
class="hover-options"
|
125
|
+
@mouseenter="handleOptionsEnter"
|
126
|
+
@mouseleave="handleOptionsLeave">
|
127
|
+
<div class="hover-options-content">
|
128
|
+
<div
|
129
|
+
v-for="(Item, idx) in select_options"
|
130
|
+
:key="idx"
|
131
|
+
class="option-item"
|
132
|
+
@click="handleOptionClick(index, Item)">
|
133
|
+
{{ Item }}
|
134
|
+
</div>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
</a-list-item>
|
138
|
+
</a-list>
|
139
|
+
</div>
|
140
|
+
</template>
|
141
|
+
|
142
|
+
<script>
|
143
|
+
|
144
|
+
import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
|
145
|
+
|
146
|
+
export default {
|
147
|
+
name: 'XList',
|
148
|
+
components: {
|
149
|
+
XReport: () => import('@vue2-client/base-client/components/common/XReport/XReport.vue'),
|
150
|
+
XButtons: () => import('@vue2-client/base-client/components/common/XButtons/XButtons.vue'),
|
151
|
+
XInput: () => import('@vue2-client/base-client/components/common/XInput/XInput.vue'),
|
152
|
+
XRadio: () => import('@vue2-client/base-client/components/his/XRadio/XRadio.vue'),
|
153
|
+
XTimeSelect: () => import('@vue2-client/base-client/components/his/XTimeSelect/XTimeSelect.vue'),
|
154
|
+
XCheckbox: () => import('@vue2-client/base-client/components/his/XCheckbox/XCheckbox.vue'),
|
155
|
+
XTitle: () => import('@vue2-client/base-client/components/his/XTitle/XTitle.vue'),
|
156
|
+
XSelect: () => import('@vue2-client/base-client/components/his/XSelect/XSelect.vue')
|
157
|
+
},
|
158
|
+
props: {
|
159
|
+
queryParamsName: {
|
160
|
+
type: Object,
|
161
|
+
default: null
|
162
|
+
},
|
163
|
+
fixedQueryForm: {
|
164
|
+
type: Object,
|
165
|
+
default: { condition: '1=1' }
|
166
|
+
},
|
167
|
+
enableHoverOptions: {
|
168
|
+
type: Boolean,
|
169
|
+
default: true
|
170
|
+
},
|
171
|
+
serviceName: {
|
172
|
+
type: String,
|
173
|
+
default: 'af-his'
|
174
|
+
},
|
175
|
+
// 点击是否触发选中(默认不通过点击选中,仅手动控制)
|
176
|
+
selectOnClick: {
|
177
|
+
type: Boolean,
|
178
|
+
default: false
|
179
|
+
},
|
180
|
+
// 受控选中索引;不传则内部维护
|
181
|
+
selectedIndex: {
|
182
|
+
type: Number,
|
183
|
+
default: undefined
|
184
|
+
},
|
185
|
+
// 卡片按钮显示控制(默认隐藏,可通过属性或配置开启)
|
186
|
+
showCardButtons: {
|
187
|
+
type: Boolean,
|
188
|
+
default: false
|
189
|
+
},
|
190
|
+
// 标题右侧下拉选项与受控值
|
191
|
+
titleSelectOptions: {
|
192
|
+
type: Array,
|
193
|
+
default: () => []
|
194
|
+
},
|
195
|
+
titleSelectValue: {
|
196
|
+
type: [String, Number],
|
197
|
+
default: undefined
|
198
|
+
}
|
199
|
+
},
|
200
|
+
data () {
|
201
|
+
return {
|
202
|
+
data: [], // 数据源
|
203
|
+
localData: [], // 本地数据源
|
204
|
+
loading: false, // 加载中
|
205
|
+
busy: false, // 繁忙状态
|
206
|
+
button: false,
|
207
|
+
icon: false,
|
208
|
+
buttonNames: [],
|
209
|
+
listMode: undefined, // 列表模式
|
210
|
+
buttonMode: true,
|
211
|
+
hoveredIndex: -1, // 当前悬浮的索引
|
212
|
+
isOptionsHovered: false, // 悬浮选项框是否悬浮
|
213
|
+
hoverTimer: null, // 悬浮选项框定时器
|
214
|
+
leaveTimer: null, // 离开选项框定时器
|
215
|
+
select_options: [], // 悬浮选项框
|
216
|
+
logicName: '',
|
217
|
+
nowPage: 0, // 当前页
|
218
|
+
pageSize: 12,
|
219
|
+
allLoaded: false,
|
220
|
+
label: 'id',
|
221
|
+
scrollTimer: null,
|
222
|
+
// 内部选中索引(非受控时使用)
|
223
|
+
localSelectedIndex: -1,
|
224
|
+
maleIcon: require('@vue2-client/assets/svg/male.svg'),
|
225
|
+
femaleIcon: require('@vue2-client/assets/svg/female.svg'),
|
226
|
+
// 下拉配置
|
227
|
+
internalTitleOptions: [],
|
228
|
+
titleValueByIndex: {},
|
229
|
+
// 选择请求的轻量防抖
|
230
|
+
selectDebounceTimer: null
|
231
|
+
}
|
232
|
+
},
|
233
|
+
inject: ['getComponentByName'],
|
234
|
+
|
235
|
+
created () {
|
236
|
+
this.getData(this.queryParamsName, this.fixedQueryForm)
|
237
|
+
},
|
238
|
+
mounted () {},
|
239
|
+
computed: {
|
240
|
+
// 参考 HForm 的 wrapperClassObject 规则,支持通过组件属性动态控制样式
|
241
|
+
wrapperClassObject () {
|
242
|
+
const a = this.$attrs || {}
|
243
|
+
const classes = {}
|
244
|
+
// 多个布尔型样式开关(存在且为真则生效)
|
245
|
+
const booleanStyleKeys = [
|
246
|
+
''
|
247
|
+
]
|
248
|
+
booleanStyleKeys.forEach(key => {
|
249
|
+
const val = a[key]
|
250
|
+
const truthy = val === true || val === '' || val === 'true'
|
251
|
+
if (truthy) classes[`x-list-${key}`] = true
|
252
|
+
})
|
253
|
+
return classes
|
254
|
+
},
|
255
|
+
// 标题下拉:优先使用外部 props,否则使用内部配置
|
256
|
+
getTitleOptions () {
|
257
|
+
if (Array.isArray(this.titleSelectOptions) && this.titleSelectOptions.length) return this.titleSelectOptions
|
258
|
+
return this.internalTitleOptions
|
259
|
+
},
|
260
|
+
currentTitleValue () {
|
261
|
+
return (index) => {
|
262
|
+
if (this.titleSelectValue !== undefined) return this.titleSelectValue
|
263
|
+
const local = this.titleValueByIndex[index]
|
264
|
+
if (local !== undefined) return local
|
265
|
+
// 二次回退:若模板标题提供了 titleRightValue,则用于首屏渲染颜色与文案
|
266
|
+
const row = Array.isArray(this.localData) ? this.localData[index] : null
|
267
|
+
if (Array.isArray(row)) {
|
268
|
+
const title = row.find(d => d && d.type === 'title')
|
269
|
+
if (title && title.titleRightValue !== undefined) return title.titleRightValue
|
270
|
+
}
|
271
|
+
// 最终回退:使用 options 的第一个
|
272
|
+
const def = this.internalTitleOptions && this.internalTitleOptions.length ? (this.internalTitleOptions[0].value !== undefined ? this.internalTitleOptions[0].value : this.internalTitleOptions[0]) : undefined
|
273
|
+
return def
|
274
|
+
}
|
275
|
+
},
|
276
|
+
// 选择控制:受控优先
|
277
|
+
currentSelectedIndex () {
|
278
|
+
return typeof this.selectedIndex === 'number' ? this.selectedIndex : this.localSelectedIndex
|
279
|
+
},
|
280
|
+
enableSelectRow () {
|
281
|
+
const a = this.$attrs || {}
|
282
|
+
const val = a.enableSelection
|
283
|
+
return val === true || val === '' || val === 'true'
|
284
|
+
},
|
285
|
+
forwardAllEvents () {
|
286
|
+
return {
|
287
|
+
// 监听所有事件并转发给父组件
|
288
|
+
'*': (eventName, ...payload) => {
|
289
|
+
this.$emit(eventName, ...payload)
|
290
|
+
}
|
291
|
+
}
|
292
|
+
}
|
293
|
+
},
|
294
|
+
methods: {
|
295
|
+
onChange (date, dateString) {
|
296
|
+
this.$emit('dateChange', date, dateString)
|
297
|
+
},
|
298
|
+
handleInfiniteOnLoad (event) {
|
299
|
+
if (this.busy || this.allLoaded) return // 防止重复加载
|
300
|
+
if (this.scrollTimer) clearTimeout(this.scrollTimer)
|
301
|
+
this.scrollTimer = setTimeout(() => {
|
302
|
+
const container = event.target
|
303
|
+
const scrollTop = container.scrollTop
|
304
|
+
const scrollHeight = container.scrollHeight
|
305
|
+
const clientHeight = container.clientHeight
|
306
|
+
const bottomOffset = 10
|
307
|
+
if (scrollTop + clientHeight >= scrollHeight - bottomOffset) {
|
308
|
+
this.busy = true
|
309
|
+
this.loading = true
|
310
|
+
this.nowPage = this.nowPage + this.pageSize
|
311
|
+
this.fixedQueryForm.condition = `Limit ${this.nowPage}, ${this.pageSize}`
|
312
|
+
runLogic(this.logicName, this.fixedQueryForm, 'af-his').then(async (res) => {
|
313
|
+
this.localData = [...this.localData, ...res]
|
314
|
+
if (res.length < this.pageSize) {
|
315
|
+
this.allLoaded = true
|
316
|
+
}
|
317
|
+
}).catch(e => {
|
318
|
+
this.$message.error(e.message)
|
319
|
+
}).finally(() => {
|
320
|
+
this.loading = false
|
321
|
+
this.busy = false
|
322
|
+
})
|
323
|
+
}
|
324
|
+
}, 100)
|
325
|
+
},
|
326
|
+
async getData (config, param) {
|
327
|
+
const that = this
|
328
|
+
getConfigByName(config, 'af-his', async (res) => {
|
329
|
+
that.listMode = await res.listMode == 'card'
|
330
|
+
that.logicName = await res.data
|
331
|
+
that.button = await res.button // 按钮
|
332
|
+
that.icon = await res.icon // 图标
|
333
|
+
that.label = await res.label // 标签
|
334
|
+
that.buttonNames = await res.buttonNames || []// 按钮文本
|
335
|
+
that.buttonMode = await res.buttonMode || false// 按钮模式
|
336
|
+
that.cardButtonsVisible = await res.cardButtonsVisible || false// 卡片按钮
|
337
|
+
// 标题下拉:从配置拿 options: [{label,value,color}]
|
338
|
+
if (Array.isArray(res.titleOptions)) {
|
339
|
+
this.internalTitleOptions = res.titleOptions
|
340
|
+
if (res.titleDefaultValue !== undefined && Array.isArray(this.localData)) {
|
341
|
+
// 初始化每个卡片的默认值
|
342
|
+
this.titleValueByIndex = {}
|
343
|
+
}
|
344
|
+
}
|
345
|
+
this.enableHoverOptions = await res.enableHoverOptions || false// 悬浮选项框
|
346
|
+
if (this.enableHoverOptions) {
|
347
|
+
this.select_options = await res.select_options
|
348
|
+
}
|
349
|
+
if (that.listMode) { param.condition = `Limit ${that.nowPage}, ${that.pageSize}` }
|
350
|
+
runLogic(res.data, param, 'af-his').then(result => {
|
351
|
+
that.data = result
|
352
|
+
if (that.nowPage === 0) { this.localData = result }
|
353
|
+
// 初始化每张卡片的 title 选中值:优先读取模板 titleRightValue,否则使用配置默认或第一项
|
354
|
+
if (Array.isArray(this.localData)) {
|
355
|
+
this.localData.forEach((row, idx) => {
|
356
|
+
if (this.titleValueByIndex[idx] !== undefined) return
|
357
|
+
if (Array.isArray(row)) {
|
358
|
+
const title = row.find(d => d && d.type === 'title')
|
359
|
+
if (title && title.titleRightValue !== undefined) {
|
360
|
+
this.$set(this.titleValueByIndex, idx, title.titleRightValue)
|
361
|
+
return
|
362
|
+
}
|
363
|
+
}
|
364
|
+
// fallback:配置默认或第一项
|
365
|
+
const def = (this.internalTitleOptions && this.internalTitleOptions.length)
|
366
|
+
? (this.internalTitleOptions[0].value !== undefined ? this.internalTitleOptions[0].value : this.internalTitleOptions[0])
|
367
|
+
: undefined
|
368
|
+
if (def !== undefined) this.$set(this.titleValueByIndex, idx, def)
|
369
|
+
})
|
370
|
+
}
|
371
|
+
})
|
372
|
+
})
|
373
|
+
},
|
374
|
+
// 提取性别:从数组数据中查找 label 含“性别”的项
|
375
|
+
getGender (item) {
|
376
|
+
if (!Array.isArray(item)) return null
|
377
|
+
const g = item.find(d => d && typeof d.label === 'string' && d.label.indexOf('性别') > -1)
|
378
|
+
const val = g && (g.value || g.text)
|
379
|
+
if (!val) return null
|
380
|
+
if (String(val).includes('男')) return '男'
|
381
|
+
if (String(val).includes('女')) return '女'
|
382
|
+
return null
|
383
|
+
},
|
384
|
+
// 读取标题项上的 hasPatient 布尔值
|
385
|
+
getHasPatient (item) {
|
386
|
+
if (!Array.isArray(item)) return true
|
387
|
+
const title = item.find(d => d && d.type === 'title')
|
388
|
+
if (!title || typeof title.hasPatient === 'undefined') return true
|
389
|
+
return !!title.hasPatient
|
390
|
+
},
|
391
|
+
// 标题右侧下拉选择
|
392
|
+
handleTitleSelectChange (val, index) {
|
393
|
+
// 重复值拦截:若值未变化则直接返回
|
394
|
+
const prev = this.normalizeValue(this.currentTitleValue(index))
|
395
|
+
const next = this.normalizeValue(val)
|
396
|
+
if (prev === next) return
|
397
|
+
|
398
|
+
this.$set(this.titleValueByIndex, index, val)
|
399
|
+
this.$emit('update:titleSelectValue', val)
|
400
|
+
this.$emit('titleSelectChange', { index, value: val })
|
401
|
+
// 变更后仅通知后端更新,不处理返回值;支持从标题项读取 extraParams 合并(如住院号)
|
402
|
+
const extra = this.getTitleExtraParams(index)
|
403
|
+
if (this.selectDebounceTimer) clearTimeout(this.selectDebounceTimer)
|
404
|
+
this.selectDebounceTimer = setTimeout(() => {
|
405
|
+
runLogic(this.logicName, Object.assign({ type: 'select', value: val }, extra), 'af-his').catch(() => {})
|
406
|
+
}, 200)
|
407
|
+
},
|
408
|
+
// 读取标题项中额外需要传递的参数(可选),例如 { inpatientNo: row.t_id }
|
409
|
+
getTitleExtraParams (index) {
|
410
|
+
const row = Array.isArray(this.localData) ? this.localData[index] : null
|
411
|
+
if (!Array.isArray(row)) return {}
|
412
|
+
const title = row.find(d => d && d.type === 'title') || {}
|
413
|
+
if (title && title.extraParams && typeof title.extraParams === 'object') return title.extraParams
|
414
|
+
return {}
|
415
|
+
},
|
416
|
+
handleTitleMenuClick ({ key }, index) {
|
417
|
+
const v = isNaN(Number(key)) ? key : Number(key)
|
418
|
+
this.handleTitleSelectChange(v, index)
|
419
|
+
},
|
420
|
+
// 根据当前选择项的 color 设置卡片背景色
|
421
|
+
normalizeValue (v) { return String(v) },
|
422
|
+
getCardStyle (val) {
|
423
|
+
const target = this.normalizeValue(val)
|
424
|
+
const opt = (this.getTitleOptions || []).find(o => this.normalizeValue(o && (o.value !== undefined ? o.value : o)) === target)
|
425
|
+
const color = opt && (opt.color || (opt.style && opt.style.background))
|
426
|
+
return color ? { backgroundColor: color } : {}
|
427
|
+
},
|
428
|
+
getTitleLabel (val) {
|
429
|
+
const target = this.normalizeValue(val)
|
430
|
+
const opt = (this.getTitleOptions || []).find(o => this.normalizeValue(o && (o.value !== undefined ? o.value : o)) === target)
|
431
|
+
return opt && (opt.label !== undefined ? opt.label : opt)
|
432
|
+
},
|
433
|
+
getDisplayTitleLabel (index) {
|
434
|
+
// 优先展示“当前选中值”的 label,未选中再回退到模板提供的 titleRightLabel
|
435
|
+
const current = this.getTitleLabel(this.currentTitleValue(index))
|
436
|
+
if (current) return current
|
437
|
+
const title = Array.isArray(this.localData && this.localData[index])
|
438
|
+
? this.localData[index].find(d => d && d.type === 'title')
|
439
|
+
: null
|
440
|
+
return title && title.titleRightLabel ? title.titleRightLabel : ''
|
441
|
+
},
|
442
|
+
// 点击列表项
|
443
|
+
handleClick (index) {
|
444
|
+
if (this.enableSelectRow && this.selectOnClick) this.setSelectedIndex(index)
|
445
|
+
this.$emit('listClick', this.data[index])
|
446
|
+
},
|
447
|
+
// 卡片模式点击卡片
|
448
|
+
handleCardClick (index) {
|
449
|
+
if (this.enableSelectRow && this.selectOnClick) this.setSelectedIndex(index)
|
450
|
+
this.$emit('listClick', this.localData[index])
|
451
|
+
},
|
452
|
+
// 外部可调用:设置选中索引
|
453
|
+
setSelectedIndex (index) {
|
454
|
+
const next = typeof index === 'number' ? index : -1
|
455
|
+
if (typeof this.selectedIndex === 'number') {
|
456
|
+
// 受控:仅派发事件
|
457
|
+
this.$emit('update:selectedIndex', next)
|
458
|
+
} else {
|
459
|
+
// 非受控:更新内部并派发事件
|
460
|
+
this.localSelectedIndex = next
|
461
|
+
this.$emit('update:selectedIndex', next)
|
462
|
+
}
|
463
|
+
},
|
464
|
+
// 外部可调用:清空选中
|
465
|
+
clearSelected () { this.setSelectedIndex(-1) },
|
466
|
+
refreshList (param) {
|
467
|
+
this.getData(this.queryParamsName, param)
|
468
|
+
},
|
469
|
+
click (index, buttonIndex) {
|
470
|
+
this.$emit('click', { data: this.data[index], name: this.buttonNames[buttonIndex] })
|
471
|
+
},
|
472
|
+
// 根据对象字段匹配选中(默认按 id 字段)
|
473
|
+
setSelectedById (id, field = 'id') {
|
474
|
+
const list = this.listMode ? this.localData : this.data
|
475
|
+
if (!Array.isArray(list)) return
|
476
|
+
const index = list.findIndex(item => {
|
477
|
+
if (Array.isArray(item)) {
|
478
|
+
const f = item.find(d => d && d.label === field)
|
479
|
+
return f && f.value === id
|
480
|
+
}
|
481
|
+
return item && item[field] === id
|
482
|
+
})
|
483
|
+
if (index >= 0) this.setSelectedIndex(index)
|
484
|
+
},
|
485
|
+
// 根据 label/value(卡片数组数据场景)匹配选中
|
486
|
+
setSelectedByLabelValue (label, value) {
|
487
|
+
const list = this.listMode ? this.localData : this.data
|
488
|
+
if (!Array.isArray(list)) return
|
489
|
+
const index = list.findIndex(item => Array.isArray(item) && item.some(d => d && d.label === label && d.value === value))
|
490
|
+
if (index >= 0) this.setSelectedIndex(index)
|
491
|
+
},
|
492
|
+
// 通用:传入谓词函数决定选中项
|
493
|
+
setSelectedBy (predicate) {
|
494
|
+
if (typeof predicate !== 'function') return
|
495
|
+
const list = this.listMode ? this.localData : this.data
|
496
|
+
if (!Array.isArray(list)) return
|
497
|
+
const index = list.findIndex(predicate)
|
498
|
+
if (index >= 0) this.setSelectedIndex(index)
|
499
|
+
},
|
500
|
+
getIconStyle (item) {
|
501
|
+
return item.picture
|
502
|
+
? { backgroundImage: `url(${item.picture})` }
|
503
|
+
: {}
|
504
|
+
},
|
505
|
+
filterData (par) {
|
506
|
+
runLogic(this.queryParamsName, par, 'af-his').then(res => {
|
507
|
+
this.data = res.data
|
508
|
+
})
|
509
|
+
},
|
510
|
+
// 鼠标进入列表项
|
511
|
+
handleMouseEnter (index) {
|
512
|
+
this.clearAllTimers()
|
513
|
+
this.hoveredIndex = index
|
514
|
+
this.isOptionsHovered = true
|
515
|
+
},
|
516
|
+
// 鼠标离开列表项
|
517
|
+
handleMouseLeave () {
|
518
|
+
this.clearAllTimers()
|
519
|
+
this.leaveTimer = setTimeout(() => {
|
520
|
+
this.isOptionsHovered = false
|
521
|
+
this.hoveredIndex = -1
|
522
|
+
}, 100)
|
523
|
+
},
|
524
|
+
// 鼠标进入悬浮选项框
|
525
|
+
handleOptionsEnter () {
|
526
|
+
this.clearAllTimers()
|
527
|
+
this.isOptionsHovered = true
|
528
|
+
},
|
529
|
+
// 鼠标离开悬浮选项框
|
530
|
+
handleOptionsLeave () {
|
531
|
+
this.clearAllTimers()
|
532
|
+
this.leaveTimer = setTimeout(() => {
|
533
|
+
this.isOptionsHovered = false
|
534
|
+
this.hoveredIndex = -1
|
535
|
+
}, 100)
|
536
|
+
},
|
537
|
+
// 清除所有定时器
|
538
|
+
clearAllTimers () {
|
539
|
+
if (this.hoverTimer) {
|
540
|
+
clearTimeout(this.hoverTimer)
|
541
|
+
this.hoverTimer = null
|
542
|
+
}
|
543
|
+
if (this.leaveTimer) {
|
544
|
+
clearTimeout(this.leaveTimer)
|
545
|
+
this.leaveTimer = null
|
546
|
+
}
|
547
|
+
},
|
548
|
+
// 选项框点击
|
549
|
+
handleOptionClick (index, action) {
|
550
|
+
this.$emit('optionClick', { data: this.data[index], action })
|
551
|
+
}
|
552
|
+
},
|
553
|
+
watch: {
|
554
|
+
// 同步受控值变化
|
555
|
+
selectedIndex (val) {
|
556
|
+
if (typeof val === 'number') {
|
557
|
+
// 允许受控值影响内部显示
|
558
|
+
this.localSelectedIndex = val
|
559
|
+
}
|
560
|
+
},
|
561
|
+
fixedQueryForm: {
|
562
|
+
deep: true,
|
563
|
+
handler (val) {
|
564
|
+
this.refreshList(val)
|
565
|
+
}
|
566
|
+
}
|
567
|
+
},
|
568
|
+
beforeDestroy () {
|
569
|
+
const ref = this.$refs.listRef
|
570
|
+
if (ref && ref.removeEventListener) {
|
571
|
+
ref.removeEventListener('scroll', this.handleInfiniteOnLoad)
|
572
|
+
}
|
573
|
+
this.clearAllTimers()
|
574
|
+
}
|
575
|
+
}
|
576
|
+
</script>
|
577
|
+
|
578
|
+
<style scoped>
|
579
|
+
.list-wrapper {
|
580
|
+
max-height: 240px;
|
581
|
+
overflow-y: auto;
|
582
|
+
padding-right: 2px;
|
583
|
+
}
|
584
|
+
|
585
|
+
.list-container {
|
586
|
+
width: 100%;
|
587
|
+
}
|
588
|
+
|
589
|
+
.list-item {
|
590
|
+
height: 35px;
|
591
|
+
border-radius: 6px;
|
592
|
+
background-color: #F4F4F4;
|
593
|
+
padding: 8px 15px;
|
594
|
+
font-size: 16px;
|
595
|
+
display: flex;
|
596
|
+
align-items: center;
|
597
|
+
width: 100%;
|
598
|
+
border: 1px solid #D9D9D9;
|
599
|
+
box-sizing: border-box;
|
600
|
+
margin-bottom: 8px !important;
|
601
|
+
position: relative;
|
602
|
+
transition: background-color 0.3s ease;
|
603
|
+
}
|
604
|
+
|
605
|
+
.icon-menu {
|
606
|
+
display: inline-block;
|
607
|
+
width: 20px;
|
608
|
+
height: 20px;
|
609
|
+
background-color: #ccc;
|
610
|
+
margin-right: 8px;
|
611
|
+
}
|
612
|
+
|
613
|
+
.item-text {
|
614
|
+
flex: 1;
|
615
|
+
}
|
616
|
+
|
617
|
+
.confirm-btn {
|
618
|
+
margin-left: auto;
|
619
|
+
padding: 0 8px;
|
620
|
+
}
|
621
|
+
|
622
|
+
.confirm-btn.hover-btn {
|
623
|
+
opacity: 0;
|
624
|
+
transition: opacity 0.3s ease;
|
625
|
+
}
|
626
|
+
|
627
|
+
.button-group {
|
628
|
+
display: flex;
|
629
|
+
gap: 2px; /* 按钮之间的间距 */
|
630
|
+
}
|
631
|
+
|
632
|
+
.list-item:hover .confirm-btn.hover-btn {
|
633
|
+
opacity: 1;
|
634
|
+
}
|
635
|
+
|
636
|
+
/* 自定义滚动条样式 */
|
637
|
+
.list-wrapper::-webkit-scrollbar {
|
638
|
+
width: 6px;
|
639
|
+
}
|
640
|
+
|
641
|
+
.list-wrapper::-webkit-scrollbar-thumb {
|
642
|
+
background-color: #d9d9d9;
|
643
|
+
border-radius: 3px;
|
644
|
+
}
|
645
|
+
|
646
|
+
.list-wrapper::-webkit-scrollbar-track {
|
647
|
+
background-color: #f0f0f0;
|
648
|
+
}
|
649
|
+
|
650
|
+
.hover-active {
|
651
|
+
color: white;
|
652
|
+
}
|
653
|
+
|
654
|
+
.list-item.hover-active {
|
655
|
+
background-color: rgb(0, 87, 254) !important;
|
656
|
+
color: white;
|
657
|
+
border: 1px solid black;
|
658
|
+
}
|
659
|
+
|
660
|
+
/* 选中态样式(通过 selectRow 开启) */
|
661
|
+
.selected-active { color: white; }
|
662
|
+
.list-item.selected-active {
|
663
|
+
background-color: #0057FE !important;
|
664
|
+
color: white;
|
665
|
+
border: none !important;
|
666
|
+
}
|
667
|
+
.list-item.selected-active .confirm-btn,
|
668
|
+
.list-item.selected-active .confirm-btn span,
|
669
|
+
.list-item.selected-active .ant-btn,
|
670
|
+
.list-item.selected-active .ant-btn > span { color: #ffffff !important; }
|
671
|
+
.card-a-col.selected-active {
|
672
|
+
background-color: #0057FE !important;
|
673
|
+
color: white;
|
674
|
+
border: none !important;
|
675
|
+
}
|
676
|
+
.card-a-col.selected-active .button-a-col,
|
677
|
+
.card-a-col.selected-active .button-a-col .ant-btn,
|
678
|
+
.card-a-col.selected-active .button-a-col .ant-btn > span { color: #ffffff !important; }
|
679
|
+
|
680
|
+
.hover-options {
|
681
|
+
position: absolute;
|
682
|
+
left: 0;
|
683
|
+
right: 0;
|
684
|
+
top: 100%;
|
685
|
+
background: white;
|
686
|
+
border: 1px solid #d9d9d9;
|
687
|
+
border-radius: 4px;
|
688
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
689
|
+
z-index: 1000;
|
690
|
+
margin-top: 4px;
|
691
|
+
width: 100%;
|
692
|
+
box-sizing: border-box;
|
693
|
+
pointer-events: auto;
|
694
|
+
}
|
695
|
+
|
696
|
+
.hover-options-content {
|
697
|
+
padding: 4px 0;
|
698
|
+
display: flex;
|
699
|
+
flex-direction: column;
|
700
|
+
width: 100%;
|
701
|
+
}
|
702
|
+
|
703
|
+
.option-item {
|
704
|
+
padding: 8px 12px;
|
705
|
+
cursor: pointer;
|
706
|
+
transition: all 0.3s ease;
|
707
|
+
color: #333;
|
708
|
+
font-size: 14px;
|
709
|
+
display: flex;
|
710
|
+
align-items: center;
|
711
|
+
}
|
712
|
+
|
713
|
+
.option-item:hover {
|
714
|
+
background-color: #f5f5f5;
|
715
|
+
color: #1890ff;
|
716
|
+
}
|
717
|
+
|
718
|
+
.option-item:active {
|
719
|
+
background-color: #e6f7ff;
|
720
|
+
}
|
721
|
+
|
722
|
+
.demo-loading-container {
|
723
|
+
position: absolute;
|
724
|
+
bottom: 40px;
|
725
|
+
width: 100%;
|
726
|
+
text-align: center;
|
727
|
+
}
|
728
|
+
|
729
|
+
.demo-infinite-container{
|
730
|
+
overflow-x: hidden;
|
731
|
+
overflow-y: auto;
|
732
|
+
height: 85vh;
|
733
|
+
}
|
734
|
+
.card-row{
|
735
|
+
height: 100%;
|
736
|
+
width: 100%;
|
737
|
+
border-radius: 6px;
|
738
|
+
padding: 6px;
|
739
|
+
}
|
740
|
+
.id-a-col{
|
741
|
+
font-size: 22px;
|
742
|
+
font-weight: 700;
|
743
|
+
display: flex;
|
744
|
+
flex-direction: column;
|
745
|
+
align-items: center;
|
746
|
+
justify-content: center;
|
747
|
+
gap: 4px;
|
748
|
+
}
|
749
|
+
.id-a-col-2{
|
750
|
+
padding: 8px;
|
751
|
+
height: 100%;
|
752
|
+
overflow: hidden;
|
753
|
+
display: flex;
|
754
|
+
flex-direction: column;
|
755
|
+
justify-content: space-between;
|
756
|
+
}
|
757
|
+
.describe-list-a-col{
|
758
|
+
display: block;
|
759
|
+
font-family: Source Han Sans;
|
760
|
+
font-size: 16px;
|
761
|
+
font-weight: normal;
|
762
|
+
line-height: normal;
|
763
|
+
overflow: hidden;
|
764
|
+
text-overflow: ellipsis;
|
765
|
+
white-space: nowrap;
|
766
|
+
max-width: 100%;
|
767
|
+
}
|
768
|
+
.name{
|
769
|
+
font-family: Source Han Sans;
|
770
|
+
font-size: 22px;
|
771
|
+
font-weight: bold;
|
772
|
+
line-height: normal;
|
773
|
+
}
|
774
|
+
.component-a-col{
|
775
|
+
display: flex;
|
776
|
+
align-items: center;
|
777
|
+
overflow: hidden;
|
778
|
+
}
|
779
|
+
.label-text{
|
780
|
+
overflow: hidden;
|
781
|
+
text-overflow: ellipsis;
|
782
|
+
white-space: nowrap;
|
783
|
+
max-width: 120px;
|
784
|
+
display: inline-block;
|
785
|
+
}
|
786
|
+
.gender-icon{
|
787
|
+
margin-top: 2px;
|
788
|
+
}
|
789
|
+
.gender-icon .male{ color: #2f54eb; font-size: 14px; }
|
790
|
+
.gender-icon .female{ color: #eb2f96; font-size: 14px; }
|
791
|
+
.gender-img{ width: 100%; height: 100%; opacity: 1; display: inline-block; }
|
792
|
+
.title-row{
|
793
|
+
display: flex;
|
794
|
+
align-items: center;
|
795
|
+
justify-content: space-between;
|
796
|
+
gap: 8px;
|
797
|
+
}
|
798
|
+
/* 标题文本占满剩余空间,确保箭头贴右 */
|
799
|
+
.title-row .describe-list-a-col{ flex: 1; }
|
800
|
+
.title-actions{ display: inline-flex; align-items: center; gap: 6px; }
|
801
|
+
.title-select-label{
|
802
|
+
font-family: Source Han Sans;
|
803
|
+
font-size: 16px;
|
804
|
+
font-weight: normal;
|
805
|
+
line-height: normal;
|
806
|
+
color: #595959;
|
807
|
+
}
|
808
|
+
.title-select{ min-width: 96px; }
|
809
|
+
.arrow-btn{ display: inline-flex; align-items: center; color: #8C8C8C; }
|
810
|
+
.arrow-btn .anticon{ font-size: 18px; }
|
811
|
+
/* 放大下拉箭头的 svg 尺寸以符合 UI(scoped 深度选择) */
|
812
|
+
::v-deep .arrow-btn .anticon svg{ width: 2em; height: 2em; }
|
813
|
+
.button-a-col{
|
814
|
+
position: absolute;
|
815
|
+
top: 245px;
|
816
|
+
left: 225px;
|
817
|
+
}
|
818
|
+
.card-a-col{
|
819
|
+
background-color: rgba(247, 249, 252);
|
820
|
+
height: 297px;
|
821
|
+
width: auto;
|
822
|
+
border-radius: 6px;
|
823
|
+
border: 1px solid #E5E9F0;
|
824
|
+
overflow: hidden;
|
825
|
+
position: relative;
|
826
|
+
box-sizing: border-box;
|
827
|
+
margin: 10px;
|
828
|
+
}
|
829
|
+
</style>
|