ext.liuqiwen3 1 viikko sitten
vanhempi
commit
1b76a9d8c7
79 muutettua tiedostoa jossa 21680 lisäystä ja 613 poistoa
  1. 41 0
      api/api.js
  2. 75 0
      api/functions.js
  3. 23 0
      api/merchant.js
  4. 0 1
      api/order.js
  5. 77 0
      api/user.js
  6. 100 0
      api/vault.js
  7. 306 0
      components/couponListWindow/index.vue
  8. 140 0
      components/goodList/index.vue
  9. 126 0
      components/orderGoods/index.vue
  10. 252 0
      components/pay-pop/pay-pop.vue
  11. 298 0
      components/pay-pop/szjp.scss
  12. 143 0
      components/productConSwiper/index.vue
  13. 485 0
      components/productWindow/index.vue
  14. 346 0
      components/productWindow/index_origin.vue
  15. 146 0
      components/recommend/index.vue
  16. 4 2
      config/app.js
  17. 1 1
      manifest.json
  18. 1 1
      package-lock.json
  19. 231 4
      pages.json
  20. 175 0
      pages/goods_cate/goods_cate.vue
  21. 1976 0
      pages/goods_details/index.vue
  22. 214 0
      pages/goods_search/index.vue
  23. 532 590
      pages/index/index.vue
  24. 1281 0
      pages/order_addcart/order_addcart.vue
  25. 367 0
      pages/personal_info/personal_info.vue
  26. 19 3
      pages/user/index.vue
  27. 414 0
      pages/users/bank_card_manage/create.vue
  28. 444 0
      pages/users/bank_card_manage/index.vue
  29. 163 0
      pages/users/browsing_history/index.vue
  30. 25 8
      pages/users/login/index.vue
  31. 1401 0
      pages/users/order_confirm/index.vue
  32. 473 0
      pages/users/share/index.vue
  33. 518 0
      pages/users/user_address/index.vue
  34. 526 0
      pages/users/user_address_list/index.vue
  35. 103 0
      pages/users/vault/aggrement.vue
  36. 662 0
      pages/users/vault/buy.vue
  37. 1236 0
      pages/users/vault/index.vue
  38. 538 0
      pages/users/vault/rechargeGold.vue
  39. 862 0
      pages/users/vault/rechargeRmb.vue
  40. 603 0
      pages/users/vault/recycle/order_fill.vue
  41. 416 0
      pages/users/vault/recycle/recyle_order.vue
  42. 398 0
      pages/users/vault/recycle/report.vue
  43. 0 0
      pages/users/vault/saveGoldAgreement.vue
  44. 671 0
      pages/users/vault/save_gold.vue
  45. 701 0
      pages/users/vault/storeMetal/GoldMailForm.vue
  46. 363 0
      pages/users/vault/storeMetal/gmReport.vue
  47. 161 0
      pages/users/vault/storeMetal/goldBullionStock.vue
  48. 1103 0
      pages/users/vault/storeMetal/index.vue
  49. 817 0
      pages/users/vault/storeMetal/metalExchange.vue
  50. 508 0
      pages/users/vault/storeMetal/nonLogisticsGold.vue
  51. 355 0
      pages/users/vault/storeMetal/order.vue
  52. 291 0
      pages/users/vault/trade_list.vue
  53. 491 0
      pages/users/vault/withdraw.vue
  54. 4 1
      static/css/base.css
  55. BIN
      static/images/2-001.png
  56. BIN
      static/images/2-002.png
  57. BIN
      static/images/2-003.png
  58. BIN
      static/images/dianpu.png
  59. BIN
      static/images/indexBG.png
  60. BIN
      static/images/line.jpg
  61. BIN
      static/images/noAddress.png
  62. BIN
      static/images/noCart.png
  63. BIN
      static/images/noCoupon.png
  64. BIN
      static/images/noSearch.png
  65. BIN
      static/images/order.png
  66. BIN
      static/images/phone.png
  67. BIN
      static/images/right.png
  68. BIN
      static/images/share.png
  69. BIN
      static/images/shishijinjia.png
  70. BIN
      static/images/stop.png
  71. BIN
      static/images/tabbar/1-003.png
  72. BIN
      static/images/xyou.png
  73. BIN
      static/images/xzuo.png
  74. BIN
      static/images/zhankai.png
  75. BIN
      static/img/fenxiang_w.png
  76. BIN
      static/img/xiazai.png
  77. 2 2
      uni.scss
  78. 27 0
      utils/useRealtimeTimestamp.js
  79. 45 0
      utils/util.js

+ 41 - 0
api/api.js

@@ -6,3 +6,44 @@ import request from "@/utils/request.js";
 export function getMiniProgramData() {
   return request.get("mini-program/config/info", {}, { noAuth: true });
 }
+/**
+ * 获取主页数据 无需授权
+ *
+ */
+export function getIndexData() {
+  return request.get("index", {}, { noAuth: true });
+}
+/**
+ * 优惠券列表
+ * @param object data
+ */
+export function getCoupons(data) {
+  return request.get("coupons", data, { noAuth: true });
+}
+/**
+ * 领取优惠卷
+ * @param int couponId
+ *
+ */
+export function setCouponReceive(couponId) {
+  return request.post("coupon/receive", { couponId: couponId });
+}
+/**
+ * 获取小程序二维码
+ */
+export function getQrcode(data) {
+  return request.post("qrcode/get", data, { noAuth: true });
+}
+/**
+ * 获取城市信息
+ */
+export function getCity() {
+  return request.get("city/list", {}, { noAuth: true });
+}
+
+/**
+ * 获取商家的轮播图
+ */
+export function getMerchantBannerList(data) {
+  return request.get("sbmerchant/banner/list", data, { noAuth: true });
+}

+ 75 - 0
api/functions.js

@@ -0,0 +1,75 @@
+import request from "@/utils/request.js";
+
+/**
+ * 支付宝转账充值
+ *
+ */
+export function rechargeAlipayAPI(data) {
+  return request.post("user/recharge/alipay-transfer", data);
+}
+export function rechargeBankAPI(data) {
+  return request.post("user/recharge/bank-transfer", data);
+}
+
+// 充值金料
+export function rechargeGoldAPI(data) {
+  return request.post("metal/buy", data);
+}
+// 金料兑换余额
+export function exchangeGoldAPI(data) {
+  return request.post("metal/exchange", data);
+}
+// 卖料-约价回收
+export function recycleCreateAPI(data) {
+  return request.post("recycle/create", data);
+}
+// 卖料-约价回收订单列表
+export function recycleListAPI(params) {
+  return request.get("recycle/list", params);
+}
+// 取消订单
+export function recycleCancelAPI(params) {
+  return request.get("recycle/cancel", params);
+}
+// 更新快递单号和实物图片
+export function recyclUpdateAPI(data) {
+  return request.post("recycle/update-express", data);
+}
+// 用户确认报告
+export function recyclDetectionReportAPI(orderNo) {
+  return request.get(`recycle/detectionReport-notarize/${orderNo}`);
+}
+// 创建邮寄存金记录
+export function depositCreateAPI(data) {
+  return request.post(`postal-deposit/create`, data);
+}
+// 分页查询邮寄存金记录
+export function depositPageAPI(params) {
+  return request.get(`postal-deposit/page`, params);
+}
+// 用户确认报告
+export function postalDepositAPI(postalId) {
+  return request.get(`postal-deposit/user/notarize/${postalId}`);
+}
+// 创建无物流存金订单
+export function noLogisticsCreateAPI(data) {
+  return request.post("no-logistics-deposit/create", data);
+}
+// 创建好评返现订单
+export function goodReviewCreateAPI(data) {
+  return request.post("good-review/create", data);
+}
+// 查询用户好评返现订单
+export function goodReviewListAPI(params) {
+  return request.get(`good-review/list`, params);
+}
+// export function depositPageAPI(data) {
+//   return request.post(`postal-deposit/page`, data);
+// }
+/**
+ * 购买svip
+ *
+ */
+export function svipBuy(data) {
+  return request.post("svip/buy", data);
+}

+ 23 - 0
api/merchant.js

@@ -0,0 +1,23 @@
+import request from "@/utils/request.js";
+
+/**
+ * 根据商家id查询商家详情
+ * @param
+ */
+export function getSbmerchantInfo(data) {
+  return request.get("sbmerchant/info",data);
+}
+/**
+ * 扫描二维码访问商家
+ * @param
+ */
+export function footprintScan(data) {
+  return request.get("merchant/footprint/scan",data);
+}
+/**
+ * 查询当前登录用户的历史足迹列表
+ * @param
+ */
+export function footprintList(data) {
+  return request.get("merchant/footprint/list",data);
+}

+ 0 - 1
api/order.js

@@ -14,7 +14,6 @@ export function getCartCounts(numType, type) {
 export function getCartList(data) {
   return request.get("cart/list", data);
 }
-
 /**
  * 修改购物车数量
  * @param int cartId  购物车id

+ 77 - 0
api/user.js

@@ -101,3 +101,80 @@ export function getAddressList(data) {
 export function getCodeApi() {
   return request.get("verify_code", {}, { noAuth: true });
 }
+/**
+ * 生成二维码
+ *
+ */
+export function getQrcode(data) {
+  return request.get('qrcode',data);
+}
+/**
+ * armg-2025/8/21-获取协议
+ * @param name 协议代码
+ *  xy_zcsm:资产说明
+ *  xy_mryz:每日一攒
+ *  xy_cjxy:存金协议
+ *  xy_cjgz:存金规则
+ *  xy_mjxy:买金协议
+ * xy_hkxy: 还款协议
+ */
+export function agreementGetoneApi(data) {
+  return request.get(`agreement/getone`, data);
+}
+
+/**
+ * 获取用户支付账户列表
+ */
+export function getAccountList(data) {
+  return request.get("user/account/list", data);
+}
+
+/**
+ * 添加或更新支付账户
+ */
+export function saveAccount(data) {
+  return request.post("user/account/save", data);
+}
+/**
+ * 删除支付账户
+ */
+export function deleteAccount(id) {
+  return request.post(`user/account/delete/${id}`);
+}
+/**
+ * 修改 添加地址
+ * @param object data
+ */
+export function editAddress(data) {
+  return request.post("address/edit", data);
+}
+/**
+ * 设置默认地址
+ * @param int id
+ */
+export function setAddressDefault(id) {
+  return request.post("address/default/set", { id: id });
+}
+/**
+ * 删除地址
+ * @param int id
+ *
+ */
+export function delAddress(id) {
+  return request.post("address/del", { id: id });
+}
+// 更改支付密码
+export function registerpayPasswordAPI(data) {
+  return request.post("register/payPassword", data);
+}
+// 支付密码确认
+export function userPayPasswordConfirmAPI(data) {
+  return request.post("user/payPassword/confirm", data);
+}
+/**
+ * 修改用户信息
+ * @param object
+ */
+export function userEdit(data) {
+  return request.post("user/edit", data);
+}

+ 100 - 0
api/vault.js

@@ -0,0 +1,100 @@
+import request from "@/utils/request.js";
+
+/**
+ * 获取提现记录列表
+ */
+export function getWithdrawList(data) {
+	return request.get('extract/record2', data);
+}
+
+/**
+ * 获取充值记录列表
+ */
+export function getRechargeList(data) {
+	return request.get('recharge/list', data);
+}
+
+/**
+ * 获取充值的金额列表
+ */
+export function getAmountList(data) {
+	return request.get('recharge/index', data);
+}
+
+/**
+ * 获取消费记录列表
+ */
+export function getConsumeList(data) {
+	return request.get('infobycondition', data);
+}
+
+/**
+ * 获取贵金属余额
+ */
+export function getMetalBalance(data) {
+	return request.get('goldbalance/getBalance', data);
+}
+
+/**
+ * 充值预下单
+ */
+export function rechargeOrder(data) {
+	return request.post('recharge/wechat-alipay', data);
+}
+
+/**
+ * 买金
+ */
+export function buyGold(data) {
+	return request.post('goldprincipal/buy', data);
+}
+
+/**
+ * 每日一攒次数获取
+ */
+export function accumulateCount(data) {
+	return request.post('goldprincipal/selectcount', data);
+}
+
+/**
+ * 直播换款
+ */
+export function liveExchange(data) {
+	return request.post('goldprincipal/changestyle', data);
+}
+
+/**
+ * 买卖金交易明细列表
+ */
+export function goldTradelist(data) {
+	return request.get('goldprincipal/list', data);
+}
+
+/**
+ * 设置金价预警
+ */
+export function setGoldAlert(data) {
+	return request.post('goldwarning/create', data);
+}
+
+/**
+ * 获取金价预警记录
+ */
+export function getGoldAlertList(data) {
+	return request.get('goldwarning/list', data);
+}
+
+/**
+ * 删除金价预警记录
+ */
+export function deleteGoldAlert(data) {
+	return request.get('goldwarning/delete', data);
+}
+
+/**
+ * 每日一攒 列表
+ */
+export function goldprincipalList2(data) {
+	return request.get('goldprincipal/list2', data);
+}
+

+ 306 - 0
components/couponListWindow/index.vue

@@ -0,0 +1,306 @@
+<template>
+  <up-popup :show="showPopup" :closeOnClickOverlay="true" @close="close">
+    <view class="coupon-list-window">
+      <view v-if="!orderShow" class="nav acea-row row-around">
+        <view
+          :class="['acea-row', 'row-middle', type === 1 ? 'on' : '']"
+          @click="setType(1)"
+          >通用券</view
+        >
+        <view
+          :class="['acea-row', 'row-middle', type === 2 ? 'on' : '']"
+          @click="setType(2)"
+          >商品券</view
+        >
+        <view
+          :class="['acea-row', 'row-middle', type === 3 ? 'on' : '']"
+          @click="setType(3)"
+          >品类券</view
+        >
+        <view
+          :class="['acea-row', 'row-middle', type === 4 ? 'on' : '']"
+          @click="setType(4)"
+          >折扣券</view
+        >
+      </view>
+      <view class="coupon-box">
+        <view
+          class="coupon-list"
+          :style="{ 'margin-top': !orderShow ? '0' : '50rpx' }"
+        >
+          <scroll-view
+            class="scroll-box"
+            v-if="coupon.list.length"
+            scroll-y="true"
+          >
+            <block v-if="coupon.list.length">
+              <!-- <view class='item acea-row row-center-wrapper' v-for="(item,index) in coupon.list" :key='index'> -->
+              <view
+                class="item acea-row row-center-wrapper"
+                v-for="(item, index) in coupon.list"
+                @click="getCouponUser(index, item.id)"
+                :key="index"
+              >
+                <view
+                  class="money acea-row row-column row-center-wrapper"
+                  :class="item.isUse ? 'moneyGray' : ''"
+                >
+                  <view
+                    >¥<text class="num">{{
+                      item.money ? Number(item.money) : ""
+                    }}</text></view
+                  >
+                  <view class="pic-num">满{{ item.minPrice }}元可用</view>
+                </view>
+                <view class="text">
+                  <view class="condition line2">
+                    <span
+                      class="line-title"
+                      :class="item.isUse ? 'gray' : ''"
+                      v-if="item.useType === 1"
+                      >通用</span
+                    >
+                    <span
+                      class="line-title"
+                      :class="item.isUse ? 'gray' : ''"
+                      v-else-if="item.useType === 3"
+                      >品类</span
+                    >
+                    <span
+                      class="line-title"
+                      :class="item.isUse ? 'gray' : ''"
+                      v-else
+                      >商品</span
+                    >
+                    <span>{{ item.name }}</span>
+                  </view>
+                  <view class="tip-row">注: 工费一次性抵扣</view>
+                  <view class="data acea-row row-between-wrapper">
+                    <view v-if="item.day > 0"
+                      >领取后{{ item.day }}天内可用</view
+                    >
+                    <view v-else>
+                      {{
+                        item.useStartTimeStr && item.useEndTimeStr
+                          ? item.useStartTimeStr + " - " + item.useEndTimeStr
+                          : ""
+                      }}
+                    </view>
+                    <view class="bnt gray" v-if="item.isUse">{{
+                      item.use_title || "已领取"
+                    }}</view>
+                    <view class="bnt bg-color" v-else>{{
+                      coupon.statusTile || "立即领取"
+                    }}</view>
+                  </view>
+                </view>
+              </view>
+            </block>
+          </scroll-view>
+          <!-- 无优惠券 -->
+          <view class="pictrue" v-else>
+            <image src="/static/images/noCoupon.png"></image>
+          </view>
+        </view>
+      </view>
+    </view>
+  </up-popup>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { setCouponReceive } from "@/api/api.js";
+import { useToast } from "@/hooks/useToast";
+
+const props = defineProps({
+  showPopup: {
+    type: Boolean,
+    default: false,
+  },
+  openType: {
+    type: Number,
+    default: 0,
+  },
+  coupon: {
+    type: Object,
+    default: () => ({}),
+  },
+  orderShow: {
+    type: String,
+    default: "",
+  },
+});
+
+const { Toast } = useToast();
+const emit = defineEmits([
+  "close",
+  "ChangCouponsUseState",
+  "ChangCoupons",
+  "tabCouponType",
+]);
+
+const type = ref(1);
+
+function close() {
+  type.value = 1;
+  emit("close");
+}
+
+function getCouponUser(index, id) {
+  console.log({index, id}, props.openType)
+  const list = props.coupon.list;
+  if (list && list[index] && list[index].isUse === true && props.openType === 0)
+    return true;
+  switch (props.openType) {
+    case 0:
+      setCouponReceive(id).then(() => {
+        emit("ChangCouponsUseState", index);
+        Toast({ title: "领取成功" });
+        emit("ChangCoupons", list[index]);
+      });
+      break;
+    case 1:
+      emit("ChangCoupons", index);
+      break;
+  }
+}
+
+function setType(val) {
+  type.value = val;
+  emit("tabCouponType", val);
+}
+</script>
+
+<style scoped lang="scss">
+.coupon-list-window {
+  width: 100%;
+  background-color: #f5f5f5;
+  border-radius: 16rpx 16rpx 0 0;
+}
+
+.coupon-list-window .title {
+  height: 124rpx;
+  width: 100%;
+  text-align: center;
+  line-height: 124rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  position: relative;
+}
+
+.coupon-list-window .iconfont {
+  position: absolute;
+  right: 30rpx;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 35rpx;
+  color: #8a8a8a;
+  font-weight: normal;
+}
+
+.coupon-list {
+  height: 823rpx;
+  overflow: auto;
+  padding: 30rpx 0;
+}
+
+.pictrue {
+  width: 414rpx;
+  height: 336rpx;
+  margin: 208rpx auto;
+  image {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.pic-num {
+  color: #fff;
+  font-size: 24rpx;
+}
+
+.line-title {
+  width: 90rpx;
+  padding: 0 10rpx;
+  box-sizing: border-box;
+  background: rgba(255, 247, 247, 1);
+  border: 1px solid rgba(232, 51, 35, 1);
+  opacity: 1;
+  border-radius: 20rpx;
+  font-size: 20rpx;
+  color: #e83323;
+  margin-right: 12rpx;
+  &.gray {
+    border-color: #bbb;
+    color: #bbb;
+    background-color: #f5f5f5;
+  }
+}
+
+.tip-row {
+  font-size: 26rpx;
+  // color: #ccc;
+}
+
+.nav {
+  width: 100%;
+  height: 96rpx;
+  border-bottom: 2rpx solid #f5f5f5;
+  border-top-left-radius: 16rpx;
+  border-top-right-radius: 16rpx;
+  background-color: #ffffff;
+  font-size: 30rpx;
+  color: #999999;
+  .acea-row {
+    border-top: 5rpx solid transparent;
+    border-bottom: 5rpx solid transparent;
+    &.on {
+      border-bottom-color: #e93323;
+      color: #282828;
+    }
+    &:only-child {
+      border-bottom-color: transparent;
+    }
+  }
+}
+
+.occupy {
+  height: 106rpx;
+}
+
+.coupon-box {
+}
+.coupon-list {
+  .scroll-box {
+    height: 100%;
+    .scroll-Y {
+      height: 300rpx;
+    }
+    .scroll-view_H {
+      white-space: nowrap;
+      width: 100%;
+    }
+    .scroll-view-item {
+      height: 300rpx;
+      line-height: 300rpx;
+      text-align: center;
+      font-size: 36rpx;
+    }
+    .scroll-view-item_H {
+      display: inline-block;
+      width: 100%;
+      height: 300rpx;
+      line-height: 300rpx;
+      text-align: center;
+      font-size: 36rpx;
+    }
+  }
+  .item {
+    margin-bottom: 20rpx;
+    box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
+    .money {
+      font-weight: normal;
+    }
+  }
+}
+</style>

+ 140 - 0
components/goodList/index.vue

@@ -0,0 +1,140 @@
+<template>
+	<view class='goodList'>
+		<block v-for="(item,index) in bastList" :key="index">
+			<view @click="goDetail(item)" class='item acea-row row-between-wrapper' hover-class="none">
+				<view class='pictrue'>
+					<image :src='item.image'></image>
+					<span class="pictrue_log pictrue_log_class" v-if="item.activityH5 && item.activityH5.type === '1'">秒杀</span>
+					<span class="pictrue_log pictrue_log_class" v-if="item.activityH5 && item.activityH5.type === '2'">砍价</span>
+					<span class="pictrue_log pictrue_log_class" v-if="item.activityH5 && item.activityH5.type === '3'">拼团</span>
+				</view>
+				<view class='underline'>
+					<view class='text'>
+						<view class='line1'>{{item.storeName}}</view>
+						<view class='money font-color'>¥<text class='num'>{{item.price}}</text></view>
+						<view class='vip-money acea-row row-middle' v-if="item.vip_price && item.vip_price > 0">¥{{item.vip_price || 0}}
+							<image src='/static/images/vip.png'></image><text class='num'>已售{{Number(item.sales) + Number(item.ficti) || 0}}{{item.unitName}}</text>
+						</view>
+						<view class='vip-money acea-row row-middle' v-else><text class='num'>已售{{Number(item.sales) + Number(item.ficti) || 0}}{{item.unitName}}</text></view>
+					</view>
+				</view>
+				<view class='iconfont icon-gouwuche cart-color acea-row row-center-wrapper'></view>
+			</view>
+		</block>
+	</view>
+</template>
+
+<script setup>
+import { useAppStore } from '@/stores/app.js';
+import { goShopDetail } from '@/libs/order.js';
+
+const appStore = useAppStore();
+const uid = appStore.uidComputed;
+
+// 定义 props
+const props = defineProps({
+  status: {
+    type: Number,
+    default: 0,
+  },
+  bastList: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+// Navigation to detail page
+const goDetail = async (item) => {
+  try {
+    await goShopDetail(item, uid.value);
+    uni.navigateTo({
+      url: `/pages/goods_details/index?id=${item.id}`,
+    });
+  } catch (err) {
+    console.error('Navigation error:', err);
+  }
+};
+</script>
+
+<style scoped lang='scss'>
+	.goodList .item {
+		position: relative;
+		padding-left: 30rpx;
+	}
+
+	.goodList .item .pictrue {
+		width: 180rpx;
+		height: 180rpx;
+		position: relative;
+	}
+
+	.goodList .item .pictrue image {
+		width: 100%;
+		height: 100%;
+		border-radius: 6rpx;
+	}
+
+	.goodList .item .pictrue .numPic {
+		position: absolute;
+		left: 7rpx;
+		top: 7rpx;
+		width: 50rpx;
+		height: 50rpx;
+		border-radius: 50%;
+	}
+
+	.goodList .item .underline {
+		padding: 30rpx 30rpx 30rpx 0;
+		border-bottom: 1px solid #f5f5f5;
+	}
+
+	.goodList .item:nth-last-child(1) .underline {
+		border-bottom: 0;
+	}
+
+	.goodList .item .text {
+		font-size: 30rpx;
+		color: #222;
+		width: 489rpx;
+	}
+
+	.goodList .item .text .money {
+		font-size: 26rpx;
+		font-weight: bold;
+		margin-top: 50rpx;
+	}
+
+	.goodList .item .text .money .num {
+		font-size: 34rpx;
+	}
+
+	.goodList .item .text .vip-money {
+		font-size: 24rpx;
+		color: #282828;
+		font-weight: bold;
+		margin-top: 15rpx;
+	}
+
+	.goodList .item .text .vip-money image {
+		width: 46rpx;
+		height: 21rpx;
+		margin: 0 22rpx 0 5rpx;
+	}
+
+	.goodList .item .text .vip-money .num {
+		font-size: 22rpx;
+		color: #aaa;
+		font-weight: normal;
+		margin: -2rpx 0 0 0;
+	}
+
+	.goodList .item .iconfont {
+		position: absolute;
+		right: 30rpx;
+		width: 50rpx;
+		height: 50rpx;
+		border-radius: 50%;
+		font-size: 30rpx;
+		bottom: 38rpx;
+	}
+</style>

+ 126 - 0
components/orderGoods/index.vue

@@ -0,0 +1,126 @@
+<template>
+  <view class="orderGoods borRadius14">
+    <view class="total"
+      >共{{ orderProNum ? orderProNum : totalNmu }}件商品</view
+    >
+    <view class="goodWrapper pad30">
+      <view
+        class="item acea-row row-between-wrapper"
+        v-for="(item, index) in cartInfo"
+        :key="index"
+        @click="jumpCon(item.productId)"
+      >
+        <view class="pictrue">
+          <image :src="item.image"></image>
+        </view>
+        <view class="text">
+          <view class="acea-row row-between-wrapper">
+            <view class="name line1">
+              {{ item.productName ? item.productName : item.storeName }}
+            </view>
+            <view class="num">
+              x {{ item.payNum ? item.payNum : item.cartNum }}
+            </view>
+          </view>
+          <view class="attr line1" v-if="item.sku">属性: {{ item.sku }}</view>
+          <view v-if="mallType === 0" class="money font-color">
+            ¥{{ Number(item.storePrice).toFixed(2) }}
+          </view>
+          <view v-else class="money font-color">
+            贝币:{{ Number(item.storePrice).toFixed(2) }}
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, watch } from "vue";
+
+const props = defineProps({
+  evaluate: {
+    type: Number,
+    default: 0,
+  },
+  mallType: {
+    type: Number,
+    default: 0, // 0: 水贝商城, 1: 贝币商城
+  },
+  cartInfo: {
+    type: Array,
+    default: () => [],
+  },
+  orderId: {
+    type: String,
+    default: "",
+  },
+  ids: {
+    type: Number,
+    default: 0,
+  },
+  jump: {
+    type: Boolean,
+    default: false,
+  },
+  orderProNum: {
+    type: Number,
+    default: 0,
+  },
+  productType: {
+    type: Number,
+    default: 0,
+  },
+});
+
+const totalNmu = ref("");
+
+watch(
+  () => props.cartInfo,
+  (nVal) => {
+    let num = 0;
+    nVal.forEach((item) => {
+      num += item.cartNum;
+    });
+    totalNmu.value = num;
+  },
+  { immediate: true }
+);
+
+function evaluateTap(item) {
+  uni.navigateTo({
+    url: `/pages/users/goods_comment_con/index?unique=${item.attrId}&orderId=${props.orderId}&id=${props.ids}`,
+  });
+}
+
+function jumpCon(id) {
+  let type = props.productType == 0 ? "normal" : "video";
+  if (props.jump) {
+    uni.navigateTo({
+      url: `/pages/goods_details/index?id=${id}&type=${type}`,
+    });
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.orderGoods {
+  background-color: #fff;
+  margin-top: 15rpx;
+}
+
+.orderGoods .total {
+  width: 100%;
+  height: 86rpx;
+  padding: 0 24rpx;
+  border-bottom: 2rpx solid #f0f0f0;
+  font-size: 30rpx;
+  color: #282828;
+  line-height: 86rpx;
+  box-sizing: border-box;
+}
+
+.pictrue image {
+  background: #f4f4f4;
+}
+</style>

+ 252 - 0
components/pay-pop/pay-pop.vue

@@ -0,0 +1,252 @@
+<template>
+  <view class="szjp">
+    <!--  -->
+    <view v-if="show">
+      <view class="mask" @tap="Close"></view>
+      <view
+        class="box"
+        style="z-index: 10000"
+        :class="AlertObj[AlertClass][AlertClassUp]"
+      >
+        <view class="bottom">
+          <view class="bg">
+            <image
+              src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/payPop/bg.png"
+              mode="widthFix"
+            ></image>
+          </view>
+          <view class="tle">
+            <view class="tle_lft">
+              <image
+                src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/payPop/pwd.png"
+                mode="widthFix"
+              ></image>
+              <view class="tle_1">{{ txt }}</view>
+              <view class="tle_2">(长度{{ pwdlength }}位)</view>
+            </view>
+            <view class="tle_rgt" @tap.stop="is_vis = !is_vis">
+              <!-- <block> -->
+              <image
+                v-show="is_vis"
+                src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/payPop/yj2.png"
+                mode="widthFix"
+                :width="20"
+                :height="20"
+              ></image>
+              <image
+                v-show="!is_vis"
+                src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/payPop/yj1.png"
+                mode="widthFix"
+                :width="20"
+                :height="20"
+              ></image>
+              <!-- </block> -->
+              <!-- <block v-show="!is_vis">
+                                <image src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/payPop/yj1.png" mode="widthFix"></image>
+                            </block> -->
+            </view>
+          </view>
+          <view class="pwd_box" @click.stop>
+            <view
+              v-for="(v, k) in pwdlength"
+              :key="k"
+              class="pwd-text"
+              :class="{ active: idx === k }"
+            >
+              {{ star_lis[k] === undefined ? "" : star_lis[k] }}
+              <!-- {{is_vis?star_lis[k]:res_pwd[k]}} -->
+            </view>
+          </view>
+          <view class="pwd_info">
+            <!-- <view>可以放些提示信息</view> -->
+          </view>
+          <view class="solt" @click.stop>
+            <view class="s_lft">
+              <button
+                v-for="(v, k) in 9"
+                :key="k"
+                class="s_li"
+                type="primary"
+                plain="true"
+                @tap="click(k + 1)"
+              >
+                {{ k + 1 }}
+              </button>
+              <button
+                class="s_li s_o"
+                type="primary"
+                plain="true"
+                @tap="click(0)"
+              >
+                0
+              </button>
+              <button
+                class="s_li s_sq"
+                type="primary"
+                plain="true"
+                @tap="Close"
+              >
+                <image
+                  src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/payPop/sq.png"
+                  mode="widthFix"
+                ></image>
+              </button>
+            </view>
+            <view class="s_rgt">
+              <view class="s_cx">
+                <button class="s_li s_x" type="primary" plain="true" @tap="del">
+                  <image
+                    src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/payPop/ht.png"
+                    mode="widthFix"
+                  ></image>
+                </button>
+              </view>
+              <view class="s_qd">
+                <button
+                  class="s_li s_s"
+                  type="primary"
+                  plain="true"
+                  @tap="submit"
+                >
+                  确定
+                </button>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+
+// 定义组件属性
+const props = defineProps({
+  txt: {
+    type: String,
+    default: "支付密码",
+  },
+  pwdlength: {
+    type: [Number, String],
+    default: 1,
+  },
+});
+
+// 定义事件
+const emit = defineEmits(["pwd_e", "close"]);
+
+// 响应式数据
+const Repeat = ref(true);
+const show = ref(false);
+const AlertObj = ref([["a-bounceinB", "a-bounceoutB"]]);
+const AlertClass = ref(0);
+const AlertClassUp = ref(0);
+const res_pwd = ref([]);
+const fa_par = ref("");
+const is_vis = ref(true); // 密码是否可见
+
+// 计算属性
+const idx = computed(() => {
+  return res_pwd.value.length;
+});
+
+const star_lis = computed(() => {
+  if (is_vis.value) {
+    return res_pwd.value;
+  } else {
+    const arr = [];
+    res_pwd.value.forEach(() => {
+      arr.push("*");
+    });
+    return arr;
+  }
+});
+
+// 方法定义
+const click = (e) => {
+  if (res_pwd.value.length <= props.pwdlength - 1) {
+    res_pwd.value.push(e);
+    // #ifdef APP-PLUS
+    plus.device.vibrate(50); // 震动
+    // #endif
+  } else {
+    toast(`最大长度${props.pwdlength}位`);
+  }
+};
+
+const del = () => {
+  res_pwd.value.pop();
+  // #ifdef APP-PLUS
+  plus.device.vibrate(50); // 震动
+  // #endif
+};
+
+const submit = () => {
+  // #ifdef APP-PLUS
+  plus.device.vibrate(50); // 震动
+  // #endif
+  if (res_pwd.value.length === props.pwdlength) {
+    const res = res_pwd.value.join("");
+    emit("pwd_e", res, fa_par.value); // 调用父组件方法
+
+    Close();
+  } else {
+    toast(`长度未满足`);
+  }
+};
+
+// 打开
+const Open = (par) => {
+  if (par) {
+    console.log("父元素传参", par);
+    fa_par.value = par; // 保存父元素传参
+  }
+  if (!Repeat.value) {
+    return false;
+  }
+  res_pwd.value = [];
+  Repeat.value = false;
+  AlertClassUp.value = 0;
+  show.value = true;
+  setTimeout(() => {
+    Repeat.value = true;
+  }, 300);
+};
+
+// 关闭
+const Close = () => {
+  if (!Repeat.value) {
+    return false;
+  }
+
+  Repeat.value = false;
+  AlertClassUp.value = 1;
+  setTimeout(() => {
+    show.value = false;
+    Repeat.value = true;
+    // 触发关闭事件
+    emit("close");
+  }, 300);
+};
+
+const toast = (val) => {
+  uni.showToast({
+    title: val,
+    icon: "error",
+  });
+};
+
+// 暴露方法给父组件调用
+defineExpose({
+  Open,
+  Close,
+  submit,
+});
+</script>
+
+<style lang="scss" scoped>
+/*引入css文件*/
+@import "./szjp.scss";
+</style>

+ 298 - 0
components/pay-pop/szjp.scss

@@ -0,0 +1,298 @@
+.mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.4);
+}
+
+.box {
+  width: 100vw;
+  height: 50vh;
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  margin: auto;
+  z-index: 101;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  .center {
+    position: absolute;
+  }
+
+  .bottom {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    background: #fff;
+    border-radius: 30rpx 30rpx 0 0;
+
+    .tle {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 30rpx 3%;
+
+      .tle_lft {
+        display: flex;
+        align-items: center;
+
+        image {
+          width:60rpx;
+          height:60rpx;
+        }
+
+        .tle_1 {
+          font-size: 30rpx;
+          color: #444;
+          font-weight: bold;
+          letter-spacing: 3rpx;
+          padding-left: 8rpx;
+        }
+
+        .tle_2 {
+          font-size: 24rpx;
+          color: #999;
+          letter-spacing: 3rpx;
+          padding-left: 10rpx;
+        }
+      }
+
+      .tle_rgt {
+        padding: 0 20rpx;
+        image {
+          width: 40rpx;
+        }
+      }
+    }
+
+    .pwd_info {
+      text-align: center;
+      font-size: 24rpx;
+      color: red;
+      height: 100rpx;
+      line-height: 80rpx;
+    }
+
+    .bg {
+      opacity: 0.5;
+      position: absolute;
+      top: 85rpx;
+      // left: 0;
+      right: 0;
+
+      image {
+        width: 250rpx;
+      }
+    }
+  }
+}
+
+.pwd_box {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #fff;
+  padding: 20rpx 0 0 0;
+
+  .pwd-text {
+    position: relative;
+    line-height: 60rpx;
+    vertical-align: middle;
+    text-align: center;
+    font-size: 46rpx;
+    color: #0fb361;
+    font-weight: bold;
+    width: 60rpx;
+    height: 70rpx;
+    margin-right: 20rpx;
+    display: inline-block;
+    border-bottom: solid 1px #666;
+  }
+
+  .pwd-text.active:after {
+    -webkit-animation: twinkle 1s infinite;
+    animation: twinkle 1s infinite;
+    height: 40rpx;
+    width: 5rpx;
+    content: "";
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background-color: #007aff;
+  }
+
+  @-webkit-keyframes twinkle {
+    from {
+      background-color: #007aff;
+    }
+
+    to {
+      background-color: transparent;
+    }
+  }
+}
+
+.solt {
+  width: 96vw;
+  background: #f5f5f5;
+  padding: 40rpx 2vw;
+  display: flex;
+  align-items: center;
+  border-top: 1px solid #ccc;
+
+  //   border-radius: 30rpx 30rpx 0 0;
+  .s_lft {
+    width: 78vw;
+    padding-right: 10rpx;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    flex-wrap: wrap;
+
+    .s_li {
+      width: 31%;
+      margin-bottom: 20rpx;
+      background: #fff;
+      height: 90rpx;
+      line-height: 90rpx;
+      border-radius: 10rpx;
+      text-align: center;
+      font-size: 40rpx;
+      color: #444;
+      font-weight: bold;
+      border: 1px solid #fff;
+    }
+
+    .s_o {
+      width: 65%;
+    }
+
+    .button-hover {
+      background: #eee !important;
+    }
+
+    .s_sq {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      image {
+        width: 30rpx;
+        height: 30rpx;
+      }
+    }
+  }
+
+  .s_rgt {
+    width: 20vw;
+    height: 100%;
+
+    .s_li {
+      width: 100%;
+      margin-bottom: 20rpx;
+      background: #fff;
+
+      border-radius: 10rpx;
+      text-align: center;
+      font-size: 28rpx;
+      color: #000;
+      font-weight: bold;
+      border: 1px solid #fff;
+    }
+
+    .s_s {
+      height: 200rpx;
+      line-height: 200rpx;
+      color: #fff;
+      background: #0fb361;
+    }
+
+    .s_x {
+      height: 200rpx;
+      line-height: 200rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      image {
+        width: 50rpx;
+      }
+    }
+
+    .s_cx {
+      .button-hover {
+        background: #eee !important;
+      }
+    }
+
+    .s_qd {
+      .button-hover {
+        background: #2aa515 !important;
+      }
+    }
+  }
+}
+
+
+/* 弹入-从下 */
+.a-bounceinB {
+  -webkit-animation: 0.3s ease-out backwards;
+  -moz-animation: 0.3s ease-out backwards;
+  -ms-animation: 0.3s ease-out backwards;
+  -moz-transform: translate3d(0, 0, 0);
+  -ms-transform: translate3d(0, 0, 0);
+  -o-transform: translate3d(0, 0, 0);
+  transform: translate3d(0, 0, 0);
+  -webkit-animation-name: bounceinB;
+  -moz-animation-name: bounceinB;
+  -ms-animation-name: bounceinB;
+  animation-name: bounceinB;
+}
+
+/* 弹入-从下 */
+@keyframes bounceinB {
+  0% {
+    opacity: 0;
+    transform: translateY(100%);
+  }
+
+  100% {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 弹出-向下 */
+.a-bounceoutB {
+  -webkit-animation: 0.3s ease-in forwards;
+  -moz-animation: 0.3s ease-in forwards;
+  -ms-animation: 0.3s ease-in forwards;
+  animation: 0.3s ease-in forwards;
+  -moz-transform: translate3d(0, 0, 0);
+  -ms-transform: translate3d(0, 0, 0);
+  -o-transform: translate3d(0, 0, 0);
+  transform: translate3d(0, 0, 0);
+  -webkit-animation-name: bounceoutB;
+  -moz-animation-name: bounceoutB;
+  -ms-animation-name: bounceoutB;
+  animation-name: bounceoutB;
+}
+
+/* 弹出-向下 */
+@keyframes bounceoutB {
+  0% {
+    opacity: 1;
+    transform: translateY(0);
+  }
+
+  100% {
+    opacity: 0;
+    transform: translateY(100%);
+  }
+}

+ 143 - 0
components/productConSwiper/index.vue

@@ -0,0 +1,143 @@
+<template>
+	<view class='product-bg'>
+		<swiper :indicator-dots="indicatorDots" indicator-active-color="#e93323" :autoplay="autoplay"
+			:circular="circular" :interval="interval" :duration="duration" @change="change">
+			
+			<swiper-item v-if="videoline">
+				<view class="item">
+					<view v-show="!controls" style="width:100%;height:100% ">
+						<video id="myVideo" :src='videoline' objectFit="cover" controls style="width:100%;height:100% "
+							show-center-play-btn show-mute-btn="true" auto-pause-if-navigate :custom-cache="false"
+							:enable-progress-gesture="false" :poster="imgUrls[0]" @pause="videoPause"></video>
+					</view>
+					<view class="poster" v-show="controls">
+						<image class="image" :src="imgUrls[0]"></image>
+					</view>
+					<view class="stop" v-show="controls" @tap="bindPause">
+						<image class="image" src="@/static/images/stop.png"></image>
+					</view>
+				</view>
+			</swiper-item>
+			
+			<block v-for="(item,index) in imgUrls" :key='index'>
+				<swiper-item>
+					<image :src="item" class="slide-image" />
+				</swiper-item>
+			</block>
+		</swiper>
+	</view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+
+const props = defineProps({
+	imgUrls: {
+		type: Array,
+		default: () => []
+	},
+	videoline: {
+		type: String,
+		default: ""
+	}
+});
+
+const indicatorDots = ref(true);
+const circular = ref(true);
+const autoplay = ref(true);
+const interval = ref(3000);
+const duration = ref(500);
+const currents = ref("1");
+const controls = ref(true);
+const isPlay = ref(true);
+const videoContext = ref('');
+onMounted(() => {
+	if (props.videoline) {
+		props.imgUrls.shift();
+	}
+});
+const bindPause = () => {
+	videoContext.value.play();
+	controls.value = false;
+	autoplay.value = false;
+};
+const change = (e) => {
+	currents.value = e.detail.current + 1;
+};
+const videoPause = (e) => {
+	// Handle video pause event if needed
+};
+</script>
+
+<style scoped lang="scss">
+	.product-bg {
+		width: 100%;
+		height: 750rpx;
+		position: relative;
+	}
+
+	.product-bg swiper {
+		width: 100%;
+		height: 100%;
+		position: relative;
+	}
+
+	.product-bg .slide-image {
+		width: 100%;
+		height: 100%;
+	}
+
+	.product-bg .pages {
+		position: absolute;
+		background-color: #fff;
+		height: 34rpx;
+		padding: 0 10rpx;
+		border-radius: 3rpx;
+		right: 30rpx;
+		bottom: 30rpx;
+		line-height: 34rpx;
+		font-size: 24rpx;
+		color: #050505;
+	}
+
+	#myVideo {
+		width: 100%;
+		height: 100%
+	}
+
+	.product-bg .item {
+		position: relative;
+		width: 100%;
+		height: 100%;
+	}
+
+	.product-bg .item .poster {
+		position: absolute;
+		top: 0;
+		left: 0;
+		height: 750rpx;
+		width: 100%;
+		z-index: 9;
+	}
+
+	.product-bg .item .poster .image {
+		width: 100%;
+		height: 100%;
+	}
+
+	.product-bg .item .stop {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		width: 136rpx;
+		height: 136rpx;
+		margin-top: -68rpx;
+		margin-left: -68rpx;
+		z-index: 9;
+	}
+
+	.product-bg .item .stop .image {
+		width: 100%;
+		height: 100%;
+	}
+</style>

+ 485 - 0
components/productWindow/index.vue

@@ -0,0 +1,485 @@
+<template>
+  <up-popup :show="showPopup" :closeOnClickOverlay="true" @close="close" closeable>
+    <view
+      class="product-window"
+      :class="(iSbnt ? 'join' : '') + ' ' + (iScart ? 'joinCart' : '')"
+    >
+      <view class="popup-title">请选择规格</view>
+      <view class="textpic acea-row">
+        <view class="pictrue">
+          <image :src="attr.productSelect.image"></image>
+        </view>
+        <view class="text">
+<!--          <view class="line1">-->
+<!--            {{ attr.productSelect.storeName }}-->
+<!--          </view>-->
+          <view class="money font-color">
+            ¥<text class="num">{{ calcNumPrice }}</text>
+
+          </view>
+          <view class="font-color" >
+            <text class="stock"
+            >工费: {{ attr.productSelect.price || 0 }}元/g</text
+            >
+            <text class="stock"
+            >附加费: {{ attr.productSelect.additionalAmount || "0" }}元</text
+            >
+          </view>
+          <view class="font-color" >
+            <view class="carnum acea-row row-left">
+              <view
+                  class="item reduce"
+                  :class="attr.productSelect.cart_num <= 1 ? 'on' : ''"
+                  @click="CartNumDes"
+              >
+                -
+              </view>
+              <view class="item num">
+                <input
+                    type="number"
+                    v-model="attr.productSelect.cart_num"
+                    data-name="productSelect.cart_num"
+                    @input="bindCode(attr.productSelect.cart_num)"
+                />
+              </view>
+              <view
+                  v-if="iSplus"
+                  class="item plus"
+                  :class="
+                attr.productSelect.cart_num >= attr.productSelect.stock
+                  ? 'on'
+                  : ''
+              "
+                  @click="CartNumAdd"
+              >
+                +
+              </view>
+              <view
+                  v-else
+                  class="item plus"
+                  :class="
+                attr.productSelect.cart_num >= attr.productSelect.quota ||
+                attr.productSelect.cart_num >= attr.productSelect.stock ||
+                attr.productSelect.cart_num >= attr.productSelect.num
+                  ? 'on'
+                  : ''
+              "
+                  @click="CartNumAdd"
+              >+</view
+              >
+            </view>
+            <text class="stock font333" v-if="isShow"
+            >库存: {{ attr.productSelect.stock }}</text
+            >
+            <text class="stock font333" v-if="limitNum"
+            >限量: {{ attr.productSelect.quota }}</text
+            >
+          </view>
+        </view>
+        <view class="iconfont icon-guanbi" @click="close"></view>
+      </view>
+      <view class="rollTop">
+        <view class="productWinList">
+          <view
+            class="item"
+            v-for="(item, indexw) in attr.productAttr"
+            :key="indexw"
+          >
+            <view class="title">{{ item.attrName }}</view>
+            <view class="listn acea-row row-middle">
+              <view
+                class="itemn"
+                :class="item.index === itemn ? 'on' : ''"
+                v-for="(itemn, indexn) in item.attrValues"
+                @click="tapAttr(indexw, indexn)"
+                :key="indexn"
+              >
+                {{ itemn }}
+              </view>
+            </view>
+          </view>
+        </view>
+<!--        <view class="cart acea-row row-between-wrapper">-->
+<!--          <view class="title">数量</view>-->
+<!--          <view class="carnum acea-row row-left">-->
+<!--            <view-->
+<!--              class="item reduce"-->
+<!--              :class="attr.productSelect.cart_num <= 1 ? 'on' : ''"-->
+<!--              @click="CartNumDes"-->
+<!--            >-->
+<!--              - -->
+<!--            </view>-->
+<!--            <view class="item num">-->
+<!--              <input-->
+<!--                type="number"-->
+<!--                v-model="attr.productSelect.cart_num"-->
+<!--                data-name="productSelect.cart_num"-->
+<!--                @input="bindCode(attr.productSelect.cart_num)"-->
+<!--              />-->
+<!--            </view>-->
+<!--            <view-->
+<!--              v-if="iSplus"-->
+<!--              class="item plus"-->
+<!--              :class="-->
+<!--                attr.productSelect.cart_num >= attr.productSelect.stock-->
+<!--                  ? 'on'-->
+<!--                  : ''-->
+<!--              "-->
+<!--              @click="CartNumAdd"-->
+<!--            >-->
+<!--              +-->
+<!--            </view>-->
+<!--            <view-->
+<!--              v-else-->
+<!--              class="item plus"-->
+<!--              :class="-->
+<!--                attr.productSelect.cart_num >= attr.productSelect.quota ||-->
+<!--                attr.productSelect.cart_num >= attr.productSelect.stock ||-->
+<!--                attr.productSelect.cart_num >= attr.productSelect.num-->
+<!--                  ? 'on'-->
+<!--                  : ''-->
+<!--              "-->
+<!--              @click="CartNumAdd"-->
+<!--              >+</view-->
+<!--            >-->
+<!--          </view>-->
+<!--        </view>-->
+      </view>
+      <view class="footer-box">
+        <button
+          v-if="attr.productSelect.stock > 0"
+          class="confirm-btn"
+          @click="submit"
+          type="primary"
+        >确定</button>
+        <button
+          v-else
+          class="confirm-btn"
+          @click="submit"
+          type="primary"
+        >已售罄</button>
+      </view>
+    </view>
+  </up-popup>
+</template>
+
+<script setup>
+import { computed,watch } from "vue";
+
+const props = defineProps({
+  attr: {
+    type: Object,
+    default: () => ({}),
+  },
+  showPopup: {
+    type: Boolean,
+    default: false,
+  },
+  limitNum: {
+    type: Number,
+    default: 0,
+  },
+  isShow: {
+    type: Number,
+    default: 0,
+  },
+  iSbnt: {
+    type: Number,
+    default: 0,
+  },
+  iSplus: {
+    type: Number,
+    default: 0,
+  },
+  iScart: {
+    type: Number,
+    default: 0,
+  },
+});
+
+const emit = defineEmits([
+  "goCat",
+  "iptCartNum",
+  "myevent",
+  "ChangeCartNum",
+  "attrVal",
+  "ChangeAttr",
+  "submit",
+  "closePopup",
+  "updatePrice"
+]);
+
+const calcNumPrice = computed(() => {
+  // 检查数据是否存在
+  if (!props.attr?.productSelect) {
+    return "0.00";
+  }
+
+  const { price, cart_num, additionalAmount, weight } =
+    props.attr.productSelect;
+  // 计算总价并保留两位小数
+  const total =
+    (Number(price) * Number(weight) + Number(additionalAmount || 0)) *
+    Number(cart_num);
+  return total.toFixed(2);
+});
+
+// 监听 calcNumPrice 变化并传递给父组件
+watch(calcNumPrice, (newPrice) => {
+  emit("updatePrice", newPrice);
+}, { immediate: true });
+function goCat() {
+  emit("goCat");
+}
+
+function bindCode() {
+  emit("iptCartNum", props.attr.productSelect.cart_num);
+}
+
+function close() {
+  emit("closePopup");
+}
+
+function submit() {
+  emit("submit");
+}
+
+function CartNumDes() {
+  emit("ChangeCartNum", false);
+}
+
+function CartNumAdd() {
+  emit("ChangeCartNum", true);
+}
+
+function tapAttr(indexw, indexn) {
+  emit("attrVal", { indexw, indexn });
+  // 直接修改 props 不推荐,建议父组件传递响应式对象
+  if (props.attr.productAttr && props.attr.productAttr[indexw]) {
+    props.attr.productAttr[indexw].index =
+      props.attr.productAttr[indexw].attrValues[indexn];
+  }
+  const value = getCheckedValue().join(",");
+  emit("ChangeAttr", value);
+}
+
+function getCheckedValue() {
+  const productAttr = props.attr.productAttr || [];
+  const value = [];
+  for (let i = 0; i < productAttr.length; i++) {
+    for (let j = 0; j < productAttr[i].attrValues.length; j++) {
+      if (productAttr[i].index === productAttr[i].attrValues[j]) {
+        value.push(productAttr[i].attrValues[j]);
+      }
+    }
+  }
+  return value;
+}
+</script>
+
+<style scoped lang="scss">
+.product-window {
+  width: 100%;
+  background-color: #fff;
+  border-radius: 16rpx 16rpx 0 0;
+  padding-bottom: 40rpx;
+
+  &.join,
+  &.joinCart {
+    padding-bottom: 30rpx;
+  }
+  &.joinCart {
+    z-index: 999;
+  }
+
+  .textpic {
+    padding: 0 30rpx 0 30rpx;
+    margin-top: 29rpx;
+    position: relative;
+    align-items: center;
+
+    .pictrue {
+      width: 150rpx;
+      height: 150rpx;
+
+      image {
+        width: 100%;
+        height: 100%;
+        border-radius: 10rpx;
+      }
+    }
+
+    .text {
+      //width: 430rpx;
+      width: 70%;
+      font-size: 32rpx;
+      color: #333333;
+      padding-left: 30rpx;
+
+      .money {
+        font-size: 24rpx;
+        //margin-top: 40rpx;
+      }
+      .num {
+        font-size: 36rpx;
+      }
+      .stock {
+        font-size: 24rpx;
+        color: #666;
+        margin-left: 18rpx;
+      }
+    }
+
+    .iconfont {
+      position: absolute;
+      right: 30rpx;
+      top: -5rpx;
+      font-size: 35rpx;
+      color: #8a8a8a;
+    }
+  }
+
+  .rollTop {
+    max-height: 500rpx;
+    overflow: auto;
+    margin-top: 36rpx;
+  }
+
+  .footer-box {
+    padding: 0 30rpx;
+    margin: 30rpx 0 0 0;
+    .confirm-btn {
+      height: 88rpx;
+      line-height: 88rpx;
+      color: #333;
+      border-radius: 16rpx;
+      background-color: $header-color;
+      border-color: $header-color;
+    }
+  }
+
+  .productWinList {
+    .item {
+      & + .item {
+        margin-top: 36rpx;
+      }
+      .title {
+        font-size: 28rpx;
+        color: #333;
+        padding: 0 30rpx;
+      }
+      .listn {
+        padding: 0 30rpx 0 16rpx;
+
+        .itemn {
+          height: 60rpx;
+          border: 1px solid #f2f2f2;
+          font-size: 26rpx;
+          color: #282828;
+          padding: 7rpx 33rpx;
+          border-radius: 10rpx;
+          margin: 20rpx 0 0 14rpx;
+          background-color: #f2f2f2;
+
+          &.on {
+            color: $theme-color;
+            background: rgba(248, 192, 8, 0.10);
+            border-color: $border-color;
+          }
+          &.limit {
+            color: #999;
+            text-decoration: line-through;
+          }
+        }
+      }
+    }
+  }
+
+  .cart {
+    margin-top: 36rpx;
+    padding: 0 30rpx;
+
+    .title {
+      font-size: 30rpx;
+      color: #999;
+    }
+
+
+  }
+  .carnum {
+    height: 54rpx;
+    margin-top: 24rpx;
+    border: 2rpx solid #DCDFE6;
+    display: inline-flex;
+
+    view {
+      width: 84rpx;
+      text-align: center;
+      height: 100%;
+      line-height: 54rpx;
+      color: #282828;
+    }
+
+    .reduce {
+      width: 58rpx;
+      border-right: 0;
+      border-radius: 6rpx 0 0 6rpx;
+      line-height: 48rpx;
+
+      &.on {
+        color: #8C8C8C;
+        font-size: 44rpx;
+        background-color: #DCDFE6;
+      }
+    }
+
+    .plus {
+      width: 58rpx;
+      border-left: 0;
+      border-radius: 0 6rpx 6rpx 0;
+      line-height: 46rpx;
+
+      &.on {
+        border-color: #e3e3e3;
+        color: #8C8C8C;
+        background-color: #DCDFE6;
+      }
+    }
+
+    .num {
+      background: #fff;
+      color: #333;
+      font-size: 24rpx;
+      border-radius: 12rpx;
+      line-height: 29px;
+      height: 54rpx;
+
+      input {
+        display: -webkit-inline-box;
+      }
+    }
+  }
+  .joinBnt {
+    font-size: 30rpx;
+    width: 620rpx;
+    height: 86rpx;
+    border-radius: 50rpx;
+    text-align: center;
+    line-height: 86rpx;
+    color: #fff;
+    margin: 21rpx auto 0 auto;
+
+    &.on {
+      background-color: #bbb;
+      color: #fff;
+    }
+  }
+}
+.popup-title{
+  height: 100rpx;
+  line-height: 100rpx;
+  font-size: 36rpx;
+  color: #333;
+  text-align: center;
+}
+</style>

+ 346 - 0
components/productWindow/index_origin.vue

@@ -0,0 +1,346 @@
+<template>
+	<view>
+		<view class="product-window"
+			:class="(attr.cartAttr === true ? 'on' : '') + ' ' + (iSbnt?'join':'') + ' ' + (iScart?'joinCart':'')">
+			<view class="textpic acea-row row-between-wrapper">
+				<view class="pictrue">
+					<image :src="attr.productSelect.image"></image>
+				</view>
+				<view class="text">
+					<view class="line1">
+						{{ attr.productSelect.storeName }}
+					</view>
+					<view class="money font-color">
+						¥<text class="num">{{ attr.productSelect.storePrice }}</text>
+						<text class="stock" v-if='isShow'>库存: {{ attr.productSelect.stock }}</text>
+						<text class='stock' v-if="limitNum">限量: {{attr.productSelect.quota}}</text>
+					</view>
+				</view>
+				<view class="iconfont icon-guanbi" @click="closeAttr"></view>
+			</view>
+			<view class="rollTop">
+				<view class="productWinList">
+					<view class="item" v-for="(item, indexw) in attr.productAttr" :key="indexw">
+						<view class="title">{{ item.attrName }}</view>
+						<view class="listn acea-row row-middle">
+							<view class="itemn" :class="item.index === itemn ? 'on' : ''"
+								v-for="(itemn, indexn) in item.attrValues" @click="tapAttr(indexw, indexn)"
+								:key="indexn">
+								{{ itemn }}
+							</view>
+						</view>
+					</view>
+				</view>
+				<view class="cart acea-row row-between-wrapper">
+					<view class="title">数量</view>
+					<view class="carnum acea-row row-left">
+						<view class="item reduce" :class="attr.productSelect.cart_num <= 1 ? 'on' : ''"
+							@click="CartNumDes">
+							-
+						</view>
+						<view class='item num'>
+							<input type="number" v-model="attr.productSelect.cart_num"
+								data-name="productSelect.cart_num"
+								@input="bindCode(attr.productSelect.cart_num)"></input>
+						</view>
+						<view
+							v-if="iSplus" 
+							class="item plus"
+							:class="attr.productSelect.cart_num >= attr.productSelect.stock ? 'on' : ''" 
+							@click="CartNumAdd">
+							+
+						</view>
+						<view v-else class='item plus'
+							:class='(attr.productSelect.cart_num >= attr.productSelect.quota) || (attr.productSelect.cart_num >= attr.productSelect.stock) || (attr.productSelect.cart_num >= attr.productSelect.num)? "on":""'
+							@click='CartNumAdd'>+</view>
+					</view>
+				</view>
+			</view>
+			<view class="joinBnt bg-color" v-if="iSbnt && attr.productSelect.stock>0 &&attr.productSelect.quota>0"
+				@click="goCat">我要参团</view>
+			<view class="joinBnt on"
+				v-else-if="(iSbnt && attr.productSelect.quota<=0)||(iSbnt &&attr.productSelect.stock<=0)">已售罄</view>
+			<view class="joinBnt bg-color" v-if="iScart && attr.productSelect.stock" @click="goCat">确定</view>
+			<!-- <view class="joinBnt bg-color" v-if="iSbnt && attr.productSelect.stock && attr.productSelect.quota" @click="goCat">确定</view> -->
+			<view class="joinBnt on" v-else-if="(iScart && !attr.productSelect.stock)">已售罄</view>
+		</view>
+		<view class="mask" @touchmove.prevent :hidden="attr.cartAttr === false" @click="closeAttr"></view>
+	</view>
+</template>
+
+<script setup>
+import { toRefs } from 'vue'
+
+const props = defineProps({
+  attr: {
+    type: Object,
+    default: () => ({})
+  },
+  limitNum: {
+    type: Number,
+    default: 0
+  },
+  isShow: {
+    type: Number,
+    default: 0
+  },
+  iSbnt: {
+    type: Number,
+    default: 0
+  },
+  iSplus: {
+    type: Number,
+    default: 0
+  },
+  iScart: {
+    type: Number,
+    default: 0
+  }
+})
+
+const emit = defineEmits([
+  'goCat',
+  'iptCartNum',
+  'myevent',
+  'ChangeCartNum',
+  'attrVal',
+  'ChangeAttr'
+])
+
+function goCat() {
+  emit('goCat')
+}
+
+function bindCode() {
+  emit('iptCartNum', props.attr.productSelect.cart_num)
+}
+
+function closeAttr() {
+  emit('myevent')
+}
+
+function CartNumDes() {
+  emit('ChangeCartNum', false)
+}
+
+function CartNumAdd() {
+  emit('ChangeCartNum', true)
+}
+
+function tapAttr(indexw, indexn) {
+  emit('attrVal', { indexw, indexn })
+  // 直接修改 props 不推荐,建议父组件传递响应式对象
+  if (props.attr.productAttr && props.attr.productAttr[indexw]) {
+    props.attr.productAttr[indexw].index = props.attr.productAttr[indexw].attrValues[indexn]
+  }
+  const value = getCheckedValue().join(',')
+  emit('ChangeAttr', value)
+}
+
+function getCheckedValue() {
+  const productAttr = props.attr.productAttr || []
+  const value = []
+  for (let i = 0; i < productAttr.length; i++) {
+    for (let j = 0; j < productAttr[i].attrValues.length; j++) {
+      if (productAttr[i].index === productAttr[i].attrValues[j]) {
+        value.push(productAttr[i].attrValues[j])
+      }
+    }
+  }
+  return value
+}
+</script>
+
+<style scoped lang="scss">
+	.product-window {
+		position: fixed;
+		bottom: 0;
+		width: 100%;
+		left: 0;
+		background-color: #fff;
+		z-index: 276;
+		border-radius: 16rpx 16rpx 0 0;
+		padding-bottom: 140rpx;
+		transform: translate3d(0, 100%, 0);
+		transition: all .3s cubic-bezier(.25, .5, .5, .9);
+	}
+  .mask {
+    z-index: 275;
+  }
+
+	.product-window.on {
+		transform: translate3d(0, 0, 0);
+	}
+
+	.product-window.join {
+		padding-bottom: 30rpx;
+	}
+
+	.product-window.joinCart {
+		padding-bottom: 30rpx;
+		z-index: 999;
+	}
+
+	.product-window .textpic {
+		padding: 0 130rpx 0 30rpx;
+		margin-top: 29rpx;
+		position: relative;
+	}
+
+	.product-window .textpic .pictrue {
+		width: 150rpx;
+		height: 150rpx;
+	}
+
+	.product-window .textpic .pictrue image {
+		width: 100%;
+		height: 100%;
+		border-radius: 10rpx;
+	}
+
+	.product-window .textpic .text {
+		width: 410rpx;
+		font-size: 32rpx;
+		color: #333333;
+	}
+
+	.product-window .textpic .text .money {
+		font-size: 24rpx;
+		margin-top: 40rpx;
+	}
+
+	.product-window .textpic .text .money .num {
+		font-size: 36rpx;
+	}
+
+	.product-window .textpic .text .money .stock {
+		color: #999;
+		margin-left: 18rpx;
+	}
+
+	.product-window .textpic .iconfont {
+		position: absolute;
+		right: 30rpx;
+		top: -5rpx;
+		font-size: 35rpx;
+		color: #8a8a8a;
+	}
+
+	.product-window .rollTop {
+		max-height: 500rpx;
+		overflow: auto;
+		margin-top: 36rpx;
+	}
+
+	.product-window .productWinList .item~.item {
+		margin-top: 36rpx;
+	}
+
+	.product-window .productWinList .item .title {
+		font-size: 30rpx;
+		color: #999;
+		padding: 0 30rpx;
+	}
+
+	.product-window .productWinList .item .listn {
+		padding: 0 30rpx 0 16rpx;
+	}
+
+	.product-window .productWinList .item .listn .itemn {
+		border: 1px solid #F2F2F2;
+		font-size: 26rpx;
+		color: #282828;
+		padding: 7rpx 33rpx;
+		border-radius: 40rpx;
+		margin: 20rpx 0 0 14rpx;
+		background-color: #F2F2F2;
+	}
+
+	.product-window .productWinList .item .listn .itemn.on {
+		color: $theme-color;
+		background: rgba(255, 244, 243, 1);
+		border-color: $theme-color;
+	}
+
+	.product-window .productWinList .item .listn .itemn.limit {
+		color: #999;
+		text-decoration: line-through;
+	}
+
+	.product-window .cart {
+		margin-top: 36rpx;
+		padding: 0 30rpx;
+	}
+
+	.product-window .cart .title {
+		font-size: 30rpx;
+		color: #999;
+	}
+
+	.product-window .cart .carnum {
+		height: 54rpx;
+		margin-top: 24rpx;
+	}
+
+	.product-window .cart .carnum view {
+		// border: 1px solid #a4a4a4;
+		width: 84rpx;
+		text-align: center;
+		height: 100%;
+		line-height: 54rpx;
+		color: #282828;
+		font-size: 45rpx;
+	}
+
+	.product-window .cart .carnum .reduce {
+		border-right: 0;
+		border-radius: 6rpx 0 0 6rpx;
+		line-height: 48rpx;
+	}
+
+	.product-window .cart .carnum .reduce.on {
+		// border-color: #e3e3e3;
+		color: #DEDEDE;
+		font-size: 44rpx;
+	}
+
+	.product-window .cart .carnum .plus {
+		border-left: 0;
+		border-radius: 0 6rpx 6rpx 0;
+		line-height: 46rpx;
+	}
+
+	.product-window .cart .carnum .plus.on {
+		border-color: #e3e3e3;
+		color: #dedede;
+	}
+
+	.product-window .cart .carnum .num {
+		background: rgba(242, 242, 242, 1);
+		color: #282828;
+		font-size: 28rpx;
+		border-radius: 12rpx;
+		line-height: 29px;
+		height: 54rpx;
+
+		input {
+			display: -webkit-inline-box;
+		}
+	}
+
+	.product-window .joinBnt {
+		font-size: 30rpx;
+		width: 620rpx;
+		height: 86rpx;
+		border-radius: 50rpx;
+		text-align: center;
+		line-height: 86rpx;
+		color: #fff;
+		margin: 21rpx auto 0 auto;
+	}
+
+	.product-window .joinBnt.on {
+		background-color: #bbb;
+		color: #fff;
+	}
+</style>

+ 146 - 0
components/recommend/index.vue

@@ -0,0 +1,146 @@
+<template>
+  <view class="recommend">
+    <view class="title acea-row row-center-wrapper">
+      <text class="iconfont icon-zhuangshixian"></text>
+      <text class="name">热门推荐</text>
+      <text class="iconfont icon-zhuangshixian lefticon"></text>
+    </view>
+    <view class="recommendList acea-row row-between-wrapper">
+      <view
+        class="item"
+        v-for="(item, index) in hostProduct"
+        :key="index"
+        hover-class="none"
+        @tap="goDetail(item)"
+      >
+        <view class="pictrue">
+          <image :src="item.image"></image>
+        </view>
+        <view class="name line1">{{ item.storeName }}</view>
+        <!-- <view class='money font-color'>¥<text class='num'>{{item.price}}</text></view> -->
+        <view class="bottom-row">
+          <text class="price">工费: {{ item.price }}/克</text>
+          <text class="sales">已售{{ item.sales || 0 }}件</text>
+          <!-- <view class="txt">券</view> -->
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { useAppStore } from "@/stores/app.js";
+import { goShopDetail } from "@/libs/order.js";
+
+const appStore = useAppStore();
+const uid = appStore.uidComputed;
+
+const props = defineProps({
+  hostProduct: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+const goDetail = async (item) => {
+  try {
+    await goShopDetail(item, uid.value);
+    uni.navigateTo({
+      url: `/pages/goods_details/index?id=${item.id}`,
+    });
+  } catch (err) {
+    console.error("Navigation error:", err);
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.recommend {
+  background-color: #fff;
+
+  .title {
+    height: 135rpx;
+    line-height: 135rpx;
+    font-size: 28rpx;
+    color: #282828;
+
+    .name {
+      margin: 0 28rpx;
+    }
+
+    .iconfont {
+      font-size: 170rpx;
+      color: #454545;
+    }
+    .iconfont.lefticon {
+      transform: rotate(180deg);
+    }
+  }
+
+  .recommendList {
+    padding: 0 30rpx;
+    /* #ifdef H5 */
+    padding-bottom: 50rpx;
+    /* #endif */
+
+    .item {
+      width: 335rpx;
+      margin-bottom: 30rpx;
+
+      .pictrue {
+        position: relative;
+        width: 100%;
+        height: 335rpx;
+
+        image {
+          width: 100%;
+          height: 100%;
+          border-radius: 14rpx;
+        }
+      }
+
+      .name {
+        font-size: 28rpx;
+        color: #282828;
+        margin-top: 20rpx;
+      }
+
+      .money {
+        font-size: 20rpx;
+        margin-top: 8rpx;
+        font-weight: 600;
+
+        .num {
+          font-size: 28rpx;
+        }
+      }
+
+      .price {
+        color: $theme-color;
+        font-weight: bold;
+      }
+    }
+  }
+
+  .bottom-row {
+    // color: $theme-color;
+
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 28rpx;
+    margin: 10rpx 0 0;
+
+    .price {
+      padding-bottom: 4rpx;
+      font-weight: 800;
+      color: #f16327 !important;
+    }
+
+    .sales {
+      color: #b4b4b4;
+      font-size: 22rpx;
+    }
+  }
+}
+</style>

+ 4 - 2
config/app.js

@@ -1,6 +1,7 @@
 // let domain = "https://www.shuibeibyg.com/front-api"; // 正式环境IP
-let domain = "https://test.shuibeibyg.com/front-api"; // 测试环境IP
-// let domain = 'http://192.168.100.199:8081' // 晋守桦IP
+// let domain = "https://test.shuibeibyg.com/front-api"; // 测试环境IP
+let domain = 'http://192.168.100.199:8081' // 晋守桦IP
+let share = "https://www.shuibeibyg.com";
 
 // export const H5_BASE_URL = "http://192.168.3.10:5174"; // 本地测试
 export const H5_BASE_URL = "https://www.shuibeibyg.com/web-h5/"; // 正式环境H5地址
@@ -9,6 +10,7 @@ export const H5_BASE_URL = "https://www.shuibeibyg.com/web-h5/"; // 正式环境
 export const HTTP_ADMIN_URL = domain;
 
 export const HTTP_REQUEST_URL = domain;
+export const HTTP_REQUEST_URL_SHARE = share;
 
 // 请求头
 export const HEADER = {

+ 1 - 1
manifest.json

@@ -50,7 +50,7 @@
     "quickapp" : {},
     /* 小程序特有相关 */
     "mp-weixin" : {
-        "appid" : "wxafbc646c10f9bd15",
+        "appid" : "wx50b11af56ee2a3ed",
         "setting" : {
             "urlCheck" : false,
             "es6" : true,

+ 1 - 1
package-lock.json

@@ -1,5 +1,5 @@
 {
-  "name": "wxapp-shuibei",
+  "name": "wxapp_shuibei",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {

+ 231 - 4
pages.json

@@ -11,7 +11,18 @@
 		{
 			"path": "pages/index/index",
 			"style": {
-				"navigationBarTitleText": "首页"
+				"navigationBarTitleText": "首页",
+				"navigationBarBackgroundColor": "#ffe079",
+				"navigationBarTextStyle": "black",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/mall/dapan",
+			"style": {
+				"navigationBarBackgroundColor": "#000000",
+				"navigationBarTextStyle": "white",
+				"navigationBarTitleText": "大盘"
 			}
 		},
 		{
@@ -69,6 +80,44 @@
 		    "navigationStyle": "custom",
 		    "onReachBottomDistance": 100
 		  }
+		},
+		{
+			"path": "pages/goods_search/index",
+			"style": {
+				"navigationBarBackgroundColor": "#FFE079",
+				"navigationBarTitleText": "搜索商品",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/goods_details/index",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/order_addcart/order_addcart",
+			"style": {
+				"navigationBarTitleText": "购物车",
+				"navigationBarBackgroundColor": "#ffe079",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/goods_cate/goods_cate",
+			"style": {
+				"navigationBarTitleText": "商品分类"
+			}
+		},
+		{
+			"path": "pages/personal_info/personal_info",
+			"style": {
+				"navigationBarTitleText": "个人资料",
+				"navigationBarBackgroundColor": "#ffe079",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false
+			}
 		}
 	],
 	"subPackages": [
@@ -98,6 +147,178 @@
 						"navigationBarTitleText": "绑定手机号",
 						"navigationStyle": "custom"
 					}
+				},
+				{
+					"path": "my_merchant/index",
+					"style": {
+						"navigationBarTitleText": "我的商家",
+						"navigationBarBackgroundColor": "#e93323",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "share/index",
+					"style": {
+						"navigationBarTitleText": "分享海报",
+						"navigationBarBackgroundColor": "#e93323",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/index",
+					"style": {
+						"navigationBarTitleText": "钱包",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/storeMetal/gmReport",
+					"style": {
+						"navigationBarTitleText": "邮寄存金检测报告",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/storeMetal/order",
+					"style": {
+						"navigationBarTitleText": "邮寄存金订单",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"enablePullDownRefresh": true,
+						"onReachBottomDistance": 100
+					}
+				},
+				{
+					"path": "vault/recycle/recyle_order",
+					"style": {
+						"navigationBarTitleText": "约价回收订单",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"enablePullDownRefresh": true,
+						"onReachBottomDistance": 100
+					}
+				},
+				{
+					"path": "vault/recycle/report",
+					"style": {
+						"navigationBarTitleText": "约价回收确认报告",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"enablePullDownRefresh": true,
+						"onReachBottomDistance": 100
+					}
+				},
+				{
+					"path": "vault/recycle/order_fill",
+					"style": {
+						"navigationBarTitleText": "订单填写",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "white",
+						"enablePullDownRefresh": true,
+						"onReachBottomDistance": 100
+					}
+				},
+				{
+					"path": "vault/storeMetal/goldBullionStock",
+					"style": {
+						"navigationBarTitleText": "存金",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/save_gold",
+					"style": {
+						"navigationBarTitleText": "存金",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/trade_list",
+					"style": {
+						"navigationBarTitleText": "明细",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "bank_card_manage/index",
+					"style": {
+						"navigationBarTitleText": "卡包管理",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "bank_card_manage/create",
+					"style": {
+						"navigationBarTitleText": "卡包编辑",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "user_address_list/index",
+					"style": {
+						"navigationBarTitleText": "地址管理",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "user_address/index",
+					"style": {
+						"navigationBarTitleText": "添加地址",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "order_confirm/index",
+					"style": {
+						"navigationBarTitleText": "提交订单",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "browsing_history/index",
+					"style": {
+						"navigationBarTitleText": "浏览足迹",
+						"navigationBarBackgroundColor": "#fff",
+						"navigationBarTextStyle": "black"
+					}
 				}
 			]
 		}
@@ -110,18 +331,24 @@
 	},
 	"tabBar": {
 		"color": "#282828",
-		"selectedColor": "#CD9933",
+		"selectedColor": "#F8C008",
 		"borderStyle": "black",
 		"backgroundColor": "#ffffff",
 		"list": [
 			{
 				"pagePath": "pages/index/index",
 				"iconPath": "static/images/tabbar/1-001.png",
-				"selectedIconPath": "static/images/tabbar/1-002.png",
+				"selectedIconPath": "static/images/tabbar/1-003.png",
 				"text": "首页"
 			},
 			{
-				"pagePath": "pages/mall/newIndex",
+				"pagePath": "pages/goods_cate/goods_cate",
+				"iconPath": "static/images/2-001.png",
+				"selectedIconPath": "static/images/2-003.png",
+				"text": "分类"
+			},
+			{
+				"pagePath": "pages/order_addcart/order_addcart",
 				"iconPath": "static/images/tabbar/3-001.png",
 				"selectedIconPath": "static/images/tabbar/3-002.png",
 				"text": "购物车"

+ 175 - 0
pages/goods_cate/goods_cate.vue

@@ -0,0 +1,175 @@
+<template>
+	<view class='productSort'>
+<!--		<view class='header acea-row row-center-wrapper'>-->
+<!--			<view class='acea-row row-between-wrapper input'>-->
+<!--				<text class='iconfont icon-sousuo'></text>-->
+<!--				<input type='text' placeholder='点击搜索商品信息' @confirm="searchSubmitValue" confirm-type='search' name="search"-->
+<!--				 placeholder-class='placeholder'></input>-->
+<!--			</view>-->
+<!--		</view>-->
+<!--		<view class='aside' :style="{bottom: tabbarH + 'px',height: height + 'rpx'}">-->
+<!--			<scroll-view scroll-y="true" scroll-with-animation='true' style="height: 100%;">-->
+<!--				<view class='item acea-row row-center-wrapper' :class='index==navActive?"on":""' v-for="(item,index) in productList"-->
+<!--			 :key="index" @click='tap(index,"b"+index)'><text>{{item.name}}</text></view>-->
+<!--			 </scroll-view>-->
+<!--			-->
+<!--		</view>-->
+<!--		<view class='conter'>-->
+<!--			<scroll-view scroll-y="true" :scroll-into-view="toView" :style='"height:"+height+"rpx;margin-top: 96rpx;"' @scroll="scroll"-->
+<!--			 scroll-with-animation='true'>-->
+<!--				<block v-for="(item,index) in productList" :key="index">-->
+<!--					-->
+<!--					<view class='listw' :id="'b'+index">-->
+<!--						<view class='title acea-row row-center-wrapper'>-->
+<!--							<view class='line'></view>-->
+<!--							<view class='name'>{{item.name}}</view>-->
+<!--							<view class='line'></view>-->
+<!--						</view>-->
+<!--						<view class='list acea-row'>-->
+<!--							<block v-for="(itemn,indexn) in item.child" :key="indexn">-->
+<!--								<navigator hover-class='none' :url='"/pages/goods_list/index?cid="+itemn.id+"&title="+itemn.name' class='item acea-row row-column row-middle'>-->
+<!--							        &lt;!&ndash; <view class='picture' :style="{'background-color':itemn.extra?'none':'#f7f7f7'}">-->
+<!--										<image :src='itemn.extra'></image>-->
+<!--									</view> &ndash;&gt;-->
+<!--									<view class='name line1'>{{itemn.name}}</view>-->
+<!--								</navigator>-->
+<!--							</block>-->
+<!--						</view>-->
+<!--					</view>-->
+<!--				</block>-->
+<!--				<view :style='"height:"+(height-300)+"rpx;"' v-if="number<15"></view>-->
+<!--			</scroll-view>-->
+<!--		</view>-->
+	</view>
+</template>
+
+<script>
+
+</script>
+
+<style scoped lang="scss">
+	.productSort .header {
+		width: 100%;
+		height: 96rpx;
+		background-color: #fff;
+		position: fixed;
+		left: 0;
+		right: 0;
+		top: 0;
+		z-index: 9;
+		border-bottom: 1rpx solid #f5f5f5;
+	}
+
+	.productSort .header .input {
+		width: 700rpx;
+		height: 60rpx;
+		background-color: #f5f5f5;
+		border-radius: 50rpx;
+		box-sizing: border-box;
+		padding: 0 25rpx;
+	}
+
+	.productSort .header .input .iconfont {
+		font-size: 26rpx;
+		color: #555;
+	}
+
+	.productSort .header .input .placeholder {
+		color: #999;
+	}
+
+	.productSort .header .input input {
+		font-size: 26rpx;
+		height: 100%;
+		width: 597rpx;
+	}
+
+	.productSort .aside {
+		position: fixed;
+		width: 180rpx;
+		left: 0;
+		top:0;
+		background-color: #f7f7f7;
+		overflow-y: scroll;
+		overflow-x: hidden;
+
+		height: auto;
+		margin-top: 96rpx;
+	}
+
+	.productSort .aside .item {
+		height: 100rpx;
+		width: 100%;
+		font-size: 26rpx;
+		color: #424242;
+	}
+
+	.productSort .aside .item.on {
+		background-color: #fff;
+		border-left: 4rpx solid #fc4141;
+		width: 100%;
+		text-align: center;
+		color: #fc4141;
+		font-weight: bold;
+	}
+
+	.productSort .conter {
+		margin: 96rpx 0 0 180rpx;
+		padding: 0 14rpx;
+		background-color: #fff;
+	}
+
+	.productSort .conter .listw {
+		padding-top: 20rpx;
+	}
+
+	.productSort .conter .listw .title {
+		height: 90rpx;
+	}
+
+	.productSort .conter .listw .title .line {
+		width: 100rpx;
+		height: 2rpx;
+		background-color: #f0f0f0;
+	}
+
+	.productSort .conter .listw .title .name {
+		font-size: 28rpx;
+		color: #333;
+		margin: 0 30rpx;
+		font-weight: bold;
+	}
+
+	.productSort .conter .list {
+		flex-wrap: wrap;
+	}
+
+	.productSort .conter .list .item {
+		width: 177rpx;
+		margin-top: 26rpx;
+	}
+
+	.productSort .conter .list .item .picture {
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 50%;
+	}
+
+	.productSort .conter .list .item .picture image {
+		width: 100%;
+		height: 100%;
+		border-radius: 50%;
+		div{
+			background-color: #f7f7f7;
+		}
+	}
+
+	.productSort .conter .list .item .name {
+		font-size: 24rpx;
+		color: #333;
+		height: 56rpx;
+		line-height: 56rpx;
+		width: 120rpx;
+		text-align: center;
+	}
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1976 - 0
pages/goods_details/index.vue


+ 214 - 0
pages/goods_search/index.vue

@@ -0,0 +1,214 @@
+<template>
+  <view>
+    <view class='searchGood'>
+      <view class='search acea-row row-between-wrapper'>
+        <view class='input acea-row row-between-wrapper'>
+          <text class='iconfont icon-sousuo'></text>
+          <input type='text' confirm-type="search" :value='searchValue' :focus="focus" placeholder='点击搜索商品' placeholder-class='placeholder'
+            @input="setValue" @confirm="searchBut" />
+        </view>
+        <view class='bnt' @tap='searchBut'>搜索</view>
+      </view>
+      <view class='title'>热门搜索</view>
+      <view class='list acea-row'>
+        <block v-for="(item, index) in hotSearchList" :key="index">
+          <view class='item' @tap='setHotSearchValue(item.title)'>{{ item.title }}</view>
+        </block>
+      </view>
+      <view class='line'></view>
+      <goodList :bastList="bastList" v-if="bastList.length > 0"></goodList>
+      <view class='loadingicon acea-row row-center-wrapper' v-if="bastList.length > 0">
+        <text class='loading iconfont icon-jiazai' :hidden='loading == false'></text>{{ loadTitle }}
+      </view>
+    </view>
+    <view class='noCommodity'>
+      <view class='pictrue' v-if="bastList.length == 0 && isbastList">
+        <image src='../../static/images/noSearch.png'></image>
+      </view>
+      <recommend :hostProduct='hostProduct' v-if="bastList.length == 0"></recommend>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onShow, onReachBottom } from '@dcloudio/uni-app'
+import { getSearchKeyword, getProductslist, getProductHot } from '@/api/store.js'
+import goodList from '@/components/goodList'
+import recommend from '@/components/recommend'
+
+// 响应式数据
+const hostProduct = ref([])
+const searchValue = ref('')
+const focus = ref(true)
+const bastList = ref([])
+const hotSearchList = ref([])
+const limit = ref(8)
+const page = ref(1)
+const loading = ref(false)
+const loadend = ref(false)
+const loadTitle = ref('加载更多')
+const hotPage = ref(1)
+const isScroll = ref(true)
+const isbastList = ref(false)
+
+// 获取热搜
+function getRoutineHotSearch() {
+  getSearchKeyword().then(res => {
+    hotSearchList.value = res.data
+  })
+}
+
+// 获取商品列表
+function getProductList() {
+  if (loadend.value || loading.value) return
+  loading.value = true
+  loadTitle.value = ''
+  getProductslist({
+    keyword: searchValue.value,
+    page: page.value,
+    limit: limit.value
+  }).then(res => {
+    const list = res.data.list
+    const isLoadend = list.length < limit.value
+    // 合并数组
+    bastList.value = (bastList.value || []).concat(list)
+    loading.value = false
+    loadend.value = isLoadend
+    loadTitle.value = isLoadend ? "😕人家是有底线的~~" : "加载更多"
+    page.value += 1
+    isbastList.value = true
+  }).catch(() => {
+    loading.value = false
+    loadTitle.value = '加载更多'
+  })
+}
+
+// 获取热门商品
+function getHostProduct() {
+  if (!isScroll.value) return
+  getProductHot(hotPage.value, limit.value).then(res => {
+    isScroll.value = res.data.list.length >= limit.value
+    hostProduct.value = hostProduct.value.concat(res.data.list)
+    hotPage.value += 1
+  })
+}
+
+// 设置热搜值
+function setHotSearchValue(val) {
+  searchValue.value = val
+  page.value = 1
+  loadend.value = false
+  bastList.value = []
+  getProductList()
+}
+
+// 输入框赋值
+function setValue(event) {
+  searchValue.value = event.detail.value
+}
+
+// 搜索按钮
+function searchBut() {
+  focus.value = false
+  if (searchValue.value.length > 0) {
+    page.value = 1
+    loadend.value = false
+    bastList.value = []
+    uni.showLoading({ title: '正在搜索中' })
+    getProductList()
+    uni.hideLoading()
+  } else {
+    // 这里假设 $util.Tips 已全局挂载
+    uni.$u.toast('请输入要搜索的商品')
+  }
+}
+
+// 生命周期
+onShow(() => {
+  getRoutineHotSearch()
+  getHostProduct()
+})
+
+onReachBottom(() => {
+  if (bastList.value.length > 0) {
+    getProductList()
+  } else {
+    getHostProduct()
+  }
+})
+</script>
+
+<style lang="scss">
+page {
+  margin-top: var(--status-bar-height);
+  background-color: #fff !important;
+}
+
+.searchGood .search {
+  padding-left: 30rpx;
+  background-color: #fff !important;
+}
+
+.searchGood .search {
+  padding-top: 20rpx;
+}
+
+.searchGood .search .input {
+  width: 598rpx;
+  background-color: #f7f7f7;
+  border-radius: 33rpx;
+  padding: 0 35rpx;
+  box-sizing: border-box;
+  height: 66rpx;
+}
+
+.searchGood .search .input input {
+  width: 472rpx;
+  font-size: 26rpx;
+}
+
+.searchGood .search .input .placeholder {
+  color: #bbb;
+}
+
+.searchGood .search .input .iconfont {
+  color: #000;
+  font-size: 35rpx;
+}
+
+.searchGood .search .bnt {
+  width: 120rpx;
+  text-align: center;
+  height: 66rpx;
+  line-height: 66rpx;
+  font-size: 30rpx;
+  color: #282828;
+}
+
+.searchGood .title {
+  font-size: 28rpx;
+  color: #999;
+  margin: 50rpx 30rpx 25rpx 30rpx;
+}
+
+.searchGood .list {
+  padding-left: 10rpx;
+}
+
+.searchGood .list .item {
+  font-size: 26rpx;
+  color: #454545;
+  padding: 0 21rpx;
+  height: 60rpx;
+  border-radius: 30rpx;
+  line-height: 60rpx;
+  border: 1rpx solid #aaa;
+  margin: 0 0 20rpx 20rpx;
+}
+
+.searchGood .line {
+  border-bottom: 1rpx solid #eee;
+  margin: 20rpx 30rpx 0 30rpx;
+}
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 532 - 590
pages/index/index.vue


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1281 - 0
pages/order_addcart/order_addcart.vue


+ 367 - 0
pages/personal_info/personal_info.vue

@@ -0,0 +1,367 @@
+<template>
+  <view class="personal-info">
+    <!-- 上传 -->
+    <view class="upload-box border-bottom">
+      <up-upload
+        @afterRead="
+          async (e) => {
+            await afterRead(e);
+            getImage();
+          }
+        "
+        name="1"
+        :maxCount="1"
+        :previewImage="false"
+        :deletable="false"
+        :multiple="false"
+      >
+        <!-- <template #default> -->
+        <view class="upload-block">
+          <up-avatar
+            size="160rpx"
+            shape="circle"
+            :src="userInfo.avatar"
+            mode="aspectFill"
+          ></up-avatar>
+        </view>
+        <!-- </template> -->
+      </up-upload>
+    </view>
+		<view class="flex-center-between border-bottom personal-info-item">
+			<text class="label_width">昵称</text>
+			<up-input
+          class="title-input"
+          placeholder="请输入"
+          type="text"
+          confirmType="完成"
+          maxlength="50"
+          :adjustPosition="false"
+          border="none"
+          :showWordLimit="true"
+          v-model="userInfo.nickname"
+					inputAlign="right"
+          @blur="changeName"
+        ></up-input>
+		</view>
+		<view class="flex-center-between border-bottom personal-info-item">
+			<text class="label_width">手机号</text>
+			<text class="gray">{{telEncrypt(userInfo.phone)}}</text>
+		</view>
+		<view class="flex-center-between border-bottom personal-info-item" @click="pickerSexShow = true">
+			<text class="label_width">性别</text>
+			<view class="flex-center-between">
+				<text>{{sexText}}</text>
+				<uni-icons type="right" size="21"  color="#888"></uni-icons>
+			</view>
+		</view>
+		<view class="flex-center-between border-bottom personal-info-item">
+			<text class="label_width">地址</text>
+      <picker mode="multiSelector" @change="bindRegionChange" @columnchange="bindMultiPickerColumnChange"
+        :value="valueRegion" :range="multiArray">
+        <view class='flex-center-between'>
+          <view class="picker line1">{{ region[0] }},{{ region[1] }},{{ region[2] }}</view>
+					<uni-icons type="right" size="21"  color="#888"></uni-icons>
+        </view>
+      </picker>
+      <!-- <up-input
+          class="title-input"
+          placeholder="请输入"
+          type="text"
+          confirmType="完成"
+          maxlength="50"
+          :adjustPosition="false"
+          border="none"
+          :showWordLimit="true"
+          v-model="userInfo.addres"
+					inputAlign="right"
+          @blur="change"
+        ></up-input> -->
+    </view>
+    <view class="flex-center-between border-bottom personal-info-item">
+      <text class="label_width">年龄</text>
+      <up-input
+        class="title-input"
+        placeholder="请输入"
+        type="text"
+        confirmType="完成"
+        maxlength="50"
+        :adjustPosition="false"
+        border="none"
+        :showWordLimit="true"
+        v-model="userInfo.age"
+        inputAlign="right"
+        @blur="change"
+      ></up-input>
+    </view>
+    <view class="flex-center-between border-bottom personal-info-item">
+      <text class="label_width">个人简介</text>
+      <up-input
+        class="title-input"
+        placeholder="请输入"
+        type="text"
+        confirmType="完成"
+        maxlength="50"
+        :adjustPosition="false"
+        border="none"
+        :showWordLimit="true"
+        v-model="userInfo.mark"
+        inputAlign="right"
+        @blur="change"
+      ></up-input>
+    </view>
+    <view
+      class="flex-center-between border-bottom personal-info-item"
+      @tap.stop="setPayword"
+    >
+      <text class="label_width">支付密码</text>
+      <!-- <up-input
+        class="title-input"
+        placeholder="请输入"
+        type="text"
+        confirmType="完成"
+        maxlength="6"
+        :adjustPosition="false"
+        border="none"
+        :showWordLimit="true"
+        v-model="userInfo.payPassword"
+        inputAlign="right"
+        :password="true"
+        :disabled="true"
+        :passwordVisibilityToggle="false"
+        disabledColor=""
+      ></up-input> -->
+      <text>* * * * * *</text>
+    </view>
+    <up-picker
+      @confirm="changeSex"
+      @cancel="pickerSexShow = false"
+      :show="pickerSexShow"
+      :columns="[
+        [
+          { label: '男', id: 1 },
+          { label: '女', id: 2 },
+          { label: '保密', id: 3 },
+        ],
+      ]"
+      keyName="label"
+      valueName="id"
+    ></up-picker>
+    <button
+      @click="submitFn"
+      class="submit-btn"
+    >提交</button>
+    <!-- 页面内 容结束 -->
+    <PayPop ref="paypopRef" :pwdlength="6" @pwd_e="handlerPwd"></PayPop>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, nextTick } from "vue";
+import PayPop from "../../components/pay-pop/pay-pop.vue";
+import { useImageUpload } from "@/hooks/useImageUpload";
+import { useAppStore } from "@/stores/app";
+import { onLoad } from "@dcloudio/uni-app";
+import { useToast } from "@/hooks/useToast";
+import { getUserInfo, userEdit, registerpayPasswordAPI } from "@/api/user";
+import { getCity } from "@/api/api.js";
+import Cache from "@/utils/cache";
+import { telEncrypt } from "@/utils/util.js";
+const paypopRef = ref(null);
+const { Toast } = useToast();
+const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
+  pid: 5,
+  model: "book",
+});
+const appStore = useAppStore();
+const userInfo = ref({
+  avatar: "",
+  nickname: "",
+  addres: "",
+  mark: "",
+  sex: "",
+  age: "",
+});
+const pickerSexShow = ref(false);
+const sexText = computed(() => {
+  return userInfo.value.sex == 1
+    ? "男"
+    : userInfo.value.sex == 2
+    ? "女"
+    : userInfo.value.sex == 3
+    ? "保密"
+    : "未知";
+});
+
+//省市区选择
+const district = ref([]);
+const multiArray = ref([[], [], []]);
+const multiIndex = ref([0, 0, 0]);
+const region = ref(["省", "市", "区"]);
+const valueRegion = ref([0, 0, 0]);
+
+onLoad(() => {
+  console.log("personal_info 页面 onLoad");
+  fetchUserInfo();
+  getCityList();
+});
+
+const changeSex = (e) => {
+  console.log("changeSex", e.value[0]);
+  userInfo.value.sex = e.value[0].id;
+  pickerSexShow.value = false;
+  // change();
+};
+// 设置支付密码
+const setPayword = () => {
+  console.log(111111);
+
+  console.log(paypopRef.value);
+  nextTick(() => {
+    paypopRef.value.Open();
+  });
+};
+const handlerPwd = async (e) => {
+  const res = await registerpayPasswordAPI({
+    account: appStore.userInfo.phone,
+    payPassword: e,
+  });
+  uni.showToast({
+    title: "修改成功",
+    duration: 2000,
+  });
+  change();
+};
+// 获取用户头像
+async function getImage() {
+  console.log("getImage", imageList.value[0]);
+  if (imageList.value.length > 0) {
+    if (imageList.value[0].status == "success") {
+      userInfo.value.avatar = imageList.value[0].info.url;
+      // change();
+    } else {
+      Toast({ title: "上传失败" });
+    }
+  }
+  imageList.value = [];
+}
+function submitFn() {
+  change();
+}
+function changeName(e) {
+  // console.log(e)
+  // console.log(userInfo.value)
+}
+const change = async () => {
+  await userEdit(userInfo.value);
+  Toast({ title: "修改成功", endtime: 1500 });
+  appStore.USERINFO();
+  setTimeout(() => {
+    uni.navigateBack();
+  }, 1600);
+  // const { data } = await getUserInfo(appStore.uid);
+  // appStore.UPDATE_userPanelInfo(data);
+};
+// 获取用户信息
+async function fetchUserInfo() {
+  try {
+    const { data } = await getUserInfo(appStore.uid);
+    userInfo.value = data;
+    userInfo.value.addres = userInfo.value.addres || "";
+    const list = userInfo.value.addres.split("-");
+    if (list.length > 0) {
+      region.value = [list[0], list[1], list[2]];
+    }
+  } catch (error) {
+    console.error("otherUserinfo", error);
+    Toast({ title: "获取用户信息失败" });
+  }
+}
+
+function bindRegionChange(e) {
+  const mi = multiIndex.value;
+  const province = district.value[mi[0]] || { child: [] };
+  const ma = multiArray.value;
+  const value = e.detail.value;
+  region.value = [ma[0][value[0]], ma[1][value[1]], ma[2][value[2]]];
+  userInfo.value.addres = region.value.join("-");
+  valueRegion.value = [0, 0, 0];
+  //   change();
+  initialize();
+}
+
+function getCityList() {
+  getCity().then((res) => {
+    district.value = res.data;
+    let oneDay = 24 * 3600 * 1000;
+    Cache.setItem({ name: "cityList", value: res.data, expires: oneDay * 7 }); //设置七天过期时间
+    initialize();
+  });
+}
+function initialize() {
+  if (district.value.length) {
+    let province = [],
+      city = [],
+      area = [];
+    let cityChildren = district.value[0].child || [];
+    let areaChildren = cityChildren.length ? cityChildren[0].child || [] : [];
+    district.value.forEach((item) => province.push(item.name));
+    cityChildren.forEach((item) => city.push(item.name));
+    areaChildren.forEach((item) => area.push(item.name));
+    multiArray.value = [province, city, area];
+  }
+}
+function bindMultiPickerColumnChange(e) {
+  const column = e.detail.column;
+  const value = e.detail.value;
+  const ma = multiArray.value;
+  const mi = multiIndex.value;
+  mi[column] = value;
+  switch (column) {
+    case 0:
+      const currentCity = district.value[value] || { child: [] };
+      const areaList = currentCity.child[0] || { child: [] };
+      ma[1] = currentCity.child.map((item) => item.name);
+      ma[2] = areaList.child.map((item) => item.name);
+      break;
+    case 1:
+      const cityList = district.value[mi[0]].child[mi[1]].child || [];
+      ma[2] = cityList.map((item) => item.name);
+      break;
+    case 2:
+      break;
+  }
+  multiArray.value = [...ma];
+  multiIndex.value = [...mi];
+}
+</script>
+
+<style lang="scss">
+.personal-info {
+  padding: 0 30rpx;
+
+  .upload-block {
+    display: flex;
+    justify-content: center;
+    width: 90vw;
+    box-sizing: border-box;
+    padding: 80rpx 0;
+  }
+  .personal-info-item {
+    padding: 30rpx 0;
+    .label_width {
+      width: 200rpx;
+      display: inline-block;
+    }
+  }
+  .submit-btn {
+    font-size: 28rpx;
+    color: #fff;
+    height: 80rpx;
+    line-height: 80rpx;
+    border-radius: 50rpx;
+    background-color: $header-color;
+    border: none;
+    margin-top: 20px;
+  }
+}
+</style>

+ 19 - 3
pages/user/index.vue

@@ -308,7 +308,7 @@ const group1 = ref([
   {
     icon: `https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/user_icon/wallet-solid%403x.png`,
     text: "卡包管理",
-    url: "/pages/users/card_page/index",
+    url: "/pages/users/bank_card_manage/index",
     is_show: true,
     width: 42,
     height: 70,
@@ -347,6 +347,22 @@ const group1 = ref([
     height: 70,
     url: "callList",
   },
+  {
+    icon: `https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/user_icon/%E5%9B%BE%E5%B1%82_x0020_1%403x.png`,
+    text: "我的商家",
+    is_show: false,
+    width: 37,
+    height: 70,
+    url: "/pages/users/my_merchant/index",
+  },
+  {
+    icon: `https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/user_icon/%E5%9B%BE%E5%B1%82_x0020_1%403x.png`,
+    text: "我的钱包",
+    is_show: false,
+    width: 37,
+    height: 70,
+    url: "/pages/users/vault/index",
+  },
 ]);
 
 onLoad(async () => {
@@ -423,7 +439,7 @@ const nativeToOrder = (item) => {
       },
     });
   }
-
+  console.log(item)
   uni.navigateTo({ url: item.url });
 };
 
@@ -797,7 +813,7 @@ const isTjg = computed(() => {
 .block {
   padding: 0px 20px;
   margin-bottom: 10px;
-  height: calc(100vh - 750rpx);
+  //height: calc(100vh - 750rpx);
   display: flex;
   justify-content: center;
   align-items: center;

+ 414 - 0
pages/users/bank_card_manage/create.vue

@@ -0,0 +1,414 @@
+<template>
+  <view class="add-card-container">
+
+    <!-- 主要内容 -->
+    <view class="main-content">
+      <!-- 卡片类型选择 -->
+      <view class="card-type-section">
+        <view class="section-title">卡片类型</view>
+        <u-radio-group
+          v-model="selectedType"
+          placement="row"
+          @change="groupChange"
+        >
+          <u-radio
+            name="bank"
+            label="银行卡"
+            :disabled="bankDisable"
+            activeColor="#4caf50"
+          ></u-radio>
+          <u-radio
+            name="alipay"
+            label="支付宝"
+            :disabled="alipayDisable"
+            activeColor="#4caf50"
+          ></u-radio>
+        </u-radio-group>
+      </view>
+
+      <!-- 表单区域 -->
+      <view class="form-section">
+        <u-form
+          :model="bankForm"
+          ref="formRef"
+          :rules="rules"
+          labelWidth="140"
+          labelPosition="left"
+        >
+          <!-- 银行卡表单 -->
+          <template v-if="selectedType === 'bank'">
+            <u-form-item label="银行名" prop="bankName" :borderBottom="true">
+              <u-input
+                v-model="bankForm.bankName"
+                placeholder="银行名"
+                border="none"
+                inputAlign="right"
+              />
+            </u-form-item>
+
+            <u-form-item label="卡号" prop="cardNumber" :borderBottom="true">
+              <u-input
+                v-model="bankForm.cardNumber"
+                placeholder="卡号"
+                border="none"
+                inputAlign="right"
+                type="number"
+              />
+            </u-form-item>
+
+            <u-form-item label="姓名" prop="holderName" :borderBottom="true">
+              <u-input
+                v-model="bankForm.holderName"
+                placeholder="姓名"
+                border="none"
+                inputAlign="right"
+              />
+            </u-form-item>
+          </template>
+
+          <!-- 支付宝表单 -->
+          <template v-else-if="selectedType === 'alipay'">
+            <u-form-item label="支付宝账号" prop="alipayAccount" :borderBottom="true">
+              <u-input
+                v-model="bankForm.alipayAccount"
+                placeholder="支付宝账号"
+                border="none"
+                inputAlign="right"
+              />
+            </u-form-item>
+
+            <u-form-item label="姓名" prop="holderName" :borderBottom="true">
+              <u-input
+                v-model="bankForm.holderName"
+                placeholder="姓名"
+                border="none"
+                inputAlign="right"
+              />
+            </u-form-item>
+          </template>
+        </u-form>
+      </view>
+
+      <!-- 设为默认 -->
+      <view class="default-section">
+        <view class="default-item">
+          <text class="default-text">设为默认</text>
+          <u-switch
+            v-model="bankForm.isDefault"
+            :activeColor="'#e9c279'"
+            size="20"
+          />
+        </view>
+      </view>
+
+      <!-- 提交按钮 -->
+      <view class="submit-section">
+        <u-button
+          type="primary"
+          :customStyle="{
+            background: 'linear-gradient(135deg, #e9c279 0%, #d4a853 100%)',
+            border: 'none',
+            borderRadius: '16rpx',
+            height: '88rpx',
+            fontSize: '32rpx',
+          }"
+          @click="submitForm"
+        >
+          {{ cardInfo?.id ? '保存' : '提交' }}
+        </u-button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { saveAccount } from "@/api/user";
+
+// 响应式数据
+const selectedType = ref("bank");
+const formRef = ref();
+const bankDisable = ref(false)
+const alipayDisable = ref(false)
+
+const bankForm = reactive({
+  bankName: "",
+  cardNumber: "",
+  alipayAccount: "",
+  holderName: "",
+  isDefault: false,
+});
+
+const cardInfo = ref(null)
+
+onLoad((options) => {
+  if (options.id) {
+    cardInfo.value = options;
+    if (options.accountType === '1') {
+      selectedType.value = 'bank'
+      bankForm.bankName = options.bankName
+      bankForm.cardNumber = options.accountNumber
+      bankForm.holderName = options.name
+      bankForm.isDefault = options.isDefault === '1'
+      alipayDisable.value = true
+    } else if (options.accountType === '2'){
+      selectedType.value = 'alipay'
+      bankDisable.value = true
+      bankForm.alipayAccount = options.accountNumber
+      bankForm.holderName = options.name
+    }
+    console.log('cardInfo.value', cardInfo.value)
+  }
+})
+
+// 表单验证规则
+const rules = computed(() => {
+  const baseRules = {
+    holderName: [
+      {
+        required: true,
+        message: "请输入姓名",
+        trigger: "blur",
+      },
+    ],
+  };
+
+  if (selectedType.value === "bank") {
+    return {
+      ...baseRules,
+      bankName: [
+        {
+          required: true,
+          message: "请输入银行名称",
+          trigger: "blur",
+        },
+      ],
+      cardNumber: [
+        {
+          required: true,
+          message: "请输入银行卡号",
+          trigger: "blur",
+        },
+        {
+          pattern: /^\d{16,19}$/,
+          message: "请输入正确的银行卡号",
+          trigger: "blur",
+        },
+      ],
+    };
+  } else {
+    return {
+      ...baseRules,
+      alipayAccount: [
+        {
+          required: true,
+          message: "请输入支付宝账号",
+          trigger: "blur",
+        },
+        {
+          pattern: /^1[3-9]\d{9}$|^[\w\.-]+@[\w\.-]+\.\w+$/,
+          message: "请输入正确的手机号或邮箱",
+          trigger: "blur",
+        },
+      ],
+    };
+  }
+});
+
+// 方法
+const goBack = () => {
+  uni.navigateBack();
+};
+
+const groupChange = (val) => {
+  // 切换类型时清空表单
+  bankForm.bankName = "";
+  bankForm.cardNumber = "";
+  bankForm.alipayAccount = "";
+  bankForm.holderName = "";
+};
+
+const submitForm = async () => {
+  try {
+    const valid = await formRef.value.validate();
+    if (valid) {
+      // 构建提交数据
+      const submitData = {
+        type: selectedType.value,
+        holderName: bankForm.holderName,
+        isDefault: bankForm.isDefault,
+      };
+
+      if (selectedType.value === "bank") {
+        submitData.bankName = bankForm.bankName;
+        submitData.cardNumber = bankForm.cardNumber;
+      } else {
+        submitData.alipayAccount = bankForm.alipayAccount;
+      }
+
+      console.log("提交数据:", submitData);
+
+      const accountType = selectedType.value === 'bank' ? 1 : 2
+      const accountNumber = selectedType.value === 'bank' ? bankForm.cardNumber : bankForm.alipayAccount
+      const params = {
+        id: cardInfo.value?.id,
+        bankName: bankForm.bankName,
+        accountType,
+        accountName: bankForm.holderName,
+        accountNumber,
+        isDefault: bankForm.isDefault ? 1 : 0
+      }
+      // 参数中的id不存在 需要把prams.id删掉
+      if (!cardInfo.value?.id) {
+        delete params.id
+      }
+      // 如果是支付宝 需要把银行名称字段删除
+      if (selectedType.value === 'alipay') {
+        delete params.bankName
+      }
+      // let params = cardInfo.value?.id ? editParams : addParams
+      await saveAccount(params)
+      const title = cardInfo.value?.id ? '保存成功' : '添加成功'
+      uni.showToast({
+        title,
+        icon: "success",
+      });
+      // 延迟返回上一页
+      setTimeout(() => {
+        uni.navigateBack();
+      }, 1000);
+    }
+  } catch (error) {
+    console.log("表单验证失败:", error);
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.add-card-container {
+  min-height: 100vh;
+  background: #f8f9fa;
+
+  .main-content {
+    padding: 0 30rpx;
+
+    .card-type-section {
+      background: #fff;
+      border-radius: 16rpx;
+      padding: 30rpx;
+      margin: 30rpx 0;
+
+      .section-title {
+        color: #333;
+        font-size: 32rpx;
+        font-weight: 600;
+        margin-bottom: 30rpx;
+      }
+
+      .type-options {
+        display: flex;
+        gap: 40rpx;
+
+        .type-option {
+          display: flex;
+          align-items: center;
+          gap: 16rpx;
+          padding: 20rpx 0;
+
+          .option-check {
+            width: 40rpx;
+            height: 40rpx;
+            border: 2rpx solid #ddd;
+            border-radius: 50%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.3s ease;
+
+            .check-icon {
+              color: #fff;
+              font-size: 24rpx;
+              font-weight: 600;
+            }
+          }
+
+          .option-text {
+            color: #333;
+            font-size: 28rpx;
+            font-weight: 500;
+          }
+
+          &.active {
+            .option-check {
+              background: #4caf50;
+              border-color: #4caf50;
+            }
+
+            .option-text {
+              color: #4caf50;
+            }
+          }
+        }
+      }
+    }
+
+    .form-section {
+      background: #fff;
+      border-radius: 16rpx;
+      padding: 20rpx 30rpx;
+      margin-bottom: 30rpx;
+
+      :deep(.u-form-item) {
+        padding: 30rpx 0;
+
+        .u-form-item__label {
+          color: #333;
+          font-size: 28rpx;
+          font-weight: 500;
+        }
+
+        .u-input {
+          color: #333;
+          font-size: 28rpx;
+
+          input {
+            color: #333;
+
+            &::placeholder {
+              color: #999;
+            }
+          }
+        }
+      }
+    }
+
+    .default-section {
+      background: #fff;
+      border-radius: 16rpx;
+      padding: 30rpx;
+      margin-bottom: 60rpx;
+
+      .default-item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        .default-text {
+          color: #333;
+          font-size: 28rpx;
+          font-weight: 500;
+        }
+      }
+    }
+
+    .submit-section {
+      padding: 0 0 60rpx;
+
+      :deep(.u-button) {
+        box-shadow: 0 6rpx 20rpx rgba(233, 194, 121, 0.3);
+      }
+    }
+  }
+}
+</style>

+ 444 - 0
pages/users/bank_card_manage/index.vue

@@ -0,0 +1,444 @@
+<template>
+  <view class="bank-manage-container">
+    <!-- 银行卡列表 -->
+    <view class="card-list">
+      <view
+        v-for="(card, index) in bankCards"
+        :key="index"
+        :class="['bank-card', selectedCard?.id === card.id ? 'active' : '']"
+        @click="selectCard(card)"
+      >
+        <view class="card-content">
+          <view class="bank-info">
+            <view class="bank-icon">
+              <!-- <uni-icons
+                size="30"
+                customPrefix="iconfont"
+                class="icon-yinhangqia"
+              ></uni-icons> -->
+
+              <uni-icons
+                size="35"
+                :color="card.accountType === 1 ? '#F2CC51' : '#019FE8'"
+                customPrefix="iconfont"
+                :class="[
+                  card.accountType === 1 ? 'icon-qianbao' : 'icon-zhifubao',
+                ]"
+              ></uni-icons>
+            </view>
+            <view class="bank-details">
+              <view class="bank-name">{{ card.accountType === 1 ? card.bankName : card.accountName }}</view>
+              <view class="card-number">{{
+                formatCardNumber(card.accountNumber)
+              }}</view>
+            </view>
+          </view>
+          <view class="right-box">
+            <view class="icon-box" @click.stop="editCard(card)">
+              <uni-icons size="25" color="#fff" type="compose"></uni-icons>
+            </view>
+            <view class="icon-box delete" @click.stop="deleteCard(card)">
+              <uni-icons size="25" color="#fff" type="trash-filled"></uni-icons>
+            </view>
+          </view>
+        </view>
+        <!-- <view class="card-selected" v-if="selectedCard?.id === card.id">
+          <text class="check-icon">✓</text>
+        </view> -->
+      </view>
+    </view>
+
+    <!-- 新增卡片按钮 -->
+    <view class="add-card-section">
+      <button class="add-card-btn" @click="addNewCard">
+        <text class="add-icon">+</text>
+        <text class="add-text">新增卡片</text>
+      </button>
+    </view>
+
+    <up-modal
+      :show="showDeleteModal"
+      title="确认删除"
+      content="确定要删除该银行卡吗?"
+      :showCancelButton="true"
+      @confirm="confirmDeleteCard"
+      @cancel="showDeleteModal = false"
+      @close="showDeleteModal = false"
+    />
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+import { getAccountList, deleteAccount, saveAccount } from "@/api/user";
+
+const bankCards = ref([]);
+
+const selectedCard = ref();
+const isEditing = ref(false);
+const currentEditIndex = ref(-1);
+
+const cardForm = reactive({
+  bankName: "",
+  cardNumber: "",
+  holderName: "",
+});
+
+const showDeleteModal = ref(false);
+const cardToDelete = ref(null);
+
+onShow(() => {
+  fetchAccountList();
+});
+
+
+async function fetchAccountList() {
+  try {
+    const { data } = await getAccountList();
+    bankCards.value = data;
+    selectedCard.value = data.find(v => v.isDefault)
+    console.log('selectedCard', selectedCard.value)
+  } catch (error) {
+    console.error("getAccountList", error);
+  }
+}
+
+const formatCardNumber = (cardNumber) => {
+  // 显示卡号,只显示后4位,其他用*代替
+  if (cardNumber.length <= 4) return cardNumber;
+  const lastFour = cardNumber.slice(-4);
+  return `****${lastFour}`;
+};
+
+const selectCard = async (card) => {
+  const params = {
+    id: card.id,
+    isDefault: 1
+  }
+  await saveAccount(params)
+  selectedCard.value = card;
+  // 可以在这里添加选中卡片的逻辑
+  uni.showToast({
+    title: "已设为默认",
+    icon: "success",
+  });
+};
+
+const editCard = (card) => {
+  const queryStr = `id=${card.id}&bankName=${card.bankName}&accountNumber=${card.accountNumber}&accountType=${card.accountType}&name=${card.accountName}&isDefault=${card.isDefault}`;
+  uni.navigateTo({
+    url: `/pages/users/bank_card_manage/create?${queryStr}`,
+  });
+  isEditing.value = true;
+  currentEditIndex.value = bankCards.value.findIndex((c) => c.id === card.id);
+  cardForm.bankName = card.bankName;
+  cardForm.cardNumber = card.cardNumber;
+  cardForm.holderName = card.holderName;
+};
+
+// 修改 deleteCard 方法为弹窗确认
+function deleteCard(card) {
+  cardToDelete.value = card;
+  showDeleteModal.value = true;
+}
+
+// 确认删除
+async function confirmDeleteCard() {
+  if (!cardToDelete.value) return;
+  try {
+    await deleteAccount(cardToDelete.value.id);
+    // 删除成功后刷新列表
+    fetchAccountList();
+    uni.showToast({ title: "删除成功", icon: "success" });
+  } catch (e) {
+    uni.showToast({ title: "删除失败", icon: "none" });
+  } finally {
+    showDeleteModal.value = false;
+    cardToDelete.value = null;
+  }
+}
+
+const addNewCard = () => {
+  uni.navigateTo({ url: "/pages/users/bank_card_manage/create" });
+  isEditing.value = false;
+  cardForm.bankName = "";
+  cardForm.cardNumber = "";
+  cardForm.holderName = "";
+};
+</script>
+
+<style lang="scss" scoped>
+.bank-manage-container {
+  min-height: 100vh;
+  background: #fff;
+
+  .card-list {
+    padding: 30rpx 30rpx 0;
+
+    .bank-card {
+      background: linear-gradient(135deg, #f6b742 0%, #e74c3c 100%);
+      border-radius: 24rpx;
+      padding: 40rpx 30rpx;
+      margin-bottom: 30rpx;
+      position: relative;
+      overflow: hidden;
+      transition: transform 0.2s ease;
+      border: 3rpx solid #fff;
+      &.active {
+        border: 3rpx solid #00c896;
+      }
+
+      &::before {
+        content: "";
+        position: absolute;
+        top: -50%;
+        right: -30%;
+        width: 200rpx;
+        height: 200rpx;
+        background: rgba(255, 255, 255, 0.1);
+        border-radius: 50%;
+      }
+
+      .card-content {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        position: relative;
+        z-index: 2;
+
+        .bank-info {
+          display: flex;
+          align-items: center;
+
+          .bank-icon {
+            width: 100rpx;
+            height: 100rpx;
+            border-radius: 50rpx;
+            background: rgba(255, 255, 255, 1);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin-right: 24rpx;
+
+            .icon {
+              font-size: 28rpx;
+              color: #fff;
+            }
+          }
+
+          .bank-details {
+            .bank-name {
+              color: #fff;
+              font-size: 32rpx;
+              font-weight: 600;
+              margin-bottom: 8rpx;
+            }
+
+            .card-number {
+              color: rgba(255, 255, 255, 0.8);
+              font-size: 28rpx;
+              font-weight: 400;
+              letter-spacing: 2rpx;
+            }
+          }
+        }
+        .right-box {
+          display: flex;
+          align-items: center;
+        }
+
+        .icon-box {
+          width: 60rpx;
+          height: 60rpx;
+          background: rgba(255, 255, 255, 0.2);
+          border-radius: 12rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          transition: background 0.3s ease;
+          &.icon-box {
+            margin-left: 20rpx;
+          }
+
+          &:active {
+            background: rgba(255, 255, 255, 0.3);
+          }
+
+          .edit-icon {
+            color: #fff;
+            font-size: 28rpx;
+          }
+        }
+      }
+
+      .card-selected {
+        position: absolute;
+        top: 20rpx;
+        right: 20rpx;
+        width: 40rpx;
+        height: 40rpx;
+        background: #4caf50;
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        z-index: 3;
+
+        .check-icon {
+          color: #fff;
+          font-size: 24rpx;
+          font-weight: 600;
+        }
+      }
+    }
+  }
+
+  .add-card-section {
+    padding: 30rpx 30rpx 60rpx;
+
+    .add-card-btn {
+      width: 100%;
+      background: linear-gradient(135deg, #e9c279 0%, #d4a853 100%);
+      color: #fff;
+      font-size: 30rpx;
+      font-weight: 600;
+      padding: 32rpx 0;
+      border-radius: 16rpx;
+      border: none;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      box-shadow: 0 6rpx 20rpx rgba(233, 194, 121, 0.3);
+      transition: all 0.3s ease;
+
+      &:active {
+        transform: translateY(2rpx);
+        box-shadow: 0 4rpx 12rpx rgba(233, 194, 121, 0.2);
+      }
+
+      .add-icon {
+        font-size: 36rpx;
+        margin-right: 16rpx;
+      }
+
+      .add-text {
+        font-size: 30rpx;
+      }
+    }
+  }
+
+  .modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1000;
+
+    .modal-content {
+      background: #fff;
+      border-radius: 24rpx;
+      width: 600rpx;
+      max-height: 80vh;
+      overflow: hidden;
+
+      .modal-header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 40rpx 30rpx 20rpx;
+        border-bottom: 1rpx solid #f0f0f0;
+
+        .modal-title {
+          color: #333;
+          font-size: 32rpx;
+          font-weight: 600;
+        }
+
+        .modal-close {
+          color: #999;
+          font-size: 48rpx;
+          font-weight: 300;
+          line-height: 1;
+        }
+      }
+
+      .modal-body {
+        padding: 30rpx;
+
+        .form-item {
+          margin-bottom: 30rpx;
+
+          .form-label {
+            display: block;
+            color: #333;
+            font-size: 28rpx;
+            font-weight: 500;
+            margin-bottom: 16rpx;
+          }
+
+          .form-input {
+            width: 100%;
+            padding: 24rpx;
+            background: #f8f9fa;
+            border: 2rpx solid #e9ecef;
+            border-radius: 12rpx;
+            font-size: 28rpx;
+            color: #333;
+            transition: border-color 0.3s ease;
+
+            &:focus {
+              border-color: #e9c279;
+            }
+
+            &::placeholder {
+              color: #999;
+            }
+          }
+        }
+      }
+
+      .modal-footer {
+        display: flex;
+        padding: 20rpx 30rpx 30rpx;
+        gap: 20rpx;
+
+        .cancel-btn {
+          flex: 1;
+          background: #f8f9fa;
+          color: #666;
+          font-size: 28rpx;
+          padding: 24rpx 0;
+          border-radius: 12rpx;
+          border: none;
+          transition: background 0.3s ease;
+
+          &:active {
+            background: #e9ecef;
+          }
+        }
+
+        .confirm-btn {
+          flex: 1;
+          background: linear-gradient(135deg, #e9c279 0%, #d4a853 100%);
+          color: #fff;
+          font-size: 28rpx;
+          padding: 24rpx 0;
+          border-radius: 12rpx;
+          border: none;
+          transition: all 0.3s ease;
+
+          &:active {
+            transform: translateY(1rpx);
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 163 - 0
pages/users/browsing_history/index.vue

@@ -0,0 +1,163 @@
+<template>
+  <view class="container">
+    <view class="record-list">
+      <view
+          v-for="(record, index) in records"
+          :key="index"
+          class="record-item"
+      >
+        <view class="icon-container">
+          <image :src="record.merchantLogo" mode="aspectFit"></image>
+        </view>
+        <view class="info-container">
+          <view class="shop-name">{{ record.merchantName }}</view>
+          <view class="browse-time">浏览时间:{{ record.createTime }}</view>
+        </view>
+        <button class="action-btn">商城主页</button>
+      </view>
+      <view class="loadingicon acea-row row-center-wrapper" v-if="goodScroll">
+        <text
+            class="loading iconfont icon-jiazai"
+            :hidden="loading == false"
+        ></text>
+      </view>
+      <view class="no-data" v-if="isNoDataState">
+        <image
+            src="https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/zhanwu_20250827104005_1720_6.png"
+        />
+      </view>
+      <view class="mores-txt flex" v-if="!goodScroll && !isNoDataState">
+        <text>我是有底线的</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import {computed, ref} from 'vue';
+import { onLoad, onShow, onReachBottom } from "@dcloudio/uni-app";
+import { footprintList } from "@/api/merchant.js";
+
+const records = ref([]);
+const loading = ref(false);
+const goodScroll = ref(true);
+// Pagination
+const params = ref({
+  page: 1,
+  limit: 10,
+});
+
+onLoad(() => {
+  getList();
+});
+onReachBottom(() => {
+  getList();
+});
+
+const isNoDataState = computed(() => {
+  return records.value.length === 0 && !loading.value;
+});
+const getList = async () => {
+  if (!goodScroll.value) return;
+  try {
+    loading.value = true;
+    const { data } = await footprintList(params.value);
+    console.log(data);
+    records.value = [...records.value, ...data.records] || [];
+    goodScroll.value = data.records.length >= params.value.limit;
+    params.value.page++;
+  } catch (err) {
+    console.error(err);
+  } finally {
+    loading.value = false;
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  padding: 20rpx;
+  box-sizing: border-box;
+}
+
+.record-list {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+
+.record-item {
+  background-color: #fff;
+  border-radius: 24rpx;
+  padding: 20rpx;
+  display: flex;
+  align-items: center;
+}
+
+.icon-container {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 16rpx;
+  margin-right: 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+
+
+.info-container {
+  flex: 1;
+}
+
+.shop-name {
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-bottom: 10rpx;
+  color: #333;
+}
+
+.browse-time {
+  font-size: 24rpx;
+  color: #666666;
+}
+
+.action-btn {
+  background-color: rgba(248, 192, 8, 0.10);
+  color: #F8C008;
+  border: none;
+  border-radius: 16rpx;
+  padding: 12rpx 24rpx;
+  font-size: 24rpx;
+  cursor: pointer;
+  transition: background-color 0.2s;
+}
+
+.footer {
+  text-align: center;
+  margin-top: 20px;
+  color: #999;
+  font-size: 13px;
+}
+
+.no-data {
+  margin: 150rpx auto 0;
+  text-align: center;
+
+  img {
+    width: 65%;
+    height: auto;
+  }
+}
+
+.mores-txt {
+  width: 100%;
+  align-items: center;
+  justify-content: center;
+  height: 70rpx;
+  color: #999;
+  font-size: 24rpx;
+
+}
+</style>

+ 25 - 8
pages/users/login/index.vue

@@ -140,6 +140,8 @@ import { useSendCode } from "@/hooks/useSendCode";
 import Cache from "@/utils/cache";
 import { EXPIRES_TIME } from "@/config/cache";
 import { useToast } from "@/hooks/useToast.js";
+import { getSceneInfo } from '@/utils/util.js';
+import { footprintScan } from "@/api/merchant.js";
 
 const appStore = useAppStore();
 const { Toast } = useToast();
@@ -166,6 +168,7 @@ const appleLoginStatus = ref(false);
 const appleUserInfo = ref(null);
 const appleShow = ref(false);
 const invite_code = ref("");
+const merchantId = ref('');
 
 // Watch formItem to update type
 watch(formItem, (newVal) => {
@@ -318,11 +321,21 @@ const submit = async () => {
 const getUserInfoFn = async (data) => {
   try {
     appStore.SETUID(data.uid);
-    await getWechatOpenid();
+    // await getWechatOpenid();
     const res = await getUserInfo();
     appStore.UPDATE_USERINFO(res.data);
-    Toast({ title: "登录成功" });
-    backHome();
+
+    if(merchantId.value != ''){
+      let obj ={
+        merchantId:merchantId.value,
+        userId:res.data.userId
+      }
+      await footprintScanFn(obj)
+    }else{
+      Toast({ title: "登录成功" });
+      backHome();
+    }
+
   } catch (err) {
     console.error(err);
     uni.showToast({ title: err.msg, icon: "none" });
@@ -330,11 +343,10 @@ const getUserInfoFn = async (data) => {
 };
 
 onLoad((options) => {
-  if (options?.invite_code) {
-    invite_code.value = options.invite_code;
-    uni.setStorageSync("inviteCode", options.invite_code);
-  } else {
-    invite_code.value = uni.getStorageSync("inviteCode") || "";
+  const data = getSceneInfo(options);
+  if (data?.merchantId) {
+    merchantId.value = Number(data.merchantId);
+    uni.setStorageSync("merchantId", data.merchantId);
   }
   getLogoImage();
 });
@@ -372,6 +384,11 @@ const change_password = () => {
     url: "/pages/change_password/change_password",
   });
 };
+  const footprintScanFn =async (data) => {
+    const res = await footprintScan(data);
+    Toast({ title: "登录成功" });
+    backHome();
+  }
 </script>
 
 <style lang="scss" scoped>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1401 - 0
pages/users/order_confirm/index.vue


+ 473 - 0
pages/users/share/index.vue

@@ -0,0 +1,473 @@
+<template>
+  <view class="share">
+    <!-- 海报内容区域 -->
+    <view class="bg_color_fff border_radius_20 save-area" id="saveArea">
+      <view class="merchant-info">
+        <image class="merchant-logo" :src="merchantInfo.merchantLogo" mode="aspectFill"></image>
+        <view class="merchant-detail">
+          <view class="merchant-name">{{merchantInfo.merchantNameNew}}</view>
+        </view>
+      </view>
+      <view class="big-merchant-logo">
+        <image class="merchant-logo" :src="merchantInfo.merchantLogo" mode="aspectFill"></image>
+      </view>
+      <view class="qrCodeInfo">
+        <view class="left">
+          <view class="title">{{ merchantInfo.merchantDescribeNew }}</view>
+          <view class="subTitle">扫码加入<text class="dot"></text>{{merchantInfo.merchantNameNew}}</view>
+        </view>
+        <view class="right">
+          <image class="qrCode" :src="qrCode" mode="widthFix"></image>
+        </view>
+      </view>
+    </view>
+
+    <!-- 操作按钮 -->
+    <view class="flex-center-between padding30">
+      <view class="order_btn flex-center save-btn" @click="saveToAlbum">
+        <view class="flex-center-between">
+          <image
+              src="/static/img/xiazai.png"
+              mode="widthFix"
+              class="share_img mr20"
+          ></image>
+          <text>保存海报</text>
+        </view>
+      </view>
+      <button class="order_btn flex-center share-btn" open-type="share">
+        <view class="flex-center-between">
+          <image
+              src="/static/img/fenxiang_w.png"
+              mode="widthFix"
+              class="share_img mr20"
+          ></image>
+          <text>分享海报</text>
+        </view>
+      </button>
+    </view>
+
+    <!-- 用于绘制海报的Canvas -->
+    <canvas
+        canvas-id="posterCanvas"
+        id="posterCanvas"
+        style="position: fixed; top: -9999px; left: -9999px;"
+        :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
+    ></canvas>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
+import { onLoad } from "@dcloudio/uni-app";
+import { getQrcode } from "@/api/user.js"
+
+// 响应式数据
+const merchantInfo = ref({})
+const qrCode = ref("")
+const canvasWidth = ref(600) // 根据参考图片调整宽度
+const canvasHeight = ref(800) // 根据参考图片调整高度
+
+// 获取二维码方法
+const QRCodeGenerationFn = async () => {
+  uni.showLoading({ title: "正在获取二维码..." })
+  try {
+    let data = {
+      id: merchantInfo.value.id
+    }
+    const response = await getQrcode(data)
+    console.log("getQrcode", response)
+    qrCode.value = response.message
+  } catch (err) {
+    console.error("获取二维码失败", err)
+    uni.showToast({ title: "获取二维码失败", icon: "none" })
+  } finally {
+    uni.hideLoading()
+  }
+}
+
+// 绘制圆角矩形
+const drawRoundRect = (ctx, x, y, width, height, radius) => {
+  ctx.beginPath()
+  ctx.moveTo(x + radius, y)
+  ctx.arcTo(x + width, y, x + width, y + height, radius)
+  ctx.arcTo(x + width, y + height, x, y + height, radius)
+  ctx.arcTo(x, y + height, x, y, radius)
+  ctx.arcTo(x, y, x + width, y, radius)
+  ctx.closePath()
+}
+
+// 绘制圆角图片
+const drawRoundImage = (ctx, imgPath, x, y, width, height, radius) => {
+  // 保存当前状态
+  ctx.save()
+
+  // 创建圆角路径
+  drawRoundRect(ctx, x, y, width, height, radius)
+  ctx.clip()
+
+  // 绘制图片
+  ctx.drawImage(imgPath, x, y, width, height)
+
+  // 恢复状态
+  ctx.restore()
+}
+
+// 保存海报到相册
+const saveToAlbum = async () => {
+  uni.showLoading({ title: "正在生成海报..." })
+
+  try {
+    // 检查必要的图片资源
+    if (!merchantInfo.value.merchantLogo) {
+      throw new Error("商家Logo不能为空")
+    }
+
+    if (!qrCode.value) {
+      throw new Error("二维码不能为空")
+    }
+
+    // 创建 Canvas 上下文
+    const ctx = uni.createCanvasContext('posterCanvas', getCurrentInstance())
+
+    // 1. 绘制白色背景
+    ctx.setFillStyle("#ffffff")
+    ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value)
+
+    // 3. 加载并绘制商家Logo(小头像)
+    await new Promise((resolve, reject) => {
+      uni.getImageInfo({
+        src: merchantInfo.value.merchantLogo,
+        success: (logoRes) => {
+          // 绘制圆形商家头像
+          const avatarSize = 80
+          const avatarX = 60
+          const avatarY = 60
+          const radius = 10 // 设置圆角半径为10
+
+          // ctx.drawImage(logoRes.path, avatarX, avatarY, avatarSize, avatarSize)
+          // ctx.restore()
+          drawRoundImage(ctx, logoRes.path, avatarX, avatarY, avatarSize, avatarSize, radius)
+
+          resolve()
+        },
+        fail: (err) => {
+          reject(new Error('获取商家Logo失败: ' + err.errMsg))
+        }
+      })
+    })
+
+    // 4. 绘制商家名称
+    const merchantName = merchantInfo.value.merchantNameNew || "商家名称"
+    ctx.setFontSize(28)
+    ctx.setFillStyle("#333333")
+    ctx.setTextAlign("left")
+    ctx.fillText(merchantName, 160, 110)
+
+    // 5. 绘制主体内容区域
+    const contentStartY = 200
+
+    // 6. 加载并绘制商家Logo(大头像)
+    await new Promise((resolve, reject) => {
+      uni.getImageInfo({
+        src: merchantInfo.value.merchantLogo,
+        success: (logoRes) => {
+          // 绘制圆形商家头像
+          const avatarSize = 400
+          const avatarX = 100
+          const avatarY = contentStartY
+          const radius = 10
+          // ctx.drawImage(logoRes.path, avatarX, contentStartY, avatarSize, avatarSize)
+          // ctx.restore()
+          drawRoundImage(ctx, logoRes.path, avatarX, avatarY, avatarSize, avatarSize, radius)
+
+          resolve()
+        },
+        fail: (err) => {
+          reject(new Error('获取商家Logo失败: ' + err.errMsg))
+        }
+      })
+    })
+
+    // 9. 绘制"好久不见"标题
+    const bottomStartY = contentStartY + 80+400
+    ctx.setFontSize(32)
+    ctx.setFillStyle("#333333")
+    ctx.setTextAlign("left")
+    ctx.fillText(merchantInfo.value.merchantDescribeNew, 40, bottomStartY)
+
+    // 10. 绘制副标题
+    ctx.setFontSize(20)
+    ctx.setFillStyle("#787878")
+    const subTitle = `扫码加入 · ${merchantInfo.value.merchantNameNew}`
+    ctx.fillText(subTitle, 40, bottomStartY + 50)
+
+    // 11. 加载并绘制二维码
+    await new Promise((resolve, reject) => {
+      uni.getImageInfo({
+        src: qrCode.value,
+        success: (qrRes) => {
+          const qrSize = 150
+          const qrX = canvasWidth.value-210;
+          const qrY = contentStartY+400+20
+          ctx.drawImage(qrRes.path, qrX, qrY, qrSize, qrSize)
+          ctx.restore()
+
+          resolve()
+        },
+        fail: (err) => {
+          reject(new Error('获取二维码失败: ' + err.errMsg))
+        }
+      })
+    })
+
+
+    // 等待绘制完成
+    await new Promise((resolve) => {
+      ctx.draw(false, () => {
+        setTimeout(() => {
+          resolve()
+        }, 800) // 增加等待时间确保绘制完成
+      })
+    })
+
+    // 13. 导出Canvas为临时文件
+    const tempFilePath = await new Promise((resolve, reject) => {
+      uni.canvasToTempFilePath({
+        canvasId: 'posterCanvas',
+        fileType: 'png',
+        quality: 1,
+        width: canvasWidth.value,
+        height: canvasHeight.value,
+        success: (res) => {
+          resolve(res.tempFilePath)
+        },
+        fail: (err) => {
+          reject(new Error('生成图片失败: ' + err.errMsg))
+        }
+      }, getCurrentInstance())
+    })
+
+    // 14. 保存到相册
+    await new Promise((resolve, reject) => {
+      uni.saveImageToPhotosAlbum({
+        filePath: tempFilePath,
+        success: () => {
+          resolve()
+        },
+        fail: (err) => {
+          if (err.errMsg.includes('auth deny') || err.errMsg.includes('auth denied')) {
+            uni.showModal({
+              title: '提示',
+              content: '需要授权保存图片到相册',
+              showCancel: false,
+              confirmText: '去设置',
+              success: () => {
+                uni.openSetting()
+              }
+            })
+          }
+          reject(new Error('保存失败: ' + err.errMsg))
+        }
+      })
+    })
+
+    uni.hideLoading()
+    uni.showToast({ title: '保存成功', icon: 'success' })
+
+  } catch (error) {
+    uni.hideLoading()
+    console.error('保存海报失败:', error)
+    uni.showToast({
+      title: error.message || '保存失败',
+      icon: 'none',
+      duration: 3000
+    })
+  }
+}
+
+// 页面加载时获取二维码
+onLoad((options) => {
+  if (options.merchantInfo) {
+    merchantInfo.value = JSON.parse(decodeURIComponent(options.merchantInfo))
+    merchantInfo.value.merchantName="一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家"
+    merchantInfo.value.merchantDescribe="一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家一号商家"
+    // 修正语法错误
+    merchantInfo.value.merchantNameNew = truncateText(merchantInfo.value.merchantName, 10) || ''
+    merchantInfo.value.merchantDescribeNew = truncateText(merchantInfo.value.merchantDescribe, 10) || ''
+        qrCode.value = merchantInfo.value.merchantCodeUrl || ''
+
+    if (!qrCode.value) {
+      QRCodeGenerationFn()
+    }
+  }
+})
+
+// 分享功能
+const onShareAppMessage = () => {
+  let keyInfo = `merchantCode=${merchantInfo.value?.merchantCode}`
+  keyInfo = encodeURIComponent(keyInfo)
+  return {
+    title: `快来加入${merchantInfo.value?.merchantName || '我们'}~`,
+    desc: merchantInfo.value.merchantDescribeNew,
+    path: `/pages/index/index?scene=${keyInfo}`
+  }
+}
+// 添加文本截取函数
+const truncateText = (text, maxLength) => {
+  if (!text) return ''
+  if (text.length <= maxLength) return text
+  return text.substring(0, maxLength) + '...'
+}
+// 暴露给模板
+defineExpose({
+  onShareAppMessage
+})
+</script>
+
+<style lang="scss" scoped>
+.share {
+  padding: 30rpx;
+  background-color: #f5f5f5;
+  min-height: 100vh;
+  box-sizing: border-box;
+
+  .save-area {
+    padding: 40rpx;
+    box-sizing: border-box;
+    position: relative;
+    margin-bottom: 30rpx;
+  }
+
+  .logo_img {
+    width: 60rpx;
+    height: 60rpx;
+    border-radius: 50%;
+  }
+
+  .share_img {
+    width: 40rpx;
+    height: 40rpx;
+  }
+
+  .order_btn {
+    padding: 20rpx;
+    width: 45%;
+    text-align: center;
+    font-size: 32rpx;
+    color: white;
+    border-radius: 12rpx;
+    border: none;
+    background: #f0ad4e;
+  }
+}
+
+.merchant-info {
+  display: flex;
+  align-items: center;
+  margin-bottom: 30rpx;
+
+  .merchant-logo {
+    width: 120rpx;
+    height: 120rpx;
+    border-radius: 12rpx;
+    margin-right: 30rpx;
+  }
+
+  .merchant-detail {
+    flex: 1;
+
+    .merchant-name {
+      font-size: 32rpx;
+      font-weight: bold;
+      color: #333;
+    }
+  }
+}
+
+.big-merchant-logo {
+  display: flex;
+  width: 100%;
+  justify-content: center;
+  align-items: center;
+  margin: 60rpx 0;
+
+  .merchant-logo {
+    width: 400rpx;
+    height: 400rpx;
+    border-radius: 20rpx;
+    box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
+  }
+}
+
+.border_radius_20 {
+  border-radius: 20rpx;
+}
+
+.bg_color_fff {
+  background-color: #ffffff;
+}
+
+.qrCodeInfo {
+  display: flex;
+  align-items: center;
+  margin-top: 60rpx;
+
+  .left {
+    flex: 1;
+
+    .title {
+      font-size: 36rpx;
+      font-weight: bold;
+      color: #333;
+      line-height: 60rpx;
+      margin-bottom: 16rpx;
+    }
+
+    .subTitle {
+      font-size: 28rpx;
+      color: #666;
+      line-height: 40rpx;
+
+      .dot {
+        display: inline-block;
+        vertical-align: middle;
+        height: 8rpx;
+        width: 8rpx;
+        border-radius: 50%;
+        background-color: #666;
+        margin: 0 16rpx;
+      }
+    }
+  }
+
+  .right {
+    width: 160rpx;
+    text-align: center;
+
+    .qrCode {
+      width: 100%;
+      border-radius: 12rpx;
+    }
+  }
+}
+
+.flex-center-between {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.padding30 {
+  padding: 30rpx 0;
+}
+
+.flex-center {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.mr20 {
+  margin-right: 20rpx;
+}
+</style>

+ 518 - 0
pages/users/user_address/index.vue

@@ -0,0 +1,518 @@
+<template>
+  <view>
+    <!-- #ifdef APP-->
+    <view class="status"></view>
+    <!-- #endif -->
+    <form @submit="formSubmit" report-submit="true">
+      <view class="addAddress pad30">
+        <view class="list borRadius14">
+          <view class="item acea-row row-between-wrapper" style="border: none">
+            <view class="name">姓名</view>
+            <input
+              type="text"
+              placeholder="请输入姓名"
+              placeholder-style="color:#ccc;"
+              name="realName"
+              :value="userAddress.realName"
+              placeholder-class="placeholder"
+              maxlength="4"
+            />
+          </view>
+          <view class="item acea-row row-between-wrapper">
+            <view class="name">联系电话</view>
+            <input
+              type="number"
+              placeholder="请输入联系电话"
+              placeholder-style="color:#ccc;"
+              name="phone"
+              :value="userAddress.phone"
+              placeholder-class="placeholder"
+              maxlength="11"
+            />
+          </view>
+          <view class="item acea-row row-between-wrapper relative">
+            <view class="name">所在地区</view>
+            <view class="address">
+              <picker
+                mode="multiSelector"
+                @change="bindRegionChange"
+                @columnchange="bindMultiPickerColumnChange"
+                :value="valueRegion"
+                :range="multiArray"
+              >
+                <view class="acea-row">
+                  <view class="picker line1"
+                    >{{ region[0] }},{{ region[1] }},{{ region[2] }}</view
+                  >
+                  <view class="iconfont icon-xiangyou abs_right"></view>
+                </view>
+              </picker>
+            </view>
+          </view>
+          <view class="item acea-row row-between-wrapper relative">
+            <view class="name">详细地址</view>
+            <input
+              type="text"
+              placeholder="请填写具体地址"
+              placeholder-style="color:#ccc;"
+              name="detail"
+              placeholder-class="placeholder"
+              v-model="userAddress.detail"
+              maxlength="100"
+            />
+            <view
+              class="iconfont icon-dizhi font-color abs_right"
+              @tap="chooseLocation"
+            ></view>
+          </view>
+        </view>
+        <view class="default acea-row row-middle borRadius14">
+          <checkbox-group @change="ChangeIsDefault">
+            <checkbox :checked="userAddress.isDefault" />设置为默认地址
+          </checkbox-group>
+        </view>
+
+        <button class="keepBnt bg-color" form-type="submit">立即保存</button>
+        <!-- #ifdef MP -->
+        <view class="wechatAddress" v-if="!id" @click="getWxAddress"
+          >导入微信地址</view
+        >
+        <!-- #endif -->
+        <!-- #ifdef H5 -->
+        <view
+          class="wechatAddress"
+          v-if="wechat.isWeixin() && !id"
+          @click="getAddress"
+          >导入微信地址</view
+        >
+        <!-- #endif -->
+      </view>
+    </form>
+    <!-- #ifdef MP -->
+    <!-- <authorize @onLoadFun="onLoadFun" :isAuto="isAuto" :isShowAuth="isShowAuth" @authColse="authColse"></authorize> -->
+    <!-- #endif -->
+    <!-- <home></home> -->
+  </view>
+</template>
+
+<script setup>
+import { onLoad } from "@dcloudio/uni-app";
+import { ref, reactive, watch } from "vue";
+import { useAppStore } from "@/stores/app.js";
+import { editAddress, getAddressDetail } from "@/api/user.js";
+import { getCity } from "@/api/api.js";
+import { toLogin } from "@/libs/login.js";
+import { useToast } from "@/hooks/useToast.js";
+import wechat from "@/libs/wechat.js";
+import Cache from "@/utils/cache";
+// #ifdef MP
+// import authorize from '@/components/Authorize';
+// #endif
+// import home from "@/components/home";
+
+const { Toast } = useToast();
+const appStore = useAppStore();
+
+const regionDval = ["浙江省", "杭州市", "滨江区"];
+const cartId = ref("");
+const pinkId = ref(0);
+const couponId = ref(0);
+const id = ref(0);
+const userAddress = reactive({
+  isDefault: false,
+  realName: "",
+  phone: "",
+  detail: "",
+});
+const region = ref(["省", "市", "区"]);
+const valueRegion = ref([0, 0, 0]);
+const isAuto = ref(false);
+const isShowAuth = ref(false);
+const district = ref([]);
+const multiArray = ref([[], [], []]);
+const multiIndex = ref([0, 0, 0]);
+const cityId = ref(0);
+const defaultRegion = ["广东省", "广州市", "番禺区"];
+const defaultRegionCode = "440113";
+const bargain = ref(false);
+const combination = ref(false);
+const secKill = ref(false);
+const preOrderNo = ref(0);
+
+watch(
+  () => appStore.isLogin,
+  (newV) => {
+    if (newV) {
+      getUserAddress();
+      getCityList();
+    }
+  }
+);
+
+onLoad((options) => {
+  if (appStore.isLogin) {
+    preOrderNo.value = options.preOrderNo || 0;
+    id.value = options.id || 0;
+    uni.setNavigationBarTitle({
+      title: options.id ? "修改地址" : "添加地址",
+    });
+    getUserAddress();
+    if (Cache.has("cityList")) {
+      district.value = Cache.getItem("cityList");
+      initialize();
+    } else {
+      getCityList();
+    }
+  } else {
+    toLogin();
+  }
+});
+
+function getCityList() {
+  getCity().then((res) => {
+    district.value = res.data;
+    let oneDay = 24 * 3600 * 1000;
+    Cache.setItem({ name: "cityList", value: res.data, expires: oneDay * 7 }); //设置七天过期时间
+    initialize();
+  });
+}
+
+function initialize() {
+  if (district.value.length) {
+    let province = [],
+      city = [],
+      area = [];
+    let cityChildren = district.value[0].child || [];
+    let areaChildren = cityChildren.length ? cityChildren[0].child || [] : [];
+    district.value.forEach((item) => province.push(item.name));
+    cityChildren.forEach((item) => city.push(item.name));
+    areaChildren.forEach((item) => area.push(item.name));
+    multiArray.value = [province, city, area];
+  }
+}
+
+function bindRegionChange(e) {
+  const mi = multiIndex.value;
+  const province = district.value[mi[0]] || { child: [] };
+  const city = province.child[mi[1]] || { cityId: 0 };
+  const ma = multiArray.value;
+  const value = e.detail.value;
+  region.value = [ma[0][value[0]], ma[1][value[1]], ma[2][value[2]]];
+  cityId.value = city.cityId;
+  valueRegion.value = [0, 0, 0];
+  initialize();
+}
+
+function bindMultiPickerColumnChange(e) {
+  const column = e.detail.column;
+  const value = e.detail.value;
+  const ma = multiArray.value;
+  const mi = multiIndex.value;
+  mi[column] = value;
+  switch (column) {
+    case 0:
+      const currentCity = district.value[value] || { child: [] };
+      const areaList = currentCity.child[0] || { child: [] };
+      ma[1] = currentCity.child.map((item) => item.name);
+      ma[2] = areaList.child.map((item) => item.name);
+      break;
+    case 1:
+      const cityList = district.value[mi[0]].child[mi[1]].child || [];
+      ma[2] = cityList.map((item) => item.name);
+      break;
+    case 2:
+      break;
+  }
+  multiArray.value = [...ma];
+  multiIndex.value = [...mi];
+}
+
+function getUserAddress() {
+  if (!id.value) return;
+  getAddressDetail(id.value).then((res) => {
+    const regionArr = [res.data.province, res.data.city, res.data.district];
+    Object.assign(userAddress, res.data);
+    region.value = regionArr;
+    cityId.value = res.data.cityId;
+  });
+}
+
+function chooseLocation() {
+  uni.chooseLocation({
+    success: (res) => {
+      userAddress.detail = res.address.replace(
+        /.+?(省|市|自治区|自治州|县|区)/g,
+        ""
+      );
+    },
+  });
+}
+
+// 导入微信地址(小程序)
+function getWxAddress() {
+  uni.authorize({
+    scope: "scope.address",
+    success: function () {
+      uni.chooseAddress({
+        success: function (res) {
+          let addressP = {
+            province: res.provinceName,
+            city: res.cityName,
+            district: res.countyName,
+            cityId: 0,
+          };
+          editAddress({
+            address: addressP,
+            isDefault: 1,
+            realName: res.userName,
+            postCode: res.postalCode,
+            phone: res.telNumber,
+            detail: res.detailInfo,
+            id: 0,
+          })
+            .then((res2) => {
+              setTimeout(() => {
+                if (cartId.value) {
+                  let url = `/pages/users/order_confirm/index?cartId=${
+                    cartId.value
+                  }&addressId=${id.value ? id.value : res2.data.id}&pinkId=${
+                    pinkId.value
+                  }&couponId=${couponId.value}&secKill=${
+                    secKill.value
+                  }&combination=${combination.value}&bargain=${bargain.value}`;
+                  cartId.value = "";
+                  pinkId.value = "";
+                  couponId.value = "";
+                  uni.navigateTo({ url });
+                } else {
+                  uni.navigateBack({ delta: 1 });
+                }
+              }, 1000);
+              Toast({ title: "添加成功", icon: "success" });
+            })
+            .catch((err) => {
+              Toast({ title: err });
+            });
+        },
+        fail: function (res) {
+          if (res.errMsg == "chooseAddress:cancel")
+            return Toast({ title: "取消选择" });
+        },
+      });
+    },
+    fail: function () {
+      uni.showModal({
+        title: "您已拒绝导入微信地址权限",
+        content: "是否进入权限管理,调整授权?",
+        success(res) {
+          if (res.confirm) {
+            uni.openSetting({});
+          } else if (res.cancel) {
+            Toast({ title: "已取消!" });
+          }
+        },
+      });
+    },
+  });
+}
+
+// 导入共享地址(微信);
+function getAddress() {
+  wechat
+    .openAddress()
+    .then((userInfo) => {
+      editAddress({
+        id: id.value,
+        realName: userInfo.userName,
+        phone: userInfo.telNumber,
+        address: {
+          province: userInfo.provinceName,
+          city: userInfo.cityName,
+          district: userInfo.countryName,
+          cityId: 0,
+        },
+        detail: userInfo.detailInfo,
+        isDefault: 1,
+        postCode: userInfo.postalCode,
+      })
+        .then((res2) => {
+          setTimeout(() => {
+            if (cartId.value) {
+              let url = `/pages/users/order_confirm/index?cartId=${
+                cartId.value
+              }&addressId=${id.value ? id.value : res2.data.id}&pinkId=${
+                pinkId.value
+              }&couponId=${couponId.value}&secKill=${
+                secKill.value
+              }&combination=${combination.value}&bargain=${bargain.value}`;
+              cartId.value = "";
+              pinkId.value = "";
+              couponId.value = "";
+              uni.navigateTo({ url });
+            } else {
+              uni.navigateTo({ url: "/pages/users/user_address_list/index" });
+            }
+          }, 1000);
+          Toast({ title: "添加成功", icon: "success" });
+        })
+        .catch((err) => {
+          Toast({ title: err || "添加失败" });
+        });
+    })
+    .catch((err) => {
+      console.log(err);
+    });
+}
+
+function formSubmit(e) {
+  const value = e.detail.value;
+  if (!value.realName) return Toast({ title: "请填写收货人姓名" });
+  if (!value.phone) return Toast({ title: "请填写联系电话" });
+  if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(value.phone))
+    return Toast({ title: "请输入正确的手机号码" });
+  if (region.value == "省-市-区") return Toast({ title: "请选择所在地区" });
+  if (!value.detail) return Toast({ title: "请填写详细地址" });
+  value.id = id.value;
+  let regionArray = region.value;
+  value.address = {
+    province: regionArray[0],
+    city: regionArray[1],
+    district: regionArray[2],
+    cityId: cityId.value,
+  };
+  value.isDefault = userAddress.isDefault;
+
+  uni.showLoading({
+    title: "保存中",
+    mask: true,
+  });
+  editAddress(value)
+    .then((res2) => {
+      Toast({
+        title: id.value ? "修改成功" : "添加成功",
+        icon: "success",
+      });
+      setTimeout(() => {
+        if (preOrderNo.value > 0) {
+          uni.redirectTo({
+            url: `/pages/users/order_confirm/index?preOrderNo=${
+              preOrderNo.value
+            }&addressId=${id.value ? id.value : res2.data.id}`,
+          });
+        } else {
+          // #ifdef H5
+          history.back();
+          // #endif
+          // #ifndef H5
+          uni.navigateBack({ delta: 1 });
+          // #endif
+        }
+      }, 1000);
+    })
+    .catch((err) => {
+      Toast({ title: err });
+    });
+}
+
+function ChangeIsDefault() {
+  userAddress.isDefault = !userAddress.isDefault;
+}
+</script>
+
+<style scoped lang="scss">
+.addAddress {
+  padding-top: 20rpx;
+}
+
+.addAddress .list {
+  background-color: #fff;
+  padding: 0 24rpx;
+}
+
+.addAddress .list .item {
+  border-top: 1rpx solid #eee;
+  height: 90rpx;
+  line-height: 90rpx;
+}
+
+.addAddress .list .item .name {
+  // width: 195rpx;
+  font-size: 30rpx;
+  color: #333;
+}
+
+.addAddress .list .item .address {
+  flex: 1;
+  margin-left: 50rpx;
+}
+
+.addAddress .list .item input {
+  width: 475rpx;
+  font-size: 30rpx;
+  font-weight: 400;
+}
+
+.addAddress .list .item .placeholder {
+  color: #ccc;
+}
+
+.addAddress .list .item picker .picker {
+  width: 410rpx;
+  font-size: 30rpx;
+}
+
+.addAddress .default {
+  padding: 0 30rpx;
+  height: 90rpx;
+  background-color: #fff;
+  margin-top: 23rpx;
+}
+
+.addAddress .default checkbox {
+  margin-right: 15rpx;
+}
+
+.addAddress .keepBnt {
+  width: 690rpx;
+  height: 86rpx;
+  border-radius: 50rpx;
+  text-align: center;
+  line-height: 86rpx;
+  margin: 80rpx auto 24rpx auto;
+  font-size: 32rpx;
+  color: #fff;
+}
+
+.addAddress .wechatAddress {
+  width: 690rpx;
+  height: 86rpx;
+  border-radius: 50rpx;
+  text-align: center;
+  line-height: 86rpx;
+  margin: 0 auto;
+  font-size: 32rpx;
+  color: #e93323;
+  border: 1px solid #e93323;
+}
+
+.relative {
+  position: relative;
+}
+
+.icon-dizhi {
+  font-size: 44rpx;
+  z-index: 100;
+}
+
+.abs_right {
+  position: absolute;
+  right: 0;
+}
+
+.status {
+  display: flex;
+  width: 750rpx;
+  // background-color: #E93323;
+  height: var(--status-bar-height);
+}
+</style>

+ 526 - 0
pages/users/user_address_list/index.vue

@@ -0,0 +1,526 @@
+<template>
+  <view>
+    <!-- #ifdef APP-->
+    <view class="status_1"></view>
+    <!-- #endif -->
+    <view class="line">
+      <image
+        src="../../../static/images/line.jpg"
+        v-if="addressList.length"
+      ></image>
+    </view>
+    <view
+      class="address-management"
+      :class="addressList.length < 1 && page > 1 ? 'fff' : ''"
+    >
+      <radio-group
+        class="radio-group"
+        @change="radioChange"
+        v-if="addressList.length"
+      >
+        <view
+          class="item borRadius14"
+          v-for="(item, index) in addressList"
+          :key="index"
+        >
+          <view class="address" @click="goOrder(item.id)">
+            <view class="consignee"
+              >收货人:{{ item.realName
+              }}<text class="phone">{{ item.phone }}</text></view
+            >
+            <view
+              >收货地址:{{ item.province }}{{ item.city }}{{ item.district
+              }}{{ item.detail }}</view
+            >
+          </view>
+          <view class="operation acea-row row-between-wrapper">
+            <!-- #ifndef MP -->
+            <radio
+              class="radio"
+              :value="index.toString()"
+              :checked="item.isDefault"
+            >
+              <text>设为默认</text>
+            </radio>
+            <!-- #endif -->
+            <!-- #ifdef MP -->
+            <radio class="radio" :value="index" :checked="item.isDefault">
+              <text>设为默认</text>
+            </radio>
+            <!-- #endif -->
+            <view class="acea-row row-middle">
+              <view @click="editAddressFn(item.id)"
+                ><text class="iconfont icon-bianji"></text>编辑</view
+              >
+              <view @click="delAddressFn(index)"
+                ><text class="iconfont icon-shanchu"></text>删除</view
+              >
+            </view>
+          </view>
+        </view>
+      </radio-group>
+      <view
+        class="loadingicon acea-row row-center-wrapper"
+        v-if="addressList.length"
+      >
+        <text
+          class="loading iconfont icon-jiazai"
+          :hidden="loading == false"
+        ></text
+        >{{ loadTitle }}
+      </view>
+      <view class="noCommodity" v-if="addressList.length < 1 && page > 1">
+        <view class="pictrue">
+          <image src="../../../static/images/noAddress.png"></image>
+        </view>
+      </view>
+      <view style="height: 120rpx"></view>
+    </view>
+    <view class="footer acea-row row-between-wrapper">
+      <!-- #ifdef MP-->
+      <view class="addressBnt bg-color" @click="addAddress"
+        ><text class="iconfont icon-tianjiadizhi"></text>添加新地址</view
+      >
+      <view class="addressBnt wxbnt" @click="getWxAddress"
+        ><text class="iconfont icon-weixin2"></text>导入微信地址</view
+      >
+      <!-- #endif -->
+      <!-- #ifdef H5-->
+
+      <view
+        class="addressBnt bg-color"
+        :class="wechat.isWeixin() ? '' : 'on'"
+        @click="addAddress"
+        ><text class="iconfont icon-tianjiadizhi"></text>添加新地址</view
+      >
+      <view
+        v-if="wechat.isWeixin()"
+        class="addressBnt wxbnt"
+        @click="getAddress"
+        ><text class="iconfont icon-weixin2"></text>导入微信地址</view
+      >
+      <!-- #endif -->
+
+      <!-- #ifdef APP-->
+      <view class="addressBnt on bg-color" @click="addAddress"
+        ><text class="iconfont icon-tianjiadizhi"></text>添加新地址
+      </view>
+      <!-- #endif -->
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch } from "vue";
+import { useAppStore } from "@/stores/app.js";
+import { onLoad, onShow, onReachBottom } from "@dcloudio/uni-app";
+import {
+  getAddressList,
+  setAddressDefault,
+  delAddress,
+  editAddress,
+} from "@/api/user.js";
+import { toLogin } from "@/libs/login.js";
+import { useToast } from "@/hooks/useToast.js";
+import wechat from "@/libs/wechat.js";
+const { Toast } = useToast();
+const appStore = useAppStore();
+
+const addressList = ref([]);
+const cartId = ref("");
+const pinkId = ref(0);
+const couponId = ref(0);
+const loading = ref(false);
+const loadend = ref(false);
+const loadTitle = ref("加载更多");
+const page = ref(1);
+const limit = ref(20);
+const isAuto = ref(false);
+const isShowAuth = ref(false);
+const bargain = ref(false);
+const combination = ref(false);
+const secKill = ref(false);
+const preOrderNo = ref(0);
+const isFlashSale = ref(false); // 是否为秒杀订单
+const isGroupBuy = ref(false); // 是否为团购订单
+
+watch(
+  () => appStore.isLogin,
+  (newV) => {
+    if (newV) {
+      getUserAddress(true);
+    }
+  }
+);
+
+function getUserAddress(isPage) {
+  getAddressListFn(isPage);
+}
+
+onLoad((options) => {
+  if (appStore.isLogin) {
+    preOrderNo.value = options.preOrderNo || 0;
+    isFlashSale.value = options.isFlashSale === "1"; // 接收秒杀订单标识
+    isGroupBuy.value = options.isGroupBuy === "1"; // 接收团购订单标识
+    getAddressListFn(true);
+  } else {
+    toLogin();
+  }
+});
+
+onShow(() => {
+  getAddressListFn(true);
+});
+
+function onLoadFun() {
+  getAddressListFn();
+}
+
+function authColse(e) {
+  isShowAuth.value = e;
+}
+
+// 导入微信地址(小程序)
+function getWxAddress() {
+  uni.authorize({
+    scope: "scope.address",
+    success: function () {
+      uni.chooseAddress({
+        success: function (res) {
+          let addressP = {
+            province: res.provinceName,
+            city: res.cityName,
+            district: res.countyName,
+            cityId: 0,
+          };
+          editAddress({
+            address: addressP,
+            isDefault: true,
+            realName: res.userName,
+            postCode: res.postalCode,
+            phone: res.telNumber,
+            detail: res.detailInfo,
+            id: 0,
+          })
+            .then(() => {
+              Toast(
+                {
+                  title: "添加成功",
+                  icon: "success",
+                },
+                () => {
+                  getAddressListFn(true);
+                }
+              );
+            })
+            .catch((err) => {
+              return Toast({
+                title: err,
+              });
+            });
+        },
+        fail: function (res) {
+          if (res.errMsg == "chooseAddress:cancel")
+            return Toast({
+              title: "取消选择",
+            });
+        },
+      });
+    },
+    fail: function () {
+      uni.showModal({
+        title: "您已拒绝导入微信地址权限",
+        content: "是否进入权限管理,调整授权?",
+        success(res) {
+          if (res.confirm) {
+            uni.openSetting({
+              success: function (res) {
+                console.log(res.authSetting);
+              },
+            });
+          } else if (res.cancel) {
+            return Toast({
+              title: "已取消!",
+            });
+          }
+        },
+      });
+    },
+  });
+}
+
+// 导入微信地址(公众号)
+function getAddress() {
+  wechat.openAddress().then((userInfo) => {
+    editAddress({
+      realName: userInfo.userName,
+      phone: userInfo.telNumber,
+      address: {
+        province: userInfo.provinceName,
+        city: userInfo.cityName,
+        district: userInfo.countryName,
+        cityId: 0,
+      },
+      detail: userInfo.detailInfo,
+      postCode: userInfo.postalCode,
+      isDefault: true,
+    })
+      .then(() => {
+        Toast(
+          {
+            title: "添加成功",
+            icon: "success",
+          },
+          () => {
+            getAddressListFn(true);
+          }
+        );
+      })
+      .catch((err) => {
+        Toast({
+          title: err || "添加失败",
+        });
+      });
+  });
+}
+
+function getAddressListFn(isPage) {
+  if (isPage) {
+    loadend.value = false;
+    page.value = 1;
+    addressList.value = [];
+  }
+  if (loading.value || loadend.value) return;
+  loading.value = true;
+  loadTitle.value = "";
+  getAddressList({
+    page: page.value,
+    limit: limit.value,
+  })
+    .then((res) => {
+      let list = res.data.list;
+      let isLoadend = list.length < limit.value;
+      addressList.value = [...addressList.value, ...list];
+      loadend.value = isLoadend;
+      loadTitle.value = isLoadend ? "我也是有底线的" : "加载更多";
+      page.value = page.value + 1;
+      loading.value = false;
+    })
+    .catch(() => {
+      loading.value = false;
+      loadTitle.value = "加载更多";
+    });
+}
+
+// 设置默认地址
+function radioChange(e) {
+  let index = parseInt(e.detail.value);
+  let address = addressList.value[index];
+  if (address == undefined) return Toast({ title: "您设置的默认地址不存在!" });
+  setAddressDefault(address.id)
+    .then(() => {
+      addressList.value.forEach((item, i) => {
+        item.isDefault = i === index;
+      });
+      Toast(
+        {
+          title: "设置成功",
+          icon: "success",
+        },
+        () => {
+          addressList.value = [...addressList.value];
+        }
+      );
+    })
+    .catch((err) => {
+      return Toast({
+        title: err,
+      });
+    });
+}
+
+// 编辑地址
+function editAddressFn(id) {
+  const cart = cartId.value;
+  const pink = pinkId.value;
+  const coupon = couponId.value;
+  cartId.value = "";
+  pinkId.value = "";
+  couponId.value = "";
+  const flashSaleParam = isFlashSale.value ? "&isFlashSale=1" : "";
+  uni.navigateTo({
+    url: `/pages/users/user_address/index?id=${id}&cartId=${cart}&pinkId=${pink}&couponId=${coupon}&secKill=${secKill.value}&combination=${combination.value}&bargain=${bargain.value}&preOrderNo=${preOrderNo.value}${flashSaleParam}`,
+  });
+}
+
+// 删除地址
+function delAddressFn(index) {
+  let address = addressList.value[index];
+  if (address == undefined) return Toast({ title: "您删除的地址不存在!" });
+  delAddress(address.id)
+    .then(() => {
+      Toast(
+        {
+          title: "删除成功",
+          icon: "success",
+        },
+        () => {
+          addressList.value.splice(index, 1);
+          addressList.value = [...addressList.value];
+        }
+      );
+    })
+    .catch((err) => {
+      return Toast({
+        title: err,
+      });
+    });
+}
+
+// 新增地址
+function addAddress() {
+  cartId.value = "";
+  pinkId.value = "";
+  couponId.value = "";
+  const flashSaleParam = isFlashSale.value ? "&isFlashSale=1" : "";
+  const groupBuyParam = isGroupBuy.value ? "&isGroupBuy=1" : "";
+  uni.navigateTo({
+    url: `/pages/users/user_address/index?preOrderNo=${preOrderNo.value}${flashSaleParam}${groupBuyParam}`,
+  });
+}
+
+function goOrder(id) {
+  if (preOrderNo.value) {
+    // 根据订单类型跳转到不同的确认页面
+    if (isFlashSale.value) {
+      // 秒杀订单跳转到秒杀确认页面
+      uni.redirectTo({
+        url: `/pages/users/utils/flashSale/confirmOrder?is_address=1&preOrderNo=${preOrderNo.value}&addressId=${id}`,
+      });
+    } else if (isGroupBuy.value) {
+      // 团购支付返回到支付详情页
+      uni.redirectTo({
+        url: `/pages/group_buying/paydetail?is_address=1&preOrderNo=${preOrderNo.value}&addressId=${id}`,
+      });
+    } else {
+      // 普通订单跳转到普通确认页面
+      uni.redirectTo({
+        url: `/pages/users/order_confirm/index?is_address=1&preOrderNo=${preOrderNo.value}&addressId=${id}`,
+      });
+    }
+  }
+}
+
+onReachBottom(() => {
+  getAddressListFn();
+});
+</script>
+
+<style lang="scss" scoped>
+.address-management {
+  padding: 20rpx 30rpx;
+}
+
+.address-management.fff {
+  background-color: #fff;
+  height: 1300rpx;
+}
+
+.line {
+  width: 100%;
+  height: 3rpx;
+
+  image {
+    width: 100%;
+    height: 100%;
+    display: block;
+  }
+}
+
+.address-management .item {
+  background-color: #fff;
+  padding: 0 20rpx;
+  margin-bottom: 20rpx;
+}
+
+.address-management .item .address {
+  padding: 35rpx 0;
+  border-bottom: 1rpx solid #eee;
+  font-size: 28rpx;
+  color: #282828;
+}
+
+.address-management .item .address .consignee {
+  font-size: 28rpx;
+  font-weight: bold;
+  margin-bottom: 8rpx;
+}
+
+.address-management .item .address .consignee .phone {
+  margin-left: 25rpx;
+}
+
+.address-management .item .operation {
+  height: 83rpx;
+  font-size: 28rpx;
+  color: #282828;
+}
+
+.address-management .item .operation .radio text {
+  margin-left: 13rpx;
+}
+
+.address-management .item .operation .iconfont {
+  color: #2c2c2c;
+  font-size: 35rpx;
+  vertical-align: -2rpx;
+  margin-right: 10rpx;
+}
+
+.address-management .item .operation .iconfont.icon-shanchu {
+  margin-left: 35rpx;
+  font-size: 38rpx;
+}
+
+.footer {
+  position: fixed;
+  width: 100%;
+  background-color: #fff;
+  bottom: 0;
+  height: 106rpx;
+  padding: 0 30rpx;
+  box-sizing: border-box;
+}
+
+.footer .addressBnt {
+  width: 330rpx;
+  height: 76rpx;
+  border-radius: 50rpx;
+  text-align: center;
+  line-height: 76rpx;
+  font-size: 30rpx;
+  color: #fff;
+}
+
+.footer .addressBnt.on {
+  width: 690rpx;
+  margin: 0 auto;
+}
+
+.footer .addressBnt .iconfont {
+  font-size: 35rpx;
+  margin-right: 8rpx;
+  vertical-align: -1rpx;
+}
+
+.footer .addressBnt.wxbnt {
+  background-color: #fe960f;
+}
+
+.status_1 {
+  display: flex;
+  width: 750rpx;
+  // background-color: #E93323;
+  height: var(--status-bar-height);
+}
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 103 - 0
pages/users/vault/aggrement.vue


+ 662 - 0
pages/users/vault/buy.vue

@@ -0,0 +1,662 @@
+<template>
+  <view class="container">
+    <!-- 顶部背景区域 -->
+    <view class="header-bg">
+      <view class="balance-info">
+        <text class="balance-title">可用余额</text>
+        <text class="balance-amount">{{ appStore.$userInfo.nowMoney }}</text>
+      </view>
+    </view>
+
+    <!-- 主要内容区域 -->
+    <view class="content">
+      <!-- Tab栏(已移入content顶部) -->
+      <view class="tab-container">
+        <view
+          class="tab-item"
+          :class="{ active: activeTab.index === index }"
+          v-for="(tab, index) in tabs"
+          :key="index"
+          @click="switchTab(tab)"
+        >
+          {{ tab.name }}
+        </view>
+      </view>
+
+      <!-- 权益标签 -->
+      <!-- <view class="rights-tag">权益 -0</view> -->
+
+      <!-- 选择克重标题 -->
+      <view class="section-title">
+        <text class="title-text">选择克重</text>
+        <view class="real-price-wrapper">
+          <view class="label">
+            <text class="real-price-label">实时金价</text>
+            <text class="real-price-value" v-show="activeTab.index === 0">{{
+              realGoldprice
+            }}</text>
+            <text class="real-price-value" v-show="activeTab.index === 1">{{
+              realPtprice
+            }}</text>
+            <text class="real-price-value" v-show="activeTab.index === 2">{{
+              realAgprice
+            }}</text>
+            <text class="real-price-unit">/g</text>
+          </view>
+          <view class="rights-tag-ellipse">
+            <text v-show="activeTab.index === 0"
+              >权益 -{{ goldAdjustPrice }}</text
+            >
+            <text v-show="activeTab.index === 1"
+              >权益 -{{ PtAdjustPrice }}</text
+            >
+            <text v-show="activeTab.index === 2"
+              >权益 -{{ AgAdjustPrice }}</text
+            >
+            <view class="ellipse-arrow"></view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 克重选择按钮 -->
+      <view class="weight-grid">
+        <view
+          class="weight-item"
+          :class="{ active: selectedWeight === weight }"
+          v-for="weight in weightOptions"
+          :key="weight"
+          @click="selectWeight(weight)"
+        >
+          {{ weight }}克
+        </view>
+      </view>
+
+      <!-- 自定义输入区域 -->
+      <view class="custom-input-section">
+        <view class="input-container">
+          <input
+            class="custom-input"
+            type="number"
+            v-model="customWeight"
+            placeholder="请输入克数"
+            @input="onInputChange"
+          />
+          <text class="input-unit">g</text>
+        </view>
+      </view>
+
+      <!-- 预计金额 -->
+      <view class="total-amount">
+        <text>预计金额 {{ estimatePrice }} 元</text>
+      </view>
+
+      <!-- 协议选择 -->
+      <view
+        class="agreement-section"
+        style="display: flex; align-items: center"
+      >
+        <up-checkbox
+          v-model:checked="agreedToTerms"
+          shape="square"
+          activeColor="#e9c279"
+          usedAlone
+          :customStyle="{ marginRight: '12rpx' }"
+        >
+          <template #label>
+            <text class="agreement-text"> 阅读并同意 </text>
+          </template>
+        </up-checkbox>
+        <view class="agreement-link" @click="showAgreement">《余料买入协议》</view>
+      </view>
+
+      <!-- 提交按钮 -->
+      <view class="submit-section">
+        <button
+          class="submit-btn"
+          :class="{ disabled: !canSubmit }"
+          :disabled="!canSubmit"
+          @click="handleSubmit"
+        >
+          立即购买
+        </button>
+      </view>
+    </view>
+
+    <!-- 协议弹窗 -->
+    <up-popup
+      :show="showAgreementPopup"
+      mode="center"
+      border-radius="20"
+      width="80%"
+      height="60%"
+    >
+      <view class="popup-content">
+        <view class="popup-header">
+          <!-- <text class="popup-title">买金协议</text> -->
+          <!-- <text class="popup-close" @click="closeAgreement">×</text> -->
+        </view>
+        <scroll-view scroll-y scroll-left="50" class="popup-body">
+          <view class="agreement-content">
+            <Aggrement /> 
+          </view>
+        </scroll-view>
+        <view class="popup-footer">
+          <button class="popup-btn" @click="closeAgreement">
+            我已详细知悉
+          </button>
+        </view>
+      </view>
+    </up-popup>
+
+    <!-- 未实名提示 -->
+    <up-modal
+      :show="showDetectModal"
+      title="当前用户未实名"
+      :showCancelButton="true"
+      confirmText="去认证"
+      @confirm="toFaceVerify"
+      @cancel="showDetectModal = false"
+      @close="showDetectModal = false"
+    />
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+import useRealGoldPrice from "@/hooks/useRealGoldPrice";
+import { buyGold } from "@/api/vault";
+import { getUserInfo } from "@/api/user";
+import { useAppStore } from "@/stores/app";
+import { useToast } from '@/hooks/useToast'
+import Aggrement from './aggrement.vue'
+
+const appStore = useAppStore();
+const { Toast } = useToast()
+
+const {
+  realGoldprice,
+  realPtprice,
+  realAgprice,
+  fetchGoldPrice,
+  goldAdjustPrice,
+  PtAdjustPrice,
+  AgAdjustPrice,
+} = useRealGoldPrice();
+
+onShow(() => {
+  fetchGoldPrice();
+});
+
+// const tabs = ["黄金", "铂金", "白银"];
+const tabs = [
+  { name: "黄金", mentalType: 1, index: 0 },
+  { name: "铂金", mentalType: 2, index: 1 },
+  { name: "白银", mentalType: 3, index: 2 },
+];
+const activeTab = ref(tabs[0]);
+const selectedWeight = ref(0);
+const customWeight = ref("");
+const currentPrice = ref("775.53");
+const agreedToTerms = ref(false);
+const showAgreementPopup = ref(false);
+const showDetectModal = ref(false)
+
+// 克重选项
+const weightOptions = [10, 20, 30, 50, 100, 200, 300, 500, 1000];
+
+// 计算总金额
+const totalAmount = computed(() => {
+  const weight = selectedWeight.value || parseFloat(customWeight.value) || 0;
+  const price = parseFloat(currentPrice.value);
+  return (weight * price).toFixed(1);
+});
+
+// 是否可以提交
+const canSubmit = computed(() => {
+  const hasWeight =
+    selectedWeight.value > 0 ||
+    (customWeight.value && parseFloat(customWeight.value) > 0);
+  return hasWeight;
+});
+
+// 切换黄金类型
+const switchTab = (tab) => {
+  console.log("tab", tab.name);
+  if (activeTab.value.name === tab.name) return;
+  activeTab.value = tab;
+  if (tab.name === "铂金" && PtAdjustPrice.value === 0) {
+    fetchGoldPrice("RTJ_Pt");
+  } else if (tab.name === "白银" && AgAdjustPrice.value === 0) {
+    fetchGoldPrice("RTJ_Ag");
+  }
+};
+
+const selectWeight = (weight) => {
+  selectedWeight.value = weight;
+  customWeight.value = "";
+};
+
+const onInputChange = (e) => {
+  const value = e.detail.value;
+  customWeight.value = value;
+  if (value) {
+    selectedWeight.value = 0;
+  }
+};
+
+const realWeight = computed(() => {
+  return selectedWeight.value === 0 ? customWeight.value : selectedWeight.value;
+});
+
+// 预估价
+const estimatePrice = computed(() => {
+  if (!realWeight.value) return 0;
+  if (activeTab.value.name === "黄金") {
+    return (realWeight.value * realGoldprice.value).toFixed(2);
+  } else if (activeTab.value.name === "铂金") {
+    return (realWeight.value * realPtprice.value).toFixed(2);
+  } else if (activeTab.value.name === "白银") {
+    return (realWeight.value * realAgprice.value).toFixed(2);
+  }
+  return 0;
+});
+
+const showAgreement = () => {
+  showAgreementPopup.value = true;
+};
+
+const closeAgreement = () => {
+  showAgreementPopup.value = false;
+};
+
+function toFaceVerify() {
+  showDetectModal.value = false
+  uni.navigateTo({ url: '/pages/users/face_detect/index' })
+}
+
+const handleSubmit = async () => {
+  if (!canSubmit.value) return;
+  try {
+    const priceMap = {
+      0: realGoldprice.value,
+      1: realPtprice.value,
+      2: realAgprice.value,
+    };
+    if (!agreedToTerms.value) {
+      return Toast({ title: "请阅读并同意协议" });
+    }
+
+    // 判断是否认证
+    if (!appStore.userPanelInfoGetter.realNameVerified) {
+      showDetectModal.value = true
+      return 
+    }
+
+    if (Number(priceMap[activeTab.value.index]) > Number(appStore.$userInfo.nowMoney)) {
+      return Toast({ title: "余额不足" });
+    }
+
+    uni.showLoading({title: '加载中'})
+    const weight = selectedWeight.value || parseFloat(customWeight.value);
+
+    const params = {
+      userId: appStore.uid,
+      operationType: 4, // 4 - 买金
+      weight,
+      price: priceMap[activeTab.value.index],
+      metalType: activeTab.value.mentalType,
+      type: 1,
+    };
+    await buyGold(params);
+    const { data } = await getUserInfo()
+    appStore.UPDATE_USERINFO(data);
+    uni.showToast({
+      title: `购买成功`,
+      icon: "success",
+    });
+    
+  } catch (error) {
+    console.error("buyGold", error);
+    const title = typeof error === 'string' ? error : '购买失败'
+    uni.showToast({
+      title,
+      icon: "error",
+    });
+  } finally {
+    uni.hideLoading()
+  }
+};
+
+// 监听自定义输入
+watch(customWeight, (newVal) => {
+  if (newVal) {
+    selectedWeight.value = 0;
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+// $header-color: #e9c279;
+// $primary-color: #e9c279;
+// $text-color: #333;
+// $light-text: #666;
+// $border-color: #f0f0f0;
+
+.container {
+  min-height: 90vh;
+  background-color: #f8f8f8;
+}
+
+.header-bg {
+  background-image: linear-gradient(
+    to bottom,
+    #e9c279 0%,
+    #e9c279 10%,
+    #f0dab2 70%,
+    transparent 100%
+  );
+  padding: 40rpx 30rpx 30rpx;
+  height: 400rpx;
+  position: relative;
+}
+
+.balance-info {
+  text-align: center;
+  margin-bottom: 60rpx;
+
+  .balance-title {
+    display: block;
+    color: white;
+    font-size: 28rpx;
+    margin-bottom: 20rpx;
+  }
+
+  .balance-amount {
+    display: block;
+    color: white;
+    font-size: 80rpx;
+    font-weight: bold;
+  }
+}
+
+.tab-container {
+  display: flex;
+  justify-content: center;
+  gap: 60rpx;
+  margin-top: 0;
+  margin-bottom: 40rpx;
+  // border-bottom: 2rpx solid #f0f0f0;
+}
+
+.tab-item {
+  color: #333;
+  font-size: 32rpx;
+  padding: 20rpx 0;
+  position: relative;
+  cursor: pointer;
+  background: transparent;
+  font-weight: normal;
+  transition: color 0.2s;
+
+  &.active {
+    font-weight: bold;
+    color: #e9c279;
+
+    &::after {
+      content: "";
+      position: absolute;
+      left: 0;
+      right: 0;
+      bottom: -2rpx;
+      height: 6rpx;
+      background: #e9c279;
+      border-radius: 3rpx 3rpx 0 0;
+      z-index: 1;
+    }
+  }
+}
+
+.content {
+  background: white;
+  margin: -90rpx 0 0;
+  border-radius: 20rpx;
+  padding: 10rpx 30rpx;
+  position: relative;
+  z-index: 10;
+  // box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
+}
+
+.section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 40rpx;
+
+  .title-text {
+    font-size: 32rpx;
+    font-weight: bold;
+    color: #333;
+  }
+}
+
+.real-price-wrapper {
+  position: relative;
+  align-items: center;
+  height: 60rpx;
+
+  .label {
+    display: flex;
+  }
+}
+
+.real-price-label {
+  font-size: 28rpx;
+  margin-right: 6rpx;
+  color: #e9c279;
+}
+
+.real-price-value {
+  font-size: 32rpx;
+  color: #e9c279;
+  font-weight: bold;
+  margin: 0 2rpx;
+}
+
+.real-price-unit {
+  font-size: 28rpx;
+  margin-left: 2rpx;
+  color: #e9c279;
+}
+
+.rights-tag-ellipse {
+  position: absolute;
+  display: inline-block;
+  left: 70%;
+  top: -100rpx;
+  transform: translateX(-50%);
+  background: #e9c279;
+  color: white;
+  padding: 0 14rpx;
+  border-radius: 12rpx;
+  font-size: 22rpx;
+  z-index: 2;
+  white-space: nowrap;
+  text-align: center;
+  min-width: 90rpx;
+  min-height: 40rpx;
+  line-height: 40rpx;
+  position: relative;
+}
+
+.ellipse-arrow {
+  position: absolute;
+  left: 40%;
+  bottom: -14rpx;
+  transform: translateX(-50%) rotate(20deg);
+  width: 0;
+  height: 0;
+  border-left: 16rpx solid transparent;
+  border-right: 16rpx solid transparent;
+  border-top: 28rpx solid #e9c279;
+  z-index: -1;
+}
+
+.weight-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 20rpx;
+  margin-bottom: 40rpx;
+}
+
+.weight-item {
+  background: #f8f8f8;
+  border: 2rpx solid #f0f0f0;
+  border-radius: 12rpx;
+  padding: 30rpx;
+  text-align: center;
+  font-size: 32rpx;
+  color: #666;
+  cursor: pointer;
+  transition: all 0.3s;
+
+  &.active {
+    background: #e9c279;
+    color: white;
+    border-color: #e9c279;
+  }
+
+  &:hover {
+    border-color: #e9c279;
+  }
+}
+
+.custom-input-section {
+  margin-bottom: 40rpx;
+}
+
+.input-container {
+  display: flex;
+  align-items: center;
+  background: #f8f8f8;
+  border-radius: 12rpx;
+  padding: 20rpx;
+
+  .custom-input {
+    flex: 1;
+    background: transparent;
+    border: none;
+    font-size: 32rpx;
+    color: #333;
+
+    &::placeholder {
+      color: #ccc;
+    }
+  }
+
+  .input-unit {
+    font-size: 32rpx;
+    color: #666;
+    margin-left: 20rpx;
+  }
+}
+
+.total-amount {
+  text-align: center;
+  font-size: 36rpx;
+  color: #e9c279;
+  font-weight: bold;
+  margin-bottom: 60rpx;
+}
+
+.agreement-section {
+  margin-bottom: 40rpx;
+  display: flex;
+  align-items: center;
+}
+
+.agreement-text {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.agreement-link {
+  color: #e9c279;
+  text-decoration: underline;
+  margin-left: 4rpx;
+}
+
+.submit-section {
+  margin-top: 40rpx;
+}
+
+.submit-btn {
+  width: 100%;
+  background: #e9c279;
+  color: white;
+  border: none;
+  border-radius: 12rpx;
+  padding: 30rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+
+  &.disabled {
+    background: #ccc;
+    cursor: not-allowed;
+  }
+}
+
+// 弹窗样式
+.popup-content {
+  width: 80vw;
+  padding: 40rpx;
+  height: 80vh;
+  // display: flex;
+  // flex-direction: column;
+}
+
+.popup-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+
+  .popup-title {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: #333;
+  }
+
+  .popup-close {
+    font-size: 48rpx;
+    color: #666;
+    cursor: pointer;
+  }
+}
+
+.popup-body {
+  height: 62vh;
+  // flex: 1;
+  // min-height: 0; // 关键,防止flex塌陷
+
+  .agreement-content {
+    font-size: 28rpx;
+    line-height: 1.6;
+    color: #666;
+  }
+}
+
+.popup-footer {
+  margin-top: 30rpx;
+
+  .popup-btn {
+    width: 100%;
+    background: #e9c279;
+    color: white;
+    border: none;
+    border-radius: 12rpx;
+    padding: 10rpx 30rpx;
+    font-size: 32rpx;
+  }
+}
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1236 - 0
pages/users/vault/index.vue


+ 538 - 0
pages/users/vault/rechargeGold.vue

@@ -0,0 +1,538 @@
+<template>
+  <view class="withdraw">
+    <view class="recharge-header">
+      <view class="title">账户余额</view>
+      <view class="price">
+        <view class="fh">¥</view>
+        <view class="price-fh">{{
+          formatNumber(appStore?.userInfo?.nowMoney) || 0
+        }}</view>
+      </view>
+    </view>
+    <view class="tabs">
+      <view
+        v-for="item in tabsList"
+        :key="item.key"
+        class="tabs-item"
+        :class="[tabsIndex === item.key ? 'active' : '']"
+        @click="tabsChange(item)"
+      >
+        {{ item.title }}
+      </view>
+    </view>
+    <view class="contanier">
+      <view class="contaniner-top">
+        <view class="box-title">克重</view>
+        <view class="price-box">
+          <view class="price">
+            实时金价
+            <span class="tit">{{ realprice.toFixed(2) }}</span>
+          </view>
+          <view class="equity">权益 -{{ rightsStore.userBenefits.buy || 0 }}</view>
+        </view>
+      </view>
+      <view class="input-box">
+        <input
+          type="text"
+          placeholder="请输入克重"
+          v-model.number="weight"
+          @input="inputHandle"
+        />
+        <view class="g">g</view>
+      </view>
+      <view class="buy-price">
+        <view class="buy-txt">买料价格</view>
+        <view class="buy-num">{{ buyGoldPrice }}</view>
+        <view class="t"> 元 </view>
+      </view>
+
+      <view class="btn-box" @click="submitHandle">
+        <view class="btn">确认</view>
+      </view>
+      <view class="aggregate" @click="aggregate = !aggregate">
+        <image
+          class="choose"
+          :src="
+            aggregate
+              ? '/static/recycle/choose.png'
+              : '/static/recycle/nochoose.png'
+          "
+          mode="scaleToFill"
+        ></image>
+        <view class="aggre">
+          阅读并同意
+          <span class="aggre-text" @click.stop="showAggre">《买金协议》</span>
+        </view>
+      </view>
+      <view class="white"></view>
+    </view>
+    <uni-popup
+      ref="singPopup"
+      type="bottom"
+      borderRadius="10px 10px 0 0"
+      maskBackgroundColor="rgba(0,0,0,0)"
+    >
+      <view class="signContent">
+        <scroll-view scrollY class="scroll">
+          <up-parse :content="content"></up-parse>
+        </scroll-view>
+        <view
+          class="comfireBtn footer"
+          @click="
+            aggregate = true;
+            $refs.singPopup.close();
+          "
+        >
+          我已详细知悉
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from "vue";
+import { useAppStore } from "@/stores/app";
+import useRealGoldPrice from "@/hooks/useRealGoldPrice";
+import { rechargeGoldAPI } from "@/api/functions";
+import { agreementGetoneApi } from "@/api/user";
+const appStore = useAppStore();
+
+// 响应式数据
+const type = ref("余额");
+const weight = ref("");
+const buyGoldPrice = ref(0);
+
+const aggregate = ref(false);
+const content = ref("");
+const tabsIndex = ref(1);
+
+const selectedGender = ref("gold");
+
+const tabsList = ref([
+  { key: 1, label: "gold", title: "黄金" },
+  { key: 2, label: "platinum", title: "铂金" },
+  { key: 3, label: "silver", title: "白银" },
+]);
+
+// 实时价格处理
+const {
+  realGoldprice, // 黄金实时销售价(基础)
+  realPtprice, // 铂金实时销售价(基础)
+  realAgprice, // 白银实时销售价(基础)
+} = useRealGoldPrice({});
+import { useStoreRights } from "@/stores/rights";
+const rightsStore = useStoreRights();
+// 黄金调整价
+const adjustGoldprice = computed(() => {
+  const res = rightsStore.userBenefits.nobleMeta.find(
+    (gold) => gold.name == "黄金"
+  );
+  return res;
+});
+
+// 铂金调整价
+const adjustPtprice = computed(() => {
+  const res = rightsStore.userBenefits.nobleMeta.find(
+    (gold) => gold.name == "铂金"
+  );
+  return res;
+});
+// 白银调整价
+const adjustAgprice = computed(() => {
+  const res = rightsStore.userBenefits.nobleMeta.find(
+    (gold) => gold.name == "白银"
+  );
+  return res;
+});
+const viprealGoldprice = computed(
+  () =>
+    Number(realGoldprice.value) -
+    Number(rightsStore.userBenefits.buy) +
+    Number(adjustGoldprice.value.sellPriceAdjust)
+);
+const viprealPtprice = computed(
+  () =>
+    Number(realPtprice.value) -
+    Number(rightsStore.userBenefits.buy) +
+    Number(adjustPtprice.value.sellPriceAdjust)
+);
+const viprealAgprice = computed(
+  () =>
+    Number(realAgprice.value) -
+    Number(rightsStore.userBenefits.buy) +
+    Number(adjustAgprice.value.sellPriceAdjust)
+);
+const realprice = computed(() => {
+  if (tabsIndex.value == 1) {
+    return viprealGoldprice.value;
+  }
+  if (tabsIndex.value == 2) {
+    return viprealPtprice.value;
+  }
+  if (tabsIndex.value == 3) {
+    return viprealAgprice.value;
+  }
+});
+
+// 监听器
+watch(realprice, (newData) => {
+  buyGoldPrice.value = (weight.value * realprice.value).toFixed(2);
+});
+// 获取协议
+function agreementGetoneFn() {
+  // 资产说明
+  agreementGetoneApi({ name: "buyGold" }).then((res) => {
+    content.value = res.data?.content;
+  });
+}
+// 生命周期钩子 - 替代onLoad
+onMounted(() => {
+  // 页面加载时执行的逻辑]
+  agreementGetoneFn();
+});
+const inputHandle = () => {
+  //   buyGoldPrice.value = vk.myfn.accMulDecimal(this.weight, this.realprice, 2);
+  buyGoldPrice.value = (weight.value * realprice.value).toFixed(2);
+};
+
+// 提交
+const submitHandle = async () => {
+  if (checkInfo()) {
+    uni.showLoading({
+      title: "买料中",
+    });
+    const res = await rechargeGoldAPI({
+      metalType: tabsIndex.value,
+      weight: weight.value,
+    });
+    // console.log(res);
+    if (res.code == 200) {
+      uni.showToast({ title: "买料成功" });
+      setTimeout(() => {
+        uni.navigateTo({
+          url: "/pages/users/vault/index",
+        });
+      }, 1500);
+    }
+    uni.hideLoading();
+  }
+};
+const formatNumber = (num) => {
+  if (isNaN(num)) return "0.00";
+  const parts = num.toString().split(".");
+  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+  if (parts.length > 1) {
+    return parts[0] + "." + parts[1].padEnd(2, "0").slice(0, 2);
+  }
+  return parts[0] + ".00";
+};
+
+const checkInfo = () => {
+  if (!weight.value) {
+    uni.showToast({
+      title: "请输入克重",
+      duration: 1000,
+      icon: "none",
+    });
+    return false;
+  }
+  if (!aggregate.value) {
+    uni.showToast({
+      title: "请阅读并同意协议",
+      duration: 1000,
+      icon: "none",
+    });
+    return false;
+  }
+  return true;
+};
+// 假设$refs.singPopup在模板中存在
+const singPopup = ref(null);
+const showAggre = () => {
+  singPopup.value.open();
+};
+
+const tabsChange = (item) => {
+  tabsIndex.value = item.key;
+  weight.value = "";
+  buyGoldPrice.value = 0;
+};
+
+const toDecimalP2 = (arg) => {};
+</script>
+
+<style lang="scss" scoped>
+.withdraw {
+  width: 750rpx;
+  height: 100%;
+  // background-image: linear-gradient(180deg, #b7ad94 0%, #fcfcfc 100%);
+  background: $uni-bg-primary !important;
+  display: flex;
+  flex-direction: column;
+  // align-items: center;
+  .recharge-header {
+    margin-left: 103rpx;
+    margin-bottom: 50rpx;
+    padding-top: 80rpx;
+
+    .title {
+      font-size: 29rpx;
+      color: #ffffff;
+    }
+    .price {
+      font-size: 56rpx;
+      color: #ffffff;
+      margin-top: 15rpx;
+      display: flex;
+      // height: ;
+      align-items: flex-end;
+
+      .fh {
+        font-size: 36rpx;
+        // color: #cc9933;
+        margin-bottom: 8rpx;
+      }
+      .price-fh {
+        margin-left: 15rpx;
+      }
+    }
+  }
+  .tabs {
+    display: flex;
+    height: 80rpx;
+    border-radius: 40rpx;
+    padding: 0 20rpx;
+    color: #fff;
+    font-size: 32rpx;
+    font-weight: 300;
+    // margin-bottom: 10rpx;
+
+    .tabs-item {
+      width: 50%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      // align-items:;
+      position: relative;
+    }
+
+    .active::after {
+      position: absolute;
+      bottom: -10rpx;
+      left: 50%;
+      transform: translateX(-50%);
+      content: "";
+      z-index: 100;
+      border-style: solid;
+      border-width: 0 25rpx 25rpx 25rpx;
+      border-color: transparent transparent #ffffff transparent;
+      width: 0;
+      height: 0;
+      transform-origin: center top;
+      animation: showTriangle 0.3s ease-out forwards;
+    }
+  }
+
+  .contanier {
+    box-sizing: border-box;
+    padding: 70rpx 70rpx;
+    background-color: #ffffff;
+    border-radius: 40rpx;
+
+    .contaniner-top {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .price-box {
+        // flex: 3;
+        display: flex;
+        align-items: center;
+        position: relative;
+
+        .price {
+          font-size: 31rpx;
+          color: #000000;
+
+          .tit {
+            color: #d8a235;
+            font-size: 36rpx;
+            margin-left: 10rpx;
+          }
+        }
+
+        .equity {
+          position: absolute;
+          top: -35rpx;
+          right: 0;
+          padding: 2rpx 12rpx;
+          font-size: 16rpx;
+          color: #fff;
+          background: #cd9933;
+          // border-radius: 10rpx;
+          border-top-left-radius: 30rpx;
+          border-bottom-right-radius: 30rpx;
+
+          &::after {
+            position: absolute;
+            bottom: -15rpx;
+            left: 50%;
+            width: 0;
+            height: 0;
+            z-index: 0;
+            transform: translateX(-50%);
+            content: "";
+            // background: red;
+            border-style: solid;
+            border-width: 0 10px 10px 10px;
+            border-color: transparent transparent transparent #cd9933;
+          }
+        }
+      }
+    }
+    .input-box {
+      width: 610rpx;
+      height: 89rpx;
+      background-color: #ededed;
+      border-radius: 10rpx;
+      padding: 0 30rpx;
+      box-sizing: border-box;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin: 20rpx 0;
+      font-size: 25rpx;
+      color: #999999;
+
+      input {
+        color: #000000;
+        width: 99%;
+      }
+      .g {
+        font-size: 32rpx;
+        color: #cc9933;
+      }
+    }
+    .buy-price {
+      display: flex;
+      justify-content: center;
+      font-size: 25rpx;
+      color: #000000;
+      align-items: center;
+      .buy-num {
+        color: #cc9933;
+        font-size: 28rpx;
+        margin: 0 5rpx;
+      }
+    }
+    .payment-method {
+      margin-top: 120rpx;
+      .method-box {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin: 50rpx 0;
+        .box-left {
+          display: flex;
+          justify-content: center;
+          image {
+            height: 48rpx;
+          }
+          text {
+            margin-left: 15rpx;
+          }
+        }
+      }
+    }
+    .btn-box {
+      display: flex;
+      justify-content: center;
+      width: 100%;
+      margin-top: 200rpx;
+      .btn {
+        width: 300rpx;
+        height: 71rpx;
+        background-color: #cc9933;
+        border-radius: 34rpx;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 33rpx;
+        color: #ffffff;
+      }
+    }
+    .aggregate {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 10px;
+
+      .aggre {
+        font-size: 14px;
+        margin-left: 10px;
+
+        .aggre-text {
+          color: #cc9933;
+        }
+      }
+
+      .choose {
+        width: 16px;
+        height: 16px;
+      }
+    }
+    .white {
+      height: 100rpx;
+      background-color: #ffffff;
+    }
+    .box-title {
+      height: 50rpx;
+      padding-left: 19rpx;
+      font-size: 31rpx;
+      color: #000000;
+      position: relative;
+      display: flex;
+      align-items: center;
+
+      &::after {
+        position: absolute;
+        bottom: -10rpx;
+        left: 5rpx;
+        top: 23%;
+        transform: translateX(-50%);
+        content: "";
+        background-color: #cc9933;
+        width: 4rpx;
+        height: 28rpx;
+      }
+    }
+  }
+  .signContent {
+    background-color: #f8f8f8;
+    padding: 20px;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    border-radius: 20px 20px 0 0;
+
+    .scroll {
+      background-color: #fff;
+      padding: 4px;
+      height: 300px;
+      overflow-y: hidden;
+      border: 1px solid #dfdfdf;
+    }
+
+    .footer {
+      margin-top: 10px;
+      color: #fff;
+      padding: 4px 20px;
+      border-radius: 20px;
+      background: linear-gradient(to right, #8ed187, #5dd665);
+    }
+  }
+}
+</style>

+ 862 - 0
pages/users/vault/rechargeRmb.vue

@@ -0,0 +1,862 @@
+<template>
+  <view class="withdraw">
+    <view class="content">
+      <!-- 充值相关内容 -->
+      <view style="height: auto">
+        <view class="balance-box">
+          <view>
+            <view class="balance-title">可用余额</view>
+            <view class="balance-rmb">
+              <view style="display: flex; align-items: flex-end">¥</view>
+              <view class="rmb">{{ userInfo?.nowMoney || 0 }}</view>
+            </view>
+          </view>
+        </view>
+        <view class="tabs">
+          <view
+            class="tabs-item"
+            :class="[tabsIndex === item.key ? 'active' : '']"
+            v-for="item in tabsList"
+            :key="item.key"
+            @click="tabsChange(item)"
+          >
+            {{ item.title }}
+          </view>
+        </view>
+        <view class="box-list" v-if="tabsIndex === 0">
+          <view class="box">
+            <view class="title-tip">自主打款</view>
+            <view class="item">
+              <view class="targe">转</view>
+              <view class="address">
+                <view>用户名:深圳市水贝珠宝贸易有限公司</view>
+                <view>账&nbsp;&nbsp;&nbsp;户:1223606060@qq.com</view>
+              </view>
+              <view class="end">
+                <view class="copy" @click="copy('账户:1223606060@qq.com')"
+                  >复制</view
+                >
+              </view>
+            </view>
+          </view>
+        </view>
+        <view class="box-list" v-else>
+          <view class="box">
+            <view class="title-tip">自主打款</view>
+            <view class="item">
+              <view class="targe">转</view>
+              <view
+                class="address"
+                style="display: flex; flex-wrap: wrap; height: 140rpx"
+              >
+                <view>用户名:深圳市水贝珠宝贸易有限公司</view>
+                <view>卡号:756279419662</view>
+                <view>开户行:中国银行深圳水贝支行</view>
+              </view>
+              <view class="end">
+                <view
+                  class="copy"
+                  @click="
+                    copy(
+                      '用户名:深圳市水贝珠宝贸易有限公司,开户行:中国银行深圳水贝支行,卡号:756279419662'
+                    )
+                  "
+                >
+                  复制
+                </view>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+      <view style="padding: 0 30px; background-color: #fff; padding-top: 30rpx">
+        <view class="withdraw-body">
+          <view class="title-tip">金额</view>
+          <view class="input-money">
+            <view class="rmb">
+              <view style="width: 20px">¥</view>
+            </view>
+            <view style="width: 100%">
+              <input
+                placeholder="请输入充值金额(最低100元起充)"
+                v-model.number="extract"
+                type="digit"
+                class="t-input"
+                @input="onKeyInput"
+              />
+            </view>
+          </view>
+          <view class="info-money">
+            <view v-if="is_lowest">
+              <text class="info-money-num" style="color: #ff1e0f"
+                >最低{{ lowest }}元起充,账户充值{{ extract }}元</text
+              >
+            </view>
+          </view>
+          <view v-if="tabsIndex === 0" style="margin-top: 50rpx">
+            <view class="title-tip">订单号</view>
+            <view class="input-order">
+              <view class="rmb">
+                <view style="width: 20px"></view>
+              </view>
+              <view style="width: 100%">
+                <input
+                  placeholder="请输入订单号"
+                  v-model="order_no"
+                  class="t-input"
+                />
+              </view>
+            </view>
+          </view>
+          <view style="margin-top: 50rpx">
+            <view class="title-tip">转账图片</view>
+            <view style="margin-top: 20px">
+              <view class="upload-box">
+                <up-upload
+                  :fileList="imageList"
+                  uploadIcon="plus"
+                  @afterRead="afterRead"
+                  @delete="deletePic"
+                  name="1"
+                  multiple
+                  :maxCount="1"
+                >
+                  <template #trigger>
+                    <view class="upload-block">
+                      <uni-icons
+                        size="38"
+                        color="#ccc"
+                        type="plusempty"
+                      ></uni-icons>
+                    </view>
+                  </template>
+                </up-upload>
+              </view>
+            </view>
+          </view>
+          <view :class="'btn-box tx' + (is_post ? '-active' : '')">
+            <view class="btn" @click="handleShowModel">提交申请</view>
+          </view>
+          <view class="btn-tip" v-if="tabsIndex === 0">
+            提交后无需审核,0秒到账!
+            <span style="color: red">(推荐)</span>
+          </view>
+          <view class="aggregate" @click="aggregate = !aggregate">
+            <image
+              class="choose"
+              :src="
+                aggregate
+                  ? '/static/recycle/choose.png'
+                  : '/static/recycle/nochoose.png'
+              "
+              mode="scaleToFill"
+            ></image>
+            <view class="aggre">
+              阅读并同意
+              <span class="aggre-text" @click="showAggre">《充值协议》</span>
+            </view>
+          </view>
+        </view>
+        <view
+          v-if="tabsIndex === 0 && !appStore?.$wxConfig?.auditModeEnabled"
+          class="process-guidelines"
+        >
+          <view v-if="tabsIndex === 0" style="margin: 30rpx 0"
+            >支付宝充值流程指引</view
+          >
+          <up-parse
+            :content="process"
+            @imgtap="handlePreviewImage"
+          ></up-parse>
+        </view>
+      </view>
+    </view>
+    <uni-popup
+      ref="singPopup"
+      type="bottom"
+      borderRadius="10px 10px 0 0"
+      maskBackgroundColor="rgba(0,0,0,0)"
+    >
+      <view class="signContent">
+        <scroll-view scrollY class="scroll">
+          <up-parse :content="content"></up-parse>
+        </scroll-view>
+        <view
+          class="comfireBtn footer"
+          @click="
+            aggregate = true;
+            $refs.singPopup.close();
+          "
+        >
+          我已详细知悉
+        </view>
+      </view>
+    </uni-popup>
+
+    <!-- 图片预览 -->
+    <!-- <image
+      src="https://mp-c7c90a6c-c53b-48dd-bd94-692abc111f89.cdn.bspapp.com/2025/08/07/65570280-62375873-0.png"
+    ></image> -->
+
+    <!-- 底部占位 -->
+    <view style="width: 100%; height: 100rpx"></view>
+  </view>
+</template>
+
+<script setup>
+import { onLoad } from "@dcloudio/uni-app";
+import { ref, computed, watch } from "vue";
+import { useAppStore } from "@/stores/app";
+import { useImageUpload } from "@/hooks/useImageUpload";
+import { rechargeAlipayAPI, rechargeBankAPI } from "@/api/functions";
+import { agreementGetoneApi } from "@/api/user";
+// 引入uni-app API(无需额外import,直接使用uni.xxx即可)
+
+// 1. 初始化图片上传钩子(解构响应式数据和方法)
+const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
+  pid: 12,
+  model: "zfb",
+});
+
+// 2. 获取Pinia Store实例
+const appStore = useAppStore();
+
+// 3. 定义响应式数据(替代Vue2的data选项)
+// 基础类型用ref,复杂类型(对象/数组)也可用ref(Vue3推荐)
+const transferImg = ref([]); // 转账图片
+const agreement = ref("");
+const extract = ref(null); // 充值金额
+const is_lowest = ref(false); // 是否低于最低充值额
+const is_post = ref(false); // 提交按钮是否可用
+const lowest = ref(100); // 最低充值额
+const aggregate = ref(false); // 是否同意协议
+const tabsIndex = ref(0); // 当前选中的标签(支付宝/银行卡)
+const tabsList = ref([
+  { key: 0, title: "" },
+  { key: 1, title: "" },
+]); // 标签列表(固定数据,也可不用ref,直接const tabsList = [...])
+const order_no = ref(null); // 订单号
+const showPreview = ref(false); // 是否显示图片预览
+// const ImageUrl = ref([{ id: 1, src: "/static/logo.png" }]); // 预览图片列表
+const previewImageUrl = ref(""); // 预览图片地址
+const userInfo = ref({}); // 用户信息(从Store获取)
+// tabs切换
+const tabsChange = (item) => {
+  if(appStore?.$wxConfig?.auditModeEnabled) return
+  tabsIndex.value = item.key;
+  extract.value = null;
+  order_no.value = null;
+  imageList.value = [];
+};
+// 4. 定义方法(替代Vue2的methods选项)
+// 复制文本方法
+const copy = (item) => {
+  uni.setClipboardData({
+    data: item,
+    success: () => {
+      uni.showToast({
+        title: "复制成功",
+        icon: "success",
+      });
+    },
+    fail: (err) => {
+      uni.showToast({
+        title: "复制失败",
+        icon: "none",
+      });
+      console.error("复制失败:", err);
+    },
+  });
+};
+
+// 输入框输入事件
+const onKeyInput = () => {
+  checkMoney();
+};
+
+// 检查充值金额是否符合要求
+const checkMoney = () => {
+  // 处理null/undefined情况
+  const currentAmount = Number(extract.value);
+
+  if (isNaN(currentAmount)) {
+    is_post.value = false;
+    is_lowest.value = true;
+    return;
+  }
+
+  if (currentAmount >= lowest.value) {
+    is_post.value = true;
+    is_lowest.value = false;
+  } else {
+    is_lowest.value = true;
+    is_post.value = false;
+  }
+};
+
+// 提交申请(模板调用但原代码未实现,补充空函数避免报错)
+const handleShowModel = async () => {
+  // 此处可添加提交前的校验(如是否同意协议、是否上传图片等)
+  // 示例:
+  if (!aggregate.value) {
+    uni.showToast({
+      title: "请先阅读并同意《充值协议》",
+      icon: "none",
+    });
+    return;
+  }
+  if (!extract.value || extract.value < lowest.value) {
+    uni.showToast({
+      title: `请输入不低于${lowest.value}元的充值金额`,
+      icon: "none",
+    });
+    return;
+  }
+  // 支付宝
+  if (tabsIndex.value == 0) {
+    const res = await rechargeAlipayAPI({
+      orderNo: order_no.value,
+      price: extract.value,
+      rechargeImage: imageList.value.map((v) => v.info.url),
+    });
+    // console.log(res);
+    if (res.code == 200) {
+      uni.showToast({ title: "提交成功!" });
+    }
+  } else {
+    const res = await rechargeBankAPI({
+      price: extract.value,
+      rechargeImage: imageList.value.map((v) => v.info.url),
+    });
+    if (res.code == 200) {
+      uni.showToast({ title: "提交成功!" });
+      setTimeout(() => {
+        uni.navigateTo({
+          url: "/pages/users/vault/index",
+        });
+      });
+    }
+  }
+
+  // 银行卡
+};
+const singPopup = ref(null); // 需在模板中给uni-popup添加ref="singPopup"
+
+const showAggre = () => {
+  singPopup.value?.open();
+};
+
+// 显示图片预览(模板调用但原代码未实现,补充空函数避免报错)
+const showImage = (url) => {
+  uni.previewImage({
+    current: url, // 当前显示图片的URL
+    urls: [url], // 需要预览的图片URL列表
+    success: () => {},
+    fail: (err) => {
+      console.error("预览图片失败:", err);
+    },
+  });
+};
+
+// 处理图片预览
+const handlePreviewImage = (e) => {
+  console.log("e", e);
+
+  const { src, urls } = e.detail;
+  // 使用 uni-app 自带的图片预览 API
+  uni.previewImage({
+    current: src, // 当前点击的图片地址
+    urls: urls, // 所有图片的数组(用于左右滑动预览)
+  });
+};
+
+// 获取协议
+const process = ref("");
+const content = ref("123");
+const agreementGetoneFn = () => {
+  agreementGetoneApi({ name: "recharge_text" }).then((res) => {
+    process.value = res.data?.content;
+  });
+  agreementGetoneApi({ name: "recharge_rule" }).then((res) => {
+    content.value = res.data?.content;
+  });
+};
+
+// 5. 生命周期钩子(uni-app的onLoad,Vue3中直接使用)
+onLoad((options) => {
+  agreementGetoneFn();
+  if (appStore?.$wxConfig?.auditModeEnabled) {
+    console.log("执行");
+  } else {
+    console.log("执行", appStore?.$wxConfig?.auditModeEnabled);
+    tabsList.value = [
+      { key: 0, title: "支付宝" },
+      { key: 1, title: "银行卡" },
+    ];
+  }
+  console.log("页面加载参数:", options);
+  // 从Pinia Store获取用户信息(注意:Store中的userInfo若为响应式,需确保已加载)
+  userInfo.value = appStore.userInfo;
+  console.log("当前用户信息:", userInfo.value);
+
+  // 可选:监听Store中userInfo的变化,实时更新本地userInfo
+  watch(
+    () => appStore.userInfo,
+    (newUserInfo) => {
+      userInfo.value = newUserInfo;
+    },
+    { immediate: true } // 立即执行一次
+  );
+});
+
+// 6. 计算属性(替代Vue2的computed选项)
+// 原代码中computed为空,此处示例如何定义(按需添加)
+const computedExample = computed(() => {
+  // 示例:计算充值金额的格式化显示
+  return extract.value ? `¥${Number(extract.value).toFixed(2)}` : "¥0.00";
+});
+
+// 7. 监听器(替代Vue2的watch选项)
+// 示例:监听extract变化,自动检查金额(也可通过@input触发,二选一)
+watch(
+  extract,
+  (newVal) => {
+    checkMoney();
+  },
+  { immediate: true } // 初始值也触发检查
+);
+
+// 监听tabsIndex变化(按需添加)
+watch(tabsIndex, (newIndex) => {
+  console.log("切换到标签:", newIndex === 0 ? "支付宝" : "银行卡");
+  // 切换标签时的额外逻辑(如清空订单号等)
+  if (newIndex !== 0) {
+    order_no.value = null;
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+page {
+  height: 100%;
+  background-color: #f7f7f7;
+}
+::v-deep .file-list-row {
+  background: #ededed;
+  overflow: hidden;
+  border-radius: 20rpx;
+}
+
+.upload-box {
+  margin-top: 40rpx;
+  .upload-block {
+    width: 160rpx;
+    height: 160rpx;
+    border: 1px solid #ccc;
+    border-radius: 10rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    color: #ccc;
+    font-weight: 700;
+    font-size: 26rpx;
+  }
+}
+.withdraw {
+  height: 100%;
+  // background-color: #f7f7f7;
+  background-image: $uni-bg-primary !important;
+  .content {
+    // background: url('https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/static/20250529221733.jpg');
+    background-size: 100% 500rpx;
+    background-position: top center;
+    background-repeat: no-repeat;
+    box-sizing: border-box;
+    // padding: 0 30rpx;
+  }
+  &-body {
+    background-color: #fff;
+    padding: 50rpx 0;
+    font-size: 14px;
+
+    .input-money,
+    .input-order {
+      box-sizing: border-box;
+    }
+
+    .input-money {
+      display: flex;
+      align-items: center;
+      border-bottom: 1px solid #eaeef1;
+      margin-top: 30rpx;
+      background-color: #ededed;
+      padding: 20rpx;
+      border-radius: 15rpx;
+      width: 100%;
+
+      .rmb {
+        font-size: 38rpx;
+        // text-wrap: nowrap;
+        font-weight: 300;
+        font-family: "Calibri";
+      }
+      .t-input {
+        height: 1.4em;
+        width: 100%;
+        font-size: 26rpx;
+        border: none;
+        position: relative;
+        left: 1%;
+        outline: none;
+      }
+    }
+    .input-order {
+      display: flex;
+      align-items: center;
+      border-bottom: 1px solid #eaeef1;
+      margin-top: 30rpx;
+      background-color: #ededed;
+      padding: 20rpx;
+      border-radius: 15rpx;
+      width: 100%;
+
+      .t-input {
+        height: 1.4em;
+        font-size: 30rpx;
+        border: none;
+        position: relative;
+        left: 0;
+        outline: none;
+      }
+    }
+    .info-money {
+      margin-top: 10px;
+      font-size: 12px;
+      margin-bottom: 20px;
+      &-num {
+        color: #b2b2b2;
+      }
+    }
+    .tx {
+      button {
+        color: #b2b2b2;
+      }
+    }
+    .tx-active {
+      button {
+        color: #fff;
+        background: #c4bba6;
+      }
+    }
+    .aggregate {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 10px;
+      .aggre {
+        font-size: 14px;
+        margin-left: 10px;
+        .aggre-text {
+          color: #3ab0ff;
+        }
+      }
+    }
+    .choose {
+      width: 16px;
+      height: 16px;
+    }
+  }
+
+  .box {
+    display: flex;
+    flex-direction: column;
+    background-color: #fff;
+    padding: 50rpx;
+    padding-top: 50rpx;
+    width: 100%;
+    padding-bottom: 0;
+    box-sizing: border-box;
+    &-title {
+      font-size: 14px;
+    }
+  }
+
+  .tabs {
+    display: flex;
+    height: 80rpx;
+    border-radius: 40rpx;
+    padding: 0 20rpx;
+    color: #fff;
+    font-size: 38rpx;
+    font-weight: 300;
+    box-sizing: border-box;
+
+    .tabs-item {
+      width: 50%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      position: relative;
+      box-sizing: border-box;
+    }
+
+    .active::after {
+      position: absolute;
+      bottom: -10rpx;
+      left: 50%;
+      transform: translateX(-50%);
+      content: "";
+      border-style: solid;
+      border-width: 0 10px 10px 10px;
+      border-color: transparent transparent #ffffff transparent;
+      width: 0;
+      height: 0;
+      opacity: 0;
+      transform-origin: center top;
+      animation: showTriangle 0.3s ease-out forwards;
+    }
+  }
+
+  @keyframes showTriangle {
+    0% {
+      opacity: 0;
+      transform: translateY(-50%) scaleX(0);
+    }
+    100% {
+      opacity: 1;
+      transform: translateY(-50%) scaleX(1);
+    }
+  }
+
+  .box-list {
+    width: 100%;
+    box-sizing: border-box;
+    .title {
+      font-size: 14px;
+    }
+  }
+  .box {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    justify-content: center;
+    background-color: #f7f7f7;
+    border-top-left-radius: 60rpx;
+    border-top-right-radius: 60rpx;
+    width: 100%;
+    box-sizing: border-box;
+    padding: 14px;
+    padding-top: 60rpx;
+
+    .head {
+      font-size: 14px;
+    }
+    .item {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      margin-top: 30px;
+      .source {
+        background-color: #f7af74;
+      }
+      .targe {
+        background-color: #cd9933;
+      }
+      .source,
+      .targe {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        color: #fff;
+        width: 35px;
+        height: 35px;
+        border-radius: 50%;
+        margin-right: 20rpx;
+      }
+      .address {
+        height: 100rpx;
+        display: flex;
+        flex-wrap: wrap;
+        align-items: center;
+        flex: 1;
+        font-size: 26rpx;
+        font-family: "微软雅黑";
+        color: #000000;
+      }
+      .write {
+        margin: 0 10px;
+        font-size: 14px;
+        color: #888888;
+      }
+      .end {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        .copy {
+          color: #888888;
+          border-radius: 3px;
+          border: 1px solid #888888;
+          font-size: 24rpx;
+          padding: 6rpx 20rpx;
+          margin-right: 10rpx;
+        }
+      }
+    }
+  }
+
+  /* 图片预览样式 */
+  .image-preview-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.8);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 9999;
+
+    .imgBox {
+      width: 100%;
+      height: 100vh;
+
+      .preview-image {
+        width: 100%;
+        height: 100% !important;
+      }
+    }
+  }
+
+  .process-guidelines {
+    color: rgb(0, 0, 0);
+    font-size: 32rpx;
+    margin-top: 20rpx;
+    margin-bottom: 20rpx;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    border: 1rpx solid #f5f5f5;
+    border-radius: 20rpx;
+    box-shadow: 0 0px 10px 6px #efefef;
+    padding-bottom: 70rpx;
+    box-sizing: border-box;
+  }
+
+  .title-tip {
+    position: relative;
+    padding-left: 20rpx;
+    height: 50rpx;
+    display: flex;
+    align-items: center;
+    font-size: 32rpx;
+    font-family: "黑体";
+    width: 100%;
+    box-sizing: border-box;
+
+    &::before {
+      position: absolute;
+      top: 50%;
+      transform: translatey(-40%);
+      left: 0;
+      content: "";
+      width: 2px;
+      height: 18px;
+      background-color: #daa520;
+    }
+  }
+
+  .balance-box {
+    height: 300rpx;
+    padding-left: 60rpx;
+    color: #fff;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    width: 100%;
+    box-sizing: border-box;
+
+    .balance-title {
+      width: 100%;
+      font-size: 36rpx;
+      font-weight: 300;
+      margin-bottom: 10rpx;
+      font-family: "微软雅黑";
+    }
+
+    .balance-rmb {
+      width: 100%;
+      display: flex;
+      width: 100%;
+      font-size: 38rpx;
+      font-weight: 300;
+
+      .rmb {
+        font-size: 60rpx;
+        font-weight: 500;
+        margin-left: 10rpx;
+      }
+    }
+  }
+
+  .btn-box {
+    height: 100rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-bottom: 40rpx;
+    margin-top: 80rpx;
+    width: 100%;
+    box-sizing: border-box;
+
+    .btn {
+      height: 75rpx;
+      width: 400rpx;
+      background-color: #cd9933;
+      color: #fff;
+      border-radius: 50rpx;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 30rpx;
+    }
+  }
+
+  .btn-tip {
+    height: 30rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-bottom: 30rpx;
+    color: #808080;
+    width: 100%;
+    box-sizing: border-box;
+  }
+}
+
+.signContent {
+  background-color: #f8f8f8;
+  padding: 20px;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  border-radius: 20px 20px 0 0;
+  .scroll {
+    background-color: #fff;
+    padding: 4px;
+    height: 300px;
+    overflow-y: hidden;
+    border: 1px solid #dfdfdf;
+  }
+  .footer {
+    margin-top: 10px;
+    color: #fff;
+    padding: 4px 20px;
+    border-radius: 20px;
+    background: linear-gradient(to right, #8ed187, #5dd665);
+  }
+}
+</style>

+ 603 - 0
pages/users/vault/recycle/order_fill.vue

@@ -0,0 +1,603 @@
+<template>
+  <view class="sell-postend">
+    <view class="sell-postend-banner"></view>
+    <view class="sell-postend-content">
+      <view class="withdraw-express">
+        <view class="header">
+          <view class="header-left">
+            <view class="line"></view>
+            <view class="title">自主邮寄</view>
+          </view>
+          <view class="header-right">
+            <uni-countdown
+              :minute="countdown"
+              color="#f8c007"
+              @timeup="timeupHandle"
+            ></uni-countdown>
+          </view>
+        </view>
+        <view class="item">
+          <view class="targe">收</view>
+          <view class="address">
+            <view
+              >{{ appStore.$wxConfig.mailerName }} :{{
+                appStore.$wxConfig.mailerPhone
+              }}</view
+            >
+            <view class="address-detail" style="margin-top: 4px">
+              <text class="receive-address">地址:</text>
+              {{ appStore.$wxConfig.mailingAddress }}
+            </view>
+          </view>
+          <view class="end">
+            <view class="copy" @click="copy">复制</view>
+          </view>
+        </view>
+      </view>
+      <view class="upload-box">
+        <view class="header">
+          <view class="header-left">
+            <view class="line"></view>
+            <view class="title">上传实物图</view>
+          </view>
+        </view>
+        <view class="upload-tips"
+          >上传实物图、包裹图,如有购买凭证(发票、单据) 可以一起上传</view
+        >
+        <view class="upload-img">
+          <view class="upload-box-contanier">
+            <up-upload
+              :fileList="imageList"
+              uploadIcon="plus"
+              @afterRead="afterRead"
+              @delete="deletePic"
+              name="1"
+              multiple
+              :maxCount="4"
+            >
+              <template #trigger>
+                <view class="upload-block">
+                  <uni-icons
+                    size="38"
+                    color="#ccc"
+                    type="plusempty"
+                  ></uni-icons>
+                </view>
+              </template>
+            </up-upload>
+          </view>
+        </view>
+        <view class="pz-tips">示例图(产品实物,凭证图片)</view>
+        <view class="pz-img">
+          <image
+            v-for="(item, index) in filelist"
+            :key="index"
+            :src="item"
+          ></image>
+        </view>
+      </view>
+      <view class="postend-info-box">
+        <view class="header">
+          <view class="header-left">
+            <view class="line"></view>
+            <view class="title">您的信息</view>
+          </view>
+        </view>
+        <view class="postend-info">
+          <view class="info-item">
+            <view class="info-item-left">快递单号</view>
+            <view class="info-item-right">
+              <input
+                type="text"
+                v-model="expressNum"
+                placeholder="请输入快递单号"
+              />
+              <text class="iconfont icon-iconfontscan" @click="scanCode"></text>
+            </view>
+          </view>
+          <view class="info-agree" @click="showAggre">
+            <up-checkbox-group @change="checkboxChange" v-model="isCheck">
+              <up-checkbox activeColor="#e9c279" label="确认协议"></up-checkbox>
+            </up-checkbox-group>
+
+            <span style="color: #b5aa90; font-size: 16px">《回收协议》</span>
+          </view>
+        </view>
+      </view>
+      <view class="recyle-price">
+        <view class="recyle-desc">预估回收价</view>
+        <view class="recyle-price-num">¥{{ orderInfo.totalMoney }}</view>
+      </view>
+      <view class="btn-box">
+        <!-- <view class="postend-cbtn" @click="cancelOrder">取消订单</view> -->
+        <view class="postend-btn" @click="submitOrder">
+          <image class="btn" src="/static/images/btn-button.png"></image>
+          <text class="btn-text">立即回收</text>
+        </view>
+      </view>
+      <view class="white"></view>
+    </view>
+    <uni-popup
+      ref="singPopup"
+      type="bottom"
+      borderRadius="10px 10px 0 0"
+      maskBackgroundColor="rgba(0,0,0,0)"
+    >
+      <view class="signContent">
+        <scroll-view scrollY class="scroll">
+          <up-parse :content="content"></up-parse>
+        </scroll-view>
+        <view
+          class="comfireBtn footer"
+          @click="
+            isCheck = true;
+            $refs.singPopup.close();
+          "
+        >
+          我已详细知悉
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted, computed, onUnmounted } from "vue";
+import { useImageUpload } from "@/hooks/useImageUpload";
+import { recycleCancelAPI, recyclUpdateAPI } from "@/api/functions";
+import { onLoad } from "@dcloudio/uni-app";
+import { agreementGetoneApi } from "@/api/user";
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+
+// 1. 初始化图片上传钩子
+const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
+  pid: 14,
+  model: "recyle",
+});
+console.log(imageList.value);
+
+// 2. 响应式数据
+const pic = ref([]);
+const expressNum = ref("");
+const singPopup = ref(null);
+const isCheck = ref(false);
+const content = ref("");
+const filelist = ref([
+  "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example1.png",
+  "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example2.png",
+  "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example3.png",
+  "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example4.png",
+]);
+const reqData = ref({}); // 假设从路由参数或其他地方获取
+const orderInfo = ref({});
+const countdown = ref(999);
+// 3. 生命周期
+onLoad((options) => {
+  if (options.order) {
+    countdown.value = getTimeDiff(JSON.parse(options.order).expirationTime);
+    orderInfo.value = JSON.parse(options.order);
+  }
+});
+function agreementGetoneFn() {
+  // 资产说明
+  agreementGetoneApi({ name: "recyle" }).then((res) => {
+    content.value = res.data?.content;
+  });
+}
+const checkboxChange = (n) => {
+  isCheck.value = true;
+};
+
+const showAggre = () => {
+  singPopup.value?.open();
+  agreementGetoneFn();
+};
+// 4. 方法
+// 扫描快递单号
+const scanCode = () => {
+  uni.scanCode({
+    success: (res) => {
+      console.log(res);
+      expressNum.value = res.result;
+    },
+    fail: (err) => {
+      uni.showToast({ title: "扫码失败,请重试", icon: "none" });
+    },
+  });
+};
+
+// 复制收件人信息
+const copy = () => {
+  uni.setClipboardData({
+    data: `${appStore.$wxConfig.mailerName}:${appStore.$wxConfig.mailerPhone} 地址: ${appStore.$wxConfig.mailingAddress}`,
+    success() {
+      uni.showToast({ title: "复制成功", icon: "success" });
+    },
+    fail: (err) => {
+      uni.showToast({
+        title: "复制失败",
+        icon: "none",
+      });
+      console.error("复制失败:", err);
+    },
+  });
+};
+
+// 处理图片上传成功
+const success = (result) => {
+  pic.value.push(result.url);
+};
+
+// 提交订单
+const submitOrder = async () => {
+  if (!isCheck.value) {
+    uni.showToast({ title: "请阅读并同意回收协议", icon: "none" });
+    return;
+  }
+
+  if (imageList.value.length === 0) {
+    uni.showToast({ title: "请上传实物图", icon: "none" });
+    return;
+  }
+
+  if (!expressNum.value.trim()) {
+    uni.showToast({ title: "请填写快递单号", icon: "none" });
+    return;
+  }
+  try {
+    uni.showLoading({
+      title: "下单中",
+    });
+    // `实际项目中调用接口`
+    const res = await recyclUpdateAPI({
+      expressNo: expressNum.value,
+      images: imageList.value.map((v) => v.info.url),
+      orderNo: orderInfo.value.orderNo,
+    });
+
+    setTimeout(() => {
+      uni.redirectTo({
+        url: "/pages/users/vault/recycle/recyle_order",
+      });
+    }, 0);
+    uni.hideLoading();
+  } catch (err) {
+    uni.showToast({
+      title: err?.msg || "下单失败,请稍后重试",
+      icon: "none",
+    });
+    setTimeout(() => {
+      uni.redirectTo({
+        url: "/pages/users/vault/recycle/recyle_order",
+      });
+    }, 1000);
+  }
+};
+
+// 取消订单
+const cancelOrder = async () => {
+  try {
+    const res = await recycleCancelAPI({ orderNo: orderInfo.value.orderNo });
+  } catch (err) {
+    console.error("取消订单失败:", err);
+  }
+};
+
+// 订单过期处理
+const timeupHandle = () => {
+  uni.showToast({
+    title: "订单已过期",
+    duration: 1000,
+    icon: "none",
+  });
+
+  // 取消订单
+  cancelOrder();
+
+  setTimeout(() => {
+    uni.reLaunch({
+      url: "/pages/users/vault/recycle/recyle_order",
+    });
+  }, 1000);
+};
+
+// 计算时间差(分钟)
+const getTimeDiff = (targetTimeStr) => {
+  if (!targetTimeStr) return 0;
+
+  const targetTime = new Date(targetTimeStr).getTime();
+  const currentTime = Date.now();
+  const diffMs = targetTime - currentTime;
+  const diffMinutes = diffMs / 60000;
+
+  return diffMinutes > 0 ? diffMinutes : 0;
+};
+</script>
+
+<style scoped lang="scss">
+.signContent {
+  background-color: #f8f8f8;
+  padding: 20px;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  border-radius: 20px 20px 0 0;
+  .scroll {
+    background-color: #fff;
+    padding: 4px;
+    height: 300px;
+    overflow-y: hidden;
+    border: 1px solid #dfdfdf;
+  }
+  .footer {
+    margin-top: 10px;
+    color: #fff;
+    padding: 4px 20px;
+    border-radius: 20px;
+    background: linear-gradient(to right, #8ed187, #5dd665);
+  }
+}
+.icon-iconfontscan {
+  font-size: 20px;
+  color: #f8c007;
+}
+.upload-box-contanier {
+  margin-top: 40rpx;
+  .upload-block {
+    width: 160rpx;
+    height: 160rpx;
+    border: 1px solid #ccc;
+    border-radius: 10rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    color: #ccc;
+    font-weight: 700;
+    font-size: 26rpx;
+  }
+}
+.sell-postend {
+  height: auto;
+  padding: 10rpx 30rpx;
+  position: relative;
+
+  .sell-postend-banner {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 50vh;
+    z-index: -1;
+    // background-image: linear-gradient(360deg, #ffffff 0%, #e8c279 100%);
+    background: $uni-bg-primary;
+  }
+
+  .sell-postend-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+
+    .withdraw-express {
+      width: 682rpx;
+      height: 306rpx;
+      background-color: #ffffff;
+      box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
+      border-radius: 20rpx;
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      justify-content: center;
+      box-sizing: border-box;
+      padding: 28rpx;
+      margin-top: 30rpx;
+
+      .item {
+        display: flex;
+        align-items: center;
+        margin-top: 15px;
+
+        .targe {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          color: #fff;
+          width: 35px;
+          height: 35px;
+          border-radius: 50%;
+          background-color: #f8c007;
+        }
+
+        .address {
+          width: 425rpx;
+          margin: 0 10px;
+          font-size: 28rpx;
+
+          .receive-address {
+            letter-spacing: 9px;
+            line-height: 2;
+          }
+        }
+
+        .end {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+
+          .copy {
+            color: #888888;
+            border-radius: 3px;
+            border: 1px solid #888888;
+            font-size: 24rpx;
+            padding: 0 23rpx;
+          }
+        }
+      }
+    }
+    .upload-box {
+      width: 682rpx;
+      height: auto;
+      background-color: #ffffff;
+      box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
+      border-radius: 20rpx;
+      box-sizing: border-box;
+      margin-top: 25rpx;
+      padding: 40rpx 40rpx;
+      .upload-tips {
+        font-size: 26rpx;
+        color: #000000;
+        margin: 30rpx 0;
+      }
+      .upload-img {
+        margin: 30rpx 0;
+      }
+      .pz-tips {
+        text-align: center;
+        font-size: 24rpx;
+        color: #7c7c7c;
+        margin: 40rpx 0;
+      }
+      .pz-img {
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        margin: 30rpx 0;
+        image {
+          width: 138rpx;
+          height: 138rpx;
+          background-color: #f3f3f3;
+          border-radius: 10rpx;
+          margin: 15rpx 0;
+        }
+      }
+    }
+    .postend-info-box {
+      padding: 40rpx 40rpx;
+      margin-top: 25rpx;
+      width: 682rpx;
+      // height: 527rpx;
+      background-color: #ffffff;
+      box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
+      border-radius: 20rpx;
+      box-sizing: border-box;
+      .postend-info {
+        margin-top: 30rpx;
+        .info-item {
+          display: flex;
+          align-items: center;
+          margin: 25rpx 0;
+          .info-item-left {
+            font-size: 26rpx;
+            color: #000000;
+          }
+
+          .info-item-right {
+            margin-left: 15rpx;
+            width: 470rpx;
+            height: 77rpx;
+            background-color: #f3f3f3;
+            border-radius: 10rpx;
+            display: flex;
+            // justify-content: space-around;
+            align-items: center;
+            input {
+              width: 86%;
+              font-size: 26rpx;
+              color: #000;
+              padding-left: 15rpx;
+            }
+          }
+        }
+        .info-agree {
+          display: flex;
+          justify-content: center;
+          margin-top: 25rpx;
+          align-items: center;
+        }
+      }
+    }
+    .recyle-price {
+      display: flex;
+      justify-content: center;
+      font-size: 26rpx;
+      color: #000000;
+      margin: 30rpx 0;
+      .recyle-price-num {
+        // color: #7c7c7c;
+        color: $txt-color;
+        margin-left: 15rpx;
+      }
+    }
+    .btn-box {
+      display: flex;
+      justify-content: center;
+      .postend-btn {
+        // width: 330rpx;
+        width: 267rpx;
+
+        height: 71rpx;
+        position: relative;
+        margin-top: 30rpx;
+        .btn {
+          width: 100%;
+          height: 100%;
+        }
+        .btn-text {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          color: #000;
+          font-size: 26rpx;
+        }
+      }
+      .postend-cbtn {
+        margin-right: 30rpx;
+        // width: 330rpx;
+        padding: 0rpx 60rpx;
+        height: 71rpx;
+        background: #cecece;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        color: #636363;
+        border-radius: 30rpx;
+        margin-top: 30rpx;
+      }
+    }
+    .white {
+      height: 100rpx;
+    }
+    .header {
+      width: 100%;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .header-left {
+        display: flex;
+        align-items: center;
+        .line {
+          width: 4rpx;
+          height: 28rpx;
+          background-color: #cc9933;
+        }
+        .title {
+          margin-left: 5rpx;
+          font-weight: bold;
+        }
+        .tips {
+          font-size: 26rpx;
+          color: #7c7c7c;
+        }
+      }
+      .header-right {
+        font-size: 26rpx;
+        color: #7c7c7c;
+      }
+    }
+  }
+}
+</style>

+ 416 - 0
pages/users/vault/recycle/recyle_order.vue

@@ -0,0 +1,416 @@
+<template>
+  <view class="list-page">
+    <view class="tabs-box">
+      <up-tabs :list="list" @click="tabsChange" lineColor="#f8c20f"></up-tabs>
+    </view>
+    <view v-if="orderList.length === 0" class="empty">
+      <image
+        style="width: 60%"
+        src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/common/empty.png"
+        mode="widthFix"
+      ></image>
+      <text>暂无订单~</text>
+    </view>
+    <view v-else class="inner">
+      <view
+        v-for="(item, index) in orderList"
+        :key="index"
+        class="block"
+        @click="nativeTo(item)"
+      >
+        <view class="header">
+          <view class="title">订单号:{{ item.orderNo }}</view>
+          <view class="tag" :class="['status' + item.status]">
+            {{ getOrderType(item.status) }}
+          </view>
+        </view>
+        <view class="detail">
+          <image
+            @click="previewImage(item.imageList)"
+            style="width: 50px; height: 50px; border-radius: 6px"
+            :src="item.imageList[0] || emptyImg"
+            mode="scaleToFill"
+          ></image>
+          <view class="info">
+            <view>{{ item.metalTypeDesc }}</view>
+            <view class="right">
+              <view>
+                金价:
+                <span class="weight">{{ item.goldPricePerGram }}/g</span>
+              </view>
+              <view>
+                自估重量:
+                <span class="weight">{{ item.estimatedWeight }}克</span>
+              </view>
+            </view>
+          </view>
+        </view>
+        <view class="end">
+          <view class="desc">
+            <view class="">下单时间:{{ item.createTime }}</view>
+            <uni-countdown
+              v-if="item.status == 1"
+              :minute="item.countdown || 0"
+              color="#ff0000"
+              @timeup="timeupHandle(item)"
+            ></uni-countdown>
+          </view>
+        </view>
+        <view v-if="item.status == 1" class="footer"
+          >*请尽快邮寄,倒计时结束后未寄出系统将自动取消订单。</view
+        >
+      </view>
+    </view>
+    <!-- 下拉加载必须代码块 -->
+    <!-- <tm-loadding v-if="loading" style="padding-bottom: 15px"></tm-loadding>
+    <tm-loadding
+      v-if="!loading && !hasMore"
+      success
+      icon="icon-times-circle-fill"
+      label="没有更多啦"
+      color="mymain"
+      style="padding-bottom: 15px"
+    ></tm-loadding> -->
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import { recycleListAPI } from "@/api/functions";
+import { useAppStore } from "@/stores/app";
+import {
+  onLoad,
+  onPullDownRefresh,
+  onShow,
+  onReachBottom,
+} from "@dcloudio/uni-app";
+const appStore = useAppStore();
+// 定义数据
+const list = ref([
+  { name: "全部", status: "" },
+  { name: "待寄出", status: 1 },
+  { name: "待检测", status: 3 },
+  { name: "待确认", status: 4 },
+  { name: "已完成", status: 7 },
+]);
+
+const orderList = ref([]);
+const orderAll = ref([]);
+const emptyImg = ref(
+  "https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/recycle/example/example1.png"
+);
+
+// 计算属性
+const pageTop = computed(() => {
+  // return vk.getVuex("$app.totalBarHeight");
+  return 0; // 占位,实际项目中替换为正确的顶部高度计算
+});
+const total = ref(0);
+const hasMore = ref(true);
+const loading = ref(false);
+const params = ref({
+  limit: 10,
+  page: 1,
+  status: "",
+  userId: appStore.userInfo.userId,
+});
+// 初始化
+const init = async (isRefresh = false) => {
+  if (loading.value) return;
+  loading.value = true;
+  uni.showLoading({
+    title: "加载中",
+  });
+  try {
+    // 模拟接口调用
+    const res = await recycleListAPI(params.value);
+    console.log(res);
+
+    const newList = res.data.list?.map((item) => {
+      return {
+        ...item,
+        countdown: getTimeDiff(item.expirationTime),
+        imageList: item.images ? JSON.parse(item.images) : [],
+      };
+    });
+    if (isRefresh) {
+      orderList.value = newList;
+    } else {
+      if (params.value.page === 1) {
+        orderList.value = newList;
+      } else {
+        orderList.value = [...orderList.value, ...newList];
+      }
+    }
+    total.value = res.data.total;
+    hasMore.value = orderList.value.length > total.value;
+    uni.hideLoading();
+  } catch (error) {
+    console.error("获取订单列表失败:", error);
+    uni.showToast({
+      title: "加载失败",
+      icon: "none",
+    });
+  } finally {
+    loading.value = false;
+    // 停止下拉刷新动画
+    if (isRefresh) {
+      uni.stopPullDownRefresh();
+    }
+  }
+};
+
+// 计算目标时间与当前时间的差值(返回分钟数)
+const getTimeDiff = (targetTimeStr) => {
+  const targetTime = new Date(targetTimeStr).getTime();
+  const currentTime = Date.now();
+  const diffMs = targetTime - currentTime;
+  const diffMinutes = diffMs / 60000;
+  return diffMinutes > 0 ? diffMinutes : 0;
+};
+// 时间到结束
+const timeupHandle = (item) => {
+  item.status = 0;
+};
+
+// 标签页切换
+const tabsChange = (item) => {
+  params.value.status = item.status;
+  params.value.page = 1;
+  init();
+};
+
+// 跳转到详情页
+const nativeTo = (item) => {
+  console.log(item);
+  if (item.status == 1) {
+    uni.navigateTo({
+      url: `/pages/users/vault/recycle/order_fill?order=${JSON.stringify(
+        item
+      )}`,
+      // url: "/pages/users/vault/recycle/report",
+    });
+  } else if (item.status == 4 || item.status == 7) {
+    uni.navigateTo({
+      url: `/pages/users/vault/recycle/report?orderInfo=${encodeURIComponent(
+        JSON.stringify(item)
+      )}`,
+    });
+  }
+};
+
+// 计算总重量
+const totalWeight = (order) => {
+  let totalWeight = 0;
+  for (const key in order.cart) {
+    if (Object.hasOwnProperty.call(order.cart, key)) {
+      const item = order.cart[key];
+      totalWeight += Number(item.weight) || 0;
+    }
+  }
+  return totalWeight.toFixed(2);
+};
+
+// 获取商品类别
+const getCate = (cate) => {
+  switch (cate) {
+    case "au":
+      return "黄金";
+    case "ag":
+      return "白银";
+    case "pt":
+      return "铂金";
+    case "kau":
+      return "K金";
+    default:
+      return "其他";
+  }
+};
+
+// 获取订单状态文本
+const getOrderType = (status) => {
+  switch (status) {
+    case 0:
+      return "已取消";
+    case 1:
+      return "待寄出";
+    case 2:
+      return "等待签收";
+    case 3:
+      return "等待检测";
+    case 4:
+      return "确认报告";
+    case 5:
+      return "确认总价";
+    case 6:
+      return "正在打款";
+    case 7:
+      return "交易完成";
+    default:
+      return "未知状态";
+  }
+};
+
+// 生命周期钩子
+onShow(() => {
+  params.value.page = 1;
+  init();
+});
+
+// 下拉刷新
+onPullDownRefresh(() => {});
+
+// 上拉加载更多
+onReachBottom(() => {
+  // 如果没有更多数据或正在加载中,则不执行
+  if (!hasMore.value || loading.value) return;
+
+  // 增加页码
+  params.value.page++;
+  // 加载下一页数据
+  init();
+});
+const previewImage = (urls) => {
+  console.log(urls[0]);
+
+  uni.previewImage({
+    current: urls[0], // 当前显示图片的URL
+    urls: urls, // 需要预览的图片URL列表
+    success: () => {},
+    fail: (err) => {
+      console.error("预览图片失败:", err);
+    },
+  });
+};
+</script>
+
+<style>
+.empty {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  color: #ffffff;
+}
+page {
+  height: 100%;
+  background-color: #f8f8f8;
+}
+</style>
+
+<style scoped lang="scss">
+.list-page {
+  background: $uni-bg-primary !important;
+  min-height: 100vh;
+}
+
+.tabs-box {
+  width: 100%;
+  height: 75rpx;
+  // background: #ffffff;
+  box-sizing: border-box;
+  ::v-deep .u-tabs__wrapper__nav__item {
+    padding: 0 37rpx;
+  }
+}
+.tabs {
+  z-index: 100;
+  position: sticky;
+}
+.inner {
+  padding: 10px;
+  .footer {
+    border-radius: 5px;
+    border: 0 0 10px 20px;
+    color: #707070;
+    font-size: 12px;
+    padding: 10px;
+    background-color: rgb(252, 247, 230);
+  }
+}
+.block {
+  margin-bottom: 10px;
+  padding-top: 10px;
+  border-radius: 5px;
+  background-color: #fff;
+  border: 1px solid #cecece;
+  .header {
+    padding: 0 10px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #eee;
+    .tag {
+      font-size: 12px;
+      padding: 2px 5px;
+      border-radius: 4px;
+      color: #ff0800;
+      background-color: #ffcece;
+      &.status-1 {
+        color: #555;
+        background-color: #eeeeee;
+      }
+      &.status1 {
+        color: #ff9900;
+        background-color: #ffeccf;
+      }
+      &.status2 {
+        color: #d6006b;
+        background-color: #fdd3e9;
+      }
+      &.status3 {
+        color: #9900ff;
+        background-color: #e2b7ff;
+      }
+      &.status4,
+      &.status5 {
+        color: rgb(48, 24, 136);
+        background-color: #bbb0fa;
+      }
+      &.status6 {
+        color: #3dac27;
+        background-color: #c8ffb7;
+      }
+    }
+    .title {
+      font-size: 14px;
+    }
+  }
+
+  .detail {
+    display: flex;
+    padding: 10px 13px;
+    .info {
+      flex: 1;
+      width: 100%;
+      font-size: 14px;
+      margin-left: 10px;
+      color: #999;
+
+      .right {
+        min-width: 120px;
+        // display: flex;
+        // justify-content: space-between;
+        .weight {
+          // width: 100%;
+          color: #daa520;
+        }
+      }
+    }
+  }
+  .end {
+    padding: 10px 10px;
+    color: #999;
+    font-size: 14px;
+
+    .desc {
+      margin-bottom: 6px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+    }
+  }
+}
+</style>

+ 398 - 0
pages/users/vault/recycle/report.vue

@@ -0,0 +1,398 @@
+<template>
+  <view class="">
+    <view class="banner">
+      <view class="step-box">
+        <up-steps :current="orderStatus" activeColor="#b5aa90">
+          <up-steps-item
+            :title="item.name"
+            v-for="item in stepData"
+            :key="item.name"
+          >
+          </up-steps-item>
+        </up-steps>
+      </view>
+
+      <view class="block">
+        <view class="check">
+          <view class="header">
+            <span class="title">合后检测确认单</span>
+          </view>
+          <view class="desc"
+            >根据双方认可的检测内容及要求进行认定,为客户合后检测认定结果如下:</view
+          >
+          <view class="table">
+            <view class="title">客户寄送品</view>
+            <view class="tab_header">
+              <up-row>
+                <up-col :span="2">
+                  <view class="col">类型</view>
+                </up-col>
+                <up-col :span="2">
+                  <view class="col">来样重</view>
+                </up-col>
+                <up-col :span="2.5">
+                  <view class="col">实际重</view>
+                </up-col>
+
+                <up-col :span="2.5">
+                  <view class="col">成色</view>
+                </up-col>
+                <up-col :span="3">
+                  <view class="col">存入克重</view>
+                </up-col>
+              </up-row>
+            </view>
+            <view class="tab_content">
+              <view>
+                <up-row v-for="item in detectionDetails" :key="item.id">
+                  <up-col :span="2">
+                    <view class="col">{{ getCate(item.type) }}</view>
+                  </up-col>
+                  <up-col :span="2">
+                    <view class="col">{{ item.userEstimatedWeight }}g</view>
+                  </up-col>
+                  <up-col :span="2.5">
+                    <view class="col">{{ item.discountedWeight }}g</view>
+                  </up-col>
+                  <up-col :span="2.5">
+                    <view class="col">{{ item.purity }}%</view>
+                  </up-col>
+                  <up-col :span="3">
+                    <view class="col"
+                      >{{
+                        item.unloadingWeight > 0
+                          ? item.unloadingWeight
+                          : "0.00" || "0.00"
+                      }}g</view
+                    >
+                  </up-col>
+                </up-row>
+              </view>
+            </view>
+          </view>
+          <view class="end">
+            <view class="end-bttom">
+              <span class="total">
+                总计:
+                <span class="price"
+                  >{{ detectionReport.totalDiscountedWeight }}g</span
+                >
+                回收价:
+                <span class="price">¥{{ detectionReport.totalPrice }}</span>
+              </span>
+            </view>
+            <view
+              class="end-bttom"
+              style="margin-top: 10rpx"
+              v-if="detectionReport.totalDeductedDeposit"
+            >
+              <span class="total">
+                扣除保证金:
+                <span class="price"
+                  >¥{{ detectionReport.totalDeductedDeposit }}</span
+                >
+              </span>
+            </view>
+            <view class="end-bttom" style="margin-top: 10rpx">
+              <span class="total">
+                快递费用:
+                <span class="price">¥{{ deliveryCosts }}</span>
+              </span>
+            </view>
+            <view class="extra"
+              >即:客户对{{
+                detectionReport.inspectionTime
+              }}合后检测重量和回收价无异议</view
+            >
+            <view class="sign">
+              <span>检测人:{{ detectionReport.nikeName || "系统" }}</span>
+            </view>
+            <view class="time">
+              {{ detectionReport.inspectionTime }}
+            </view>
+          </view>
+          <view style="font-size: 14px; padding: 10px">
+            <span style="font-size: 16px; font-weight: 700">注:</span>
+            <span>折算公式:兑料重量 = 折足重量 = 来料熔后重量 * 折足成色</span>
+          </view>
+        </view>
+      </view>
+      <view class="block">
+        <view class="check">
+          <view class="header">
+            <span class="title">检测图片</span>
+          </view>
+          <view class="checlImage">
+            <cl-upload
+              v-model="detectionImages"
+              :add="false"
+              :remove="false"
+            ></cl-upload>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view style="height: 70px"></view>
+    <view class="footer" v-if="orderStatus == 4">
+      <view class="btn" @click="next()">确认报告</view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { recyclDetectionReportAPI } from "@/api/functions";
+const stepData = ref([
+  {
+    name: "待填写",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "待邮寄",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "待签收",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "待检测",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "待确认",
+    isNow: 1,
+    type: 0,
+  },
+
+  {
+    name: "待打款",
+    isNow: 0,
+    type: 0,
+  },
+  {
+    name: "完成",
+    isNow: 0,
+    type: 0,
+  },
+]);
+// 注意:onLoad是uni-app的生命周期,如果是纯Vue3,应使用onMounted
+
+// 替代Vue2的methods中的方法
+const getCate = (cate) => {
+  console.log(cate);
+
+  switch (cate) {
+    case 1:
+      return "黄金";
+    case 3:
+      return "白银";
+    case 2:
+      return "铂金";
+    case 4:
+      return "K金";
+  }
+};
+// 检测信息-成色类型等
+const detectionDetails = ref([]);
+// 检测详情
+const detectionReport = ref({});
+// 检测图片
+const detectionImages = ref([]);
+// 订单号
+const orderNo = ref("");
+// 订单状态
+const orderStatus = ref(4);
+// 快递费用
+const deliveryCosts = ref(0);
+onLoad((options) => {
+  if (options.orderInfo) {
+    // 先解码再解析
+    const orderInfoStr = decodeURIComponent(options.orderInfo);
+    const res = JSON.parse(orderInfoStr);
+    detectionDetails.value = res.detectionDetails;
+
+    detectionReport.value = res.detectionReport;
+    detectionImages.value = JSON.parse(res.detectionReport.detectionImages);
+    orderNo.value = res.id;
+    orderStatus.value = res.status;
+    deliveryCosts.value = res.deliveryCosts;
+  }
+});
+// 确认报告
+const next = async () => {
+  uni.showLoading({
+    title: "确认中",
+  });
+  const res = await recyclDetectionReportAPI(orderNo.value);
+  uni.hideLoading();
+  setTimeout(() => {
+    uni.redirectTo({
+      url: "/pages/users/vault/recycle/recyle_order",
+    });
+  }, 1000);
+};
+</script>
+
+<style scoped lang="scss">
+.banner {
+  padding: 10px 15px;
+  background: $uni-bg-primary; /* fallback for old browsers */
+
+  .step-box {
+    margin: 30rpx 0;
+    width: 100%;
+    background: #fff;
+    height: 150rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    border-radius: 4px;
+
+    &::v-deep .u-text__value {
+      font-size: 24rpx !important;
+    }
+  }
+}
+
+.block {
+  margin-bottom: 10px;
+
+  .check {
+    background-color: #fff;
+    border-radius: 4px;
+    padding-top: 10px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  }
+  .header {
+    font-size: 14px;
+    font-weight: 700;
+    padding-left: 10px;
+    margin-left: 10px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    &::before {
+      position: absolute; /*绝对定位*/
+      top: 50%; /*Y轴方向偏移自身高度的50%*/
+      transform: translatey(-40%); /*Y轴方向偏移微调*/
+      left: 0px; /*紧靠容器左边缘*/
+      content: ""; /*伪元素需要有内容才能显示*/
+      width: 2px; /*伪元素宽度*/
+      height: 18px; /*伪元素高度*/
+      background-color: #daa520; /*伪元素颜色*/
+    }
+    .title {
+      font-size: 18px;
+    }
+  }
+  .desc {
+    padding: 8px 10px;
+    color: #888;
+    font-size: 14px;
+  }
+}
+.table {
+  padding: 14px 10px;
+  .title {
+    text-align: center;
+    padding: 4px 0;
+    color: #fff;
+    font-weight: 700;
+    font-size: 20rpx;
+    width: 100%;
+    background-color: #b5aa90;
+  }
+  .tab_header {
+    border-left: 1px solid #b5aa90;
+    font-size: 18px;
+    background-color: #fff;
+    .col {
+      padding: 12px 0;
+      line-height: 19px;
+      font-size: 22rpx;
+    }
+  }
+  .tab_content {
+    border-left: 1px solid #b5aa90;
+    font-size: 16px;
+    background-color: #fff;
+    .col {
+      padding: 12px 0;
+      line-height: 19px;
+      font-size: 22rpx;
+    }
+  }
+  .tab_header .col,
+  .tab_content .col {
+    border-bottom: 1px solid #b5aa90;
+    border-right: 1px solid #b5aa90;
+    text-align: center;
+    font-size: 22rpx;
+  }
+  .symbol-name {
+    font-family: Microsoft YaHei, Arial, Helvetica, sans-serif;
+    font-size: 20px;
+    color: #ffbf24;
+  }
+}
+.end {
+  width: 100%;
+  display: inline-block;
+  text-align: right;
+  .end-bttom {
+    color: #888;
+    .total {
+      font-size: 16px;
+    }
+    .price {
+      margin-right: 10px;
+      color: #ffa034;
+    }
+  }
+  .extra {
+    font-size: 14px;
+    margin-top: 15px;
+    color: #888;
+    margin-right: 10px;
+  }
+  .sign {
+    margin: 10px;
+  }
+  .time {
+    padding: 10px;
+  }
+}
+
+.checlImage {
+  padding: 10px;
+}
+.footer {
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
+  position: fixed;
+  width: 100%;
+  border-radius: 8px 8px 0 0;
+  background-color: #fff;
+  bottom: 0;
+  height: 60px;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  .btn {
+    color: #fff;
+    font-weight: 700;
+    margin: 0 15px;
+    border-radius: 10px;
+    width: 100px;
+    padding: 10px;
+    text-align: center;
+    background-color: #cc9933;
+  }
+}
+</style>

+ 0 - 0
pages/users/vault/saveGoldAgreement.vue


+ 671 - 0
pages/users/vault/save_gold.vue

@@ -0,0 +1,671 @@
+<template>
+  <view class="deposit-page">
+    <view class="top-tabs-container">
+      <view
+        class="tab-item"
+        :class="{ active: activeTab === 'mail' }"
+        @click="activeTab = 'mail'"
+      >
+        邮寄存金
+      </view>
+      <!-- <view
+        class="tab-item"
+        :class="{ active: activeTab === 'warehouse' }"
+        @click="activeTab = 'warehouse'"
+      >
+        无物流存金
+      </view> -->
+    </view>
+
+    <view class="content-body" v-if="activeTab === 'mail'">
+      <view class="card-section">
+        <view class="section-title">自主邮寄</view>
+        <view class="address-box">
+          <view class="icon-location">收</view>
+          <view class="address-info">
+            <view class="info-line"
+              >{{ appStore.$wxConfig.mailerName || "回收部" }} |
+              {{ contact.phone }}</view
+            >
+            <view class="info-line">地址 | {{ contact.address }}</view>
+          </view>
+          <view class="copy-btn" @click="copyAddress">复制</view>
+        </view>
+      </view>
+
+      <view class="card-section">
+        <view class="section-title">快递单号</view>
+        <view class="input-row">
+          <up-input
+            type="text"
+            v-model="formData.trackingNumber"
+            placeholder="请输入正确的快递单号"
+            placeholder-class="placeholder"
+            border="none"
+          />
+          <image class="icon-scan" src="/static/images/scan-icon.png" />
+        </view>
+      </view>
+
+      <view class="card-section">
+        <view class="section-title">快递图片</view>
+        <view class="upload-box">
+          <up-upload
+            :fileList="imageList"
+            @afterRead="afterRead"
+            @delete="deletePic"
+            name="1"
+            multiple
+            :maxCount="1"
+          >
+            <template #trigger>
+              <view class="upload-block">
+                <uni-icons size="38" color="#ccc" type="plusempty"></uni-icons>
+              </view>
+            </template>
+          </up-upload>
+        </view>
+      </view>
+
+      <view class="card-section">
+        <!-- <template  v-for="metal in metalInputs" > -->
+        <view class="input-row with-arrow">
+          <text class="input-label">{{ metalTypeText }}克重</text>
+          <up-input
+            type="digit"
+            v-model="formData.weight"
+            placeholder="请输入"
+            placeholder-class="placeholder"
+            class="text-right"
+            border="none"
+          />
+          <text class="arrow">g</text>
+        </view>
+        <!-- </template> -->
+      </view>
+
+      <view class="submit-section">
+        <button class="submit-btn" @click="handleSubmit">立即提交</button>
+        <!-- 协议选择 -->
+        <view
+          class="agreement-section"
+          style="display: flex; align-items: center"
+        >
+          <up-checkbox
+            v-model:checked="agreedToTerms"
+            shape="square"
+            activeColor="#e9c279"
+            usedAlone
+            :customStyle="{ marginRight: '12rpx' }"
+          >
+            <template #label>
+              <text class="agreement-text"> 阅读并同意 </text>
+            </template>
+          </up-checkbox>
+          <view class="agreement-link" @click="showAgreement"
+            >《存金协议》</view
+          >
+        </view>
+      </view>
+      <!-- 协议弹窗 -->
+      <up-popup
+        :show="showAgreementPopup"
+        mode="center"
+        border-radius="20"
+        width="80%"
+        height="60%"
+      >
+        <view class="popup-content">
+          <view class="popup-header"></view>
+          <scroll-view scroll-y scroll-left="50" class="popup-body">
+            <view class="agreement-content">
+              <Aggrement />
+            </view>
+          </scroll-view>
+          <view class="popup-footer">
+            <!-- <button class="popup-btn" @click="closeAgreement">
+              我已详细知悉
+            </button> -->
+            <up-button
+              class="popup-btn"
+              text="我已详细知悉"
+              @click="closeAgreement"
+            ></up-button>
+          </view>
+        </view>
+      </up-popup>
+
+      <view class="info-image-section">
+        <view class="item-image">
+          <!-- <image
+            style="width: 100%"
+            mode="widthFix"
+            src="/static//images/formula.png"
+          /> -->
+        </view>
+        <view class="tip-box">
+          <view class="title">温馨提示</view>
+          <view class="tip-content"
+            >※寄出快递前,客户留底打包视频及快递单号,以便工作人员签收快递核验※</view
+          >
+        </view>
+        <view class="item-image">
+          <!-- <image
+            style="width: 100%"
+            mode="widthFix"
+            src="/static/images/gold_table.png"
+          /> -->
+        </view>
+        <view class="text-section">
+          <view class="paragraph"
+            >1、下午三点前收到寄回包裹当天处理,三点之后统一隔天处理(48小时内打款)</view
+          >
+          <view class="paragraph"
+            >2、如有塑料膜,珐琅、烤漆,镶嵌等辅料,熔烧会产生损耗,按实际熔烧后成色、克重回收;</view
+          >
+          <view class="paragraph"
+            >3、任意时间可提交订单,客服在线时间为早10点~晚8点;</view
+          >
+          <view class="paragraph"
+            >4、如用户担心货品运输风险,建议您保价邮寄。(更多咨询请联系在线客服);</view
+          >
+        </view>
+        <view class="item-image qr_code">
+          <!-- <image
+            style="width: 100%"
+            mode="widthFix"
+            src="/static/images/tpy_qr_code.jpg"
+          /> -->
+        </view>
+        <view class="text-section">
+          <view class="paragraph"
+            >5、黄金到账时间:通常情况下,在线上平台确认金重后,黄金会实时存入用户贵金属账户中;</view
+          >
+          <view class="paragraph">6、线上存金,需选择自主邮寄;</view>
+          <view class="paragraph"
+            >7、足金,K金一定要分开装袋,如熔完后出现因足金K金未分开而造成的成色不足,则需客户自己承担。</view
+          >
+        </view>
+        <view class="alert-text">
+          <view class="paragraph">注:周日休息,请错开时间!</view>
+          <view class="paragraph"
+            >客服热线:19925434991 客服微信:19925434991</view
+          >
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+import { useImageUpload } from "@/hooks/useImageUpload";
+import Aggrement from "./aggrement.vue";
+import { liveExchange } from "@/api/vault";
+import { useToast } from "@/hooks/useToast";
+
+// 当前激活的Tab
+const activeTab = ref("mail");
+const { Toast } = useToast();
+
+const appStore = useAppStore();
+
+const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
+  pid: 9,
+  model: "gold",
+});
+
+// 联系人信息 - 从 appStore 获取
+const contact = computed(() => ({
+  phone: appStore.$wxConfig.mailerPhone,
+  address: appStore.$wxConfig.mailingAddress,
+}));
+
+// 表单数据
+const formData = reactive({
+  trackingNumber: "", // 订单号
+  imageUrl: "", // 上传的图片URL
+  weight: "",
+  weights: {
+    gold: "",
+    kGold: "",
+    platinum: "",
+    silver: "",
+  },
+  agreeToTerms: false,
+});
+
+// 金属输入项配置
+const metalInputs = ref([
+  { label: "黄金金重", key: "gold", metalType: 1 },
+  { label: "铂金金重", key: "kGold", metalType: 2 },
+  { label: "白银金重", key: "platinum", metalType: 3 },
+  // { label: "白银金重", key: "silver", metalType: 4 },
+]);
+
+//metalType  金属类型(1黄金、2铂金、3白银···)
+const metalType = ref(1);
+const metalTypeText = computed(() => {
+  return metalType.value == 1
+    ? "黄金"
+    : metalType.value == 2
+    ? "铂金"
+    : metalType.value == 3
+    ? "白银"
+    : "";
+});
+
+// 复制地址方法
+const copyAddress = () => {
+  const fullText = `${appStore.$wxConfig.mailerName || "回收部"}:${
+    contact.phone
+  } 地址: ${contact.address}`;
+  uni.setClipboardData({
+    data: fullText,
+    success: () => {
+      Toast({
+        title: "地址已复制",
+        icon: "none",
+      });
+    },
+  });
+};
+
+// 协议弹窗相关逻辑
+const showAgreementPopup = ref(false);
+const showAgreement = () => {
+  showAgreementPopup.value = true;
+};
+const closeAgreement = () => {
+  showAgreementPopup.value = false;
+};
+
+// 协议勾选状态
+const agreedToTerms = ref(false);
+
+onLoad((p) => {
+  metalType.value = p.type;
+});
+// 提交表单方法
+const handleSubmit = async () => {
+  if (!agreedToTerms.value) {
+    Toast({
+      title: "请先阅读并同意存金协议",
+      icon: "none",
+    });
+    return;
+  }
+  if (!formData.trackingNumber) {
+    Toast({
+      title: "请填写快递单号",
+      icon: "none",
+    });
+    return;
+  }
+  if (!formData.weight || Number(formData.weight) <= 0) {
+    Toast({
+      title: `请填写${metalTypeText.value}金重`,
+      icon: "none",
+    });
+    return;
+  }
+  if (!imageList.value || imageList.value.length === 0) {
+    Toast({
+      title: "请上传快递图片",
+      icon: "none",
+    });
+    return;
+  }
+  if (uploadLoading.value) {
+    Toast({
+      title: "正在上传图片",
+      icon: "none",
+    });
+    return;
+  }
+  try {
+    uni.showLoading({
+      title: "提交中...",
+    });
+    const params = {
+      userId: appStore.uid,
+      operationType: 1, // 1 - 存金
+      orderNo: formData.trackingNumber,
+      weight: formData.weight,
+      metalType: metalType.value,
+      price: 0,
+      type: 1,
+      imageUrl: imageList.value[0].info.url,
+    };
+    await liveExchange(params);
+    setTimeout(() => {
+      uni.navigateBack();
+    }, 500);
+    Toast({ title: "提交成功" });
+  } catch (error) {
+    console.error("liveExchange", error);
+    const title = typeof error === "string" ? error : "提交失败";
+    Toast({ title });
+  } finally {
+    uni.hideLoading();
+  }
+};
+</script>
+
+<style lang="scss">
+// 页面和主题变量
+$page-bg-color: #f7f7f7;
+$theme-color: #e9c279;
+$text-primary: #333;
+$text-secondary: #999;
+$white: #ffffff;
+$border-color: #f0f0f0;
+
+page {
+  background-color: $page-bg-color;
+  color: $text-primary;
+}
+
+.deposit-page {
+  padding-bottom: 40rpx;
+}
+
+// 顶部 Tabs
+.top-tabs-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 30rpx 0;
+  background-color: $white;
+  gap: 80rpx;
+  border-bottom: 1px solid $border-color;
+
+  .tab-item {
+    font-size: 30rpx;
+    color: $text-secondary;
+    padding-bottom: 10rpx;
+    border-bottom: 2px solid transparent;
+    transition: all 0.3s ease;
+
+    &.active {
+      font-weight: bold;
+      color: $theme-color;
+      border-bottom-color: $theme-color;
+    }
+  }
+}
+
+.content-body {
+  padding: 30rpx;
+}
+
+// 卡片样式
+.card-section {
+  background-color: $white;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 24rpx;
+
+  .section-title {
+    font-size: 30rpx;
+    line-height: 30rpx;
+    font-weight: bold;
+    margin-bottom: 20rpx;
+    position: relative;
+    padding-left: 20rpx;
+    &::before {
+      content: "";
+      width: 4rpx;
+      height: 100%;
+      background: #daa520;
+      position: absolute;
+      left: 0;
+      top: 0;
+    }
+  }
+}
+
+.upload-box {
+  border-top: 1rpx solid #ccc;
+  padding: 50rpx 0 0;
+  ::v-deep .u-upload__wrap__preview__image {
+    width: 143rpx !important;
+    height: 143rpx !important;
+  }
+  .upload-block {
+    width: 150rpx;
+    height: 150rpx;
+    border: 1px solid #ccc;
+    border-radius: 10rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    color: #ccc;
+    font-weight: 700;
+    font-size: 26rpx;
+  }
+}
+
+// 地址框
+.address-box {
+  display: flex;
+  align-items: center;
+  background-color: #fcf9f3;
+  padding: 20rpx;
+  border-radius: 12rpx;
+
+  .icon-location {
+    width: 70rpx;
+    height: 70rpx;
+    line-height: 70rpx;
+    color: #fff;
+    text-align: center;
+    background-color: #cc9933;
+    border-radius: 50rpx;
+    margin-right: 20rpx;
+    flex-shrink: 0;
+  }
+
+  .address-info {
+    flex-grow: 1;
+    font-size: 26rpx;
+    line-height: 1.6;
+  }
+
+  .copy-btn {
+    flex-shrink: 0;
+    margin-left: 20rpx;
+    padding: 10rpx 24rpx;
+    border: 1px solid $theme-color;
+    color: $theme-color;
+    border-radius: 30rpx;
+    font-size: 24rpx;
+  }
+}
+
+// 输入行
+.input-row {
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid $border-color;
+  padding: 20rpx 0;
+
+  .placeholder {
+    color: #cccccc;
+  }
+
+  input {
+    flex-grow: 1;
+    font-size: 28rpx;
+  }
+
+  .icon-scan {
+    width: 40rpx;
+    height: 40rpx;
+    margin-left: 20rpx;
+  }
+
+  .input-label {
+    font-size: 28rpx;
+    color: $text-primary;
+    margin-right: 15rpx;
+  }
+
+  .text-right {
+    text-align: right;
+  }
+
+  &.with-arrow {
+    .arrow {
+      font-size: 28rpx;
+      color: $header-color;
+      margin-left: 10rpx;
+    }
+  }
+
+  // &:last-child {
+  //   border-bottom: none;
+  // }
+}
+
+// 图片上传
+.image-uploader {
+  width: 160rpx;
+  height: 160rpx;
+  border: 2rpx dashed $text-secondary;
+  border-radius: 12rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: $text-secondary;
+  font-size: 60rpx;
+  overflow: hidden;
+
+  .preview-image {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+// 提交区域
+.submit-section {
+  margin-top: 40rpx;
+
+  .submit-btn {
+    background-color: $theme-color;
+    color: $white;
+    border-radius: 50rpx;
+    font-size: 32rpx;
+    height: 90rpx;
+    line-height: 90rpx;
+    &:after {
+      border: none;
+    }
+  }
+
+  .agreement-box {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-top: 30rpx;
+    font-size: 24rpx;
+    color: $text-secondary;
+  }
+}
+
+.agreement-section {
+  margin: 30rpx 0 40rpx;
+  display: flex;
+  align-items: center;
+}
+
+.agreement-text {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.agreement-link {
+  color: #e9c279;
+  text-decoration: underline;
+  margin-left: 4rpx;
+}
+
+// 弹窗样式
+.popup-content {
+  width: 80vw;
+  padding: 40rpx 40rpx 0;
+  height: 80vh;
+  // display: flex;
+  // flex-direction: column;
+}
+
+.popup-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+
+  .popup-title {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: #333;
+  }
+
+  .popup-close {
+    font-size: 48rpx;
+    color: #666;
+    cursor: pointer;
+  }
+}
+
+.popup-footer {
+  margin-top: 30rpx;
+
+  .popup-btn {
+    width: 100%;
+    background: #e9c279;
+    color: white;
+    border: none;
+    border-radius: 12rpx;
+    padding: 30rpx 30rpx;
+    font-size: 32rpx;
+  }
+}
+
+.popup-body {
+  height: 85%;
+  // flex: 1;
+  // min-height: 0; // 关键,防止flex塌陷
+
+  .agreement-content {
+    font-size: 28rpx;
+    line-height: 1.6;
+    color: #666;
+  }
+}
+
+// 说明图片区域
+.info-image-section {
+  margin-top: 40rpx;
+  .tip-box {
+    .title {
+      color: #cc9933;
+    }
+  }
+  .paragraph {
+    color: #cc9933;
+  }
+  .qr_code {
+    width: 200rpx;
+  }
+  .alert-text {
+    margin: 20rpx 0 0;
+    .paragraph {
+      color: red;
+    }
+  }
+}
+</style>

+ 701 - 0
pages/users/vault/storeMetal/GoldMailForm.vue

@@ -0,0 +1,701 @@
+<template>
+  <view class="withdraw">
+    <view class="content">
+      <view class="withdraw-express">
+        <view class="header">
+          <span class="title">自主邮寄</span>
+        </view>
+        <view class="item">
+          <view class="targe">收</view>
+          <view class="address">
+            <view
+              >{{ appStore.$wxConfig.mailerName }} :{{
+                appStore.$wxConfig.mailerPhone
+              }}</view
+            >
+            <view class="address-detail" style="margin-top: 4px">
+              <text class="receive-address">地址:</text>
+              {{ appStore.$wxConfig.mailingAddress }}
+            </view>
+          </view>
+          <view class="end">
+            <view class="copy" @click="copyAddress">复制</view>
+          </view>
+        </view>
+      </view>
+      <view>
+        <view style="padding: 0rpx 10px">
+          <view class="gold-item">
+            <view class="header">
+              <span class="title">快递单号</span>
+            </view>
+            <view class="input-box">
+              <input
+                type="text"
+                class="inpu-box-ds"
+                placeholder="请输入快递单号"
+                v-model="expressNum"
+                placeholder-style="color: #999999; font-size: 28rpx;"
+              />
+              <text class="iconfont icon-iconfontscan" @click="scanCode"></text>
+            </view>
+          </view>
+
+          <view class="gold-item">
+            <view class="header">
+              <span class="title">快递图片</span>
+            </view>
+            <view style="margin-top: 10px">
+              <view class="upload-box">
+                <up-upload
+                  :fileList="imageList"
+                  uploadIcon="plus"
+                  @afterRead="afterRead"
+                  @delete="deletePic"
+                  name="1"
+                  multiple
+                  :maxCount="3"
+                >
+                  <template #trigger>
+                    <view class="upload-block">
+                      <uni-icons
+                        size="38"
+                        color="#ccc"
+                        type="plusempty"
+                      ></uni-icons>
+                    </view>
+                  </template>
+                </up-upload>
+              </view>
+            </view>
+          </view>
+
+          <view class="gold-item">
+            <view class="form-body">
+              <view class="weight-input">
+                <view class="input-label">黄金金重</view>
+                <view class="form-body-box">
+                  <input
+                    v-model="auExtract"
+                    type="digit"
+                    class="weight-value"
+                  />
+                  <text>g</text>
+                </view>
+              </view>
+              <view class="weight-input">
+                <view class="input-label">K金金重</view>
+                <view class="form-body-box">
+                  <input
+                    v-model="kauExtract"
+                    type="digit"
+                    class="weight-value"
+                  />
+                  <text>g</text>
+                </view>
+              </view>
+              <view class="weight-input">
+                <view class="input-label">铂金金重</view>
+                <view class="form-body-box">
+                  <input
+                    v-model="ptExtract"
+                    type="digit"
+                    class="weight-value"
+                  />
+                  <text>g</text>
+                </view>
+              </view>
+              <view class="weight-input">
+                <view class="input-label">白银金重</view>
+                <view class="form-body-box">
+                  <input
+                    v-model="agExtract"
+                    type="digit"
+                    class="weight-value"
+                  />
+                  <text>g</text>
+                </view>
+              </view>
+
+              <view class="tips">* 若无相关产品,可不用填写</view>
+            </view>
+          </view>
+        </view>
+        <view class="withdraw-body">
+          <view class="tx-active" style="margin-top: 10px">
+            <button @click="submitGoldOrder">点击提交</button>
+          </view>
+          <view class="aggregate" @click="aggregate = !aggregate">
+            <image
+              class="choose"
+              :src="
+                aggregate
+                  ? '/static/recycle/choose.png'
+                  : '/static/recycle/nochoose.png'
+              "
+              mode="scaleToFill"
+            ></image>
+            <view class="aggre">
+              阅读并同意
+              <span class="aggre-text" @click="showAggre">《存金协议》</span>
+            </view>
+          </view>
+          <view style="margin-top: 20px">
+            <up-parse :content="content"></up-parse>
+          </view>
+        </view>
+      </view>
+    </view>
+    <uni-popup
+      background-color="#fff"
+      ref="singPopup"
+      type="bottom"
+      :safeArea="false"
+      borderRadius="10px 10px 0 0"
+      maskBackgroundColor="rgba(0, 0, 0,0)"
+    >
+      <view class="signContent">
+        <scroll-view scrollY class="scroll">
+          <up-parse :content="agreement"></up-parse>
+        </scroll-view>
+        <view
+          class="comfireBtn footer"
+          @click="
+            aggregate = true;
+            $refs.singPopup.close();
+          "
+        >
+          我已详细知悉
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+// 1. 导入依赖(保持不变)
+import { ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { depositCreateAPI } from "@/api/functions";
+import { useImageUpload } from "@/hooks/useImageUpload";
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+
+// 2. 响应式变量(保持不变,明确金属类型对应关系)
+const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
+  pid: 9,
+  model: "gold",
+});
+const extract = ref([]);
+const auExtract = ref(null); // 黄金重量 → type:1
+const ptExtract = ref(null); // 铂金重量 → type:2
+const agExtract = ref(null); // 白银重量 → type:3
+const kauExtract = ref(null); // K金重量 → type:4
+const aggregate = ref(false);
+const expressNum = ref("");
+const cust_img = ref([]);
+const singPopup = ref(null);
+const props = defineProps(["agreement", "content"]);
+onLoad(() => {});
+const showAggre = () => {
+  singPopup.value?.open();
+};
+// 3. 扫码方法(保持不变)
+const scanCode = () => {
+  uni.scanCode({
+    success: (res) => {
+      expressNum.value = res.result;
+      console.log("赋值后:", expressNum.value);
+    },
+    fail: (err) => {
+      console.error("扫码失败:", err);
+      uni.showToast({ title: "扫码失败,请重试", icon: "none" });
+    },
+  });
+};
+const copy = (item) => {
+  uni.setClipboardData({
+    data: item,
+    success: () => {
+      uni.showToast({
+        title: "复制成功",
+        icon: "success",
+      });
+    },
+    fail: (err) => {
+      uni.showToast({
+        title: "复制失败",
+        icon: "none",
+      });
+      console.error("复制失败:", err);
+    },
+  });
+};
+
+// 复制地址方法
+const copyAddress = () => {
+  uni.setClipboardData({
+    data: `${appStore.$wxConfig.mailerName}:${appStore.$wxConfig.mailerPhone} 地址: ${appStore.$wxConfig.mailingAddress}`,
+    success() {
+      uni.showToast({ title: "复制成功", icon: "success" });
+    },
+    fail: (err) => {
+      uni.showToast({
+        title: "复制失败",
+        icon: "none",
+      });
+      console.error("复制失败:", err);
+    },
+  });
+};
+
+/**
+ * 转换金属重量为目标格式:[{type:1,weight:1}, ...]
+ * 改用直接引用ref变量的映射表,避免eval
+ */
+const buildMetalWeightList = () => {
+  // 优化:金属映射表直接包含ref变量,无需通过字符串动态获取
+  const metalConfigList = [
+    { metalVar: auExtract, type: 1, label: "黄金" }, // 直接传auExtract(ref变量)
+    { metalVar: ptExtract, type: 2, label: "铂金" },
+    { metalVar: agExtract, type: 3, label: "白银" },
+    { metalVar: kauExtract, type: 4, label: "K金" },
+  ];
+
+  // 循环收集有效重量(逻辑与之前一致,仅获取重量的方式变化)
+  const metalList = [];
+  metalConfigList.forEach(({ metalVar, type }) => {
+    // 直接访问ref变量的.value获取重量值
+    const weightValue = metalVar.value;
+    // 处理重量:去空格、排除空值、转为数字
+    const trimmedWeight = weightValue ? String(weightValue).trim() : "";
+    const validWeight = Number(trimmedWeight);
+
+    // 仅保留「非空、非NaN、正数」的重量
+    if (trimmedWeight !== "" && !isNaN(validWeight) && validWeight > 0) {
+      metalList.push({
+        type: type,
+        weight: validWeight, // 转为数字格式,符合接口要求
+      });
+    }
+  });
+
+  return metalList;
+};
+
+// 5. 提交方法(逻辑不变,依赖优化后的buildMetalWeightList)
+const submitGoldOrder = async () => {
+  // 步骤1:基础校验(图片+重量+快递单号)
+  const isCheckPass = checkHandle();
+  if (!isCheckPass) return;
+
+  // 步骤2:构建目标格式的金属重量数组
+  const metalWeightList = buildMetalWeightList();
+  if (metalWeightList.length === 0) {
+    uni.showToast({ title: "请至少填写一个有效金属重量!", icon: "none" });
+    return;
+  }
+
+  // 步骤3:整合提交数据
+  const submitData = {
+    metalInfos: metalWeightList, // 核心目标数组
+    expressNo: expressNum.value.trim(),
+    expressImages: imageList.value.map((v) => v.info.url),
+  };
+
+  // 步骤4:执行提交(示例接口调用)
+  try {
+    const res = await depositCreateAPI(submitData);
+
+    uni.showToast({ title: "提交成功!" });
+    setTimeout(() => {
+      uni.navigateTo({
+        url: "/pages/users/vault/storeMetal/order",
+      });
+    }, 1000);
+  } catch (error) {
+    console.error("提交失败:", error);
+    uni.showToast({ title: "提交失败,请重试", icon: "none" });
+  }
+};
+
+// 6. 校验方法(逻辑不变,依赖优化后的buildMetalWeightList)
+const checkHandle = () => {
+  // 校验1:快递图片
+  if (imageList.value.length === 0) {
+    uni.showToast({ title: "请上传快递图片!", icon: "none" });
+    return false;
+  }
+
+  // 校验2:至少一个有效金属重量
+  const metalWeightList = buildMetalWeightList();
+  if (metalWeightList.length === 0) {
+    uni.showToast({
+      title: "请至少填写一个金属重量(黄金/铂金/白银/K金)!",
+      icon: "none",
+    });
+    return false;
+  }
+
+  // 校验3:快递单号(可选,根据需求保留)
+  if (!expressNum.value.trim()) {
+    uni.showToast({ title: "请填写或扫码获取快递单号!", icon: "none" });
+    return false;
+  }
+
+  return true;
+};
+</script>
+
+<style lang="scss" scoped>
+page {
+  height: 100%;
+}
+.icon-iconfontscan {
+  font-size: 20px;
+}
+.upload-box {
+  .upload-block {
+    width: 160rpx;
+    height: 160rpx;
+    border: 1px solid #ccc;
+    border-radius: 10rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    color: #ccc;
+    font-weight: 700;
+    font-size: 26rpx;
+  }
+}
+.tips {
+  text-align: center;
+  color: #bdbdbd;
+  font-size: 24rpx;
+  margin: 20rpx auto;
+}
+
+.form-body {
+  // background-color: #fff;
+  // padding: 20px 30px;
+  font-size: 14px;
+  margin-top: 90rpx;
+
+  .weight-input {
+    display: flex;
+    align-items: center;
+    // justify-content: space-around;
+    margin: 30rpx 0;
+
+    .form-body-box {
+      display: flex;
+      align-items: center;
+      background-color: #ededed;
+      margin-left: 30rpx;
+      height: 72rpx;
+      border-radius: 10rpx;
+      font-size: 30rpx;
+
+      text {
+        color: #cc9933;
+        padding-right: 20rpx;
+        font-size: 25rpx;
+      }
+
+      input {
+        // font-size: 18rpx;
+        padding-left: 10rpx;
+      }
+    }
+
+    .input-label {
+      font-size: 32rpx;
+      font-weight: 500;
+
+      width: 135rpx;
+    }
+
+    .weight-value {
+      height: 1.9em;
+      font-size: 1.5em;
+      border: none;
+      outline: none;
+    }
+  }
+
+  .submit-btn-area {
+    button {
+      color: #fff;
+      background: #c4bba6;
+    }
+  }
+
+  .agreement-section {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 20rpx;
+    margin-top: 30rpx;
+
+    .agreement-checkbox {
+      width: 32rpx;
+      height: 16px;
+    }
+
+    .agreement-text {
+      font-size: 14px;
+      margin-left: 10px;
+
+      .agreement-link {
+        color: #3ab0ff;
+      }
+    }
+  }
+}
+
+.input-box {
+  display: flex;
+  background-color: #ededed;
+  border-radius: 5px;
+  height: 90rpx;
+  align-items: center;
+  justify-content: space-around;
+  font-size: 28rpx;
+  // paddingft: ;
+  padding-left: 20rpx;
+
+  // color:#c7c7c7 ;
+  input {
+    // background-color: ;
+    width: 80%;
+    // color: #c7c7c7;
+  }
+}
+
+.inpu-box-zl {
+  justify-content: space-between;
+  padding-left: 47rpx;
+}
+
+.gold-item {
+  margin: 50rpx 0;
+}
+
+// .gold-input{
+//    background-color: red !important;
+// }
+.address-detail {
+  line-height: 2.2;
+}
+
+.header {
+  padding-left: 5px;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 10px 10px;
+  border-radius: 5px;
+  // background-color: #fff;
+  font-weight: bold;
+  font-size: 18px;
+
+  &::before {
+    position: absolute;
+    /*绝对定位*/
+    top: 50%;
+    /*Y轴方向偏移自身高度的50%*/
+    transform: translatey(-50%);
+    /*Y轴方向偏移微调*/
+    left: 0;
+    /*紧靠容器左边缘*/
+    content: "";
+    /*伪元素需要有内容才能显示*/
+    width: 2px;
+    /*伪元素宽度*/
+    height: 15px;
+    /*伪元素高度*/
+    background-color: #daa520;
+    /*伪元素颜色*/
+  }
+
+  .title {
+    font-weight: 500;
+    font-size: 32rpx;
+    font-family: "黑体";
+  }
+}
+
+.withdraw {
+  height: 100%;
+  background-color: #f7f7f7;
+  border-radius: 10px 10px 0 0;
+  position: relative;
+  // top: -20rpx;
+  padding: 0 25rpx;
+  top: -67rpx;
+
+  .withdraw-express {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    justify-content: center;
+
+    padding: 28rpx;
+    margin-bottom: 30rpx;
+    border-radius: 30rpx;
+    padding-right: 0;
+
+    .head {
+      font-size: 14px;
+    }
+
+    .item {
+      display: flex;
+      align-items: center;
+      margin-top: 15px;
+
+      .targe {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        color: #fff;
+        width: 35px;
+        height: 35px;
+        border-radius: 50%;
+        background-color: #cc9933;
+      }
+
+      .address {
+        width: 430rpx;
+        margin: 0 10px;
+        font-size: 28rpx;
+
+        .receive-address {
+          letter-spacing: 9px;
+        }
+      }
+
+      .end {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+
+        .copy {
+          color: #888888;
+          border-radius: 3px;
+          border: 1px solid #888888;
+          font-size: 24rpx;
+          padding: 6rpx 23rpx;
+        }
+      }
+    }
+  }
+
+  .withdraw-body {
+    // background-color: #fff;
+    padding: 20px 30px;
+    font-size: 14px;
+
+    .input-money {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-weight: 600;
+      border-bottom: 1px solid #eaeef1;
+
+      .rmb {
+        font-size: 16px;
+      }
+
+      .t-input {
+        height: 1.9em;
+        font-size: 1.5em;
+        border: none;
+        outline: none;
+      }
+    }
+
+    .aggregate {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 10px;
+      color: #ababab;
+      font-size: 12px;
+
+      .aggre {
+        font-size: 30rpx;
+        margin-left: 10px;
+
+        .aggre-text {
+          color: #cc9933;
+        }
+      }
+    }
+
+    .choose {
+      width: 28rpx;
+      height: 28rpx;
+    }
+  }
+}
+
+.tx {
+  button {
+    color: #b2b2b2;
+  }
+}
+
+.tx-active {
+  margin: 0 auto;
+  display: flex;
+  justify-content: center;
+
+  button {
+    width: 420rpx;
+    height: 84rpx;
+    background-color: #cc9933;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: white;
+    font-size: 16px;
+    border-radius: 30px;
+  }
+}
+
+.signContent {
+  // background-color: #f8f8f8;
+  padding: 20px;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  border-radius: 20px 20px 0 0;
+
+  .scroll {
+    // background-color: #fff;
+    padding: 4px;
+    height: 300px;
+    overflow-y: hidden;
+    border: 1px solid #dfdfdf;
+  }
+
+  .footer {
+    margin-top: 10px;
+    color: #fff;
+    padding: 4px 20px;
+    border-radius: 20px;
+    background: linear-gradient(to right, #8ed187, #5dd665);
+  }
+}
+
+.express-input {
+  &:nth-child(1) {
+    padding: 0;
+  }
+}
+</style>

+ 363 - 0
pages/users/vault/storeMetal/gmReport.vue

@@ -0,0 +1,363 @@
+<template>
+  <view class="">
+    <view class="banner">
+      <view class="step-box">
+        <up-steps :current="orderStatus" activeColor="#b5aa90">
+          <up-steps-item
+            :title="item.name"
+            v-for="item in stepData"
+            :key="item.name"
+          >
+          </up-steps-item>
+        </up-steps>
+      </view>
+
+      <view class="block">
+        <view class="check">
+          <view class="header">
+            <span class="title">合后检测确认单</span>
+          </view>
+          <view class="desc"
+            >根据双方认可的检测内容及要求进行认定,为客户合后检测认定结果如下:</view
+          >
+          <view class="table">
+            <view class="title">客户寄送品</view>
+            <view class="tab_header">
+              <up-row>
+                <up-col :span="3">
+                  <view class="col">类型</view>
+                </up-col>
+                <up-col :span="3">
+                  <view class="col">来样重</view>
+                </up-col>
+                <up-col :span="3">
+                  <view class="col">实际重</view>
+                </up-col>
+                <up-col :span="3">
+                  <view class="col">成色</view>
+                </up-col>
+              </up-row>
+            </view>
+            <view class="tab_content">
+              <view>
+                <up-row v-for="item in detectionDetails" :key="item.id">
+                  <up-col :span="3">
+                    <view class="col">{{ getCate(item.type) }}</view>
+                  </up-col>
+                  <up-col :span="3">
+                    <view class="col"
+                      >{{ Number(item.userEstimatedWeight).toFixed(2) }}g</view
+                    >
+                  </up-col>
+                  <up-col :span="3">
+                    <view class="col"
+                      >{{ Number(item.discountedWeight).toFixed(2) }}g</view
+                    >
+                  </up-col>
+                  <up-col :span="3">
+                    <view class="col">{{ item.purity }}%</view>
+                  </up-col>
+                </up-row>
+              </view>
+            </view>
+          </view>
+          <view class="end">
+            <view class="end-bttom">
+              <span class="total">
+                总计:
+                <span class="price">{{ deTotalWeight.toFixed(2) }}g</span>
+              </span>
+            </view>
+            <view class="extra"
+              >即:客户对{{
+                detectionReport.inspectionTime
+              }}合后检测重量和回收价无异议</view
+            >
+            <view class="sign">
+              <span
+                >检测人:{{ detectionReport.inspectorNickname || "系统" }}</span
+              >
+            </view>
+            <view class="time">
+              {{ detectionReport.inspectionTime }}
+            </view>
+          </view>
+          <view style="font-size: 14px; padding: 10px">
+            <span style="font-size: 16px; font-weight: 700">注:</span>
+            <span>折算公式:兑料重量 = 折足重量 = 来料熔后重量 * 折足成色</span>
+          </view>
+        </view>
+      </view>
+      <view class="block">
+        <view class="check">
+          <view class="header">
+            <span class="title">检测图片</span>
+          </view>
+          <view class="checlImage">
+            <cl-upload
+              v-model="detectionImages"
+              :add="false"
+              :remove="false"
+            ></cl-upload>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view style="height: 70px"></view>
+    <view class="footer" v-if="orderStatus == 2">
+      <view class="btn" @click="next()">确认报告</view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { onMounted, computed, ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { postalDepositAPI } from "@/api/functions";
+const stepData = ref([
+  {
+    name: "待收货",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "待检测",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "待确认",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "待充值",
+    isNow: 0,
+    type: 1,
+  },
+  {
+    name: "已完成",
+    isNow: 1,
+    type: 0,
+  },
+]);
+// 注意:onLoad是uni-app的生命周期,如果是纯Vue3,应使用onMounted
+
+// 替代Vue2的methods中的方法
+const getCate = (cate) => {
+  // console.log(cate);
+
+  switch (cate) {
+    case 1:
+      return "黄金";
+    case 3:
+      return "白银";
+    case 2:
+      return "铂金";
+    case 4:
+      return "K金";
+  }
+};
+// 检测信息-成色类型等
+const detectionDetails = ref([]);
+// 检测详情
+const detectionReport = ref({});
+// 检测图片
+const detectionImages = ref([]);
+// 订单号
+const orderNo = ref("");
+// 订单状态
+const orderStatus = ref(4);
+onLoad((options) => {
+  if (options.orderInfo) {
+    // 先解码再解析
+    const orderInfoStr = decodeURIComponent(options.orderInfo);
+    const res = JSON.parse(orderInfoStr);
+    detectionDetails.value = res.goldBalances;
+
+    detectionReport.value = res;
+    detectionImages.value = JSON.parse(res.inspectionImage);
+    orderNo.value = res.id;
+    orderStatus.value = res.status;
+  }
+});
+// 确认报告
+const next = async () => {
+  const res = await postalDepositAPI(orderNo.value);
+  uni.showToast({
+    title: "确认成功",
+    duration: 1000,
+  });
+  setTimeout(() => {
+    uni.redirectTo({
+      url: "/pages/users/vault/storeMetal/order",
+    });
+  }, 1000);
+};
+const deTotalWeight = computed(() => {
+  if (detectionReport.value.goldBalances) {
+    const totalWeight = detectionReport.value.goldBalances.reduce(
+      (sum, material) => {
+        return sum + (Number(material.discountedWeight) || 0);
+      },
+      0
+    );
+    return totalWeight;
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.banner {
+  padding: 10px 15px;
+  background: $uni-bg-primary; /* fallback for old browsers */
+
+  .step-box {
+    margin: 30rpx 0;
+    width: 100%;
+    background: #fff;
+    height: 150rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    border-radius: 4px;
+
+    &::v-deep .u-text__value {
+      font-size: 24rpx !important;
+    }
+  }
+}
+
+.block {
+  margin-bottom: 10px;
+
+  .check {
+    background-color: #fff;
+    border-radius: 4px;
+    padding-top: 10px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  }
+  .header {
+    font-size: 14px;
+    font-weight: 700;
+    padding-left: 10px;
+    margin-left: 10px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    &::before {
+      position: absolute; /*绝对定位*/
+      top: 50%; /*Y轴方向偏移自身高度的50%*/
+      transform: translatey(-40%); /*Y轴方向偏移微调*/
+      left: 0px; /*紧靠容器左边缘*/
+      content: ""; /*伪元素需要有内容才能显示*/
+      width: 2px; /*伪元素宽度*/
+      height: 18px; /*伪元素高度*/
+      background-color: #daa520; /*伪元素颜色*/
+    }
+    .title {
+      font-size: 18px;
+    }
+  }
+  .desc {
+    padding: 8px 10px;
+    color: #888;
+    font-size: 14px;
+  }
+}
+.table {
+  padding: 14px 10px;
+  .title {
+    text-align: center;
+    padding: 4px 0;
+    color: #fff;
+    font-weight: 700;
+    font-size: 16px;
+    width: 100%;
+    background-color: #b5aa90;
+  }
+  .tab_header {
+    border-left: 1px solid #b5aa90;
+    font-size: 18px;
+    background-color: #fff;
+    .col {
+      padding: 12px 0;
+      line-height: 19px;
+    }
+  }
+  .tab_content {
+    border-left: 1px solid #b5aa90;
+    font-size: 16px;
+    background-color: #fff;
+    .col {
+      padding: 12px 0;
+      line-height: 19px;
+    }
+  }
+  .tab_header .col,
+  .tab_content .col {
+    border-bottom: 1px solid #b5aa90;
+    border-right: 1px solid #b5aa90;
+    text-align: center;
+  }
+  .symbol-name {
+    font-family: Microsoft YaHei, Arial, Helvetica, sans-serif;
+    font-size: 20px;
+    color: #ffbf24;
+  }
+}
+.end {
+  width: 100%;
+  display: inline-block;
+  text-align: right;
+  .end-bttom {
+    color: #888;
+    .total {
+      font-size: 16px;
+    }
+    .price {
+      margin-right: 10px;
+      color: #ffa034;
+    }
+  }
+  .extra {
+    font-size: 14px;
+    margin-top: 15px;
+    color: #888;
+    margin-right: 10px;
+  }
+  .sign {
+    margin: 10px;
+  }
+  .time {
+    padding: 10px;
+  }
+}
+
+.checlImage {
+  padding: 10px;
+}
+.footer {
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
+  position: fixed;
+  width: 100%;
+  border-radius: 8px 8px 0 0;
+  background-color: #fff;
+  bottom: 0;
+  height: 60px;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  .btn {
+    color: #fff;
+    font-weight: 700;
+    margin: 0 15px;
+    border-radius: 10px;
+    width: 100px;
+    padding: 10px;
+    text-align: center;
+    background-color: #cc9933;
+  }
+}
+</style>

+ 161 - 0
pages/users/vault/storeMetal/goldBullionStock.vue

@@ -0,0 +1,161 @@
+<template>
+  <view class="content">
+    <view class="tabs-wrap">
+      <!-- 新增tabs导航 -->
+      <view class="takeout-tabs">
+        <view
+          class="tab-item"
+          :class="['tab-active', currentTab === 0 ? 'active' : '']"
+          @click="currentTab = 0"
+          >邮寄存金</view
+        >
+        <view
+          class="tab-item"
+          :key="currentTab"
+          :class="['tab-active', currentTab === 1 ? 'active' : '']"
+          @click="currentTab = 1"
+          >无物流存金</view
+        >
+      </view>
+    </view>
+    <view v-if="!currentTab">
+      <gold-mail-form :content="content" :agreement="agreement">
+      </gold-mail-form>
+    </view>
+    <view v-else>
+      <non-logistics-gold :viprealGoldprice="viprealGoldprice">
+      </non-logistics-gold>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from "vue";
+import GoldMailForm from "./GoldMailForm.vue";
+import nonLogisticsGold from "./nonLogisticsGold.vue";
+import { agreementGetoneApi } from "@/api/user";
+import { useStoreRights } from "@/stores/rights";
+import useRealGoldPrice from "@/hooks/useRealGoldPrice";
+import { number } from "../../../../uni_modules/uview-plus/libs/function/test";
+const {
+  realGoldprice, // 黄金实时销售价(基础)
+} = useRealGoldPrice({});
+const rightsStore = useStoreRights();
+// 响应式数据
+const currentTab = ref(0);
+const content = ref("");
+const agreement = ref("");
+// Vue 3 中使用 onMounted 替代 onLoad
+// 获取协议
+function agreementGetoneFn() {
+  agreementGetoneApi({ name: "saveGoldRules" }).then((res) => {
+    content.value = res.data?.content;
+  });
+  // 资产说明
+  agreementGetoneApi({ name: "saveGold" }).then((res) => {
+    agreement.value = res.data?.content;
+  });
+}
+
+// 黄金调整价
+const adjustGoldprice = computed(() => {
+  const res = rightsStore.userBenefits.nobleMeta?.find(
+    (gold) => gold.name == "黄金"
+  ) || 0;
+  return res;
+});
+// 获取实时金价
+const viprealGoldprice = computed(
+  () =>
+    Number(realGoldprice.value) -
+    Number(rightsStore.userBenefits.buy) +
+    Number(adjustGoldprice.value?.sellPriceAdjust)
+);
+onMounted((options) => {
+  agreementGetoneFn();
+});
+
+// const someComputed = computed(() => { ... })
+
+// function someMethod() { ... }
+</script>
+
+<style scoped lang="scss">
+/* 新增tabs样式 */
+.takeout-tabs {
+  display: flex;
+  height: 120rpx;
+  overflow: hidden;
+  position: relative;
+  top: 85rpx;
+}
+
+.tabs-wrap {
+  height: 270rpx;
+  width: 100%;
+}
+
+.tab-item {
+  width: 50%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+  box-sizing: border-box;
+  color: #fff;
+}
+
+.tab-item.active {
+  color: #fff;
+  font-weight: bold;
+}
+
+.tab-item.active::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  /* 修改为0,使三角形显示在底部 */
+  left: 50%;
+  transform: translateX(-50%);
+  z-index: 100;
+  border-style: solid;
+  border-width: 0 20rpx 20rpx 20rpx;
+  /* 使用rpx单位 */
+  border-color: transparent transparent #f7f7f7 transparent;
+  width: 0;
+  height: 0;
+  opacity: 1;
+  /* 设置为可见 */
+  transform-origin: center top;
+  animation: showTriangle 0.3s ease-out forwards;
+}
+
+/* 添加动画定义 */
+@keyframes showTriangle {
+  from {
+    opacity: 0;
+    transform: translate(-50%, 10rpx);
+  }
+
+  to {
+    opacity: 1;
+    transform: translate(-50%, 0);
+  }
+}
+
+.content {
+  position: relative;
+  height: 100%;
+  // background-color: #ededed;
+  // background: url('https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/static/20250529221733.jpg') top center no-repeat;
+  // background-size: 100% 40%;
+  // background-color: #f7f7f7;
+  background: $uni-bg-primary !important;
+}
+
+::v-deep .uni-input-placeholder {
+  font-size: 14px;
+  color: #bdbdbd;
+}
+</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1103 - 0
pages/users/vault/storeMetal/index.vue


+ 817 - 0
pages/users/vault/storeMetal/metalExchange.vue

@@ -0,0 +1,817 @@
+<template>
+  <view class="withdraw">
+    <view class="withdrawContent">
+      <view class="tabs">
+        <view
+          v-for="item in tabsList"
+          :key="item.key"
+          class="tabs-item"
+          :class="[tabsIndex === item.key ? 'active' : '']"
+          @click="tabsChange(item)"
+        >
+          {{ item.title }}
+        </view>
+      </view>
+      <!-- 收货地址模块 -->
+      <view class="address" @click="onAddress">
+        <view class="addressCon" v-if="addressInfo.realName">
+          <view class="name"
+            >{{ addressInfo.realName }}
+            <text class="phone">{{ addressInfo.phone }}</text>
+          </view>
+          <view class="acea-row">
+            <text class="default font-color" v-if="addressInfo.isDefault"
+              >[默认]</text
+            >
+            <text class="line2"
+              >{{ addressInfo.province }}{{ addressInfo.city
+              }}{{ addressInfo.district }}{{ addressInfo.detail }}</text
+            >
+          </view>
+        </view>
+        <view class="addressCon" v-else>
+          <view class="setaddress">设置收货地址</view>
+        </view>
+        <view class="iconfont icon-jiantou"></view>
+      </view>
+      <view class="content-top">
+        <!-- 快递公司选择模块 -->
+        <view class="section">
+          <view class="section-title">选择快递公司</view>
+          <view class="courier-list">
+            <view
+              class="courier-item"
+              :class="{ active: item.isSelected }"
+              v-for="(item, index) in courierList"
+              :key="index"
+              @click="handleSelectCourier(index)"
+            >
+              <image class="courier-logo" :src="item.logo"></image>
+              <image
+                v-show="item.isSelected"
+                class="gou"
+                src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/courier/dui.png"
+              ></image>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <view class="gold-box">
+        <view class="gold-item">
+          <view class="header">
+            <h3 class="title">板料克重</h3>
+            <view class="live-gold">
+              实时金价
+              <text class="price">{{ realprice.toFixed(2) }}</text>
+            </view>
+          </view>
+          <view class="input-box">
+            <input
+              v-model.number="extract"
+              placeholder="请输入克数"
+              type="digit"
+              class="t-input"
+              @input="onKeyInput"
+            />
+          </view>
+          <view class="infoMoney" style="font-size: 16px">
+            <view v-if="is_out">
+              <text class="info-money-num" style="color: #ff1e0f">
+                输入克重超过可提现克重,账户克重{{ accountWeight }}克
+              </text>
+            </view>
+            <view v-else-if="is_lowest">
+              <text class="info-money-num" style="color: #ff1e0f">
+                最低{{ lowest }}克起兑换,账户克重{{
+                  accountWeight
+                }}克,且最多两位小数
+              </text>
+              <text class="info-money-num" style="color: #ff1e0f"
+                >最低{{ lowest }}g起购买,且最多两位小数</text
+              >
+            </view>
+            <text v-else class="infoMoneyNum">
+              {{ `手续费:${1}元 运费:${1}元 账户克重:${accountWeight}克` }}
+            </text>
+            <view class="infoTip">*默认融成小圆饼寄出</view>
+          </view>
+        </view>
+      </view>
+
+      <view class="withdraw-bottom">
+        <view
+          :class="['submitBtn', is_post ? '' : 'submitBtnActive']"
+          style="margin-top: 10px"
+        >
+          <button @click="handleShowModel">提交兑换</button>
+        </view>
+      </view>
+    </view>
+
+    <!-- <uni-popup
+      ref="singPopup"
+      type="bottom"
+      borderRadius="10px 10px 0 0"
+      maskBackgroundColor="rgba(0,0,0,0)"
+    >
+      <view class="signContent">
+        <scroll-view scrollY class="scrollView">
+          <rich-text :nodes="agreement"></rich-text>
+        </scroll-view>
+        <view
+          class="confirmBtn footer"
+          @click="
+            aggregate = true;
+            $refs.singPopup.close();
+          "
+        >
+          我已详细知悉
+        </view>
+      </view>
+    </uni-popup> -->
+    <!-- 收货地址组件 -->
+    <addressWindow
+      ref="addressWindowRef"
+      :address="address"
+      :pagesUrl="pagesUrl"
+      @OnDefaultAddress="OnDefaultAddress"
+      @OnChangeAddress="OnChangeAddress"
+      @changeClose="changeClose"
+    />
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, nextTick } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+// 导入用户地址详情API接口
+import { getAddressDetail, getAddressDefault } from "@/api/user.js";
+// 导入地址选择组件
+import addressWindow from "@/components/addressWindow";
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+console.log(appStore.userInfo);
+
+const tabsList = ref([
+  { key: 1, label: "gold", title: "黄金" },
+  { key: 2, label: "platinum", title: "铂金" },
+  { key: 3, label: "silver", title: "白银" },
+]);
+// 响应式数据(对应原data())
+const tabsIndex = ref(1);
+const needPrice = ref(0); // 手续费
+const goldAllPrice = ref(0); // 总金价
+const extract = ref(0);
+const lowest = ref(1);
+const is_out = ref(false);
+const is_post = ref(false);
+const is_lowest = ref(false);
+const aggregate = ref(false);
+const agreement = ref([]);
+
+// 快递公司列表(静态结构,内部选中状态需响应式)
+const courierList = ref([
+  {
+    type: 2,
+    name: "顺丰陆运",
+    logo: "https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/courier/sf-land.png",
+    isSelected: true,
+    price: 15,
+  },
+  {
+    type: 3,
+    name: "顺丰空运",
+    logo: "https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/courier/sf-air.png",
+    isSelected: false,
+    price: 24,
+  },
+  {
+    type: 4,
+    name: "顺丰到付",
+    logo: "https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/courier/sf-cod.png",
+    isSelected: false,
+    price: 0,
+  },
+]);
+
+// 快递相关响应式数据
+const courierPrice = ref(15);
+const selectedCourierType = ref(2);
+const selectedGender = ref("gold");
+
+// 用户余额及手续费(响应式对象)
+const preciousMetal = ref({
+  gold: { name: "黄金", pool: 0, fee: 0 },
+  platinum: { name: "铂金", pool: 0, fee: 0 },
+  silver: { name: "白银", pool: 0, fee: 0 },
+});
+const tabsChange = (item) => {
+  tabsIndex.value = item.key;
+  selectedGender.value = item.label;
+};
+const handleSelectCourier = (index) => {
+  courierList.value.forEach((item, i) => {
+    item.isSelected = i === index;
+  });
+
+  const selectedCourier = courierList.value.find((item) => item.isSelected);
+  if (selectedCourier) {
+    // this.courierPrice = this.userInfo?.isSVIP
+    //   ? vk.myfn.accDivDecimal(selectedCourier.price, 2)
+    //   : selectedCourier.price;
+    // this.selectedCourierType = selectedCourier.type; // 保存选中快递的type
+  }
+};
+
+// 输入框逻辑
+// 在<script setup>中添加输入验证方法
+const onKeyInput = () => {
+  // 重置提示状态
+  is_lowest.value = false;
+  is_out.value = false;
+
+  // 处理空输入
+  if (extract.value === null || extract.value === "" || isNaN(extract.value)) {
+    is_lowest.value = true; // 空输入时显示最低限制提示
+    return;
+  }
+
+  const inputWeight = Number(extract.value);
+  const minWeight = lowest.value; // 最低1克(来自定义的lowest: ref(1))
+  const accountWeightNum = Number(accountWeight.value) || 0;
+
+  // 验证1:是否低于最低限制(1克)
+  const isBelowMin = inputWeight < minWeight;
+
+  // 验证2:是否超过两位小数(通过正则判断)
+  const hasMoreThanTwoDecimals = /\.\d{3,}$/.test(extract.value.toString());
+
+  // 验证3:是否超过账户可用克重
+  const isOverAccount = inputWeight > accountWeightNum;
+
+  // 设置提示状态
+  if (isBelowMin || hasMoreThanTwoDecimals) {
+    is_lowest.value = true; // 显示最低限制提示(包含兑换和购买)
+  }
+  if (isOverAccount) {
+    is_out.value = true; // 显示超过账户克重提示
+  }
+};
+
+// 账户克重
+const accountWeight = computed(() => {
+  if (tabsIndex.value == 1) {
+    return appStore.userInfo.goldBalance;
+  }
+  if (tabsIndex.value == 2) {
+    return appStore.userInfo.ptBalance;
+  }
+  if (tabsIndex.value == 3) {
+    return appStore.userInfo.agBalance;
+  }
+});
+import useRealGoldPrice from "@/hooks/useRealGoldPrice";
+// 实时价格处理
+const {
+  realGoldRecyclePrice,
+  realKGoldRecyclePrice,
+  realPtRecyclePrice,
+  realAgRecyclePrice,
+} = useRealGoldPrice({});
+import { useStoreRights } from "@/stores/rights";
+const rightsStore = useStoreRights();
+// 回收
+const vipRealGoldRecyclePrice = computed(() => {
+  let res = 0;
+  const basePrice = Number(realGoldRecyclePrice.value || 0);
+  const soldBenefit = Number(rightsStore.userBenefits?.sold || 0);
+  res = appStore.userInfo.svip
+    ? basePrice + soldBenefit + 0.3
+    : basePrice + soldBenefit;
+
+  return res;
+});
+const viprealKGoldRecyclePrice = computed(() => {
+  let res = 0;
+  const basePrice = Number(realKGoldRecyclePrice.value || 0);
+  const soldBenefit = Number(rightsStore.userBenefits?.sold || 0);
+  res = appStore.userInfo.svip
+    ? basePrice + soldBenefit + 0.3
+    : basePrice + soldBenefit;
+
+  return res;
+});
+const viprealPtRecyclePrice = computed(() => {
+  let res = 0;
+  const basePrice = Number(realPtRecyclePrice.value || 0);
+  const soldBenefit = Number(rightsStore.userBenefits?.sold || 0);
+  res = appStore.userInfo.svip
+    ? basePrice + soldBenefit + 0.3
+    : basePrice + soldBenefit;
+
+  return res;
+});
+const viprealAgRecyclePrice = computed(() => {
+  let res = 0;
+  const basePrice = Number(realAgRecyclePrice.value || 0);
+  const soldBenefit = Number(rightsStore.userBenefits?.sold || 0);
+  res = appStore.userInfo.svip
+    ? basePrice + soldBenefit + 0.3
+    : basePrice + soldBenefit;
+
+  return res;
+});
+// 实时金价
+const realprice = computed(() => {
+  if (tabsIndex.value == 1) {
+    return vipRealGoldRecyclePrice.value;
+  }
+
+  if (tabsIndex.value == 2) {
+    return viprealPtRecyclePrice.value;
+  }
+  if (tabsIndex.value == 3) {
+    return viprealAgRecyclePrice.value;
+  }
+});
+// 地址相关
+const textareaStatus = ref(true);
+const pagesUrl = ref("");
+const addressWindowRef = ref(null);
+const address = ref({
+  address: false,
+  addressId: 0,
+}); // 地址组件
+const addressInfo = ref({}); // 地址信息
+const addressId = ref(0); // 地址id
+// 首次进入页面时展示默认地址
+const OnDefaultAddress = (e) => {
+  console.log("保存默认地址详情");
+
+  addressInfo.value = e; // 保存默认地址详情
+  address.value.addressId = e.id; // 更新选中地址ID
+};
+// 打开地址选择弹窗
+const onAddress = () => {
+  textareaStatus.value = false; // 隐藏备注框
+  address.value.address = true; // 显示地址弹窗
+
+  // 设置地址页面跳转链接
+  pagesUrl.value = "/pages/users/user_address_list/index";
+  nextTick(() => {
+    addressWindowRef.value.fetchAddressList();
+  });
+};
+// 选择地址后触发的事件
+const OnChangeAddress = (e) => {
+  console.log("OnChangeAddress", e);
+
+  addressInfo.value = e; // 保存选中的地址详情
+  address.value.addressId = e.id; // 更新选中地址ID
+  textareaStatus.value = true; // 显示备注框
+  address.value.address = false; // 关闭地址弹窗
+};
+// 获取默认地址或指定地址详情
+const getaddressInfo = () => {
+  if (addressId.value) {
+    // 若有指定地址ID,获取该地址详情
+    getAddressDetail(addressId.value).then((res) => {
+      if (res.data) {
+        // 若地址存在
+        res.data.isDefault = parseInt(res.data.isDefault); // 转换默认地址标识为数字
+        addressInfo.value = res.data || {}; // 保存地址详情
+        addressId.value = res.data.id || 0; // 更新地址ID
+        address.value.addressId = res.data.id || 0; // 更新选中地址ID
+      }
+    });
+  } else {
+    // 若没有指定地址ID,获取默认地址
+    getAddressDefault().then((res) => {
+      // 注意:原代码中未导入getAddressDefault,可能是遗漏
+      if (res.data) {
+        // 若默认地址存在
+        res.data.isDefault = parseInt(res.data.isDefault);
+        addressInfo.value = res.data || {};
+        addressId.value = res.data.id || 0;
+        address.value.addressId = res.data.id || 0;
+      }
+    });
+  }
+};
+onLoad(() => {
+  // 若已登录且非支付页面,则获取地址信息(注释掉的逻辑,预留)
+  if (appStore.isLogin) {
+    // console.log(1111);
+
+    getaddressInfo();
+    // 等待DOM更新后,调用地址组件的方法获取地址列表
+    nextTick(() => {
+      addressWindowRef.value.fetchAddressList();
+    });
+  }
+});
+// 关闭地址弹窗
+const changeClose = () => {
+  address.value.address = false; // 隐藏地址弹窗
+};
+</script>
+
+<style lang="scss" scoped>
+$item-value-color: #dca537;
+$card-bcolor: #f8f8f8;
+
+page {
+  height: 100%;
+  background-color: #ededed;
+}
+.tabs {
+  display: flex;
+  height: 80rpx;
+  border-radius: 40rpx;
+  padding: 0 20rpx;
+  color: #fff;
+  font-size: 38rpx;
+  font-weight: 300;
+  // margin-bottom: 10rpx;
+
+  .tabs-item {
+    width: 50%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    // align-items:;
+    position: relative;
+    color: #000000;
+  }
+
+  .active::after {
+    position: absolute;
+    bottom: 18rpx;
+    left: 50%;
+    transform: translateX(-50%);
+    content: "";
+    z-index: 100;
+    display: block;
+    width: 65rpx;
+    height: 6rpx;
+    background-color: #cc9933;
+  }
+}
+.withdraw-bottom {
+  width: 100%;
+  margin-top: 100rpx;
+  display: flex;
+  justify-content: center;
+}
+
+.header {
+  padding-left: 5px;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 10px 10px;
+  border-radius: 5px;
+  // background-color: #fff;
+  // font-weight: bold;
+  font-size: 18px;
+
+  .live-gold {
+    font-weight: 0;
+    font-size: 30rpx;
+
+    .price {
+      color: #dcbe81;
+      margin-left: 10rpx;
+    }
+  }
+
+  .item {
+    display: flex;
+    align-items: center;
+    margin-top: 15px;
+
+    .targe {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      color: #fff;
+      width: 35px;
+      height: 35px;
+      border-radius: 50%;
+      background-color: #cc9933;
+    }
+
+    .address {
+      width: 440rpx;
+      margin: 0 10px;
+      font-size: 28rpx;
+
+      .receive-address {
+        letter-spacing: 9px;
+      }
+    }
+
+    .end {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      .copy {
+        color: #888888;
+        border-radius: 3px;
+        border: 1px solid #888888;
+        font-size: 24rpx;
+        padding: 6rpx 23rpx;
+      }
+    }
+  }
+
+  &::before {
+    position: absolute;
+    top: 50%;
+    transform: translatey(-50%);
+    left: 0;
+    content: "";
+    width: 2px;
+    height: 15px;
+    background-color: #daa520;
+  }
+
+  .title {
+    font-weight: 500;
+    font-size: 32rpx;
+    font-family: "黑体";
+  }
+}
+
+.gold-box {
+  padding: 30rpx 20rpx;
+}
+
+.gold-item {
+  margin: 0 0 25rpx 0;
+}
+
+.input-box {
+  display: flex;
+  background-color: #ededed;
+  border-radius: 5px;
+  height: 90rpx;
+  align-items: center;
+  justify-content: space-around;
+  font-size: 28rpx;
+  padding-left: 20rpx;
+  margin-top: 50rpx;
+
+  input {
+    width: 90%;
+  }
+}
+// 地址相关样式
+.address {
+  width: 690rpx;
+  max-height: 180rpx;
+  margin: 40rpx 0;
+  padding: 28rpx;
+  box-sizing: border-box;
+  border-radius: 30rpx;
+
+  .addressCon {
+    width: 596rpx;
+    font-size: 26rpx;
+    color: #666;
+
+    .name {
+      font-size: 30rpx;
+      color: #282828;
+      font-weight: bold;
+      margin-bottom: 10rpx;
+
+      .phone {
+        margin-left: 50rpx;
+      }
+    }
+
+    .default {
+      margin-right: 12rpx;
+    }
+
+    .setaddress {
+      color: #333;
+      font-size: 28rpx;
+    }
+  }
+
+  .iconfont {
+    font-size: 35rpx;
+    color: #707070;
+  }
+}
+.content-top {
+  border-radius: 30rpx;
+  // padding: 20rpx;
+  background: $card-bcolor;
+  margin-bottom: 20rpx;
+
+  // 通用区块标题
+  .section {
+    // margin: 0 24rpx 32rpx;
+
+    .section-title {
+      font-size: 32rpx;
+      font-weight: bold;
+      margin-bottom: 24rpx;
+      position: relative;
+      padding-left: 20rpx;
+
+      &::after {
+        position: absolute;
+        top: 0;
+        left: 0;
+        height: 100%;
+        width: 3px;
+        background: $item-value-color;
+        content: "";
+      }
+    }
+
+    // 快递公司选择
+    .courier-list {
+      display: flex;
+      // overflow-x: auto;
+      justify-content: space-between;
+      padding-bottom: 16rpx;
+
+      .courier-item {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        min-width: 160rpx;
+        margin-right: 24rpx;
+        padding: 24rpx 20rpx;
+        border: 2rpx solid #eee;
+        border-radius: 12rpx;
+        cursor: pointer;
+        position: relative;
+
+        .gou {
+          position: absolute;
+          width: 40rpx;
+          height: 40rpx;
+          // background: #dca537;
+          right: -7rpx;
+          bottom: -7rpx;
+          border-radius: 50%;
+          right: -6rpx;
+          bottom: -6rpx;
+        }
+
+        &.active {
+          border-color: #dbb870;
+        }
+
+        .courier-logo {
+          width: 120rpx;
+          height: 120rpx;
+          margin-bottom: 8rpx;
+        }
+
+        .courier-name {
+          font-size: 26rpx;
+        }
+      }
+    }
+  }
+}
+
+.withdraw {
+  height: 100%;
+  background-size: 100% 40%;
+  background: $uni-bg-primary !important;
+  .withdrawContent {
+    padding: 45rpx 40rpx;
+    background-color: #f7f7f7;
+    // margin-top: ;
+    position: relative;
+    top: 150rpx;
+    border-radius: 50rpx 50rpx 0 0;
+  }
+
+  .withdrawBody {
+    background-color: #fff;
+    padding: 20px 30px;
+    font-size: 14px;
+
+    .inputMoney {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-weight: 600;
+      border-bottom: 1px solid #eaeef1;
+
+      .rmb {
+        font-size: 16px;
+        // text-wrap: nowrap;
+      }
+
+      .tInput {
+        height: 1.9em;
+        font-size: 2.5em;
+        border: none;
+        position: relative;
+        left: 3.5%;
+        outline: none;
+      }
+    }
+  }
+}
+
+.infoMoney {
+  margin-top: 10px;
+  font-size: 12px;
+  margin-bottom: 20px;
+
+  .infoMoneyNum {
+    color: #b2b2b2;
+    font-size: 26rpx;
+  }
+
+  .infoTip {
+    color: red;
+    font-size: 23rpx;
+    margin-top: 10rpx;
+  }
+}
+
+.agreement {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 10px;
+
+  .chooseIcon {
+    width: 16px;
+    height: 16px;
+  }
+
+  .agreementText {
+    font-size: 14px;
+    margin-left: 10px;
+    color: #999999;
+
+    .agreementLink {
+      color: #dca12b;
+    }
+  }
+}
+
+.submitBtn {
+  button {
+    background-color: #dca12b;
+    color: #fff;
+    width: 450rpx;
+    height: 72rpx;
+    display: flex;
+    font-size: 30rpx;
+    justify-content: center;
+    align-items: center;
+    border-radius: 30rpx;
+  }
+}
+
+.submitBtnActive {
+  button {
+    color: #fff;
+    background: #c4bba6;
+  }
+}
+
+.signContent {
+  background-color: #f8f8f8;
+  padding: 20px;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  border-radius: 20px 20px 0 0;
+
+  .scrollView {
+    background-color: #fff;
+    padding: 4px;
+    height: 300px;
+    overflow-y: hidden;
+    border: 1px solid #dfdfdf;
+  }
+
+  .confirmBtn {
+    margin-top: 10px;
+    color: #fff;
+    padding: 4px 20px;
+    border-radius: 20px;
+    background: linear-gradient(to right, #8ed187, #5dd665);
+  }
+}
+</style>

+ 508 - 0
pages/users/vault/storeMetal/nonLogisticsGold.vue

@@ -0,0 +1,508 @@
+<template>
+  <view class="withdraw">
+    <view class="content">
+      <view class="withdraw-body">
+        <view class="gold-box">
+          <view class="gold-item">
+            <view class="header">
+              <h3 class="title">存金克重</h3>
+              <view class="live-gold">
+                实时金价
+                <text class="price">{{ viprealGoldprice.toFixed(2) }}</text>
+              </view>
+            </view>
+
+            <view class="input-box">
+              <input
+                type="text"
+                class="inpu-box-ds"
+                placeholder="请输入克重"
+                v-model="extract"
+                @input="onKeyInput"
+                placeholder-style="color: #999999; font-size: 28rpx;"
+              />
+            </view>
+            <!-- <view class="header">
+              <span class="title">淘宝订单号</span>
+            </view>
+            <view class="input-box">
+              <input
+                type="text"
+                class="inpu-box-ds"
+                placeholder="请输入淘宝定单号"
+                v-model="expressNo"
+                @input="onKeyInput"
+                placeholder-style="color: #999999; font-size: 28rpx;"
+              />
+            </view> -->
+          </view>
+        </view>
+        <view class="info-money" style="font-size: 16px">
+          <text class="info-money-num" v-if="extract"
+            >预存金额= {{ extract }} * {{ viprealGoldprice.toFixed(2) }} =
+            {{ totalPrice }}元</text
+          >
+          <text class="info-money-num" v-else>预存金额0元</text>
+        </view>
+        <view class="img-container">
+          <view class="img-title">
+            <span class="title">实物图片</span>
+          </view>
+          <view class="img-box" style="margin-top: 10px">
+            <view class="upload-box">
+              <up-upload
+                :fileList="imageList"
+                uploadIcon="plus"
+                @afterRead="afterRead"
+                @delete="deletePic"
+                name="1"
+                multiple
+                :maxCount="3"
+              >
+                <template #trigger>
+                  <view class="upload-block">
+                    <uni-icons
+                      size="38"
+                      color="#ccc"
+                      type="plusempty"
+                    ></uni-icons>
+                  </view>
+                </template>
+              </up-upload>
+            </view>
+          </view>
+        </view>
+        <view class="submit-box">
+          <view
+            :class="'tx' + (is_post ? '' : '-active')"
+            style="margin-top: 10px"
+            class="submit"
+          >
+            <button @click="handleShowModel">提交存金</button>
+          </view>
+          <view class="aggregate" @click="aggregate = !aggregate">
+            <image
+              class="choose"
+              :src="
+                aggregate
+                  ? '/static/recycle/choose.png'
+                  : '/static/recycle/nochoose.png'
+              "
+              mode="scaleToFill"
+            ></image>
+            <view class="aggre">
+              阅读并同意
+              <span class="aggre-text" @click="showAggre">《存金协议》</span>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <uni-popup
+      ref="singPopup"
+      type="bottom"
+      borderRadius="10px 10px 0 0"
+      maskBackgroundColor="rgba(0,0,0,0)"
+    >
+      <view class="signContent">
+        <scroll-view scrollY class="scroll">
+          <up-parse :content="agreement"></up-parse>
+        </scroll-view>
+        <view
+          class="comfireBtn footer"
+          @click="
+            aggregate = true;
+            $refs.singPopup.close();
+          "
+        >
+          我已详细知悉
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch } from "vue";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+
+import { noLogisticsCreateAPI } from "@/api/functions";
+import { useImageUpload } from "@/hooks/useImageUpload";
+import { agreementGetoneApi } from "@/api/user";
+import useRealGoldPrice from "@/hooks/useRealGoldPrice";
+const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
+  pid: 9,
+  model: "gold",
+});
+const maoding = ref(0);
+const props = defineProps(["viprealGoldprice"]);
+const singPopup = ref(null);
+const agreement = ref("");
+const type = ref("store"); // 初始值与原data一致
+const is_lock = ref(false);
+const needPrice = ref(0);
+const extract = ref(null);
+const is_post = ref(false);
+const totalPrice = ref(0);
+const aggregate = ref(false);
+// const expressNo = ref("");
+// 获取实时金价
+const { realGoldprice } = useRealGoldPrice("RTJ_Au");
+
+const viprealGoldprice = computed(() => Number(realGoldprice.value));
+
+// 获取协议
+function agreementGetoneFn() {
+  // 资产说明
+  agreementGetoneApi({ name: "saveGold" }).then((res) => {
+    agreement.value = res.data?.content;
+  });
+}
+// 页面生命周期(对应原onLoad)
+onShow(() => {});
+const showAggre = () => {
+  agreementGetoneFn();
+  singPopup.value?.open();
+};
+
+const onKeyInput = (e) => {
+  extract.value = e.target.value;
+};
+
+// 提交存金
+const handleShowModel = async () => {
+  if (!extract.value) {
+    return uni.showToast({
+      title: "请输入克重",
+      duration: 2000,
+      icon: "none",
+    });
+  }
+  // if (!expressNo.value) {
+  //   return uni.showToast({
+  //     title: "请输入快递单号",
+  //     duration: 2000,
+  //     icon: "none",
+  //   });
+  // }
+  const res = await noLogisticsCreateAPI({
+    depositWeight: extract.value,
+    image: imageList.value.map((v) => v.info.url),
+    // taobaoOrderNo: expressNo.value,
+  });
+  uni.showToast({ title: "下单成功!" });
+
+  setTimeout(() => {
+    uni.navigateTo({
+      url: "/pages/users/vault/index",
+    });
+  }, 1000);
+};
+watch(
+  () => extract.value,
+  (val) => {
+    if (val) {
+      totalPrice.value = (
+        Number(extract.value) * Number(props.viprealGoldprice)
+      ).toFixed(2);
+      is_post.value = true;
+    } else {
+      is_post.value = false;
+    }
+  },
+  { immediate: true }
+);
+</script>
+
+<style lang="scss" scoped>
+page {
+  height: 100%;
+  background-color: #ededed;
+}
+.upload-box {
+  .upload-block {
+    width: 160rpx;
+    height: 160rpx;
+    border: 1px solid #ccc;
+    border-radius: 10rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    color: #ccc;
+    font-weight: 700;
+    font-size: 26rpx;
+  }
+}
+.info-money-num {
+  color: #999999;
+  font-size: 26rpx;
+}
+
+.submit-box {
+  margin-top: 300rpx;
+}
+
+.submit {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  button {
+    background-color: #dca12b;
+    color: #fff;
+    width: 450rpx;
+    height: 72rpx;
+    display: flex;
+    font-size: 30rpx;
+    justify-content: center;
+    align-items: center;
+    border-radius: 30rpx;
+  }
+}
+
+.header {
+  padding-left: 5px;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 10px 10px;
+  border-radius: 5px;
+  // background-color: #fff;
+  // font-weight: bold;
+  font-size: 18px;
+
+  .live-gold {
+    font-weight: 500;
+    font-size: 32rpx;
+    font-family: "黑体";
+
+    .price {
+      color: #d0a34a;
+      margin-left: 10rpx;
+      font-weight: bold;
+      font-size: 38rpx;
+    }
+  }
+
+  .item {
+    display: flex;
+    align-items: center;
+    margin-top: 15px;
+
+    .targe {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      color: #fff;
+      width: 35px;
+      height: 35px;
+      border-radius: 50%;
+      background-color: #cc9933;
+    }
+
+    .address {
+      width: 440rpx;
+      margin: 0 10px;
+      font-size: 28rpx;
+
+      .receive-address {
+        letter-spacing: 9px;
+      }
+    }
+
+    .end {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      .copy {
+        color: #888888;
+        border-radius: 3px;
+        border: 1px solid #888888;
+        font-size: 24rpx;
+        padding: 6rpx 23rpx;
+      }
+    }
+  }
+
+  &::before {
+    position: absolute;
+    /*绝对定位*/
+    top: 50%;
+    /*Y轴方向偏移自身高度的50%*/
+    transform: translatey(-50%);
+    /*Y轴方向偏移微调*/
+    left: 0;
+    /*紧靠容器左边缘*/
+    content: "";
+    /*伪元素需要有内容才能显示*/
+    width: 2px;
+    /*伪元素宽度*/
+    height: 15px;
+    /*伪元素高度*/
+    background-color: #daa520;
+    /*伪元素颜色*/
+  }
+
+  .title {
+    font-weight: 500;
+    font-size: 32rpx;
+    font-family: "黑体";
+  }
+}
+
+.gold-box {
+  padding-top: 30rpx;
+  .gold-item {
+    .input-box {
+      display: flex;
+      background-color: #ededed;
+      border-radius: 5px;
+      height: 90rpx;
+      align-items: center;
+      justify-content: space-around;
+      font-size: 28rpx;
+
+      margin: 20rpx 0;
+
+      // color:#c7c7c7 ;
+      input {
+        padding-left: 12rpx;
+        width: 92%;
+      }
+    }
+  }
+}
+
+.img-container {
+  padding: 30rpx 0;
+
+  .img-title {
+    padding-left: 5px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 10px;
+    border-radius: 5px;
+    font-weight: 500;
+    font-size: 32rpx;
+    font-family: "黑体";
+
+    &::before {
+      position: absolute;
+      top: 50%;
+      transform: translatey(-50%);
+      left: 0;
+      content: "";
+      width: 2px;
+      height: 15px;
+      background-color: #daa520;
+    }
+  }
+}
+
+.withdraw {
+  height: 100%;
+  background-color: #f7f7f7;
+  height: 100%;
+
+  border-radius: 10px 10px 0 0;
+  position: relative;
+  // top: -20rpx;
+  padding: 0 25rpx;
+  top: -67rpx;
+
+  &-body {
+    // background-color: #fff;
+    padding: 0 28rpx;
+    font-size: 14px;
+
+    .input-money {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-weight: 600;
+      border-bottom: 1px solid #eaeef1;
+
+      .rmb {
+        font-size: 16px;
+      }
+
+      .t-input {
+        height: 1.9em;
+        font-size: 2.5em;
+        border: none;
+        position: relative;
+        left: 3.5%;
+        outline: none;
+      }
+    }
+
+    .info-money {
+      font-size: 26rpx;
+      margin-left: 34rpx;
+      &-num {
+        color: #c9c9c9;
+      }
+    }
+
+    .aggregate {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 10px;
+
+      .aggre {
+        font-size: 28rpx;
+        margin-left: 10px;
+        color: #929292;
+
+        .aggre-text {
+          color: #cc9933;
+        }
+      }
+    }
+
+    .choose {
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
+
+.tx-active {
+  button {
+    color: #fff;
+    background: #c4bba6;
+  }
+}
+
+.signContent {
+  background-color: #f8f8f8;
+  padding: 20px;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  border-radius: 20px 20px 0 0;
+
+  .scroll {
+    // background-color: #fff;
+    padding: 4px;
+    height: 300px;
+    overflow-y: hidden;
+    border: 1px solid #dfdfdf;
+  }
+
+  .footer {
+    margin-top: 10px;
+    color: #fff;
+    padding: 4px 20px;
+    border-radius: 20px;
+    background: linear-gradient(to right, #8ed187, #5dd665);
+  }
+}
+</style>

+ 355 - 0
pages/users/vault/storeMetal/order.vue

@@ -0,0 +1,355 @@
+<template>
+  <view class="list-page">
+    <view class="tabs-box">
+      <up-tabs :list="list" @click="tabsChange" lineColor="#f8c20f"></up-tabs>
+    </view>
+    <view v-if="orderList.length === 0" class="empty">
+      <image
+        style="width: 60%"
+        src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/common/empty.png"
+        mode="widthFix"
+      ></image>
+      <text>暂无订单~</text>
+    </view>
+    <view v-else class="inner">
+      <view
+        v-for="(item, index) in orderList"
+        :key="index"
+        class="block"
+        @click="nativeTo(item)"
+      >
+        <view class="header">
+          <view class="title">订单号:{{ item.orderNo }}</view>
+          <view class="tag" :class="['status' + item.status]">
+            {{ getOrderType(item.status) }}
+          </view>
+        </view>
+        <view class="detail">
+          <image
+            style="width: 50px; height: 50px; border-radius: 6px"
+            :src="item.images[0] || emptyImg"
+            mode="scaleToFill"
+            v-if="item.images"
+            @click="previewImage(item.images)"
+          ></image>
+        </view>
+
+        <view class="end">
+          <view>
+            <view class="desc">
+              订单自估重量:
+              <span
+                class="price"
+                v-for="(i, index) in item.goldMaterials"
+                :key="index"
+                >{{ `${getCate(i.type)}${i.weight}g` }}</span
+              >
+            </view>
+          </view>
+          <view class="desc">
+            <view class="">下单时间:{{ item.createTime }}</view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import {
+  onLoad,
+  onShow,
+  onPullDownRefresh,
+  onReachBottom,
+} from "@dcloudio/uni-app";
+import { depositPageAPI } from "@/api/functions";
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+// 响应式变量(替代 Vue2 的 data)
+const list = ref([
+  { name: "全部", status: "" },
+  { name: "待签收", status: 0 },
+  { name: "待检测", status: 1 },
+  { name: "待确认", status: 2 },
+  { name: "已完成", status: 4 },
+]);
+const options = ref({ status: 0 });
+const orderList = ref([]);
+const emptyImg = ref(
+  "https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/recycle/example/example1.png"
+);
+
+const params = ref({
+  page: 1,
+  limit: 20,
+  status: "",
+  // userId: appStorze.userInfo.userId,
+});
+const total = ref(0);
+const loading = ref(false); // 加载状态
+const hasMore = ref(true); // 是否还有更多数据
+
+onShow((options) => {
+  console.log(1111);
+  params.value.page = 1;
+  getOrderList();
+});
+const getOrderList = async (isRefresh = false) => {
+  if (loading.value) return;
+  loading.value = true;
+  uni.showLoading({
+    title: "加载中",
+  });
+  try {
+    const res = await depositPageAPI(params.value);
+    console.log(res);
+
+    const newList = res.data.list.map((v) => {
+      return {
+        ...v,
+        images: JSON.parse(v.expressImage),
+      };
+    });
+
+    if (isRefresh) {
+      orderList.value = newList;
+    } else {
+      // 避免重复添加
+      if (params.value.page === 1) {
+        orderList.value = newList;
+      } else {
+        orderList.value = [...orderList.value, ...newList];
+      }
+    }
+    total.value = res.data.total;
+    // 判断是否还有更多数据
+    hasMore.value = orderList.value.length < total.value;
+    uni.hideLoading();
+  } catch (error) {
+    console.error("获取订单列表失败:", error);
+    uni.showToast({
+      title: "加载失败",
+      icon: "none",
+    });
+  } finally {
+    loading.value = false;
+    // 停止下拉刷新动画
+    if (isRefresh) {
+      uni.stopPullDownRefresh();
+    }
+  }
+};
+// 其他生命周期
+onPullDownRefresh(() => {
+  // 下拉刷新逻辑(保持原有空实现,可根据需求补充)
+});
+
+onReachBottom(() => {
+  // 如果没有更多数据或正在加载中,则不执行
+  if (!hasMore.value || loading.value) return;
+
+  // 增加页码
+  params.value.page++;
+  // 加载下一页数据
+  getOrderList();
+});
+
+onShow(() => {
+  // 页面显示时逻辑(保持原有空实现,可根据需求补充)
+});
+
+// 标签页切换
+const tabsChange = (item) => {
+  params.value.status = item.status;
+  params.value.page = 1;
+  getOrderList();
+};
+// 跳转详情页(原 methods 中的 nativeTo)
+const nativeTo = (item) => {
+  if (item.status === 2 || item.status == 3 || item.status == 4) {
+    uni.navigateTo({
+      url: `/pages/users/vault/storeMetal/gmReport?orderInfo=${encodeURIComponent(
+        JSON.stringify(item)
+      )}`,
+    });
+  }
+};
+
+// 获取商品类别(原 methods 中的 getCate)
+const getCate = (cate) => {
+  switch (cate) {
+    case 1:
+      return "黄金";
+    case 3:
+      return "白银";
+    case 2:
+      return "铂金";
+    case 4:
+      return "K金";
+    default:
+      return ""; // 增加默认值,避免 undefined
+  }
+};
+
+// 获取订单状态文本(原 methods 中的 getOrderType)
+const getOrderType = (status) => {
+  switch (status) {
+    case 0:
+      return "待签收";
+    case 1:
+      return "待检测";
+    case 2:
+      return "待确认";
+    case 3:
+      return "待充值";
+    case 4:
+      return "已完成";
+    default:
+      return "-"; // 增加默认值,避免 undefined
+  }
+};
+const previewImage = (urls) => {
+  uni.previewImage({
+    current: urls[0],
+    urls: urls, // 需要预览的图片URL列表
+    success: () => {},
+    fail: (err) => {
+      console.error("预览图片失败:", err);
+    },
+  });
+};
+</script>
+
+<style scoped lang="scss">
+.list-page {
+  // min-height: 100vh;
+  background: $uni-bg-primary !important;
+}
+
+.tabs-box {
+  width: 100%;
+  height: 75rpx;
+  // background: #ffffff;
+  box-sizing: border-box;
+  ::v-deep .u-tabs__wrapper__nav__item {
+    padding: 0 37rpx;
+  }
+}
+
+.empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #fff;
+}
+.tabs {
+  z-index: 100;
+  position: sticky;
+}
+.inner {
+  padding: 10px;
+  .footer {
+    border-radius: 5px;
+    border: 0 0 10px 20px;
+    color: #707070;
+    font-size: 12px;
+    padding: 10px;
+    background-color: rgb(252, 247, 230);
+  }
+}
+.block {
+  margin-bottom: 10px;
+  padding-top: 10px;
+  border-radius: 5px;
+  background-color: #fff;
+  border: 1px solid #cecece;
+  .header {
+    padding: 0 10px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #eee;
+    .tag {
+      font-size: 12px;
+      padding: 2px 5px;
+      border-radius: 4px;
+      color: #ff0800;
+      background-color: #ffcece;
+      &.status-1 {
+        color: #555;
+        background-color: #eeeeee;
+      }
+      &.status1 {
+        color: #ff9900;
+        background-color: #ffeccf;
+      }
+      &.status2 {
+        color: #d6006b;
+        background-color: #fdd3e9;
+      }
+      &.status3 {
+        color: #9900ff;
+        background-color: #e2b7ff;
+      }
+      &.status4 {
+        color: rgb(48, 24, 136);
+        background-color: #bbb0fa;
+      }
+      &.status5 {
+        color: #0051ff;
+        background-color: #b7d6ff;
+      }
+      &.status6 {
+        color: #3dac27;
+        background-color: #c8ffb7;
+      }
+    }
+    .title {
+      font-size: 14px;
+    }
+  }
+  .info {
+    flex: 1;
+    width: 100%;
+    font-size: 14px;
+    margin-left: 10px;
+    color: #999;
+    .cartList {
+      margin-bottom: 3px;
+      display: flex;
+      justify-content: space-between;
+      .right {
+        min-width: 120px;
+        display: flex;
+        justify-content: space-between;
+        .weight {
+          color: #daa520;
+        }
+      }
+    }
+  }
+  .detail {
+    display: flex;
+    padding: 10px 13px;
+  }
+  .end {
+    padding: 10px 10px;
+    color: #999;
+    font-size: 14px;
+
+    .desc {
+      margin-bottom: 6px;
+      display: flex;
+      align-items: center;
+      // justify-content: space-between;
+
+      .price {
+        margin-right: 10rpx;
+      }
+    }
+  }
+}
+</style>

+ 291 - 0
pages/users/vault/trade_list.vue

@@ -0,0 +1,291 @@
+<template>
+  <view class="records-page">
+    <view class="tab-header">
+      <view
+        v-for="(tab, index) in tabs"
+        :key="index"
+        :class="['tab-item', { 'is-active': activeTab === tab.id }]"
+        @click="changeTab(tab.id)"
+      >
+        {{ tab.name }}
+      </view>
+    </view>
+
+    <z-paging
+      ref="paging"
+      v-model="dataList"
+      @query="queryList"
+      :use-page-scroll="true"
+      :show-refresher-update-time="true"
+      :empty-view-text="emptyText"
+      :refresher-enabled="true"
+      :loading-text-no-more="loadingNoMoreText"
+      :hide-loading-more-when-no-more-by-default="true"
+    >
+      <view
+        v-for="(item, index) in dataList"
+        :key="index"
+        class="record-item-wrapper"
+      >
+        <view class="record-item">
+          <view class="item-left">
+            <view class="top">
+              <text class="item-title">{{ metalTypeMap[metalType] || '黄金' }}</text>
+              <view
+                :class="[
+                  'status-tag',
+                  item.status === '待审核' ? 'status-pending' : 'status-live',
+                ]"
+              >
+                {{ statusMap[item.status] }}
+              </view>
+            </view>
+            <view class="time">
+              <text class="item-time">{{ item.createTime }}</text>
+            </view>
+          </view>
+          <view class="item-right">
+            <text class="item-weight">{{ item.weight }}</text>
+          </view>
+        </view>
+      </view>
+    </z-paging>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted, nextTick, watch } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import useZPaging from "@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js";
+import { useAppStore } from "@/stores/app";
+import { goldTradelist } from "@/api/vault";
+
+
+
+const paging = ref(null);
+useZPaging(paging)
+
+const appStore = useAppStore();
+
+const dataList = ref([]);
+
+const activeTab = ref(0);
+const emptyText = ref("");
+const loadingNoMoreText = ref("我也是有底线的");
+const statusMap = {
+  1: '已完成',
+  2: '待签收',
+  3: '待检测',
+  4: '待确认',
+  5: '待打款'
+}
+
+const tabs = [
+  {
+    id: 0,
+    name: "存入记录",
+    empty: "暂无存入记录",
+  },
+  {
+    id: 1,
+    name: "换款记录",
+    empty: "暂无换款记录",
+  },
+  {
+    id: 2,
+    name: "出售记录",
+    empty: "暂无出售记录",
+  },
+];
+
+const metalTypeMap = {
+  1: '黄金',
+  2: '铂金',
+  3: '白银'
+}
+const metalType = ref(1)
+
+onLoad((options) => {
+  if (options?.metalType) {
+    metalType.value = Number(options.metalType) 
+    uni.setNavigationBarTitle({
+      title: metalTypeMap[options.metalType]+"明细"
+    })
+  }
+ 
+})
+
+const queryList = async (pageNo, pageSize) => {
+  emptyText.value = tabs.find((tab) => tab.id === activeTab.value).empty;
+
+  try {
+    const tempParams = {
+      page: pageNo,
+      limit: pageSize,
+      metalType: metalType.value,
+      isDel: 0,
+      userId: appStore.uid,
+    }
+    const params = Object.assign({}, tempParams, tabParams.value)
+    console.log('params', params)
+    const { data } = await goldTradelist(params);
+
+    if (paging.value) {
+      paging.value.complete(data.list);
+    }
+  } catch (error) {
+    console.error("goldTradelist", error);
+    paging.value.complete(false);
+  }
+
+};
+
+const tabParams = ref({
+  type: 1
+})
+const changeTab = (tabId) => {
+  if (activeTab.value === tabId) return; 
+  activeTab.value = tabId;
+  if (tabId === 0) {
+    tabParams.value = { type: 1 }
+  } else if (tabId === 1) {
+    tabParams.value = { operationType: 2 }
+  } else if (tabId === 2) {
+    tabParams.value = { type: 0 }
+  }
+  
+  nextTick(() => {
+    if (paging.value) {
+      paging.value.reload();
+    }
+  });
+};
+</script>
+
+<style lang="scss">
+$theme-color: #e9c279;
+$border-color: #f0f0f0;
+$text-color-light: #999;
+$text-color-dark: #333;
+$button-text-color: #fff;
+$gold-tag-bg: #ffeac7; // Light yellow for "直播间换金" tag
+$gold-tag-text: #e9c279; // Darker yellow for text
+$pending-tag-bg: #f0f0f0; // Light gray for "待审核" tag
+$pending-tag-text: #666; // Darker gray for text
+
+.records-page {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+}
+
+.tab-header {
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  background-color: #fff;
+  padding: 20rpx 0;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+
+  .tab-item {
+    flex: 1;
+    text-align: center;
+    font-size: 32rpx;
+    color: $text-color-dark;
+    padding: 15rpx 0;
+    position: relative;
+    cursor: pointer; // Indicate clickable
+
+    &.is-active {
+      color: $theme-color;
+      font-weight: bold;
+
+      &::after {
+        content: "";
+        position: absolute;
+        bottom: -10rpx;
+        left: 50%;
+        transform: translateX(-50%);
+        width: 60rpx;
+        height: 6rpx;
+        background-color: $theme-color;
+        border-radius: 3rpx;
+      }
+    }
+  }
+}
+
+/* z-paging specific styles (optional, but good to have some defaults) */
+:deep(.z-paging-content) {
+  padding-top: 20rpx; // Space below the tabs
+}
+
+.record-item-wrapper {
+  padding: 0 30rpx;
+  margin-bottom: 20rpx; // Spacing between cards
+
+  &:first-child {
+    // No extra margin-top for the first item if needed
+  }
+}
+
+.record-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+
+  .item-left {
+
+    .item-title {
+      font-size: 32rpx;
+      color: $text-color-dark;
+      font-weight: bold;
+      margin-right: 15rpx;
+    }
+
+    .status-tag {
+      font-size: 24rpx;
+      padding: 8rpx 16rpx;
+      border-radius: 8rpx;
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .status-live {
+      background-color: $header-color;
+      color: #fff;
+    }
+
+    .status-pending {
+      background-color: $pending-tag-bg;
+      color: $pending-tag-text;
+    }
+  }
+
+
+  .item-time {
+    font-size: 26rpx;
+    color: $text-color-light;
+  }
+
+  .item-right {
+    flex-shrink: 0;
+    margin-left: 20rpx;
+    text-align: right;
+  }
+
+  .item-weight {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: $header-color;
+  }
+}
+</style>

+ 491 - 0
pages/users/vault/withdraw.vue

@@ -0,0 +1,491 @@
+<template>
+  <view class="withdraw-container">
+    <!-- 顶部余额展示 -->
+    <view class="balance-section">
+      <view class="balance-bg">
+        <view class="balance-title">可提现余额</view>
+        <view class="balance-amount">¥ {{ appStore?.$userInfo?.nowMoney || 0 }}</view>
+      </view>
+    </view>
+
+    <!-- 主要内容区域 -->
+    <view class="main-content">
+      <!-- 到账账户 -->
+      <view class="section" v-if="!appStore.$wxConfig?.auditModeEnabled">
+        <view class="section-title">到账账户</view>
+        <view class="account-item" @click="goToBankManage">
+          <view class="account-info">
+            <view class="bank-icon">
+              <text
+                :style="{
+                  color:
+                    defaultAccount?.accountType === 1 ? '#f2cb51' : '#019FE8',
+                }"
+                class="iconfont"
+                :class="[
+                  defaultAccount?.accountType === 1
+                    ? 'icon-qianbao'
+                    : 'icon-zhifubao',
+                ]"
+              ></text>
+            </view>
+            <view class="account-details" v-if="defaultAccount">
+              <text
+                class="account-number"
+                v-if="defaultAccount.accountType === 1"
+                >{{ defaultAccount.bankName }}({{
+                  formatCardNumber(defaultAccount.accountNumber)
+                }})</text
+              >
+              <text class="account-number" v-else
+                >{{ defaultAccount.accountName }}({{
+                  formatCardNumber(defaultAccount.accountNumber)
+                }})</text
+              >
+            </view>
+          </view>
+          <view class="arrow">{{ ">" }}</view>
+        </view>
+      </view>
+
+      <!-- 提现金额 -->
+      <view class="section">
+        <view class="section-title">提现金额</view>
+        <view class="amount-input-container">
+          <view class="currency-symbol">¥</view>
+          <input
+            class="amount-input"
+            type="digit"
+            v-model="withdrawAmount"
+            placeholder="请输入提现金额"
+            @input="onAmountInput"
+          />
+          <view class="withdraw-all" @click="withdrawAll">全部提现</view>
+        </view>
+        <view class="balance-info"
+          >账户余额{{ appStore.$userInfo.nowMoney }}元</view
+        >
+      </view>
+
+      <!-- 提交按钮 -->
+      <view class="submit-section">
+        <button
+          class="submit-btn"
+          :class="{ disabled: !canSubmit }"
+          @click="submitWithdraw"
+        >
+          提交申请
+        </button>
+      </view>
+    </view>
+    <!-- 协议窗口 -->
+    <up-parse :content="content"></up-parse>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+import { withdrawToCard } from "@/api/user";
+import { onShow } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+import { getDefaultAccount, getUserInfo, agreementGetoneApi } from "@/api/user";
+
+// 响应式数据
+const availableBalance = ref("5733.67");
+const withdrawAmount = ref("");
+const defaultAccount = ref(null);
+const appStore = useAppStore();
+const content = ref("");
+// 计算属性
+const canSubmit = computed(() => {
+  return withdrawAmount.value && parseFloat(withdrawAmount.value) > 0;
+});
+// 获取协议
+function agreementGetoneFn() {
+  // 资产说明
+  agreementGetoneApi({ name: "withraw" }).then((res) => {
+    content.value = res.data?.content;
+  });
+}
+onShow(() => {
+  agreementGetoneFn();
+  fetchDefaultAccount();
+  fetchUserInfo();
+});
+
+async function fetchUserInfo() {
+  try {
+    const { data } = await getUserInfo();
+    appStore.UPDATE_USERINFO(data);
+  } catch (error) {
+    console.error("getUserInfo", error);
+  }
+}
+
+// 获取默认账户详情
+async function fetchDefaultAccount() {
+  try {
+    const { data } = await getDefaultAccount();
+    defaultAccount.value = data;
+  } catch (error) {
+    console.error("getDefaultAccount", error);
+  }
+}
+
+const formatCardNumber = (cardNumber) => {
+  // 显示卡号,只显示后4位,其他用*代替
+  if (cardNumber.length <= 4) return cardNumber;
+  const lastFour = cardNumber.slice(-4);
+  return `****${lastFour}`;
+};
+
+// 方法
+const onAmountInput = (e) => {
+  let value = e.detail.value;
+  // 限制输入格式,只允许数字和小数点
+  value = value.replace(/[^0-9.]/g, "");
+  // 限制小数点后两位
+  if (value.includes(".")) {
+    const parts = value.split(".");
+    if (parts[1] && parts[1].length > 2) {
+      value = parts[0] + "." + parts[1].substring(0, 2);
+    }
+  }
+  withdrawAmount.value = value;
+};
+
+const withdrawAll = () => {
+  withdrawAmount.value = appStore.$userInfo.nowMoney;
+};
+
+const goToBankManage = () => {
+  uni.navigateTo({
+    url: "/pages/users/card_page/index",
+  });
+};
+
+const submitWithdraw = () => {
+  if (!canSubmit.value) return;
+  if (!defaultAccount.value || !defaultAccount.value.id) {
+    uni.showToast({
+      title: "请选择提现账户",
+      icon: "none",
+    });
+    return;
+  }
+
+  // 提交提现申请
+  uni.showModal({
+    title: "提示",
+    content: `确认提现 ¥${withdrawAmount.value} 吗?`,
+    success: async (res) => {
+      if (res.confirm) {
+        try {
+          if (
+            Number(withdrawAmount.value) > Number(appStore.$userInfo.nowMoney)
+          ) {
+            return uni.showToast({
+              title: "余额不足",
+            });
+          }
+          // 提现方式| alipay=支付宝,bank=银行卡,weixin=微信
+          const extractType =
+            defaultAccount.value.accountType === 1 ? "bank" : "alipay";
+          // 提现金额
+          const money = withdrawAmount.value;
+          // // 姓名
+          const name = defaultAccount.value.accountName;
+          const alipayParams = {
+            alipayCode: defaultAccount.value.accountNumber, // 支付宝账号
+            extractType,
+            name,
+            money,
+          };
+          const bankParams = {
+            extractType,
+            bankName: defaultAccount.value.bankName, // 提现银行名称
+            cardum: defaultAccount.value.accountNumber, // 银行卡
+            money,
+            name,
+          };
+          const params =
+            defaultAccount.value.accountType === 1 ? bankParams : alipayParams;
+
+          await withdrawToCard(params);
+          // fetchUserInfo()
+          const { data } = await getUserInfo();
+          appStore.UPDATE_USERINFO(data);
+          uni.showToast({
+            title: "提现申请已提交",
+            icon: "success",
+          });
+          setTimeout(() => {
+            uni.navigateTo({
+              url: "/pages/users/vault/index",
+            });
+          }, 1000);
+        } catch (error) {
+          console.error("withdrawToCard", error);
+          const title = typeof error === "string" ? error : "提现失败";
+          uni.showToast({
+            title,
+            duration: 2000,
+          });
+        } finally {
+          withdrawAmount.value = "";
+        }
+      }
+    },
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.withdraw-container {
+  min-height: 90vh;
+  width: 100%;
+
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  .balance-section {
+    width: 100%;
+    padding: 40rpx 30rpx 140rpx;
+    margin: 0 0 -100rpx;
+    background-image: linear-gradient(
+      to bottom,
+      $header-color 0%,
+      $header-color 10%,
+      #f0dab2 70%,
+      transparent 100%
+    );
+
+    .balance-bg {
+      padding: 50rpx 40rpx;
+      position: relative;
+      overflow: hidden;
+
+      &::after {
+        content: "";
+        position: absolute;
+        bottom: -30%;
+        left: -20%;
+        width: 150rpx;
+        height: 150rpx;
+        background: rgba(255, 255, 255, 0.05);
+        border-radius: 50%;
+      }
+
+      .balance-title {
+        color: rgba(255, 255, 255, 0.9);
+        font-size: 28rpx;
+        margin-bottom: 16rpx;
+        font-weight: 400;
+      }
+
+      .balance-amount {
+        color: #fff;
+        font-size: 56rpx;
+        font-weight: 600;
+        letter-spacing: 2rpx;
+      }
+    }
+  }
+
+  .main-content {
+    width: 100%;
+    padding: 40rpx 40rpx;
+    box-sizing: border-box;
+    background: #fff;
+    border-radius: 32rpx 32rpx 0 0;
+    margin-top: -20rpx;
+    box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
+
+    .section {
+      margin-bottom: 50rpx;
+
+      .section-title {
+        color: #333;
+        font-size: 32rpx;
+        font-weight: 600;
+        margin-bottom: 24rpx;
+        position: relative;
+
+        &::before {
+          content: "";
+          position: absolute;
+          left: -16rpx;
+          top: 50%;
+          transform: translateY(-50%);
+          width: 6rpx;
+          height: 32rpx;
+          background: #e9c279;
+          border-radius: 3rpx;
+        }
+      }
+
+      .account-item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 20rpx 24rpx;
+        background: #f8f9fa;
+        border-radius: 16rpx;
+        border: 2rpx solid #e9ecef;
+        transition: all 0.3s ease;
+
+        &:active {
+          background: #f0f0f0;
+          transform: scale(0.98);
+        }
+
+        .account-info {
+          display: flex;
+          align-items: center;
+
+          .bank-icon {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin-right: 24rpx;
+
+            .icon {
+              font-size: 28rpx;
+            }
+          }
+
+          .account-details {
+            .account-number {
+              color: #333;
+              font-size: 30rpx;
+              font-weight: 500;
+            }
+          }
+        }
+
+        .arrow {
+          color: #999;
+          font-size: 32rpx;
+          font-weight: 300;
+        }
+      }
+
+      .amount-input-container {
+        display: flex;
+        align-items: center;
+        background-color: #ededed;
+        border-radius: 15rpx;
+        padding: 25rpx 24rpx;
+
+        transition: border-color 0.3s ease;
+
+        &:focus-within {
+          border-color: #e9c279;
+        }
+
+        .currency-symbol {
+          color: #333;
+          font-size: 32rpx;
+          font-weight: 600;
+          margin-right: 16rpx;
+        }
+
+        .amount-input {
+          height: 100%;
+          flex: 1;
+          display: flex;
+          align-items: center;
+          color: #333;
+          font-weight: 600;
+          font-size: 26rpx;
+
+          &::placeholder {
+            color: #999;
+            font-weight: 400;
+          }
+        }
+
+        .withdraw-all {
+          color: #e9c279;
+          font-size: 28rpx;
+          font-weight: 500;
+          transition: all 0.3s ease;
+
+          &:active {
+            background: #e9c279;
+            color: #fff;
+          }
+        }
+      }
+
+      .balance-info {
+        color: #666;
+        font-size: 24rpx;
+        margin-top: 16rpx;
+        padding-left: 8rpx;
+      }
+    }
+
+    .submit-section {
+      width: 100%;
+      display: flex;
+      justify-content: center;
+
+      .submit-btn {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 400rpx;
+        height: 75rpx;
+        background: linear-gradient(135deg, #e9c279 0%, #d4a853 100%);
+        color: #fff;
+        border-radius: 50rpx;
+        font-size: 30rpx;
+        border: none;
+        box-shadow: 0 6rpx 20rpx rgba(233, 194, 121, 0.3);
+        transition: all 0.3s ease;
+
+        &:active {
+          transform: translateY(2rpx);
+          box-shadow: 0 4rpx 12rpx rgba(233, 194, 121, 0.2);
+        }
+
+        &.disabled {
+          background: #e9ecef;
+          color: #adb5bd;
+          box-shadow: none;
+
+          &:active {
+            transform: none;
+          }
+        }
+      }
+    }
+
+    .tips-section {
+      padding: 30rpx 0;
+
+      .tips-title {
+        color: #333;
+        font-size: 28rpx;
+        font-weight: 600;
+        margin-bottom: 16rpx;
+      }
+
+      .tips-item {
+        color: #666;
+        font-size: 26rpx;
+        line-height: 40rpx;
+        margin-bottom: 8rpx;
+      }
+
+      .tips-note {
+        color: #999;
+        font-size: 24rpx;
+        line-height: 36rpx;
+        margin-top: 8rpx;
+      }
+    }
+  }
+}
+</style>

+ 4 - 1
static/css/base.css

@@ -200,7 +200,7 @@ page, view, text, image, button, navigator, scroll-view, swiper, input {
 page {
 	font-size: 28rpx;
 	/* background-color: #f5f5f5 !important; */
-	background-color: #ffffff !important;
+	background-color: #F8F7F1 !important;
 	color: #333;
 	/* height: 100%; */
 }
@@ -467,4 +467,7 @@ checkbox .uni-checkbox-input.uni-checkbox-input-checked::before {
 }
 .gray{
 	color: #c8c7cc;
+}
+.font333{
+	color:#333 !important;
 }

BIN
static/images/2-001.png


BIN
static/images/2-002.png


BIN
static/images/2-003.png


BIN
static/images/dianpu.png


BIN
static/images/indexBG.png


BIN
static/images/line.jpg


BIN
static/images/noAddress.png


BIN
static/images/noCart.png


BIN
static/images/noCoupon.png


BIN
static/images/noSearch.png


BIN
static/images/order.png


BIN
static/images/phone.png


BIN
static/images/right.png


BIN
static/images/share.png


BIN
static/images/shishijinjia.png


BIN
static/images/stop.png


BIN
static/images/tabbar/1-003.png


BIN
static/images/xyou.png


BIN
static/images/xzuo.png


BIN
static/images/zhankai.png


BIN
static/img/fenxiang_w.png


BIN
static/img/xiazai.png


+ 2 - 2
uni.scss

@@ -26,8 +26,8 @@
  $uni-color-error: #dd524d;
  
  /* 商城颜色变量 */
- $theme-color:#ffe079;
- $border-color:#f8c20f;
+ $theme-color:#F8C008;
+ $border-color:#F8C008;
  $note-red: #FF2442;
  $theme-color-opacity:rgba(233,51,35,.6);
  // $header-color: #e9c279;

+ 27 - 0
utils/useRealtimeTimestamp.js

@@ -0,0 +1,27 @@
+import { ref, onMounted, onUnmounted } from "vue";
+
+// 组合式函数:返回实时更新的时间戳
+export function useRealtimeTimestamp() {
+  // 初始化时间戳(当前时间)
+  const timestamp = ref(Date.now());
+  // 存储定时器ID,用于后续清除
+  let timer = null;
+
+  // 组件挂载时启动定时器
+  onMounted(() => {
+    // 每隔1000ms(1秒)更新一次时间戳
+    timer = setInterval(() => {
+      timestamp.value = Date.now(); // 实时更新
+    }, 1000);
+  });
+
+  // 组件卸载时清除定时器(关键:防止内存泄漏)
+  onUnmounted(() => {
+    if (timer) {
+      clearInterval(timer);
+      timer = null;
+    }
+  });
+
+  return { timestamp };
+}

+ 45 - 0
utils/util.js

@@ -3,6 +3,7 @@ import { HTTP_ADMIN_URL, BASE_OSS_URL } from "@/config/app.js";
 import { useAppStore } from "@/stores/app.js";
 import { pathToBase64 } from "@/plugin/image-tools/index.js";
 import { useToast } from "@/hooks/useToast";
+import pageJson from '@/pages.json';
 export default {
   /**
    * 移除数组中的某个数组并组成新的数组返回
@@ -856,3 +857,47 @@ export function isHttpsImage(imageUrl) {
     return BASE_OSS_URL + imageUrl;
   }
 }
+// 判断当前页面是否为 tabBar 页面
+export function isTabBarPage() {
+  // 获取当前页面栈
+  const pages = getCurrentPages();
+  // 获取当前页面路径(不包含参数部分)
+  if(!pages[pages.length - 1]){
+    return false;
+  }
+  const currentPagePath = pages[pages.length - 1].route;
+  // 获取 app.json 中的 tabBar 配置
+  const tabBarPages = getApp().globalData.tabBarPages || (() => {
+    // 从 app.json 中读取 tabBar 配置
+    // const appJson = import('@/pages.json');
+    // console.log('pageJson', pageJson)
+    const tabBarList = pageJson.tabBar ? pageJson.tabBar.list : [];
+    // 提取所有 tabBar 页面的路径
+    const pages = tabBarList.map(item => item.pagePath);
+    // 缓存到全局数据中,避免重复解析
+    getApp().globalData.tabBarPages = pages;
+    return pages;
+  })();
+
+  // 判断当前页面是否在 tabBar 配置中
+  return tabBarPages.includes(currentPagePath);
+}
+
+// 获取邀请码
+export function getSceneInfo(e) {
+  if (e.scene) {
+    const decodedScene = decodeURIComponent(e.scene);
+    const params = {};
+    if (decodedScene) {
+      decodedScene.split('&').forEach(item => {
+        const [key, value] = item.split('=');
+        if (key && value) {
+          params[key] = value;
+        }
+      });
+    }
+    console.log("获取邀请码-params", params)
+    return params;
+  }
+  return {};
+}