ソースを参照

零售端 分销分享处理,分类页、商城首页、商品详情页(秒杀、拼团、花束、花材)

ouyang 6 日 前
コミット
21b4462cfb

+ 1 - 2
hdApp/src/admin/member/distributionContrib.vue

@@ -24,7 +24,7 @@
         </view>
       </picker>
       <text v-else class="month-text month-text--static">{{ dateName || '全部时间' }}</text>
-      <text class="month-total">月分红 ¥{{ formatAmount(monthCommission) }}</text>
+      <text class="month-total">月分红 ¥{{ formatAmount(monthCommission) }}</text>
     </view>
 
     <element-loading :loading="listLoading" text="加载中" color="#09c567">
@@ -94,7 +94,6 @@ export default {
         { value: 'all', label: '全部' },
         { value: '0', label: '待结算' },
         { value: '1', label: '已结算' },
-        { value: '2', label: '已失效' }
       ],
       queryParams: {
         searchTime: 'byMonth',

+ 5 - 0
mallApp/src/api/distribution/index.js

@@ -23,3 +23,8 @@ export const getDistributionInviteList = data => {
 export const getDistributionContribOrderList = data => {
   return https.get('/distribution/get-contrib-order-list', data)
 }
+
+/** 可存分红全额存入花店余额 */
+export const postDepositAvail = data => {
+  return https.post('/distribution/deposit-avail', data)
+}

+ 14 - 1
mallApp/src/api/shop/index.js

@@ -1,7 +1,20 @@
 import https from '@/plugins/luch-request_0.0.7/request'
+import { getShareInviterCustomId, clearShareInviter } from '@/utils/launchScene'
 
+/** 门店详情;有缓存的 inviterCustomId 时一并提交,供分享拉新绑定 */
 export const getInfo = data => {
-	return https.get('/shop/info', data)
+	const params = Object.assign({}, data || {})
+	const account = uni.getStorageSync('account') || ''
+	const inviterCustomId = getShareInviterCustomId(account)
+	if (inviterCustomId > 0) {
+		params.inviterCustomId = inviterCustomId
+	}
+	return https.get('/shop/info', params).then(res => {
+		if (res && res.code === 1 && res.data && Number(res.data.inviteBound) === 1) {
+			clearShareInviter(account)
+		}
+		return res
+	})
 }
 
 /** *

+ 1 - 1
mallApp/src/components/wangCg.vue

@@ -87,7 +87,7 @@ export default {
     confirmFn() {
       let currentMiniOpenId = uni.getStorageSync('miniOpenId')
       let that = this
-      create({miniOpenId: currentMiniOpenId, ...this.form}).then(res => {
+      create({ miniOpenId: currentMiniOpenId, ...this.form }).then(res => {
         if(res.code == 1){
           uni.setStorageSync(TOKEN_STORAGE_KEY, res.data.token)
           if(res.data.user && !that.$util.isEmpty(res.data.user)){

+ 60 - 59
mallApp/src/mixins/globalMixins.js

@@ -1,59 +1,60 @@
-import { pageTo } from '@/utils/util.js'
-import { wxLoginFn } from '@/utils/auth'
-import { mapGetters } from 'vuex'
-import { TOKEN_STORAGE_KEY } from '@/constant/storageKeys'
-import { applyLaunchQuery, persistLaunchStorage } from '@/utils/launchScene'
-
-export default {
-	data() {
-		return {
-			option: null,
-			constant: this.$constant,
-			$util: this.$util,
-			pageTo: pageTo,
-			//0没有登录 1有登录
-			globalLoginStyle :0
-		}
-	},
-	computed: {
-		...mapGetters({ loginInfo: "getLoginInfo" })
-	},
-	// 页面生命周期才触发
-	onLoad: function (option) {
-		let that = this
-		option = applyLaunchQuery(option || {})
-		this.option = option
-
-		if (option.token) {
-			uni.setStorageSync(TOKEN_STORAGE_KEY, option.token)
-		}
-
-		persistLaunchStorage(option)
-
-		//线上检查小程序的登录态是否有效 checkSession
-		//checkSession开发环境无效,在真机调试有效
-		if (process.env.NODE_ENV == "production") {
-			uni.checkSession({
-				success() {
-					console.log('checkSession success')
-				},
-				fail() {
-					uni.removeStorageSync(TOKEN_STORAGE_KEY);
-				}
-			})
-		}
-		if (that.$util.isEmpty(that.loginInfo)) {
-			wxLoginFn().then(wxRes=>{
-				if (typeof that.init === 'function') {
-					that.init()
-				}
-			})
-		}else{
-			if (typeof that.init === 'function') {
-				that.init()
-			}
-		}
-	}
-
-}
-
+import { pageTo } from '@/utils/util.js'
+import { wxLoginFn } from '@/utils/auth'
+import { mapGetters } from 'vuex'
+import { TOKEN_STORAGE_KEY } from '@/constant/storageKeys'
+import { applyLaunchQuery, persistLaunchStorage, persistShareInviter } from '@/utils/launchScene'
+
+export default {
+	data() {
+		return {
+			option: null,
+			constant: this.$constant,
+			$util: this.$util,
+			pageTo: pageTo,
+			//0没有登录 1有登录
+			globalLoginStyle :0
+		}
+	},
+	computed: {
+		...mapGetters({ loginInfo: "getLoginInfo" })
+	},
+	// 页面生命周期才触发
+	onLoad: function (option) {
+		let that = this
+		option = applyLaunchQuery(option || {})
+		this.option = option
+
+		if (option.token) {
+			uni.setStorageSync(TOKEN_STORAGE_KEY, option.token)
+		}
+
+		persistLaunchStorage(option)
+		persistShareInviter(option)
+
+		//线上检查小程序的登录态是否有效 checkSession
+		//checkSession开发环境无效,在真机调试有效
+		if (process.env.NODE_ENV == "production") {
+			uni.checkSession({
+				success() {
+					console.log('checkSession success')
+				},
+				fail() {
+					uni.removeStorageSync(TOKEN_STORAGE_KEY);
+				}
+			})
+		}
+		if (that.$util.isEmpty(that.loginInfo)) {
+			wxLoginFn().then(wxRes=>{
+				if (typeof that.init === 'function') {
+					that.init()
+				}
+			})
+		}else{
+			if (typeof that.init === 'function') {
+				that.init()
+			}
+		}
+	}
+
+}
+

+ 7 - 1
mallApp/src/pages/billing/affirmMix.vue

@@ -1201,7 +1201,7 @@ export default {
 			const numOk = minNum <= 0 || bigNum >= minNum;
 			return !(amountOk || numOk);
 		},
-		/** 同步未达最低消费时的附加运费/包装费(与后端 PsMethodClass.calcFee 一致) */
+		/** 同步未达最低消费时的附加运费/包装费(与后端 PsMethodClass.calcFee 一致,按购买方式每日一次) */
 		syncUnMeetFee() {
 			const method = this.currentPsMethod;
 			this.form.unMeetSendCost = 0;
@@ -1215,8 +1215,14 @@ export default {
 				return;
 			}
 			if (unMeet === 0) {
+				if (Number(method.todaySendFeeLocked) === 1) {
+					return;
+				}
 				this.form.unMeetSendCost = fee;
 			} else if (unMeet === 2) {
+				if (Number(method.todayPackFeeLocked) === 1) {
+					return;
+				}
 				this.form.packCost = fee;
 			}
 		},

+ 28 - 9
mallApp/src/pages/custom/myDividend.vue

@@ -105,7 +105,7 @@
           </view>
         </view>
         <view class="deposit-modal-confirm" :class="{ 'deposit-modal-confirm--disabled': depositSubmitting }" @click="confirmDeposit">
-          <text class="deposit-modal-confirm-text">{{ depositSubmitting ? '存入中...' : '确认存入' }}</text>
+          <text class="deposit-modal-confirm-text">{{ depositSubmitting ? '存入中...' : '确认存入 ¥' + formatAmount(stat.availDeposit) }}</text>
         </view>
         <view class="deposit-modal-tip">
           <view class="deposit-modal-tip-head">
@@ -131,7 +131,7 @@
 <script>
 import AppWrapperEmpty from '@/components/app-wrapper-empty'
 import SpriteIcon from '@/components/sprite-icon/index.vue'
-import { getDistributionMyStat, getDistributionFlowList } from '@/api/distribution'
+import { getDistributionMyStat, getDistributionFlowList, postDepositAvail } from '@/api/distribution'
 
 export default {
   name: 'myDividend',
@@ -202,18 +202,37 @@ export default {
       }
       this.showDepositModal = false
     },
-    /** 确认存入:后端接口未上线前先提示 */
-    confirmDeposit() {
+    /** 确认存入:全额 availDeposit 转入花店余额 */
+    async confirmDeposit() {
       if (this.depositSubmitting) {
         return
       }
+      const amount = Number(this.stat.availDeposit)
+      if (!amount || amount <= 0) {
+        this.$msg('暂无可存入分红')
+        return
+      }
       this.depositSubmitting = true
-      // 接口上线后在此调用 deposit API 并刷新 loadPageData
-      setTimeout(() => {
+      try {
+        const account = this.option.account || uni.getStorageSync('account') || ''
+        const hdId = this.option.hdId || uni.getStorageSync('hdId') || ''
+        const res = await postDepositAvail({ account, hdId })
+        if (res.code == 1) {
+          this.showDepositModal = false
+          if (res.data && res.data.stat) {
+            this.stat = Object.assign({}, this.stat, res.data.stat)
+          } else {
+            await this.loadPageData()
+          }
+          this.$msg('存入成功')
+        } else {
+          this.$msg(res.msg || '存入失败')
+        }
+      } catch (e) {
+        this.$msg('存入失败,请稍后再试')
+      } finally {
         this.depositSubmitting = false
-        this.showDepositModal = false
-        this.$msg('功能即将上线,请稍后再试')
-      }, 300)
+      }
     },
     goFlowList() {
       const account = this.option.account || uni.getStorageSync('account') || ''

+ 85 - 8
mallApp/src/pages/goods/detail.vue

@@ -109,6 +109,7 @@ import productMins from "@/mixins/cgProduct";
 import { mapGetters } from "vuex";
 import { getDetail } from "@/api/goods";
 import { getHdInfo } from "@/api/hd";
+import { getInfo } from "@/api/shop";
 import RichMediaViewer from "@/components/RichMediaViewer/index.vue";
 export default {
   name: "detail",
@@ -137,6 +138,8 @@ export default {
       common_title: "",
       common_previewContent: [],
       shopInfo: {},
+      /** 分享 path 追加 inviterCustomId 用,不改变原有 id/account 参数 */
+      myCustomId: 0,
       /** 秒杀活动信息:来自首页秒杀专区/更多列表跳转带过来的 query,仅用于展示与购物车标记;
        * 实际下单价格由后端独立核对 Redis 秒杀配置,避免前端数据被篡改 */
       activityType: '',
@@ -160,6 +163,17 @@ export default {
   beforeDestroy() {
     this.clearSeckillTimer();
   },
+  onShareAppMessage() {
+    const path = this.buildGoodsSharePath()
+    const sharePrice = this.data && this.data.priceType == 1
+      ? (this.data.price ? parseFloat(this.data.price).toFixed(2) : 0)
+      : '面议'
+    return {
+      title: ((this.data && this.data.name) || '') + ' ¥' + sharePrice,
+      path,
+      imageUrl: (this.data && (this.data.cover || this.data.smallCover)) || ''
+    }
+  },
   onPageScroll(e) {
     // console.log('Scroll position:', parseInt(e.scrollTop))
     // 通过 ref 调用子组件的方法
@@ -252,6 +266,7 @@ export default {
           if (res.code == 1 && res.data.hd && res.data.hd.id) {
             this.hdId = res.data.hd.id;
           }
+          this.syncMyCustomId();
           this.loadGoodsDetail();
         });
         return;
@@ -259,6 +274,65 @@ export default {
       this.loadGoodsDetail();
     },
 
+    /** 拉取当前用户 custom.id,仅用于分享追加 inviterCustomId */
+    syncMyCustomId() {
+      const hdId = Number(this.hdId) || Number(this.option.hdId) || Number(uni.getStorageSync('hdId')) || 0
+      if (!hdId) {
+        return Promise.resolve()
+      }
+      return getInfo({ hdId }).then((res) => {
+        if (res.code === 1 && res.data.custom && res.data.custom.id) {
+          this.myCustomId = Number(res.data.custom.id)
+        }
+      })
+    },
+    /** 在分享 path 后追加 inviterCustomId */
+    appendInviterCustomId(path) {
+      if (this.myCustomId <= 0) {
+        return path
+      }
+      const sep = path.indexOf('?') >= 0 ? '&' : '?'
+      return path + sep + 'inviterCustomId=' + this.myCustomId
+    },
+    /**
+     * 分享 path:与进入详情页时的 query 一致(this.option),排除 hdId,再追加 inviterCustomId
+     */
+    buildGoodsSharePath() {
+      const opt = this.option || {}
+      const skipKeys = {
+        hdId: true,
+        token: true,
+        inviterCustomId: true,
+        inviterId: true,
+        scene: true
+      }
+      const pairs = []
+      Object.keys(opt).forEach((key) => {
+        if (skipKeys[key]) {
+          return
+        }
+        const val = opt[key]
+        if (val === undefined || val === null || val === '') {
+          return
+        }
+        pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(val)))
+      })
+      if (!opt.id && this.data && this.data.id) {
+        pairs.push('id=' + encodeURIComponent(String(this.data.id)))
+      }
+      if (!opt.account && !opt.shopId) {
+        const acc = uni.getStorageSync('account')
+        if (acc) {
+          pairs.push('account=' + encodeURIComponent(String(acc)))
+        }
+      }
+      let path = 'pages/goods/detail'
+      if (pairs.length) {
+        path += '?' + pairs.join('&')
+      }
+      return this.appendInviterCustomId(path)
+    },
+
     initializeOption(option) {
       this.shopInfo = {
         ...this.shopInfo,
@@ -401,15 +475,18 @@ export default {
         const sharePrice = res.data.priceType == 1
           ? (res.data.price ? parseFloat(res.data.price).toFixed(2) : 0)
           : '面议';
-        const shareParams = {
-          title: (res.data.name || '') + ' ¥' + sharePrice,
-          desc: sharePrice,
-          imgUrl: res.data.cover || res.data.smallCover || '',
-          pagePath: `pages/goods/detail?id=${id}&account=${account}`
+        const applyShare = () => {
+          const shareParams = {
+            title: (res.data.name || '') + ' ¥' + sharePrice,
+            desc: sharePrice,
+            imgUrl: res.data.cover || res.data.smallCover || '',
+            pagePath: this.buildGoodsSharePath()
+          };
+          if (typeof this.jweixinFn === 'function') {
+            this.jweixinFn(shareParams);
+          }
         };
-        if (typeof this.jweixinFn === 'function') {
-          this.jweixinFn(shareParams);
-        }
+        this.syncMyCustomId().finally(applyShare);
       });
     },
     // 更换数量

+ 16 - 1
mallApp/src/pages/home/index.vue

@@ -115,7 +115,9 @@ export default {
       /** 门店信息,用于补充 merchantName(get-home 未返回时兜底) */
       shopInfo: null,
       /** 底部 tab 购物车角标数量(本地缓存非响应式,需 onShow 主动刷新) */
-      cartBadgeCount: 0
+      cartBadgeCount: 0,
+      /** 当前用户在该店的 xhCustom.id,分享 path 带 inviterCustomId */
+      myCustomId: 0
     }
   },
   created() {
@@ -128,6 +130,16 @@ export default {
   onShow() {
     this.refreshCartBadgeCount()
   },
+  onShareAppMessage() {
+    let path = 'pages/home/index?account=' + this.account
+    if (this.myCustomId > 0) {
+      path += '&inviterCustomId=' + this.myCustomId
+    }
+    return {
+      title: this.merchantName || '店铺首页',
+      path
+    }
+  },
   computed: {
     /** 商户名称:优先 get-home.topNav,其次 /shop/info 与本地缓存 */
     merchantName() {
@@ -207,6 +219,9 @@ export default {
             this.shopInfo = shopRes.data.info
             uni.setStorageSync('currentShop', shopRes.data.info)
           }
+          if (shopRes && shopRes.code === 1 && shopRes.data.custom && shopRes.data.custom.id) {
+            this.myCustomId = Number(shopRes.data.custom.id)
+          }
         })
         .finally(() => {
           this.loading = false

+ 11 - 1
mallApp/src/pages/home/shop-category.vue

@@ -387,6 +387,8 @@ export default {
       searchKeyword: '',
       hdId: 0,
       shopAccount: '',
+      /** 当前登录用户在该店的 xhCustom.id,用于分享带 inviterCustomId */
+      myCustomId: 0,
       categorySetting: {
         itemCateName: '',
         isItemCateTop: 0,
@@ -464,9 +466,14 @@ export default {
     }
   },
   onShareAppMessage() {
+    const account = this.shopAccount || uni.getStorageSync('account') || ''
+    let path = 'pages/home/shop-category?account=' + account
+    if (this.myCustomId > 0) {
+      path += '&inviterCustomId=' + this.myCustomId
+    }
     return {
       title: '商城分类',
-      path: 'pages/home/shop-category?account=' + (this.shopAccount || '')
+      path
     }
   },
   onShow() {
@@ -516,6 +523,9 @@ export default {
         if (res.data.info) {
           uni.setStorageSync('currentShop', res.data.info)
         }
+        if (res.data.custom && res.data.custom.id) {
+          this.myCustomId = Number(res.data.custom.id)
+        }
       })
     },
     /** 并行加载:先读 xhCategorySetting,再按配置拉花材/花束 */

+ 79 - 3
mallApp/src/pages/item/detail.vue

@@ -70,6 +70,7 @@ import SelPopup from "./components/sel-popup.vue";
 import { mapGetters } from "vuex";
 import { getMallItemInfo } from "@/api/item";
 import { getHdInfo } from "@/api/hd";
+import { getInfo } from "@/api/shop";
 import productMins from "@/mixins/cgProduct";
 import { share } from "@/mixins";
 export default {
@@ -96,6 +97,8 @@ export default {
 			pageType: "cg",
       autoLoad:false,
       hdId:0,
+      /** 分享 path 追加 inviterCustomId 用,不改变进入页 query(除 hdId) */
+      myCustomId: 0,
       previewContent: []
     };
   },
@@ -115,6 +118,19 @@ export default {
         this.globalLoginStyle = 0
       }
   },
+  onShareAppMessage() {
+    const path = this.buildItemSharePath()
+    let skPrice = this.data.skPrice ? parseFloat(this.data.skPrice) : 0
+    let title = (this.data.name || '花材详情') + ' ¥' + skPrice
+    if (this.data.discountPrice && Number(this.data.discountPrice) > 0) {
+      title = '【推荐】' + title
+    }
+    return {
+      title,
+      path,
+      imageUrl: this.data.cover || ''
+    }
+  },
   computed: {
     ...mapGetters({ shopUser: "getShopUser",loginInfo:"getLoginInfo" })
   },
@@ -170,14 +186,70 @@ export default {
             if (res.data.hd && res.data.hd.id) {
               this.hdId = res.data.hd.id
             }
+            this.syncMyCustomId()
             that.getItemDetail(id, account)
           }
         })
       } else {
+        this.syncMyCustomId()
         that.getItemDetail(id, account)
       }
     },
-    getItemDetail(id,account){
+    syncMyCustomId() {
+      const hdId = Number(this.hdId) || Number((this.option && this.option.hdId)) || Number(uni.getStorageSync('hdId')) || 0
+      if (!hdId) {
+        return Promise.resolve()
+      }
+      return getInfo({ hdId }).then((res) => {
+        if (res.code === 1 && res.data.custom && res.data.custom.id) {
+          this.myCustomId = Number(res.data.custom.id)
+        }
+      })
+    },
+    appendInviterCustomId(path) {
+      if (this.myCustomId <= 0) {
+        return path
+      }
+      const sep = path.indexOf('?') >= 0 ? '&' : '?'
+      return path + sep + 'inviterCustomId=' + this.myCustomId
+    },
+    /** 分享 path:与进入详情页 query(this.option)一致,排除 hdId,再追加 inviterCustomId */
+    buildItemSharePath() {
+      const opt = this.option || {}
+      const skipKeys = {
+        hdId: true,
+        token: true,
+        inviterCustomId: true,
+        inviterId: true,
+        scene: true
+      }
+      const pairs = []
+      Object.keys(opt).forEach((key) => {
+        if (skipKeys[key]) {
+          return
+        }
+        const val = opt[key]
+        if (val === undefined || val === null || val === '') {
+          return
+        }
+        pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(val)))
+      })
+      if (!opt.id && this.data && this.data.id) {
+        pairs.push('id=' + encodeURIComponent(String(this.data.id)))
+      }
+      if (!opt.account && !opt.shopId) {
+        const acc = uni.getStorageSync('account')
+        if (acc) {
+          pairs.push('account=' + encodeURIComponent(String(acc)))
+        }
+      }
+      let path = 'pages/item/detail'
+      if (pairs.length) {
+        path += '?' + pairs.join('&')
+      }
+      return this.appendInviterCustomId(path)
+    },
+    getItemDetail(id, account){
         getMallItemInfo({id: id}).then(res => {
           if (this.$util.isEmpty(res.data)){
             return false
@@ -201,9 +273,13 @@ export default {
             title: title,
             desc: skPrice,
             imgUrl: this.data.cover,
-            pagePath: `pages/item/detail?id=${id}&account=${account}`
+            pagePath: this.buildItemSharePath()
           };
-          this.jweixinFn(shareParams)
+          const applyShare = () => {
+            shareParams.pagePath = this.buildItemSharePath()
+            this.jweixinFn(shareParams)
+          }
+          this.syncMyCustomId().finally(applyShare)
         })
     },
     changeNum(e) {

+ 37 - 0
mallApp/src/utils/launchScene.js

@@ -115,6 +115,43 @@ export function persistLaunchStorage(option) {
 	uni.setStorageSync('currentQuery', JSON.stringify(option))
 }
 
+/** 分享邀请人 customId 的 storage key(按门店隔离) */
+export function getShareInviterStorageKey(account) {
+	const shopId = account || uni.getStorageSync('account') || ''
+	return shopId ? 'shareInviter_' + shopId : ''
+}
+
+/**
+ * 缓存分享链接中的邀请人 xhCustom.id,供注册后拉新绑定
+ */
+export function persistShareInviter(option) {
+	const inviterCustomId = option && option.inviterCustomId ? Number(option.inviterCustomId) : 0
+	const account = option && option.account ? option.account : uni.getStorageSync('account')
+	const key = getShareInviterStorageKey(account)
+	if (!key || inviterCustomId <= 0) {
+		return
+	}
+	uni.setStorageSync(key, inviterCustomId)
+}
+
+/** 读取当前门店缓存的邀请人 ID */
+export function getShareInviterCustomId(account) {
+	const key = getShareInviterStorageKey(account)
+	if (!key) {
+		return 0
+	}
+	const val = uni.getStorageSync(key)
+	return val ? Number(val) : 0
+}
+
+/** 绑定成功后清除邀请人缓存 */
+export function clearShareInviter(account) {
+	const key = getShareInviterStorageKey(account)
+	if (key) {
+		uni.removeStorageSync(key)
+	}
+}
+
 /**
  * 热启动:根据 getEnterOptionsSync 判断是否需要 reLaunch 到展开 query 的目标页
  * @returns {boolean} 是否已触发 reLaunch