refactor(person-picker): 调整为使用配置的单一人员类型搜索
适配更新后的人员搜索接口,移除不再需要的多类型筛选功能 更新接口文档注释,修正type参数说明并添加备注 移除顶部的类型筛选栏UI,清理未使用的计算属性和工具函数 调整人员记录映射和类型格式化逻辑,使用配置的人员类型作为搜索参数 修复人员类型缺失时的回退逻辑,优先使用当前字段配置的类型
Showing
2 changed files
with
32 additions
and
90 deletions
| ... | @@ -49,8 +49,9 @@ export const searchUserDeptRoleAPI = (params) => fn(fetch.get(Api.SEARCH_USER_DE | ... | @@ -49,8 +49,9 @@ export const searchUserDeptRoleAPI = (params) => fn(fetch.get(Api.SEARCH_USER_DE |
| 49 | * @param: form_code 表单code | 49 | * @param: form_code 表单code |
| 50 | * @param: field_name 字段名 | 50 | * @param: field_name 字段名 |
| 51 | * @param: keyword 搜索关键字 | 51 | * @param: keyword 搜索关键字 |
| 52 | - * @param: type volunteer=义工、contact=联系人 | 52 | + * @param: type 人员类型编码,直接使用配置接口返回的 person_type,例如 contact / volunteer |
| 53 | * @param: page 页码,从0开始 | 53 | * @param: page 页码,从0开始 |
| 54 | * @param: limit 每页数量 | 54 | * @param: limit 每页数量 |
| 55 | + * @note: 配置接口里的 person_type_title 仅用于前端中文展示,这个搜索接口不接收该字段 | ||
| 55 | */ | 56 | */ |
| 56 | export const searchPersonPickerAPI = (params) => fn(fetch.get(Api.SEARCH_PERSON_PICKER, params)); | 57 | export const searchPersonPickerAPI = (params) => fn(fetch.get(Api.SEARCH_PERSON_PICKER, params)); | ... | ... |
| ... | @@ -54,16 +54,6 @@ | ... | @@ -54,16 +54,6 @@ |
| 54 | </template> | 54 | </template> |
| 55 | </van-field> | 55 | </van-field> |
| 56 | </div> | 56 | </div> |
| 57 | - <div v-if="typeFilterOptions.length > 1" class="type-filter-row"> | ||
| 58 | - <div | ||
| 59 | - v-for="option in typeFilterOptions" | ||
| 60 | - :key="option.value || 'all'" | ||
| 61 | - :class="['type-filter-chip', { 'type-filter-chip--active': selectedTypeFilter === option.value }]" | ||
| 62 | - @click="onSelectSearchType(option)" | ||
| 63 | - > | ||
| 64 | - {{ option.text }} | ||
| 65 | - </div> | ||
| 66 | - </div> | ||
| 67 | </div> | 57 | </div> |
| 68 | 58 | ||
| 69 | <div class="selected-box" style="margin: 1rem;"> | 59 | <div class="selected-box" style="margin: 1rem;"> |
| ... | @@ -126,7 +116,7 @@ | ... | @@ -126,7 +116,7 @@ |
| 126 | </template> | 116 | </template> |
| 127 | 117 | ||
| 128 | <script setup> | 118 | <script setup> |
| 129 | -import { computed, inject, onMounted, ref } from 'vue'; | 119 | +import { inject, onMounted, ref } from 'vue'; |
| 130 | import { useRoute } from 'vue-router'; | 120 | import { useRoute } from 'vue-router'; |
| 131 | import { useCustomFieldValue } from '@vant/use'; | 121 | import { useCustomFieldValue } from '@vant/use'; |
| 132 | import Cookies from 'js-cookie'; | 122 | import Cookies from 'js-cookie'; |
| ... | @@ -155,11 +145,28 @@ const fieldValue = ref([]); | ... | @@ -155,11 +145,28 @@ const fieldValue = ref([]); |
| 155 | const searchPage = ref(0); | 145 | const searchPage = ref(0); |
| 156 | const searchLimit = ref(Number(props.component_props.limit) || 20); | 146 | const searchLimit = ref(Number(props.component_props.limit) || 20); |
| 157 | const searchTotal = ref(0); | 147 | const searchTotal = ref(0); |
| 158 | -const selectedTypeFilter = ref(''); | 148 | + |
| 149 | +/** | ||
| 150 | + * 当前字段配置的人员类型编码,直接传给搜索接口的 type 参数。 | ||
| 151 | + * | ||
| 152 | + * @returns {string} | ||
| 153 | + */ | ||
| 154 | +const getConfiguredPersonType = () => { | ||
| 155 | + return String(props.component_props.person_type || '').trim(); | ||
| 156 | +}; | ||
| 157 | + | ||
| 158 | +/** | ||
| 159 | + * 当前字段配置的人员类型中文名,用于已选项和结果列表展示。 | ||
| 160 | + * | ||
| 161 | + * @returns {string} | ||
| 162 | + */ | ||
| 163 | +const getConfiguredPersonTypeTitle = () => { | ||
| 164 | + return String(props.component_props.person_type_title || '').trim(); | ||
| 165 | +}; | ||
| 159 | 166 | ||
| 160 | /** | 167 | /** |
| 161 | * 将人员接口记录整理成组件内部统一结构。 | 168 | * 将人员接口记录整理成组件内部统一结构。 |
| 162 | - * 这里直接按后端约定字段读取,只补齐展示时会用到的空字符串。 | 169 | + * 这里直接按后端约定字段读取;如果历史数据里没带 type,就回退到当前字段配置的 person_type。 |
| 163 | * | 170 | * |
| 164 | * @param {Object} person 人员记录 | 171 | * @param {Object} person 人员记录 |
| 165 | * @returns {{ id: number, name: string, nickname: string, phone: string, idcard: string, type: string, is_blacklist: boolean }} | 172 | * @returns {{ id: number, name: string, nickname: string, phone: string, idcard: string, type: string, is_blacklist: boolean }} |
| ... | @@ -171,7 +178,7 @@ const mapPersonRecord = (person) => { | ... | @@ -171,7 +178,7 @@ const mapPersonRecord = (person) => { |
| 171 | nickname: person.nickname || '', | 178 | nickname: person.nickname || '', |
| 172 | phone: person.phone || '', | 179 | phone: person.phone || '', |
| 173 | idcard: person.idcard || '', | 180 | idcard: person.idcard || '', |
| 174 | - type: person.type || '', | 181 | + type: String(person.type || getConfiguredPersonType()).trim(), |
| 175 | is_blacklist: normalizeBlacklistFlag(person.is_blacklist), | 182 | is_blacklist: normalizeBlacklistFlag(person.is_blacklist), |
| 176 | }; | 183 | }; |
| 177 | }; | 184 | }; |
| ... | @@ -204,24 +211,6 @@ const normalizeStoredPersonList = (value) => { | ... | @@ -204,24 +211,6 @@ const normalizeStoredPersonList = (value) => { |
| 204 | }; | 211 | }; |
| 205 | 212 | ||
| 206 | /** | 213 | /** |
| 207 | - * 后端返回的人员类型配置已经自带 title/value,这里只做结构清洗和按 value 去重。 | ||
| 208 | - * | ||
| 209 | - * @param {Array} personTypes 人员类型配置 | ||
| 210 | - * @returns {{ title: string, value: string }[]} | ||
| 211 | - */ | ||
| 212 | -const normalizePersonTypes = (personTypes = []) => { | ||
| 213 | - return _.uniqBy( | ||
| 214 | - personTypes | ||
| 215 | - .map((item) => ({ | ||
| 216 | - title: String(item?.title || '').trim(), | ||
| 217 | - value: String(item?.value || '').trim(), | ||
| 218 | - })) | ||
| 219 | - .filter((item) => item.title && item.value), | ||
| 220 | - 'value', | ||
| 221 | - ); | ||
| 222 | -}; | ||
| 223 | - | ||
| 224 | -/** | ||
| 225 | * 将已确认人员同步到自定义字段值,保证表单提交拿到的是最终确认结果。 | 214 | * 将已确认人员同步到自定义字段值,保证表单提交拿到的是最终确认结果。 |
| 226 | */ | 215 | */ |
| 227 | const syncFieldValue = () => { | 216 | const syncFieldValue = () => { |
| ... | @@ -229,16 +218,15 @@ const syncFieldValue = () => { | ... | @@ -229,16 +218,15 @@ const syncFieldValue = () => { |
| 229 | props.value = fieldValue.value; | 218 | props.value = fieldValue.value; |
| 230 | }; | 219 | }; |
| 231 | 220 | ||
| 232 | -const findPersonTypeOption = (type) => { | 221 | +const formatPersonType = (type) => { |
| 233 | - if (!type) { | 222 | + const configuredType = getConfiguredPersonType(); |
| 234 | - return null; | 223 | + const configuredTypeTitle = getConfiguredPersonTypeTitle(); |
| 235 | - } | ||
| 236 | 224 | ||
| 237 | - return allowedPersonTypes.value.find((item) => item.value === type) || null; | 225 | + if (configuredTypeTitle && (!type || type === configuredType)) { |
| 238 | -}; | 226 | + return configuredTypeTitle; |
| 227 | + } | ||
| 239 | 228 | ||
| 240 | -const formatPersonType = (type) => { | 229 | + return type || configuredTypeTitle || '人员'; |
| 241 | - return findPersonTypeOption(type)?.title || type || '人员'; | ||
| 242 | }; | 230 | }; |
| 243 | 231 | ||
| 244 | const formatPersonTitle = (person) => { | 232 | const formatPersonTitle = (person) => { |
| ... | @@ -253,44 +241,6 @@ const isPersonBlacklisted = (person) => { | ... | @@ -253,44 +241,6 @@ const isPersonBlacklisted = (person) => { |
| 253 | }; | 241 | }; |
| 254 | 242 | ||
| 255 | /** | 243 | /** |
| 256 | - * 当前字段允许搜索的人员类型列表,直接取后端配置。 | ||
| 257 | - */ | ||
| 258 | -const allowedPersonTypes = computed(() => { | ||
| 259 | - return normalizePersonTypes(props.component_props.person_types); | ||
| 260 | -}); | ||
| 261 | - | ||
| 262 | -/** | ||
| 263 | - * 多类型字段默认停在“全部类型”,发请求时显式传空字符串给后端。 | ||
| 264 | - */ | ||
| 265 | -const defaultSearchType = computed(() => { | ||
| 266 | - return ''; | ||
| 267 | -}); | ||
| 268 | - | ||
| 269 | -/** | ||
| 270 | - * 根据后端返回的 person_types 生成顶部筛选项。 | ||
| 271 | - */ | ||
| 272 | -const typeFilterOptions = computed(() => { | ||
| 273 | - if (!allowedPersonTypes.value.length) { | ||
| 274 | - return []; | ||
| 275 | - } | ||
| 276 | - | ||
| 277 | - const allowedOptions = allowedPersonTypes.value.map((typeOption) => ({ | ||
| 278 | - text: typeOption.title, | ||
| 279 | - value: typeOption.value, | ||
| 280 | - })); | ||
| 281 | - | ||
| 282 | - if (allowedOptions.length === 1) { | ||
| 283 | - return allowedOptions; | ||
| 284 | - } | ||
| 285 | - | ||
| 286 | - return [{ text: '全部类型', value: '' }, ...allowedOptions]; | ||
| 287 | -}); | ||
| 288 | - | ||
| 289 | -const getSearchType = () => { | ||
| 290 | - return selectedTypeFilter.value || defaultSearchType.value || ''; | ||
| 291 | -}; | ||
| 292 | - | ||
| 293 | -/** | ||
| 294 | * 根据当前草稿已选项回填搜索结果勾选状态,保证翻页后勾选表现一致。 | 244 | * 根据当前草稿已选项回填搜索结果勾选状态,保证翻页后勾选表现一致。 |
| 295 | */ | 245 | */ |
| 296 | const syncSearchChecked = () => { | 246 | const syncSearchChecked = () => { |
| ... | @@ -384,14 +334,6 @@ const onClearSearch = () => { | ... | @@ -384,14 +334,6 @@ const onClearSearch = () => { |
| 384 | resetSearchState(); | 334 | resetSearchState(); |
| 385 | }; | 335 | }; |
| 386 | 336 | ||
| 387 | -const onSelectSearchType = async (action) => { | ||
| 388 | - selectedTypeFilter.value = action.value; | ||
| 389 | - | ||
| 390 | - if (searchKeyword.value.trim() && hasSearched.value) { | ||
| 391 | - await onSearch(); | ||
| 392 | - } | ||
| 393 | -}; | ||
| 394 | - | ||
| 395 | /** | 337 | /** |
| 396 | * 组装人员搜索接口参数。 | 338 | * 组装人员搜索接口参数。 |
| 397 | * | 339 | * |
| ... | @@ -405,7 +347,7 @@ const buildSearchParams = (page) => { | ... | @@ -405,7 +347,7 @@ const buildSearchParams = (page) => { |
| 405 | keyword: searchKeyword.value.trim(), | 347 | keyword: searchKeyword.value.trim(), |
| 406 | page, | 348 | page, |
| 407 | limit: searchLimit.value, | 349 | limit: searchLimit.value, |
| 408 | - type: getSearchType(), | 350 | + type: getConfiguredPersonType(), |
| 409 | }; | 351 | }; |
| 410 | if ($route.query.force_back) { | 352 | if ($route.query.force_back) { |
| 411 | params.force_back = $route.query.force_back; | 353 | params.force_back = $route.query.force_back; |
| ... | @@ -430,7 +372,7 @@ const fetchSearchPage = async (page) => { | ... | @@ -430,7 +372,7 @@ const fetchSearchPage = async (page) => { |
| 430 | }; | 372 | }; |
| 431 | } | 373 | } |
| 432 | 374 | ||
| 433 | - // 搜索结果以接口返回为准,前端不再额外按 person_types 二次过滤,避免把有效数据误筛掉。 | 375 | + // 搜索结果以接口返回为准,当前字段只会请求配置里的单一 person_type。 |
| 434 | const remoteResults = result.data.map(mapPersonRecord); | 376 | const remoteResults = result.data.map(mapPersonRecord); |
| 435 | 377 | ||
| 436 | return { | 378 | return { |
| ... | @@ -532,7 +474,6 @@ useCustomFieldValue(() => fieldValue.value); | ... | @@ -532,7 +474,6 @@ useCustomFieldValue(() => fieldValue.value); |
| 532 | onMounted(() => { | 474 | onMounted(() => { |
| 533 | confirmedPersons.value = normalizeStoredPersonList(props.component_props.default); | 475 | confirmedPersons.value = normalizeStoredPersonList(props.component_props.default); |
| 534 | draftPersons.value = _.cloneDeep(confirmedPersons.value); | 476 | draftPersons.value = _.cloneDeep(confirmedPersons.value); |
| 535 | - selectedTypeFilter.value = defaultSearchType.value || ''; | ||
| 536 | syncFieldValue(); | 477 | syncFieldValue(); |
| 537 | }); | 478 | }); |
| 538 | </script> | 479 | </script> | ... | ... |
-
Please register or login to post a comment