zlwu 6 bulan lalu
induk
melakukan
e66f906658

+ 9 - 9
sqxp-wx/components/client/client-tab-bar.vue

@@ -23,7 +23,7 @@
 <script setup lang="ts">
 /**
  * 非 tabBar 页面底部占位:与 pages.json 中客户端 tab 一致,避免 H5 子路由下只有空白条。
- * 图标使用项目内 static/tabbar(微信小程序 tabBar 仅支持本地路径,不支持 http 远程图)。
+ * 图标使用项目内 static/tabbar 下 PNG(与 pages.json 原生 tabBar 一致;微信小程序不支持 SVG 作原生 tab 图)。
  * 固定在视口底部,主内容区底部操作栏需留出高度:calc(100rpx + env(safe-area-inset-bottom))
  */
 const props = defineProps<{
@@ -35,29 +35,29 @@ const tabs = [
     key: 'home' as const,
     text: '首页',
     pagePath: '/pages/client/home/home',
-    iconPath: '/static/tabbar/首页.svg',
-    selectedIconPath: '/static/tabbar/首页-active.svg'
+    iconPath: '/static/tabbar/首页.png',
+    selectedIconPath: '/static/tabbar/首页-active.png'
   },
   {
     key: 'order' as const,
     text: '订单',
     pagePath: '/pages/client/order/order',
-    iconPath: '/static/tabbar/订单.svg',
-    selectedIconPath: '/static/tabbar/订单-active.svg'
+    iconPath: '/static/tabbar/订单.png',
+    selectedIconPath: '/static/tabbar/订单-active.png'
   },
   {
     key: 'cart' as const,
     text: '购物车',
     pagePath: '/pages/client/cart/cart',
-    iconPath: '/static/tabbar/购物车.svg',
-    selectedIconPath: '/static/tabbar/购物车-active.svg'
+    iconPath: '/static/tabbar/购物车.png',
+    selectedIconPath: '/static/tabbar/购物车-active.png'
   },
   {
     key: 'profile' as const,
     text: '我的',
     pagePath: '/pages/client/profile/profile',
-    iconPath: '/static/tabbar/我的.svg',
-    selectedIconPath: '/static/tabbar/我的-active.svg'
+    iconPath: '/static/tabbar/我的.png',
+    selectedIconPath: '/static/tabbar/我的-active.png'
   }
 ]
 

+ 15 - 1
sqxp-wx/components/client/product-detail-overlay.vue

@@ -77,7 +77,7 @@
 
                 <view v-if="!props.readonly" class="info-right" @click.stop>
                   <view v-if="currentCartQty === 0" class="add-btn" @click.stop="addOneToCart">
-                    <text>+</text>
+                    <text class="add-btn-plus">+</text>
                   </view>
                   <view v-else class="quantity-control">
                     <view class="qty-btn" @click.stop="updateQuantity(-1)">
