Files
laravel_swoole/resources/admin/src/components/scSelect/index.vue
T
2026-02-19 11:46:27 +08:00

303 lines
6.0 KiB
Vue

<template>
<a-select v-bind="$attrs" :loading="loading" :options="options" :field-names="fieldNames" @focus="handleFocus">
<template v-for="(_, slot) of $slots" #[slot]="scope">
<slot :name="slot" v-bind="scope || {}" />
</template>
</a-select>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useDictionaryStore } from '@/stores/modules/dictionary'
import request from '@/utils/request'
defineOptions({
name: 'ScSelect',
inheritAttrs: false,
})
const props = defineProps({
// 数据源类型:data(直接数据)、api(接口数据)、dictionary(字典数据)
sourceType: {
type: String,
default: 'data',
validator: (value) => ['data', 'api', 'dictionary'].includes(value),
},
// 直接数据(当 sourceType 为 data 时使用)
data: {
type: Array,
default: () => [],
},
// API 接口地址(当 sourceType 为 api 时使用)
api: {
type: String,
default: '',
},
// API 请求参数(当 sourceType 为 api 时使用)
apiParams: {
type: Object,
default: () => ({}),
},
// 字典编码(当 sourceType 为 dictionary 时使用)
dictionaryCode: {
type: String,
default: '',
},
// 是否启用 API 数据缓存(当 sourceType 为 api 时使用)
enableApiCache: {
type: Boolean,
default: true,
},
// API 缓存时间(毫秒,当 sourceType 为 api 时使用)
apiCacheTime: {
type: Number,
default: 5 * 60 * 1000, // 默认 5 分钟
},
// 字段映射配置
fieldNames: {
type: Object,
default: () => ({
label: 'label',
value: 'value',
}),
},
// 是否在组件挂载时立即加载数据
immediate: {
type: Boolean,
default: false,
},
// 数据处理函数,用于自定义数据格式转换
dataProcessor: {
type: Function,
default: null,
},
})
const dictionaryStore = useDictionaryStore()
const loading = ref(false)
const apiData = ref(null)
const apiCacheTime = ref(null)
const options = computed(() => {
switch (props.sourceType) {
case 'data':
return processData(props.data)
case 'api':
return processData(apiData.value || [])
case 'dictionary':
return processData(getDictionaryData())
default:
return []
}
})
// API 数据缓存
const apiCache = new Map()
/**
* 处理数据格式
*/
function processData(data) {
if (!data || !Array.isArray(data)) {
return []
}
// 如果有自定义处理函数,使用自定义处理
if (props.dataProcessor && typeof props.dataProcessor === 'function') {
return props.dataProcessor(data)
}
// 默认处理:确保数据有 label 和 value 字段
return data.map((item) => {
if (typeof item === 'string' || typeof item === 'number') {
return {
label: item,
value: item,
}
}
// 如果已经有 label 和 value 字段,直接返回
if (item.label !== undefined && item.value !== undefined) {
return item
}
// 尝试使用 fieldNames 映射
const labelKey = props.fieldNames.label || 'label'
const valueKey = props.fieldNames.value || 'value'
return {
label: item[labelKey] || item.name || item.title || item.id,
value: item[valueKey] !== undefined ? item[valueKey] : item.id,
...item,
}
})
}
/**
* 获取字典数据
*/
function getDictionaryData() {
if (!props.dictionaryCode) {
return []
}
return dictionaryStore.dictionaries[props.dictionaryCode] || []
}
/**
* 加载 API 数据
*/
async function loadApiData() {
if (!props.api) {
return
}
// 检查缓存
if (props.enableApiCache) {
const cacheKey = props.api + JSON.stringify(props.apiParams)
const cached = apiCache.get(cacheKey)
if (cached && Date.now() - cached.time < props.apiCacheTime) {
apiData.value = cached.data
return
}
}
loading.value = true
try {
const res = await request.get(props.api, { params: props.apiParams })
if (res.code === 200) {
const data = res.data || res.list || []
apiData.value = data
// 缓存数据
if (props.enableApiCache) {
const cacheKey = props.api + JSON.stringify(props.apiParams)
apiCache.set(cacheKey, {
data,
time: Date.now(),
})
}
}
} catch (error) {
console.error('加载 API 数据失败:', error)
apiData.value = []
} finally {
loading.value = false
}
}
/**
* 加载字典数据
*/
async function loadDictionaryData() {
if (!props.dictionaryCode) {
return
}
// 检查缓存
if (dictionaryStore.dictionaries[props.dictionaryCode]) {
return
}
loading.value = true
try {
await dictionaryStore.getDictionary(props.dictionaryCode)
} catch (error) {
console.error('加载字典数据失败:', error)
} finally {
loading.value = false
}
}
/**
* 处理 focus 事件,按需加载数据
*/
async function handleFocus() {
switch (props.sourceType) {
case 'api':
if (!apiData.value || apiData.value.length === 0) {
await loadApiData()
}
break
case 'dictionary':
if (!dictionaryStore.dictionaries[props.dictionaryCode]) {
await loadDictionaryData()
}
break
}
}
/**
* 强制刷新数据
*/
async function refresh() {
switch (props.sourceType) {
case 'api':
await loadApiData()
break
case 'dictionary':
await dictionaryStore.getDictionary(props.dictionaryCode, true)
break
}
}
// 监听数据源变化
watch(
() => props.data,
(newVal) => {
if (props.sourceType === 'data') {
// data 类型不需要特殊处理,计算属性会自动更新
}
},
{ immediate: true },
)
watch(
() => props.api,
() => {
if (props.sourceType === 'api') {
apiData.value = null
}
},
)
watch(
() => props.apiParams,
() => {
if (props.sourceType === 'api') {
apiData.value = null
}
},
{ deep: true },
)
watch(
() => props.dictionaryCode,
() => {
if (props.sourceType === 'dictionary') {
// dictionary 变化时,数据会自动从 store 获取
}
},
)
// 组件挂载时立即加载数据
if (props.immediate) {
switch (props.sourceType) {
case 'api':
loadApiData()
break
case 'dictionary':
loadDictionaryData()
break
}
}
// 暴露方法供外部调用
defineExpose({
refresh,
loadApiData,
loadDictionaryData,
})
</script>
<style scoped lang="scss">
// 组件样式继承自 a-select
</style>