feat(pageQuota): 采购产品页面增加供应商搜索功能

- 在采购产品页面添加供应商搜索组件
- 更新 API 调用以支持供应商名称搜索
- 优化供应商列表展示逻辑
- 调整每页显示数量为 50 条
- 更新本地测试环境 URL
This commit is contained in:
mkm 2025-01-02 11:18:21 +08:00
parent 85d4220002
commit c1f28d2184
8 changed files with 833 additions and 8 deletions

View File

@ -17,7 +17,7 @@ switch (env) {
WSS_URL = 'wss://ceshi-multi-store.lihaink.cn/pull'
break;
case 'local':
BASE_URL = 'http://192.168.1.7:8545';
BASE_URL = 'http://192.168.1.22:8545';
WSS_URL = 'wss://ceshi-multi-store.lihaink.cn/pull'
break;
default:

View File

@ -79,7 +79,7 @@
name: '',
cart_id: "",
page_no: 1,
page_size: 6
page_size: 50
})
//

View File

@ -48,11 +48,23 @@
<up-modal :show="show" title="采购确认" showCancelButton @cancel="show=false" @confirm="offerUpdate()">
<up-form labelPosition="left">
<up-form-item label="供应商" labelWidth='auto'>
<up-input @click="showSupplier = true">
<!-- <up-input @click="showSupplier = true">
<template #suffix>
<span>{{supplierText}}</span>
</template>
</up-input>
</up-input> -->
<next-search-select
:multiple="false"
:list="supplierList"
label-key="mer_name"
value-key="id"
placeholder=" 请选择供应商"
title="选择供应商"
v-model:value="formData.supplier_id"
@change="changeCallback"
@search="getSupplierList"
clearable
></next-search-select>
</up-form-item>
<up-form-item label="名称">
<up-input v-model="formData.store_name" border="none" disabled=""></up-input>
@ -162,17 +174,20 @@
goodsList1.value = res.data.lists
}
const supplierList = ref([])
const getSupplierList = () => {
supplierListApi().then(res => {
supplierList.value = [res.data.lists]
const getSupplierList = (val='') => {
supplierListApi({mer_name:val}).then(res => {
supplierList.value =res.data.lists
})
}
function changeCallback(item) {
console.log("选中的item", item)
}
const supplierConfirm = (e) => {
showSupplier.value = false
supplierText.value = e.value[0].mer_name
formData.value.supplier_id = e.value[0].id
}
const cancleOrder = (item) => {
const cancleOrder = (item) => {
columns.value[0].forEach(e => {
if (e['name_b'] == item['category_name']) {
pickerText.value = e['name']

View File

@ -0,0 +1,18 @@
## 1.0.82024-09-14
更新说明
## 1.0.72023-12-14
修复icon
## 1.0.62023-09-25
增加多选,多次搜索默认值选项回显功能
## 1.0.52023-08-15
修复小程序端兼容性问题
## 1.0.42023-08-15
增加props配置disabledKey
## 1.0.32023-08-14
增加visibleChange
## 1.0.22023-06-06
增加按需加载说明
## 1.0.12023-05-31
增加没有搜索到数据的提示
## 1.0.02023-05-31
发布next-search-select

View File

@ -0,0 +1,557 @@
<template>
<view class="main" @touchmove.stop.prevent="">
<view class="input" @click="showModal">
<view class="input-block">
<input
v-model="_value"
:style="disabled ? 'color:#c0c4cc' : ''"
:placeholder="placeholder"
placeholder-style="color: #c0c4cc;font-size: 32rpx;font-weight: none"
disabled
/>
</view>
<view v-if="showArrow"><text style="font-size: 28rpx;align-items: center;" class="icon">&#xe615;</text></view>
</view>
<view
class="select-modal"
:class="isShowModal ? 'show' : ''"
@tap="hideModal"
>
<view
class="select-dialog"
@tap.stop
:style="{ backgroundColor: bgColor }"
>
<view class="title-main">
<text class="title-detail">{{ title }}</text>
</view>
<view class="search-box" v-if="showSearch">
<input
class="search-input"
confirm-type="search"
v-model="searchInput"
placeholder="输入内容进行模糊查询"
placeholder-style="color:#c0c4cc;font-size: 32rpx;font-weight: none"
/>
<text v-if="showSearchBtn" class="search-text" @click="handleSearch">
搜索
</text>
</view>
<scroll-view :scroll-y="true" class="select-content">
<view
class="select-item"
v-for="(item, index) in list"
:key="index"
:id="`select-item-${index}`"
:style="[getItemStyle(item)]"
@click="select(item)"
>
<view class="title-block">{{ getLabelKeyValue(item) }}</view>
<text class="selectIcon icongou" v-if="valueIndexOf(item)"></text>
</view>
<view v-if="!list.length" class="no-data"><text>暂无数据</text></view>
</scroll-view>
<view class="select-bar bg-white" v-if="multiple">
<button
plain="true"
class="mini-btn action"
type="default"
size="mini"
@tap="empty"
>
{{ emptyText }}
</button>
<button
class="mini-btn action"
type="primary"
size="mini"
:style="{backgroundColor: selectColor}"
@tap="confirmClick"
>
{{ confirmText }}
</button>
</view>
</view>
</view>
</view>
</template>
<script>
import rightIconSrc from './right_icon.png';
export default {
name: 'NextSearchSelect',
data() {
return {
isShowModal: false,
searchInput: '',
options: [],
rightIconSrc,
localChecklist: []
}
},
props: {
showSearch: {
//
type: Boolean,
default: true,
},
value: {
type: [Number, String, Array, Object],
default: null,
},
placeholder: {
//
default: '',
type: String,
},
multiple: {
//
default: false,
type: Boolean,
},
list: {
default: () => [],
type: Array,
},
valueKey: {
// listvalueKey
default: 'value',
type: String,
},
labelKey: {
// listlabelKey
default: 'label',
type: String,
},
// listlabelKeydisabled
disabledKey: {
default: 'disabled',
type: String,
},
disabled: {
default: false,
type: Boolean,
},
emptyText: {
default: '重置',
type: String,
},
title: {
default: '选择内容',
type: String,
},
confirmText: {
default: '确定',
type: String,
},
color: {
default: '#000000',
type: String,
},
selectColor: {
default: '#f9ae3d',
type: String,
},
bgColor: {
default: '#ffffff',
type: String,
},
selectBgColor: {
default: '#FFFFFF',
type: String,
},
showSearchBtn: {
default: true,
type: Boolean, // single || all
},
showArrow: {
type: Boolean,
default: true,
},
},
emits: ['openDeepScroll', 'closeDeepScroll'],
computed: {
_value: {
get() {
return this.get_value(this.value)
},
set(val) {
this.$emit('input', val)
},
}
},
created() {
this.$watch(() => this.searchInput, (val) => {
if (!this.showSearchBtn) this.$emit('search', val)
})
},
methods: {
handleSearch() {
this.$emit('search', this.searchInput)
},
getItemStyle(item) {
const disabledColor = this.disabledKey && item[this.disabledKey] === true ? '#dedede' : ''
const color = disabledColor ? disabledColor : this.valueIndexOf(item) ? this.selectColor : this.color
const backgroundColor = this.valueIndexOf(item) ? this.selectBgColor : undefined
return {
color,
backgroundColor
}
},
get_value(val) {
// ,
if (val || val === 0) {
if (Array.isArray(val)) {
let chooseAttr = []
val.forEach((item) => {
const list = this.options.concat(this.list)
let choose = list.find((temp) => {
let val_val = this.getValueKeyValue(temp)
return item === val_val
})
let chooseLocal = this.localChecklist.find((temp) => {
let val_val = this.getValueKeyValue(temp)
return item === val_val
})
//
if (choose) {
chooseAttr.push(choose)
const bool = this.localChecklist.some((temp) => {
return this.getValueKeyValue(choose) === this.getValueKeyValue(temp)
})
if(!bool) {
this.localChecklist.push(choose)
}
} else if(chooseLocal) {
chooseAttr.push(chooseLocal)
}
})
let values = ''
if (chooseAttr.length > 0) {
values = chooseAttr
.map((temp) => this.getLabelKeyValue(temp))
.join(',')
}
return values
} else {
let choose = this.list.find((temp) => {
let val_val = this.getValueKeyValue(temp)
return val === val_val
})
if (choose) {
return this.getLabelKeyValue(choose)
} else {
return val
}
}
} else {
return ''
}
},
select(item) {
//
if (this.disabledKey && item[this.disabledKey] === true) return
let val = this.getValueKeyValue(item)
if (this.multiple) {
const o = this.localChecklist.find(it => it[this.valueKey] === item[this.valueKey])
if(!o) {
this.localChecklist.push(item)
}
let _value = this.value
let index = _value ? _value.indexOf(val) : -1
if (index != -1) {
_value.splice(index, 1)
this.options.splice(index, 1)
// this.$emit('input', _value)
} else {
_value.push(val)
this.options.push(item)
// this.$emit('input', _value)
}
// #ifdef VUE2
this.$emit('input', _value)
// #endif
// #ifdef VUE3
this.$emit('update:value', _value)
// #endif
this.$emit('change', item)
} else {
let label = this.getLabelKeyValue(item)
let emitValue = ''
if (this._value) {
if (label.indexOf(this._value) !== -1) {
emitValue = ''
} else {
emitValue = val
}
} else {
emitValue = val
}
// #ifdef VUE2
this.$emit('input', emitValue)
// #endif
// #ifdef VUE3
this.$emit('update:value', emitValue)
// #endif
//
this.$emit('change', item)
this.hideModal()
}
},
valueIndexOf(item) {
let val = this.getValueKeyValue(item)
if (Array.isArray(this.value)) {
return this.value.indexOf(val) != -1
} else {
return this.value === val
}
},
getLabelKeyValue(item) {
// label
return item[this.labelKey] || '--'
},
getValueKeyValue(item) {
// value
return item[this.valueKey]
},
empty() {
//
if (this.multiple) {
this.$emit('change', [])
// #ifdef VUE2
this.$emit('input', [])
// #endif
// #ifdef VUE3
this.$emit('update:value', [])
// #endif
} else {
this.$emit('change', '')
// #ifdef VUE2
this.$emit('input', '')
// #endif
// #ifdef VUE3
this.$emit('update:value', '')
// #endif
}
},
confirmClick() {
//
this.$emit('confirm', this._value)
this.hideModal()
},
showModal() {
// model
if (!this.disabled) {
this.isShowModal = true
// 穿
this.$emit('openDeepScroll')
this.$emit('visibleChange', true)
}
},
hideModal() {
// model
this.isShowModal = false
// 穿
this.$emit('closeDeepScroll')
this.$emit('visibleChange', false)
},
}
}
</script>
<style lang="scss" scoped>
@font-face {
font-family: 'iconfont';
src: url('//at.alicdn.com/t/c/font_4110624_3hfahswu4mf.ttf?t=1695353456719') format('truetype');
}
.icon {
font-family: iconfont;
font-size: 32upx;
font-style: normal;
color: #999;
}
.title-main {
display: flex;
justify-content: center;
width: 100%;
}
.title-detail {
display: flex;
width: 88%;
justify-content: center;
padding: 30rpx 0;
}
.selectIcon {
font-family: 'iconfont' !important;
font-size: 18px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icongou:before {
content: '\e6cf';
}
</style>
<style lang="scss" scoped>
.main {
font-size: 32rpx;
}
.bg-white {
background-color: #ffffff;
}
.input {
display: flex;
align-items: center;
.input-block {
flex: 1;
input {
flex: 1;
text-align: right;
color: #333333;
}
}
}
.select-modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
opacity: 0;
outline: 0;
text-align: center;
-ms-transform: scale(1.185);
transform: scale(1.185);
backface-visibility: hidden;
perspective: 2000rpx;
background: rgba(0, 0, 0, 0.6);
transition: all 0.3s ease-in-out 0s;
pointer-events: none;
margin-bottom: -1000rpx;
&::before {
content: '\200B';
display: inline-block;
height: 100%;
vertical-align: bottom;
}
.select-dialog {
position: absolute;
left: 0;
bottom: 0;
display: inline-block;
margin-left: auto;
margin-right: auto;
background-color: #f8f8f8;
overflow: hidden;
width: 100%;
border-radius: 0;
.select-content {
// background-color: #F1F1F1;
height: 50vh;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
.select-item {
text-align: left;
padding: 20rpx 40rpx;
display: flex;
position: relative;
::after {
position: absolute;
right: 0;
bottom: 0;
content: '';
width: 100%;
height: 1px;
display: block;
margin: 0 auto;
border-bottom: 1px solid #f5f2f2;
}
.title-block {
flex: 1;
}
}
}
.no-data {
text-align: center;
padding: 30rpx;
color: #cccccc;
}
}
}
.select-modal.show {
opacity: 1;
transition-duration: 0.3s;
-ms-transform: scale(1);
transform: scale(1);
overflow-x: hidden;
overflow-y: auto;
pointer-events: auto;
margin-bottom: 0;
}
.select-bar {
padding: 0 80rpx;
display: flex;
position: relative;
align-items: center;
min-height: 80rpx;
justify-content: space-between;
margin: 25rpx 0;
.action {
display: flex;
align-items: center;
height: 78rpx;
justify-content: center;
max-width: 100%;
padding: 0 100rpx;
}
}
.search-box {
display: flex;
margin: 10rpx 0;
align-items: center;
padding: 10rpx 40rpx;
}
.search-input {
display: flex;
flex: 1;
height: 67rpx;
line-height: 67rpx;
border-radius: 40rpx;
background: #f5f2f2;
justify-content: flex-start;
text-align: left;
padding: 0 30rpx;
}
.search-text {
padding-left: 30rpx;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View File

@ -0,0 +1,80 @@
{
"id": "next-search-select",
"displayName": "next-search-select-搜索框组件全端可用",
"version": "1.0.8",
"description": "简单好用的带搜索功能select组件支持多种展示形式可自定义placeholder远程搜索多选单选",
"keywords": [
"远程搜索",
"多选",
"单选",
"search",
"api搜索"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.1"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "n",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "n",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "n",
"联盟": "n"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,155 @@
## next-search-select --下拉多选,单选,搜索
> 遇到问题或有建议可以加入QQ群(<font color=#f00>455948571</font>)反馈
> 如果觉得组件不错,<font color=#f00>给五星鼓励鼓励</font>咯!
### 如果有使用问题请加群
注意如果插件问题请务必给一个完整的复现demo谢谢配合
[点击链接加入群聊前端开发uniapp插件](https://qm.qq.com/q/S1bJzQfJAG)
## 使用
>[从uniapp插件市场导入](https://ext.dcloud.net.cn/plugin?name=next-search-select)
```html
<template>
<view class="title"><text>1. 单选模式:</text></view>
<view class="item">
<next-search-select
:multiple="false"
:list="options"
label-key="projectName"
value-key="id"
placeholder=" 请选择报备项目"
title="选择报备项目"
v-model:value="projectId"
@search="searchFunc"
@change="changeCallback"
clearable
></next-search-select>
</view>
<view class="title"><text>2. 多选模式:</text></view>
<view class="item">
<next-search-select
:multiple="true"
:list="options"
label-key="projectName"
value-key="id"
placeholder=" 请选择报备项目"
title="选择报备项目"
v-model:value="projectIds"
@search="searchFunc"
@change="changeCallback"
clearable
></next-search-select>
</view>
</template>
```
### vue3 + ts 使用
```ts
<script setup lang="ts">
import {ref, unref} from "vue"
const options = ref<any>([])
// 单选模式下绑定的值是字符串
const projectId = ref("")
// 多选模式值必须是数值类型
const projectIds = ref([])
let dataLength = 0
function searchFunc(val?) {
console.log("搜索的关键字:", val)
uni.showLoading({
title: '请稍后...',
icon: 'none'
})
// 模拟ajax请求
setTimeout(() => {
options.value = []
dataLength = 0
if (dataLength < 40) {
for (let i = 0; i < 40; i++) {
options.value.push({
id: `id-${val ? val + '-' : ''}${dataLength + i}`,
projectName: `项目item-${val ? val + '-' : ''}${dataLength + i}`,
ohterKey: `test-${i}`,
disabled: i%2 === 0
})
}
dataLength = unref(options).length
}
uni.hideLoading()
}, 1000)
}
function changeCallback(item) {
console.log("选中的item", item)
}
searchFunc()
</script>
<style lang="scss">
.title {
color: #f9ae3d;
font-size: 30rpx;
padding: 20rpx 10rpx;
background-color: #333;
}
.item {
padding: 10rpx 20rpx;
border: 1rpx solid #ccc;
}
</style>
```
### vue2 同样支持在这里不再写demo
### 组件按需加载
如果不需要组件全局加载而已把组件拷贝到项目的components目录下单独引入进来使用即可达到按需加载的效果
### 预览
***
| 功能预览 |
| :--------------------------------------------------------------------------:|
| ![](https://lixueshiaa.github.io/webtest/www/static/next-search-select.gif) |
## 参数
### next-search-select Props
可选参数属性列表
|参数名 |说明 |类型 |是否必填 |默认值 |可选值 |
|---- |---- |---- |---- |---- |---- |
|showSearch |是否显示搜索框 |Boolean |否 |true |false |
|value |v-model v-model:value 绑定值 |Number, String, Array, Object |是 |"" |- |
|placeholder |搜索框placeholder |String |否 |"" |- |
|multiple |是否多选 |Boolean |否 |false |true |
|list | 列表值 |Array |是 |[] |- |
|valueKey |list列表绑定的value关键属性key |String |否 |value |- |
|labelKey |list列表绑定的label(显示)关键属性key |String |否 |label |- |
|disabledKey |list列表绑定的disabled(显示)关键属性key |String |否 |disabled |- |
|disabled| disabled |Boolean |否 |false |true |
|emptyText |重置按钮文本text |String |否 |重置 |- |
|title |弹层标题 |String |否 |选择内容 |- |
|confirmText |确定按钮文本text |String |否 |确定 |- |
|color |文字颜色 |String |否 |#000000 |- |
|selectColor |激活颜色 |String |否 |#f9ae3d |- |
|bgColor |弹层背景颜色 |String |否 |#ffffff |- |
|selectBgColor |激活项背景颜色 |String |否 |#ffffff |- |
|showSearchBtn |是否显示搜索按钮 |Boolean |否 |true |false |
|showArrow |是否显示右指示箭头 |Boolean |否 |true |false |
# Event 事件
|事件名 |说明 |类型 |回调参数 |
|---- |---- |---- |---- |
|confirm|点击确定触发事件 |emit |- |
|change|change |emit |- |
|visibleChange|弹层改变触发 |emit |- |