Răsfoiți Sursa

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

ouyang 2 zile în urmă
părinte
comite
3238eb61da

+ 130 - 107
hdApp/src/admin/goods/manage.vue

@@ -18,21 +18,23 @@
     <view v-if="activeType === 'goods'" class="panel">
       <view class="search-row">
         <button class="admin-button-com middle small-btn blue" @click="changeSearchType">
-          <text>{{ searchTypeLabel }}</text>
-          <zui-svg-icon class="search-type-icon" icon="general-switch" :width="14" :height="14" color="#fff" />
+          <view class="small-btn-inner">
+            <text class="small-btn-text">{{ searchTypeLabel }}</text>
+            <zui-svg-icon class="search-type-icon" icon="general-switch" :width="14" :height="14" color="#fff" />
+          </view>
         </button>
-        <view class="search-box has-search-btn">
+        <view class="search-box">
           <text class="search-icon">⌕</text>
           <input v-model="searchText" class="search-input" placeholder="请输入" confirm-type="search" @confirm="searchGoods" />
-          <button class="admin-button-com middle blue search-btn" @click="searchGoods">搜索</button>
         </view>
+        <button class="admin-button-com middle blue add-btn" @click="addCurrent">添花束</button>
       </view>
 
       <view class="filter-row">
         <view class="filter-tabs">
-          <view class="filter-tab" :class="{ active: priceType === -1 }" @click="switchPriceTab(-1)">全部</view>
-          <view class="filter-tab" :class="{ active: priceType === 0 }" @click="switchPriceTab(0)">无价</view>
-          <view class="filter-tab" :class="{ active: priceType === 1 }" @click="switchPriceTab(1)">有价</view>
+          <button class="filter-chip" :class="{ active: priceType === -1 }" @click="switchPriceTab(-1)">全部</button>
+          <button class="filter-chip" :class="{ active: priceType === 0 }" @click="switchPriceTab(0)">无价</button>
+          <button class="filter-chip" :class="{ active: priceType === 1 }" @click="switchPriceTab(1)">有价</button>
         </view>
         <view class="action-btns">
           <button class="admin-button-com middle blue tool-btn" @click="openFilterPanel"> <zui-svg-icon class="tool-btn-icon" icon="general-filter" :width="14" :height="14" color="#fff" />筛选</button>
@@ -108,36 +110,24 @@
       </view>
     </view>
 
-    <view v-else class="panel">
-      <!-- 已开通批发店:进入花材页仅展示引导,不加载零售端花材列表 -->
-      <view v-if="pfItemLocked" class="pf-item-tip-panel">
-        <view class="pf-item-tip-card">
-          <text class="pf-item-tip-text">请到批发端修改花材</text>
-          <view class="pf-item-tip-actions">
-            <button class="admin-button-com middle default pf-tip-btn" @click="switchType('goods')">返回花束</button>
-            <button class="admin-button-com middle blue pf-tip-btn" @click="goToPfMiniProgram">去批发端</button>
-          </view>
-        </view>
-      </view>
-
-      <block v-else>
+    <view v-else class="panel item-panel">
       <view class="search-row">
-        <button class="admin-button-com middle blue image-class-btn" @click="selectFlowerNumFn">图片分类</button>
+        <button class="admin-button-com middle blue image-class-btn" @click="selectFlowerNumFn">图片筛选</button>
         <view class="search-box">
           <text class="search-icon">⌕</text>
-          <input v-model="globalPy" class="search-input" placeholder="请输入/搜索商品名称" confirm-type="search" @confirm="searchItem" />
+          <input v-model="globalPy" class="search-input" placeholder="请输入" confirm-type="search" @confirm="searchItem" />
         </view>
-        <button class="admin-button-com middle blue search-btn" @click="searchItem">搜索</button>
+        <!-- 锁定时仍展示,由蒙板挡住点击,与下方花材列表一致 -->
+        <button class="admin-button-com middle blue add-btn" @click="addCurrent">添花材</button>
       </view>
 
       <view class="filter-row">
