479 lines
10 KiB
Vue
479 lines
10 KiB
Vue
<template>
|
|
<view class="product-select-page">
|
|
<!-- 固定顶部区域 -->
|
|
<view class="fixed-top" :style="{ paddingTop: statusBarHeight + 'px' }">
|
|
<view class="page-header" :style="{ height: navBarHeight + 'px' }">
|
|
<view class="header-title">选择商品</view>
|
|
</view>
|
|
<view class="search-row">
|
|
<view class="search-box">
|
|
<image :src="BASE_IMG_URL + 'search.png'" class="search-icon" mode=""></image>
|
|
<input type="text" placeholder="输入产品编号/名称搜索" @input="queryList" class="search-input" placeholder-style="color:#999;">
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="main-content" :style="{ marginTop: fixedTopHeight + 'px' }">
|
|
<!-- 左侧分类 -->
|
|
<scroll-view scroll-y class="category-list">
|
|
<view
|
|
class="category-item"
|
|
:class="{ active: typeCurrent === index }"
|
|
v-for="(item, index) in typeList"
|
|
:key="index"
|
|
@click="changeType(index)"
|
|
>
|
|
{{ item.name }}
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- 右侧商品列表 -->
|
|
<scroll-view scroll-y class="product-list">
|
|
<view class="product-items">
|
|
<view class="product-item" v-for="(item, index) in list" :key="index" @tap.stop="changeSelect(index)">
|
|
<view class="product-top">
|
|
<view class="product-name">{{ item.name }}</view>
|
|
<view class="product-num">{{ item.num }}</view>
|
|
</view>
|
|
<view class="product-info">
|
|
<view class="product-radio">
|
|
<radio color="#008EFF" :checked="item.select"></radio>
|
|
</view>
|
|
<image :src="item.img ? item.img : BASE_IMG_URL + 'img/index-4.png'" class="product-img" mode="aspectFill"></image>
|
|
<view class="product-detail">
|
|
<view class="detail-text">单位:{{ item.unit }}</view>
|
|
<view class="detail-text">零售价:¥{{ item.price }}</view>
|
|
<view class="detail-text">批发价:¥{{ item.wholesale }}</view>
|
|
<view class="number-box" @tap.stop.prevent>
|
|
数量
|
|
<view class="number-input">
|
|
<uni-number-box :min="1" v-model="item.number" @change="(e) => { changeNumber(e, index) }"></uni-number-box>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<u-empty v-if="list.length === 0" text="暂无商品"></u-empty>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
|
|
<!-- 底部操作栏 -->
|
|
<view class="bottom-bar">
|
|
<view class="left-box">
|
|
<view class="select-all" @click="selectAll">
|
|
<radio color="#008EFF" :checked="isAll"></radio>全选
|
|
</view>
|
|
<view class="select-count">已选{{ selectArr.length }}项</view>
|
|
</view>
|
|
<view class="right-box">
|
|
<view class="btn-confirm" @click="confirmSelect">确认选择</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 底部导航 -->
|
|
<uniTabbar></uniTabbar>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { netContractProduct, netProductTypeList } from '@/api/kehu.js'
|
|
import { BASE_IMG_URL } from '@/util/api.js'
|
|
import uniTabbar from '@/components/tabbar/tabbar.vue'
|
|
|
|
export default {
|
|
components: {
|
|
uniTabbar
|
|
},
|
|
data() {
|
|
return {
|
|
BASE_IMG_URL: BASE_IMG_URL,
|
|
name: '',
|
|
list: [],
|
|
isAll: false,
|
|
selectArr: [],
|
|
typeList: [],
|
|
typeCurrent: 0,
|
|
typeId: '',
|
|
selectedMap: {},
|
|
statusBarHeight: 0,
|
|
navBarHeight: 44,
|
|
fixedTopHeight: 0
|
|
}
|
|
},
|
|
onLoad() {
|
|
this.getSystemInfo()
|
|
this.init()
|
|
},
|
|
methods: {
|
|
getSystemInfo() {
|
|
const systemInfo = uni.getSystemInfoSync()
|
|
this.statusBarHeight = systemInfo.statusBarHeight || 0
|
|
|
|
// #ifdef MP-WEIXIN
|
|
const menuButton = uni.getMenuButtonBoundingClientRect()
|
|
this.navBarHeight = (menuButton.top - this.statusBarHeight) * 2 + menuButton.height
|
|
// #endif
|
|
|
|
// #ifndef MP-WEIXIN
|
|
this.navBarHeight = 44
|
|
// #endif
|
|
|
|
// 计算固定头部总高度:状态栏 + 导航栏 + 搜索行(约54px)
|
|
this.fixedTopHeight = this.statusBarHeight + this.navBarHeight + 54
|
|
},
|
|
async init() {
|
|
this.list = []
|
|
this.name = ''
|
|
this.isAll = false
|
|
this.selectArr = []
|
|
this.selectedMap = {}
|
|
this.typeId = ''
|
|
this.typeCurrent = 0
|
|
await this.getTypeList()
|
|
this.getProductList()
|
|
},
|
|
queryList(e) {
|
|
this.name = e.detail.value
|
|
this.getProductList()
|
|
},
|
|
getTypeList() {
|
|
return netProductTypeList().then(res => {
|
|
const data = res.data || []
|
|
const tabs = [{ id: '', name: '全部' }].concat(data.map(item => ({
|
|
...item,
|
|
name: item.name || item.title || item.cate_name || item.label || item.text
|
|
})))
|
|
this.typeList = tabs
|
|
})
|
|
},
|
|
getProductList() {
|
|
let params = {
|
|
name: this.name,
|
|
type_id: this.typeId
|
|
}
|
|
netContractProduct(params).then(res => {
|
|
let arr = res.data
|
|
arr.forEach(ele => {
|
|
const saved = this.selectedMap[ele.id]
|
|
if (saved) {
|
|
ele.select = true
|
|
ele.number = saved.number || 1
|
|
this.selectedMap[ele.id] = { ...ele }
|
|
} else {
|
|
ele.number = 1
|
|
ele.select = false
|
|
}
|
|
})
|
|
this.list = arr
|
|
this.refreshSelectedState()
|
|
})
|
|
},
|
|
changeType(index) {
|
|
this.typeCurrent = index
|
|
const current = this.typeList[index]
|
|
this.typeId = current && current.id ? current.id : ''
|
|
this.getProductList()
|
|
},
|
|
changeNumber(e, index) {
|
|
let obj = this.list[index]
|
|
if (e > 1) {
|
|
obj.select = true
|
|
}
|
|
obj.number = e
|
|
this.$set(this.list, index, obj)
|
|
this.handleSelect()
|
|
},
|
|
changeSelect(index) {
|
|
let obj = this.list[index]
|
|
obj.select = !obj.select
|
|
this.$set(this.list, index, obj)
|
|
this.handleSelect()
|
|
},
|
|
selectAll() {
|
|
this.isAll = !this.isAll
|
|
let arr = this.list
|
|
if (this.isAll) {
|
|
arr.forEach(ele => {
|
|
ele.select = true
|
|
this.selectedMap[ele.id] = { ...ele }
|
|
})
|
|
} else {
|
|
arr.forEach(ele => {
|
|
ele.select = false
|
|
delete this.selectedMap[ele.id]
|
|
})
|
|
}
|
|
this.list = arr
|
|
this.refreshSelectedState()
|
|
},
|
|
handleSelect() {
|
|
let arr = this.list
|
|
arr.forEach(ele => {
|
|
if (ele.select) {
|
|
this.selectedMap[ele.id] = { ...ele }
|
|
} else {
|
|
delete this.selectedMap[ele.id]
|
|
}
|
|
})
|
|
this.refreshSelectedState()
|
|
},
|
|
refreshSelectedState() {
|
|
this.selectArr = Object.keys(this.selectedMap)
|
|
this.isAll = this.list.length ? this.list.every(item => item.select) : false
|
|
},
|
|
confirmSelect() {
|
|
const selectedProducts = Object.values(this.selectedMap)
|
|
if (selectedProducts.length === 0) {
|
|
uni.showToast({
|
|
title: '请至少选择一个商品',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
// 将选中的商品存储到全局或通过参数传递
|
|
uni.setStorageSync('selectedProducts', JSON.stringify(selectedProducts))
|
|
uni.navigateTo({
|
|
url: '/pagesA/crm/contract/contract?fromProductSelect=1'
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.product-select-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.fixed-top {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 100;
|
|
background: #008EFF;
|
|
}
|
|
|
|
.page-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.header-title {
|
|
font-size: 34rpx;
|
|
color: #fff;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
|
|
.search-row {
|
|
display: flex;
|
|
align-items: center;
|
|
background: #008EFF;
|
|
padding: 16rpx 24rpx;
|
|
|
|
.search-box {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
background: #fff;
|
|
border-radius: 34rpx;
|
|
padding: 0 24rpx;
|
|
height: 64rpx;
|
|
|
|
.search-icon {
|
|
width: 36rpx;
|
|
height: 36rpx;
|
|
margin-right: 12rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.search-input {
|
|
flex: 1;
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
}
|
|
}
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
display: flex;
|
|
height: calc(100vh - 200rpx);
|
|
}
|
|
|
|
.category-list {
|
|
width: 160rpx;
|
|
background: #fff;
|
|
height: 100%;
|
|
flex-shrink: 0;
|
|
|
|
.category-item {
|
|
padding: 28rpx 12rpx;
|
|
font-size: 24rpx;
|
|
color: #333;
|
|
text-align: center;
|
|
border-bottom: 1rpx solid #f5f5f5;
|
|
word-break: break-all;
|
|
|
|
&.active {
|
|
background: #f5f5f5;
|
|
color: #008EFF;
|
|
font-weight: 500;
|
|
position: relative;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 6rpx;
|
|
height: 40rpx;
|
|
background: #008EFF;
|
|
border-radius: 3rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.product-list {
|
|
flex: 1;
|
|
height: 100%;
|
|
padding: 20rpx;
|
|
padding-bottom: 220rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.product-items {
|
|
padding-bottom: 40rpx;
|
|
|
|
.product-item {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
margin-bottom: 16rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
|
padding: 20rpx;
|
|
|
|
.product-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 16rpx;
|
|
|
|
.product-name {
|
|
flex: 1;
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
font-weight: 500;
|
|
line-height: 1.3;
|
|
margin-right: 16rpx;
|
|
}
|
|
|
|
.product-num {
|
|
font-size: 22rpx;
|
|
color: #999;
|
|
flex-shrink: 0;
|
|
}
|
|
}
|
|
|
|
.product-info {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.product-radio {
|
|
margin-right: 16rpx;
|
|
flex-shrink: 0;
|
|
|
|
radio {
|
|
transform: scale(0.7);
|
|
}
|
|
}
|
|
|
|
.product-img {
|
|
width: 100rpx;
|
|
height: 100rpx;
|
|
border-radius: 12rpx;
|
|
margin-right: 16rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.product-detail {
|
|
flex: 1;
|
|
min-width: 0;
|
|
|
|
.detail-text {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
margin-bottom: 6rpx;
|
|
}
|
|
|
|
.number-box {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
margin-top: 8rpx;
|
|
|
|
.number-input {
|
|
margin-left: 10rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.bottom-bar {
|
|
position: fixed;
|
|
left: 0;
|
|
bottom: calc(100rpx + env(safe-area-inset-bottom) / 2);
|
|
width: 750rpx;
|
|
height: 100rpx;
|
|
background: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 30rpx;
|
|
border-top: 1rpx solid #f5f5f5;
|
|
box-sizing: border-box;
|
|
z-index: 99;
|
|
|
|
.left-box {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.select-all {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
|
|
radio {
|
|
transform: scale(0.8);
|
|
}
|
|
}
|
|
|
|
.select-count {
|
|
font-size: 28rpx;
|
|
color: #FE9440;
|
|
margin-left: 20rpx;
|
|
}
|
|
}
|
|
|
|
.right-box {
|
|
.btn-confirm {
|
|
background: #008EFF;
|
|
color: #fff;
|
|
font-size: 30rpx;
|
|
padding: 20rpx 50rpx;
|
|
border-radius: 40rpx;
|
|
}
|
|
}
|
|
}
|
|
</style>
|