20260810172235

This commit is contained in:
oneao committed 2026-08-10 17:22:36 +08:00
1 parent c4891538a7
commit ed5ab3885d
35 files changed
+1314 -653

No files matched your search

@@ -0,0 +1,174 @@
import { ref, watch } from "vue";
import { useDebounceFn } from "@vueuse/core";
import { pageDataApi } from "@/services/api";
export interface LookupOptionsConfig {
/** 后端表名/视图名(必填) */
viewName: string;
/** 显示文本字段名,默认 "b_name" */
labelKey?: string;
/** 取值字段名,默认 "b_id" */
valueKey?: string;
/** 额外的固定查询条件 */
searchCondition?: string;
/** 排序规则 */
orderBy?: string;
/** 需要返回的字段列表 */
searchColumns?: string[];
/** 每页加载数量,默认 20 */
pageSize?: number;
/** 搜索防抖延迟(ms),默认 300 */
searchDelay?: number;
}
export function useLookupOptions(config: LookupOptionsConfig) {
const {
viewName,
labelKey = "b_name",
valueKey = "b_id",
searchCondition = "",
orderBy = "",
searchColumns = [],
pageSize = 20,
searchDelay = 300,
} = config;
const rows = ref<Record<string, any>[]>([]);
const loading = ref(false);
const total = ref(0);
const keyword = ref("");
// Keep the unfiltered first page available while a later open is refreshing.
const defaultRows = ref<Record<string, any>[]>([]);
const defaultTotal = ref(0);
let requestVersion = 0;
let currentPage = 1;
function buildSearchCondition() {
const k = keyword.value.trim();
if (!k) return searchCondition;
const escaped = k.replaceAll("'", "''");
const like = `${labelKey} LIKE '%${escaped}%'`;
return searchCondition ? `${searchCondition} AND ${like}` : like;
}
async function load(reset = false, preserveRows = false) {
if (reset) {
currentPage = 1;
if (!preserveRows) {
rows.value = [];
total.value = 0;
}
}
const version = ++requestVersion;
loading.value = true;
const requestedKeyword = keyword.value.trim();
try {
const condition = buildSearchCondition();
const result = await pageDataApi({
viewName,
orderBy: orderBy || `${labelKey} ASC`,
searchColumns,
searchCondition: condition,
pageNo: currentPage,
pageSize,
});
if (version !== requestVersion) return;
const data = result.data;
if (reset) {
rows.value = data.rows;
} else {
rows.value.push(...data.rows);
}
total.value = data.total;
if (!requestedKeyword) {
defaultRows.value = [...rows.value];
defaultTotal.value = total.value;
}
} finally {
if (version === requestVersion) {
loading.value = false;
}
}
}
/**
* 搜索:防抖后重置并加载首页
*/
const search = useDebounceFn((q: string) => {
keyword.value = q;
load(true);
}, searchDelay);
/**
* 立即搜索(focus/打开时使用,不防抖)
*/
function immediate(q: string) {
keyword.value = q;
if (!q && defaultRows.value.length) {
rows.value = [...defaultRows.value];
total.value = defaultTotal.value;
load(true, true);
return;
}
load(true);
}
/**
* 打开下拉:若尚无数据则首页加载
*/
function open() {
if (!rows.value.length) {
load(true);
}
}
/**
* 滚动触底时加载下一页
*/
function loadMore() {
if (loading.value) return;
if (rows.value.length >= total.value) return;
currentPage++;
load(false);
}
/**
* 重置:清空数据、关键词、分页
*/
function reset(clearCache = false) {
requestVersion++;
rows.value = [];
total.value = 0;
keyword.value = "";
currentPage = 1;
loading.value = false;
if (clearCache) {
defaultRows.value = [];
defaultTotal.value = 0;
}
}
// 关键 props 变化时自动重置
watch(
() => [viewName, labelKey, valueKey, orderBy, searchCondition, searchColumns],
() => reset(true),
);
return {
rows,
loading,
total,
keyword,
search,
immediate,
open,
loadMore,
reset,
};
}