-        <view class="filter-tabs item-filter-tabs">
-          <view class="filter-tab" :class="{ active: globalLookStock === 0 }" @click="changeHasStock(0)">全部</view>
-          <view class="filter-tab" :class="{ active: globalLookStock === 1 }" @click="changeHasStock(1)">有库存</view>
-          <view class="filter-tab" :class="{ active: globalLookStock === 2 }" @click="changeHasStock(2)">没库存</view>
+        <view class="filter-tabs">
+          <button class="filter-chip" :class="{ active: globalLookStock === 0 }" @click="changeHasStock(0)">全部</button>
+          <button class="filter-chip" :class="{ active: globalLookStock === 1 }" @click="changeHasStock(1)">有库存</button>
+          <button class="filter-chip" :class="{ active: globalLookStock === 2 }" @click="changeHasStock(2)">没库存</button>
         </view>
         <view class="action-btns">
-          <button class="admin-button-com middle blue tool-btn tool-btn--wide" @click="selectFlowerNumFn"><zui-svg-icon class="tool-btn-icon" icon="general-filter" :width="14" :height="14" color="#fff" />筛选</button>
           <button class="admin-button-com middle blue tool-btn" @click="refreshItem"><zui-svg-icon class="tool-btn-icon" icon="general-refresh" :width="14" :height="14" color="#fff" />刷新</button>
         </view>
       </view>
@@ -255,14 +245,23 @@
           <app-wrapper-empty v-else title="暂无花材" :is-empty="$util.isEmpty(globalItemData)" />
         </scroll-view>
       </view>
-      </block>
-    </view>
 
-    <button
-      v-if="!(activeType === 'item' && pfItemLocked)"
-      class="admin-button-com big blue footer-btn"
-      @click="addCurrent"
-    >{{ activeType === 'goods' ? '新增花束' : '新增花材' }}</button>
+      <!-- 已开通批发店:底下仍展示花材,中间蒙板引导去批发端维护 -->
+      <view
+        v-if="pfItemLocked"
+        class="pf-item-tip-mask"
+        @touchmove.stop.prevent
+        @click.stop
+      >
+        <view class="pf-item-tip-card" @click.stop>
+          <text class="pf-item-tip-text">请到批发端修改</text>
+          <view class="pf-item-tip-actions">
+            <button class="admin-button-com middle default pf-tip-btn" @click="switchType('goods')">返回花束栏</button>
+            <button class="admin-button-com middle blue pf-tip-btn" @click="goToPfMiniProgram">去批发端</button>
+          </view>
+        </view>
+      </view>
+    </view>
 
     <view v-if="showFilterPanel" class="filter-overlay" @click="closeFilterPanel">
       <view class="filter-panel" @click.stop>
@@ -307,7 +306,7 @@
       </view>
     </view>
 
-    <!-- 花材引导已改为页内居中提示(pfItemLocked),不再使用弹框 -->
+    <!-- 花材引导:pfItemLocked 时蒙板盖在列表上,不再使用弹框 -->
 
     <modal-module :show="printLabelShow" @click="dialogInputConfirm" :maskClosable="true" title="数量" padding="30rpx 30rpx">
       <template v-slot:content>
@@ -515,7 +514,7 @@ export default {
       this.getAppIdList = res.data
     })
     this.getCategoryData()
-    // 深链直接进花材时走同一套切换逻辑(已开通批发店则页内引导)
+    // 深链直接进花材时走同一套切换逻辑(已开通批发店则列表+蒙板引导)
     if (options && options.type === "item") {
       this.switchType("item")
     }
