af-mobile-client-vue3 1.1.11 → 1.1.12

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.
@@ -1,763 +1,807 @@
1
- <script setup lang="ts">
2
- import XBadge from '@af-mobile-client-vue3/components/data/XBadge/index.vue'
3
- import XCellListFilter from '@af-mobile-client-vue3/components/data/XCellListFilter/index.vue'
4
- import { getConfigByName, query } from '@af-mobile-client-vue3/services/api/common'
5
- import LoadError from '@af-mobile-client-vue3/views/common/LoadError.vue'
6
- import {
7
- showConfirmDialog,
8
- BackTop as VanBackTop,
9
- Button as VanButton,
10
- Col as VanCol,
11
- Icon as VanIcon,
12
- List as VanList,
13
- Popover as VanPopover,
14
- PullRefresh as VanPullRefresh,
15
- Row as VanRow,
16
- Search as VanSearch,
17
- Space as VanSpace,
18
- Tag as VanTag,
19
- } from 'vant'
20
- import { computed, defineEmits, defineProps, onBeforeMount, ref, watch } from 'vue'
21
- import { useRouter } from 'vue-router'
22
-
23
- const { configName, serviceName, fixQueryForm } = withDefaults(defineProps<{
24
- configName?: string
25
- fixQueryForm?: object
26
- idKey?: string
27
- serviceName?: string
28
- scanOptions?: {
29
- show?: boolean // 是否显示扫码按钮
30
- type?: string | string[] // 显示类型:可以是单个类型或类型数组 'nfc' / ['scan', 'nfc']
31
- defaultMode?: string // 默认模式
32
- }
33
- }>(), {
34
- configName: '',
35
- fixQueryForm: null,
36
- idKey: 'o_id',
37
- serviceName: undefined,
38
- scanOptions: undefined,
39
- })
40
-
41
- const emit = defineEmits([])
42
-
43
- const router = useRouter()
44
-
45
- const orderVal = ref(undefined)
46
-
47
- const sortordVal = ref(undefined)
48
-
49
- // 配置内容
50
- const configContent = ref({})
51
-
52
- // 主列
53
- const mainColumns = ref([])
54
-
55
- // 副标题列
56
- const subTitleColumns = ref([])
57
-
58
- // 详情列
59
- const detailColumns = ref([])
60
-
61
- // 标签列
62
- const tagList = ref([])
63
-
64
- // 标题按钮列
65
- const btnList = ref([])
66
-
67
- // 底部列
68
- const footColumns = ref([])
69
-
70
- // 所有的复杂操作列
71
- const allActions = ref([])
72
-
73
- // 复杂操作列前三项
74
- const mainActions = ref([])
75
-
76
- // 复杂操作列其余项
77
- const otherActions = ref([])
78
-
79
- // 数据集
80
- const list = ref([])
81
-
82
- // 每个 Popover 的显示状态
83
- const showPopover = ref<boolean[]>(list.value.map(() => false))
84
-
85
- // 排序集
86
- const orderList = ref([])
87
-
88
- // 表单查询数组
89
- const formQueryList = ref([])
90
-
91
- // 总数据
92
- const totalCount = ref(0)
93
-
94
- // 当前页数
95
- const pageNo = ref(1)
96
-
97
- // 每页数量
98
- const pageSize = 20
99
-
100
- const searchValue = ref('')
101
-
102
- const inputSpan = ref(16)
103
-
104
- // 数据加载状态
105
- const loading = ref(false)
106
- const refreshing = ref(false)
107
- const finished = ref(false)
108
- const isError = ref(false)
109
- const finishedText = ref('加载完成')
110
- // 避免查询多次
111
- const isLastPage = ref(false)
112
-
113
- const conditionParams = ref(undefined)
114
-
115
- // 主要按钮的状态
116
- const buttonState = ref(undefined)
117
-
118
- // 新增or查询的表单配置
119
- const groupFormItems = ref({})
120
- const title = ref('')
121
-
122
- onBeforeMount(() => {
123
- initComponent()
124
- })
125
-
126
- // 组件初始化
127
- function initComponent() {
128
- getConfigByName(configName, (result) => {
129
- groupFormItems.value = result
130
- title.value = result?.title
131
- for (let i = 0; i < result.columnJson.length; i++) {
132
- const item = result.columnJson[i]
133
- item.span = item.flexSpan
134
- if (item.slotType === 'badge')
135
- item.dictName = item.slotKeyMap
136
-
137
- if (item.mobileColumnType === 'mobile_header_column') {
138
- mainColumns.value.push(item)
139
- }
140
- else if (item.mobileColumnType === 'mobile_subtitle_column') {
141
- subTitleColumns.value.push(item)
142
- }
143
- else if (item.mobileColumnType === 'mobile_details_column') {
144
- detailColumns.value.push(item)
145
- }
146
- else if (item.mobileColumnType === 'mobile_footer_column') {
147
- footColumns.value.push(item)
148
- }
149
- else if (item.mobileColumnType === 'mobile_tag_column') {
150
- tagList.value.push(item)
151
- }
152
- else if (item.slotType === 'action' && item.actionArr) {
153
- for (let j = 0; j < item.actionArr.length; j++) {
154
- allActions.value.push({
155
- text: item.actionArr[j].text,
156
- func: item.actionArr[j].func,
157
- customFunction: item.actionArr[j].customFunction,
158
- })
159
- }
160
- }
161
-
162
- if (item.showTitleBtn)
163
- btnList.value.push(item)
164
-
165
- if (result.showSortIcon && item.sortable) {
166
- orderList.value.push({
167
- title: item.title,
168
- value: item.dataIndex,
169
- })
170
- }
171
- }
172
- configContent.value = result
173
- formQueryList.value = result.formJson
174
-
175
- if (result.buttonState) {
176
- buttonState.value = result.buttonState
177
- if (buttonState.value.edit && buttonState.value.edit === true)
178
- allActions.value.push({ text: '修改', func: 'updateRow' })
179
- if (buttonState.value.delete && buttonState.value.delete === true)
180
- allActions.value.push({ text: '删除', func: 'deleteRow' })
181
- }
182
- splitArrayAt(allActions.value, 3)
183
- }, serviceName)
184
- }
185
-
186
- // 刷新数据
187
- function onRefresh() {
188
- isError.value = false
189
- setTimeout(() => {
190
- // 重新加载数据
191
- // loading 设置为 true,表示处于加载状态
192
- refreshing.value = true
193
- finishedText.value = '加载完成'
194
- finished.value = false
195
- loading.value = true
196
- onLoad()
197
- }, 100)
198
- }
199
-
200
- // 加载数据
201
- function onLoad() {
202
- if (refreshing.value) {
203
- list.value = []
204
- pageNo.value = 1
205
- isLastPage.value = false
206
- }
207
- if (!isLastPage.value) {
208
- let searchVal = searchValue.value
209
- if (searchVal === '')
210
- searchVal = undefined
211
- query({
212
- queryParamsName: configName,
213
- pageNo: pageNo.value,
214
- pageSize,
215
- conditionParams: {
216
- $queryValue: searchVal,
217
- ...conditionParams.value,
218
- ...fixQueryForm,
219
- },
220
- sortField: orderVal?.value,
221
- sortOrder: sortordVal?.value,
222
- }, serviceName).then((res: any) => {
223
- totalCount.value = res.totalCount
224
- if (res.data.length === 0)
225
- isLastPage.value = true
226
-
227
- for (const item of res.data)
228
- list.value.push(item)
229
- if (list.value.length >= res.totalCount)
230
- finished.value = true
231
- else
232
- pageNo.value = pageNo.value + 1
233
- }).catch(() => {
234
- finishedText.value = ''
235
- finished.value = true
236
- isError.value = true
237
- }).finally(() => {
238
- // 加载状态结束
239
- loading.value = false
240
- refreshing.value = false
241
- })
242
- }
243
- }
244
-
245
- // 区分主要操作列与其他操作列
246
- function splitArrayAt<T>(array: T[], index: number) {
247
- mainActions.value = array.slice(0, index)
248
- otherActions.value = array.slice(index)
249
- }
250
-
251
- watch(() => searchValue.value, (newVal) => {
252
- if (newVal === '')
253
- onRefresh()
254
- })
255
-
256
- function handleFunctionStyle(funcString, param) {
257
- try {
258
- if (!funcString || funcString === '')
259
- return {}
260
-
261
- // 匹配参数名、函数体
262
- const innerFuncRegex = /function\s+\w*\((\w+)\)\s*\{([\s\S]*)\}/
263
- const matches = funcString.match(innerFuncRegex)
264
-
265
- const paramName = matches[1] // 提取参数名
266
-
267
- // eslint-disable-next-line no-new-func
268
- const func = new Function(paramName, matches[2])
269
-
270
- return func(param)
271
- }
272
- catch {
273
- return {}
274
- }
275
- }
276
-
277
- // 逆序排列主要按钮
278
- const reversedMainActions = computed(() => {
279
- return [...mainActions.value].reverse()
280
- })
281
-
282
- // 设置 Popover 的事件
283
- function onSelectMenu(item: any, event: any) {
284
- if (event.text === '删除') {
285
- showConfirmDialog({
286
- title: '删除',
287
- message:
288
- '请确认是否删除!!!',
289
- }).then(() => {
290
- emit(event.func, item)
291
- }).catch(() => {
292
- })
293
- }
294
- else if (event.text === '修改') {
295
- router.push({
296
- name: 'XForm',
297
- // params: { id: item },
298
- query: {
299
- groupFormItems: JSON.stringify(groupFormItems.value),
300
- serviceName,
301
- formData: JSON.stringify(item),
302
- mode: '修改',
303
- },
304
- })
305
- // emit('addOption', totalCount.value)
306
- }
307
- else {
308
- emit(event.func, item)
309
- }
310
- }
311
-
312
- // 抛出新增按钮的事件
313
- function addOption() {
314
- router.push({
315
- name: 'XForm',
316
- // params: { id: totalCount.value },
317
- query: {
318
- groupFormItems: JSON.stringify(groupFormItems.value),
319
- serviceName,
320
- formData: JSON.stringify({}),
321
- mode: '新增',
322
- },
323
- })
324
- // emit('addOption', totalCount.value)
325
- }
326
-
327
- // 处理按钮点击
328
- function handleButtonClick(btn, item) {
329
- emit(`${btn.btnIcon}`, item)
330
- }
331
-
332
- // 处理自定义函数
333
- function evaluateCustomFunction(funcString: string | undefined, record: any, index: number): boolean {
334
- try {
335
- // 如果 customFunction 不存在,返回 true 表示正常显示
336
- if (!funcString || funcString === '')
337
- return true
338
-
339
- // 匹配参数名、函数体
340
- const innerFuncRegex = /function\s*\((\w+)\s*,\s*(\w+)\)\s*\{([\s\S]*)\}/
341
- const matches = funcString.match(innerFuncRegex)
342
-
343
- if (!matches)
344
- return true
345
-
346
- const [, param1, param2, functionBody] = matches
347
-
348
- // eslint-disable-next-line no-new-func
349
- const func = new Function(param1, param2, functionBody)
350
- return func(record, index)
351
- }
352
- catch (error) {
353
- console.error('Error evaluating custom function:', error)
354
- return true
355
- }
356
- }
357
- </script>
358
-
359
- <template>
360
- <div id="XCellList">
361
- <VanRow class="filter-condition">
362
- <VanCol :span="inputSpan">
363
- <VanSearch
364
- v-model="searchValue"
365
- class="title-search"
366
- clearable
367
- placeholder="搜索信息..."
368
- shape="round"
369
- @search="onRefresh"
370
- />
371
- </VanCol>
372
- <VanCol span="8" class="search-icon">
373
- <XCellListFilter
374
- v-model:sortord-val="sortordVal"
375
- v-model:order-val="orderVal"
376
- v-model:condition-params="conditionParams"
377
- :fix-query-form="fixQueryForm"
378
- :service-name="serviceName"
379
- :order-list="orderList"
380
- :form-query="formQueryList"
381
- :button-state="buttonState"
382
- :scan-options="scanOptions"
383
- @on-refresh="onRefresh"
384
- @add-option="addOption"
385
- />
386
- </VanCol>
387
- </VanRow>
388
- <slot name="search-after" />
389
- <div class="main">
390
- <VanPullRefresh v-model="refreshing" :success-text="finishedText" head-height="70" @refresh="onRefresh">
391
- <template v-if="!isError">
392
- <VanList
393
- v-model:loading="loading"
394
- class="list_main"
395
- :finished="finished"
396
- finished-text="本来无一物,何处惹尘埃"
397
- @load="onLoad"
398
- >
399
- <div v-for="(item, index) in list" :key="`card_${index}`" class="card_item_main">
400
- <VanRow gutter="20" class="card_item_header" align="center" @click="emit('toDetail', item)">
401
- <VanCol :span="24">
402
- <div class="title-row">
403
- <div v-for="(column) in mainColumns" :key="`main_${column.dataIndex}`" class="main-title">
404
- <p
405
- class="card_item_title"
406
- :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
407
- >
408
- {{ item[column.dataIndex] ? item[column.dataIndex] : '--' }}
409
- </p>
410
- </div>
411
- <div v-for="(column) in subTitleColumns" :key="`subtitle_${column.dataIndex}`" class="sub-title">
412
- <p
413
- class="card_item_subtitle"
414
- :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
415
- >
416
- <XBadge
417
- :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
418
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
419
- :service-name="serviceName"
420
- />
421
- </p>
422
- </div>
423
- <div class="action-buttons">
424
- <VanButton
425
- v-for="btn in btnList"
426
- :key="btn.dataIndex"
427
- class="action-button"
428
- :icon="btn.btnIcon"
429
- size="small"
430
- @click.stop="handleButtonClick(btn, item)"
431
- />
432
- </div>
433
- </div>
434
- </VanCol>
435
- </VanRow>
436
- <VanRow gutter="20" class="card_item_details" @click="emit('toDetail', item)">
437
- <VanCol v-for="column of detailColumns" :key="`details_${column.dataIndex}`" :span="column.span">
438
- <p>
439
- {{ `${column.title}: ` }}
440
- <XBadge
441
- :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
442
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
443
- :service-name="serviceName"
444
- />
445
- </p>
446
- </VanCol>
447
- </VanRow>
448
- <VanRow v-if="tagList.length > 0" gutter="20" class="tag-row" @click="emit('toDetail', item)">
449
- <VanCol :span="24">
450
- <div class="tag-container">
451
- <div class="tag-wrapper">
452
- <VanCol
453
- v-for="column of tagList"
454
- :key="`tag_${column.dataIndex}`"
455
- :span="column.flexSpan"
456
- class="tag-col"
457
- >
458
- <VanTag
459
- :text-color="column.tagColor"
460
- :color="column.tagBorderColor"
461
- round
462
- size="medium"
463
- class="tag-item"
464
- >
465
- <div class="tag-content">
466
- <VanIcon v-if="column.tagIcon" :name="column.tagIcon" class="tag-icon" />
467
- <span class="tag-title">{{ column.title }}:</span>
468
- <XBadge
469
- :dict-name="column.dictName"
470
- :dict-value="item[column.dataIndex]"
471
- :service-name="serviceName"
472
- />
473
- </div>
474
- </VanTag>
475
- </VanCol>
476
- </div>
477
- </div>
478
- </VanCol>
479
- </VanRow>
480
- <VanRow gutter="20" class="card_item_footer" @click="emit('toDetail', item)">
481
- <VanCol v-for="column of footColumns" :key="`foot_${column.dataIndex}`" :span="12">
482
- <p>
483
- <span :style="handleFunctionStyle(column.styleFunctionForTitle, item[column.dataIndex])">
484
- {{ column.title }}:
485
- </span>
486
- <XBadge
487
- :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
488
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
489
- :service-name="serviceName"
490
- />
491
- </p>
492
- </VanCol>
493
- </VanRow>
494
- <VanRow v-if="allActions.length > 0" gutter="20" class="card_item_bottom">
495
- <VanCol span="4">
496
- <VanPopover
497
- v-model:show="showPopover[index]"
498
- placement="bottom-start"
499
- :actions="otherActions.filter(action => evaluateCustomFunction(action.customFunction, item, index))"
500
- @select="onSelectMenu(item, $event)"
501
- >
502
- <template #reference>
503
- <div
504
- v-show="otherActions && otherActions.length !== 0 && otherActions.some(action => evaluateCustomFunction(action.customFunction, item, index))"
505
- class="more-button"
506
- >
507
- <span>⋯</span>
508
- </div>
509
- </template>
510
- </VanPopover>
511
- </VanCol>
512
- <VanCol span="20">
513
- <VanRow justify="end">
514
- <VanSpace>
515
- <VanButton
516
- v-for="button in reversedMainActions"
517
- v-show="evaluateCustomFunction(button.customFunction, item, index)"
518
- :key="button.func"
519
- type="primary"
520
- size="small"
521
- class="action-btn"
522
- @click="onSelectMenu(item, button)"
523
- >
524
- {{ button.text }}
525
- </VanButton>
526
- </VanSpace>
527
- </VanRow>
528
- </VanCol>
529
- </VanRow>
530
- </div>
531
- </VanList>
532
- </template>
533
- <template v-else>
534
- <LoadError />
535
- </template>
536
- </VanPullRefresh>
537
- <VanBackTop />
538
- </div>
539
- </div>
540
- </template>
541
-
542
- <style scoped lang="less">
543
- #XCellList {
544
- height: calc(94vh - var(--van-nav-bar-height) - 5px);
545
- --van-search-padding:5px;
546
- .main {
547
- overflow-y: auto;
548
- height: 100%;
549
- background-color: var(--van-background);
550
- padding: var(--van-padding-base) var(--van-padding-sm);
551
-
552
- p {
553
- white-space: nowrap;
554
- overflow: hidden;
555
- text-overflow: ellipsis;
556
- margin: 0;
557
- }
558
-
559
- .card_item_main {
560
- background-color: var(--van-background-2);
561
- border-radius: var(--van-radius-lg);
562
- margin: 0 0 var(--van-padding-xs) 0;
563
- padding: var(--van-padding-sm);
564
-
565
- .card_item_header {
566
- margin-bottom: var(--van-padding-base);
567
-
568
- .title-row {
569
- display: flex;
570
- align-items: center;
571
- margin-bottom: 8px;
572
- width: 100%;
573
-
574
- .main-title {
575
- display: inline-flex;
576
- align-items: center;
577
- .card_item_title {
578
- font-size: var(--van-font-size-lg);
579
- margin: 0;
580
- }
581
- }
582
-
583
- .sub-title {
584
- display: inline-flex;
585
- align-items: center;
586
- margin-left: 4px;
587
- .card_item_subtitle {
588
- font-size: var(--van-font-size-xs);
589
- color: var(--van-text-color-2);
590
- margin: 0;
591
- }
592
- }
593
-
594
- .action-buttons {
595
- display: flex;
596
- align-items: center;
597
- margin-left: auto;
598
- .action-button {
599
- margin-left: 4px;
600
- width: 36px;
601
- height: 36px;
602
- padding: 0;
603
- border: none;
604
- color: var(--van-primary-color);
605
- background-color: rgba(25, 137, 250, 0.3);
606
- border-radius: 4px;
607
- font-size: 20px;
608
- display: flex;
609
- align-items: center;
610
- justify-content: center;
611
- &:active {
612
- opacity: 0.7;
613
- background-color: rgba(25, 137, 250, 0.5);
614
- }
615
- }
616
- }
617
- }
618
- }
619
-
620
- .tag-row {
621
- margin-bottom: var(--van-padding-base);
622
- }
623
-
624
- .tag-container {
625
- width: 100%;
626
- .tag-wrapper {
627
- display: flex;
628
- flex-wrap: wrap;
629
- padding: 0 !important;
630
- align-items: center;
631
- width: 100%;
632
- margin: 0 -8px;
633
-
634
- .tag-col {
635
- display: flex;
636
- align-items: center;
637
- padding: 0 8px;
638
- min-height: 32px;
639
- }
640
-
641
- .tag-item {
642
- width: auto;
643
- text-align: left;
644
- font-size: var(--van-font-size-xs);
645
- display: flex;
646
- align-items: center;
647
- margin: 4px 0;
648
- justify-content: center;
649
- :deep(.van-tag) {
650
- width: fit-content;
651
- display: inline-flex;
652
- align-items: center;
653
- }
654
- .tag-content {
655
- display: flex;
656
- align-items: center;
657
- white-space: nowrap;
658
- }
659
- .tag-icon {
660
- margin-right: 4px;
661
- font-size: 14px;
662
- display: inline-flex;
663
- align-items: center;
664
- }
665
- .tag-title {
666
- font-weight: normal;
667
- }
668
- }
669
- }
670
- }
671
-
672
- .card_item_details {
673
- margin-bottom: var(--van-padding-base);
674
- font-size: var(--van-font-size-sm);
675
- color: #666;
676
-
677
- .van-col {
678
- margin-bottom: 2px;
679
- }
680
- }
681
-
682
- .card_item_footer {
683
- font-size: var(--van-font-size-sm);
684
- color: var(--van-text-color-2);
685
- margin-bottom: 15px;
686
-
687
- .van-col:last-child {
688
- text-align: right;
689
- }
690
-
691
- .van-col:first-child {
692
- text-align: left;
693
- }
694
- }
695
- }
696
- }
697
-
698
- .filter-condition {
699
- display: flex;
700
- align-items: center;
701
- padding: 8px 12px;
702
- background-color: #fff;
703
- :deep(.van-search) {
704
- padding: var(--van-search-padding);
705
- background-color: transparent;
706
- }
707
- :deep(.van-search__content) {
708
- border-radius: 5px;
709
- background-color: #f5f5f5;
710
- padding: 4px 8px;
711
- }
712
- :deep(.van-field__left-icon) {
713
- color: #999;
714
- }
715
- :deep(.van-cell) {
716
- background-color: transparent;
717
- }
718
- :deep(.van-field__control::placeholder) {
719
- color: #999;
720
- font-size: 14px;
721
- }
722
- .search-icon {
723
- display: flex;
724
- align-items: center;
725
- justify-content: flex-end;
726
- height: 100%;
727
- padding: 0;
728
- flex-shrink: 0;
729
- }
730
- }
731
-
732
- .custom-button {
733
- border: none !important;
734
- box-shadow: none !important;
735
- }
736
-
737
- .more-button {
738
- width: 24px;
739
- height: 24px;
740
- display: flex;
741
- align-items: center;
742
- justify-content: center;
743
- color: #666;
744
- cursor: pointer;
745
- font-size: 20px;
746
- background-color: #f7f8fa;
747
- border-radius: 4px;
748
- span {
749
- line-height: 1;
750
- margin-top: -2px;
751
- }
752
- &:active {
753
- opacity: 0.7;
754
- background-color: #ebedf0;
755
- }
756
- }
757
- .action-btn {
758
- min-width: 80px;
759
- border-radius: 20px;
760
- font-size: 14px;
761
- }
762
- }
763
- </style>
1
+ <script setup lang="ts">
2
+ import XBadge from '@af-mobile-client-vue3/components/data/XBadge/index.vue'
3
+ import XCellListFilter from '@af-mobile-client-vue3/components/data/XCellListFilter/index.vue'
4
+ import { getConfigByName, query } from '@af-mobile-client-vue3/services/api/common'
5
+ import LoadError from '@af-mobile-client-vue3/views/common/LoadError.vue'
6
+ import {
7
+ showConfirmDialog,
8
+ BackTop as VanBackTop,
9
+ Button as VanButton,
10
+ Col as VanCol,
11
+ Icon as VanIcon,
12
+ List as VanList,
13
+ Popover as VanPopover,
14
+ PullRefresh as VanPullRefresh,
15
+ Row as VanRow,
16
+ Search as VanSearch,
17
+ Space as VanSpace,
18
+ Tag as VanTag,
19
+ } from 'vant'
20
+ import { computed, defineEmits, defineProps, onBeforeMount, ref, watch } from 'vue'
21
+ import { useRouter } from 'vue-router'
22
+
23
+ const { configName, serviceName, fixQueryForm } = withDefaults(defineProps<{
24
+ configName?: string
25
+ fixQueryForm?: object
26
+ idKey?: string
27
+ serviceName?: string
28
+ scanOptions?: {
29
+ show?: boolean // 是否显示扫码按钮
30
+ type?: string | string[] // 显示类型:可以是单个类型或类型数组 'nfc' / ['scan', 'nfc']
31
+ defaultMode?: string // 默认模式
32
+ }
33
+ }>(), {
34
+ configName: '',
35
+ fixQueryForm: null,
36
+ idKey: 'o_id',
37
+ serviceName: undefined,
38
+ scanOptions: undefined,
39
+ })
40
+
41
+ const emit = defineEmits(['toDetail', 'update', 'deleteRow', 'add'])
42
+
43
+ const router = useRouter()
44
+
45
+ const orderVal = ref(undefined)
46
+
47
+ const sortordVal = ref(undefined)
48
+
49
+ // 配置内容
50
+ const configContent = ref({})
51
+
52
+ // 主列
53
+ const mainColumns = ref([])
54
+
55
+ // 副标题列
56
+ const subTitleColumns = ref([])
57
+
58
+ // 详情列
59
+ const detailColumns = ref([])
60
+
61
+ // 标签列
62
+ const tagList = ref([])
63
+
64
+ // 标题按钮列
65
+ const btnList = ref([])
66
+
67
+ // 底部列
68
+ const footColumns = ref([])
69
+
70
+ // 所有的复杂操作列
71
+ const allActions = ref([])
72
+
73
+ // 复杂操作列前三项
74
+ const mainActions = ref([])
75
+
76
+ // 复杂操作列其余项
77
+ const otherActions = ref([])
78
+
79
+ // 数据集
80
+ const list = ref([])
81
+
82
+ // 每个 Popover 的显示状态
83
+ const showPopover = ref<boolean[]>(list.value.map(() => false))
84
+
85
+ // 排序集
86
+ const orderList = ref([])
87
+
88
+ // 表单查询数组
89
+ const formQueryList = ref([])
90
+
91
+ // 总数据
92
+ const totalCount = ref(0)
93
+
94
+ // 当前页数
95
+ const pageNo = ref(1)
96
+
97
+ // 每页数量
98
+ const pageSize = 20
99
+
100
+ const searchValue = ref('')
101
+
102
+ // 数据加载状态
103
+ const loading = ref(false)
104
+ const refreshing = ref(false)
105
+ const finished = ref(false)
106
+ const isError = ref(false)
107
+ const finishedText = ref('加载完成')
108
+ // 避免查询多次
109
+ const isLastPage = ref(false)
110
+
111
+ const conditionParams = ref(undefined)
112
+
113
+ // 主要按钮的状态
114
+ const buttonState = ref(undefined)
115
+
116
+ // 新增or查询的表单配置
117
+ const groupFormItems = ref({})
118
+ const title = ref('')
119
+
120
+ onBeforeMount(() => {
121
+ initComponent()
122
+ })
123
+
124
+ // 组件初始化
125
+ function initComponent() {
126
+ getConfigByName(configName, (result) => {
127
+ groupFormItems.value = result
128
+ title.value = result?.title
129
+ for (let i = 0; i < result.columnJson.length; i++) {
130
+ const item = result.columnJson[i]
131
+ item.span = item.flexSpan
132
+ if (item.slotType === 'badge')
133
+ item.dictName = item.slotKeyMap
134
+
135
+ if (item.mobileColumnType === 'mobile_header_column') {
136
+ mainColumns.value.push(item)
137
+ }
138
+ else if (item.mobileColumnType === 'mobile_subtitle_column') {
139
+ subTitleColumns.value.push(item)
140
+ }
141
+ else if (item.mobileColumnType === 'mobile_details_column') {
142
+ detailColumns.value.push(item)
143
+ }
144
+ else if (item.mobileColumnType === 'mobile_footer_column') {
145
+ footColumns.value.push(item)
146
+ }
147
+ else if (item.mobileColumnType === 'mobile_tag_column') {
148
+ tagList.value.push(item)
149
+ }
150
+ else if (item.slotType === 'action' && item.actionArr) {
151
+ for (let j = 0; j < item.actionArr.length; j++) {
152
+ allActions.value.push({
153
+ text: item.actionArr[j].text,
154
+ func: item.actionArr[j].func,
155
+ customFunction: item.actionArr[j].customFunction,
156
+ })
157
+ }
158
+ }
159
+
160
+ if (item.showTitleBtn)
161
+ btnList.value.push(item)
162
+
163
+ if (result.showSortIcon && item.sortable) {
164
+ orderList.value.push({
165
+ title: item.title,
166
+ value: item.dataIndex,
167
+ })
168
+ }
169
+ }
170
+ configContent.value = result
171
+ formQueryList.value = result.formJson
172
+
173
+ if (result.buttonState) {
174
+ buttonState.value = result.buttonState
175
+ if (buttonState.value.edit && buttonState.value.edit === true)
176
+ allActions.value.push({ text: '修改', func: 'updateRow' })
177
+ if (buttonState.value.delete && buttonState.value.delete === true)
178
+ allActions.value.push({ text: '删除', func: 'deleteRow' })
179
+ }
180
+ splitArrayAt(allActions.value, 3)
181
+ }, serviceName)
182
+ }
183
+
184
+ // 刷新数据
185
+ function onRefresh() {
186
+ isError.value = false
187
+ setTimeout(() => {
188
+ // 重新加载数据
189
+ // loading 设置为 true,表示处于加载状态
190
+ refreshing.value = true
191
+ finishedText.value = '加载完成'
192
+ finished.value = false
193
+ loading.value = true
194
+ onLoad()
195
+ }, 100)
196
+ }
197
+
198
+ // 加载数据
199
+ function onLoad() {
200
+ if (refreshing.value) {
201
+ list.value = []
202
+ pageNo.value = 1
203
+ isLastPage.value = false
204
+ }
205
+ if (!isLastPage.value) {
206
+ let searchVal = searchValue.value
207
+ if (searchVal === '')
208
+ searchVal = undefined
209
+ query({
210
+ queryParamsName: configName,
211
+ pageNo: pageNo.value,
212
+ pageSize,
213
+ conditionParams: {
214
+ $queryValue: searchVal,
215
+ ...conditionParams.value,
216
+ ...fixQueryForm,
217
+ },
218
+ sortField: orderVal?.value,
219
+ sortOrder: sortordVal?.value,
220
+ }, serviceName).then((res: any) => {
221
+ totalCount.value = res.totalCount
222
+ if (res.data.length === 0)
223
+ isLastPage.value = true
224
+
225
+ for (const item of res.data)
226
+ list.value.push(item)
227
+ if (list.value.length >= res.totalCount)
228
+ finished.value = true
229
+ else
230
+ pageNo.value = pageNo.value + 1
231
+ }).catch(() => {
232
+ finishedText.value = ''
233
+ finished.value = true
234
+ isError.value = true
235
+ }).finally(() => {
236
+ // 加载状态结束
237
+ loading.value = false
238
+ refreshing.value = false
239
+ })
240
+ }
241
+ }
242
+
243
+ // 区分主要操作列与其他操作列
244
+ function splitArrayAt<T>(array: T[], index: number) {
245
+ mainActions.value = array.slice(0, index)
246
+ otherActions.value = array.slice(index)
247
+ }
248
+
249
+ watch(() => searchValue.value, (newVal) => {
250
+ if (newVal === '')
251
+ onRefresh()
252
+ })
253
+
254
+ function handleFunctionStyle(funcString, param) {
255
+ try {
256
+ if (!funcString || funcString === '')
257
+ return {}
258
+
259
+ // 匹配参数名、函数体
260
+ const innerFuncRegex = /function\s+\w*\((\w+)\)\s*\{([\s\S]*)\}/
261
+ const matches = funcString.match(innerFuncRegex)
262
+
263
+ const paramName = matches[1] // 提取参数名
264
+
265
+ // eslint-disable-next-line no-new-func
266
+ const func = new Function(paramName, matches[2])
267
+
268
+ return func(param)
269
+ }
270
+ catch {
271
+ return {}
272
+ }
273
+ }
274
+
275
+ // 逆序排列主要按钮
276
+ const reversedMainActions = computed(() => {
277
+ return [...mainActions.value].reverse()
278
+ })
279
+
280
+ // 设置 Popover 的事件
281
+ function onSelectMenu(item: any, event: any) {
282
+ if (event.text === '删除') {
283
+ showConfirmDialog({
284
+ title: '删除',
285
+ message:
286
+ '请确认是否删除!!!',
287
+ }).then(() => {
288
+ emit(event.func, item)
289
+ }).catch(() => {
290
+ })
291
+ }
292
+ else if (event.text === '修改') {
293
+ // 触发update事件,父组件可以监听并处理
294
+ // 使用isHandledByParent标志来跟踪是否已经被父组件处理
295
+ let isHandledByParent = false
296
+
297
+ // 使用一个自定义事件来获取父组件的处理结果
298
+ const handledByParent = (handled: boolean) => {
299
+ isHandledByParent = handled
300
+ }
301
+
302
+ // 触发update事件,将回调函数作为第二个参数传递给父组件
303
+ emit('update', item, handledByParent)
304
+
305
+ // 如果父组件已处理,则不执行默认行为
306
+ if (isHandledByParent) {
307
+ return
308
+ }
309
+
310
+ // 默认行为 - 导航到XForm页面
311
+ router.push({
312
+ name: 'XForm',
313
+ // params: { id: item },
314
+ query: {
315
+ groupFormItems: JSON.stringify(groupFormItems.value),
316
+ serviceName,
317
+ formData: JSON.stringify(item),
318
+ mode: '修改',
319
+ },
320
+ })
321
+ // emit('addOption', totalCount.value)
322
+ }
323
+ else {
324
+ emit(event.func, item)
325
+ }
326
+ }
327
+
328
+ // 抛出新增按钮的事件
329
+ function addOption() {
330
+ // 触发update事件,父组件可以监听并处理
331
+ // 使用isHandledByParent标志来跟踪是否已经被父组件处理
332
+ let isHandledByParent = false
333
+
334
+ // 使用一个自定义事件来获取父组件的处理结果
335
+ const handledByParent = (handled: boolean) => {
336
+ isHandledByParent = handled
337
+ }
338
+
339
+ // 触发update事件,将回调函数作为第二个参数传递给父组件
340
+ emit('add', handledByParent)
341
+
342
+ // 如果父组件已处理,则不执行默认行为
343
+ if (isHandledByParent) {
344
+ return
345
+ }
346
+
347
+ // 默认行为 - 导航到XForm页面
348
+ router.push({
349
+ name: 'XForm',
350
+ // params: { id: totalCount.value },
351
+ query: {
352
+ groupFormItems: JSON.stringify(groupFormItems.value),
353
+ serviceName,
354
+ formData: JSON.stringify({}),
355
+ mode: '新增',
356
+ },
357
+ })
358
+ // emit('addOption', totalCount.value)
359
+ }
360
+
361
+ // 处理按钮点击
362
+ function handleButtonClick(btn, item) {
363
+ emit(`${btn.btnIcon}`, item)
364
+ }
365
+
366
+ // 处理自定义函数
367
+ function evaluateCustomFunction(funcString: string | undefined, record: any, index: number): boolean {
368
+ try {
369
+ // 如果 customFunction 不存在,返回 true 表示正常显示
370
+ if (!funcString || funcString === '')
371
+ return true
372
+
373
+ // 匹配参数名、函数体
374
+ const innerFuncRegex = /function\s*\((\w+)\s*,\s*(\w+)\)\s*\{([\s\S]*)\}/
375
+ const matches = funcString.match(innerFuncRegex)
376
+
377
+ if (!matches)
378
+ return true
379
+
380
+ const [, param1, param2, functionBody] = matches
381
+
382
+ // eslint-disable-next-line no-new-func
383
+ const func = new Function(param1, param2, functionBody)
384
+ return func(record, index)
385
+ }
386
+ catch (error) {
387
+ console.error('Error evaluating custom function:', error)
388
+ return true
389
+ }
390
+ }
391
+ </script>
392
+
393
+ <template>
394
+ <div id="XCellList">
395
+ <VanRow class="filter-condition">
396
+ <VanCol>
397
+ <VanSearch
398
+ v-model="searchValue"
399
+ class="title-search"
400
+ clearable
401
+ placeholder="综合查询框..."
402
+ shape="round"
403
+ @search="onRefresh"
404
+ />
405
+ </VanCol>
406
+ <VanCol class="search-icon">
407
+ <XCellListFilter
408
+ v-model:sortord-val="sortordVal"
409
+ v-model:order-val="orderVal"
410
+ v-model:condition-params="conditionParams"
411
+ :fix-query-form="fixQueryForm"
412
+ :service-name="serviceName"
413
+ :order-list="orderList"
414
+ :form-query="formQueryList"
415
+ :button-state="buttonState"
416
+ :scan-options="scanOptions"
417
+ @on-refresh="onRefresh"
418
+ @add-option="addOption"
419
+ />
420
+ </VanCol>
421
+ </VanRow>
422
+ <slot name="search-after" />
423
+ <div class="main">
424
+ <VanPullRefresh v-model="refreshing" :success-text="finishedText" head-height="70" @refresh="onRefresh">
425
+ <template v-if="!isError">
426
+ <VanList
427
+ v-model:loading="loading"
428
+ class="list_main"
429
+ :finished="finished"
430
+ finished-text="本来无一物,何处惹尘埃"
431
+ @load="onLoad"
432
+ >
433
+ <div v-for="(item, index) in list" :key="`card_${index}`" class="card_item_main">
434
+ <VanRow gutter="20" class="card_item_header" align="center" @click="emit('toDetail', item)">
435
+ <VanCol :span="24">
436
+ <div class="title-row">
437
+ <div v-for="(column) in mainColumns" :key="`main_${column.dataIndex}`" class="main-title">
438
+ <p
439
+ class="card_item_title"
440
+ :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
441
+ >
442
+ {{ item[column.dataIndex] ? item[column.dataIndex] : '--' }}
443
+ </p>
444
+ </div>
445
+ <div v-for="(column) in subTitleColumns" :key="`subtitle_${column.dataIndex}`" class="sub-title">
446
+ <p
447
+ class="card_item_subtitle"
448
+ :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
449
+ >
450
+ <XBadge
451
+ :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
452
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
453
+ :service-name="serviceName"
454
+ />
455
+ </p>
456
+ </div>
457
+ <div class="action-buttons">
458
+ <VanButton
459
+ v-for="btn in btnList"
460
+ :key="btn.dataIndex"
461
+ class="action-button"
462
+ :icon="btn.btnIcon"
463
+ size="small"
464
+ @click.stop="handleButtonClick(btn, item)"
465
+ />
466
+ </div>
467
+ </div>
468
+ </VanCol>
469
+ </VanRow>
470
+ <VanRow gutter="20" class="card_item_details" @click="emit('toDetail', item)">
471
+ <VanCol v-for="column of detailColumns" :key="`details_${column.dataIndex}`" :span="column.span">
472
+ <p>
473
+ {{ `${column.title}: ` }}
474
+ <XBadge
475
+ :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
476
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
477
+ :service-name="serviceName"
478
+ />
479
+ </p>
480
+ </VanCol>
481
+ </VanRow>
482
+ <VanRow v-if="tagList.length > 0" gutter="20" class="tag-row" @click="emit('toDetail', item)">
483
+ <VanCol :span="24">
484
+ <div class="tag-container">
485
+ <div class="tag-wrapper">
486
+ <VanCol
487
+ v-for="column of tagList"
488
+ :key="`tag_${column.dataIndex}`"
489
+ :span="column.flexSpan"
490
+ class="tag-col"
491
+ >
492
+ <VanTag
493
+ :text-color="column.tagColor"
494
+ :color="column.tagBorderColor"
495
+ round
496
+ size="medium"
497
+ class="tag-item"
498
+ >
499
+ <div class="tag-content">
500
+ <VanIcon v-if="column.tagIcon" :name="column.tagIcon" class="tag-icon" />
501
+ <span class="tag-title">{{ column.title }}:</span>
502
+ <XBadge
503
+ :dict-name="column.dictName"
504
+ :dict-value="item[column.dataIndex]"
505
+ :service-name="serviceName"
506
+ />
507
+ </div>
508
+ </VanTag>
509
+ </VanCol>
510
+ </div>
511
+ </div>
512
+ </VanCol>
513
+ </VanRow>
514
+ <VanRow gutter="20" class="card_item_footer" @click="emit('toDetail', item)">
515
+ <VanCol v-for="column of footColumns" :key="`foot_${column.dataIndex}`" :span="12">
516
+ <p>
517
+ <span :style="handleFunctionStyle(column.styleFunctionForTitle, item[column.dataIndex])">
518
+ {{ column.title }}:
519
+ </span>
520
+ <XBadge
521
+ :style="handleFunctionStyle(column.styleFunctionForValue, item[column.dataIndex])"
522
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
523
+ :service-name="serviceName"
524
+ />
525
+ </p>
526
+ </VanCol>
527
+ </VanRow>
528
+ <VanRow v-if="allActions.length > 0" gutter="20" class="card_item_bottom">
529
+ <VanCol span="4">
530
+ <VanPopover
531
+ v-model:show="showPopover[index]"
532
+ placement="bottom-start"
533
+ :actions="otherActions.filter(action => evaluateCustomFunction(action.customFunction, item, index))"
534
+ @select="onSelectMenu(item, $event)"
535
+ >
536
+ <template #reference>
537
+ <div
538
+ v-show="otherActions && otherActions.length !== 0 && otherActions.some(action => evaluateCustomFunction(action.customFunction, item, index))"
539
+ class="more-button"
540
+ >
541
+ <span>⋯</span>
542
+ </div>
543
+ </template>
544
+ </VanPopover>
545
+ </VanCol>
546
+ <VanCol span="20">
547
+ <VanRow justify="end">
548
+ <VanSpace>
549
+ <VanButton
550
+ v-for="button in reversedMainActions"
551
+ v-show="evaluateCustomFunction(button.customFunction, item, index)"
552
+ :key="button.func"
553
+ type="primary"
554
+ size="small"
555
+ class="action-btn"
556
+ @click="onSelectMenu(item, button)"
557
+ >
558
+ {{ button.text }}
559
+ </VanButton>
560
+ </VanSpace>
561
+ </VanRow>
562
+ </VanCol>
563
+ </VanRow>
564
+ </div>
565
+ </VanList>
566
+ </template>
567
+ <template v-else>
568
+ <LoadError />
569
+ </template>
570
+ </VanPullRefresh>
571
+ <VanBackTop />
572
+ </div>
573
+ </div>
574
+ </template>
575
+
576
+ <style scoped lang="less">
577
+ #XCellList {
578
+ height: calc(94vh - var(--van-nav-bar-height) - 5px);
579
+ --van-search-padding:5px;
580
+ --van-dropdown-menu-title-padding: 5px;
581
+ .main {
582
+ overflow-y: auto;
583
+ height: 100%;
584
+ background-color: var(--van-background);
585
+ padding: var(--van-padding-base) var(--van-padding-sm);
586
+
587
+ p {
588
+ white-space: nowrap;
589
+ overflow: hidden;
590
+ text-overflow: ellipsis;
591
+ margin: 0;
592
+ }
593
+
594
+ .card_item_main {
595
+ background-color: var(--van-background-2);
596
+ border-radius: var(--van-radius-lg);
597
+ margin: 0 0 var(--van-padding-xs) 0;
598
+ padding: var(--van-padding-sm);
599
+
600
+ .card_item_header {
601
+ margin-bottom: var(--van-padding-base);
602
+
603
+ .title-row {
604
+ display: flex;
605
+ align-items: center;
606
+ margin-bottom: 8px;
607
+ width: 100%;
608
+
609
+ .main-title {
610
+ display: inline-flex;
611
+ align-items: center;
612
+ .card_item_title {
613
+ font-size: var(--van-font-size-lg);
614
+ margin: 0;
615
+ }
616
+ }
617
+
618
+ .sub-title {
619
+ display: inline-flex;
620
+ align-items: center;
621
+ margin-left: 4px;
622
+ .card_item_subtitle {
623
+ font-size: var(--van-font-size-xs);
624
+ color: var(--van-text-color-2);
625
+ margin: 0;
626
+ }
627
+ }
628
+
629
+ .action-buttons {
630
+ display: flex;
631
+ align-items: center;
632
+ margin-left: auto;
633
+ .action-button {
634
+ margin-left: 4px;
635
+ width: 36px;
636
+ height: 36px;
637
+ padding: 0;
638
+ border: none;
639
+ color: var(--van-primary-color);
640
+ background-color: rgba(25, 137, 250, 0.3);
641
+ border-radius: 4px;
642
+ font-size: 20px;
643
+ display: flex;
644
+ align-items: center;
645
+ justify-content: center;
646
+ &:active {
647
+ opacity: 0.7;
648
+ background-color: rgba(25, 137, 250, 0.5);
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
654
+
655
+ .tag-row {
656
+ margin-bottom: var(--van-padding-base);
657
+ }
658
+
659
+ .tag-container {
660
+ width: 100%;
661
+ .tag-wrapper {
662
+ display: flex;
663
+ flex-wrap: wrap;
664
+ padding: 0 !important;
665
+ align-items: center;
666
+ width: 100%;
667
+ margin: 0 -8px;
668
+
669
+ .tag-col {
670
+ display: flex;
671
+ align-items: center;
672
+ padding: 0 8px;
673
+ min-height: 32px;
674
+ }
675
+
676
+ .tag-item {
677
+ width: auto;
678
+ text-align: left;
679
+ font-size: var(--van-font-size-xs);
680
+ display: flex;
681
+ align-items: center;
682
+ margin: 4px 0;
683
+ justify-content: center;
684
+ :deep(.van-tag) {
685
+ width: fit-content;
686
+ display: inline-flex;
687
+ align-items: center;
688
+ }
689
+ .tag-content {
690
+ display: flex;
691
+ align-items: center;
692
+ white-space: nowrap;
693
+ }
694
+ .tag-icon {
695
+ margin-right: 4px;
696
+ font-size: 14px;
697
+ display: inline-flex;
698
+ align-items: center;
699
+ }
700
+ .tag-title {
701
+ font-weight: normal;
702
+ }
703
+ }
704
+ }
705
+ }
706
+
707
+ .card_item_details {
708
+ margin-bottom: var(--van-padding-base);
709
+ font-size: var(--van-font-size-sm);
710
+ color: #666;
711
+
712
+ .van-col {
713
+ margin-bottom: 2px;
714
+ }
715
+ }
716
+
717
+ .card_item_footer {
718
+ font-size: var(--van-font-size-sm);
719
+ color: var(--van-text-color-2);
720
+ margin-bottom: 15px;
721
+
722
+ .van-col:last-child {
723
+ text-align: right;
724
+ }
725
+
726
+ .van-col:first-child {
727
+ text-align: left;
728
+ }
729
+ }
730
+ }
731
+ }
732
+
733
+ .filter-condition {
734
+ display: flex;
735
+ align-items: center;
736
+ padding: 8px 12px;
737
+ background-color: #fff;
738
+ :deep(.van-search) {
739
+ width: 100%;
740
+ padding: var(--van-search-padding);
741
+ background-color: transparent;
742
+ }
743
+ :deep(.van-search__content) {
744
+ border-radius: 5px;
745
+ background-color: #f5f5f5;
746
+ padding: 4px 8px;
747
+ }
748
+ :deep(.van-field__left-icon) {
749
+ color: #999;
750
+ }
751
+ :deep(.van-cell) {
752
+ background-color: transparent;
753
+ }
754
+ :deep(.van-field__control::placeholder) {
755
+ color: #999;
756
+ font-size: 14px;
757
+ }
758
+ .van-col {
759
+ &:first-child {
760
+ flex: 1;
761
+ min-width: 0;
762
+ }
763
+ &:last-child {
764
+ flex-shrink: 0;
765
+ }
766
+ }
767
+ .search-icon {
768
+ display: flex;
769
+ align-items: center;
770
+ justify-content: flex-end;
771
+ height: 100%;
772
+ padding: 0;
773
+ }
774
+ }
775
+
776
+ .custom-button {
777
+ border: none !important;
778
+ box-shadow: none !important;
779
+ }
780
+
781
+ .more-button {
782
+ width: 24px;
783
+ height: 24px;
784
+ display: flex;
785
+ align-items: center;
786
+ justify-content: center;
787
+ color: #666;
788
+ cursor: pointer;
789
+ font-size: 20px;
790
+ background-color: #f7f8fa;
791
+ border-radius: 4px;
792
+ span {
793
+ line-height: 1;
794
+ margin-top: -2px;
795
+ }
796
+ &:active {
797
+ opacity: 0.7;
798
+ background-color: #ebedf0;
799
+ }
800
+ }
801
+ .action-btn {
802
+ min-width: 80px;
803
+ border-radius: 20px;
804
+ font-size: 14px;
805
+ }
806
+ }
807
+ </style>