Browse Source

1. 花掌柜收银结算新增红包功能
2. 其它优化修改

shizhongqi 6 months ago
parent
commit
253e5d6117

+ 1 - 1
ghsPad/src/pages/home/components/console.vue

@@ -387,7 +387,7 @@ export default {
 			this.$util.hitVoice()
 		},
 		/**
-		 * 结算弹框变化事件
+		 * 结算弹框关闭时统一同步状态,避免 settleOpenStatus 残留为 1
 		 * @param e 结算弹框变化事件
 		 */
         settlePopChange(e){

+ 2 - 1
hdApp/src/admin/billing/components/hb-select.vue

@@ -30,7 +30,8 @@
 								<view class="condition">
 									{{ Number(item.minConsume) > 0 ? `满${item.minConsume}可用` : '无门槛' }}
 								</view>
-								<view class="date">{{ formatTime(item.beginTime) }}-{{ formatTime(item.endTime) }}到期</view>
+								<view class="date" v-if="item.endTime == 4102416000">永久可用</view>
+								<view class="date" v-else>{{ formatTime(item.beginTime) }}-{{ formatTime(item.endTime) }}到期</view>
 							</view>
 							<view class="card-right">
 								<view class="radio-circle" :class="{ checked: currentSelectedId == item.id }">

+ 5 - 0
hdPad/src/api/hb/index.js

@@ -3,4 +3,9 @@ import https from '@/plugins/luch-request_0.0.7/request'
 //列表
 export const getHblist = data => {
 	return https.get('/hb/list', data)
+}
+
+//获取可用红包
+export const getAvailableHb = data => {
+	return https.get('/hb/available-hb', data)
 }

+ 1 - 3
hdPad/src/pages/home/cash.vue

@@ -6,7 +6,6 @@
 		<loginComponent></loginComponent>
 	</template>
 	<template v-else>
-
 		<view class="app-casher-area">
 			<view class="nav-left">
 				<navComponent navType="casher"></navComponent>
@@ -34,10 +33,9 @@
 		<button class="admin-button-com mini-btn" @click="confirmRefresh" style="font-size:20upx;margin-left:10upx;margin-right:28upx;">刷新</button>
 		<text @click="cancelRefresh">x</text>
 		</view>
-
 	</template>
 
-
+	<!-- 客户列表 -->
 	<uni-popup ref="myCustomRef" background-color="#fff" type="center" :animation="false">
         <customerList @fastCreate="fastCreate" :myCustomList.sync="myCustomList" @confirmCustom="confirmCustom" @cancelChoose="cancelChoose">
 		</customerList>

+ 7 - 2
hdPad/src/pages/home/components/console.vue

@@ -41,7 +41,7 @@
 	</uni-popup>
 
     <!-- 结算弹框 -->
-    <uni-popup ref="settlePopRef" background-color="#fff" type="center" :animation="false">
+    <uni-popup ref="settlePopRef" background-color="#fff" type="center" :animation="false" @change="onSettlePopChange">
         <settlePop @settleCommit="settleCommit" @cancelSettle="cancelSettle" ref="settlePop" :settleOpenStatus.sync="settleOpenStatus" :balance.sync="balance" :customId.sync="customId" :custom.sync="custom"></settlePop>
     </uni-popup>
 
@@ -132,7 +132,7 @@ export default {
             currentJobType:'bill',
             currentJobId:0,
 			calc:'add',
-			settleOpenStatus:0,
+			settleOpenStatus:0, // 结算弹框状态
 			price:0,
 			fastAction:1
         }
@@ -342,6 +342,11 @@ export default {
 			this.settleOpenStatus = 0
 			this.$refs.settlePopRef.close()
 		},
+		onSettlePopChange(e){
+			// 点击遮罩关闭/代码关闭都会触发 change;关闭时统一同步状态,避免 settleOpenStatus 残留为 1
+			if(!e || e.show === true) return
+			this.settleOpenStatus = 0
+		},
 		async settleReturn(){
 			this.$util.hitVoice()
 			this.setCreateOrder()

+ 318 - 0
hdPad/src/pages/home/components/hb-select.vue

@@ -0,0 +1,318 @@
+<template>
+	<view class="hb-select-container">
+		<!-- Trigger Row -->
+        <view class="cell-content" @click="openPopup" v-if="hasAvailableHb">
+            <view class="right-content">
+                <text v-if="selectedHb" class="selected-text">-¥{{ selectedHb.amount }}</text>
+                <view v-else class="available-tag">
+                    <text>有红包可用</text>
+                </view>
+            </view>
+        </view>
+
+		<!-- Popup -->
+		<uni-popup ref="popup" type="center" background-color="#ffffff">
+			<view class="hb-popup-content">
+				<view class="popup-header">
+					<text>红包</text>
+					<text class="close-btn" @click="closePopup">×</text>
+				</view>
+
+				<scroll-view scroll-y class="popup-scroll">
+					<view class="hb-list">
+						<view class="hb-card" v-for="item in availableHbList" :key="item.id" @click="selectHb(item)">
+							<view class="card-left">
+								<view class="amount"><text class="symbol">¥</text>{{ item.amount }}</view>
+								<view class="type">{{ item.name }}</view>
+							</view>
+							<view class="card-middle">
+								<view class="name">{{ item.name }}</view>
+								<view class="condition">
+									{{ Number(item.minConsume) > 0 ? `满${item.minConsume}可用` : '无门槛' }}
+								</view>
+								<view class="date" v-if="item.endTime == 4102416000">永久可用</view>
+								<view class="date" v-else>{{ formatTime(item.beginTime) }}-{{ formatTime(item.endTime) }}到期</view>
+							</view>
+							<view class="card-right">
+								<view class="radio-circle" :class="{ checked: currentSelectedId == item.id }">
+									<text v-if="currentSelectedId == item.id" class="iconfont icon-check">✓</text>
+								</view>
+							</view>
+						</view>
+
+						<!-- Option to not use red packet -->
+						<view class="hb-card" @click="selectHb(null)">
+							<view class="card-left" style="border:none;">
+								<text style="font-size: 20upx; color: #333; font-weight: bold;">不使用红包</text>
+							</view>
+							<view class="card-middle"></view>
+							<view class="card-right">
+								<view class="radio-circle" :class="{ checked: !currentSelectedId }">
+									<text v-if="!currentSelectedId" class="iconfont icon-check">✓</text>
+								</view>
+							</view>
+						</view>
+					</view>
+				</scroll-view>
+
+				<view class="popup-footer">
+					<button class="confirm-btn" @click="confirmSelection">确定</button>
+				</view>
+			</view>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+
+export default {
+	name: "HbSelect",
+	props: {
+		hbData: {
+			type: Array,
+			default: () => []
+		},
+		totalPrice: {
+			type: [Number, String],
+			default: 0
+		},
+		value: { // v-model for selected Hb ID
+			type: [String, Number],
+			default: null
+		}
+	},
+	data() {
+		return {
+			currentSelectedId: null
+		};
+	},
+	computed: {
+		availableHbList() {
+			const price = Number(this.totalPrice) || 0;
+			return this.hbData.filter(item => {
+				const min = Number(item.minConsume) || 0;
+				// typically condition is total >= minConsume. 
+				return price >= min; 
+			});
+		},
+		hasAvailableHb() {
+			return this.availableHbList.length > 0;
+		},
+		selectedHb() {
+			if (!this.value) return null;
+			return this.hbData.find(item => item.id == this.value);
+		}
+	},
+	watch: {
+		value: {
+			immediate: true,
+			handler(val) {
+				this.currentSelectedId = val;
+			}
+		}
+	},
+	methods: {
+		openPopup() {
+			this.currentSelectedId = this.value; // Reset to current value when opening
+			this.$refs.popup.open();
+		},
+		closePopup() {
+			this.$refs.popup.close();
+		},
+		selectHb(item) {
+			this.currentSelectedId = item ? item.id : null;
+		},
+		confirmSelection() {
+			this.$emit('input', this.currentSelectedId); // Update v-model
+            const selectedItem = this.hbData.find(item => item.id == this.currentSelectedId);
+			this.$emit('change', selectedItem || null);
+			this.closePopup();
+		},
+		formatTime(timestamp) {
+			if (!timestamp) return '';
+			let date;
+			if (String(timestamp).length === 10) {
+				date = new Date(timestamp * 1000);
+			} else {
+				date = new Date(timestamp);
+			}
+			const y = date.getFullYear();
+			const m = (date.getMonth() + 1).toString().padStart(2, '0');
+			const d = date.getDate().toString().padStart(2, '0');
+            const h = date.getHours().toString().padStart(2, '0');
+            const min = date.getMinutes().toString().padStart(2, '0');
+			return `${y}.${m}.${d} ${h}:${min}`;
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.hb-select-container{
+	display: flex;
+	align-items: center;
+	justify-content: flex-end;
+	width: 100%;
+	height: 30upx; /* 对齐 settlePop 右侧 input-list 行高 */
+}
+.cell-content {
+	display: flex;
+	justify-content: flex-end;
+	align-items: center;
+	width: 100%;
+	height: 30upx;
+}
+.tui-title {
+    width: auto !important; /* Override tui-list-cell default if needed */
+    font-size: 11upx;
+    color: #333;
+}
+.right-content {
+	display: flex;
+	align-items: center;
+}
+.available-tag {
+	background-color: #F82547;
+	color: #fff;
+	font-size: 10upx;
+	padding: 2upx 10upx;
+	border-radius: 20upx;
+	line-height: 1.6;
+}
+.selected-text {
+	color: #ff2842;
+	font-size: 11upx;
+	font-weight: 600;
+}
+
+/* Popup Styles */
+.hb-popup-content {
+	background-color: #f5f5f5;
+	border-radius: 12upx;
+	display: flex;
+	flex-direction: column;
+	width: 78vw;
+	max-width: 440upx;
+	max-height: 70vh;
+	overflow: hidden;
+}
+.popup-header {
+	height: 70upx;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	position: relative;
+	background-color: #fff;
+	font-size: 14upx;
+	font-weight: 600;
+	border-radius: 12upx 12upx 0 0;
+}
+.close-btn {
+	position: absolute;
+	right: 1upx;
+	font-size: 28upx;
+	color: #999;
+	padding: 4upx;
+}
+.popup-scroll {
+	flex: 1;
+    height: 0; /* Important for flex child scroll */
+	padding: 12upx;
+	box-sizing: border-box;
+}
+.hb-list {
+    padding: 6upx;
+}
+.hb-card {
+	background-color: #fff6f0; /* Light orange bg */
+	border-radius: 8upx;
+	margin-bottom: 10upx;
+	display: flex;
+	align-items: center;
+	padding: 12upx 10upx;
+    position: relative;
+    border: 1px solid transparent;
+}
+/* Selected border effect if needed, but image shows radio check */
+
+.card-left {
+	width: 110upx;
+	text-align: center;
+	border-right: 1px dashed #eecbc0;
+	margin-right: 12upx;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+}
+.amount {
+	color: #ff2842;
+	font-size: 22upx;
+	font-weight: 700;
+}
+.symbol {
+	font-size: 12upx;
+    margin-right: 2upx;
+}
+.type {
+	font-size: 10upx;
+	color: #666;
+	margin-top: 2upx;
+}
+.card-middle {
+	flex: 1;
+}
+.name {
+	font-size: 12upx;
+	font-weight: 600;
+	color: #333;
+	margin-bottom: 6upx;
+}
+.condition {
+	font-size: 10upx;
+	color: #666;
+	margin-bottom: 4upx;
+}
+.date {
+	font-size: 10upx;
+	color: #999;
+}
+.card-right {
+	width: 50upx;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+.radio-circle {
+    width: 26upx;
+    height: 26upx;
+    border-radius: 50%;
+    border: 2upx solid #ccc;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background: #fff;
+    transition: all 0.2s;
+}
+.radio-circle.checked {
+    background: #ff6600; /* Orange check */
+    border-color: #ff6600;
+}
+.icon-check {
+    color: #fff;
+    font-size: 16upx;
+}
+
+.popup-footer {
+	padding: 10upx 14upx;
+	background-color: #fff;
+}
+.confirm-btn {
+	background-color: #ff6600;
+	color: #fff;
+	border-radius: 20upx;
+	font-size: 22upx;
+	height: 60upx;
+	line-height: 60upx;
+}
+</style>

+ 99 - 14
hdPad/src/pages/home/components/settlePop.vue

@@ -181,6 +181,8 @@
 
         </view>
         <view class="line"></view>
+
+
         <view class="select-right">
             <view style="position: absolute;background-color: white;z-index: 99999999;" v-if="showDiscount">
                 <view style="display:flex;padding:20upx;justify-content: space-between;align-items:center;flex-wrap:wrap;">
@@ -205,6 +207,18 @@
                 <text style="color:green;">包装资材费</text>
                 <text><text :class="{selected:checkType == 2}">{{form.labourCost}}</text></text>
             </view>
+            <view class="input-list" v-if="hasAvailableHb">
+                <text>红包</text>
+                <!-- 注意:不要用 <text> 包裹组件(小程序端 text 只能嵌套 text),否则 hb-select 不会渲染 -->
+                <view>
+                    <hb-select
+                        :hbData="hbData"
+                        :totalPrice="preDiscountPrice"
+                        v-model="selectedHbId"
+                        @change="(hb) => { selectedHb = hb }"
+                    ></hb-select>
+                </view>
+            </view>
             <view class="input-list">
                 <text>总金额</text>
                 <text @click="viewDiscount()">
@@ -243,10 +257,12 @@ import VKeyboard from '@/components/vKeyboard/VKeyboard.vue'
 import TimePicker from '@/components/TimePicker';
 import WeightInput from '@/components/delivery-input/weight-input.vue';
 import RemarkInput from '@/components/delivery-input/remark-input.vue';
+import HbSelect from './hb-select'
 import productMins from "@/mixins/product";
 import { getAllStaff} from '@/api/shop-admin'
 import { merchantDetail } from '@/api/merchant' 
 import { quickOrderAllDeliveryQuotes } from '@/api/delivery'
+import { getAvailableHb } from "@/api/hb"
 import { mapGetters } from "vuex";
 export default {
     name:'selectCustomer',
@@ -311,6 +327,11 @@ export default {
                 value: ''
             },
             weightDebounceTimer: null,
+            selectedHbId: null,
+            selectedHb: null,
+            hbData: [],
+            // 用于确保异步回填 hbData 后,相关 computed 一定重新计算(兼容部分端的更新差异)
+            hbDataVersion: 0,
         };
     },
     props:{
@@ -339,11 +360,16 @@ export default {
             default:0
         }
     },
-    components:{ VKeyboard, TimePicker, WeightInput, RemarkInput },
+    components:{ VKeyboard, TimePicker, WeightInput, RemarkInput, HbSelect },
 	mixins: [productMins],
     computed:{
         ...mapGetters(["getLoginInfo"]),
+        // 总金额, 也就是合计金额
         totalPrice(){
+            return this.systemPrice
+        },
+        // 计算不含红包的总价
+        preDiscountPrice(){
             let price = 0
             const sendCost = Number(this.form.sendCost)
 
@@ -355,38 +381,36 @@ export default {
                     if(this.form.reductionRule != 0){
                         if(this.form.reductionRule == 1){
                             if(this.isHcMapRule()){
-                                price = this.allPrice+Number(this.form.customSendCost)
+                                price = this.allPrice + Number(this.form.customSendCost)
                             }else{
-                                price = this.allPrice+sendCost
+                                price = this.allPrice + sendCost
                             }
                         }else if(this.form.reductionRule == 2){
                             if(this.isHsMapRule()){
-                                price = this.allPrice+Number(this.form.customSendCost)
+                                price = this.allPrice + Number(this.form.customSendCost)
                             }else{
                                 const distanceInMeters = Number(this.showDistance) || 0
                                 const freeKmInMeters = (Number(this.hsFreeKm) || 0) * 1000
-                                const diff = distanceInMeters - freeKmInMeters  
+                                const diff = distanceInMeters - freeKmInMeters
                                 if(diff > 0){
                                     //花束满减规则 -- 每超出1公里,加收 hsAddFee 元(不足1公里按1公里计算)
                                     const addFeePrice = Math.ceil(diff / 1000) * this.hsAddFee
                                     this.form.sendCost = addFeePrice
-                                    price = this.allPrice+addFeePrice
+                                    price = this.allPrice + addFeePrice
                                 }else{
-                                    price = this.allPrice+sendCost
+                                    price = this.allPrice + sendCost
                                 }
                             }
                         }
                     }else{
-                        price = this.allPrice+sendCost
+                        price = this.allPrice + sendCost
                     }
                     price = parseFloat(price.toFixed(2))
                 }else{
-                    price = Number(this.allPrice)+Number(this.form.labourCost)
+                    price = Number(this.allPrice) + Number(this.form.labourCost)
                     price = parseFloat(price.toFixed(2))
                 }
             }
-            this.form.modifyPrice = price
-
             return price
         },
         diffPrice(){
@@ -405,9 +429,38 @@ export default {
         isAddressInvalid() {
             //this.custom 有也会出现空的情况
             return this.$util.isEmpty(this.custom) || this.$util.isEmpty(this.custom.long) || this.$util.isEmpty(this.custom.lat) || this.$util.isEmpty(this.custom.address)
-        }
+        },
+        hasAvailableHb(){
+            // 显式依赖一次版本号,确保 hbData 异步更新后 computed 一定失效重算
+            this.hbDataVersion // eslint-disable-next-line no-unused-expressions
+
+            if (!this.hbData || this.hbData.length === 0) return false
+            const price = Number(this.preDiscountPrice) || 0
+            return this.hbData.some(item => {
+                const min = Number(item.minConsume) || 0
+                //console.log('price, min ----- ', price, min)
+                return price >= min
+            })
+        },
+        // 合计金额(含红包扣减)
+        systemPrice(){
+            let finalPrice = Number(this.preDiscountPrice) || 0
+            // 减去红包金额
+            if(this.hasAvailableHb && this.selectedHb){
+                finalPrice = finalPrice - Number(this.selectedHb.amount || 0)
+            }
+            if(finalPrice < 0) finalPrice = 0
+            return parseFloat(Number(finalPrice).toFixed(2))
+        },
     },
     watch:{
+        // systemPrice 变化时,默认回填应付金额
+        systemPrice: {
+            handler(val){
+                this.form.modifyPrice = val
+            },
+            immediate: true
+        },
         settleOpenStatus:{
             handler(val){
                 if(Number(val) == 1){
@@ -421,6 +474,11 @@ export default {
                     this.form.dealPrice = 0
                     this.form.sendType = 1
                     this.form.getGoods = 1
+                    // 重置红包选择,避免上次结算残留
+                    this.selectedHbId = null
+                    this.selectedHb = null
+                    this.form.hbId = null
+                    this.form.hbAmount = null
 
                     //如果余额够使用余额支付
                     if(Number(this.balance)>Number(this.form.modifyPrice)){
@@ -441,6 +499,9 @@ export default {
                             this.hsAddFee = res.data.hsAddFee
                         }
                     })
+
+                    //获取可用红包数据
+                    this.getHbData()
                 }
             },
             immediate:true
@@ -546,7 +607,7 @@ export default {
         setDiscount(num){
             this.$util.hitVoice()
             this.discountValue = Number(num)
-            let price = Number(this.totalPrice)*Number(num)
+            let price = Number(this.systemPrice)*Number(num)
             price = parseFloat(price.toFixed(2))
             this.form.modifyPrice = price
             this.showDiscount = false
@@ -863,6 +924,17 @@ export default {
                 }
             }
 
+            // 红包信息
+            if(this.hasAvailableHb && this.selectedHbId && this.selectedHb){
+                this.form.hbId = this.selectedHbId
+                this.form.hbAmount = this.selectedHb.amount
+            }else{
+                // this.form.hbId = null //后端会产生 Column 'hbId' cannot be null
+                // this.form.hbAmount = null
+                this.form.hbId = 0
+                this.form.hbAmount = 0
+            }
+
             if(this.form.callErrand == 1){
                 if(this.form.sendType == 2 && this.form.hasPay == 0){
                     this.$msg('扫码付款,暂不支持马上发跑腿')
@@ -878,7 +950,20 @@ export default {
         },
         cancel(){
             this.$emit('cancelSettle')
-        }
+        },
+        getHbData(){
+            getAvailableHb({
+                customId: this.customId,
+                shopId: this.getLoginInfo.shopId
+            }).then(res=>{
+                if(res.code == 1){
+                    const list = res && res.data ? res.data : []
+                    // 用数组替换 + 版本号自增,确保视图/计算属性稳定刷新
+                    this.hbData = Array.isArray(list) ? list : []
+                    this.hbDataVersion += 1
+                }
+            })
+        },
     }
 }
 </script>

+ 2 - 1
mallApp/src/components/hb/hb-select.vue

@@ -30,7 +30,8 @@
 								<view class="condition">
 									{{ Number(item.minConsume) > 0 ? `满${item.minConsume}可用` : '无门槛' }}
 								</view>
-								<view class="date">{{ formatTime(item.endTime) }} 到期</view>
+								<view class="date" v-if="item.endTime == 4102416000">永久可用</view>
+								<view class="date" v-else>{{ formatTime(item.endTime) }} 到期</view>
 							</view>
 							<view class="card-right">
 								<view class="radio-circle" :class="{ checked: currentSelectedId == item.id }">

+ 2 - 1
mallApp/src/pages/hb/list.vue

@@ -26,7 +26,8 @@
 							<view class="coupon-message">
 								<view class="title">{{ item.name }}</view>
 								<view class="shop">{{ item.merchantName }}</view>
-								<view class="date">有效期 {{ item.endTime | formatTime('YYYY-MM-DD') }}</view>
+								<view class="date" v-if="item.endTime == 4102416000">有效期:永久可用</view>
+								<view class="date" v-else>有效期:{{ item.endTime | formatTime('YYYY-MM-DD') }}</view>
 							</view>
 							<!-- 未使用 -->
 							<template v-if="item.status == 0">