Explorar o código

Merge branch 'redesign‌-260706' of git.huaml.com:zhh/front-end into redesign‌-260706

shizhongqi hai 13 horas
pai
achega
5f30e1c336

+ 34 - 2
ghsApp/src/admin/order/customOrder.vue

@@ -1,5 +1,12 @@
 <template>
   <div class="app-main app-content">
+    <!-- 时间筛选:默认近30天,与订单列表 order.vue 同一 DateSelect 组件 -->
+    <view class="input-wrap_box">
+      <view class="select-dn_bx">
+        <DateSelect ref="dateSelectRef" class="bar" top="0" @selectDateFn="selectDateFn" defaultShowName="近30天" />
+      </view>
+    </view>
+
     <div class="list-wrap">
       <block v-if="!$util.isEmpty(list.data)">
         <div class="list" v-for="(item, index) in list.data" :key="index">
@@ -47,6 +54,7 @@
 <script>
 import AppWrapperEmpty from "@/components/app-wrapper-empty";
 import ModalModule from "@/components/plugin/modal";
+import DateSelect from "@/components/module/dateSelect";
 import { list } from "@/mixins";
 import { mapGetters } from "vuex";
 import orderMixin from "@/mixins/order";
@@ -57,6 +65,7 @@ export default {
   components: {
     AppWrapperEmpty,
     ModalModule,
+    DateSelect,
     OrderItem,
   },
   mixins: [list,orderMixin],
@@ -69,6 +78,10 @@ export default {
       fhWl:'顺丰',
       fhWlFocus:false,
       fhLabelShow:false,
+      // 默认近30天,与订单列表一致,避免一次加载客户全量历史订单
+      searchTime: 'last30Days',
+      startTime: '',
+      endTime: '',
     }
   },
   onPullDownRefresh() {
@@ -182,8 +195,26 @@ export default {
         })
       })
     },