@@ -694,6 +694,20 @@ watch(
   color: #ffffff;
   font-size: 34rpx;
   font-weight: 700;
+  box-sizing: border-box;
+  padding: 0;
+  line-height: 1;
+}
+
+/* 「+」在圆内几何居中:抵消 text 默认行高/基线导致的视觉偏移 */
+.add-btn-plus {
+  line-height: 1;
+  font-size: inherit;
+  font-weight: inherit;
+  color: inherit;
+  text-align: center;
+  display: block;
+  transform: translateY(-2rpx);
 }
 
 .quantity-control {

+ 72 - 0
sqxp-wx/components/sq-copy-btn.vue

@@ -0,0 +1,72 @@
+<script setup>
+import { computed } from 'vue';
+import { staticAssetUrl } from '@/utils/staticAsset';
+
+const props = defineProps({
+  text: { type: [String, Number, Object], default: '' },
+  label: { type: String, default: '' },
+  iconOnly: { type: Boolean, default: false }
+});
+
+const value = computed(() => String(props.text ?? '').trim());
+const ICON_COPY = staticAssetUrl('copy.svg');
+
+function onCopy() {
+  const v = value.value;
+  if (!v || v === '—') return;
+  uni.setClipboardData({
+    data: v,
+    success: () => uni.showToast({ title: `${props.label || '内容'}已复制`, icon: 'none' })
+  });
+}
+</script>
+
+<template>
+  <view
+    v-if="value"
+    class="sq-copy-btn"
+    :class="{ 'sq-copy-btn--icon-only': !!iconOnly }"
+    @click.stop="onCopy"
+  >
+    <image class="sq-copy-btn__icon" :src="ICON_COPY" mode="aspectFit" />
+    <text v-if="!iconOnly" class="sq-copy-btn__text">复制</text>
+  </view>
+</template>
+
+<style lang="scss" scoped>
+/* 参考配送员端复制按钮样式:浅绿底 + 绿字 */
+.sq-copy-btn {
+  flex-shrink: 0;
+  min-height: 36rpx;
+  padding: 0 10rpx;
+  border-radius: 999rpx;
+  background: #eef8f3;
+  display: inline-flex;
+  align-items: center;
+  gap: 6rpx;
+}
+
+.sq-copy-btn--icon-only {
+  width: 44rpx;
+  height: 44rpx;
+  padding: 0;
+  justify-content: center;
+  border-radius: 10rpx;
+}
+
+.sq-copy-btn__icon {
+  width: 20rpx;
+  height: 20rpx;
+}
+
+.sq-copy-btn--icon-only .sq-copy-btn__icon {
+  width: 22rpx;
+  height: 22rpx;
+}
+
+.sq-copy-btn__text {
+  font-size: 18rpx;
+  color: #189b65;
+}
+</style>
+

+ 10 - 9
sqxp-wx/pages.json

@@ -40,8 +40,9 @@
 		{
 			"path": "pages/client/order/detail/detail",
 			"style": {
-				"navigationStyle": "custom",
 				"navigationBarTitleText": "订单详情",
+				"navigationBarBackgroundColor": "#f3f7f5",
+				"navigationBarTextStyle": "black",
 				"disableScroll": true
 			}
 		},
@@ -305,24 +306,24 @@
 		"backgroundColor": "#ffffff",
 		"list": [{
 				"pagePath": "pages/client/home/home",
-				"iconPath": "static/tabbar/首页.png",
-				"selectedIconPath": "static/tabbar/首页-active.png",
+				"iconPath": "/static/tabbar/首页.png",
+				"selectedIconPath": "/static/tabbar/首页-active.png",
 				"text": "首页"
 			},
 			{
 				"pagePath": "pages/client/order/order",
-				"iconPath": "static/tabbar/订单.png",
-				"selectedIconPath": "static/tabbar/订单-active.png",
+				"iconPath": "/static/tabbar/订单.png",
+				"selectedIconPath": "/static/tabbar/订单-active.png",
 				"text": "订单"
 			}, {
 				"pagePath": "pages/client/cart/cart",
-				"iconPath": "static/tabbar/购物车.png",
-				"selectedIconPath": "static/tabbar/购物车-active.png",
+				"iconPath": "/static/tabbar/购物车.png",
+				"selectedIconPath": "/static/tabbar/购物车-active.png",
 				"text": "购物车"
 			}, {
 				"pagePath": "pages/client/profile/profile",
-				"iconPath": "static/tabbar/我的.png",
-				"selectedIconPath": "static/tabbar/我的-active.png",
+				"iconPath": "/static/tabbar/我的.png",
+				"selectedIconPath": "/static/tabbar/我的-active.png",
 				"text": "我的"
 			}
 		]

+ 0 - 7
sqxp-wx/pages/client/address/edit.vue

@@ -286,7 +286,6 @@ onLoad(async (options) => {
         >
           <view class="picker-row">
             <text :class="['picker-text', !form.province && 'placeholder']">{{ regionLabel() }}</text>
-            <text class="chev">›</text>
           </view>
         </picker>
       </view>
@@ -399,12 +398,6 @@ onLoad(async (options) => {
   color: #c0c4cc;
 }
 
-.chev {
-  color: #c0c4cc;
-  font-size: 34rpx;
-  margin-left: 12rpx;
-}
-
 .textarea {
   width: 100%;
   font-size: 28rpx;

+ 139 - 19
sqxp-wx/pages/client/address/select.vue

@@ -90,6 +90,24 @@
         <button class="add-btn-bottom" type="default" @click="goAdd">新增收货地址</button>
       </view>
     </template>
+    <view v-if="deleteDialogVisible" class="delete-dialog-mask" @click="closeDeleteDialog">
+      <view class="delete-dialog" @click.stop>
+        <view class="delete-dialog__icon">!</view>
+        <text class="delete-dialog__title">删除收货地址</text>
+        <text class="delete-dialog__desc">删除后将无法恢复,确定继续吗?</text>
+        <view class="delete-dialog__actions">
+          <view class="delete-dialog__action delete-dialog__action--cancel" @click="closeDeleteDialog">取消</view>
+          <button
+            class="delete-dialog__action delete-dialog__action--danger"
+            :disabled="deleting"
+            :loading="deleting"
+            @click="confirmDelete"
+          >
+            删除
+          </button>
+        </view>
+      </view>
+    </view>
   </view>
 </template>
 
@@ -112,6 +130,9 @@ const hasMore = ref(true)
 const nextPage = ref(1)
 const list = ref([])
 const selectedAddressId = ref('')
+const deleteDialogVisible = ref(false)
+const deleting = ref(false)
+const deletingItem = ref(null)
 
 function fullRegion(addr) {
   const p = addr.province || ''
@@ -213,25 +234,35 @@ async function handleSetDefault(id) {
 }
 
 function handleDelete(item) {
-  uni.showModal({
-    title: '删除地址',
-    content: '确定删除该收货地址?',
-    success: async (res) => {
-      if (!res.confirm) return
-      try {
-        const req = await request({ url: `/mini/client/addresses/${item.id}`, method: 'DELETE' })
-        const body = parseResponseJson(req)
-        if (body.code === 200) {
-          uni.showToast({ title: '已删除', icon: 'success' })
-          await fetchAddresses(true)
-        } else {
-          uni.showToast({ title: body.message || '删除失败', icon: 'none' })
-        }
-      } catch {
-        uni.showToast({ title: '网络异常', icon: 'none' })
-      }
+  deletingItem.value = item
+  deleteDialogVisible.value = true
+}
+
+function closeDeleteDialog() {
+  if (deleting.value) return
+  deleteDialogVisible.value = false
+  deletingItem.value = null
+}
+
+async function confirmDelete() {
+  const item = deletingItem.value
+  if (!item?.id || deleting.value) return
+  deleting.value = true
+  try {
+    const req = await request({ url: `/mini/client/addresses/${item.id}`, method: 'DELETE' })
+    const body = parseResponseJson(req)
+    if (body.code === 200) {
+      uni.showToast({ title: '已删除', icon: 'success' })
+      closeDeleteDialog()
+      await fetchAddresses(true)
+    } else {
+      uni.showToast({ title: body.message || '删除失败', icon: 'none' })
     }
-  })
+  } catch {
+    uni.showToast({ title: '网络异常', icon: 'none' })
+  } finally {
+    deleting.value = false
+  }
 }
 
 function onAddressChange(e) {
@@ -393,7 +424,7 @@ page {
   max-width: 600rpx;
   height: 88rpx;
   line-height: 88rpx;
-  background: linear-gradient(145deg, var(--c-barley-yellow), #e4b458);
+  background: linear-gradient(145deg, var(--c-brand-green), var(--c-brand-green-2));
   color: #ffffff;
   border-radius: 44rpx;
   font-size: 30rpx;
@@ -505,4 +536,93 @@ page {
 .danger {
   color: #ee0a24;
 }
+
+.delete-dialog-mask {
+  position: fixed;
+  inset: 0;
+  z-index: 120;
+  background: rgba(18, 28, 24, 0.45);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 40rpx;
+  box-sizing: border-box;
+}
+
+.delete-dialog {
+  width: 100%;
+  max-width: 620rpx;
+  background: #ffffff;
+  border-radius: 26rpx;
+  padding: 40rpx 34rpx 32rpx;
+  box-sizing: border-box;
+  box-shadow: 0 12rpx 48rpx rgba(0, 0, 0, 0.15);
+}
+
+.delete-dialog__icon {
+  width: 76rpx;
+  height: 76rpx;
+  margin: 0 auto 20rpx;
+  border-radius: 50%;
+  background: #fff1f0;
+  color: #ee0a24;
+  font-size: 42rpx;
+  font-weight: 700;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.delete-dialog__title {
+  display: block;
+  text-align: center;
+  color: var(--c-text);
+  font-size: 34rpx;
+  font-weight: 700;
+}
+
+.delete-dialog__desc {
+  display: block;
+  margin-top: 14rpx;
+  text-align: center;
+  color: var(--c-muted);
+  font-size: 26rpx;
+  line-height: 1.6;
+}
+
+.delete-dialog__actions {
+  margin-top: 28rpx;
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  column-gap: 16rpx;
+}
+
+.delete-dialog__action {
+  width: 100%;
+  min-height: 76rpx;
+  border-radius: 999rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28rpx;
+  font-weight: 600;
+  border: none;
+  box-sizing: border-box;
+}
+
+.delete-dialog__action--cancel {
+  background: #f4f7f6;
+  color: #5b6b64;
+  border: 1rpx solid #ddebe5;
+}
+
+.delete-dialog__action--danger {
+  background: linear-gradient(145deg, #ff6565, #ee0a24);
+  color: #ffffff;
+}
+
+.delete-dialog__action--danger[disabled] {
+  color: #ffffff !important;
+  opacity: 0.92;
+}
 </style>

+ 45 - 22
sqxp-wx/pages/client/home/home.vue

@@ -8,16 +8,23 @@
     <view v-else class="content-wrap">
       <!-- 顶栏:仅渐变背景 + 配送行 + 搜索条(与 sanqiang-client-ui-demo.html .top 一致) -->
       <view class="filter-top-wrap sq-hero-header">
-        <view class="top-row">
-          <text class="top-row-left">{{ deliveryLineLeft }}</text>
+        <view class="hero-top-main">
+          <view class="hero-top-text">
+            <text class="hero-brand">叁强鲜配</text>
+            <text class="hero-region-line">{{ deliveryLineLeft }}</text>
+          </view>
+          <ClientImageLoader
+            class="hero-top-logo"
+            :src="IMG_LOGO_WATERMARK"
+            mode="aspectFit"
+          />
         </view>
-        <text class="top-row-brand">叁强鲜配</text>
         <view class="search-bar">
           <input
             v-model="keyword"
             class="search-input"
             type="text"
-            placeholder="搜索商品、品牌、一级/二级分类"
+            placeholder="请输入商品名称搜索"
             placeholder-class="search-placeholder"
             confirm-type="search"
             @confirm="onSearchClick"
@@ -200,7 +207,7 @@ import ProductDetailOverlay from '@/components/client/product-detail-overlay.vue
 import ClientTabBar from '@/components/client/client-tab-bar.vue'
 import ClientSkeleton from '@/components/client/client-skeleton.vue'
 import ClientImageLoader from '@/components/client/client-image-loader.vue'
-import { DEFAULT_PLACEHOLDER_IMAGE_URL, IMG_DEFAULT_PRODUCT } from '@/utils/staticAsset'
+import { DEFAULT_PLACEHOLDER_IMAGE_URL, IMG_DEFAULT_PRODUCT, IMG_LOGO_WATERMARK } from '@/utils/staticAsset'
 
 // Store
 const userStore = useUserStore()
@@ -256,7 +263,7 @@ const firstCategorySwiperIndex = ref(0)
 
 const deliveryLineLeft = computed(() => {
   const name = userStore.userInfo?.regionName?.trim()
-  return name ? `配送到:${name}` : '配送到:请选择区域'
+  return name ? `配送区域:${name}` : '配送区域:请选择区域'
 })
 
 function onFirstCategorySwiperChange(e) {
@@ -669,32 +676,48 @@ page {
   margin: 0 -32rpx 0;
 }
 
-.top-row {
+.hero-top-main {
   display: flex;
-  align-items: center;
-  justify-content: flex-start;
-  color: #ffffff;
-  font-size: 28rpx;
-  line-height: 1.35;
-  margin-bottom: 8rpx;
+  align-items: flex-end;
+  justify-content: space-between;
+  gap: 20rpx;
+  margin-bottom: 20rpx;
 }
 
-.top-row-left {
+.hero-top-text {
   flex: 1;
   min-width: 0;
-  margin-right: 16rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.hero-brand {
+  color: #ffffff;
+  font-size: 48rpx;
+  font-weight: 700;
+  line-height: 1.25;
+  letter-spacing: 0.5rpx;
+  margin-bottom: 12rpx;
+}
+
+.hero-region-line {
+  color: #ffffff;
+  font-size: 28rpx;
+  line-height: 1.35;
+  width: 100%;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
 
-.top-row-brand {
-  display: block;
-  margin-bottom: 20rpx;
-  width: fit-content;
-  font-weight: 600;
-  opacity: 0.96;
-  letter-spacing: 0.5rpx;
+/* 与供应商首页 header-watermark 同源素材,置于顶栏右侧底部对齐 */
+.hero-top-logo {
+  flex-shrink: 0;
+  width: 168rpx;
+  height: 168rpx;
+  opacity: 0.4;
+  pointer-events: none;
 }
 
 /* demo .search:白底圆角条 + 占位灰绿字 */

+ 70 - 65
sqxp-wx/pages/client/order/detail/detail.vue

@@ -150,15 +150,6 @@ function goPay() {
   })
 }
 
-function goBack() {
-  uni.navigateBack({
-    delta: 1,
-    fail: () => {
-      uni.switchTab({ url: '/pages/client/order/order' })
-    }
-  })
-}
-
 const hasBottomActions = computed(() => {
   const d = orderDetail.value
   if (!d) return false
@@ -201,32 +192,9 @@ onLoad((query) => {
     <template v-else-if="orderDetail">
       <view class="detail-fixed-top">
         <view class="client-subpage-hero order-detail-hero sq-hero-header sq-hero-header--compact">
-          <view class="sq-hero-back-row">
-            <view class="sq-hero-back-btn" @click="goBack">
-              <text>‹</text>
-            </view>
-            <text class="sq-hero-back-title">订单详情</text>
-          </view>
           <text class="client-subpage-hero__sub">订单号 {{ orderDetail.orderNo }}</text>
           <text class="order-status-badge">{{ statusMap[unifiedStatus(orderDetail)]?.text }}</text>
         </view>
-
-        <!-- 收货信息 -->
-        <view class="address-section">
-          <image class="address-icon" :src="ICON_COORDINATE" mode="aspectFit" />
-          <view class="address-info">
-            <view class="address-contact">
-              <text class="contact-name">{{ orderDetail.contactPerson }}</text>
-              <PhoneDialInline
-                class="contact-phone-dial"
-                :phone="orderDetail.contactPhone"
-                hide-icon
-                color="#0a9f6d"
-              />
-            </view>
-            <text class="address-detail">{{ orderDetail.deliveryAddress }}</text>
-          </view>
-        </view>
       </view>
 
       <scroll-view
@@ -240,6 +208,23 @@ onLoad((query) => {
           class="detail-scroll-inner"
           :class="{ 'detail-scroll-inner--with-actions': hasBottomActions }"
         >
+          <!-- 收货信息(与下方区块一并滚动) -->
+          <view class="address-section">
+            <image class="address-icon" :src="ICON_COORDINATE" mode="aspectFit" />
+            <view class="address-info">
+              <view class="address-contact">
+                <text class="contact-name">{{ orderDetail.contactPerson }}</text>
+                <PhoneDialInline
+                  class="contact-phone-dial"
+                  :phone="orderDetail.contactPhone"
+                  hide-icon
+                  color="#000000"
+                />
+              </view>
+              <text class="address-detail">{{ orderDetail.deliveryAddress }}</text>
+            </view>
+          </view>
+
           <!-- 配送员(有分配时显示在商品信息上方) -->
           <view v-if="hasDelivererInfo" class="deliverer-section">
             <view class="section-title-row">
@@ -259,7 +244,7 @@ onLoad((query) => {
                   <PhoneDialInline
                     v-if="orderDetail.delivererPhone"
                     :phone="orderDetail.delivererPhone"
-                    color="#0a9f6d"
+                    color="#000000"
                     icon-size="20"
                     expand
                     end-align
@@ -299,12 +284,16 @@ onLoad((query) => {
                 />
                 <view class="item-body">
                   <text class="item-name">{{ item.productName }}</text>
-                  <text class="item-unit">{{ formatMeasureText(item.measureValue, item.unitName) }}</text>
-                  <view class="item-line">
-                    <text class="item-price">¥{{ linePrice(item) }}</text>
-                    <text class="item-qty">x{{ item.quantity }}</text>
+                  <view class="item-meta">
+                    <view class="item-meta-row item-meta-row--qty-unit">
+                      <text class="item-unit-inline">{{ formatMeasureText(item.measureValue, item.unitName) }}</text>
+                      <text class="item-qty-inline">x{{ item.quantity }}</text>
+                    </view>
+                    <view class="item-meta-row item-meta-row--sub-price">
+                      <text class="item-price-inline">¥{{ linePrice(item) }}</text>
+                      <text class="item-sub-inline">小计(元) ¥{{ lineAmount(item) }}</text>
+                    </view>
                   </view>
-                  <text class="item-sub">小计(元) ¥{{ lineAmount(item) }}</text>
                 </view>
               </view>
             </view>
@@ -472,7 +461,7 @@ onLoad((query) => {
   align-items: flex-start;
   background: var(--c-card);
   border: 1rpx solid var(--c-border);
-  margin: 16rpx 32rpx 0;
+  margin: 0 0 16rpx;
   border-radius: 24rpx;
   padding: 24rpx;
   box-shadow: 0 8rpx 20rpx rgba(10, 159, 109, 0.08);
@@ -507,7 +496,7 @@ onLoad((query) => {
 
       .contact-phone-dial :deep(.phone-dial-inline__text) {
         font-size: 28rpx;
-        color: var(--c-muted);
+        color: #000000;
         min-width: 0;
       }
     }
@@ -567,6 +556,12 @@ onLoad((query) => {
 
     .deliverer-field--time {
       border-top: 1rpx solid #f0f0f0;
+
+      .deliverer-label {
+        width: 248rpx;
+        min-width: 248rpx;
+        white-space: nowrap;
+      }
     }
 
     .deliverer-label {
@@ -599,7 +594,7 @@ onLoad((query) => {
     }
 
     .deliverer-phone-cell :deep(.phone-dial-inline__text) {
-      color: #303133;
+      color: #000000;
       font-weight: 500;
       font-size: 30rpx;
       text-align: right;
@@ -688,51 +683,61 @@ onLoad((query) => {
     word-break: break-word;
   }
 
-  .item-unit {
-    display: block;
-    margin: 6rpx 0 12rpx;
+  .item-meta {
+    margin-top: auto;
+    display: flex;
+    flex-direction: column;
+    gap: 10rpx;
+    padding-top: 8rpx;
+    min-width: 0;
+  }
+
+  .item-meta-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 16rpx;
+    min-width: 0;
+  }
+
+  .item-unit-inline {
+    flex: 1;
+    min-width: 0;
     font-size: clamp(20rpx, 3vw, 22rpx);
     color: var(--c-muted);
+    text-align: left;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
   }
 
-  .item-line {
-    margin-top: auto;
-    display: grid;
-    grid-template-columns: minmax(0, 1fr) auto;
-    align-items: center;
-    column-gap: 12rpx;
-    min-width: 0;
-    padding-bottom: 6rpx;
+  .item-qty-inline {
+    flex-shrink: 0;
+    font-size: clamp(26rpx, 3.4vw, 30rpx);
+    color: #000000;
   }
 
-  .item-price {
+  .item-price-inline {
+    flex-shrink: 0;
     font-size: clamp(24rpx, 3.8vw, 32rpx);
     color: var(--c-danger);
     font-weight: 700;
+    white-space: nowrap;
     min-width: 0;
     overflow: hidden;
     text-overflow: ellipsis;
-    white-space: nowrap;
-  }
-
-  .item-qty {
-    font-size: 24rpx;
-    color: #909399;
-    flex-shrink: 0;
-    padding-right: 8rpx;
   }
 
-  .item-sub {
-    margin-top: 6rpx;
+  .item-sub-inline {
+    flex: 1;
+    min-width: 0;
     font-size: 24rpx;
     color: var(--c-danger);
     font-weight: 600;
-    display: block;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
     text-align: right;
-    padding-right: 8rpx;
   }
 }
 

+ 1 - 1
sqxp-wx/pages/client/order/order.vue

@@ -30,7 +30,7 @@
             </view>
           </view>
         </scroll-view>
-        </view>
+      </view>
     </view>
 
     <scroll-view

+ 1 - 1
sqxp-wx/pages/deliverer/home/home.vue

@@ -690,7 +690,7 @@ page {
   transform: translateY(-50%);
   width: 260rpx;
   height: 260rpx;
-  opacity: 0.09;
+  opacity: 0.5;
   pointer-events: none;
 }
 

+ 0 - 6
sqxp-wx/pages/supplier/address/edit.vue

@@ -300,7 +300,6 @@ onLoad(async (options) => {
         >
           <view class="picker-row">
             <text :class="['picker-text', !form.province && 'placeholder']">{{ regionLabel() }}</text>
-            <text class="chev">›</text>
           </view>
         </picker>
       </view>
@@ -459,11 +458,6 @@ $supplier: #0a9f6d;
   }
 }
 
-.chev {
-  font-size: 32rpx;
-  color: #c0c4cc;
-}
-
 .submit-btn {
   margin-top: 48rpx;
   width: 100%;

+ 39 - 13
sqxp-wx/pages/supplier/delivery/delivery.vue

@@ -8,6 +8,7 @@ import { formatYuan } from '@/utils/format';
 import PhoneDialInline from '@/components/phone-dial-inline.vue';
 import { DEFAULT_PLACEHOLDER_IMAGE_URL, IMG_EMPTY } from '@/utils/staticAsset';
 import ClientSkeleton from '@/components/client/client-skeleton.vue';
+import SqCopyBtn from '@/components/sq-copy-btn.vue';
 const userStore = useUserStore();
 const PAGE_SIZE = 10;
 function taskItemImage(it) {
@@ -369,10 +370,10 @@ onShow(() => {
       >
         <view v-for="(row, idx) in list" :key="row.id" class="card" @click="goDetail(row.id)">
           <view class="row top-row">
-            <text class="no">
-              <text class="no-idx">{{ idx + 1 }}.</text>
-              <text class="no-text">{{ row.taskNo || row.id }}</text>
-            </text>
+            <view class="no-line">
+              <text class="no">{{ idx + 1 }}. 任务号:{{ row.taskNo || row.id }}</text>
+              <SqCopyBtn class="no-copy" :text="row.taskNo || row.id" label="任务号" />
+            </view>
             <text class="st" :class="String(row.status || '')">{{ displayStatus(row) }}</text>
           </view>
           <view
@@ -382,7 +383,7 @@ onShow(() => {
           >
             <button
               v-if="canStartStock(row)"
-              class="btn-act btn-done"
+              class="sq-stock-btn sq-stock-btn--outline"
               @click="onGoTaskDetail(row, $event)"
             >
               开始备货
@@ -630,22 +631,27 @@ page {
     align-items: center;
   }
   .no {
-    flex: 1;
-    min-width: 0;
     font-size: 28rpx;
     color: #303133;
     font-weight: 500;
     word-break: break-all;
   }
-  .no-idx {
-    flex: 0 0 auto;
-    margin-right: 10rpx;
-    font-weight: 700;
-    color: #303133;
+  .no-line {
+    flex: 1;
+    min-width: 0;
+    display: flex;
+    align-items: center;
+    gap: 10rpx;
   }
-  .no-text {
+  .no-line .no {
     flex: 1;
     min-width: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .no-copy {
+    flex-shrink: 0;
   }
   .st {
     flex-shrink: 0;
@@ -710,6 +716,26 @@ page {
     color: #fff;
     font-weight: 500;
   }
+  /* 对齐备货详情页「开始备货」按钮样式 */
+  .sq-stock-btn {
+    flex: 1;
+    min-width: 200rpx;
+    height: 72rpx;
+    line-height: 72rpx;
+    font-size: 28rpx;
+    border-radius: 12rpx;
+    padding: 0;
+    margin: 0;
+    border: none;
+  }
+  .sq-stock-btn::after {
+    border: none;
+  }
+  .sq-stock-btn--outline {
+    background: #ecfdf5;
+    color: #087f57;
+    font-weight: 600;
+  }
   .delivered-hint {
     margin-top: 6rpx;
     color: #07c160;

+ 44 - 8
sqxp-wx/pages/supplier/delivery/detail/detail.vue

@@ -7,6 +7,7 @@ import { API_BASE, TOKEN_KEY } from '@/config/api';
 import PhoneDialInline from '@/components/phone-dial-inline.vue';
 import { DEFAULT_PLACEHOLDER_IMAGE_URL, staticAssetUrl } from '@/utils/staticAsset';
 import ClientImageLoader from '@/components/client/client-image-loader.vue';
+import SqCopyBtn from '@/components/sq-copy-btn.vue';
 const userStore = useUserStore();
 const ICON_SECTION_PROOF = staticAssetUrl('funct.svg');
 const ICON_SECTION_DELIVER = staticAssetUrl('trant.svg');
@@ -357,7 +358,10 @@ onLoad((query) => {
     </view>
     <scroll-view v-else scroll-y class="scroll">
       <view class="hero">
-        <text class="hero-title">{{ taskNo || '备货任务' }}</text>
+        <view class="hero-title-row">
+          <text class="hero-title">{{ taskNo ? `任务号:${taskNo}` : '备货任务' }}</text>
+          <SqCopyBtn v-if="taskNo" class="hero-copy" :text="taskNo" label="任务号" icon-only />
+        </view>
         <text class="hero-sub">{{ headlinePrepareLabel() }}</text>
         <text v-if="effectiveTime" class="hero-time">送达客户截止时间:{{ effectiveTime }}</text>
         <view v-if="canStartStockDetail() || canFinishStockDetail()" class="hero-actions" @click.stop>
@@ -441,7 +445,10 @@ onLoad((query) => {
         </view>
         <view v-if="order && order.orderNo" class="cell">
           <text class="k">客户订单号</text>
-          <text class="v">{{ order.orderNo }}</text>
+          <view class="v copy-v">
+            <text class="copy-v-text">{{ order.orderNo }}</text>
+            <SqCopyBtn class="copy-btn" :text="order.orderNo" label="客户订单号" icon-only />
+          </view>
         </view>
         <view v-if="deliver && (deliver.deliverName || deliver.phone)" class="cell">
           <text class="k">配送员姓名</text>
@@ -462,9 +469,7 @@ onLoad((query) => {
             <image class="gdp-title-icon" :src="ICON_SECTION_GOODS" mode="aspectFit" />
             <text class="gdp-title">备货明细</text>
           </view>
-          <text class="gdp-tag">供应商报价</text>
         </view>
-        <text class="gdp-hint">单价与合计均为供货价;点击商品可查看详情</text>
       </view>
 
       <view class="card card--goods-block">
@@ -508,12 +513,10 @@ onLoad((query) => {
                 </text>
               </view>
               <view class="price-block">
-                <view class="price-row-inner">
-                  <text class="price-label">供货单价(元)</text>
+                <view class="price-row-inner price-row-inner--value">
                   <text class="price-val">{{ fmtMoney(row.supplierQuoteUnit ?? null) }}</text>
                 </view>
-                <view class="price-row-inner strong">
-                  <text class="price-label">报价小计(元)</text>
+                <view class="price-row-inner price-row-inner--value strong">
                   <text class="price-val">{{ fmtMoney(row.supplierQuoteLine ?? null) }}</text>
                 </view>
               </view>
@@ -554,6 +557,20 @@ onLoad((query) => {
   font-weight: 600;
   margin-bottom: 8rpx;
 }
+.hero-title-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12rpx;
+}
+.hero-title-row .hero-title {
+  flex: 1;
+  min-width: 0;
+  margin-bottom: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
 .hero-sub {
   display: block;
   font-size: 26rpx;
@@ -963,6 +980,22 @@ onLoad((query) => {
   color: #303133;
   word-break: break-all;
 }
+.copy-v {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12rpx;
+}
+.copy-v-text {
+  flex: 1;
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.copy-btn {
+  flex-shrink: 0;
+}
 
 /* 对齐询价单商品区域:左图右文、底部分割线 */
 .goods-pane {
@@ -1059,6 +1092,9 @@ onLoad((query) => {
   align-items: center;
   padding: 4rpx 0;
 }
+.price-row-inner--value {
+  justify-content: flex-end;
+}
 .price-row-inner.strong .price-val {
   font-weight: 700;
   font-size: 30rpx;

+ 41 - 13
sqxp-wx/pages/supplier/home/home.vue

@@ -9,6 +9,7 @@ import { resolveUserAvatarUrl } from '@/utils/avatar';
 import ClientImageLoader from '@/components/client/client-image-loader.vue';
 import ClientSkeleton from '@/components/client/client-skeleton.vue';
 import PhoneDialInline from '@/components/phone-dial-inline.vue';
+import SqCopyBtn from '@/components/sq-copy-btn.vue';
 import { DEFAULT_PLACEHOLDER_IMAGE_URL, staticAssetUrl } from '@/utils/staticAsset';
 const DELIVERY_LIST_URL = '/pages/supplier/delivery/delivery';
 const ASSIGNED_PAGE_SIZE = 8;
@@ -391,15 +392,17 @@ onShow(() => {
         <view class="tasks">
           <view v-for="(row, idx) in assignedTaskList" :key="row.id" class="card" @click="goTaskDetail(row.id)">
             <view class="row top-row">
-              <text class="no">
-                <text class="no-idx">{{ idx + 1 }}.</text>
-                <text class="no-text">任务号:{{ row.taskNo || row.id }}</text>
-              </text>
+              <view class="no-line">
+                <text class="no">{{ idx + 1 }}. 任务号:{{ row.taskNo || row.id }}</text>
+                <SqCopyBtn class="no-copy" :text="row.taskNo || row.id" label="任务号" />
+              </view>
               <text class="st" :class="String(row.status || '')">{{ displayAssignedStatus(row) }}</text>
             </view>
 
             <view v-if="canStartStock(row) || canFinishStock(row)" class="action-row" @click.stop>
-              <button v-if="canStartStock(row)" class="btn-act btn-done" @click="goTaskDetail(row.id)">开始备货</button>
+              <button v-if="canStartStock(row)" class="sq-stock-btn sq-stock-btn--outline" @click="goTaskDetail(row.id)">
+                开始备货
+              </button>
               <button v-if="canFinishStock(row)" class="btn-act btn-done" @click="goTaskDetail(row.id)">备货完成</button>
             </view>
 
@@ -492,7 +495,7 @@ onShow(() => {
   transform: translateY(-50%);
   width: 260rpx;
   height: 260rpx;
-  opacity: 0.09;
+  opacity: 0.5;
   pointer-events: none;
 }
 
@@ -709,21 +712,27 @@ onShow(() => {
   align-items: center;
 }
 .card .no {
-  flex: 1;
-  min-width: 0;
   font-size: 28rpx;
   color: #303133;
   font-weight: 500;
   word-break: break-all;
 }
-.card .no-idx {
-  margin-right: 10rpx;
-  font-weight: 700;
-  color: #303133;
+.card .no-line {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  align-items: center;
+  gap: 10rpx;
 }
-.card .no-text {
+.card .no-line .no {
   flex: 1;
   min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.card .no-copy {
+  flex-shrink: 0;
 }
 .card .st {
   flex-shrink: 0;
@@ -780,6 +789,25 @@ onShow(() => {
   color: #fff;
   font-weight: 500;
 }
+.card .sq-stock-btn {
+  flex: 1;
+  min-width: 200rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  font-size: 28rpx;
+  border-radius: 12rpx;
+  padding: 0;
+  margin: 0;
+  border: none;
+}
+.card .sq-stock-btn::after {
+  border: none;
+}
+.card .sq-stock-btn--outline {
+  background: #ecfdf5;
+  color: #087f57;
+  font-weight: 600;
+}
 .card .product-scroll {
   margin-top: 12rpx;
   padding-bottom: 16rpx;

+ 2 - 1
sqxp-wx/utils/safeArea.js

@@ -37,7 +37,8 @@ export function computeSafeAreaMetrics() {
     const rightInset = Math.max(windowWidth - Number(menuRect.right || windowWidth), 0)
     capsuleRightSafeWidth = Math.max(capsuleWidth + rightInset + DEFAULT_HORIZONTAL_PADDING, 96)
   } else {
-    capsuleRightSafeWidth = 96
+    // 无胶囊位(H5、无自定义导航栏等):不预留右侧占位;样式里已有 32rpx 水平内边距
+    capsuleRightSafeWidth = 0
   }
 
   const topSafeHeight = statusBarHeight