cd-vue-filter 2.2.5 → 2.2.6

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,359 +0,0 @@
1
- <!-- 过滤器组件 -->
2
- <template>
3
- <div class="filter-component" >
4
- <t-select
5
- v-model="props.filterCondition.field"
6
- placeholder="请选择字段"
7
- :size="props.size"
8
- style="min-width: 80px; max-width: 110px;"
9
- @change="handleFieldChange"
10
- clearable
11
- filterable
12
- >
13
- <t-option
14
- v-for="option in props.fieldOptions"
15
- :key="option.key"
16
- :label="option.key"
17
- :value="option.value"
18
- />
19
- </t-select>
20
- <t-select
21
- v-model="props.filterCondition.operator"
22
- placeholder="请选择条件"
23
- :size="props.size"
24
- style="width: 80px;"
25
- :clearable="false"
26
- @change="handleOperatorChange"
27
- >
28
- <t-option
29
- v-for="option in operatorOptions"
30
- :key="option.key"
31
- :label="option.label"
32
- :value="option.value"
33
- />
34
- </t-select>
35
- <!-- 动态输入框 -->
36
- <template v-if="getFieldType(props.filterCondition.field) === 'number'">
37
- <t-input
38
- type="number"
39
- v-model="props.filterCondition.value"
40
- placeholder="请输入数字"
41
- :size="props.size"
42
- style="min-width: 80px; max-width: 120px;"
43
- @change="handleValueChange"
44
- clearable
45
- />
46
- </template>
47
- <template v-else-if="getFieldType(props.filterCondition.field) === 'money'">
48
- <t-input
49
- type="number"
50
- v-model="props.filterCondition.value"
51
- placeholder="请输入金额"
52
- :size="props.size"
53
- style="min-width: 80px; max-width: 120px;"
54
- @change="handleValueChange"
55
- clearable
56
- />
57
- </template>
58
- <template v-else-if="getFieldType(props.filterCondition.field) === 'date'">
59
- <t-date-picker
60
- v-if="!isPresetDateOperator(props.filterCondition.operator)"
61
- v-model="props.filterCondition.value"
62
- placeholder="请选择日期"
63
- :size="props.size"
64
- style="min-width: 80px; max-width: 120px;"
65
- @change="handleValueChange"
66
- clearable
67
- />
68
- <t-input
69
- v-else
70
- :value="operatorOptions.find(op => op.value === props.filterCondition.operator)?.label"
71
- disabled
72
- :size="props.size"
73
- style="min-width: 80px; max-width: 120px;"
74
- />
75
- </template>
76
- <template v-else-if="getFieldType(props.filterCondition.field) === 'time'">
77
- <t-date-picker
78
- v-model="props.filterCondition.value"
79
- placeholder="请选择日期时间"
80
- :size="props.size"
81
- style="min-width: 80px; max-width: 120px;"
82
- enable-time-picker
83
- format="YYYY-MM-DD HH:mm:ss"
84
- @change="handleValueChange"
85
- clearable
86
- />
87
- </template>
88
- <template v-else-if="getFieldType(props.filterCondition.field) === 'select'">
89
- <t-select
90
- v-model="props.filterCondition.value"
91
- :options="getSelectOptions(props.filterCondition.field)"
92
- placeholder="请选择"
93
- :size="props.size"
94
- style="min-width: 80px; max-width: 120px;"
95
- @change="handleValueChange"
96
- clearable
97
- />
98
- </template>
99
- <template v-else-if="getFieldType(props.filterCondition.field) === 'selectProvince'">
100
- <t-cascader
101
- v-model="props.filterCondition.value"
102
- :options="cascaderOptions"
103
- placeholder="请选择地区"
104
- :size="props.size"
105
- style="min-width: 80px; max-width: 120px;"
106
- clearable
107
- :loading="false"
108
- :checkStrictly="true"
109
- @change="handleValueChange"
110
- />
111
- </template>
112
- <template v-else>
113
- <t-input
114
- v-model="props.filterCondition.value"
115
- placeholder="请输入关键字"
116
- :size="props.size"
117
- style="min-width: 80px; max-width: 120px;"
118
- @change="handleValueChange"
119
- clearable
120
- />
121
- </template>
122
- </div>
123
- </template>
124
- <script setup lang="ts">
125
- import { ref, watch, computed } from 'vue';
126
- import { provinces } from '../utils/province';
127
- // 缓存处理后的省市区数据
128
- const municipalities = ['北京市', '上海市', '天津市', '重庆市','香港特别行政区','澳门特别行政区'];
129
- const cascaderOptions = computed(() => {
130
- return provinces.map(province => {
131
- // 如果是直辖市,直接返回省级选项,不包含下级
132
- if (municipalities.includes(province.name)) {
133
- return {
134
- label: province.name,
135
- value: province.name,
136
- children:province.city[0].area.map(area2 => ({
137
- label: area2,
138
- value: `${province.name}/${area2}`, // 使用省份名称作为前缀
139
- }))
140
- };
141
- }
142
- // 非直辖市,保持原有的三级结构
143
- return {
144
- label: province.name,
145
- value: province.name,
146
- children: province.city.map(city => ({
147
- label: city.name,
148
- value: `${province.name}/${city.name}`, // 使用省份名称作为前缀
149
- children: city.area.map(area => ({
150
- label: area,
151
- value: `${province.name}/${city.name}/${area}` // 使用省份和城市名称作为前缀
152
- }))
153
- }))
154
- };
155
- });
156
- });
157
- // 判断是否为预设日期操作符
158
- const isPresetDateOperator = (operator: string): boolean => {
159
- const presetOperators = [
160
- 'today', 'yesterday', 'tomorrow',
161
- 'this_week', 'last_week', 'next_week',
162
- 'this_month', 'last_month', 'next_month',
163
- 'this_year', 'last_year', 'next_year'
164
- ];
165
- return presetOperators.includes(operator);
166
- };
167
- // 字段类型定义
168
- type FieldType = 'number' | 'money' | 'text' | 'date' | 'time' | 'select' | 'selectProvince' | 'basedata' | 'billdata';
169
- // 字段选项接口
170
- interface FieldOption {
171
- key: string;
172
- label: string;
173
- value: string;
174
- type: FieldType;
175
- options?: { key: string; label: string; value: string }[];
176
- }
177
- // 过滤条件接口
178
- interface FilterCondition {
179
- field: string;
180
- operator: string;
181
- value: any;
182
- }
183
- // 定义组件属性
184
- interface Props {
185
- fieldOptions: FieldOption[];
186
- filterCondition: FilterCondition;
187
- selectOptions?: Record<string, any[]>; // 添加可选的selectOptions属性
188
- size?: string;
189
- }
190
- // 获取选择框的选项
191
- // 获取选择框的选项
192
- const getSelectOptions = (field: string) => {
193
- // 如果父组件传递了对应字段的选项,则使用父组件传递的选项
194
- if (props.selectOptions && (props.selectOptions as any)[field]) {
195
- return (props.selectOptions as any)[field];
196
- }
197
- // 尝试从父组件传来的字段列表中查找匹配的字段
198
- if (props.selectOptions && Array.isArray(props.selectOptions)){
199
- const fieldFromParent = (props.selectOptions as any[]).find((opt: any) =>
200
- opt.columnName === field
201
- );
202
- if (fieldFromParent && fieldFromParent.value) {
203
- return sortDepartments(fieldFromParent.value.map((item: any) => ({
204
- label: item,
205
- value: item
206
- })));
207
- }
208
- }
209
- // 如果都没有,返回空数组
210
- return [];
211
- };
212
- function sortDepartments(departments: any[]) {
213
- return [...departments].sort((a: any, b: any) => a.value.localeCompare(b.value, 'zh-CN'));
214
- }
215
- const props = defineProps<Props>();
216
- // Emits 定义
217
- const emit = defineEmits<{
218
- (e: 'search', condition: FilterCondition): void;
219
- }>();
220
- // 使用传入的筛选条件
221
- const filterCondition = ref<FilterCondition>({
222
- field: props.filterCondition?.field || '',
223
- operator: props.filterCondition?.operator || 'eq',
224
- value: props.filterCondition?.value || null
225
- });
226
- // 监听筛选条件变化
227
- watch(() => props.filterCondition, (newVal) => {
228
- if (newVal) {
229
- filterCondition.value = { ...newVal };
230
- }
231
- }, { deep: true });
232
- // 获取字段类型
233
- const getFieldType = (field: string) => {
234
- const option = props.fieldOptions.find(opt => opt.value === field);
235
- return option ? option.type : 'text';
236
- };
237
- // 处理值变化
238
- const handleValueChange = () => {
239
- // 将当前筛选条件直接发送给父组件
240
- emit('search', { ...filterCondition.value });
241
- };
242
- // 处理操作符变化
243
- const handleOperatorChange = () => {
244
- // 将当前筛选条件直接发送给父组件
245
- emit('search', { ...filterCondition.value });
246
- };
247
- // 操作符选项
248
- const operatorOptions = computed(() => {
249
- const field = props.filterCondition.field;
250
- const fieldType = props.fieldOptions.find(opt => opt.value === field)?.type || 'text';
251
- switch (fieldType) {
252
- case 'number':
253
- return [
254
- { key: 'eq', label: '等于', value: 'eq' },
255
- { key: 'ne', label: '不等于', value: 'ne' },
256
- { key: 'gt', label: '大于', value: 'gt' },
257
- { key: 'gte', label: '大于等于', value: 'gte' },
258
- { key: 'lt', label: '小于', value: 'lt' },
259
- { key: 'lte', label: '小于等于', value: 'lte' }
260
- ];
261
- case 'date':
262
- return [
263
- { key: 'eq', label: '等于', value: 'eq' },
264
- { key: 'ne', label: '不等于', value: 'ne' },
265
- { key: 'gt', label: '在此之后', value: 'gt' },
266
- { key: 'gte', label: '在此之后(含)', value: 'gte' },
267
- { key: 'lt', label: '在此之前', value: 'lt' },
268
- { key: 'lte', label: '在此之前(含)', value: 'lte' },
269
- { key: 'today', label: '今天', value: 'today' },
270
- { key: 'yesterday', label: '昨天', value: 'yesterday' },
271
- { key: 'tomorrow', label: '明天', value: 'tomorrow' },
272
- { key: 'this_week', label: '本周', value: 'this_week' },
273
- { key: 'last_week', label: '上周', value: 'last_week' },
274
- { key: 'next_week', label: '下周', value: 'next_week' },
275
- { key: 'this_month', label: '本月', value: 'this_month' },
276
- { key: 'last_month', label: '上月', value: 'last_month' },
277
- { key: 'next_month', label: '下月', value: 'next_month' },
278
- { key: 'this_year', label: '今年', value: 'this_year' },
279
- { key: 'last_year', label: '去年', value: 'last_year' },
280
- { key: 'next_year', label: '明年', value: 'next_year' }
281
- ];
282
- case 'time':
283
- return [
284
- { key: 'eq', label: '等于', value: 'eq' },
285
- { key: 'ne', label: '不等于', value: 'ne' },
286
- { key: 'gt', label: '在此之后', value: 'gt' },
287
- { key: 'gte', label: '在此之后(含)', value: 'gte' },
288
- { key: 'lt', label: '在此之前', value: 'lt' },
289
- { key: 'lte', label: '在此之前(含)', value: 'lte' }
290
- ];
291
- case 'select':
292
- return [
293
- { key: 'eq', label: '等于', value: 'eq' },
294
- { key: 'ne', label: '不等于', value: 'ne' }
295
- ];
296
- case 'selectProvince':
297
- return [
298
- { key: 'eq', label: '等于', value: 'eq' },
299
- { key: 'ne', label: '不等于', value: 'ne' },
300
- { key: 'contains', label: '包含', value: 'contains' },
301
- { key: 'not_contains', label: '不包含', value: 'not_contains' },
302
- { key: 'one_of', label: '等于其中之一', value: 'one_of' },
303
- ];
304
- case 'basedata':
305
- return [
306
- { key: 'eq', label: '等于', value: 'eq' },
307
- { key: 'ne', label: '不等于', value: 'ne' },
308
- { key: 'contains', label: '包含', value: 'contains' },
309
- { key: 'not_contains', label: '不包含', value: 'not_contains' },
310
- { key: 'one_of', label: '等于其中之一', value: 'one_of' },
311
- ];
312
- case 'billdata':
313
- return [
314
- { key: 'eq', label: '等于', value: 'eq' },
315
- { key: 'ne', label: '不等于', value: 'ne' },
316
- { key: 'contains', label: '包含', value: 'contains' },
317
- { key: 'not_contains', label: '不包含', value: 'not_contains' },
318
- { key: 'one_of', label: '等于其中之一', value: 'one_of' },
319
- ];
320
- default:
321
- return [
322
- { key: 'contains', label: '包含', value: 'contains' },
323
- { key: 'eq', label: '等于', value: 'eq' },
324
- { key: 'ne', label: '不等于', value: 'ne' },
325
- { key: 'starts_with', label: '开头是', value: 'starts_with' },
326
- { key: 'ends_with', label: '结尾是', value: 'ends_with' }
327
- ];
328
- }
329
- });
330
- // 处理字段变化
331
- const handleFieldChange = () => {
332
- };
333
- // 监听字段变化
334
- watch(() => props.filterCondition.field, (oldField,newField) => {
335
- if(oldField && newField){
336
- props.filterCondition.operator = 'eq';
337
- props.filterCondition.value = null;
338
- }
339
- });
340
- </script>
341
- <style scoped>
342
- .filter-component {
343
- display: flex;
344
- align-items: center;
345
- gap: 5px;
346
- flex: 1;
347
- border-radius: var(--td-radius-medium);
348
- }
349
- .filter-component :deep(.t-button) {
350
- margin-left: 8px;
351
- }
352
- .filter-component :deep(.t-input),
353
- .filter-component :deep(.t-select),
354
- .filter-component :deep(.t-date-picker),
355
- .filter-component :deep(.t-time-picker),
356
- .filter-component :deep(.t-cascader) {
357
- flex-shrink: 0;
358
- }
359
- </style>
@@ -1,239 +0,0 @@
1
- <template>
2
- <t-dialog
3
- :header="isEditing ? '编辑筛选方案' : '保存筛选方案'"
4
- :visible="visible"
5
- @update:visible="$emit('update:visible', $event)"
6
- width="600px"
7
- :footer="true"
8
- @confirm="handleConfirm"
9
- @close="handleClose"
10
- >
11
- <template #body>
12
- <div class="save-plan-content">
13
- <t-form :data="formData" label-align="left" label-width="100px">
14
- <t-form-item label="方案名称" required>
15
- <t-input v-model="formData.planName" placeholder="请输入方案名称" />
16
- </t-form-item>
17
- <t-form-item label="共享设置">
18
- <t-radio-group v-model="formData.shareMode" :disabled="isShareModeDisabled">
19
- <t-radio value="none">不共享</t-radio>
20
- <t-radio value="share">共享给其他人</t-radio>
21
- <t-tooltip content="复制方案只支持复制一次,建议共享方案" placement="top">
22
- <t-radio value="copy">复制给其他人</t-radio>
23
- </t-tooltip>
24
- </t-radio-group>
25
- </t-form-item>
26
- <t-form-item v-if="formData.shareMode === 'share'" label="共享范围">
27
- <t-radio-group v-model="formData.shareType">
28
- <t-radio value="specific">指定用户</t-radio>
29
- <t-radio value="all">所有用户</t-radio>
30
- </t-radio-group>
31
- </t-form-item>
32
- <t-form-item v-if="(formData.shareMode === 'share' && formData.shareType === 'specific') || formData.shareMode === 'copy'" label="共享人员">
33
- <CdUsersList
34
- :users="sharedUsersList"
35
- :avatar-size="32"
36
- :max-display="10"
37
- :edit="true"
38
- :person-tabs="tabs"
39
- :person-organizations="organizations"
40
- :dept-members-data="deptMembersData"
41
- @person-select="handlePersonSelect"
42
- @load-users="handleLoadUsers"
43
- @search="handlePersonSearch"
44
- @dept-click="handleDeptClick"
45
- />
46
- </t-form-item>
47
- </t-form>
48
- </div>
49
- </template>
50
- <template #footer>
51
- <t-button theme="default" @click="handleClose">取消</t-button>
52
- <t-button theme="primary" @click="handleConfirm">确定</t-button>
53
- </template>
54
- </t-dialog>
55
- </template>
56
- <script setup>
57
- import { ref, watch, computed } from 'vue'
58
- import { MessagePlugin } from 'tdesign-vue-next'
59
- import { CdUsersList } from 'cd-usercard'
60
-
61
- const props = defineProps({
62
- visible: {
63
- type: Boolean,
64
- default: false
65
- },
66
- isEditing: {
67
- type: Boolean,
68
- default: false
69
- },
70
- planName: {
71
- type: String,
72
- default: ''
73
- },
74
- userOptions: {
75
- type: Array,
76
- default: () => []
77
- },
78
- tabs: {
79
- type: Array,
80
- default: () => []
81
- },
82
- organizations: {
83
- type: Array,
84
- default: () => []
85
- },
86
- deptMembersDataProp: {
87
- type: Array,
88
- default: () => []
89
- },
90
- // 原始方案的共享配置
91
- originalShareMode: {
92
- type: String,
93
- default: 'none'
94
- },
95
- originalShareType: {
96
- type: String,
97
- default: 'specific'
98
- },
99
- originalSelectedUsers: {
100
- type: Array,
101
- default: () => []
102
- }
103
- })
104
-
105
- const emit = defineEmits(['update:visible', 'confirm', 'close', 'load-users', 'search', 'dept-click'])
106
-
107
- const formData = ref({
108
- planName: '',
109
- shareMode: 'none',
110
- shareType: 'specific',
111
- selectedUsers: []
112
- })
113
-
114
- const sharedUsersList = ref([])
115
- const deptMembersData = ref([])
116
-
117
- // 判断是否禁用共享设置radio - 编辑模式下如果原方案是复制模式则禁用
118
- const isShareModeDisabled = computed(() => {
119
- return props.isEditing && props.originalShareMode === 'copy'
120
- })
121
-
122
- watch(() => props.visible, (newVal) => {
123
- if (newVal) {
124
- // 打开对话框时,如果是编辑模式,预填充方案名称和共享配置
125
- if (props.isEditing && props.planName) {
126
- formData.value.planName = props.planName
127
- formData.value.shareMode = props.originalShareMode || 'none'
128
- formData.value.shareType = props.originalShareType || 'specific'
129
-
130
- // 恢复原始共享用户列表
131
- if (props.originalSelectedUsers && props.originalSelectedUsers.length > 0) {
132
- formData.value.selectedUsers = [...props.originalSelectedUsers]
133
- // 这里需要从父组件传入完整的用户信息
134
- // 暂时只保存ID,实际使用时需要完整的用户信息
135
- }
136
- }
137
- } else {
138
- resetForm()
139
- }
140
- })
141
-
142
- watch(() => props.deptMembersDataProp, (newVal) => {
143
- if (newVal) {
144
- deptMembersData.value = newVal
145
- }
146
- })
147
-
148
- const resetForm = () => {
149
- formData.value = {
150
- planName: '',
151
- shareMode: 'none',
152
- shareType: 'specific',
153
- selectedUsers: []
154
- }
155
- sharedUsersList.value = []
156
- deptMembersData.value = []
157
- }
158
-
159
- const handlePersonSelect = (persons) => {
160
- console.log('选中人员:', persons)
161
- // 将选中的人员/部门转换为用户列表格式并添加到列表中
162
- const newItems = persons.map(p => ({
163
- id: p.id,
164
- name: p.displayName || p.name,
165
- avatar: '',
166
- department: p.department,
167
- role: p.position,
168
- email: '',
169
- mobile: p.phone || '',
170
- employeeId: '',
171
- signature: '',
172
- tags: [],
173
- isUser: p.isUser === true,
174
- }))
175
- // 去重并添加
176
- newItems.forEach(newItem => {
177
- const exists = sharedUsersList.value.find(u => u.id === newItem.id)
178
- if (!exists) {
179
- sharedUsersList.value.push(newItem)
180
- }
181
- })
182
- // 更新 selectedUsers 用于提交
183
- formData.value.selectedUsers = sharedUsersList.value.map(u => u.id)
184
- }
185
-
186
- const handleLoadUsers = (payload) => {
187
- emit('load-users', payload)
188
- }
189
-
190
- const handlePersonSearch = (payload) => {
191
- emit('search', payload)
192
- }
193
-
194
- const handleDeptClick = (dept) => {
195
- emit('dept-click', dept)
196
- }
197
-
198
- const handleConfirm = () => {
199
- if (!formData.value.planName.trim()) {
200
- MessagePlugin.warning('请输入方案名称')
201
- return
202
- }
203
- if ((formData.value.shareMode === 'share' || formData.value.shareMode === 'copy') &&
204
- formData.value.shareType === 'specific' &&
205
- formData.value.selectedUsers.length === 0) {
206
- MessagePlugin.warning('请选择要共享的用户')
207
- return
208
- }
209
- emit('confirm', {
210
- planName: formData.value.planName,
211
- shareMode: formData.value.shareMode,
212
- shareType: formData.value.shareType,
213
- selectedUsers: formData.value.selectedUsers
214
- })
215
- emit('update:visible', false)
216
- }
217
- const handleClose = () => {
218
- emit('close')
219
- emit('update:visible', false)
220
- }
221
- </script>
222
- <style scoped>
223
- .save-plan-content {
224
- padding: 20px 0;
225
- font-size: 9pt;
226
- }
227
- .save-plan-content :deep(.t-form-item__label) {
228
- font-size: 9pt;
229
- }
230
- .save-plan-content :deep(.t-input),
231
- .save-plan-content :deep(.t-checkbox),
232
- .save-plan-content :deep(.t-radio),
233
- .save-plan-content :deep(.t-select) {
234
- font-size: 9pt;
235
- }
236
- .save-plan-content :deep(.t-input__inner) {
237
- font-size: 9pt;
238
- }
239
- </style>