hls_crm/pages/index/productSelect/productSelect.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>