Просмотр исходного кода

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

shizhongqi 20 часов назад
Родитель
Сommit
8c14a827d3

+ 5 - 1
hdApp/src/admin/home/me.vue

@@ -384,8 +384,12 @@ export default {
       const number = Number(value) || 0;
       return number.toFixed(2);
     },
+    /**
+     * 续费:跳转年度会员开通页(与 cg/me 到期卡一致)
+     * 原 /admin/recharge/renew 已停用
+     */
     goRenew () {
-      //this.$util.pageTo({ url: "/admin/recharge/renew" })
+      this.$util.pageTo({ url: "/pagesClient/official/applyInvite", type: 2 })
     },
     handleMenuTap (item) {
       if (item.key == "address") {

+ 26 - 9
hdApp/src/admin/home/shopInviteHub.vue

@@ -1,6 +1,7 @@
 <!--
   邀请花店 / 我的分佣 hub
   用途:我的-邀请花店;最近邀请手机号来自接口 mobile(表 inviteeMobile 快照)
+  「我的邀请码」仅当当前门店 hasRenew=1 且 deadline 未过期时展示(接口 inviteCodeEligible)
 -->
 <template>
   <view class="hub-page app-content">
@@ -21,7 +22,7 @@
       </view>
     </view>
 
-    <view class="invite-code-card">
+    <view v-if="inviteCodeEligible" class="invite-code-card">
       <view class="code-head">
         <view class="code-icon-wrap">
           <text class="code-icon-text">邀</text>
@@ -111,6 +112,8 @@ export default {
         hdDiscountAmount: 0,
         inviteCount: 0
       },
+      /** 当前门店已续费且未到期时才为 true,控制「我的邀请码」模块 */
+      inviteCodeEligible: false,
       recentList: [],
       depositSubmitting: false
     }
@@ -128,6 +131,10 @@ export default {
       const comm = this.moneyText(this.invite.commissionAmount)
       const disc = this.moneyText(this.invite.hdDiscountAmount)
       return `花店开通账号并使用邀请码支付,花店享受${disc}元优惠,邀请人获得${comm}元佣金。`
+    },
+    /** 是否允许分享(须有码且门店邀请权益有效) */
+    canShareInvite () {
+      return this.inviteCodeEligible && !!this.invite.inviteCode
     }
   },
   onShow () {
@@ -177,6 +184,8 @@ export default {
       if (res.data.invite) {
         this.invite = { ...this.invite, ...res.data.invite }
       }
+      // 已续费且未到期才展示「我的邀请码」
+      this.inviteCodeEligible = Number(res.data.inviteCodeEligible) === 1
       this.recentList = res.data.recentList || []
     },
     onCommissionTip () {
@@ -222,7 +231,23 @@ export default {
         this.depositSubmitting = false
       }
     },
+    onShareInviteClick () {
+      if (!this.canShareInvite) {
+        uni.showToast({ title: '暂无邀请码', icon: 'none' })
+      }
+    },
+    onShareInvite () {
+      if (!this.canShareInvite) {
+        uni.showToast({ title: '暂无邀请码', icon: 'none' })
+        return
+      }
+      uni.showToast({ title: '请在微信小程序内分享邀请', icon: 'none' })
+    },
     copyInviteCode () {
+      if (!this.canShareInvite) {
+        uni.showToast({ title: '暂无邀请码', icon: 'none' })
+        return
+      }
       const code = this.invite.inviteCode
       if (!code) {
         uni.showToast({ title: '暂无邀请码', icon: 'none' })
@@ -235,14 +260,6 @@ export default {
         }
       })
     },
-    onShareInviteClick () {
-      if (!this.invite.inviteCode) {
-        uni.showToast({ title: '暂无邀请码', icon: 'none' })
-      }
-    },
-    onShareInvite () {
-      uni.showToast({ title: '请在微信小程序中使用分享', icon: 'none' })
-    },
     goInviteShopList () {
       this.$util.pageTo({ url: '/admin/home/shopInviteList' })
     },

+ 67 - 10
hdApp/src/pagesClient/official/applyInvite.vue

@@ -1,6 +1,7 @@
 <!--
   邀请开店 / 会员开通页
   用途:分享带邀请码新用户注册+支付;已开店用户续费(仅首次付费享邀请减免与佣金)
+  规则:自己的邀请码不带入;邀请人须 hasRenew=1 且未到期,否则提示「邀请码无效」且无减免,均可继续支付
 -->
 <template>
   <view class="apply-page app-content">
@@ -164,6 +165,8 @@
               @confirm="onInviteCodeBlur"
             />
           </view>
+          <!-- 邀请码无效提示:邀请人未续费或已到期,仍可支付(无减免) -->
+          <view v-if="inviteCodeStatusTip" class="fee-invite-tip">{{ inviteCodeStatusTip }}</view>
           <view class="fee-row fee-row--border">
             <text class="fee-label">会员费</text>
             <text class="fee-value">¥{{ formatMoney(feePreview.price) }}</text>
