Cpte-Vue3/src/views/base/stock/components/StockSelect.vue

424 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- 容器选择 -->
<template>
<a-select
v-model:value="selectedValue"
showSearch
:placeholder="placeholder"
:loading="loading"
:allowClear="true"
:filterOption="filterOption"
:notFoundContent="notFoundContent"
:mode="multiple ? 'multiple' : 'default'"
@change="handleChange"
@search="handleSearch"
@focus="handleFocus"
@popup-scroll="handlePopupScroll"
:getPopupContainer="getParentContainer"
v-bind="attrs"
>
<template #notFoundContent>
</template>
<a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)">
{{ getOptionLabel(option) }}
</a-select-option>
</a-select>
</template>
<script lang="ts">
import { defineComponent, ref, watch, computed, onMounted } from 'vue';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { propTypes } from '/@/utils/propTypes';
import { defHttp } from '/@/utils/http/axios';
import { queryById } from '@/views/base/stock/Stock.api';
import { setPopContainer } from '/@/utils';
import { debounce } from 'lodash-es';
//
interface Stock {
id: string;
stockCode: string;
}
//
interface ResponseData {
records: Stock[];
total: number;
size: number;
current: number;
page: number;
}
export default defineComponent({
name: 'StockSelect',
inheritAttrs: false,
props: {
// v-model
value: propTypes.oneOfType([propTypes.string, propTypes.array, propTypes.object]),
//
placeholder: propTypes.string.def(''),
//
multiple: propTypes.bool.def(false),
//
async: propTypes.bool.def(true),
//
pageSize: propTypes.number.def(10),
//
popContainer: propTypes.string,
//
getPopupContainer: {
type: Function,
default: (node: HTMLElement) => node?.parentNode,
},
// 是否立即触发change事件
immediateChange: propTypes.bool.def(false),
// 返回值类型: 'id'(默认) | 'object' | 其他字段名
returnValue: propTypes.string.def('id'),
//默认空闲
status: propTypes.number.def(0),
//默认启用
izActive: propTypes.number.def(1),
//默认未删除
delFlag: propTypes.number.def(0),
//是否扫描
izScan: propTypes.bool.def(false),
},
emits: ['change', 'update:value', 'optionsLoaded'],
setup(props, { emit }) {
const Options = ref<Stock[]>([]);
const loading = ref<boolean>(false);
const allStocks = ref<Stock[]>([]);
const attrs = useAttrs({ excludeDefaultKeys: false });
// 分页相关
const pageNo = ref(1);
const isHasData = ref(true);
const scrollLoading = ref(false);
const searchKeyword = ref('');
// 选中值
const selectedValue = ref<string | string[] | undefined>(undefined);
// 未找到内容
const notFoundContent = computed(() => {
return loading.value ? undefined : '暂无数据';
});
/**
* 获取选项显示文本 - 始终显示完整格式
*/
function getOptionLabel(option: Stock) {
return `${option.stockCode}`;
}
/**
* 获取选项值 - 根据returnValue确定实际存储的值
*/
function getOptionValue(option: Stock) {
if (props.returnValue === 'object') {
return option.id; // 对于object类型仍然使用id作为选项值但在change事件中返回完整对象
} else if (props.returnValue === 'id') {
return option.id;
} else {
return option[props.returnValue as keyof Stock] as string;
}
}
/**
* 获取弹出层容器
*/
function getParentContainer(node: HTMLElement) {
if (props.popContainer) {
return setPopContainer(node, props.popContainer);
} else {
if (typeof props.getPopupContainer === 'function') {
return props.getPopupContainer(node);
} else {
return node?.parentNode;
}
}
}
/**
* 过滤选项 - 禁用前端过滤,使用后端搜索
*/
function filterOption(_input: string, _option: any) {
return true; // 禁用前端过滤,完全依赖后端搜索
}
/**
* 确保容器唯一性的辅助函数
*/
function ensureUnique(Stocks: Stock[], newStock: Stock): Stock[] {
// 如果已经存在相同id的容器先移除旧的再添加新的
const filtered = Stocks.filter((Stock) => Stock.id !== newStock.id);
return [newStock, ...filtered];
}
/**
* 获取容器数据
*/
const queryData = async (page = 1, keyword = '', isSearch = false) => {
try {
loading.value = true;
const res = await defHttp.get<ResponseData>({
url: '/base/stock/list',
params: {
pageSize: props.pageSize,
pageNo: page,
keyword: keyword,
status: props.status,
izActive: props.izActive,
delFlag: props.delFlag,
izScan: props.izScan,
},
});
const records = res.records || [];
console.log('res', records)
if (page === 1 || isSearch) {
// 第一页或搜索时,重置数据
allStocks.value = records;
Options.value = records;
} else {
// 滚动加载时,追加数据(确保不重复)
const newRecords = records.filter((record) => !allStocks.value.some((Stock) => Stock.id === record.id));
allStocks.value = [...allStocks.value, ...newRecords];
Options.value = [...Options.value, ...newRecords];
}
// 修正分页判断逻辑
isHasData.value = records.length >= props.pageSize;
emit('optionsLoaded', allStocks.value);
} catch (error) {
if (page === 1) {
allStocks.value = [];
Options.value = [];
}
} finally {
loading.value = false;
scrollLoading.value = false;
}
};
async function queryDataById(value: string) {
try {
const res = await queryById({ id: value });
if (res) {
// 将查询到的单个容器信息添加到列表中,确保唯一性
const Stock = Array.isArray(res) ? res[0] : res;
if (Stock) {
// 使用ensureUnique确保容器不重复
allStocks.value = ensureUnique(allStocks.value, Stock);
Options.value = ensureUnique(Options.value, Stock);
}
return Stock;
}
} catch (error) {
console.error('查询容器失败:', error);
}
return null;
}
/**
* 根据选项值找到对应的选项对象
*/
function findOptionByValue(value: string): Stock | undefined {
if (props.returnValue === 'object' || props.returnValue === 'id') {
return allStocks.value.find((Stock) => Stock.id === value);
} else {
return allStocks.value.find((Stock) => Stock[props.returnValue as keyof Stock] === value);
}
}
/**
* 获取需要返回的值
*/
function getReturnValue(value: string | string[]) {
if (!value) {
return props.multiple ? [] : undefined;
}
// 如果返回整个对象
if (props.returnValue === 'object') {
if (Array.isArray(value)) {
return value.map((v) => findOptionByValue(v)).filter(Boolean);
} else {
return findOptionByValue(value);
}
}
// 如果返回ID默认情况
else if (props.returnValue === 'id') {
return value;
}
// 如果返回对象中的某个字段
else {
if (Array.isArray(value)) {
return value.map((v) => {
const option = findOptionByValue(v);
return option ? option[props.returnValue as keyof Stock] : v;
});
} else {
const option = findOptionByValue(value);
return option ? option[props.returnValue as keyof Stock] : value;
}
}
}
/**
* 搜索处理(防抖)
*/
const handleSearch = debounce(function (value: string) {
searchKeyword.value = value;
pageNo.value = 1;
isHasData.value = true;
// 直接调用API进行搜索
queryData(1, value, true);
}, 300);
/**
* 处理焦点事件
*/
function handleFocus() {
// 如果还没有数据,加载数据
if (allStocks.value.length === 0 && props.async) {
pageNo.value = 1;
isHasData.value = true;
queryData(1, '');
}
attrs.onFocus?.();
}
/**
* 处理值变化
*/
function handleChange(value: string | string[]) {
selectedValue.value = value;
// 根据配置返回相应的值
const returnValue = getReturnValue(value);
emit('update:value', returnValue);
emit('change', returnValue);
console.log('值变化:', returnValue);
}
/**
* 滚动加载处理
*/
function handlePopupScroll(e: Event) {
const target = e.target as HTMLElement;
const { scrollTop, scrollHeight, clientHeight } = target;
if (!scrollLoading.value && isHasData.value && scrollTop + clientHeight >= scrollHeight - 10) {
scrollLoading.value = true;
pageNo.value++;
queryData(pageNo.value, searchKeyword.value)
.finally(() => {
scrollLoading.value = false;
})
.catch(() => {
pageNo.value--;
});
}
}
/**
* 根据选中值初始化显示文本
*/
const initSelectValue = async () => {
if (!props.value) {
selectedValue.value = props.multiple ? [] : undefined;
return;
}
// 如果是异步模式且还没有加载数据,则先加载数据
if (props.async && allStocks.value.length === 0) {
await queryData();
}
// 根据不同的returnValue设置选中的值
let valueIds: string[] = [];
if (props.returnValue === 'object') {
// 如果返回的是对象value可能是对象或对象数组
if (Array.isArray(props.value)) {
valueIds = props.value.map((Stock: any) => Stock.id);
selectedValue.value = valueIds;
} else {
valueIds = [(props.value as any).id];
selectedValue.value = valueIds[0];
}
} else if (props.returnValue === 'id') {
if (Array.isArray(props.value)) {
valueIds = props.value as string[];
selectedValue.value = valueIds;
} else {
valueIds = [props.value as string];
selectedValue.value = valueIds[0];
}
} else {
// 对于其他字段类型,直接使用传入的值
selectedValue.value = props.value as string | string[];
// 这里需要根据字段值查找对应的ID
if (Array.isArray(props.value)) {
valueIds = props.value.map((v) => {
const option = allStocks.value.find((Stock) => Stock[props.returnValue as keyof Stock] === v);
return option ? option.id : v;
});
} else {
const option = allStocks.value.find((Stock) => Stock[props.returnValue as keyof Stock] === props.value);
valueIds = option ? [option.id] : [props.value as string];
}
}
// 检查是否有值不在当前列表中,如果有则查询
const missingIds = valueIds.filter((id) => !allStocks.value.some((Stock) => Stock.id === id));
if (missingIds.length > 0) {
// 对每个不在列表中的ID进行查询
const queryPromises = missingIds.map((id) => queryDataById(id));
await Promise.all(queryPromises);
}
};
// 监听value变化
watch(
() => props.value,
() => {
initSelectValue();
},
{ immediate: true }
);
// 组件挂载时初始化
onMounted(() => {
if (!props.async) {
queryData();
}
});
return {
attrs,
Options,
loading,
selectedValue,
notFoundContent,
getParentContainer,
filterOption,
handleChange,
handleSearch,
handleFocus,
getOptionLabel,
getOptionValue,
handlePopupScroll,
};
},
});
</script>
<style lang="less" scoped></style>