功能:按资源配置平台列表搜索

This commit is contained in:
czl231
2026-08-11 22:22:36 +08:00
parent 8d6ffca1e2
commit 95d0f7dc54
19 changed files with 741 additions and 62 deletions

View File

@@ -14,9 +14,9 @@
请从详情页中的专用操作推进流程操作结果会按服务端状态校验并保留审计记录
</a-alert>
<div class="list-toolbar">
<a-form :model="filters" layout="inline" class="filters" @submit="search">
<a-form-item label="关键字">
<a-input v-model="filters.keyword" allow-clear placeholder="关键字段模糊搜索" />
<a-form v-if="searchEnabled" :model="filters" layout="inline" class="filters" @submit="search">
<a-form-item label="模糊搜索">
<a-input v-model="filters.keyword" allow-clear :placeholder="searchPlaceholder" />
</a-form-item>
<a-button type="primary" html-type="submit">查询</a-button>
<a-button @click="resetSearch">重置</a-button>
@@ -185,6 +185,7 @@ import {
protectedListAvatarDisplayName,
} from './protected-list-avatar-loader';
import { useResourceListExtras } from './use-resource-list-extras';
import { useResourceListSearch } from './use-resource-list-search';
const props = defineProps<{ definition: ResourceUiDefinition }>();
const route = useRoute();
@@ -196,9 +197,16 @@ const page = ref(Math.max(1, Number(route.query.page) || 1));
const pageSize = 50;
const total = ref(0);
const list = ref<ResourceRow[]>([]);
const filters = reactive({
keyword: typeof route.query.keyword === 'string' ? route.query.keyword : '',
});
const {
clearUnsupportedKeyword,
displayFields,
filters,
requestKeyword,
resetSearchState,
searchEnabled,
searchPlaceholder,
syncQuery,
} = useResourceListSearch(definitionRef, page);
const relations = useResourceRelations(() => ({
staffType: String(route.query.staff_type ?? route.meta.staffType ?? ''),
}));
@@ -244,20 +252,6 @@ const canArchive = computed(
props.definition.canArchive &&
(!requiresRoot.value || userStore.role === 'root'),
);
const displayFields = computed(() =>
props.definition.fields
.filter((field) => field.key !== 'identity' && field.type !== 'password')
.filter(
(field) =>
!(
props.definition.name === 'gas_basic' &&
['credit_code', 'address', 'longitude', 'latitude'].includes(
field.key,
)
),
)
.slice(0, 6),
);
const listTitle = computed(() => {
const owner = String(route.query.owner_name ?? '');
return owner
@@ -280,7 +274,8 @@ async function load() {
if (props.definition.name === 'delivery_account')
serverFilters.delivery_basic_identities = managedOwnerIdentity.value;
}
if (filters.keyword.trim()) serverFilters.keyword = filters.keyword.trim();
const keyword = requestKeyword();
if (keyword) serverFilters.keyword = keyword;
const result = await resourceApi.list<ResourceRow>(
props.definition.resource,
page.value,
@@ -423,23 +418,6 @@ function confirmArchive(row: ResourceRow) {
});
}
async function syncQuery() {
const query: Record<string, string> = {};
for (const key of [
'owner_identity',
'owner_name',
'relation_key',
'staff_type',
'return_to',
]) {
const value = route.query[key];
if (typeof value === 'string' && value) query[key] = value;
}
if (page.value > 1) query.page = String(page.value);
if (filters.keyword.trim()) query.keyword = filters.keyword.trim();
await router.replace({ query });
}
async function search() {
page.value = 1;
await syncQuery();
@@ -483,6 +461,7 @@ async function loadFieldOptions() {
}
onMounted(async () => {
await clearUnsupportedKeyword();
await Promise.all([
relations.preload(displayFields.value),
loadFieldOptions(),
@@ -493,8 +472,7 @@ onBeforeUnmount(avatarLoader.reset);
watch(
() => props.definition.resource,
async () => {
page.value = 1;
filters.keyword = '';
await resetSearchState();
await Promise.all([
relations.preload(displayFields.value),
loadFieldOptions(),

View File

@@ -4,9 +4,27 @@
*/
import { optionLabel } from '@/api/resource-display';
import type { ResourceRow } from '@/api/resource-page-rules';
import type { ResourceField } from '@/api/resources';
import type { ResourceField, ResourceUiDefinition } from '@/api/resources';
import { isProtectedListAvatarField } from './protected-list-avatar-loader';
/** 返回标准列表实际渲染的业务字段,搜索提示与表格共同复用该规则。 */
export function resourceListDisplayFields(
definition: ResourceUiDefinition,
): ResourceField[] {
return definition.fields
.filter((field) => field.key !== 'identity' && field.type !== 'password')
.filter(
(field) =>
!(
definition.name === 'gas_basic' &&
['credit_code', 'address', 'longitude', 'latitude'].includes(
field.key,
)
),
)
.slice(0, 6);
}
/** 读取关系列表字段的完整唯一标识。 */
export function identityFieldValue(field: ResourceField, row: ResourceRow) {
return String(row[field.key] ?? row[`${field.key}_masked`] ?? '');

View File

@@ -0,0 +1,89 @@
/**
* 功能描述:管理标准资源列表的可搜索字段、关键字状态和 URL 查询参数。
* 版本v1.0.0
*/
import { computed, reactive, type Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import type { ResourceUiDefinition } from '@/api/resources';
import { resourceListDisplayFields } from './resource-list-field-display';
/**
* 创建资源列表搜索状态。
* 参数definition 为当前资源定义page 为当前页码。
* 返回值:实际显示字段、搜索提示、请求关键字及 URL 同步方法。
*/
export function useResourceListSearch(
definition: Ref<ResourceUiDefinition>,
page: Ref<number>,
) {
const route = useRoute();
const router = useRouter();
const filters = reactive({
keyword: typeof route.query.keyword === 'string' ? route.query.keyword : '',
});
const displayFields = computed(() =>
resourceListDisplayFields(definition.value),
);
const searchableFields = computed(() => {
const searchableKeys = new Set(
definition.value.searchFields.map((field) => field.key),
);
return displayFields.value.filter((field) => searchableKeys.has(field.key));
});
const searchEnabled = computed(() => searchableFields.value.length > 0);
const searchPlaceholder = computed(
() =>
`可搜索:${searchableFields.value
.map((field) => field.listLabel ?? field.label)
.join('、')}`,
);
/** 仅在当前资源支持搜索时返回去除首尾空格的关键字。 */
function requestKeyword() {
return searchEnabled.value ? filters.keyword.trim() : '';
}
/** 将受支持的搜索状态写入 URL并移除无效或陈旧的 keyword。 */
async function syncQuery() {
const query: Record<string, string> = {};
for (const key of [
'owner_identity',
'owner_name',
'relation_key',
'staff_type',
'return_to',
]) {
const value = route.query[key];
if (typeof value === 'string' && value) query[key] = value;
}
if (page.value > 1) query.page = String(page.value);
const keyword = requestKeyword();
if (keyword) query.keyword = keyword;
await router.replace({ query });
}
/** 首次进入不支持搜索的页面时清除地址栏遗留关键字。 */
async function clearUnsupportedKeyword() {
if (searchEnabled.value || !route.query.keyword) return;
filters.keyword = '';
await syncQuery();
}
/** 切换资源时复位搜索状态,并同步移除旧资源关键字。 */
async function resetSearchState() {
page.value = 1;
filters.keyword = '';
if (route.query.keyword) await syncQuery();
}
return {
clearUnsupportedKeyword,
displayFields,
filters,
requestKeyword,
resetSearchState,
searchEnabled,
searchPlaceholder,
syncQuery,
};
}