@@ -279,6 +282,10 @@ export default {
         memberTypeName: '年度会员',
         inviteValid: 0,
         inviteNotFound: 0,
+        /** 是否为自己的邀请码(前端不带入) */
+        inviteSelf: 0,
+        /** 邀请人未续费或已到期 */
+        inviteInvalid: 0,
         hdDiscountAmount: 0,
         showInviteDiscount: 0,
         payAmount: 0
@@ -329,6 +336,26 @@ export default {
     inviteBenefitAvailable () {
       return !this.hasPaidRegisterRenew
     },
+    /**
+     * 邀请码状态文案:无效时展示「邀请码无效」,不阻断支付
+     * 自邀会清空输入,不在此提示
+     */
+    inviteCodeStatusTip () {
+      if (!this.inviteBenefitAvailable) {
+        return ''
+      }
+      const code = (this.form.inviteCode || '').trim()
+      if (!code) {
+        return ''
+      }
+      if (this.feePreview.inviteInvalid) {
+        return '邀请码无效'
+      }
+      if (this.feePreview.inviteNotFound) {
+        return '邀请人不存在'
+      }
+      return ''
+    },
     /** 提交按钮文案 */
     submitBtnText () {
       if (this.hasShop && this.hasPaidRegisterRenew) {
@@ -578,6 +605,23 @@ export default {
         if (res.code === 1 && res.data) {
           this.syncInviteBenefitFlags(res.data)
           this.feePreview = { ...this.feePreview, ...res.data }
+          // 自己的邀请码:不带入输入框,可继续支付(无减免)
+          if (res.data.inviteSelf && code) {
+            this.form.inviteCode = ''
+            this.inviteCodeFromLink = ''
+            this.feePreview = {
+              ...this.feePreview,
+              inviteSelf: 1,
+              inviteValid: 0,
+              showInviteDiscount: 0,
+              hdDiscountAmount: 0,
+              payAmount: this.feePreview.price
+            }
+            if (notifyInvalid) {
+              uni.showToast({ title: '不能使用自己的邀请码', icon: 'none' })
+            }
+            return
+          }
           if (notifyInvalid && code !== '' && res.data.inviteNotFound) {
             uni.showToast({ title: '邀请人不存在', icon: 'none' })
           }
@@ -985,7 +1029,7 @@ export default {
       }
       this.confirmPayOnly()
     },
-    /** 已开店:保存邀请码并唤起支付(续费跳过邀请码) */
+    /** 已开店:保存邀请码并唤起支付(续费跳过邀请码;自邀/无效码仍可支付) */
     confirmPayOnly () {
       const that = this
       if (this.hasPaidRegisterRenew) {
@@ -993,10 +1037,13 @@ export default {
         that.startPayFlow(that.applyId)
         return
       }
-      const inviteCode = (this.form.inviteCode || '').trim()
-      if (inviteCode !== '' && this.feePreview.inviteNotFound) {
-        this.$msg('邀请人不存在')
-        return false
+      // 自邀已清空;无效/不存在由后端落库时清空邀请码,前端不阻断支付
+      let inviteCode = (this.form.inviteCode || '').trim()
+      if (
+        inviteCode &&
+        (this.feePreview.inviteSelf || this.feePreview.inviteInvalid || this.feePreview.inviteNotFound)
+      ) {
+        inviteCode = ''
       }
       uni.showLoading({ mask: true })
       saveRegisterInvite({ inviteCode }).then(res => {
@@ -1135,10 +1182,13 @@ export default {
     confirmFn () {
       restorePendingInviteFromLegacy()
       const inviteParams = getRegisterInviteParams(this.option)
-      const inviteCode = (this.form.inviteCode || '').trim()
-      if (inviteCode !== '' && this.feePreview.inviteNotFound) {
-        this.$msg('邀请人不存在')
-        return false
+      // 自邀已清空;无效/不存在提交时空邀请码,不阻断注册支付
+      let inviteCode = (this.form.inviteCode || '').trim()
+      if (
+        inviteCode &&
+        (this.feePreview.inviteSelf || this.feePreview.inviteInvalid || this.feePreview.inviteNotFound)
+      ) {
+        inviteCode = ''
       }
       uni.showLoading({ mask: true })
       const currentMiniOpenId = uni.getStorageSync('currentMiniOpenId')
@@ -1154,8 +1204,8 @@ export default {
         hdShopAdminId: inviteParams.hdShopAdminId,
         ghsShopAdminId: inviteParams.ghsShopAdminId,
         inviteGhsShopId: inviteParams.inviteGhsShopId,
-        inviteCode,
         ...this.form,
+        inviteCode,
         style,
         from,
         miniOpenId: currentMiniOpenId,
@@ -1266,6 +1316,13 @@ export default {
   padding: 12upx 0;
 }
 
+.fee-invite-tip {
+  padding: 0 0 16upx;
+  font-size: 24upx;
+  color: #e54d42;
+  line-height: 1.4;
+}
+
 .fee-row--border {
   border-top: 1upx solid #eef1f5;
 }

+ 53 - 14
mallApp/src/pages/home/shop-category.vue

@@ -1,6 +1,7 @@
 <!--
   店铺分类页(合并花材 + 花束)
-  用途:商城消费者在一个页面浏览花材/花束分类、公告、广告与商品
+  用途:商城消费者在一个页面浏览花材/花束分类、公告、广告与商品;
+  顶部搜索框点搜索/键盘确认后跳转商品列表页,并带上 searchText 关键词
   路由:pages/home/shop-category?account=xxx&hdId=xxx
 -->
 <template>
@@ -9,15 +10,20 @@
     <view class="page-top">
       <view class="search-section">
         <view class="search-input-wrap">
-          <text class="search-placeholder-icon">🔍</text>
+          <view class="search-icon">
+            <zui-svg-icon icon="general-search" :width="24" :height="24" color="#999" />
+          </view>
           <input
             class="search-input"
-            v-model="searchKeyword"
-            placeholder="搜索鲜花、花束、绿植等"
-            disabled
+            type="text"
+            confirm-type="search"
+            :value="searchKeyword"
+            placeholder="搜索商品"
+            @input="onSearchKeywordInput"
+            @confirm="goSearchToGoodsList"
           />
         </view>
-        <view class="search-btn">搜索</view>
+        <view class="search-btn" @tap.stop="goSearchToGoodsList">搜索</view>
       </view>
 
       <!-- 通知公告:点击文案进详情,点击 › 进列表 -->
@@ -75,7 +81,7 @@
             <block v-if="itemExpanded">
               <view
                 v-for="cls in normalItemClasses"
-                :key="'item-top-' + cls.classId"
+                :key="cls.classId"
                 class="item-cate-sub"
                 :class="{ 'item-cate-sub--active': isItemNavActive(cls.classId) }"
                 :data-id="cls.classId"
@@ -93,7 +99,7 @@
         <!-- 花束分类(category/list,平铺展示) -->
         <view
           v-for="cat in bouquetCategories"
-          :key="'bouquet-' + cat.id"
+          :key="cat.id"
           class="nav-item nav-item--bouquet"
           :class="{ 'nav-item--active': isBouquetNavActive(cat.id) }"
           :data-id="cat.id"
@@ -138,7 +144,7 @@
             <block v-if="itemExpanded">
               <view
                 v-for="cls in normalItemClasses"
-                :key="'item-bottom-' + cls.classId"
+                :key="cls.classId"
                 class="item-cate-sub"
                 :class="{ 'item-cate-sub--active': isItemNavActive(cls.classId) }"
                 :data-id="cls.classId"
@@ -182,7 +188,7 @@
           <view class="ad-dots" v-if="adList.length > 1">
             <view
               v-for="(ad, dotIndex) in adList"
-              :key="'dot-' + dotIndex"
+              :key="dotIndex"
               class="ad-dot"
               :class="{ 'ad-dot--active': currentAdIndex === dotIndex }"
             ></view>
@@ -256,9 +262,10 @@
     <!-- 花材自定义扎数弹窗 -->
     <modal-module
       :show="isModel"
+      :use-custom-content="true"
       @cancel="modalCancel"
       @click="affirm"
-      :title="customData.itemName"
+      :title="modalTitle"
       color="#333"
       :size="32"
       padding="30rpx 30rpx"
@@ -383,6 +390,7 @@ export default {
       },
       cartItemShow: false,
       limitBuyWarnList: [],
+      /** 分类页搜索框输入,点搜索后带到 goods/list 的 searchText */
       searchKeyword: '',
       hdId: 0,
       shopAccount: '',
@@ -462,6 +470,11 @@ export default {
     },
     bouquetEmptyTitle() {
       return this.bouquetCategories.length ? '暂无商品' : '暂无分类'
+    },
+    /** 弹窗标题:保证传给 modal 的是字符串,避免空对象/异常类型 */
+    modalTitle() {
+      const name = this.customData && this.customData.itemName
+      return name ? String(name) : ''
     }
   },
   onShareAppMessage() {
@@ -509,6 +522,27 @@ export default {
     syncLoginStyle() {
       this.globalLoginStyle = this.$util.isEmpty(this.loginInfo) ? 0 : 1
     },
+    /**
+     * 同步搜索框内容(不用 v-model,与首页 topNav 一致,避免小程序端取值不稳)
+     */
+    onSearchKeywordInput(e) {
+      this.searchKeyword = (e && e.detail && e.detail.value != null) ? e.detail.value : ''
+    },
+    /**
+     * 分类页搜索:跳转商品列表并带上 searchText,由 list 页回填并请求
+     */
+    goSearchToGoodsList() {
+      const keyword = (this.searchKeyword || '').trim()
+      if (!keyword) {
+        this.$msg('请输入搜索内容')
+        return
+      }
+      const account = this.shopAccount || uni.getStorageSync('account') || ''
+      const hdId = this.hdId || uni.getStorageSync('hdId') || 0
+      this.$util.pageTo({
+        url: `/pages/goods/list?account=${account}&hdId=${hdId}&searchText=${encodeURIComponent(keyword)}`
+      })
+    },
     /** 拉取门店信息,同步 hdId 后再请求花材接口 */
     loadShopInfo() {
       return getInfo({ hdId: this.hdId }).then((res) => {
@@ -1074,10 +1108,15 @@ export default {
   border-radius: 36upx;
 }
 
-.search-placeholder-icon {
-  font-size: 28upx;
-  margin-right: 12upx;
+.search-icon {
   flex-shrink: 0;
+  width: 48upx;
+  height: 48upx;
+  margin-right: 8upx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  line-height: 1;
 }
 
 .search-input {

+ 53 - 30
mallApp/src/pages/interest/level.vue

@@ -20,22 +20,28 @@
       </view>
       <view class="level-list-wrap">
         <view class="level-list-tit">
-          <view>会员等级</view>
-          <view>成长值</view>
-          <view>折扣</view>
+          <view class="col-icon">图标</view>
+          <view class="col-level">等级</view>
+          <view class="col-growth">成长值</view>
+          <view class="col-discount">折扣</view>
         </view>
         <view class="level-list-blo">
           <view class="level-block" v-for="(item, index) in list" :key="index">
             <view class="level-list">
-              <view class="level-name-cell">
-                <appVipModule
+              <view class="col-icon">
+                <!-- 等级图标:雪碧图 member sheet,name 为 member-levle1~5 -->
+                <sprite-icon
                   class="level-img"
-                  :vipImgUrl="`${constant.imgUrl}/retail/member/grade/${item.level}.png`"
+                  :name="memberLevelIconName(item)"
+                  sheet="member"
+                  :height="64"
                 />
+              </view>
+              <view class="col-level">
                 <text class="level-name">{{ item.name || '会员' }}</text>
               </view>
-              <view>{{ formatGrowth(item) }}</view>
-              <view>{{ formatDiscount(item.discount) }}</view>
+              <view class="col-growth">{{ formatGrowth(item) }}</view>
+              <view class="col-discount">{{ formatDiscount(item.discount) }}</view>
             </view>
             <!-- 生日权益 / 会员权益说明:与 hd 后台设置字段一致,有内容才展示 -->
             <view class="level-extra" v-if="item.birthdayBenefit || item.benefitDesc">
@@ -62,14 +68,12 @@
 </template>
 
 <script>
-import AppVipModule from "@/components/module/app-vip";
 import AppTrademarkModule from "@/components/module/app-trademark";
 import { userGrade } from "@/api/member";
 
 export default {
   name: "interest-rank",
   components: {
-    AppVipModule,
     AppTrademarkModule
   },
   data() {
@@ -107,6 +111,20 @@ export default {
         }
       });
     },
+    /**
+     * 会员等级雪碧图 name:member-levle1~5(拼写与 sprite 图集一致)
+     * 优先 level/member,越界时回落到 1
+     */
+    memberLevelIconName(item) {
+      let level = Number((item && (item.level != null ? item.level : item.member)) || 1);
+      if (!level || level < 1) {
+        level = 1;
+      }
+      if (level > 5) {
+        level = 5;
+      }
+      return "member-levle" + level;
+    },
     /** 成长值门槛:优先 amount(新字段),兼容旧 growth */
     formatGrowth(item) {
       const val = item.amount != null && item.amount !== "" ? item.amount : item.growth;
@@ -171,29 +189,37 @@ export default {
     font-size: 26upx;
     .level-list-tit,
     .level-list {
+      border-bottom: 1upx solid #eeeeee;
       @include disFlex(center, space-between);
-      & > view {
-        margin: 0 auto;
-        flex: 1;
+      .col-icon,
+      .col-level,
+      .col-growth,
+      .col-discount {
         @include disFlex(center, center);
-        height: 70upx;
+        min-height: 88upx;
+        box-sizing: border-box;
+      }
+      .col-icon {
+        width: 140upx;
+        flex-shrink: 0;
+      }
+      .col-level {
+        flex: 1.2;
+        padding: 0 8upx;
+      }
+      .col-growth,
+      .col-discount {
+        flex: 1;
       }
       .level-img {
-        ::v-deep.vip-text {
-          left: 42upx;
-          transform: scale(0.8);
-        }
+        flex-shrink: 0;
       }
-    }
-    .level-name-cell {
-      flex-direction: column !important;
-      height: auto !important;
-      padding: 12upx 0;
       .level-name {
-        margin-top: 4upx;
-        font-size: 22upx;
-        color: #666;
-        line-height: 1.2;
+        font-size: 26upx;
+        color: #333;
+        text-align: center;
+        line-height: 1.3;
+        word-break: break-all;
       }
     }
     .level-list-tit {
@@ -208,9 +234,6 @@ export default {
     }
     .level-block {
       border-top: 1upx solid #eeeeee;
-      &:nth-child(2n) {
-        background-color: #f7f7ed;
-      }
       &:first-child {
         border-top: none;
       }

+ 260 - 248
mallApp/src/utils/mainIndexSprite.js

@@ -1,248 +1,260 @@
-/**
- * 雪碧图注册表
- * 用途:sprite-icon 按 sheet + name 从多张 CDN 雪碧图裁切显示图标
- * 图片地址随 ext.json / 环境 imgHost 变化,必须用函数运行时读取,不能用 this.$constant
- */
-import constant from "@/constant/index";
-
-/** 默认图床(ext 未配置 imgHost 时的兜底) */
-const DEFAULT_IMG_HOST = "https://img.huaml.com";
-
-/** 未传 sheet 时默认使用的雪碧图 key */
-const DEFAULT_SHEET_KEY = "main";
-
-/**
- * 雪碧图注册表:每张图含 path、原图尺寸、icons 坐标
- */
-export const SPRITE_SHEETS = {
-  main: {
-    path: "/hhb/sprite_icon/main-index.webp?time=2607171640",
-    width: 500,
-    height: 500,
-    icons: {
-      "dengji-biandong": { x: 14, y: 17, w: 32, h: 32 },
-      "yue-biandong": { x: 60, y: 16, w: 32, h: 32 },
-      "huiyuan-quanyi": { x: 102, y: 14, w: 33, h: 32 },
-      "lianxikefu": { x: 145, y: 17, w: 32, h: 32 },
-      "huangguan": { x: 189, y: 20, w: 22, h: 21 },
-      "shezhi": { x: 13, y: 62, w: 34, h: 36 },
-      "shaixuan": { x: 71, y: 67, w: 25, h: 25 },
-      "dianpu-fill": { x: 116, y: 63, w: 31, h: 30 },
-      "huoche-cheng": { x: 171, y: 68, w: 23, h: 21 },
-      "huoche-lv": { x: 204, y: 68, w: 23, h: 21 },
-      "gou-quan": { x: 246, y: 68, w: 22, h: 21 },
-      "huashu": { x: 4, y: 118, w: 52, h: 49 },
-      "hua": { x: 94, y: 118, w: 52, h: 49 },
-      "dianpu-line": { x: 167, y: 114, w: 51, h: 51 },
-      "tuichu": { x: 243, y: 111, w: 49, h: 51 },
-      "xiaoxi": { x: 8, y: 186, w: 45, h: 45 },
-      "qianbao": { x: 68, y: 182, w: 55, h: 50 },
-      "huoche-da": { x: 142, y: 182, w: 60, h: 50 },
-      "baoguo": { x: 219, y: 182, w: 50, h: 50 },
-      "fanghu-yuan": { x: 282, y: 182, w: 42, h: 50 },
-      "youhuiquan-yellow": { x: 344, y: 184, w: 50, h: 50 },
-      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
-      "icon-home": { x: 274, y: 102, w: 52, h: 52 },
-      "icon-home-active": { x: 274, y: 102, w: 52, h: 52 },
-      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
-	  "icon-home": { x: 7, y: 245, w: 50, h: 50 },
-	  "icon-home-active": { x: 60, y: 245, w: 50, h: 50 },
-	  "icon-category": { x: 116, y: 245, w: 50, h: 50 },
-	  "icon-category-active": { x: 176, y: 245, w: 50, h: 50 },
-	  "icon-cart": { x: 238, y: 245, w: 50, h: 50 },
-	  "icon-cart-active": { x: 293, y: 245, w: 50, h: 50 },
-	  "icon-order": { x: 366, y: 245, w: 50, h: 50 },
-	  "icon-order-active": { x: 418, y: 245 , w: 50, h: 50 },
-	  "cart-delete": { x: 7, y: 298 , w: 46, h: 52 },//购物车-删除
-	  "notice": { x: 216, y: 15 , w: 31, h: 31 },//公告通知
-    }
-  },
-  /** 进货/供货商专用雪碧图,坐标待设计稿测量后补全 */
-  order: {
-    path: "/hhb/sprite_icon/icon-order.webp?time=26072009",
-    width: 600,
-    height: 600,
-    icons: {
-		"ps-shop": { x: 257, y: 8 , w: 57, h: 52 },//商家配送
-		"ps-shop-active": { x: 190, y: 8 , w: 57, h: 52 },//商家配送-active
-		"ps-paotui": { x: 198, y: 63 , w: 60, h: 52 },//跑腿配送
-		"ps-paotui-active": { x: 129, y: 63 , w: 60, h: 52 },//跑腿配送-active
-		"ps-daodian": { x: 383, y: 8 , w: 48, h: 50 },//到店自取
-		"ps-daodian-active": { x: 324, y: 8 , w: 49, h: 50 },//到店自取-active
-		"ps-kuaidi": { x: 72, y: 63 , w: 49, h: 50 },//快递
-		"ps-kuaidi-active": { x: 12, y: 63 , w: 49, h: 50 },//快递-active
-		"ps-wuliu": { x: 441, y: 8 , w: 67, h: 52 },//物流
-		"ps-wuliu-active": { x: 518, y: 8 , w: 67, h: 52 },//物流-active
-		"huoche": { x: 7, y: 8 , w: 46, h: 50 },//配送说明-小货车
-		"youhuiquan": { x: 267, y: 63 , w: 58, h: 50 },//优惠券
-		"beizhu": { x: 12, y: 8 , w: 50, h: 50 },//订单-备注
-		"shijian": { x: 70, y: 8 , w: 52, h: 52 },//配送时间
-		"shijianduan": { x: 129, y: 8 , w: 52, h: 52 },//配送时间段
-		"dingwei": { x: 339, y: 63 , w: 42, h: 51 },//定位
-	}
-  }
-};
-
-/** tabBar 未选中 / 选中 图标名映射(main 雪碧图) */
-export const TAB_SPRITE_MAP = {
-  home: { normal: "dianpu-line", active: "dianpu-fill" },
-  order: { normal: "huoche-lv", active: "huoche-da" },
-  mine: { normal: "gou-quan", active: "qianbao" }
-};
-
-/** @deprecated 兼容旧引用 */
-export const MAIN_INDEX_SPRITE = {
-  width: SPRITE_SHEETS.main.width,
-  height: SPRITE_SHEETS.main.height
-};
-
-/** @deprecated 兼容旧引用 */
-export const MAIN_INDEX_ICONS = SPRITE_SHEETS.main.icons;
-
-function buildIconSheetIndex(sheets) {
-  const index = {};
-  Object.keys(sheets).forEach((sheetKey) => {
-    const icons = sheets[sheetKey].icons || {};
-    Object.keys(icons).forEach((name) => {
-      if (index[name] && index[name] !== sheetKey && process.env.NODE_ENV !== "production") {
-        console.warn(`[sprite] 图标名 "${name}" 在 ${index[name]} 与 ${sheetKey} 中重复,以 ${sheetKey} 为准`);
-      }
-      index[name] = sheetKey;
-    });
-  });
-  return index;
-}
-
-const ICON_SHEET_INDEX = buildIconSheetIndex(SPRITE_SHEETS);
-
-function getImgHostBase() {
-  let base = (constant.imgUrl || DEFAULT_IMG_HOST).replace(/\/$/, "");
-  if (base.indexOf("http://") === 0) {
-    base = `https://${base.slice(7)}`;
-  }
-  return base;
-}
-
-function rpx2px(rpx) {
-  if (typeof uni !== "undefined" && typeof uni.upx2px === "function") {
-    return uni.upx2px(rpx);
-  }
-  return rpx;
-}
-
-/**
- * 计算图标显示宽高与缩放比(支持按宽度 size 或按高度 height 等比缩放)
- * @param {{ w: number, h: number }} icon 雪碧图坐标中的原始宽高
- * @param {{ size?: number|string, height?: number|string }} opts
- * @returns {{ displayW: number, displayH: number, scale: number }}
- */
-function resolveSpriteDisplaySize(icon, opts = {}) {
-  const heightNum = Number(opts.height);
-  const useHeight = opts.height !== "" && opts.height != null && !Number.isNaN(heightNum) && heightNum > 0;
-
-  let scale;
-  let displayW;
-  let displayH;
-
-  if (useHeight) {
-    displayH = Math.round(heightNum);
-    scale = displayH / icon.h;
-    displayW = Math.round(icon.w * scale);
-  } else {
-    const sizeNum = Number(opts.size);
-    displayW = !Number.isNaN(sizeNum) && sizeNum > 0 ? sizeNum : 48;
-    scale = displayW / icon.w;
-    displayH = Math.round(icon.h * scale);
-  }
-
-  return { displayW, displayH, scale };
-}
-
-export function resolveSprite(name, sheetKey = "") {
-  if (!name) {
-    return null;
-  }
-  let key = sheetKey;
-  if (!key) {
-    key = ICON_SHEET_INDEX[name] || DEFAULT_SHEET_KEY;
-  }
-  const sheet = SPRITE_SHEETS[key];
-  if (!sheet) {
-    return null;
-  }
-  const icon = (sheet.icons || {})[name];
-  if (!icon) {
-    return null;
-  }
-  return { sheetKey: key, sheet, icon };
-}
-
-export function getSpriteSheetUrl(sheetKey = DEFAULT_SHEET_KEY) {
-  const sheet = SPRITE_SHEETS[sheetKey];
-  if (!sheet) {
-    return "";
-  }
-  return `${getImgHostBase()}${sheet.path}`;
-}
-
-export function spriteImageStyle(name, size = 48, sheetKey = "", height = "") {
-  const resolved = resolveSprite(name, sheetKey);
-  if (!resolved) {
-    if (process.env.NODE_ENV !== "production") {
-      console.warn(`[sprite-icon] 未找到图标: name=${name}${sheetKey ? ` sheet=${sheetKey}` : ""}`);
-    }
-    return { wrap: "", inner: "", image: "" };
-  }
-  const { sheet, icon } = resolved;
-  const { displayW, displayH, scale } = resolveSpriteDisplaySize(icon, { size, height });
-  const sheetW = sheet.width * scale;
-  const sheetH = sheet.height * scale;
-  const left = -rpx2px(icon.x * scale);
-  const top = -rpx2px(icon.y * scale);
-  const sheetWPx = rpx2px(sheetW);
-  const sheetHPx = rpx2px(sheetH);
-  return {
-    wrap: `position:relative;width:${displayW}rpx;height:${displayH}rpx;overflow:hidden;line-height:0;`,
-    inner: `position:absolute;left:${left}px;top:${top}px;width:${sheetWPx}px;height:${sheetHPx}px;`,
-    image: "width:100%;height:100%;display:block;"
-  };
-}
-
-export function spriteStyle(name, size = 48, sheetKey = "", height = "") {
-  const resolved = resolveSprite(name, sheetKey);
-  if (!resolved) {
-    return "";
-  }
-  const { sheetKey: key, sheet, icon } = resolved;
-  const { displayW, displayH, scale } = resolveSpriteDisplaySize(icon, { size, height });
-  const sheetW = sheet.width * scale;
-  const sheetH = sheet.height * scale;
-  const posX = rpx2px(icon.x * scale);
-  const posY = rpx2px(icon.y * scale);
-  const sheetWPx = rpx2px(sheetW);
-  const sheetHPx = rpx2px(sheetH);
-  const url = getSpriteSheetUrl(key);
-  return [
-    `width:${displayW}rpx`,
-    `height:${displayH}rpx`,
-    `background-image:url(${url})`,
-    `background-size:${sheetWPx}px ${sheetHPx}px`,
-    `background-position:-${posX}px -${posY}px`,
-    "background-repeat:no-repeat",
-    "display:inline-block",
-    "flex-shrink:0",
-    "vertical-align:middle"
-  ].join(";");
-}
-
-export function getMainIndexSpriteUrl() {
-  return getSpriteSheetUrl(DEFAULT_SHEET_KEY);
-}
-
-export function mainIndexSpriteImageStyle(name, size = 48, height = "") {
-  return spriteImageStyle(name, size, DEFAULT_SHEET_KEY, height);
-}
-
-export function mainIndexSpriteStyle(name, size = 48, height = "") {
-  return spriteStyle(name, size, DEFAULT_SHEET_KEY, height);
-}
+/**
+ * 雪碧图注册表
+ * 用途:sprite-icon 按 sheet + name 从多张 CDN 雪碧图裁切显示图标
+ * 图片地址随 ext.json / 环境 imgHost 变化,必须用函数运行时读取,不能用 this.$constant
+ */
+import constant from "@/constant/index";
+
+/** 默认图床(ext 未配置 imgHost 时的兜底) */
+const DEFAULT_IMG_HOST = "https://img.huaml.com";
+
+/** 未传 sheet 时默认使用的雪碧图 key */
+const DEFAULT_SHEET_KEY = "main";
+
+/**
+ * 雪碧图注册表:每张图含 path、原图尺寸、icons 坐标
+ */
+export const SPRITE_SHEETS = {
+  main: {
+    path: "/hhb/sprite_icon/main-index.webp?time=2607171640",
+    width: 500,
+    height: 500,
+    icons: {
+      "dengji-biandong": { x: 14, y: 17, w: 32, h: 32 },
+      "yue-biandong": { x: 60, y: 16, w: 32, h: 32 },
+      "huiyuan-quanyi": { x: 102, y: 14, w: 33, h: 32 },
+      "lianxikefu": { x: 145, y: 17, w: 32, h: 32 },
+      "huangguan": { x: 189, y: 20, w: 22, h: 21 },
+      "shezhi": { x: 13, y: 62, w: 34, h: 36 },
+      "shaixuan": { x: 71, y: 67, w: 25, h: 25 },
+      "dianpu-fill": { x: 116, y: 63, w: 31, h: 30 },
+      "huoche-cheng": { x: 171, y: 68, w: 23, h: 21 },
+      "huoche-lv": { x: 204, y: 68, w: 23, h: 21 },
+      "gou-quan": { x: 246, y: 68, w: 22, h: 21 },
+      "huashu": { x: 4, y: 118, w: 52, h: 49 },
+      "hua": { x: 94, y: 118, w: 52, h: 49 },
+      "dianpu-line": { x: 167, y: 114, w: 51, h: 51 },
+      "tuichu": { x: 243, y: 111, w: 49, h: 51 },
+      "xiaoxi": { x: 8, y: 186, w: 45, h: 45 },
+      "qianbao": { x: 68, y: 182, w: 55, h: 50 },
+      "huoche-da": { x: 142, y: 182, w: 60, h: 50 },
+      "baoguo": { x: 219, y: 182, w: 50, h: 50 },
+      "fanghu-yuan": { x: 282, y: 182, w: 42, h: 50 },
+      "youhuiquan-yellow": { x: 344, y: 184, w: 50, h: 50 },
+      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
+      "icon-home": { x: 274, y: 102, w: 52, h: 52 },
+      "icon-home-active": { x: 274, y: 102, w: 52, h: 52 },
+      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
+	  "icon-home": { x: 7, y: 245, w: 50, h: 50 },
+	  "icon-home-active": { x: 60, y: 245, w: 50, h: 50 },
+	  "icon-category": { x: 116, y: 245, w: 50, h: 50 },
+	  "icon-category-active": { x: 176, y: 245, w: 50, h: 50 },
+	  "icon-cart": { x: 238, y: 245, w: 50, h: 50 },
+	  "icon-cart-active": { x: 293, y: 245, w: 50, h: 50 },
+	  "icon-order": { x: 366, y: 245, w: 50, h: 50 },
+	  "icon-order-active": { x: 418, y: 245 , w: 50, h: 50 },
+	  "cart-delete": { x: 7, y: 298 , w: 46, h: 52 },//购物车-删除
+	  "notice": { x: 216, y: 15 , w: 31, h: 31 },//公告通知
+    }
+  },
+  /** 进货/供货商专用雪碧图,坐标待设计稿测量后补全 */
+  order: {
+    path: "/hhb/sprite_icon/icon-order.webp?time=26072009",
+    width: 600,
+    height: 600,
+    icons: {
+		"ps-shop": { x: 257, y: 8 , w: 57, h: 52 },//商家配送
+		"ps-shop-active": { x: 190, y: 8 , w: 57, h: 52 },//商家配送-active
+		"ps-paotui": { x: 198, y: 63 , w: 60, h: 52 },//跑腿配送
+		"ps-paotui-active": { x: 129, y: 63 , w: 60, h: 52 },//跑腿配送-active
+		"ps-daodian": { x: 383, y: 8 , w: 48, h: 50 },//到店自取
+		"ps-daodian-active": { x: 324, y: 8 , w: 49, h: 50 },//到店自取-active
+		"ps-kuaidi": { x: 72, y: 63 , w: 49, h: 50 },//快递
+		"ps-kuaidi-active": { x: 12, y: 63 , w: 49, h: 50 },//快递-active
+		"ps-wuliu": { x: 441, y: 8 , w: 67, h: 52 },//物流
+		"ps-wuliu-active": { x: 518, y: 8 , w: 67, h: 52 },//物流-active
+		"huoche": { x: 7, y: 8 , w: 46, h: 50 },//配送说明-小货车
+		"youhuiquan": { x: 267, y: 63 , w: 58, h: 50 },//优惠券
+		"beizhu": { x: 12, y: 8 , w: 50, h: 50 },//订单-备注
+		"shijian": { x: 70, y: 8 , w: 52, h: 52 },//配送时间
+		"shijianduan": { x: 129, y: 8 , w: 52, h: 52 },//配送时间段
+		"dingwei": { x: 339, y: 63 , w: 42, h: 51 },//定位
+	}
+  },
+  member: {
+    path: "/hhb/sprite_icon/icon-member.webp",
+    width: 500,
+    height: 500,
+    icons: {
+  		"member-levle1": { x: 0, y: 0, w: 80, h: 80 },
+  		"member-levle2": { x: 80, y: 0, w: 80, h: 80 },
+  		"member-levle3": { x: 160, y: 0, w: 80, h: 80 },
+  		"member-levle4": { x: 240, y: 0, w: 80, h: 80 },
+  		"member-levle5": { x: 320, y: 0, w: 80, h: 80 },
+	}
+  }
+};
+
+/** tabBar 未选中 / 选中 图标名映射(main 雪碧图) */
+export const TAB_SPRITE_MAP = {
+  home: { normal: "dianpu-line", active: "dianpu-fill" },
+  order: { normal: "huoche-lv", active: "huoche-da" },
+  mine: { normal: "gou-quan", active: "qianbao" }
+};
+
+/** @deprecated 兼容旧引用 */
+export const MAIN_INDEX_SPRITE = {
+  width: SPRITE_SHEETS.main.width,
+  height: SPRITE_SHEETS.main.height
+};
+
+/** @deprecated 兼容旧引用 */
+export const MAIN_INDEX_ICONS = SPRITE_SHEETS.main.icons;
+
+function buildIconSheetIndex(sheets) {
+  const index = {};
+  Object.keys(sheets).forEach((sheetKey) => {
+    const icons = sheets[sheetKey].icons || {};
+    Object.keys(icons).forEach((name) => {
+      if (index[name] && index[name] !== sheetKey && process.env.NODE_ENV !== "production") {
+        console.warn(`[sprite] 图标名 "${name}" 在 ${index[name]} 与 ${sheetKey} 中重复,以 ${sheetKey} 为准`);
+      }
+      index[name] = sheetKey;
+    });
+  });
+  return index;
+}
+
+const ICON_SHEET_INDEX = buildIconSheetIndex(SPRITE_SHEETS);
+
+function getImgHostBase() {
+  let base = (constant.imgUrl || DEFAULT_IMG_HOST).replace(/\/$/, "");
+  if (base.indexOf("http://") === 0) {
+    base = `https://${base.slice(7)}`;
+  }
+  return base;
+}
+
+function rpx2px(rpx) {
+  if (typeof uni !== "undefined" && typeof uni.upx2px === "function") {
+    return uni.upx2px(rpx);
+  }
+  return rpx;
+}
+
+/**
+ * 计算图标显示宽高与缩放比(支持按宽度 size 或按高度 height 等比缩放)
+ * @param {{ w: number, h: number }} icon 雪碧图坐标中的原始宽高
+ * @param {{ size?: number|string, height?: number|string }} opts
+ * @returns {{ displayW: number, displayH: number, scale: number }}
+ */
+function resolveSpriteDisplaySize(icon, opts = {}) {
+  const heightNum = Number(opts.height);
+  const useHeight = opts.height !== "" && opts.height != null && !Number.isNaN(heightNum) && heightNum > 0;
+
+  let scale;
+  let displayW;
+  let displayH;
+
+  if (useHeight) {
+    displayH = Math.round(heightNum);
+    scale = displayH / icon.h;
+    displayW = Math.round(icon.w * scale);
+  } else {
+    const sizeNum = Number(opts.size);
+    displayW = !Number.isNaN(sizeNum) && sizeNum > 0 ? sizeNum : 48;
+    scale = displayW / icon.w;
+    displayH = Math.round(icon.h * scale);
+  }
+
+  return { displayW, displayH, scale };
+}
+
+export function resolveSprite(name, sheetKey = "") {
+  if (!name) {
+    return null;
+  }
+  let key = sheetKey;
+  if (!key) {
+    key = ICON_SHEET_INDEX[name] || DEFAULT_SHEET_KEY;
+  }
+  const sheet = SPRITE_SHEETS[key];
+  if (!sheet) {
+    return null;
+  }
+  const icon = (sheet.icons || {})[name];
+  if (!icon) {
+    return null;
+  }
+  return { sheetKey: key, sheet, icon };
+}
+
+export function getSpriteSheetUrl(sheetKey = DEFAULT_SHEET_KEY) {
+  const sheet = SPRITE_SHEETS[sheetKey];
+  if (!sheet) {
+    return "";
+  }
+  return `${getImgHostBase()}${sheet.path}`;
+}
+
+export function spriteImageStyle(name, size = 48, sheetKey = "", height = "") {
+  const resolved = resolveSprite(name, sheetKey);
+  if (!resolved) {
+    if (process.env.NODE_ENV !== "production") {
+      console.warn(`[sprite-icon] 未找到图标: name=${name}${sheetKey ? ` sheet=${sheetKey}` : ""}`);
+    }
+    return { wrap: "", inner: "", image: "" };
+  }
+  const { sheet, icon } = resolved;
+  const { displayW, displayH, scale } = resolveSpriteDisplaySize(icon, { size, height });
+  const sheetW = sheet.width * scale;
+  const sheetH = sheet.height * scale;
+  const left = -rpx2px(icon.x * scale);
+  const top = -rpx2px(icon.y * scale);
+  const sheetWPx = rpx2px(sheetW);
+  const sheetHPx = rpx2px(sheetH);
+  return {
+    wrap: `position:relative;width:${displayW}rpx;height:${displayH}rpx;overflow:hidden;line-height:0;`,
+    inner: `position:absolute;left:${left}px;top:${top}px;width:${sheetWPx}px;height:${sheetHPx}px;`,
+    image: "width:100%;height:100%;display:block;"
+  };
+}
+
+export function spriteStyle(name, size = 48, sheetKey = "", height = "") {
+  const resolved = resolveSprite(name, sheetKey);
+  if (!resolved) {
+    return "";
+  }
+  const { sheetKey: key, sheet, icon } = resolved;
+  const { displayW, displayH, scale } = resolveSpriteDisplaySize(icon, { size, height });
+  const sheetW = sheet.width * scale;
+  const sheetH = sheet.height * scale;
+  const posX = rpx2px(icon.x * scale);
+  const posY = rpx2px(icon.y * scale);
+  const sheetWPx = rpx2px(sheetW);
+  const sheetHPx = rpx2px(sheetH);
+  const url = getSpriteSheetUrl(key);
+  return [
+    `width:${displayW}rpx`,
+    `height:${displayH}rpx`,
+    `background-image:url(${url})`,
+    `background-size:${sheetWPx}px ${sheetHPx}px`,
+    `background-position:-${posX}px -${posY}px`,
+    "background-repeat:no-repeat",
+    "display:inline-block",
+    "flex-shrink:0",
+    "vertical-align:middle"
+  ].join(";");
+}
+
+export function getMainIndexSpriteUrl() {
+  return getSpriteSheetUrl(DEFAULT_SHEET_KEY);
+}
+
+export function mainIndexSpriteImageStyle(name, size = 48, height = "") {
+  return spriteImageStyle(name, size, DEFAULT_SHEET_KEY, height);
+}
+
+export function mainIndexSpriteStyle(name, size = 48, height = "") {
+  return spriteStyle(name, size, DEFAULT_SHEET_KEY, height);
+}