+    /**
+     * DateSelect 选中日期后刷新列表
+     * @param {Object} val - { searchTime, startTime, endTime }
+     */
+    selectDateFn(val) {
+      this.searchTime = val.searchTime || ''
+      this.startTime = val.startTime || ''
+      this.endTime = val.endTime || ''
+      this.resetList()
+      this.getOrderList()
+    },
+    /** 按客户 + 时间范围拉取订单列表 */
     getOrderList() {
-        return getListB({ page: this.list.page,customId:this.option.customId }).then((res) => {
+        return getListB({
+          page: this.list.page,
+          customId: this.option.customId,
+          searchTime: this.searchTime,
+          startTime: this.startTime,
+          endTime: this.endTime,
+        }).then((res) => {
           this.completes(res)
         })
     }
@@ -222,7 +253,8 @@ page{
     }
   }
   .list-wrap {
-    padding-top: 20upx;
+    // 为顶部固定时间筛选栏留出空间
+    padding-top: 100upx;
     .list {
       background-color: #fff;
       margin-bottom: 20upx;

+ 3 - 4
ghsApp/src/pagesClient/member/detail.vue

@@ -905,6 +905,7 @@ export default {
         homeAmount: this.userInfo.homeAmount,
         homeNum: this.userInfo.homeNum,
         homeUnMeet: homeUnMeet,
+        homeRule: this.form.homeRule,
         homeUnFee: homeUnMeet === 2 ? 0 : (this.userInfo.homeUnFee || 0)
       }).then(res=>{
         if(res.code == 1){
@@ -912,10 +913,7 @@ export default {
         }
       })
     },
-    /**
-     * 切换「不满最低要求时」的处理方式
-     * @param {number} value - 0 收运费,1 收包装费,2 不能下单
-     */
+    //切换「不满最低要求时」的处理方式,0 收运费,1 收包装费,2 不能下单
     homeUnMeetBtn (value) {
       if (Number(this.form.homeUnMeet) === value) {
         return true
@@ -926,6 +924,7 @@ export default {
         id: this.option.id,
         homeAmount: this.userInfo.homeAmount,
         homeNum: this.userInfo.homeNum,
+        homeRule: this.form.homeRule,
         homeUnMeet: value,
         homeUnFee: value === 2 ? 0 : (this.userInfo.homeUnFee || 0)
       }).then(res => {

+ 8 - 8
ghsApp/src/pagesOrder/shMethod.vue

@@ -107,24 +107,24 @@
               </button>
               <button
                 class="admin-button-com middle"
-                :class="[form.unMeet == 2 ? 'blue' : 'default']"
-                @click="form.unMeet = 2"
+                :class="[form.unMeet == 1 ? 'blue' : 'default']"
+                @click="form.unMeet = 1"
                 style="margin-left: 10upx;"
               >
                 收包装费
               </button>
               <button
                 class="admin-button-com middle"
-                :class="[form.unMeet == 1 ? 'blue' : 'default']"
-                @click="form.unMeet = 1"
+                :class="[form.unMeet == 2 ? 'blue' : 'default']"
+                @click="form.unMeet = 2"
                 style="margin-left: 10upx;"
               >
                 不能买
               </button>
             </view>
           </tui-list-cell>
-          <tui-list-cell class="line-cell" :hover="false" v-if="form.unMeet != 1">
-            <view class="tui-title">{{ form.unMeet == 2 ? '包装费' : '运费' }}</view>
+          <tui-list-cell class="line-cell" :hover="false" v-if="form.unMeet != 2">
+            <view class="tui-title">{{ form.unMeet == 1 ? '包装费' : '运费' }}</view>
             <input type="digit" v-model="form.unMeetFee" @focus="form.unMeetFee = ''" class="tui-input" placeholder="请填写金额" />
           </tui-list-cell>
         </block>
@@ -157,7 +157,7 @@
           </view>
 
           <view class="notice-text">
-            不符合满减规则时,将通过跑腿计算运费,跑腿无法使用时,将使用自定义计费
+            不符合满减规则时,将通过跑腿或用自定义方式计费
           </view>
 
           <!-- 自定义计费 -->
@@ -748,7 +748,7 @@ export default {
 
 /* 提示文字 */
 .notice-text {
-  font-size: 32upx;
+  font-size: 34upx;
   color: #ff6900;
   line-height: 36upx;
   padding: 15upx 20upx;

+ 189 - 31
hdApp/src/admin/billing/affirmSh.vue

@@ -101,10 +101,10 @@
 							</view>
 						</tui-list-cell>
 
-						<!-- 未达标提示 -->
-						<tui-list-cell class="line-cell" :hover="false" :arrow="false" v-if="showShMethodExtraFee">
+						<!-- 未达标提示:加运费/加包装费/不能下单 -->
+						<tui-list-cell class="line-cell" :hover="false" :arrow="false" v-if="showShMethodUnMeetWarning">
 							<view>
-								<text style="color: red;width:100%;">{{ shMethodUnMeetWarningText }}</text>
+								<text style="color: red;width:100%;font-weight: bold;font-size: 30upx;text-decoration: underline;">{{ shMethodUnMeetWarningText }}</text>
 							</view>
 						</tui-list-cell>
 
@@ -168,7 +168,11 @@
 							<view>
 								<text :class="isFreePt ? 'delivery-price-free' : 'delivery-price-normal'">¥{{ form.sendCost }}</text>
 								<text v-if="isFreePt" class="free-delivery-tag">¥0</text>
-								<text class="delivery-policy-link" @click="showDeliveryPolicyModal">免运费条件</text>
+								<!-- 展示最接近的免运费差额提示;右侧问号提示可点击查看全部条件 -->
+								<view v-if="!isFreePt" class="delivery-policy-link" @click="showDeliveryPolicyModal">
+									<text>{{ nearestDeliveryPolicyText }}</text>
+									<text class="policy-icon">?</text>
+								</view>
 							</view>
 						</tui-list-cell>
 
@@ -409,14 +413,32 @@ export default {
 			}
 			const unMeet = Number(method.unMeet) || 0
 			const fee = Number(method.unMeetFee) || 0
-			return (unMeet === 0 || unMeet === 2) && fee > 0
+			return (unMeet === 0 || unMeet === 1) && fee > 0
+		},
+		/**
+		 * 未达配送方式最低消费时是否展示红色提示
+		 * unMeet:0 加运费、1 加包装费、2 不能下单
+		 */
+		showShMethodUnMeetWarning() {
+			const method = this.currentShMethod
+			if (!method || !this.checkShMethodBelowMinimum(method)) {
+				return false
+			}
+			const unMeet = Number(method.unMeet) || 0
+			// 不能下单:直接提示
+			if (unMeet === 2) {
+				return true
+			}
+			// 加运费 / 加包装费:有费用时提示
+			const fee = Number(method.unMeetFee) || 0
+			return (unMeet === 0 || unMeet === 1) && fee > 0
 		},
 		shMethodExtraFeeLabel() {
 			const method = this.currentShMethod
 			if (!method) {
 				return "包装费"
 			}
-			return Number(method.unMeet) === 2 ? "包装费" : "运费"
+			return Number(method.unMeet) === 1 ? "包装费" : "运费"
 		},
 		shMethodExtraFeeAmount() {
 			if (!this.showShMethodExtraFee) {
@@ -425,6 +447,7 @@ export default {
 			const method = this.currentShMethod
 			return parseFloat(Number(method && method.unMeetFee) || 0)
 		},
+		/** 未达标红色文案:加运费/包装费金额,或不能下单 */
 		shMethodUnMeetWarningText() {
 			const method = this.currentShMethod
 			if (!method) {
@@ -434,13 +457,21 @@ export default {
 			const minAmount = Number(method.minAmount) || 0
 			const minNum = Number(method.minNum) || 0
 			if (minAmount > 0) {
-				tips.push("满" + minAmount + "元")
+				tips.push("满" + minAmount + "元")
 			}
 			if (minNum > 0) {
-				tips.push("不满" + minNum + "扎")
+				tips.push("未满" + minNum + "扎")
+			}
+			const conditionText = tips.length > 0 ? tips.join("并且") : "未达最低消费"
+			const unMeet = Number(method.unMeet) || 0
+			// unMeet=2:未满最低消费时引导加购,文案避免生硬的「不能下单」
+			if (unMeet === 2) {
+				return conditionText + ",请再加购后下单"
 			}
-			const conditionText = tips.length > 0 ? tips.join("或") : "未达最低消费"
-			return "当天订单" + conditionText + ",加" + this.shMethodExtraFeeLabel + this.shMethodExtraFeeAmount + "元"
+			// unMeet=0 加运费;unMeet=1 加包装费,并写出具体金额
+			const fee = parseFloat(Number(method.unMeetFee) || 0)
+			const feeLabel = unMeet === 1 ? "包装费" : "运费"
+			return conditionText + ",加" + feeLabel + fee + "元"
 		},
 		currentMethodExplains() {
 			const method = this.currentShMethod
@@ -512,6 +543,69 @@ export default {
 
 			return false
 		},
+		/**
+		 * 跑腿费旁简短提示:筛出最接近规则后,只展示尚未满足的满扎/满额
+		 * 已满足的条件不写;公里已在筛选范围内,不写「x公里内」,统一用「免运费」
+		 * 完整规则仍在点击弹框中展示
+		 */
+		nearestDeliveryPolicyText() {
+			const method = this.currentShMethod
+			let rules = method && method.reduceRules
+			if (typeof rules === "string") {
+				try {
+					rules = JSON.parse(rules)
+				} catch (e) {
+					rules = []
+				}
+			}
+			if (!Array.isArray(rules) || rules.length === 0) {
+				return "免运费条件"
+			}
+
+			const flowerCount = Number(this.allCountFun.bigLength) || 0
+			const flowerPrice = Number(this.allPriceFun) || 0
+			const distanceInMeters = Number(this.showDistance) || 0
+
+			let bestRule = null
+			let bestScore = Infinity
+
+			rules.forEach(rule => {
+				const meetNum = Number(rule.meetNum) || 0
+				const meetAmount = Number(rule.meetAmount) || 0
+				const freeKm = Number(rule.freeKm) || 0
+				const freeKmInMeters = freeKm * 1000
+				// 无有效门槛的规则跳过
+				if (meetNum <= 0 && meetAmount <= 0 && freeKm <= 0) {
+					return
+				}
+
+				const numGap = Math.max(0, meetNum - flowerCount)
+				const amountGap = Math.max(0, meetAmount - flowerPrice)
+				const distanceOk = freeKmInMeters > 0 && distanceInMeters <= freeKmInMeters
+
+				let score = 0
+				if (numGap === 0 && amountGap === 0 && distanceOk) {
+					// 已满足该规则,最优先
+					score = 0
+				} else if (distanceOk || freeKmInMeters <= 0) {
+					// 距离可达:按金额/扎数差额选最接近的
+					score = 1 + amountGap + numGap * 50
+				} else {
+					// 距离超限:排在可达规则之后
+					score = 1000000 + (distanceInMeters - freeKmInMeters) + amountGap + numGap * 50
+				}
+
+				if (score < bestScore) {
+					bestScore = score
+					bestRule = rule
+				}
+			})
+
+			if (!bestRule) {
+				return "免运费条件"
+			}
+			return this.formatNearestReduceRuleTip(bestRule, flowerCount, flowerPrice)
+		},
 		/**
 		 * 计算总金额
 		 * @returns {number}
@@ -613,6 +707,58 @@ export default {
 				uni.showToast({ title: '获取自定义运费失败', icon: 'none' })
 			})
 		},
+		/**
+		 * 将单条满减规则格式化为弹框完整文案(含公里)
+		 * @param {Object} rule - meetNum/meetAmount/freeKm
+		 * @returns {string}
+		 */
+		formatReduceRuleText(rule) {
+			if (!rule) {
+				return ""
+			}
+			const meetNum = Number(rule.meetNum) || 0
+			const meetAmount = Number(rule.meetAmount) || 0
+			const freeKm = Number(rule.freeKm) || 0
+			if (meetNum > 0 && meetAmount > 0) {
+				return `满${meetNum}扎并且满${meetAmount}元,${freeKm}公里免运费`
+			}
+			if (meetNum > 0) {
+				return `满${meetNum}扎,${freeKm}公里内免运费`
+			}
+			if (meetAmount > 0) {
+				return `满${meetAmount}元,${freeKm}公里内免运费`
+			}
+			if (freeKm > 0) {
+				return `${freeKm}公里内免跑腿费`
+			}
+			return ""
+		},
+		/**
+		 * 跑腿费旁简短文案:用「还差」提示差额,已满足的不写;公里已在筛选内只写「免运费」
+		 * @param {Object} rule - 筛出的最接近规则
+		 * @param {number} flowerCount - 当前扎数
+		 * @param {number} flowerPrice - 当前金额
+		 * @returns {string}
+		 */
+		formatNearestReduceRuleTip(rule, flowerCount, flowerPrice) {
+			const meetNum = Number(rule && rule.meetNum) || 0
+			const meetAmount = Number(rule && rule.meetAmount) || 0
+			const count = Number(flowerCount) || 0
+			const price = Number(flowerPrice) || 0
+			// 计算相对门槛还差多少扎/多少元
+			const lackNum = meetNum > 0 ? Math.max(0, meetNum - count) : 0
+			const lackAmount = meetAmount > 0 ? Math.max(0, parseFloat((meetAmount - price).toFixed(2))) : 0
+			if (lackNum <= 0 && lackAmount <= 0) {
+				return "免运费"
+			}
+			if (lackNum > 0 && lackAmount > 0) {
+				return `差${lackNum}扎${lackAmount}元免运费`
+			}
+			if (lackNum > 0) {
+				return `差${lackNum}扎免运费`
+			}
+			return `差${lackAmount}元免运费`
+		},
 		// 展示包配送政策弹窗
 		showDeliveryPolicyModal() {
 			this.deliveryPolicyList = []
@@ -628,20 +774,7 @@ export default {
 				}
 				if (Array.isArray(rules) && rules.length > 0) {
 					rules.forEach(rule => {
-						const meetNum = Number(rule.meetNum) || 0
-						const meetAmount = Number(rule.meetAmount) || 0
-						const freeKm = Number(rule.freeKm) || 0
-						
-						let text = ''
-						if (meetNum > 0 && meetAmount > 0) {
-							text = `满${meetNum}扎和${meetAmount}元,${freeKm}公里内免跑腿费`
-						} else if (meetNum > 0) {
-							text = `满${meetNum}扎,${freeKm}公里内免跑腿费`
-						} else if (meetAmount > 0) {
-							text = `满${meetAmount}元,${freeKm}公里内免跑腿费`
-						} else if (freeKm > 0) {
-							text = `${freeKm}公里内免跑腿费`
-						}
+						const text = this.formatReduceRuleText(rule)
 						if (text) {
 							this.deliveryPolicyList.push(text)
 						}
@@ -664,7 +797,7 @@ export default {
 			if (method && this.checkShMethodBelowMinimum(method)) {
 				const unMeet = Number(method.unMeet) || 0
 				const fee = parseFloat(Number(method.unMeetFee) || 0)
-				if ((unMeet === 0 || unMeet === 2) && fee > 0) {
+				if ((unMeet === 0 || unMeet === 1) && fee > 0) {
 					this.form.packCost = fee
 					return
 				}
@@ -973,8 +1106,19 @@ export default {
 					return false
 				}
 				const method = this.currentShMethod
-				if (method && Number(method.unMeet) === 1 && this.checkShMethodBelowMinimum(method)) {
-					this.$msg("未达到最低消费,不能下单")
+				if (method && Number(method.unMeet) === 2 && this.checkShMethodBelowMinimum(method)) {
+					// 未满最低消费且不可下单时,提示具体需要的最低金额与数量
+					const minAmount = Number(method.minAmount) || 0
+					const minNum = Number(method.minNum) || 0
+					const tips = []
+					if (minAmount > 0) {
+						tips.push(`满${minAmount}元`)
+					}
+					if (minNum > 0) {
+						tips.push(`满${minNum}扎`)
+					}
+					const tipStr = tips.length > 0 ? `${tips.join("并且")},请加购后下单` : "未达到最低消费,请再加购后下单"
+					this.$msg(tipStr)
 					return false
 				}
 				
@@ -1547,16 +1691,30 @@ export default {
 }
 
 .delivery-policy-link {
+	display: inline-flex;
+	align-items: center;
 	color: rgb(201, 52, 52);
-	font-size: 30upx;
+	font-size: 32upx;
 	margin-left: 20upx;
 	font-weight: bold;
-	text-decoration: underline;
 }
 
+/* 右侧问号提示符:表明文案可点击查看全部免运费条件 */
 .policy-icon {
-	font-size: 28upx;
-	margin-left: 4upx;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	width: 28upx;
+	height: 28upx;
+	margin-left: 8upx;
+	font-size: 22upx;
+	font-weight: bold;
+	line-height: 28upx;
+	color: rgb(201, 52, 52);
+	background-color: transparent;
+	border: 2upx solid rgb(201, 52, 52);
+	border-radius: 50%;
+	box-sizing: border-box;
 }
 
 .policy-popup-content {

+ 16 - 7
pt/src/saas/shop/apply.vue

@@ -84,12 +84,7 @@ export default {
 		},
 		onLoad({ ctx, app }) {
 			this.app = app;
-			let applyHost = () => {
-				return this.$service.saasShop.applyList();
-			};
-			ctx.service({
-				page: applyHost
-			})
+			ctx.service(this.$service.apply)
 				.set('table', {
 					columns: [
 						{
@@ -160,8 +155,22 @@ export default {
 						layout: ['slot-apply']
 					}
 				})
+				.set('dict', {
+					search: {
+						keyWord: 'name'
+					}
+				})
+				.set('search', {
+					key: {
+						placeholder: '请填写名称/手机号'
+					}
+				})
+				.set('pagination', {
+					page: 1,
+					size: 20
+				})
 				.set('layout', [
-					['slot-tabs'],
+					['slot-tabs', 'search-key'],
 					['flex1', 'refresh-btn'],
 					['data-table'],
 					['flex1', 'pagination']

+ 304 - 13
pt/src/saas/shop/shop.vue

@@ -8,6 +8,7 @@
                 <el-link :underline="true" @click="AmountShowFn(scope.row,'1')" style="font-size:12px;display:inline-block;">{{ scope.row.totalIncome }}</el-link>
             </template>
             <template #slot-apply="{scope}">
+              <el-button type="text" @click="editShop(scope.row)">修改信息</el-button>
               <el-button type="text" @click="addStaff(scope.row)">添加员工</el-button>
               <el-button type="text" @click="clearStock(scope.row)">清库存</el-button>
             </template>
@@ -22,15 +23,19 @@
             </template>
 
             <template #table-column-hasRenew="{scope}">
-                <span v-if="scope.row.hasRenew == 0" style="color:#CCCCCC;">-</span>
-                <span v-else style="color:blue;">
-                  已续
-                </span>
+                <span v-if="scope.row.hasRenew == 0" style="color:#999999;">未续费</span>
+                <span v-else style="color:blue;">已续费</span>
             </template>
 
             <template #table-column-deadline="{scope}">
                 <span>
-                  {{ scope.row.deadline?scope.row.deadline.substr(0,11):'' }}
+                  {{ scope.row.deadline ? scope.row.deadline.substr(0,16) : '-' }}
+                </span>
+            </template>
+
+            <template #table-column-beforeDeadline="{scope}">
+                <span>
+                  {{ scope.row.beforeDeadline ? scope.row.beforeDeadline.substr(0,16) : '-' }}
                 </span>
             </template>
 
@@ -44,6 +49,18 @@
               </span>
             </template>
 
+            <template #slot-batch-btn>
+              <el-button
+                type="primary"
+                size="small"
+                style="margin-left: 10px;"
+                :disabled="selectedShops.length === 0"
+                @click="batchEditShop"
+              >
+                批量修改信息 ({{ selectedShops.length }})
+              </el-button>
+            </template>
+
         </x-crud>
 
         <balance-detail v-if='showBalanceDetail' @closeFn='showBalanceDetail = false' ref="balanceDetail" :orderOperate="optionData" />
@@ -82,11 +99,130 @@
           </div>
         </el-dialog>
 
+        <!-- 修改门店信息弹窗 -->
+        <el-dialog :visible.sync="editShopPop" :close-on-click-modal="false" width="550px" title="修改门店信息">
+          <el-form :model="editShopForm" :rules="editShopRule" ref="editShopRef" label-width="110px" class="demo-form">
+            <el-form-item label="门店">
+              <span style="font-weight: bold; color: #333;">{{ editShopForm.merchantName }} - {{ editShopForm.shopName }}</span>
+            </el-form-item>
+            <el-form-item label="商户号" prop="lklSjNo">
+              <el-input placeholder="请输入拉卡拉商户号" size="small" v-model="editShopForm.lklSjNo"></el-input>
+            </el-form-item>
+            <el-form-item label="B2B终端号" prop="lklB2BTermNo">
+              <el-input placeholder="请输入B2B终端号" size="small" v-model="editShopForm.lklB2BTermNo"></el-input>
+            </el-form-item>
+            <el-form-item label="扫码终端号" prop="lklScanTermNo">
+              <el-input placeholder="请输入扫码终端号" size="small" v-model="editShopForm.lklScanTermNo"></el-input>
+            </el-form-item>
+            <el-form-item label="到期时间" prop="deadline">
+              <el-date-picker
+                v-model="editShopForm.deadline"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="选择到期时间"
+                size="small"
+                style="width: 100%;"
+              ></el-date-picker>
+              <div style="margin-top: 6px; display: flex; align-items: center; gap: 8px;">
+                <span style="font-size: 12px; color: #888;">快捷选择:</span>
+                <el-button size="mini" type="primary" plain @click="setQuickDeadline('edit', 'halfYear')">半年</el-button>
+                <el-button size="mini" type="primary" plain @click="setQuickDeadline('edit', 'oneYear')">明年</el-button>
+                <el-button size="mini" type="primary" plain @click="setQuickDeadline('edit', 'twoYears')">后年</el-button>
+              </div>
+            </el-form-item>
+            <el-form-item label="上次到期时间" prop="beforeDeadline">
+              <el-date-picker
+                v-model="editShopForm.beforeDeadline"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="选择上次到期时间"
+                size="small"
+                style="width: 100%;"
+              ></el-date-picker>
+              <div style="margin-top: 6px; display: flex; align-items: center; gap: 8px;">
+                <span style="font-size: 12px; color: #888;">快捷选择:</span>
+                <el-button size="mini" type="primary" plain @click="setQuickBeforeDeadline('edit')">此刻</el-button>
+              </div>
+            </el-form-item>
+            <el-form-item label="续费状态" prop="hasRenew">
+              <el-radio-group v-model="editShopForm.hasRenew">
+                <el-radio :label="0">未续费</el-radio>
+                <el-radio :label="1">已续费</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-form>
+          <div slot="footer" class="dialog-footer">
+            <el-button size="small" @click="resetEditShopForm">取 消</el-button>
+            <el-button type="primary" size="small" @click="putEditShopForm">确 认</el-button>
+          </div>
+        </el-dialog>
+
+        <!-- 批量修改门店信息弹窗 -->
+        <el-dialog :visible.sync="batchEditShopPop" :close-on-click-modal="false" width="580px" title="批量修改门店信息">
+          <div style="margin-bottom: 15px; padding: 10px 12px; background: #e6f7ff; border: 1px solid #91d5ff; border-radius: 4px; font-size: 13px; color: #1890ff;">
+            <div>已选择 <b>{{ selectedShops.length }}</b> 家门店:<span style="color: #333; word-break: break-all;">{{ selectedShopsNames }}</span></div>
+            <div style="margin-top: 4px; color: #ff4d4f; font-size: 12px;">说明:不填或选择“不修改”的字段将保持各个门店原本的数值。</div>
+          </div>
+          <el-form :model="batchEditShopForm" ref="batchEditShopRef" label-width="110px" class="demo-form">
+            <el-form-item label="商户号">
+              <el-input placeholder="留空则不修改" size="small" v-model="batchEditShopForm.lklSjNo"></el-input>
+            </el-form-item>
+            <el-form-item label="B2B终端号">
+              <el-input placeholder="留空则不修改" size="small" v-model="batchEditShopForm.lklB2BTermNo"></el-input>
+            </el-form-item>
+            <el-form-item label="扫码终端号">
+              <el-input placeholder="留空则不修改" size="small" v-model="batchEditShopForm.lklScanTermNo"></el-input>
+            </el-form-item>
+            <el-form-item label="到期时间">
+              <el-date-picker
+                v-model="batchEditShopForm.deadline"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="不选则不修改"
+                size="small"
+                style="width: 100%;"
+              ></el-date-picker>
+              <div style="margin-top: 6px; display: flex; align-items: center; gap: 8px;">
+                <span style="font-size: 12px; color: #888;">快捷选择:</span>
+                <el-button size="mini" type="primary" plain @click="setQuickDeadline('batch', 'halfYear')">半年</el-button>
+                <el-button size="mini" type="primary" plain @click="setQuickDeadline('batch', 'oneYear')">明年</el-button>
+                <el-button size="mini" type="primary" plain @click="setQuickDeadline('batch', 'twoYears')">后年</el-button>
+              </div>
+            </el-form-item>
+            <el-form-item label="上次到期时间">
+              <el-date-picker
+                v-model="batchEditShopForm.beforeDeadline"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="不选则不修改"
+                size="small"
+                style="width: 100%;"
+              ></el-date-picker>
+              <div style="margin-top: 6px; display: flex; align-items: center; gap: 8px;">
+                <span style="font-size: 12px; color: #888;">快捷选择:</span>
+                <el-button size="mini" type="primary" plain @click="setQuickBeforeDeadline('batch')">此刻</el-button>
+              </div>
+            </el-form-item>
+            <el-form-item label="续费状态">
+              <el-radio-group v-model="batchEditShopForm.hasRenew">
+                <el-radio :label="-1">不修改</el-radio>
+                <el-radio :label="0">未续费</el-radio>
+                <el-radio :label="1">已续费</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-form>
+          <div slot="footer" class="dialog-footer">
+            <el-button size="small" @click="resetBatchEditShopForm">取 消</el-button>
+            <el-button type="primary" size="small" @click="putBatchEditShopForm">确 认</el-button>
+          </div>
+        </el-dialog>
+
     </div>
 </template>
 <script>
 import balanceDetail from './components/balance-detail';
 import amountDetail from './components/amount-detail';
+import dayjs from 'dayjs';
 export default {
     components:{balanceDetail,amountDetail},
     data() {
@@ -115,9 +251,38 @@ export default {
             },
             clearStockRule:{
               password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
+            },
+            editShopPop: false,
+            editShopForm: {
+              id: '',
+              merchantName: '',
+              shopName: '',
+              lklSjNo: '',
+              lklB2BTermNo: '',
+              lklScanTermNo: '',
+              deadline: '',
+              beforeDeadline: '',
+              hasRenew: 0
+            },
+            editShopRule: {},
+            editShopOriginalDeadline: '',
+            selectedShops: [],
+            batchEditShopPop: false,
+            batchEditShopForm: {
+              lklSjNo: '',
+              lklB2BTermNo: '',
+              lklScanTermNo: '',
+              deadline: '',
+              beforeDeadline: '',
+              hasRenew: -1
             }
         };
     },
+    computed: {
+      selectedShopsNames() {
+        return this.selectedShops.map(e => e.shopName || e.merchantName || ('ID:' + e.id)).join('、');
+      }
+    },
     mounted() {
     },
     methods: {
@@ -127,6 +292,116 @@ export default {
             this.app.refresh()
           })
         },
+        editShop(shop) {
+          this.editShopOriginalDeadline = shop.deadline || '';
+          this.editShopForm = {
+            id: shop.id,
+            merchantName: shop.merchantName || '',
+            shopName: shop.shopName || '',
+            lklSjNo: shop.lklSjNo || '',
+            lklB2BTermNo: shop.lklB2BTermNo || '',
+            lklScanTermNo: shop.lklScanTermNo || '',
+            deadline: shop.deadline || '',
+            beforeDeadline: shop.beforeDeadline || '',
+            hasRenew: shop.hasRenew != null ? Number(shop.hasRenew) : 0
+          };
+          this.editShopPop = true;
+        },
+        /**
+         * 到期时间快捷选择 (半年、明年、后年)
+         * @param {string} type 'edit' | 'batch'
+         * @param {string} durationKey 'halfYear' | 'oneYear' | 'twoYears'
+         */
+        setQuickDeadline(type, durationKey) {
+          let base = dayjs();
+          if (type === 'edit') {
+            const orig = this.editShopOriginalDeadline;
+            if (orig && dayjs(orig).isValid() && dayjs(orig).isAfter(dayjs())) {
+              base = dayjs(orig);
+            }
+          }
+
+          let resultDate = base;
+          if (durationKey === 'halfYear') {
+            resultDate = base.add(6, 'month');
+          } else if (durationKey === 'oneYear') {
+            resultDate = base.add(1, 'year');
+          } else if (durationKey === 'twoYears') {
+            resultDate = base.add(2, 'year');
+          }
+
+          const formatted = resultDate.format('YYYY-MM-DD HH:mm:ss');
+          if (type === 'edit') {
+            this.editShopForm.deadline = formatted;
+          } else if (type === 'batch') {
+            this.batchEditShopForm.deadline = formatted;
+          }
+        },
+        /**
+         * 上次到期时间快捷选择 (此刻)
+         * @param {string} type 'edit' | 'batch'
+         */
+        setQuickBeforeDeadline(type) {
+          const nowFormatted = dayjs().format('YYYY-MM-DD HH:mm:ss');
+          if (type === 'edit') {
+            this.editShopForm.beforeDeadline = nowFormatted;
+          } else if (type === 'batch') {
+            this.batchEditShopForm.beforeDeadline = nowFormatted;
+          }
+        },
+        resetEditShopForm() {
+          if (this.$refs['editShopRef']) {
+            this.$refs['editShopRef'].resetFields();
+          }
+          this.editShopPop = false;
+        },
+        putEditShopForm() {
+          this.$refs['editShopRef'].validate(valid => {
+            if (valid) {
+              this.$service.shop.update(this.editShopForm).then(() => {
+                this.$message.success('修改成功');
+                this.editShopPop = false;
+                this.app.refresh();
+              });
+            }
+          });
+        },
+        batchEditShop() {
+          if (this.selectedShops.length === 0) {
+            this.$message.warning('请先勾选要批量修改的门店');
+            return;
+          }
+          this.batchEditShopForm = {
+            lklSjNo: '',
+            lklB2BTermNo: '',
+            lklScanTermNo: '',
+            deadline: '',
+            beforeDeadline: '',
+            hasRenew: -1
+          };
+          this.batchEditShopPop = true;
+        },
+        resetBatchEditShopForm() {
+          this.batchEditShopPop = false;
+        },
+        putBatchEditShopForm() {
+          const form = this.batchEditShopForm;
+          if (!form.lklSjNo && !form.lklB2BTermNo && !form.lklScanTermNo && !form.deadline && !form.beforeDeadline && form.hasRenew === -1) {
+            this.$message.warning('请至少选择或填写一项要修改的内容');
+            return;
+          }
+          const ids = this.selectedShops.map(e => e.id);
+          const postData = {
+            ids: ids,
+            isBatch: 1,
+            ...form
+          };
+          this.$service.shop.update(postData).then(() => {
+            this.$message.success(`成功批量修改 ${ids.length} 家门店的信息`);
+            this.batchEditShopPop = false;
+            this.app.refresh();
+          });
+        },
         clearStock(shop){
           this.clearStockPop = true 
           this.clearStockForm.shopId = shop.id
@@ -158,6 +433,11 @@ export default {
             ctx.service(this.$service.shop)
                 .set('table', {
                     columns: [
+                        {
+                            type: 'selection',
+                            align: 'center',
+                            width: '50'
+                        },
                         {
                             prop: 'id',
                             label: 'ID',
@@ -213,14 +493,20 @@ export default {
                             align: 'center'
                         },
                         {
-                            prop: 'needRenew',
-                            label: '提醒',
-                            width: '90',
+                            prop: 'deadline',
+                            label: '到期时间',
+                            width: '120',
                             align: 'center'
                         },
                         {
-                            prop: 'deadline',
-                            label: '过期时间',
+                            prop: 'beforeDeadline',
+                            label: '上次到期',
+                            width: '120',
+                            align: 'center'
+                        },
+                        {
+                            prop: 'needRenew',
+                            label: '提醒',
                             width: '90',
                             align: 'center'
                         },
@@ -249,12 +535,17 @@ export default {
                     op: {
                       visible: true,
                       props: {
-                        width: 190,
+                        width: 240,
                         align: 'center',
                         fixed: 'right',
                         label: '操作'
                       },
                       layout: ['slot-apply']
+                    },
+                    on: {
+                      'selection-change': selection => {
+                        this.selectedShops = selection;
+                      }
                     }
                 })
                 .set('dict', {
@@ -264,14 +555,14 @@ export default {
                 })
                 .set('search', {
                   key: {
-                    placeholder: '请填写名称,支持拼音字母',
+                    placeholder: '请填写名称/手机号,支持拼音字母',
                   },
                 })
                 .set('pagination', {
                   size: 50
                 })
                 .set('layout', [
-                    ['slot-tabs'],['search-key'],
+                    ['slot-tabs'],['search-key', 'slot-batch-btn'],
                     ['flex1', 'refresh-btn'],
                     ['data-table'],
                     ['flex1', 'pagination']

+ 8 - 0
pt/src/service/shop/index.js

@@ -20,5 +20,13 @@ export class ShopService extends BaseService {
 		});
 	}
 
+	update(data) {
+		return this.request({
+			url: '/update',
+			method: 'POST',
+			data: data
+		});
+	}
+
 }
 export default new ShopService();