cd-vue-filter 2.2.5 → 2.2.7

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,1002 +0,0 @@
1
- <template>
2
- <t-dialog header="方案设置" placement="center" :visible="visible" @update:visible="$emit('update:visible', $event)"
3
- :width="width" :z-index="999" :footer="true" @close="closeDialog" id="leader-line-clip-container">
4
- <template #body>
5
- <div class="filter-dialog-content">
6
- <div class="leader-line-container">
7
- <div class="filter-scroll-wrapper" id="filter-scroll-wrapper">
8
- <!-- 中间筛选内容 -->
9
- <div class="filter-dialog-middle" id="filter-dialog-middle">
10
- <!-- Tabs 切换 -->
11
- <t-tabs v-model="activeTab" size="medium">
12
- <t-tab-panel value="filter" label="筛选">
13
- <div class="filter-tab-content">
14
- <!-- 左侧顶层连接器 -->
15
- <div class="filter-dialog-left" v-if="innerFilterCards.length > 1">
16
- <div class="connectors-container">
17
- <t-radio-group :size="size" class="connector-selector" v-model="innerTopOp">
18
- <t-radio-button value="and">且</t-radio-button>
19
- <t-radio-button value="or">或</t-radio-button>
20
- </t-radio-group>
21
- </div>
22
- </div>
23
- <div class="filter-cards">
24
- <!-- 筛选卡片 -->
25
- <div v-for="(card, cardIndex) in innerFilterCards" :key="card.id" class="filter-card-item">
26
- <div class="filter-card-header">
27
- <div class="filter-card-header-left">
28
- <span class="filter-card-title">筛选卡片 {{ cardIndex + 1 }}</span>
29
- <t-radio-group :size="size" v-model="card.connector" class="connector-selector">
30
- <t-radio-button value="and">且</t-radio-button>
31
- <t-radio-button value="or">或</t-radio-button>
32
- </t-radio-group>
33
- </div>
34
- <t-button theme="default" variant="text" :size="size" @click="removeFilterCard(cardIndex)"
35
- :disabled="innerFilterCards.length === 1">
36
- <template #icon>
37
- <t-icon name="close" />
38
- </template>
39
- </t-button>
40
- </div>
41
- <div class="filter-card-content">
42
- <div v-for="(condition, condIndex) in card.conditions" :key="condIndex"
43
- class="filter-combination">
44
- <FilterComponent :field-options="fieldOptions"
45
- :filter-condition="card.conditions[condIndex]" :select-options="selectOptions"
46
- :size="size" />
47
- <t-button theme="default" variant="text" :size="size"
48
- @click="removeConditionFromCard(cardIndex, condIndex)"
49
- :disabled="card.conditions.length === 1">
50
- <template #icon>
51
- <t-icon name="minus" />
52
- </template>
53
- </t-button>
54
- </div>
55
- <div class="filter-card-actions">
56
- <t-button theme="default" :size="size" @click="addConditionToCard(cardIndex)">
57
- <template #icon>
58
- <t-icon name="add" />
59
- </template>
60
- 添加条件
61
- </t-button>
62
- </div>
63
- </div>
64
- </div>
65
- <!-- 增加筛选卡片按钮 -->
66
- <div class="add-filter-card" @click="addFilterCard">
67
- <div class="add-card-content">
68
- <t-icon name="add" />
69
- <span>增加筛选卡片</span>
70
- </div>
71
- </div>
72
- </div>
73
- </div>
74
- </t-tab-panel>
75
- <t-tab-panel value="column" label="列配置">
76
- <div class="column-settings-panel">
77
- <div class="column-settings-header">
78
- <t-checkbox v-model="selectAllColumns" @change="handleSelectAllChange">全选</t-checkbox>
79
- </div>
80
- <!-- 标题行 -->
81
- <div class="column-title-row">
82
- <div class="column-title-name">名称</div>
83
- <div class="column-title-show">显示</div>
84
- <div class="column-title-width">宽度</div>
85
- <div class="column-title-sort">排序</div>
86
- </div>
87
- <div class="column-list">
88
- <div v-for="(field, index) in sortedFieldOptions" :key="field.value" class="column-item-row"
89
- draggable="true" @dragstart="handleDragStart(index, $event)"
90
- @dragover="handleDragOver(index, $event)" @dragend="handleDragEnd"
91
- @drop="handleDrop(index, $event)" :class="{ 'dragging': draggedIndex === index }">
92
- <div class="column-name">
93
- <t-icon name="move" style="cursor: move; color: #999; margin-right: 8px;" />
94
- {{ field.label }}
95
- </div>
96
- <div class="column-show">
97
- <t-checkbox :checked="selectedColumns.includes(field.value)"
98
- @change="(checked) => handleSingleColumnChange(field.value, checked)" />
99
- </div>
100
- <div class="column-width">
101
- <t-input-number v-model="columnWidths[field.value]" :min="50" :max="500" :step="10"
102
- size="small" placeholder="" style="width: 130px;" suffix="px" />
103
- </div>
104
- <div class="column-sort-buttons">
105
- <t-button theme="default" variant="text" size="small" @click="moveColumnUp(index)"
106
- :disabled="index === 0">
107
- <template #icon>
108
- <t-icon name="chevron-up" />
109
- </template>
110
- </t-button>
111
- <t-button theme="default" variant="text" size="small" @click="moveColumnDown(index)"
112
- :disabled="index === sortedFieldOptions.length - 1">
113
- <template #icon>
114
- <t-icon name="chevron-down" />
115
- </template>
116
- </t-button>
117
- </div>
118
- </div>
119
- </div>
120
- </div>
121
- </t-tab-panel>
122
- </t-tabs>
123
- </div>
124
- <!-- 右侧方案列表 -->
125
- <div class="filter-dialog-right">
126
- <div class="filter-plan-title">我的方案</div>
127
- <div class="filter-plan-list">
128
- <div v-for="plan in planFilterOptions" :key="plan.value"
129
- :class="['filter-plan-item', { active: selectedPlanFilter?.value === plan.value }]"
130
- @click="handlePlanClick(plan)">
131
- <t-icon name="file-text" class="plan-icon" />
132
- <span class="plan-name">{{ plan.content }}</span>
133
- <div class="plan-actions" @click.stop>
134
- <t-tooltip content="编辑" placement="top">
135
- <t-button theme="default" variant="text" size="small" @click.stop="handleEditPlan(plan)"
136
- :disabled="!plan.value || plan.value === 'empty'">
137
- <template #icon>
138
- <t-icon name="edit"
139
- :style="{ color: '#0052d9' }" />
140
- </template>
141
- </t-button>
142
- </t-tooltip>
143
- <t-tooltip content="设置为默认方案" placement="top">
144
- <t-button theme="default" variant="text" size="small" @click="handleSetDefaultPlan(plan)"
145
- :disabled="!plan.value || plan.value === 'empty'">
146
- <template #icon>
147
- <t-icon :name="plan.isDefault ? 'check-circle-filled' : 'check-circle'"
148
- :style="{ color: plan.isDefault ? '#0052d9' : '#bbb' }" />
149
- </template>
150
- </t-button>
151
- </t-tooltip>
152
- <t-tooltip content="复制方案" placement="top">
153
- <t-button theme="default" variant="text" size="small" @click="handleCopyPlan(plan)"
154
- :disabled="!plan.value || plan.value === 'empty'">
155
- <template #icon>
156
- <t-icon name="file-copy" />
157
- </template>
158
- </t-button>
159
- </t-tooltip>
160
- <t-tooltip content="删除方案" placement="top">
161
- <t-button theme="default" variant="text" size="small" @click="handleDeletePlan(plan)"
162
- :disabled="!plan.value || plan.value === 'empty'">
163
- <template #icon>
164
- <t-icon name="delete" />
165
- </template>
166
- </t-button>
167
- </t-tooltip>
168
- </div>
169
- </div>
170
- </div>
171
- </div>
172
- </div>
173
- </div>
174
- </div>
175
- </template>
176
- <template #footer>
177
- <div class="filter-dialog-footer-container">
178
- <div class="filter-dialog-footer-left">
179
- <t-button theme="default" :size="size" @click="handleSavePlan">
180
- <template #icon>
181
- <t-icon name="save" />
182
- </template>
183
- 保存方案
184
- </t-button>
185
- </div>
186
- <div class="filter-dialog-footer-right">
187
- <t-button theme="default" @click="handleReset">重置</t-button>
188
- <t-button theme="primary" @click="handleConfirm">确定</t-button>
189
- </div>
190
- </div>
191
- </template>
192
- </t-dialog>
193
- <!-- 保存方案对话框 -->
194
- <SavePlanDialog
195
- v-model:visible="savePlanDialogVisible"
196
- :is-editing="isEditingPlan"
197
- :plan-name="editingPlanName"
198
- :original-share-mode="editingPlanShareMode"
199
- :original-share-type="editingPlanShareType"
200
- :original-selected-users="editingPlanSelectedUsers"
201
- :tabs="personTabs"
202
- :organizations="personOrganizations"
203
- :dept-members-data-prop="deptMembersData"
204
- @confirm="handleSavePlanConfirm"
205
- @load-users="handleLoadUsers"
206
- @search="handleSearch"
207
- @dept-click="handleDeptClick"
208
- />
209
- </template>
210
- <script setup>
211
- import { ref, watch } from 'vue'
212
- import { MessagePlugin } from 'tdesign-vue-next'
213
- import FilterComponent from './FilterComponent.vue'
214
- import SavePlanDialog from './SavePlanDialog.vue'
215
- // Props
216
- const props = defineProps({
217
- visible: {
218
- type: Boolean,
219
- default: false
220
- },
221
- fieldOptions: {
222
- type: Array,
223
- required: true
224
- },
225
- selectOptions: {
226
- type: Array,
227
- default: () => []
228
- },
229
- planFilterOptions: {
230
- type: Array,
231
- default: () => []
232
- },
233
- filterCards: {
234
- type: Array,
235
- default: () => [
236
- {
237
- id: 1,
238
- connector: 'and',
239
- conditions: [
240
- { field: '', operator: 'eq', value: '' },
241
- { field: '', operator: 'eq', value: '' }
242
- ]
243
- }
244
- ]
245
- },
246
- topOp: {
247
- type: String,
248
- default: 'and'
249
- },
250
- size: {
251
- type: String,
252
- default: 'small'
253
- },
254
- width: {
255
- type: String,
256
- default: '1000px'
257
- },
258
- personTabs: {
259
- type: Array,
260
- default: () => []
261
- },
262
- personOrganizations: {
263
- type: Array,
264
- default: () => []
265
- },
266
- visibleColumns: {
267
- type: Array,
268
- default: () => []
269
- }
270
- })
271
- // Emits
272
- const emit = defineEmits([
273
- 'update:visible',
274
- 'confirm',
275
- 'reset',
276
- 'plan-click',
277
- 'save-plan',
278
- 'delete-plan',
279
- 'copy-plan',
280
- 'set-default-plan',
281
- 'update-plan',
282
- 'load-users',
283
- 'search',
284
- 'dept-click',
285
- 'column-change'
286
- ])
287
- // 内部状态
288
- const innerFilterCards = ref(JSON.parse(JSON.stringify(props.filterCards)))
289
- const innerTopOp = ref(props.topOp)
290
- const selectedPlanFilter = ref(null)
291
- const savePlanDialogVisible = ref(false)
292
- const deptMembersData = ref([])
293
- const activeTab = ref('filter') // 默认显示筛选tab
294
- const isEditingPlan = ref(false) // 是否正在编辑方案
295
- const editingPlanName = ref('') // 正在编辑的方案名称
296
- const editingPlanShareMode = ref('none') // 正在编辑的方案共享模式
297
- const editingPlanShareType = ref('specific') // 正在编辑的方案共享范围
298
- const editingPlanSelectedUsers = ref([]) // 正在编辑的方案共享用户
299
- const editingPlan = ref(null) // 正在编辑的方案对象
300
- let nextCardId = 100
301
- // 监听 props 变化
302
- watch(() => props.filterCards, (newVal) => {
303
- innerFilterCards.value = JSON.parse(JSON.stringify(newVal))
304
- }, { deep: true })
305
- watch(() => props.topOp, (newVal) => {
306
- innerTopOp.value = newVal
307
- })
308
- // 添加筛选卡片
309
- const addFilterCard = () => {
310
- innerFilterCards.value.push({
311
- id: nextCardId++,
312
- connector: 'and',
313
- conditions: [{ field: '', operator: 'eq', value: '' }]
314
- })
315
- }
316
- // 删除筛选卡片
317
- const removeFilterCard = (cardIndex) => {
318
- if (innerFilterCards.value.length > 1) {
319
- innerFilterCards.value.splice(cardIndex, 1)
320
- } else {
321
- MessagePlugin.warning('至少需要保留一个筛选卡片')
322
- }
323
- }
324
- // 向指定卡片添加筛选条件
325
- const addConditionToCard = (cardIndex) => {
326
- if (cardIndex >= 0 && cardIndex < innerFilterCards.value.length) {
327
- innerFilterCards.value[cardIndex].conditions.push({
328
- field: '',
329
- operator: 'eq',
330
- value: ''
331
- })
332
- }
333
- }
334
- // 从指定卡片中删除筛选条件
335
- const removeConditionFromCard = (cardIndex, condIndex) => {
336
- if (cardIndex >= 0 && cardIndex < innerFilterCards.value.length) {
337
- const card = innerFilterCards.value[cardIndex]
338
- if (card.conditions.length > 1 && condIndex >= 0 && condIndex < card.conditions.length) {
339
- card.conditions.splice(condIndex, 1)
340
- } else if (card.conditions.length <= 1) {
341
- MessagePlugin.warning('每个卡片至少需要一个筛选条件')
342
- }
343
- }
344
- }
345
- // 清理筛选卡片,保证字段有效
346
- const cleanFilterCards = (cards) => {
347
- return cards
348
- .map((card) => {
349
- const validConditions = card.conditions
350
- .filter((condition) => condition.field && condition.field.trim() !== '')
351
- .map((condition) => {
352
- let cleanValue = ''
353
- if (condition.value !== null && condition.value !== undefined) {
354
- if (Array.isArray(condition.value)) {
355
- cleanValue = condition.value.join('/')
356
- } else {
357
- cleanValue = String(condition.value)
358
- }
359
- }
360
- return {
361
- field: condition.field.trim(),
362
- operator: condition.operator || 'eq',
363
- value: cleanValue
364
- }
365
- })
366
- return {
367
- ...card,
368
- connector: card.connector || 'and',
369
- conditions: validConditions.length > 0 ? validConditions : [{ field: '', operator: 'eq', value: '' }]
370
- }
371
- })
372
- .filter((card) => card.conditions.length > 0 && card.conditions[0].field !== '')
373
- }
374
- // 方案点击
375
- const handlePlanClick = (plan) => {
376
- if (plan.value === 'empty') return
377
- selectedPlanFilter.value = plan
378
- emit('plan-click', plan)
379
- // 根据方案更新本地卡片与顶层关联
380
- if (plan.precepts) {
381
- innerFilterCards.value = JSON.parse(JSON.stringify(plan.precepts))
382
- }
383
- if (plan.sqlConnectType) {
384
- innerTopOp.value = plan.sqlConnectType
385
- }
386
- // 恢复列配置
387
- if (plan.columns && plan.columns.length > 0) {
388
- // 清空之前的宽度设置
389
- columnWidths.value = {}
390
- // 恢复显示状态和宽度
391
- selectedColumns.value = plan.columns.filter(col => col.show !== false).map(col => col.value)
392
- plan.columns.forEach(col => {
393
- if (col.width) {
394
- columnWidths.value[col.value] = parseInt(col.width)
395
- }
396
- })
397
- // 根据方案的列顺序排序
398
- const columnOrder = plan.columns.map(col => col.value)
399
- const visibleFields = columnOrder.map(value =>
400
- props.fieldOptions.find(f => f.value === value)
401
- ).filter(Boolean)
402
- const remainingFields = props.fieldOptions.filter(f =>
403
- !columnOrder.includes(f.value)
404
- )
405
- sortedFieldOptions.value = [...visibleFields, ...remainingFields]
406
- selectAllColumns.value = selectedColumns.value.length === props.fieldOptions.length
407
- // 通知父组件列配置变化(只传递显示的列)
408
- const visibleColumns = plan.columns.filter(col => col.show !== false)
409
- emit('column-change', visibleColumns)
410
- }
411
- }
412
- // 保存方案
413
- const handleSavePlan = () => {
414
- isEditingPlan.value = false
415
- editingPlan.value = null
416
- editingPlanName.value = ''
417
- editingPlanShareMode.value = 'none'
418
- editingPlanShareType.value = 'specific'
419
- editingPlanSelectedUsers.value = []
420
- savePlanDialogVisible.value = true
421
- }
422
- // 保存方案确认
423
- const handleSavePlanConfirm = (planData) => {
424
- // 构建列配置信息(包含所有列,保存显示状态和宽度)
425
- const columnsConfig = sortedFieldOptions.value.map(field => {
426
- const width = columnWidths.value[field.value]
427
- return {
428
- value: field.value,
429
- show: selectedColumns.value.includes(field.value), // 是否显示
430
- width: width ? `${width}px` : '120px'
431
- }
432
- })
433
-
434
- if (isEditingPlan.value && editingPlan.value) {
435
- // 编辑模式:触发更新事件
436
- emit('update-plan', {
437
- ...planData,
438
- plan: editingPlan.value,
439
- filterCards: innerFilterCards.value,
440
- topOp: innerTopOp.value,
441
- columns: columnsConfig
442
- })
443
- } else {
444
- // 新建模式:触发保存事件
445
- emit('save-plan', {
446
- ...planData,
447
- filterCards: innerFilterCards.value,
448
- topOp: innerTopOp.value,
449
- columns: columnsConfig
450
- })
451
- }
452
-
453
- // 重置编辑状态
454
- isEditingPlan.value = false
455
- editingPlan.value = null
456
- }
457
- // 编辑方案
458
- const handleEditPlan = (plan) => {
459
- isEditingPlan.value = true
460
- editingPlan.value = plan
461
- editingPlanName.value = plan.content || ''
462
- editingPlanShareMode.value = plan.shareMode || 'none'
463
- editingPlanShareType.value = plan.shareType || 'specific'
464
- editingPlanSelectedUsers.value = plan.selectedUsers || []
465
- savePlanDialogVisible.value = true
466
- }
467
- // 复制方案
468
- const handleCopyPlan = (plan) => {
469
- emit('copy-plan', plan)
470
- }
471
- // 删除方案
472
- const handleDeletePlan = (plan) => {
473
- emit('delete-plan', plan)
474
- }
475
- // 设置默认方案
476
- const handleSetDefaultPlan = (plan) => {
477
- emit('set-default-plan', plan)
478
- }
479
- // 重置
480
- const handleReset = () => {
481
- selectedPlanFilter.value = null
482
- innerFilterCards.value = [
483
- {
484
- id: 1,
485
- connector: 'and',
486
- conditions: [
487
- { field: '', operator: 'eq', value: '' },
488
- { field: '', operator: 'eq', value: '' }
489
- ]
490
- }
491
- ]
492
- innerTopOp.value = 'and'
493
- emit('reset')
494
- }
495
- // 确定
496
- const handleConfirm = () => {
497
- if (activeTab.value === 'filter') {
498
- // 筛选tab:保存筛选条件
499
- const cleaned = cleanFilterCards(innerFilterCards.value)
500
- emit('confirm', {
501
- filterCards: cleaned,
502
- type1: innerTopOp.value
503
- })
504
- } else if (activeTab.value === 'column') {
505
- // 列配置tab:保存列设置
506
- confirmColumnSettings()
507
- }
508
- }
509
- // 关闭对话框
510
- const closeDialog = () => {
511
- emit('update:visible', false)
512
- }
513
- // 加载用户
514
- const handleLoadUsers = (payload) => {
515
- emit('load-users', payload)
516
- }
517
- // 搜索
518
- const handleSearch = (payload) => {
519
- emit('search', payload)
520
- }
521
- // 部门点击
522
- const handleDeptClick = (dept) => {
523
- // 创建一个回调函数来接收部门成员数据
524
- const callback = (members) => {
525
- deptMembersData.value = members
526
- }
527
- emit('dept-click', { dept, callback })
528
- }
529
- // ========== 列设置相关 ==========
530
- const selectedColumns = ref([])
531
- const selectAllColumns = ref(false)
532
- const columnWidths = ref({})
533
- const sortedFieldOptions = ref([])
534
- const draggedIndex = ref(null)
535
- const dragOverIndex = ref(null)
536
- // 初始化选中的列和宽度
537
- watch(() => props.visibleColumns, (newVal) => {
538
- // 清空之前的宽度设置
539
- columnWidths.value = {}
540
- if (newVal && newVal.length > 0) {
541
- // 如果visibleColumns包含对象(带宽度信息)
542
- if (typeof newVal[0] === 'object') {
543
- // 恢复显示状态和宽度
544
- selectedColumns.value = newVal.filter(col => col.show !== false).map(col => col.value)
545
- newVal.forEach(col => {
546
- if (col.width) {
547
- columnWidths.value[col.value] = parseInt(col.width)
548
- }
549
- })
550
- // 根据visibleColumns的顺序排序fieldOptions
551
- const visibleOrder = newVal.map(col => col.value)
552
- const visibleFields = visibleOrder.map(value =>
553
- props.fieldOptions.find(f => f.value === value)
554
- ).filter(Boolean)
555
- const remainingFields = props.fieldOptions.filter(f =>
556
- !visibleOrder.includes(f.value)
557
- )
558
- sortedFieldOptions.value = [...visibleFields, ...remainingFields]
559
- } else {
560
- // 如果只是简单的值数组
561
- selectedColumns.value = [...newVal]
562
- const visibleFields = newVal.map(value =>
563
- props.fieldOptions.find(f => f.value === value)
564
- ).filter(Boolean)
565
- const remainingFields = props.fieldOptions.filter(f =>
566
- !newVal.includes(f.value)
567
- )
568
- sortedFieldOptions.value = [...visibleFields, ...remainingFields]
569
- }
570
- selectAllColumns.value = selectedColumns.value.length === props.fieldOptions.length
571
- } else if (props.fieldOptions.length > 0) {
572
- selectedColumns.value = props.fieldOptions.map(field => field.value)
573
- sortedFieldOptions.value = [...props.fieldOptions]
574
- selectAllColumns.value = true
575
- }
576
- }, { immediate: true })
577
- // 同步监听fieldOptions的变化
578
- watch(() => props.fieldOptions, (newVal) => {
579
- if (!sortedFieldOptions.value.length) {
580
- sortedFieldOptions.value = [...newVal]
581
- }
582
- }, { immediate: true })
583
- // 单个列选择变化
584
- const handleSingleColumnChange = (value, checked) => {
585
- if (checked) {
586
- if (!selectedColumns.value.includes(value)) {
587
- selectedColumns.value.push(value)
588
- }
589
- } else {
590
- const index = selectedColumns.value.indexOf(value)
591
- if (index > -1) {
592
- selectedColumns.value.splice(index, 1)
593
- }
594
- }
595
- selectAllColumns.value = selectedColumns.value.length === props.fieldOptions.length
596
- }
597
- // 全选/取消全选
598
- const handleSelectAllChange = (checked) => {
599
- if (checked) {
600
- selectedColumns.value = sortedFieldOptions.value.map(field => field.value)
601
- } else {
602
- selectedColumns.value = []
603
- }
604
- }
605
- // 向上移动列
606
- const moveColumnUp = (index) => {
607
- if (index > 0) {
608
- const temp = sortedFieldOptions.value[index]
609
- sortedFieldOptions.value[index] = sortedFieldOptions.value[index - 1]
610
- sortedFieldOptions.value[index - 1] = temp
611
- // 触发响应式更新
612
- sortedFieldOptions.value = [...sortedFieldOptions.value]
613
- }
614
- }
615
- // 向下移动列
616
- const moveColumnDown = (index) => {
617
- if (index < sortedFieldOptions.value.length - 1) {
618
- const temp = sortedFieldOptions.value[index]
619
- sortedFieldOptions.value[index] = sortedFieldOptions.value[index + 1]
620
- sortedFieldOptions.value[index + 1] = temp
621
- // 触发响应式更新
622
- sortedFieldOptions.value = [...sortedFieldOptions.value]
623
- }
624
- }
625
- // 拖拽开始
626
- const handleDragStart = (index, event) => {
627
- draggedIndex.value = index
628
- event.dataTransfer.effectAllowed = 'move'
629
- event.dataTransfer.setData('text/html', event.target.innerHTML)
630
- }
631
- // 拖拽经过
632
- const handleDragOver = (index, event) => {
633
- event.preventDefault()
634
- event.dataTransfer.dropEffect = 'move'
635
- dragOverIndex.value = index
636
- }
637
- // 拖拽结束
638
- const handleDragEnd = () => {
639
- draggedIndex.value = null
640
- dragOverIndex.value = null
641
- }
642
- // 放下
643
- const handleDrop = (dropIndex, event) => {
644
- event.preventDefault()
645
- const dragIndex = draggedIndex.value
646
- if (dragIndex === null || dragIndex === dropIndex) {
647
- return
648
- }
649
- const items = [...sortedFieldOptions.value]
650
- const draggedItem = items[dragIndex]
651
- // 移除拖拽项
652
- items.splice(dragIndex, 1)
653
- // 在目标位置插入
654
- items.splice(dropIndex, 0, draggedItem)
655
- sortedFieldOptions.value = items
656
- draggedIndex.value = null
657
- dragOverIndex.value = null
658
- }
659
- // 确认列设置
660
- const confirmColumnSettings = () => {
661
- // 按照sortedFieldOptions的顺序构建列配置(只传递显示的列给父组件)
662
- const columnsConfig = sortedFieldOptions.value
663
- .filter(field => selectedColumns.value.includes(field.value))
664
- .map(field => {
665
- const width = columnWidths.value[field.value]
666
- return {
667
- value: field.value,
668
- show: true, // 已通过filter筛选,都是显示的
669
- width: width ? `${width}px` : '120px' // 没有设置宽度时使用默认值120px
670
- }
671
- })
672
- emit('column-change', columnsConfig)
673
- }
674
- </script>
675
- <style scoped lang="scss">
676
- .filter-dialog-content {
677
- min-height: 400px;
678
- position: relative;
679
- }
680
- .leader-line-container {
681
- position: relative;
682
- flex: 1;
683
- overflow: hidden;
684
- }
685
- .filter-scroll-wrapper {
686
- display: flex;
687
- flex: 1;
688
- overflow: auto;
689
- height: auto;
690
- background-color: #f2f4f7;
691
- position: relative;
692
- }
693
- .filter-tab-content .filter-dialog-left {
694
- width: 150px;
695
- background-color: #f2f4f7;
696
- display: flex;
697
- flex-direction: column;
698
- align-items: center;
699
- position: relative;
700
- justify-content: center;
701
- }
702
- .filter-tab-content .filter-dialog-left::before {
703
- content: "";
704
- position: absolute;
705
- width: 100px;
706
- left: 78px;
707
- border: 1px solid #6aa2ff;
708
- border-right-color: transparent;
709
- top: 50px;
710
- bottom: 50px;
711
- z-index: 1;
712
- }
713
- .connectors-container {
714
- background: #fff;
715
- position: relative;
716
- z-index: 9;
717
- display: flex;
718
- align-items: center;
719
- }
720
- .connector-selector {
721
- min-width: 60px;
722
- }
723
- .filter-dialog-middle {
724
- flex: 2;
725
- border-right: 1px solid #e7e7e7;
726
- z-index: 99;
727
- display: flex;
728
- flex-direction: column;
729
- }
730
- .filter-dialog-middle :deep(.t-tabs) {
731
- display: flex;
732
- flex-direction: column;
733
- height: 100%;
734
- }
735
- .filter-dialog-middle :deep(.t-tabs__content) {
736
- flex: 1;
737
- overflow: auto;
738
- }
739
- .filter-tab-content {
740
- display: flex;
741
- flex: 1;
742
- overflow: auto;
743
- height: 100%;
744
- background-color: #f2f4f7;
745
- position: relative;
746
- }
747
- .filter-cards {
748
- flex: 1;
749
- background-color: #f2f4f7;
750
- padding: 15px 10px;
751
- margin-bottom: 10px;
752
- padding-left: 20px;
753
- }
754
- .filter-card-item {
755
- background: #fff;
756
- border-radius: 6px;
757
- padding: 10px;
758
- margin-bottom: 10px;
759
- box-shadow: 0 2px 4px rgba(0, 0, 0, .05);
760
- }
761
- .filter-card-header {
762
- display: flex;
763
- justify-content: space-between;
764
- align-items: center;
765
- }
766
- .filter-card-header-left {
767
- display: flex;
768
- align-items: center;
769
- margin-bottom: 10px;
770
- }
771
- .filter-card-title {
772
- font-weight: 500;
773
- color: var(--td-text-color-primary);
774
- margin-right: 20px;
775
- }
776
- .filter-card-content .filter-combination {
777
- display: flex;
778
- align-items: center;
779
- gap: 8px;
780
- margin-bottom: 8px;
781
- }
782
- .filter-card-actions {
783
- margin-top: 12px;
784
- padding-top: 8px;
785
- border-top: 1px dashed var(--td-component-stroke);
786
- }
787
- .add-filter-card {
788
- border: 1px dashed var(--td-brand-color-light);
789
- border-radius: 6px;
790
- padding: 20px;
791
- display: flex;
792
- justify-content: center;
793
- align-items: center;
794
- cursor: pointer;
795
- background-color: #f9f9f9;
796
- box-shadow: 0 2px 4px rgba(0, 0, 0, .05);
797
- transition: all .3s ease;
798
- }
799
- .add-card-content {
800
- display: flex;
801
- flex-direction: column;
802
- align-items: center;
803
- gap: 8px;
804
- color: var(--td-brand-color);
805
- }
806
- .add-filter-card:hover {
807
- transform: translateY(-2px);
808
- }
809
- .filter-dialog-right {
810
- padding-left: 20px;
811
- background: #fff;
812
- padding-right: 10px;
813
- width: 300px;
814
- }
815
- .filter-plan-title {
816
- font-size: 14px;
817
- font-weight: 600;
818
- margin: 10px 10px;
819
- }
820
- .filter-plan-list {
821
- max-height: 300px;
822
- overflow-y: auto;
823
- margin-bottom: 16px;
824
- }
825
- .filter-plan-item {
826
- background: #fafafa;
827
- display: flex;
828
- align-items: center;
829
- padding: 2px 12px;
830
- border-radius: 4px;
831
- cursor: pointer;
832
- transition: background-color .2s ease;
833
- margin-bottom: 8px;
834
- }
835
- .filter-plan-item:hover {
836
- background-color: rgba(0, 82, 217, 0.05);
837
- }
838
- .filter-plan-item.active {
839
- background-color: rgba(0, 82, 217, 0.1);
840
- }
841
- .plan-icon {
842
- color: #0052d9;
843
- }
844
- .plan-name {
845
- flex: 1;
846
- font-size: 14px;
847
- }
848
- .plan-actions {
849
- display: flex;
850
- opacity: 0;
851
- transition: opacity 0.2s ease;
852
- }
853
- .filter-plan-item:hover .plan-actions {
854
- opacity: 1;
855
- }
856
- .plan-actions .t-icon[name="star-filled"] {
857
- color: #ff9800 !important;
858
- }
859
- .plan-actions .t-icon[name="star"] {
860
- color: #bbb !important;
861
- }
862
- .plan-actions .t-button:hover .t-icon[name="star"] {
863
- color: #ff9800 !important;
864
- }
865
- .plan-actions .t-icon[name="delete"] {
866
- color: #f44336 !important;
867
- }
868
- .plan-actions .t-button:hover .t-icon[name="delete"] {
869
- color: #d32f2f !important;
870
- }
871
- .filter-dialog-footer-container {
872
- display: flex;
873
- justify-content: space-between;
874
- width: 100%;
875
- }
876
- .filter-dialog-footer-left {
877
- display: flex;
878
- gap: 8px;
879
- }
880
- .filter-dialog-footer-right {
881
- display: flex;
882
- gap: 8px;
883
- }
884
- .filter-dialog-footer-right .t-button {
885
- margin-right: 8px;
886
- }
887
- /* 连接器选择器样式 - 为"且"和"或"添加背景区分 */
888
- .connector-selector {
889
- :deep(.t-radio-button.t-is-checked) {
890
- background-color: #e8f4ff;
891
- border-color: #0052d9;
892
- .t-radio-button__label {
893
- color: #0052d9;
894
- font-weight: 500;
895
- }
896
- }
897
- :deep(.t-radio-button:last-child.t-is-checked) {
898
- background-color: #fff3e8;
899
- border-color: #ff8800;
900
- .t-radio-button__label {
901
- color: #ff8800;
902
- font-weight: 500;
903
- }
904
- }
905
- }
906
- /* 列设置面板样式 */
907
- .column-settings-panel {
908
- padding: 20px 10px;
909
- background-color: #f2f4f7;
910
- height: 100%;
911
- overflow: auto;
912
- }
913
- .column-settings-header {
914
- padding: 8px 0 16px 0;
915
- }
916
- .column-title-row {
917
- display: flex;
918
- align-items: center;
919
- gap: 16px;
920
- padding: 8px 12px;
921
- font-weight: 600;
922
- color: #666;
923
- border-bottom: 1px solid #e7e7e7;
924
- margin-bottom: 8px;
925
- }
926
- .column-title-name {
927
- flex: 1;
928
- min-width: 120px;
929
- text-align: left;
930
- }
931
- .column-title-show {
932
- width: 60px;
933
- display: flex;
934
- justify-content: center;
935
- align-items: center;
936
- }
937
- .column-title-width {
938
- width: 150px;
939
- display: flex;
940
- justify-content: center;
941
- align-items: center;
942
- }
943
- .column-title-sort {
944
- width: 80px;
945
- display: flex;
946
- justify-content: center;
947
- align-items: center;
948
- }
949
- .column-list {
950
- max-height: 400px;
951
- overflow-y: auto;
952
- }
953
- .column-item-row {
954
- display: flex;
955
- align-items: center;
956
- gap: 16px;
957
- padding: 8px 12px;
958
- border-radius: 4px;
959
- transition: background-color 0.2s, opacity 0.2s;
960
- margin-bottom: 8px;
961
- cursor: move;
962
- border: 2px solid transparent;
963
- }
964
- .column-item-row:hover {
965
- background-color: var(--td-bg-color-container-hover, #f5f5f5);
966
- }
967
- .column-item-row.dragging {
968
- opacity: 0.5;
969
- border-color: #0052d9;
970
- background-color: #e8f4ff;
971
- }
972
- .column-name {
973
- flex: 1;
974
- min-width: 120px;
975
- display: flex;
976
- align-items: center;
977
- }
978
- .column-show {
979
- width: 60px;
980
- display: flex;
981
- justify-content: center;
982
- align-items: center;
983
- }
984
- .column-width {
985
- width: 150px;
986
- display: flex;
987
- justify-content: center;
988
- align-items: center;
989
- }
990
- .column-sort-buttons {
991
- width: 80px;
992
- display: flex;
993
- justify-content: center;
994
- align-items: center;
995
- gap: 4px;
996
- }
997
- .column-sort-buttons .t-button {
998
- padding: 0;
999
- width: 24px;
1000
- height: 24px;
1001
- }
1002
- </style>