@@ -533,17 +532,18 @@ export default {
   methods: {
     /**
      * 切换花束 / 花材面板。
-     * 已开通花材批发店(pfShopId>0)时仍进入花材页,但锁定为页内引导(不加载零售花材)
-     * 未开通时切换并按需懒加载花材数据。
+     * 已开通花材批发店(pfShopId>0)时仍进入花材页并加载列表,再用蒙板引导去批发端
+     * 未开通时正常切换并按需懒加载花材数据。
      * @param {string} type goods | item
      */
     switchType(type) {
       if (type === this.activeType) return
       if (type === "item") {
         this.activeType = "item"
-        // 零售店已绑定批发店:花材应在批发端维护,页内提示并引导跳转销花宝
+        // 零售店已绑定批发店:底下照常展示花材,蒙板提示去批发端维护
         if (Number(this.getMyShopInfo && this.getMyShopInfo.pfShopId) > 0) {
           this.pfItemLocked = true
+          this.ensureItemLoaded()
           return
         }
         this.pfItemLocked = false
@@ -1289,25 +1289,34 @@ page {
   flex-direction: column;
 }
 
-/* 已开通批发店时的花材页内引导:居中卡片,替代原弹框 */
-.pf-item-tip-panel {
-  flex: 1;
+.item-panel {
+  position: relative;
+}
+
+/* 批发店锁定:半透明蒙板盖住花材操作区;卡片宽度缩 1/4,高度(内边距/字号)保持原样 */
+.pf-item-tip-mask {
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  z-index: 20;
   display: flex;
   align-items: center;
   justify-content: center;
   padding: 40upx 48upx;
   box-sizing: border-box;
-  background: #f5f6f8;
+  background: rgba(0, 0, 0, 0.45);
 }
 
 .pf-item-tip-card {
-  width: 100%;
-  max-width: 620upx;
+  width: 75%;
+  max-width: 465upx;
   background: #fff;
   border-radius: 16upx;
-  padding: 56upx 40upx 40upx;
+  padding: 56upx 30upx 40upx;
   box-sizing: border-box;
-  box-shadow: 0 4upx 24upx rgba(0, 0, 0, 0.06);
+  box-shadow: 0 8upx 32upx rgba(0, 0, 0, 0.12);
 }
 
 .pf-item-tip-text {
@@ -1343,26 +1352,43 @@ page {
 
 .small-btn,
 .image-class-btn,
-.search-btn,
 .tool-btn {
   box-sizing: border-box;
   white-space: nowrap;
 }
 
-/* 名称/编号合并按钮:文字+切换图标同一行,宽度随内容伸缩 */
+/* 名称/编号合并按钮:内层 flex,保证文字与切换图标垂直居中 */
 .admin-button-com.middle.small-btn {
   width: auto;
   min-width: 120upx;
   height: 68upx;
   padding: 0 16upx;
+  line-height: 1;
   display: inline-flex;
   align-items: center;
   justify-content: center;
+  box-sizing: border-box;
+}
+
+.small-btn-inner {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+}
+
+.small-btn-text {
+  line-height: 1;
+  font-size: 26upx;
 }
 
 .search-type-icon {
   flex-shrink: 0;
   margin-left: 6upx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
 .image-class-btn {
@@ -1370,12 +1396,14 @@ page {
   font-size: 24upx;
 }
 
+/* 搜索框:图标与输入文字垂直居中 */
 .search-box {
   flex: 1;
-  height: 64upx;
+  height: 68upx;
   border: 2upx solid #e2e2e2;
   border-radius: 34upx;
   display: flex;
+  flex-direction: row;
   align-items: center;
   padding: 0 24upx;
   box-sizing: border-box;
@@ -1383,39 +1411,42 @@ page {
   margin-left: 8upx;
 }
 
-/* 内嵌搜索按钮时收紧右侧内边距,让按钮贴在圆角框内 */
-.search-box.has-search-btn {
-  padding-right: 6upx;
-}
-
 .search-icon {
-  font-size: 42upx;
+  font-size: 32upx;
+  line-height: 1;
   color: #8d8d8d;
   margin-right: 12upx;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  height: 100%;
 }
 
 .search-input {
   flex: 1;
-  height: 60upx;
+  height: 64upx;
+  line-height: 64upx;
   font-size: 26upx;
   color: #333;
+  padding: 0;
+  margin: 0;
+  background: transparent;
 }
 
-.search-btn {
-  width: 108upx;
-}
-
-/* 花束搜索框内的搜索按钮:压低高度以免撑破圆角输入框 */
-.search-box .search-btn {
-  height: 54upx;
-  padding-top: 0;
-  padding-bottom: 0;
+/* 搜索行右侧:添花束 / 添花材 — 高度对齐左侧按钮,与搜索框间隔 10upx */
+.admin-button-com.middle.add-btn {
+  width: auto;
+  min-width: 120upx;
+  height: 68upx;
+  line-height: 68upx;
+  padding: 0 18upx;
+  margin: 0 0 0 10upx;
   flex-shrink: 0;
+  font-size: 26upx;
   display: inline-flex;
   align-items: center;
   justify-content: center;
-  font-size: 24upx;
-  border-radius: 26upx;
+  box-sizing: border-box;
 }
 
 .filter-row {
@@ -1429,44 +1460,49 @@ page {
 .filter-tabs {
   display: flex;
   align-items: center;
+  flex: 1;
+  min-width: 0;
+  margin-right: 16upx;
 }
 
-.filter-tab + .filter-tab {
-  margin-left: 50upx;
-}
-
-.item-filter-tabs .filter-tab + .filter-tab {
-  margin-left: 42upx;
+/* 左侧状态筛选:描边浅色按钮,与右侧实心绿按钮区分;高度与 tool-btn 一致 */
+.filter-chip {
+  height: 68upx;
+  padding: 0 18upx;
+  margin: 0;
+  font-size: 24upx;
+  line-height: 1;
+  color: #666;
+  background: #f5f6f8;
+  border: 2upx solid #e5e7eb;
+  border-radius: 8upx;
+  box-sizing: border-box;
+  white-space: nowrap;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
 }
 
-.filter-tab {
-  height: 56upx;
-  line-height: 56upx;
-  font-size: 26upx;
-  color: #111;
-  font-weight: 600;
-  position: relative;
-  white-space: nowrap;
+.filter-chip::after {
+  border: none;
 }
 
-.filter-tab.active {
-  color: #22a500;
+.filter-chip + .filter-chip {
+  margin-left: 12upx;
 }
 
-.filter-tab.active::after {
-  content: "";
-  position: absolute;
-  left: 0;
-  right: 0;
-  bottom: -4upx;
-  height: 4upx;
-  background: #22a500;
-  border-radius: 4upx;
+.filter-chip.active {
+  color: #09C567;
+  background: #eefaf3;
+  border-color: #09C567;
+  font-weight: 600;
 }
 
 .action-btns {
   display: flex;
   align-items: center;
+  flex-shrink: 0;
 }
 
 .action-btns > * + * {
@@ -1866,19 +1902,6 @@ page {
   font-weight: normal;
 }
 
-.footer-btn {
-  flex-shrink: 0;
-  margin: 14upx 18upx calc(14upx + env(safe-area-inset-bottom));
-  padding: 0;
-  height: 68upx;
-  line-height: 68upx;
-  border-radius: 6upx;
-  text-align: center;
-  color: #fff;
-  font-size: 30upx;
-  background: #09C567;
-}
-
 .bottom-space {
   height: 36upx;
 }

+ 1 - 1
hdApp/src/pagesPurchase/addCg.vue

@@ -3,7 +3,7 @@
 	<view class="billing_box_bg">
 		<view class="input-wrap" >
 			<view style="padding:0upx 0upx 0upx 0upx;margin-right:15upx;" >
-				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片分类</button>
+				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片筛选</button>
 			</view>
 			<view class="search-bar">
 				<app-search-module ref="globalSearchRef" v-model="globalPy" placeholder="这里搜索" @input="globalSearch" />

+ 1 - 1
hdApp/src/pagesPurchase/bookProduct.vue

@@ -19,7 +19,7 @@
 		</view>
 		<view class="input-wrap" >
 			<view style="padding:0upx 0upx 0upx 0upx;margin-right:10upx;" >
-				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片分类</button>
+				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片筛选</button>
 			</view>
 			<view class="search-bar">
 				<app-search-module v-model="py" placeholder="最少输二到三字搜索" @input="goSearchFn" />

+ 1 - 1
hdApp/src/pagesPurchase/bookProduct2.vue

@@ -19,7 +19,7 @@
 		</view>
 		<view class="input-wrap" >
 			<view style="padding:0upx 0upx 0upx 0upx;margin-right:10upx;" >
-				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片分类</button>
+				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片筛选</button>
 			</view>
 			<view class="search-bar">
 				<app-search-module v-model="py" placeholder="这里搜索" @input="goSearchFn" />

+ 1 - 1
hdApp/src/pagesPurchase/ghsProduct.vue

@@ -38,7 +38,7 @@
 
 		<view class="input-wrap" >
 			<view class="header-action-wrap header-action-wrap--left">
-				<button class="admin-button-com blue header-action-btn" @click="selectFlowerNumFn()">图片分类</button>
+				<button class="admin-button-com blue header-action-btn" @click="selectFlowerNumFn()">图片筛选</button>
 			</view>
 			<view class="search-bar">
 				<app-search-module v-model="py" placeholder="最少输二到三字搜索" placeholder-color="red" @input="goSearchFn" />

+ 1 - 1
hdApp/src/pagesPurchase/ghsProduct2.vue

@@ -20,7 +20,7 @@
 
 		<view class="input-wrap" >
 			<view style="padding:0upx 0upx 0upx 0upx;margin-right:10upx;" >
-				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片分类</button>
+				<button class="admin-button-com middle blue" @click="selectFlowerNumFn()">图片筛选</button>
 			</view>
 			<view class="search-bar">
 				<app-search-module v-model="py" placeholder="这里搜索" @input="goSearchFn" />

+ 13 - 0
mallApp/src/pages/goods/components/sel-popup.vue

@@ -88,6 +88,11 @@ export default {
       type: String,
       default: 'buy',
     },
+    /** 打开弹层时默认选中的规格下标:与详情页规格卡片选中保持一致 */
+    defaultSpecIndex: {
+      type: Number,
+      default: 0,
+    },
     /** 是否为进行中的秒杀:用于限购与活动库存校验 */
     isSeckill: {
       type: Boolean,
@@ -114,6 +119,14 @@ export default {
       selIndex: 0,
     };
   },
+  watch: {
+    /** 每次打开弹层都对齐详情页当前选中的规格 */
+    show(val) {
+      if (val) {
+        this.selIndex = Number(this.defaultSpecIndex) || 0;
+      }
+    },
+  },
   computed: {
     /**
      * 是否为挂在主商品下的规格 SKU(masterId/master 不为 0)

+ 273 - 66
mallApp/src/pages/goods/detail.vue

@@ -1,7 +1,8 @@
 <!--
   商城商品详情
   谁用:mallApp 顾客查看花束并加购/购买
-  解决问题:多规格 specList 首项即主记录可售规格,选中后 specGoodsId 可等于主商品 id
+  解决问题:多规格 specList 首项即主记录可售规格,选中后 specGoodsId 可等于主商品 id;
+  顶部信息卡按标题/分享、副文案、价格已售、服务保障排布,秒杀条保留在价格上方
 -->
 <template>
   <view class="page-container">
@@ -12,42 +13,83 @@
         :list="imgList"
       />
       <view class="shop-intro">
-        <view v-if="isSeckillActive" class="seckill-bar">
-          <view class="seckill-bar-left">
-            <text class="seckill-badge">秒杀价</text>
-            <text v-if="seckillCountdownText" class="seckill-countdown">{{ seckillCountdownText }}后结束</text>
-          </view>
-          <text v-if="activityLimit > 0" class="seckill-limit">每人限购{{ activityLimit }}件</text>
-        </view>
-        <view class="app-price app-bold" v-if="data.priceType == 1">
-          <text>¥</text>
-          <text class="app-size-34">
-            {{ data.price ? parseFloat(data.price).toFixed(2) : 0}}
-          </text>
-          <text v-if="isSeckillActive && data.originPrice > 0" class="app-price-origin">¥{{ parseFloat(data.originPrice).toFixed(2) }}</text>
-        </view>
-        <!-- 无价格商品:不展示数字价,引导底部询价 -->
-        <view class="app-price app-bold" v-else>¥?</view>
+        <!-- 标题左、分享右;规格名作副文案,避免挤掉秒杀等原有信息 -->
         <view class="tui-pro-titbox">
           <view class="tui-pro-title-wrap">
-            <view class="tui-pro-title app-size-32">{{ data.name }}</view>
+            <view class="tui-pro-title">{{ data.name }}</view>
             <view
               v-if="displaySpecName"
-              class="shop-spec app-color-3"
+              class="shop-spec"
             >{{ displaySpecName }}</view>
           </view>
           <button open-type="share" class="share-button" @click="shareFn">
-            <i class="iconfont iconfenxiang"></i>
+            <text class="iconfont iconfenxiang share-icon"></text>
             <text class="share-text">分享</text>
           </button>
         </view>
-        <view class="shop-express app-color-3">
-          <view>线下门店</view>
-          <view>
-            <span class="prompt-text">快速发货</span>
+        <!-- 秒杀进行中才展示:倒计时 + 限购,紧贴价格区提醒活动价 -->
+        <view v-if="isSeckillActive" class="seckill-bar">
+          <view class="seckill-bar-left">
+            <text class="seckill-badge">秒杀价</text>
+            <text v-if="seckillCountdownText" class="seckill-countdown">{{ seckillCountdownText }}后结束</text>
+          </view>
+          <text v-if="activityLimit > 0" class="seckill-limit">每人限购{{ activityLimit }}件</text>
+        </view>
+        <view class="price-sold-row">
+          <view class="price-wrap">
+            <view class="app-price app-bold" v-if="data.priceType == 1">
+              <text class="price-symbol">¥</text>
+              <text class="price-num">
+                {{ data.price ? parseFloat(data.price).toFixed(2) : 0}}
+              </text>
+              <text v-if="isSeckillActive && data.originPrice > 0" class="app-price-origin">¥{{ parseFloat(data.originPrice).toFixed(2) }}</text>
+            </view>
+            <!-- 无价格商品:不展示数字价,引导底部询价 -->
+            <view class="app-price app-bold" v-else>¥?</view>
+          </view>
+          <text class="sold-text">已售 {{ displaySold }}</text>
+        </view>
+        <!-- 三项服务保障:保留原有文案,按设计稿改成图标+主副标题横条 -->
+        <view class="shop-express">
+          <view class="express-item">
+            <text class="iconfont icondianpu express-icon"></text>
+            <view class="express-text">
+              <text class="express-title">线下门店</text>
+              <text class="express-sub">到店自提</text>
+            </view>
+          </view>
+          <view class="express-item">
+            <text class="iconfont iconfahuo express-icon"></text>
+            <view class="express-text">
+              <text class="express-title">快速发货</text>
+              <text class="express-sub">最快1-2小时达</text>
+            </view>
+          </view>
+          <view class="express-item">
+            <text class="iconfont icondagou express-icon"></text>
+            <view class="express-text">
+              <text class="express-title">品质保证</text>
+              <text class="express-sub">100%满意服务</text>
+            </view>
           </view>
-          <view>
-            <span class="prompt-text">品质保证</span>
+        </view>
+      </view>
+      <!-- 多规格:卡片选规格,切换时同步价格/库存与顶部轮播图 -->
+      <view
+        v-if="data.specEnabled == 1 && data.specList && data.specList.length"
+        class="module-wrap spec-module"
+      >
+        <view class="spec-module-tit app-size-32 app-bold">花束规格</view>
+        <view class="spec-card-list">
+          <view
+            v-for="(spec, index) in data.specList"
+            :key="spec.id"
+            class="spec-card"
+            :class="{ active: specIndex === index }"
+            @click="selectSpecOnPage(index)"
+          >
+            <view class="spec-card-name app-bold">{{ spec.specName }}</view>
+            <view class="spec-card-price">{{ specPriceText(spec) }}</view>
           </view>
         </view>
       </view>
@@ -96,6 +138,7 @@
       :hdId="hdId"
       :account="account"
       :mode="popupMode"
+      :default-spec-index="specIndex"
       :is-seckill="isSeckillActive"
       :activity-limit="activityLimit"
       :activity-stock="activityStock"
@@ -153,6 +196,8 @@ export default {
       autoLoad: false,
       data: {}, //商品数据
       imgList: [],
+      /** 当前选中规格下标(specList),默认首规格 */
+      specIndex: 0,
       buyNum: 1,
       popupShow: false,
       popupMode: 'buy',
@@ -278,6 +323,16 @@ export default {
       }
       return '';
     },
+    /**
+     * 已售文案数字:优先 sold,兼容列表口径 totalSold;过万缩成 x.x万+
+     */
+    displaySold() {
+      const n = parseInt(this.data.sold, 10) || parseInt(this.data.totalSold, 10) || 0;
+      if (n >= 10000) {
+        return (n / 10000).toFixed(1) + '万+';
+      }
+      return String(n);
+    },
   },
   methods: {
     /** 按登录态驱动 wangCg:0 未登录弹层 / 1 已登录关闭 */
@@ -543,6 +598,8 @@ export default {
           newArr.push({ img: res.data.imgList[i] });
         }
         this.imgList = newArr;
+        // 重新拉详情后回到首规格(specList 首项即主记录自身)
+        this.specIndex = 0;
         res.data.stock = res.data.stock ? parseInt(res.data.stock) : 0;
         this.data = res.data;
         this.customId = res.data.customId;
@@ -584,8 +641,48 @@ export default {
     changeNum(e) {
       this.buyNum = e.value;
     },
+    /** 弹层内切换规格:同步页面选中态(价格/轮播图一并更新) */
     changeSpec(spec) {
-      this.data = {...this.data, price: spec.price, priceType: spec.priceType, stock: spec.stock, selectedSpecGoodsId: spec.id, selectedSpecName: spec.specName};
+      const list = (this.data && this.data.specList) || [];
+      const index = list.findIndex((el) => String(el.id) === String(spec.id));
+      this.applySpecSelection(index >= 0 ? index : this.specIndex);
+    },
+    /** 页面规格卡片点击 */
+    selectSpecOnPage(index) {
+      if (this.specIndex === index) {
+        return;
+      }
+      this.applySpecSelection(index);
+    },
+    /**
+     * 应用选中规格:更新价格/库存/选中标记,并把轮播图切到该规格自己的图片
+     * specList 每项都由后端 formatGoodsImg 处理过,自带完整 imgList
+     */
+    applySpecSelection(index) {
+      const spec = (this.data.specList || [])[index];
+      if (!spec) {
+        return;
+      }
+      this.specIndex = index;
+      this.data = {
+        ...this.data,
+        price: spec.price,
+        priceType: spec.priceType,
+        stock: spec.stock ? parseInt(spec.stock) : 0,
+        selectedSpecGoodsId: spec.id,
+        selectedSpecName: spec.specName,
+      };
+      if (Array.isArray(spec.imgList) && spec.imgList.length) {
+        this.imgList = spec.imgList.map((img) => ({ img }));
+      }
+    },
+    /** 规格卡片价格文案:无价格商品与主价格区一致展示 ¥? */
+    specPriceText(spec) {
+      if (Number(spec.priceType) !== 1) {
+        return '¥?';
+      }
+      const price = parseFloat(spec.price) || 0;
+      return '¥' + (Number.isInteger(price) ? price : price.toFixed(2));
     },
     /**
      * 从本地缓存恢复当前店铺购物车到 Vuex
@@ -818,16 +915,20 @@ export default {
       }
     }
   }
-  // 公共模块
+  // 商品信息卡:上圆角压住轮播,标题/价格/保障按设计稿分层
   .shop-intro {
+    position: relative;
+    z-index: 1;
+    margin-top: -32upx;
+    padding: 32upx 28upx 28upx;
     background: #fff;
-    padding: 30upx 0 30upx 30upx;
+    border-radius: 32upx 32upx 0 0;
     .seckill-bar {
       display: flex;
       align-items: center;
       justify-content: space-between;
-      padding: 10upx 20upx 10upx 0;
-      margin-bottom: 10upx;
+      padding: 12upx 0 0;
+      margin-top: 8upx;
       .seckill-bar-left {
         display: flex;
         align-items: center;
@@ -853,66 +954,172 @@ export default {
     .app-price-origin {
       margin-left: 12upx;
       font-size: 26upx;
+      font-weight: 400;
       color: #bbb;
       text-decoration: line-through;
     }
     .tui-pro-titbox {
-      margin: 20upx 0 20upx;
-      @include disFlex(center, space-between);
+      @include disFlex(flex-start, space-between);
       .tui-pro-title-wrap {
-        width: 80%;
+        flex: 1;
+        padding-right: 16upx;
+        overflow: hidden;
       }
       .tui-pro-title {
         width: 100%;
+        font-size: 36upx;
+        font-weight: 700;
+        color: #222;
+        line-height: 1.4;
       }
-      // 与 .shop-express 同色同字号,紧跟商品名称下方
       .shop-spec {
-        margin-top: 8upx;
+        margin-top: 10upx;
+        font-size: 26upx;
+        color: #999;
+        line-height: 1.5;
       }
       .share-button {
         display: flex;
+        flex-direction: column;
         align-items: center;
-        justify-content: center;
-        width: 168upx;
-        height: 84upx;
-        background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
-        border-radius: 12upx;
-        border: 1upx solid #e1e5e9;
-        box-shadow: 0 2upx 8upx rgba(0, 0, 0, 0.08);
-        transition: all 0.3s ease;
+        justify-content: flex-start;
+        flex-shrink: 0;
+        width: 72upx;
+        min-width: 72upx;
+        height: auto;
+        min-height: 0;
         margin: 0;
         padding: 0;
-
-        &:active {
-          background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
-          box-shadow: 0 1upx 4upx rgba(0, 0, 0, 0.12);
+        background: transparent;
+        border: none;
+        line-height: 1;
+        &::after {
+          border: none;
         }
-
-        .iconfont {
-          font-size: 32upx;
-          color: #4a5568;
-          text-shadow: 0 1upx 2upx rgba(255, 255, 255, 0.8);
-          margin-right: 8upx;
-          display: flex;
-          align-items: center;
+        .share-icon {
+          font-size: 36upx;
+          color: #999;
           line-height: 1;
         }
-
         .share-text {
-          font-size: 28upx;
-          color: #4a5568;
-          font-weight: 500;
-          display: flex;
-          align-items: center;
+          margin-top: 6upx;
+          font-size: 22upx;
+          color: #999;
           line-height: 1;
         }
       }
     }
+    .price-sold-row {
+      display: flex;
+      align-items: baseline;
+      justify-content: space-between;
+      margin-top: 24upx;
+      .price-wrap {
+        flex: 1;
+        overflow: hidden;
+      }
+      .app-price {
+        display: flex;
+        align-items: baseline;
+      }
+      .price-symbol {
+        font-size: 28upx;
+      }
+      .price-num {
+        font-size: 48upx;
+        line-height: 1;
+      }
+      .sold-text {
+        flex-shrink: 0;
+        margin-left: 16upx;
+        font-size: 24upx;
+        color: #999;
+      }
+    }
     .shop-express {
-      padding-right: 30upx;
-      @include disFlex(center, space-between);
-      .prompt-text {
-        margin-right: 4upx;
+      display: flex;
+      align-items: center;
+      margin-top: 24upx;
+      padding: 26upx 38upx;
+      background: #f7f7f7;
+      border-radius: 16upx;
+      .express-item {
+        display: flex;
+        align-items: center;
+        flex: 1;
+        overflow: hidden;
+        .express-icon {
+          margin-right: 8upx;
+          font-size: 48upx;
+          color: #52c41a;
+          line-height: 1;
+        }
+        .express-text {
+          display: flex;
+          flex-direction: column;
+          overflow: hidden;
+        }
+        .express-title {
+          font-size: 22upx;
+          color: #333;
+          line-height: 1.3;
+          white-space: nowrap;
+        }
+        .express-sub {
+          margin-top: 4upx;
+          font-size: 20upx;
+          color: #999;
+          line-height: 1.3;
+          white-space: nowrap;
+        }
+      }
+      .express-item + .express-item {
+        margin-left: 8upx;
+      }
+    }
+  }
+  // 规格卡片模块:选中项绿色描边+绿色价格,不带角标
+  .spec-module {
+    $specGreen: #7ba23f;
+    padding: 24upx;
+    .spec-module-tit {
+      margin-bottom: 24upx;
+    }
+    .spec-card-list {
+      display: flex;
+      flex-wrap: wrap;
+    }
+    .spec-card {
+      width: calc((100% - 40upx) / 3);
+      margin: 0 20upx 20upx 0;
+      padding: 24upx;
+      box-sizing: border-box;
+      background: #f7f7f5;
+      border: 2upx solid transparent;
+      border-radius: 16upx;
+      overflow: hidden;
+      &:nth-child(3n) {
+        margin-right: 0;
+      }
+      .spec-card-name {
+        font-size: 30upx;
+        color: #333;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+      .spec-card-price {
+        margin-top: 16upx;
+        font-size: 28upx;
+        color: #999;
+      }
+      &.active {
+        background: #fff;
+        border-color: $specGreen;
+        .spec-card-price {
+          color: $specGreen;
+          font-weight: 600;
+        }
       }
     }
   }