Files
cashier_app/entryManager/add/components/bankBranchList.vue
2026-01-09 18:52:40 +08:00

292 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view>
<view class="box" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16" v-if="!modelValue">请选择</text>
<text class="u-font-28 color-333 u-p-r-16" v-else>{{returnLabel()}}</text>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup :show="show" placement="bottom" round="18rpx" closeOnClickOverlay @close="close">
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择银行</view>
<view class="u-m-t-24">
<up-search v-model="keywords" @search="search" @change="search" @custom="search"
@clear="search"></up-search>
</view>
<scroll-view scroll-with-animation :scroll-into-view="selid" class="scroll-view u-m-t-30"
scroll-y="true" style="max-height :60vh;">
<template v-if="list.length">
<view class="u-m-b-10 u-flex item" v-for="item in list" :key="item.bankCode"
@click="itemClick(item)" :id="'shop_'+item.bankCode"
:class="{active:selItem&&selItem.bankCode==item.bankCode}">
<view class="checkbox">
<up-icon name="checkbox-mark" color="#fff"></up-icon>
</view>
<view class="u-flex-1">{{item.branchName}}</view>
</view>
<view class="u-p-20 u-flex u-row-center">
<up-loadmore status="nomore"></up-loadmore>
</view>
</template>
<template v-else>
<template v-if="keywords">
<up-empty text="未搜索到相关支行"></up-empty>
</template>
<up-empty v-else text="暂无支行"></up-empty>
</template>
</scroll-view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import {
computed,
onMounted,
reactive,
ref,
watch
} from 'vue';
import {
bankBranchList
} from '@/http/api/order/entryManager.js';
const customStyle = ref({
marginRight: '20px'
});
const show = ref(false);
const modelValue = defineModel();
const bankBranchName = defineModel('bankBranchName', {
default: '',
});
const selid = ref('')
function returnLabel() {
const findShop = list.value.find(v => v.bankCode == modelValue.value)
return findShop ? findShop.branchName : ''
}
const selItem = ref(null)
function itemClick(data) {
selItem.value = data
}
function returnbranchName(bankCode) {
const item = list.value.find((v) => v.bankCode == bankCode);
return item?.branchName || '';
}
function close() {
show.value = false;
}
function submit() {
modelValue.value = selItem.value.bankCode
bankBranchName.value = selItem.value.branchName
show.value = false;
}
const keywords = ref('')
function search() {
list.value = matchStr(allList, keywords.value)
}
/**
* 模糊匹配对象数组中每个对象的全部属性,按匹配度由高到低排序返回,未匹配到的对象直接过滤(不返回)
* @param {Array<Object>} arr - 待匹配的对象数组(必传,非数组返回空数组)
* @param {string} str - 待匹配的目标字符串(空字符串返回原数组浅拷贝)
* @returns {Array<Object>} 按匹配度降序排序后的对象数组(仅包含匹配项,不修改原数组)
*/
function matchStr(arr, str) {
// ------------- 步骤1严格边界处理避免运行报错 -------------
if (!Array.isArray(arr)) {
console.warn('入参arr必须是对象数组');
return [];
}
const targetStr = String(str || '').trim().toLowerCase();
if (!targetStr) {
return [...arr]; // 空字符串返回原数组浅拷贝(保持原有逻辑)
}
// ------------- 步骤2定义匹配度得分规则 -------------
const getSinglePropScore = (propValue) => {
const propStr = String(propValue).trim().toLowerCase();
if (propStr === targetStr) return 3;
if (propStr.startsWith(targetStr)) return 2;
if (propStr.includes(targetStr)) return 1;
return 0;
};
// ------------- 步骤3遍历数组计算每个对象的总匹配度得分 -------------
const arrWithScore = arr.map(item => {
if (typeof item !== 'object' || item === null) {
return {
originItem: item,
totalScore: 0
};
}
let totalScore = 0;
Object.keys(item).forEach(propKey => {
const propValue = item[propKey];
totalScore += getSinglePropScore(propValue);
});
return {
originItem: item,
totalScore: totalScore
};
});
// ------------- 步骤4先过滤仅保留得分>0的项再排序最后返回原对象格式 -------------
return arrWithScore
.filter(item => item.totalScore > 0) // 核心新增:过滤未匹配项(总得分=0的对象不保留
.sort((a, b) => b.totalScore - a.totalScore) // 仅对匹配项进行降序排序
.map(item => item.originItem); // 剔除得分字段,返回原对象格式
}
const list = ref([]);
function openPopup() {
selid.value = 'shop_' + modelValue.value
const findShop = list.value.find(v => v.bankCode == modelValue.value)
selItem.value=findShop?findShop:null
show.value = true;
}
// --------------- 核心新增:节流函数实现 ---------------
/**
* 节流函数:限制函数在指定时间内只触发一次
* @param {Function} fn - 要节流的函数
* @param {number} delay - 节流延迟时间(毫秒)
* @returns {Function} 节流后的函数
*/
function throttle(fn, delay = 300) {
let timer = null; // 定时器标识
return function(...args) {
// 如果已有定时器,直接返回(未到触发时间)
if (timer) return;
// 执行函数并设置新定时器
fn.apply(this, args);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
}, delay);
};
}
// --------------- 核心修改创建节流后的search方法 ---------------
// 300ms内只触发一次search可根据需求调整delay值如500ms
const throttledSearch = throttle(search, 300);
// --------------- 改造bankNameChange调用节流后的搜索 ---------------
function bankNameChange() {
// 输入变化时,调用节流后的搜索方法
throttledSearch();
}
const props = defineProps({
query: {
type: Object,
default: () => ({
province: '',
city: '',
instId: '',
})
}
})
watch(() => show.value, (newval) => {
init()
})
let allList = []
async function init() {
const res = await bankBranchList(props.query);
list.value = res
allList = res
}
</script>
<style lang="scss">
.box {
border-radius: 8upx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 20rpx 40rpx 20rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
}
.shop-item {
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
}
.scroll-view {
.item {
border: 1px solid #eee;
padding: 20rpx;
border-radius: 12rpx;
&.active {
border-color: $my-main-color;
}
}
}
.checkbox {
margin-right: 10rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 6rpx;
border: 1px solid #999;
}
.item {
&.active {
.checkbox {
background-color: $my-main-color;
border-color: $my-main-color;
}
}
}
</style>