hookehuyr

refactor(person-picker): 调整为使用配置的单一人员类型搜索

适配更新后的人员搜索接口,移除不再需要的多类型筛选功能
更新接口文档注释,修正type参数说明并添加备注
移除顶部的类型筛选栏UI,清理未使用的计算属性和工具函数
调整人员记录映射和类型格式化逻辑,使用配置的人员类型作为搜索参数
修复人员类型缺失时的回退逻辑,优先使用当前字段配置的类型
......@@ -49,8 +49,9 @@ export const searchUserDeptRoleAPI = (params) => fn(fetch.get(Api.SEARCH_USER_DE
* @param: form_code 表单code
* @param: field_name 字段名
* @param: keyword 搜索关键字
* @param: type volunteer=义工、contact=联系人
* @param: type 人员类型编码,直接使用配置接口返回的 person_type,例如 contact / volunteer
* @param: page 页码,从0开始
* @param: limit 每页数量
* @note: 配置接口里的 person_type_title 仅用于前端中文展示,这个搜索接口不接收该字段
*/
export const searchPersonPickerAPI = (params) => fn(fetch.get(Api.SEARCH_PERSON_PICKER, params));
......
......@@ -54,16 +54,6 @@
</template>
</van-field>
</div>
<div v-if="typeFilterOptions.length > 1" class="type-filter-row">
<div
v-for="option in typeFilterOptions"
:key="option.value || 'all'"
:class="['type-filter-chip', { 'type-filter-chip--active': selectedTypeFilter === option.value }]"
@click="onSelectSearchType(option)"
>
{{ option.text }}
</div>
</div>
</div>
<div class="selected-box" style="margin: 1rem;">
......@@ -126,7 +116,7 @@
</template>
<script setup>
import { computed, inject, onMounted, ref } from 'vue';
import { inject, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useCustomFieldValue } from '@vant/use';
import Cookies from 'js-cookie';
......@@ -155,11 +145,28 @@ const fieldValue = ref([]);
const searchPage = ref(0);
const searchLimit = ref(Number(props.component_props.limit) || 20);
const searchTotal = ref(0);
const selectedTypeFilter = ref('');
/**
* 当前字段配置的人员类型编码,直接传给搜索接口的 type 参数。
*
* @returns {string}
*/
const getConfiguredPersonType = () => {
return String(props.component_props.person_type || '').trim();
};
/**
* 当前字段配置的人员类型中文名,用于已选项和结果列表展示。
*
* @returns {string}
*/
const getConfiguredPersonTypeTitle = () => {
return String(props.component_props.person_type_title || '').trim();
};
/**
* 将人员接口记录整理成组件内部统一结构。
* 这里直接按后端约定字段读取,只补齐展示时会用到的空字符串
* 这里直接按后端约定字段读取;如果历史数据里没带 type,就回退到当前字段配置的 person_type
*
* @param {Object} person 人员记录
* @returns {{ id: number, name: string, nickname: string, phone: string, idcard: string, type: string, is_blacklist: boolean }}
......@@ -171,7 +178,7 @@ const mapPersonRecord = (person) => {
nickname: person.nickname || '',
phone: person.phone || '',
idcard: person.idcard || '',
type: person.type || '',
type: String(person.type || getConfiguredPersonType()).trim(),
is_blacklist: normalizeBlacklistFlag(person.is_blacklist),
};
};
......@@ -204,24 +211,6 @@ const normalizeStoredPersonList = (value) => {
};
/**
* 后端返回的人员类型配置已经自带 title/value,这里只做结构清洗和按 value 去重。
*
* @param {Array} personTypes 人员类型配置
* @returns {{ title: string, value: string }[]}
*/
const normalizePersonTypes = (personTypes = []) => {
return _.uniqBy(
personTypes
.map((item) => ({
title: String(item?.title || '').trim(),
value: String(item?.value || '').trim(),
}))
.filter((item) => item.title && item.value),
'value',
);
};
/**
* 将已确认人员同步到自定义字段值,保证表单提交拿到的是最终确认结果。
*/
const syncFieldValue = () => {
......@@ -229,16 +218,15 @@ const syncFieldValue = () => {
props.value = fieldValue.value;
};
const findPersonTypeOption = (type) => {
if (!type) {
return null;
}
const formatPersonType = (type) => {
const configuredType = getConfiguredPersonType();
const configuredTypeTitle = getConfiguredPersonTypeTitle();
return allowedPersonTypes.value.find((item) => item.value === type) || null;
};
if (configuredTypeTitle && (!type || type === configuredType)) {
return configuredTypeTitle;
}
const formatPersonType = (type) => {
return findPersonTypeOption(type)?.title || type || '人员';
return type || configuredTypeTitle || '人员';
};
const formatPersonTitle = (person) => {
......@@ -253,44 +241,6 @@ const isPersonBlacklisted = (person) => {
};
/**
* 当前字段允许搜索的人员类型列表,直接取后端配置。
*/
const allowedPersonTypes = computed(() => {
return normalizePersonTypes(props.component_props.person_types);
});
/**
* 多类型字段默认停在“全部类型”,发请求时显式传空字符串给后端。
*/
const defaultSearchType = computed(() => {
return '';
});
/**
* 根据后端返回的 person_types 生成顶部筛选项。
*/
const typeFilterOptions = computed(() => {
if (!allowedPersonTypes.value.length) {
return [];
}
const allowedOptions = allowedPersonTypes.value.map((typeOption) => ({
text: typeOption.title,
value: typeOption.value,
}));
if (allowedOptions.length === 1) {
return allowedOptions;
}
return [{ text: '全部类型', value: '' }, ...allowedOptions];
});
const getSearchType = () => {
return selectedTypeFilter.value || defaultSearchType.value || '';
};
/**
* 根据当前草稿已选项回填搜索结果勾选状态,保证翻页后勾选表现一致。
*/
const syncSearchChecked = () => {
......@@ -384,14 +334,6 @@ const onClearSearch = () => {
resetSearchState();
};
const onSelectSearchType = async (action) => {
selectedTypeFilter.value = action.value;
if (searchKeyword.value.trim() && hasSearched.value) {
await onSearch();
}
};
/**
* 组装人员搜索接口参数。
*
......@@ -405,7 +347,7 @@ const buildSearchParams = (page) => {
keyword: searchKeyword.value.trim(),
page,
limit: searchLimit.value,
type: getSearchType(),
type: getConfiguredPersonType(),
};
if ($route.query.force_back) {
params.force_back = $route.query.force_back;
......@@ -430,7 +372,7 @@ const fetchSearchPage = async (page) => {
};
}
// 搜索结果以接口返回为准,前端不再额外按 person_types 二次过滤,避免把有效数据误筛掉
// 搜索结果以接口返回为准,当前字段只会请求配置里的单一 person_type
const remoteResults = result.data.map(mapPersonRecord);
return {
......@@ -532,7 +474,6 @@ useCustomFieldValue(() => fieldValue.value);
onMounted(() => {
confirmedPersons.value = normalizeStoredPersonList(props.component_props.default);
draftPersons.value = _.cloneDeep(confirmedPersons.value);
selectedTypeFilter.value = defaultSearchType.value || '';
syncFieldValue();
});
</script>
......