c-admin-kit 1.0.0
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/README.md +232 -0
- package/dist/c-admin-kit.css +1 -0
- package/dist/c-admin-kit.js +2482 -0
- package/dist/c-admin-kit.umd.cjs +10 -0
- package/dist/components/AsyncButton/index.vue.d.ts +48 -0
- package/dist/components/CollapsibleContainer/index.vue.d.ts +73 -0
- package/dist/components/CustomDrawer/index.vue.d.ts +149 -0
- package/dist/components/ImagePreview/index.vue.d.ts +34 -0
- package/dist/components/SearchBox/index.vue.d.ts +122 -0
- package/dist/components/SelectWithAll/index.vue.d.ts +169 -0
- package/dist/components/SelectWithPage/index.vue.d.ts +78 -0
- package/dist/components/SimpleTable/index.vue.d.ts +337 -0
- package/dist/components/SimpleTable/useTableColumnConfig.d.ts +5 -0
- package/dist/components/diff/index.vue.d.ts +33 -0
- package/dist/components/index.d.ts +12 -0
- package/dist/composables/index.d.ts +6 -0
- package/dist/composables/useConfirmAction.d.ts +5 -0
- package/dist/composables/useConfirmSubmit.d.ts +5 -0
- package/dist/composables/useDialog.d.ts +5 -0
- package/dist/composables/useDownload.d.ts +5 -0
- package/dist/composables/useForm.d.ts +5 -0
- package/dist/composables/useListPage.d.ts +5 -0
- package/dist/index.d.ts +9 -0
- package/dist/types.d.ts +282 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/scroll-to.d.ts +7 -0
- package/dist/utils/searchFieldFactory.d.ts +34 -0
- package/dist/utils/treeManager.d.ts +64 -0
- package/dist/utils/validate.d.ts +30 -0
- package/package.json +86 -0
- package/src/components/AsyncButton/index.vue +58 -0
- package/src/components/CollapsibleContainer/index.vue +240 -0
- package/src/components/CustomDrawer/index.vue +167 -0
- package/src/components/ImagePreview/index.vue +88 -0
- package/src/components/SearchBox/index.vue +582 -0
- package/src/components/SelectWithAll/index.vue +281 -0
- package/src/components/SelectWithPage/index.vue +204 -0
- package/src/components/SimpleTable/index.vue +781 -0
- package/src/components/SimpleTable/useTableColumnConfig.ts +139 -0
- package/src/components/diff/index.vue +265 -0
- package/src/components/index.ts +35 -0
- package/src/composables/index.ts +6 -0
- package/src/composables/useConfirmAction.ts +62 -0
- package/src/composables/useConfirmSubmit.ts +68 -0
- package/src/composables/useDialog.ts +48 -0
- package/src/composables/useDownload.ts +83 -0
- package/src/composables/useForm.ts +114 -0
- package/src/composables/useListPage.ts +243 -0
- package/src/env.d.ts +7 -0
- package/src/index.ts +44 -0
- package/src/types.ts +344 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/scroll-to.ts +60 -0
- package/src/utils/searchFieldFactory.ts +302 -0
- package/src/utils/treeManager.ts +218 -0
- package/src/utils/validate.ts +64 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-select
|
|
3
|
+
v-model="internalValue"
|
|
4
|
+
v-bind="$attrs"
|
|
5
|
+
multiple
|
|
6
|
+
collapse-tags
|
|
7
|
+
collapse-tags-tooltip
|
|
8
|
+
filterable
|
|
9
|
+
:placeholder="placeholder"
|
|
10
|
+
:remote="remote"
|
|
11
|
+
:remote-method="remote ? handleRemoteSearch : undefined"
|
|
12
|
+
:loading="loading"
|
|
13
|
+
:loading-text="loadingText"
|
|
14
|
+
@change="handleChange"
|
|
15
|
+
@visible-change="handleVisibleChange"
|
|
16
|
+
>
|
|
17
|
+
<!-- 全选选项 -->
|
|
18
|
+
<el-option
|
|
19
|
+
v-if="showSelectAll"
|
|
20
|
+
:value="SELECT_ALL_VALUE"
|
|
21
|
+
:label="selectAllLabel"
|
|
22
|
+
class="select-all-option"
|
|
23
|
+
>
|
|
24
|
+
<el-checkbox
|
|
25
|
+
:model-value="isAllSelected"
|
|
26
|
+
:indeterminate="isIndeterminate"
|
|
27
|
+
@change="handleSelectAll"
|
|
28
|
+
@click.stop
|
|
29
|
+
>
|
|
30
|
+
{{ selectAllLabel }}
|
|
31
|
+
</el-checkbox>
|
|
32
|
+
</el-option>
|
|
33
|
+
|
|
34
|
+
<!-- 普通选项 -->
|
|
35
|
+
<el-option
|
|
36
|
+
v-for="item in displayOptions"
|
|
37
|
+
:key="item[valueKey]"
|
|
38
|
+
:label="item[labelKey]"
|
|
39
|
+
:value="item[valueKey]"
|
|
40
|
+
:disabled="item.disabled"
|
|
41
|
+
/>
|
|
42
|
+
|
|
43
|
+
<!-- 空状态 -->
|
|
44
|
+
<template #empty>
|
|
45
|
+
<div class="empty-text">{{ emptyText }}</div>
|
|
46
|
+
</template>
|
|
47
|
+
</el-select>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { ref, computed, watch, onBeforeUnmount, onMounted, type PropType } from "vue";
|
|
52
|
+
import type { AnyRecord } from "../../types";
|
|
53
|
+
|
|
54
|
+
defineOptions({ name: "CSelectWithAll" });
|
|
55
|
+
|
|
56
|
+
const SELECT_ALL_VALUE = "__SELECT_ALL__";
|
|
57
|
+
|
|
58
|
+
const props = defineProps({
|
|
59
|
+
modelValue: {
|
|
60
|
+
type: Array as PropType<any[]>,
|
|
61
|
+
default: () => [],
|
|
62
|
+
},
|
|
63
|
+
options: {
|
|
64
|
+
type: Array as PropType<AnyRecord[]>,
|
|
65
|
+
default: () => [],
|
|
66
|
+
},
|
|
67
|
+
// 是否显示全选选项
|
|
68
|
+
showSelectAll: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: true,
|
|
71
|
+
},
|
|
72
|
+
// 全选文本
|
|
73
|
+
selectAllLabel: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: "全选",
|
|
76
|
+
},
|
|
77
|
+
// 占位符
|
|
78
|
+
placeholder: {
|
|
79
|
+
type: String,
|
|
80
|
+
default: "请选择",
|
|
81
|
+
},
|
|
82
|
+
// 选项的标签字段名
|
|
83
|
+
labelKey: {
|
|
84
|
+
type: String,
|
|
85
|
+
default: "label",
|
|
86
|
+
},
|
|
87
|
+
// 选项的值字段名
|
|
88
|
+
valueKey: {
|
|
89
|
+
type: String,
|
|
90
|
+
default: "value",
|
|
91
|
+
},
|
|
92
|
+
// 是否启用远程搜索
|
|
93
|
+
remote: {
|
|
94
|
+
type: Boolean,
|
|
95
|
+
default: false,
|
|
96
|
+
},
|
|
97
|
+
// 远程搜索方法(返回 Promise)
|
|
98
|
+
remoteMethod: {
|
|
99
|
+
type: Function as PropType<(params: AnyRecord) => Promise<any>>,
|
|
100
|
+
default: null,
|
|
101
|
+
},
|
|
102
|
+
// 传给后端的搜索关键词参数名
|
|
103
|
+
remoteSearchKey: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: "keyword",
|
|
106
|
+
},
|
|
107
|
+
// 传给后端的其他额外参数
|
|
108
|
+
remoteParams: {
|
|
109
|
+
type: Object as PropType<AnyRecord>,
|
|
110
|
+
default: () => ({}),
|
|
111
|
+
},
|
|
112
|
+
// 防抖延迟时间(ms)
|
|
113
|
+
debounceDelay: {
|
|
114
|
+
type: Number,
|
|
115
|
+
default: 300,
|
|
116
|
+
},
|
|
117
|
+
// 加载中文本
|
|
118
|
+
loadingText: {
|
|
119
|
+
type: String,
|
|
120
|
+
default: "加载中...",
|
|
121
|
+
},
|
|
122
|
+
// 无数据文本
|
|
123
|
+
emptyText: {
|
|
124
|
+
type: String,
|
|
125
|
+
default: "暂无数据",
|
|
126
|
+
},
|
|
127
|
+
// 是否在打开下拉框时自动搜索
|
|
128
|
+
searchOnOpen: {
|
|
129
|
+
type: Boolean,
|
|
130
|
+
default: false,
|
|
131
|
+
},
|
|
132
|
+
// 初始搜索关键词(用于打开时的默认搜索)
|
|
133
|
+
initialSearchKeyword: {
|
|
134
|
+
type: String,
|
|
135
|
+
default: "",
|
|
136
|
+
},
|
|
137
|
+
dataKey: {
|
|
138
|
+
type: String,
|
|
139
|
+
default: "data",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const emit = defineEmits(["update:modelValue", "change", "remote-search"]);
|
|
144
|
+
|
|
145
|
+
// 内部值
|
|
146
|
+
const internalValue = ref<any[]>([...props.modelValue]);
|
|
147
|
+
|
|
148
|
+
// 远程搜索的选项列表
|
|
149
|
+
const remoteOptions = ref<AnyRecord[]>([]);
|
|
150
|
+
|
|
151
|
+
// 加载状态
|
|
152
|
+
const loading = ref(false);
|
|
153
|
+
|
|
154
|
+
// 防抖定时器
|
|
155
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
156
|
+
|
|
157
|
+
// 显示的选项列表
|
|
158
|
+
const displayOptions = computed<AnyRecord[]>(() => {
|
|
159
|
+
return props.remote ? remoteOptions.value : (props.options as AnyRecord[]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// 所有可选项的值(排除禁用项)
|
|
163
|
+
const allValues = computed(() => {
|
|
164
|
+
return displayOptions.value
|
|
165
|
+
.filter((item) => !item.disabled)
|
|
166
|
+
.map((item) => item[props.valueKey]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// 是否全选
|
|
170
|
+
const isAllSelected = computed(() => {
|
|
171
|
+
if (allValues.value.length === 0) return false;
|
|
172
|
+
return allValues.value.every((val) => internalValue.value.includes(val));
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 是否半选(部分选中)
|
|
176
|
+
const isIndeterminate = computed(() => {
|
|
177
|
+
const selectedCount = internalValue.value.filter((val) =>
|
|
178
|
+
allValues.value.includes(val)
|
|
179
|
+
).length;
|
|
180
|
+
return selectedCount > 0 && selectedCount < allValues.value.length;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// 处理全选
|
|
184
|
+
const handleSelectAll = (checked: boolean | string | number) => {
|
|
185
|
+
if (checked) {
|
|
186
|
+
internalValue.value = [...new Set([...allValues.value])];
|
|
187
|
+
} else {
|
|
188
|
+
internalValue.value = [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
emit("update:modelValue", internalValue.value);
|
|
192
|
+
emit("change", internalValue.value);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// 处理选择变化
|
|
196
|
+
const handleChange = (val: any[]) => {
|
|
197
|
+
const filteredValue = val.filter((v) => v !== SELECT_ALL_VALUE);
|
|
198
|
+
internalValue.value = filteredValue;
|
|
199
|
+
emit("update:modelValue", filteredValue);
|
|
200
|
+
emit("change", filteredValue);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// 处理远程搜索
|
|
204
|
+
const handleRemoteSearch = (query: string = "") => {
|
|
205
|
+
if (debounceTimer) {
|
|
206
|
+
clearTimeout(debounceTimer);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
debounceTimer = setTimeout(async () => {
|
|
210
|
+
if (!props.remoteMethod) return;
|
|
211
|
+
|
|
212
|
+
loading.value = true;
|
|
213
|
+
try {
|
|
214
|
+
const searchParams = {
|
|
215
|
+
...props.remoteParams,
|
|
216
|
+
[props.remoteSearchKey]: query || "",
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const res = await props.remoteMethod(searchParams);
|
|
220
|
+
let data: any[] = [];
|
|
221
|
+
if (res && res.code === 200) {
|
|
222
|
+
data = res[props.dataKey] || res.data || [];
|
|
223
|
+
} else if (Array.isArray(res)) {
|
|
224
|
+
data = res;
|
|
225
|
+
}
|
|
226
|
+
remoteOptions.value = Array.isArray(data) ? data : [];
|
|
227
|
+
emit("remote-search", { query, result: remoteOptions.value });
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error("CSelectWithAll 远程搜索失败:", error);
|
|
230
|
+
remoteOptions.value = [];
|
|
231
|
+
} finally {
|
|
232
|
+
loading.value = false;
|
|
233
|
+
}
|
|
234
|
+
}, props.debounceDelay);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const handleVisibleChange = (visible: boolean) => {
|
|
238
|
+
if (visible && props.remote && props.searchOnOpen) {
|
|
239
|
+
handleRemoteSearch(props.initialSearchKeyword);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
watch(
|
|
244
|
+
() => props.modelValue,
|
|
245
|
+
(newVal) => {
|
|
246
|
+
internalValue.value = [...(newVal || [])];
|
|
247
|
+
},
|
|
248
|
+
{ deep: true }
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
onMounted(() => {
|
|
252
|
+
if (props.remote && props.searchOnOpen) {
|
|
253
|
+
handleRemoteSearch();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
onBeforeUnmount(() => {
|
|
258
|
+
if (debounceTimer) {
|
|
259
|
+
clearTimeout(debounceTimer);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
</script>
|
|
263
|
+
|
|
264
|
+
<style scoped>
|
|
265
|
+
.select-all-option {
|
|
266
|
+
border-bottom: 1px solid #e4e7ed;
|
|
267
|
+
font-weight: 600;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
:deep(.el-checkbox) {
|
|
271
|
+
width: 100%;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.empty-text {
|
|
275
|
+
padding: 10px 0;
|
|
276
|
+
margin: 0;
|
|
277
|
+
text-align: center;
|
|
278
|
+
color: #999;
|
|
279
|
+
font-size: 14px;
|
|
280
|
+
}
|
|
281
|
+
</style>
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="remote-select-wrapper">
|
|
3
|
+
<el-select
|
|
4
|
+
v-model="selectedValue"
|
|
5
|
+
filterable
|
|
6
|
+
remote
|
|
7
|
+
:remote-method="handleSearch"
|
|
8
|
+
:loading="loading"
|
|
9
|
+
:placeholder="placeholder"
|
|
10
|
+
clearable
|
|
11
|
+
v-bind="$attrs"
|
|
12
|
+
@clear="handleClear"
|
|
13
|
+
@change="handleChange"
|
|
14
|
+
>
|
|
15
|
+
<el-option
|
|
16
|
+
v-for="item in options"
|
|
17
|
+
:key="item[valueKey]"
|
|
18
|
+
:label="item[labelKey]"
|
|
19
|
+
:value="item[valueKey]"
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
<!-- 加载中 -->
|
|
23
|
+
<el-option v-if="loading" disabled value="">
|
|
24
|
+
<div class="loading-text">
|
|
25
|
+
<el-icon class="is-loading"><Loading /></el-icon>
|
|
26
|
+
加载中...
|
|
27
|
+
</div>
|
|
28
|
+
</el-option>
|
|
29
|
+
|
|
30
|
+
<!-- 无数据(已搜索) -->
|
|
31
|
+
<el-option
|
|
32
|
+
v-if="!loading && options.length === 0 && keyword"
|
|
33
|
+
disabled
|
|
34
|
+
value=""
|
|
35
|
+
>
|
|
36
|
+
<div class="no-data-text">暂无数据</div>
|
|
37
|
+
</el-option>
|
|
38
|
+
|
|
39
|
+
<!-- 未搜索 -->
|
|
40
|
+
<el-option
|
|
41
|
+
v-if="!loading && options.length === 0 && !keyword"
|
|
42
|
+
disabled
|
|
43
|
+
value=""
|
|
44
|
+
>
|
|
45
|
+
<div class="no-data-text">请输入关键词进行搜索</div>
|
|
46
|
+
</el-option>
|
|
47
|
+
</el-select>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import { ref, watch, onBeforeUnmount, type PropType } from "vue";
|
|
53
|
+
import { Loading } from "@element-plus/icons-vue";
|
|
54
|
+
import type { AnyRecord } from "../../types";
|
|
55
|
+
|
|
56
|
+
defineOptions({ name: "CSelectWithPage" });
|
|
57
|
+
|
|
58
|
+
const props = defineProps({
|
|
59
|
+
modelValue: {
|
|
60
|
+
type: [String, Number] as PropType<string | number>,
|
|
61
|
+
default: "",
|
|
62
|
+
},
|
|
63
|
+
placeholder: {
|
|
64
|
+
type: String,
|
|
65
|
+
default: "请输入关键词搜索",
|
|
66
|
+
},
|
|
67
|
+
labelKey: {
|
|
68
|
+
type: String,
|
|
69
|
+
default: "keyword",
|
|
70
|
+
},
|
|
71
|
+
valueKey: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: "id",
|
|
74
|
+
},
|
|
75
|
+
fetchApi: {
|
|
76
|
+
type: Function as PropType<(params: AnyRecord) => Promise<any>>,
|
|
77
|
+
required: true,
|
|
78
|
+
},
|
|
79
|
+
// 外部传入的初始选项,用于回显 label
|
|
80
|
+
initialOptions: {
|
|
81
|
+
type: Array as PropType<AnyRecord[]>,
|
|
82
|
+
default: () => [],
|
|
83
|
+
},
|
|
84
|
+
debounceDelay: {
|
|
85
|
+
type: Number,
|
|
86
|
+
default: 300,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const emit = defineEmits(["update:modelValue", "selectItem", "clear"]);
|
|
91
|
+
|
|
92
|
+
const selectedValue = ref(props.modelValue);
|
|
93
|
+
const options = ref<AnyRecord[]>([...props.initialOptions]);
|
|
94
|
+
const loading = ref(false);
|
|
95
|
+
const keyword = ref("");
|
|
96
|
+
let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
97
|
+
const selectedItem = ref<AnyRecord | null>(null);
|
|
98
|
+
|
|
99
|
+
const loadData = async (searchKeyword = "") => {
|
|
100
|
+
if (loading.value || !props.fetchApi) return;
|
|
101
|
+
|
|
102
|
+
loading.value = true;
|
|
103
|
+
try {
|
|
104
|
+
const result = await props.fetchApi({
|
|
105
|
+
keyword: searchKeyword.trim(),
|
|
106
|
+
pageNum: 1,
|
|
107
|
+
pageSize: 2000,
|
|
108
|
+
});
|
|
109
|
+
const rows = (result?.rows || result?.data || []).slice(0, 1000);
|
|
110
|
+
options.value = Array.isArray(rows) ? rows : [];
|
|
111
|
+
|
|
112
|
+
if (selectedValue.value) {
|
|
113
|
+
const matched = options.value.find((item) => item[props.valueKey] === selectedValue.value);
|
|
114
|
+
if (matched) selectedItem.value = matched;
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("CSelectWithPage 加载数据失败:", error);
|
|
118
|
+
options.value = [];
|
|
119
|
+
} finally {
|
|
120
|
+
loading.value = false;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleSearch = (query: string) => {
|
|
125
|
+
if (searchTimer) clearTimeout(searchTimer);
|
|
126
|
+
|
|
127
|
+
keyword.value = query;
|
|
128
|
+
|
|
129
|
+
if (!query.trim()) {
|
|
130
|
+
options.value = [...props.initialOptions];
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
searchTimer = setTimeout(() => {
|
|
135
|
+
loadData(query);
|
|
136
|
+
}, props.debounceDelay);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const handleClear = () => {
|
|
140
|
+
keyword.value = "";
|
|
141
|
+
options.value = [...props.initialOptions];
|
|
142
|
+
selectedItem.value = null;
|
|
143
|
+
selectedValue.value = "";
|
|
144
|
+
emit("update:modelValue", "");
|
|
145
|
+
emit("selectItem", null);
|
|
146
|
+
emit("clear");
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const handleChange = (val: any) => {
|
|
150
|
+
const item = options.value.find((e) => e[props.valueKey] === val);
|
|
151
|
+
selectedItem.value = item || null;
|
|
152
|
+
|
|
153
|
+
emit("update:modelValue", val || "");
|
|
154
|
+
emit("selectItem", selectedItem.value);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
watch(
|
|
158
|
+
() => props.modelValue,
|
|
159
|
+
async (newVal) => {
|
|
160
|
+
if (newVal !== selectedValue.value) {
|
|
161
|
+
selectedValue.value = newVal;
|
|
162
|
+
|
|
163
|
+
if (newVal && !options.value.some((item) => item[props.valueKey] === newVal)) {
|
|
164
|
+
await loadData("");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
selectedItem.value =
|
|
168
|
+
options.value.find((item) => item[props.valueKey] === newVal) || null;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{ immediate: true }
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
watch(selectedValue, (newVal) => {
|
|
175
|
+
if (newVal !== props.modelValue) {
|
|
176
|
+
emit("update:modelValue", newVal || "");
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
onBeforeUnmount(() => {
|
|
181
|
+
if (searchTimer) clearTimeout(searchTimer);
|
|
182
|
+
});
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<style scoped>
|
|
186
|
+
.remote-select-wrapper {
|
|
187
|
+
width: 100%;
|
|
188
|
+
}
|
|
189
|
+
.loading-text {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: center;
|
|
193
|
+
gap: 8px;
|
|
194
|
+
color: #909399;
|
|
195
|
+
font-size: 14px;
|
|
196
|
+
padding: 10px;
|
|
197
|
+
}
|
|
198
|
+
.no-data-text {
|
|
199
|
+
text-align: center;
|
|
200
|
+
color: #c0c4cc;
|
|
201
|
+
font-size: 14px;
|
|
202
|
+
padding: 10px;
|
|
203
|
+
}
|
|
204
|
+
</style>
|