ext.liuqiwen3 1 天之前
父節點
當前提交
6cfd2a2878
共有 68 個文件被更改,包括 11225 次插入1412 次删除
  1. 50 0
      api/merchant.js
  2. 12 0
      api/user.js
  3. 44 0
      api/vault.js
  4. 281 0
      components/CategorySelector/index.vue
  5. 198 0
      components/OrderListCard/index.vue
  6. 378 0
      components/appointmentCalendar/index.vue
  7. 36 0
      components/emptyPage.vue
  8. 268 0
      components/payment/index.vue
  9. 182 29
      pages.json
  10. 51 26
      pages/index/index.vue
  11. 591 0
      pages/merchantCenter/index.vue
  12. 150 0
      pages/merchantCenter/postInformation.vue
  13. 420 0
      pages/merchantCenter/productManagement.vue
  14. 1004 0
      pages/merchantCenter/releaseProduct.vue
  15. 154 41
      pages/order_addcart/order_addcart.vue
  16. 974 0
      pages/order_details/index.vue
  17. 394 0
      pages/order_list/index.vue
  18. 42 36
      pages/personal_info/personal_info.vue
  19. 47 51
      pages/user/index.vue
  20. 4 1
      pages/users/browsing_history/index.vue
  21. 18 12
      pages/users/login/index.vue
  22. 206 0
      pages/users/user_return_list/index.vue
  23. 485 598
      pages/users/vault/buy.vue
  24. 36 30
      pages/users/vault/index.vue
  25. 887 0
      pages/users/vault/recharge.vue
  26. 44 39
      pages/users/vault/storeMetal/GoldMailForm.vue
  27. 3 1
      pages/users/vault/storeMetal/gmReport.vue
  28. 10 10
      pages/users/vault/storeMetal/goldBullionStock.vue
  29. 109 76
      pages/users/vault/storeMetal/index.vue
  30. 214 338
      pages/users/vault/storeMetal/metalExchange.vue
  31. 503 0
      pages/users/vault/storeMetal/metalExchangeList.vue
  32. 614 0
      pages/users/vault/storeMetal/metalExchangeWithdraw.vue
  33. 30 38
      pages/users/vault/storeMetal/nonLogisticsGold.vue
  34. 6 2
      pages/users/vault/storeMetal/order.vue
  35. 72 83
      pages/users/vault/withdraw.vue
  36. 二進制
      static/images/2-001.png
  37. 二進制
      static/images/empty-box.png
  38. 二進制
      static/images/hand.png
  39. 二進制
      static/images/orderTime.png
  40. 二進制
      static/images/product.png
  41. 二進制
      static/images/sb_btn.png
  42. 二進制
      static/images/setting/fabu.png
  43. 二進制
      static/images/setting/jinqian.png
  44. 二進制
      static/images/setting/kabao.png
  45. 二進制
      static/images/setting/kucun.png
  46. 二進制
      static/images/setting/mendian.png
  47. 二進制
      static/images/setting/shangpin.png
  48. 二進制
      static/images/writeOff.jpg
  49. 二進制
      static/recycle/choose.png
  50. 二進制
      static/recycle/kong.png
  51. 二進制
      static/recycle/nochoose.png
  52. 二進制
      static/recycle/pass.png
  53. 二進制
      static/recycle/success.png
  54. 6 1
      stores/app.js
  55. 131 0
      uni_modules/sp-editor/changelog.md
  56. 825 0
      uni_modules/sp-editor/components/sp-editor/color-picker.vue
  57. 140 0
      uni_modules/sp-editor/components/sp-editor/fab-tool.vue
  58. 152 0
      uni_modules/sp-editor/components/sp-editor/link-edit.vue
  59. 852 0
      uni_modules/sp-editor/components/sp-editor/sp-editor.vue
  60. 24 0
      uni_modules/sp-editor/icons/custom-icon.css
  61. 238 0
      uni_modules/sp-editor/icons/editor-icon.css
  62. 83 0
      uni_modules/sp-editor/package.json
  63. 19 0
      uni_modules/sp-editor/readme.md
  64. 1 0
      uni_modules/sp-editor/static/image-resize.min.js
  65. 8 0
      uni_modules/sp-editor/static/quill.min.js
  66. 132 0
      uni_modules/sp-editor/utils/index.js
  67. 94 0
      utils/SubscribeMessage.js
  68. 3 0
      utils/util.js

+ 50 - 0
api/merchant.js

@@ -53,4 +53,54 @@ export function fetchMerchantRecordAPI(params) {
  */
 export function fetchMerchantRestockListAPI(params) {
   return request.get(`merchant/restock/list?metalType=${params.metalType}&limit=${params.limit}&page=${params.page}&type=${params.type}`);
+}
+
+/**
+ * 获取商家销售汇总数据
+ * @param
+ */
+export function merchantSalesSummary(data) {
+  return request.get(`order/merchant/sales/summary`,data);
+}
+/**
+ * 获取商家销售汇总数据
+ * @param
+ */
+export function productsList(data) {
+  return request.get(`products`,data);
+}
+/**
+ * 商品上架
+ * @param
+ */
+export function productPutOnShell(id) {
+  return request.get(`product/putOnShell/${id}`);
+}
+/**
+ * 商品下架
+ * @param
+ */
+export function productOffShell(id) {
+  return request.get(`product/offShell/${id}`);
+}
+/**
+ * 获取商品分类
+ * @param
+ */
+export function productCategory(data) {
+  return request.get(`category/list/tree`,data);
+}
+/**
+ * 获取运费模板
+ * @param
+ */
+export function templatesList(data) {
+  return request.get(`express/shipping/templates/list`,data);
+}
+/**
+ * 新增商品
+ * @param
+ */
+export function productSave(data) {
+  return request.post(`product/save`,data);
 }

+ 12 - 0
api/user.js

@@ -177,4 +177,16 @@ export function userPayPasswordConfirmAPI(data) {
  */
 export function userEdit(data) {
   return request.post("user/edit", data);
+}
+/**
+ * 获取默认支付账户
+ */
+export function getDefaultAccount() {
+  return request.get(`user/account/default`);
+}
+/**
+ * 提现
+ */
+export function withdrawToCard(data) {
+  return request.post("extract/cash", data);
 }

+ 44 - 0
api/vault.js

@@ -98,3 +98,47 @@ export function goldprincipalList2(data) {
 	return request.get('goldprincipal/list2', data);
 }
 
+/**
+ * 创建预约
+ */
+export function createReservation(data) {
+	return request.post("plate-reservation/create", data);
+}
+
+/**
+ * 查询预约
+ */
+export function getReservationDetail(reservationId) {
+	return request.get(`plate-reservation/detail/${reservationId}`);
+}
+/**
+ * 提料 预约订单列表
+ */
+export function getMyReservations(data) {
+	return request.get("plate-reservation/my-reservations", data);
+}
+/**
+ * 提料 订单列表
+ */
+export function getMetalOrderList(data) {
+	return request.get("goldprincipal/plate-exchange-orders", data);
+}
+/**
+ * 取消预约
+ * @param {number} reservationId - 预约ID
+ */
+export function cancelReservation(reservationId) {
+	return request.post(`plate-reservation/cancel/${reservationId}`);
+}
+/**
+ * 新 - 提料兑换订单
+ */
+export function nest_exchangeMetalOrder(data) {
+	return request.post("goldprincipal/plate-exchange2", data);
+}
+/**
+ * 根据克重和金属类型查询可预约时间
+ */
+export function quotaByWeight(data) {
+	return request.get("plate-reservation/quotaByWeight", data);
+}

+ 281 - 0
components/CategorySelector/index.vue

@@ -0,0 +1,281 @@
+<template>
+  <view class="category-selector">
+    <!-- 一级分类 -->
+    <scroll-view class="first-level" scroll-y>
+      <view
+          v-for="item in categoryList"
+          :key="item.id"
+          class="first-item"
+          :class="{ active: selectedFirstLevel.includes(item.id) }"
+          @click="toggleFirstLevel(item)"
+      >
+        <view class="item-content">
+<!--          <image-->
+<!--              v-if="item.extra"-->
+<!--              :src="item.extra"-->
+<!--              class="category-icon"-->
+<!--              mode="aspectFit"-->
+<!--          />-->
+          <text class="category-name">{{ item.name }}</text>
+        </view>
+        <view class="checkbox">
+          <text v-if="selectedFirstLevel.includes(item.id)" class="checked">✓</text>
+          <text v-else class="unchecked">○</text>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 二级分类 -->
+    <scroll-view class="second-level" scroll-y v-if="hasSecondLevel">
+      <view
+          v-for="child in currentSecondLevel"
+          :key="child.id"
+          class="second-item"
+          :class="{ active: selectedSecondLevel.includes(child.id) }"
+          @click="toggleSecondLevel(child)"
+      >
+        <text class="category-name">{{ child.name }}</text>
+        <view class="checkbox">
+          <text v-if="selectedSecondLevel.includes(child.id)" class="checked">✓</text>
+          <text v-else class="unchecked">○</text>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+  categoryList: {
+    type: Array,
+    default: () => []
+  },
+  // 初始选中的分类ID数组
+  selectedIds: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const emit = defineEmits(['change'])
+
+// 选中的一级分类ID
+const selectedFirstLevel = ref([])
+// 选中的二级分类ID
+const selectedSecondLevel = ref([])
+
+// 当前显示二级分类的一级分类
+const currentFirstLevel = ref(null)
+// 当前显示的二级分类列表
+const currentSecondLevel = computed(() => {
+  if (!currentFirstLevel.value) return []
+  const parent = props.categoryList.find(item => item.id === currentFirstLevel.value)
+  return parent?.child || []
+})
+
+// 是否有二级分类显示
+const hasSecondLevel = computed(() => currentSecondLevel.value.length > 0)
+
+// 初始化选中状态
+const initSelected = () => {
+  selectedFirstLevel.value = []
+  selectedSecondLevel.value = []
+
+  props.selectedIds.forEach(id => {
+    // 检查是否是一级分类
+    const firstLevel = props.categoryList.find(item => item.id === id)
+    if (firstLevel) {
+      selectedFirstLevel.value.push(id)
+    }
+
+    // 检查是否是二级分类
+    for (const parent of props.categoryList) {
+      if (parent.child) {
+        const secondLevel = parent.child.find(child => child.id === id)
+        if (secondLevel) {
+          selectedSecondLevel.value.push(id)
+          // 如果选中了二级分类,自动选中对应的一级分类
+          if (!selectedFirstLevel.value.includes(parent.id)) {
+            selectedFirstLevel.value.push(parent.id)
+          }
+        }
+      }
+    }
+  })
+}
+
+// 切换一级分类选中状态
+const toggleFirstLevel = (item) => {
+  const index = selectedFirstLevel.value.indexOf(item.id)
+
+  if (index > -1) {
+    // 取消选中
+    selectedFirstLevel.value.splice(index, 1)
+    // 如果取消选中一级分类,同时取消选中其下的所有二级分类
+    if (item.child) {
+      item.child.forEach(child => {
+        const childIndex = selectedSecondLevel.value.indexOf(child.id)
+        if (childIndex > -1) {
+          selectedSecondLevel.value.splice(childIndex, 1)
+        }
+      })
+    }
+  } else {
+    // 选中
+    selectedFirstLevel.value.push(item.id)
+  }
+
+  // 设置当前显示的一级分类
+  if (item.child && item.child.length > 0) {
+    currentFirstLevel.value = item.id
+  } else {
+    currentFirstLevel.value = null
+  }
+
+  emitSelectionChange()
+}
+
+// 切换二级分类选中状态
+const toggleSecondLevel = (child) => {
+  const index = selectedSecondLevel.value.indexOf(child.id)
+
+  if (index > -1) {
+    selectedSecondLevel.value.splice(index, 1)
+  } else {
+    selectedSecondLevel.value.push(child.id)
+    // 如果选中二级分类,确保对应的一级分类也被选中
+    const parentId = getParentId(child.id)
+    if (parentId && !selectedFirstLevel.value.includes(parentId)) {
+      selectedFirstLevel.value.push(parentId)
+    }
+  }
+
+  emitSelectionChange()
+}
+
+// 根据二级分类ID获取一级分类ID
+const getParentId = (childId) => {
+  for (const parent of props.categoryList) {
+    if (parent.child) {
+      const child = parent.child.find(item => item.id === childId)
+      if (child) return parent.id
+    }
+  }
+  return null
+}
+
+// 发射选择变化事件
+const emitSelectionChange = () => {
+  const selectedIds = [...selectedFirstLevel.value, ...selectedSecondLevel.value]
+  emit('change', {
+    selectedIds,
+    firstLevel: selectedFirstLevel.value,
+    secondLevel: selectedSecondLevel.value
+  })
+}
+
+// 获取所有选中的分类ID
+const getSelectedIds = () => {
+  return [...selectedFirstLevel.value, ...selectedSecondLevel.value]
+}
+
+// 清空所有选择
+const clearSelection = () => {
+  selectedFirstLevel.value = []
+  selectedSecondLevel.value = []
+  currentFirstLevel.value = null
+  emitSelectionChange()
+}
+
+// 监听props变化
+watch(() => props.selectedIds, (newVal) => {
+  initSelected()
+})
+
+watch(() => props.categoryList, (newVal) => {
+  initSelected()
+})
+
+// 初始化
+initSelected()
+
+// 暴露方法给父组件
+defineExpose({
+  getSelectedIds,
+  clearSelection
+})
+</script>
+
+<style scoped>
+.category-selector {
+  display: flex;
+  height: 600rpx;
+  border: 1rpx solid #eee;
+  border-radius: 10rpx;
+}
+
+.first-level {
+  width: 40%;
+  background-color: #f8f8f8;
+}
+
+.second-level {
+  width: 60%;
+  background-color: #fff;
+}
+
+.first-item, .second-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.first-item.active {
+  background-color: #fff;
+  font-weight: bold;
+  color: #F8C008;
+}
+
+.second-item.active {
+  background-color: #f0f8ff;
+  color: #F8C008;
+}
+
+.item-content {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+
+.category-icon {
+  width: 40rpx;
+  height: 40rpx;
+  margin-right: 15rpx;
+}
+
+.category-name {
+  font-size: 28rpx;
+}
+
+.checkbox {
+  width: 40rpx;
+  height: 40rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.checked {
+  color: #F8C008;
+  font-weight: bold;
+}
+
+.unchecked {
+  color: #ccc;
+}
+</style>

+ 198 - 0
components/OrderListCard/index.vue

@@ -0,0 +1,198 @@
+<template>
+  <view class="item">
+    <view @click="goOrderDetails(order.orderId)">
+      <view class="title acea-row row-between-wrapper">
+        <view class="acea-row row-middle">
+          <text
+            class="sign cart-color acea-row row-center-wrapper"
+            v-if="
+              order.activityType !== '普通' && order.activityType !== '核销'
+            "
+            >{{ order.activityType }}</text
+          >
+          <view>{{ order.createTime }}</view>
+        </view>
+        <view class="font-color">{{ order.orderStatus }}</view>
+      </view>
+      <view
+        class="item-info acea-row row-between row-top"
+        v-for="(items, idx) in order.orderInfoList"
+        :key="idx"
+      >
+        <view class="pictrue">
+          <image :src="items.image"></image>
+        </view>
+        <view class="text">
+          <view class="name line2">{{ items.storeName }}</view>
+          <view class="money">
+            <view v-if="mallType === 0">¥{{ items.storePrice || 0 }}</view>
+            <view>x{{ items.cartNum }}</view>
+          </view>
+        </view>
+      </view>
+      <view class="totalPrice"
+        >共{{ order.totalNum }}件商品,总金额
+        <text class="money font-color" v-if="mallType === 0">¥{{ order.payPrice }}</text>
+        <text class="money font-color" v-else>{{ order.useIntegral }}贝币</text>
+      </view>
+    </view>
+    <view class="bottom acea-row row-right row-middle" v-if="mallType === '0'">
+      <view class="bnt cancelBnt" v-if="!order.paid" @click.stop="cancelOrder"
+        >取消订单</view
+      >
+      <view class="bnt bg-color" v-if="!order.paid" @click.stop="goPay"
+        >立即付款</view
+      >
+      <view
+        class="bnt bg-color"
+        v-else-if="order.status == 0 || order.status == 1 || order.status == 3"
+        @click.stop="goOrderDetails(order.orderId)"
+        >查看详情</view
+      >
+      <view
+        class="bnt bg-color"
+        v-else-if="order.status == 2"
+        @click.stop="goOrderDetails(order.orderId)"
+        >去评价</view
+      >
+      <view
+        class="bnt cancelBnt"
+        v-if="order.status == 3"
+        @click.stop="delOrder"
+        >删除订单</view
+      >
+    </view>
+  </view>
+</template>
+
+<script setup>
+const props = defineProps({
+  order: Object,
+  index: Number,
+  mallType: {
+    type: Number,
+    default: 0,
+  }, // 0: 水贝商城 1: 贝币商城
+});
+const emit = defineEmits([
+  "cancelOrder",
+  "goPay",
+  "goOrderDetails",
+  "delOrder",
+]);
+
+function cancelOrder() {
+  emit("cancelOrder", props.index, props.order.id);
+}
+function goPay() {
+  emit("goPay", props.order.payPrice, props.order.orderId);
+}
+function goOrderDetails(orderId) {
+  emit("goOrderDetails", orderId);
+}
+function delOrder() {
+  emit("delOrder", props.order.id, props.index);
+}
+</script>
+
+<style scoped lang="scss">
+.item {
+  background-color: #fff;
+  border-radius: 14rpx;
+  margin-bottom: 14rpx;
+
+  .title {
+    height: 84rpx;
+    padding: 0 24rpx;
+    border-bottom: 1rpx solid #eee;
+    font-size: 28rpx;
+    color: #282828;
+
+    .sign {
+      font-size: 24rpx;
+      padding: 0 13rpx;
+      height: 36rpx;
+      margin-right: 15rpx;
+      border-radius: 18rpx;
+    }
+  }
+
+  .item-info {
+    padding: 0 24rpx;
+    margin-top: 22rpx;
+
+    .pictrue {
+      width: 120rpx;
+      height: 120rpx;
+
+      image {
+        width: 100%;
+        height: 100%;
+        border-radius: 14rpx;
+      }
+    }
+
+    .text {
+      flex: 1;
+      // width: 500rpx;
+      font-size: 28rpx;
+      color: #999;
+      display: flex;
+      justify-content: space-between;
+      flex-wrap: nowrap;
+      padding: 0 0 0 20rpx;
+
+      .name {
+        width: 350rpx;
+        color: #282828;
+      }
+
+      .money {
+        text-align: right;
+        white-space: nowrap;
+        text {
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+
+  .totalPrice {
+    font-size: 26rpx;
+    color: #282828;
+    text-align: right;
+    margin: 27rpx 0 0 30rpx;
+    padding: 0 30rpx 30rpx 0;
+    border-bottom: 1rpx solid #eee;
+
+    .money {
+      font-size: 28rpx;
+      font-weight: bold;
+    }
+  }
+
+  .bottom {
+    height: 107rpx;
+    padding: 0 30rpx;
+
+    .bnt {
+      width: 176rpx;
+      height: 60rpx;
+      text-align: center;
+      line-height: 60rpx;
+      color: #fff;
+      border-radius: 50rpx;
+      font-size: 27rpx;
+
+      &.cancelBnt {
+        border: 1rpx solid #ddd;
+        color: #aaa;
+      }
+
+      & ~ .bnt {
+        margin-left: 17rpx;
+      }
+    }
+  }
+}
+</style>

+ 378 - 0
components/appointmentCalendar/index.vue

@@ -0,0 +1,378 @@
+<template>
+  <uni-popup ref="popupRef" type="bottom" :safe-area="true">
+    <view class="calendar-container">
+      <!-- 头部 -->
+      <view class="calendar-header">
+        <view class="header-title">选择预约日期</view>
+        <view class="header-subtitle">{{ currentMonth }}</view>
+      </view>
+
+      <!-- 星期标题 -->
+      <view class="week-header">
+        <view class="week-item" v-for="week in weekDays" :key="week">
+          {{ week }}
+        </view>
+      </view>
+
+      <!-- 日历日期 -->
+      <view class="calendar-body">
+        <view
+          class="date-item"
+          v-for="(date, index) in dateList"
+          :key="index"
+          :class="{
+            empty: !date.day,
+            available: date.available,
+            unavailable: !date.available && date.day,
+            selected: date.selected,
+          }"
+          @click="selectDate(date)"
+        >
+          <view class="date-day" v-if="date.day">{{ date.day }}</view>
+          <view class="date-dot" v-if="date.selected"></view>
+        </view>
+      </view>
+
+      <!-- 无可用日期提示(在日历下方、底部按钮上方) -->
+      <view v-if="hasNoAvailableDates" class="no-available-tip">
+        无可预约的日期
+      </view>
+
+      <!-- 底部按钮 -->
+      <view class="calendar-footer">
+        <view class="legend">
+          <view class="legend-item">
+            <view class="legend-dot available"></view>
+            <text>可预约</text>
+          </view>
+          <view class="legend-item">
+            <view class="legend-dot unavailable"></view>
+            <text>不可预约</text>
+          </view>
+        </view>
+        <view class="button-group">
+          <button class="btn-cancel" @click="close">取消</button>
+          <button
+            class="btn-confirm"
+            @click="confirm"
+            :disabled="!selectedDate"
+          >
+            确认
+          </button>
+        </view>
+      </view>
+    </view>
+  </uni-popup>
+</template>
+
+<script setup>
+import { ref, computed, watch } from "vue";
+import { quotaByWeight } from "@/api/vault"; // 引入克重查询接口
+
+const hasNoAvailableDates = ref(false);
+
+const props = defineProps({
+  metalType: {
+    type: Number,
+    default: 1,
+  },
+  reservedWeight: {
+    // 新增:接收父组件传递的克重
+    type: Number,
+    required: true,
+    validator: (val) => val > 0,
+  },
+});
+
+const emit = defineEmits(["confirm"]);
+
+const popupRef = ref(null);
+const weekDays = ["日", "一", "二", "三", "四", "五", "六"];
+const dateList = ref([]);
+const availableDates = ref([]); // 存储可预约日期(从接口获取)
+const selectedDate = ref(null);
+const currentYear = ref(new Date().getFullYear());
+const currentMonthNum = ref(new Date().getMonth() + 1);
+
+// 当前月份显示
+const currentMonth = computed(() => {
+  return `${currentYear.value}年${currentMonthNum.value}月`;
+});
+
+// 打开弹窗
+const open = async () => {
+  popupRef.value?.open();
+  await fetchAvailableDates(); // 打开时查询可预约日期
+  generateCalendar();
+};
+
+// 关闭弹窗
+const close = () => {
+  popupRef.value?.close();
+  selectedDate.value = null;
+};
+
+// 获取可预约日期(基于克重和金属类型)
+const fetchAvailableDates = async () => {
+  try {
+    const res = await quotaByWeight({
+      metalType: props.metalType,
+      weight: props.reservedWeight,
+    });
+    const availableDateList = res.data
+      .filter((item) => item.canReserve)
+      .map((item) => item.date);
+    availableDates.value = availableDateList;
+    hasNoAvailableDates.value = availableDateList.length === 0;
+  } catch (error) {
+    console.error("获取可预约日期失败:", error);
+    uni.showToast({ title: "获取日期失败", icon: "none" });
+  }
+};
+
+// 生成日历
+const generateCalendar = () => {
+  const year = currentYear.value;
+  const month = currentMonthNum.value;
+
+  // 获取当月第一天是星期几(0-6)
+  const firstDay = new Date(year, month - 1, 1).getDay();
+  // 获取当月天数
+  const daysInMonth = new Date(year, month, 0).getDate();
+
+  const dates = [];
+
+  // 填充空白(月初前的空白)
+  for (let i = 0; i < firstDay; i++) {
+    dates.push({ day: null, available: false, selected: false });
+  }
+
+  // 填充日期
+  for (let day = 1; day <= daysInMonth; day++) {
+    const dateStr = `${year}-${String(month).padStart(2, "0")}-${String(
+      day
+    ).padStart(2, "0")}`;
+    // 判断是否在可预约列表中
+    const isAvailable = availableDates.value.includes(dateStr);
+
+    dates.push({
+      day,
+      date: dateStr,
+      available: isAvailable,
+      selected: false,
+    });
+  }
+
+  dateList.value = dates;
+};
+
+// 选择日期
+const selectDate = (date) => {
+  if (!date.day || !date.available) {
+    if (date.day && !date.available) {
+      uni.showToast({ title: "该日期不可预约", icon: "none" });
+    }
+    return;
+  }
+
+  // 取消之前的选择
+  dateList.value.forEach((item) => {
+    item.selected = false;
+  });
+
+  // 设置新选择
+  date.selected = true;
+  selectedDate.value = date.date;
+};
+
+// 确认选择
+const confirm = () => {
+  if (!selectedDate.value) {
+    uni.showToast({ title: "请选择预约日期", icon: "none" });
+    return;
+  }
+
+  emit("confirm", selectedDate.value);
+  close();
+};
+
+// 监听金属类型或克重变化,重新获取日期
+watch([() => props.metalType, () => props.reservedWeight], async () => {
+  if (props.reservedWeight > 0) {
+    // 确保克重有效
+    await fetchAvailableDates();
+    generateCalendar();
+  }
+});
+
+defineExpose({
+  open,
+  close,
+});
+</script>
+
+<style lang="scss" scoped>
+.calendar-container {
+  background: #fff;
+  border-radius: 30rpx 30rpx 0 0;
+  padding: 40rpx 30rpx 60rpx;
+}
+
+.calendar-header {
+  text-align: center;
+  margin-bottom: 40rpx;
+
+  .header-title {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: #333;
+    margin-bottom: 10rpx;
+  }
+
+  .header-subtitle {
+    font-size: 28rpx;
+    color: #999;
+  }
+}
+
+.week-header {
+  display: flex;
+  justify-content: space-around;
+  margin-bottom: 20rpx;
+
+  .week-item {
+    width: 14.28%;
+    text-align: center;
+    font-size: 28rpx;
+    color: #666;
+    font-weight: 500;
+  }
+}
+
+.calendar-body {
+  display: flex;
+  flex-wrap: wrap;
+  margin-bottom: 40rpx;
+
+  .date-item {
+    width: 14.28%;
+    height: 100rpx;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+    margin-bottom: 10rpx;
+
+    .date-day {
+      font-size: 30rpx;
+      color: #333;
+    }
+
+    .date-dot {
+      width: 8rpx;
+      height: 8rpx;
+      background: #e9c279;
+      border-radius: 50%;
+      margin-top: 8rpx;
+    }
+
+    &.empty {
+      pointer-events: none;
+    }
+
+    &.available {
+      .date-day {
+        color: #333;
+        font-weight: 500;
+      }
+
+      &:active {
+        background: rgba(233, 194, 121, 0.1);
+        border-radius: 50%;
+      }
+    }
+
+    &.unavailable {
+      .date-day {
+        color: #ccc;
+      }
+    }
+
+    &.selected {
+      .date-day {
+        background: #e9c279;
+        color: #fff;
+        width: 60rpx;
+        height: 60rpx;
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-weight: bold;
+      }
+    }
+  }
+}
+
+.calendar-footer {
+  .legend {
+    display: flex;
+    justify-content: center;
+    gap: 40rpx;
+    margin-bottom: 30rpx;
+
+    .legend-item {
+      display: flex;
+      align-items: center;
+      font-size: 24rpx;
+      color: #666;
+
+      .legend-dot {
+        width: 30rpx;
+        height: 30rpx;
+        border-radius: 50%;
+        margin-right: 10rpx;
+
+        &.available {
+          background: #333;
+        }
+
+        &.unavailable {
+          background: #ccc;
+        }
+      }
+    }
+  }
+
+  .button-group {
+    display: flex;
+    gap: 20rpx;
+
+    button {
+      flex: 1;
+      height: 80rpx;
+      border-radius: 40rpx;
+      font-size: 30rpx;
+      border: none;
+    }
+
+    .btn-cancel {
+      background: #f5f5f5;
+      color: #666;
+    }
+
+    .btn-confirm {
+      background: #e9c279;
+      color: #fff;
+    }
+  }
+}
+.no-available-tip {
+  color: #ff1e0f; // 红色提示
+  font-size: 28rpx;
+  text-align: center;
+  padding: 15rpx 0; // 上下留白
+  width: 100%;
+}
+</style>

+ 36 - 0
components/emptyPage.vue

@@ -0,0 +1,36 @@
+<template>
+  <view class="empty-box">
+    <image src="../static/images/empty-box.png"></image>
+    <view class="txt">{{ title }}</view>
+  </view>
+</template>
+
+<script setup>
+defineProps({
+  title: {
+    type: String,
+    default: '暂无记录'
+  }
+})
+</script>
+
+<style lang="scss">
+.empty-box {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  margin-top: 200rpx;
+  padding-bottom: 60rpx;
+
+  image {
+    width: 414rpx;
+    height: 240rpx;
+  }
+
+  .txt {
+    font-size: 26rpx;
+    color: #999;
+  }
+}
+</style>

+ 268 - 0
components/payment/index.vue

@@ -0,0 +1,268 @@
+<template>
+  <up-popup :show="showPopup" :closeOnClickOverlay="true" @close="close">
+    <view class="payment">
+      <view class="title acea-row row-center-wrapper">
+        选择付款方式<text class="iconfont icon-guanbi" @click="close"></text>
+      </view>
+      <view
+        class="item acea-row row-between-wrapper"
+        @click="goPay(item)"
+        v-for="(item, index) in payMode"
+        :key="index"
+      >
+        <view class="left acea-row row-between-wrapper">
+          <view class="iconfont" :class="item.icon"></view>
+          <view class="text">
+            <view class="name">{{ item.name }}</view>
+            <view class="info" v-if="item.value === 'yue'">
+              {{ item.title }}
+              <span class="money">¥{{ appStore.userInfo.nowMoney }}</span>
+            </view>
+            <view class="info" v-else>{{ item.title }}</view>
+          </view>
+        </view>
+        <!-- <view class="iconfont icon-xiangyou"></view> -->
+      </view>
+    </view>
+  </up-popup>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { usePayment } from "@/hooks/usePayment.js";
+import { wechatOrderPay, wechatQueryPayResult } from "@/api/order.js";
+import { useAppStore } from "@/stores/app";
+import { useToast } from "@/hooks/useToast";
+import { getUserInfo } from "@/api/user";
+
+const emit = defineEmits(["closePopup", "payComplete", "payFail"]);
+
+const { Toast } = useToast();
+const { submitPayment } = usePayment();
+
+const props = defineProps({
+  mallType: {
+    type: Number,
+    default: 0, // 0: 水贝商城 1: 贝币商城
+  },
+  payMode: {
+    type: Array,
+    default: () => [],
+  },
+  showPopup: {
+    type: Boolean,
+    default: false,
+  },
+  order_id: {
+    type: String,
+    default: "",
+  },
+  totalPrice: {
+    type: String,
+    default: "0",
+  },
+});
+
+const appStore = useAppStore();
+const systemPlatform = computed(() => appStore.systemPlatform);
+
+function close() {
+  // emit("onChangeFun", { action: "payClose" });
+  emit("closePopup");
+}
+
+async function fetchUserInfo() {
+  const { data } = await getUserInfo();
+  appStore.UPDATE_USERINFO(data);
+}
+
+onLoad(() => {
+  fetchUserInfo();
+});
+
+async function goPay(item) {
+  try {
+    const number = item.number || 0;
+    const paytype = item.value;
+    let goPages = "/pages/order_pay_status/index?order_id=" + props.order_id;
+    if (!props.order_id) {
+      return Toast({ title: "请选择要支付的订单" });
+    }
+    if (
+      paytype === "yue" &&
+      parseFloat(number) < parseFloat(props.totalPrice)
+    ) {
+      return Toast({ title: "余额不足!" });
+    }
+    uni.showLoading({ title: "支付中" });
+    const params = {
+      mallType: props.mallType,
+      orderNo: props.order_id,
+      payChannel: item.payChannel,
+      payType: paytype,
+    };
+    const res = await wechatOrderPay(params);
+    let jsConfig = res.data.jsConfig;
+    let orderId = res.data.orderNo;
+    switch (res.data.payType) {
+      case "alipay":
+        const result = await submitPayment({
+          type: res.data.payType,
+          orderInfo: res.data.alipayRequest,
+        });
+        if (result && result.status === "success") {
+          emit("payComplete");
+          return Toast(
+            { title: result.message, icon: "success" },
+            { tab: 4, url: goPages }
+          );
+        } else {
+          emit("payFail", { message: result.message || "支付失败" });
+          return Toast(
+            { title: result.message },
+            { tab: 5, url: goPages + "&status=0" }
+          );
+        }
+        break;
+      case "yue":
+        Toast(
+          { title: "余额支付成功", icon: "success" },
+          { tab: 4, url: goPages }
+        );
+        emit("payComplete");
+        break;
+      case "weixinh5":
+        location.replace(
+          jsConfig.mwebUrl +
+            "&redirect_url=" +
+            window.location.protocol +
+            "//" +
+            window.location.host +
+            goPages +
+            "&status=1"
+        );
+        Toast({ title: "支付中" });
+        emit("payComplete");
+        break;
+      case "weixinApp":
+        const wxPayResult = await submitPayment({
+          type: res.data.payType,
+          orderInfo: res.data.prepayWithRequestPaymentResponse,
+        });
+        let wxPayFailMsg = wxPayResult?.message || "支付失败";
+        if (wxPayResult.status === "支付成功") {
+          emit("payComplete");
+          return Toast(
+            { title: "支付成功", icon: "success" },
+            { tab: 4, url: goPages }
+          );
+        } else if (wxPayResult.status === "支付失败") {
+          emit("payFail", { message: wxPayFailMsg });
+          return Toast(
+            { title: "支付失败" },
+            { tab: 5, url: `${goPages}&msg=${wxPayFailMsg}` }
+          );
+        } else if (wxPayResult.status === "用户取消支付") {
+          emit("payFail", { message: "用户取消支付" });
+          return Toast(
+            { title: "用户取消支付" },
+            {
+              tab: 5,
+              url: `${goPages}&msg=用户取消支付`,
+            }
+          );
+        }
+        break;
+    }
+  } catch (error) {
+    console.error("goPay error", error);
+    emit("payFail", { message: error.message || error.msg || "支付失败" });
+    return Toast({
+      title: error.message || error.msg || "支付失败",
+    });
+  } finally {
+    uni.hideLoading();
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.payment {
+  background-color: #fff;
+}
+
+.payment .title {
+  text-align: center;
+  height: 123rpx;
+  font-size: 32rpx;
+  color: #282828;
+  font-weight: bold;
+  padding-right: 30rpx;
+  margin-left: 30rpx;
+  position: relative;
+  border-bottom: 1rpx solid #eee;
+}
+
+.payment .title .iconfont {
+  position: absolute;
+  right: 30rpx;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 43rpx;
+  color: #8a8a8a;
+  font-weight: normal;
+}
+
+.payment .item {
+  border-bottom: 1rpx solid #eee;
+  height: 130rpx;
+  margin-left: 30rpx;
+  padding-right: 30rpx;
+}
+
+.payment .item .left {
+  width: 610rpx;
+}
+
+.payment .item .left .text {
+  width: 540rpx;
+}
+
+.payment .item .left .text .name {
+  font-size: 32rpx;
+  color: #282828;
+}
+
+.payment .item .left .text .info {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.payment .item .left .text .info .money {
+  color: #ff9900;
+}
+
+.payment .item .left .iconfont {
+  font-size: 45rpx;
+  color: #09bb07;
+}
+
+.payment .item .left .iconfont.icon-zhifubao {
+  color: #00aaea;
+}
+
+.payment .item .left .iconfont.icon-balance {
+  color: #ff9900;
+  font-size: 50rpx;
+}
+
+.payment .item .left .iconfont.icon-yuezhifu1 {
+  color: #eb6623;
+}
+
+.payment .item .iconfont {
+  font-size: 0.3rpx;
+  color: #999;
+}
+</style>

+ 182 - 29
pages.json

@@ -128,6 +128,69 @@
 				"navigationBarTextStyle": "black",
 				"enablePullDownRefresh": false
 			}
+		},
+		{
+			"path": "pages/order_list/index",
+			"style": {
+				"navigationBarTitleText": "我的订单",
+				"navigationBarBackgroundColor": "#ffe079",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/order_details/index",
+			"style": {
+				"navigationBarTitleText": "订单详情",
+				"navigationBarBackgroundColor": "#ffe079",
+				"navigationBarTextStyle": "black",
+				"app-plus": {
+					// #ifdef APP-PLUS
+					"titleNView": {
+						"type": "default"
+					}
+					// #endif
+				}
+			}
+		},
+		{
+			"path": "pages/merchantCenter/index",
+			"style": {
+				"navigationBarTitleText": "门店主页",
+				"navigationBarBackgroundColor": "#ffe079",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/merchantCenter/postInformation",
+			"style": {
+				"navigationBarTitleText": "发布商品",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/merchantCenter/productManagement",
+			"style": {
+				"navigationBarTitleText": "商品管理",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/merchantCenter/releaseProduct",
+			"style": {
+				"navigationBarTitleText": "发布商品",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/merchantCenter/productCenter",
+			"style": {
+				"navigationBarTitleText": "产品中心",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black"
+			}
 		}
 	],
 	"subPackages": [
@@ -162,7 +225,7 @@
 					"path": "my_merchant/index",
 					"style": {
 						"navigationBarTitleText": "我的商家",
-						"navigationBarBackgroundColor": "#e93323",
+						"navigationBarBackgroundColor": "#FFE079",
 						"navigationBarTextStyle": "black"
 					}
 				},
@@ -190,16 +253,6 @@
 						"navigationBarTextStyle": "black"
 					}
 				},
-				{
-					"path": "vault/storeMetal/order",
-					"style": {
-						"navigationBarTitleText": "邮寄存金订单",
-						"navigationBarBackgroundColor": "#ffe079",
-						"navigationBarTextStyle": "black",
-						"enablePullDownRefresh": true,
-						"onReachBottomDistance": 100
-					}
-				},
 				{
 					"path": "vault/recycle/recyle_order",
 					"style": {
@@ -210,16 +263,6 @@
 						"onReachBottomDistance": 100
 					}
 				},
-				{
-					"path": "vault/recycle/report",
-					"style": {
-						"navigationBarTitleText": "约价回收确认报告",
-						"navigationBarBackgroundColor": "#ffe079",
-						"navigationBarTextStyle": "black",
-						"enablePullDownRefresh": true,
-						"onReachBottomDistance": 100
-					}
-				},
 				{
 					"path": "vault/recycle/order_fill",
 					"style": {
@@ -230,14 +273,6 @@
 						"onReachBottomDistance": 100
 					}
 				},
-				{
-					"path": "vault/storeMetal/goldBullionStock",
-					"style": {
-						"navigationBarTitleText": "存金",
-						"navigationBarBackgroundColor": "#ffe079",
-						"navigationBarTextStyle": "black"
-					}
-				},
 				{
 					"path": "vault/save_gold",
 					"style": {
@@ -353,6 +388,124 @@
 						"navigationBarBackgroundColor": "#fff",
 						"navigationBarTextStyle": "black"
 					}
+				},
+				{
+					"path": "user_return_list/index",
+					"style": {
+						"navigationBarTitleText": "退货列表",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "vault/recharge",
+					"style": {
+						"navigationBarTitleText": "充值",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "vault/withdraw",
+					"style": {
+						"navigationBarTitleText": "提现",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "vault/buy",
+					"style": {
+						"navigationBarTitleText": "买金",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"app-plus": {
+							// #ifdef APP-PLUS
+							"titleNView": {
+								"type": "default"
+							}
+							// #endif
+						}
+					}
+				},
+				{
+					"path": "vault/storeMetal/goldBullionStock",
+					"style": {
+						"navigationBarTitleText": "存金",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/storeMetal/index",
+					"style": {
+						"navigationBarTitleText": "卖料",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/storeMetal/order",
+					"style": {
+						"navigationBarTitleText": "邮寄存金订单",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black",
+						"enablePullDownRefresh": true,
+						"onReachBottomDistance": 100
+					}
+				},
+				{
+					"path": "vault/recycle/report",
+					"style": {
+						"navigationBarTitleText": "检测报告",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/storeMetal/metalExchange",
+					"style": {
+						"navigationBarTitleText": "提料兑换",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/storeMetal/metalExchangeList",
+					"style": {
+						"navigationBarTitleText": "提料兑换列表",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "vault/storeMetal/metalExchangeWithdraw",
+					"style": {
+						"navigationBarTitleText": "提料确认下单",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
 				}
 			]
 		}

文件差異過大導致無法顯示
+ 51 - 26
pages/index/index.vue


文件差異過大導致無法顯示
+ 591 - 0
pages/merchantCenter/index.vue


+ 150 - 0
pages/merchantCenter/postInformation.vue

@@ -0,0 +1,150 @@
+<template>
+  <view class="publish-type-page">
+    <!-- 顶部标题区域 -->
+    <view class="page-header">
+      <text class="main-title">选择要发布类型</text>
+      <text class="sub-title">请根据您的需求选择合适的发布类型</text>
+    </view>
+
+    <!-- 发布选项区域 -->
+    <view class="publish-options">
+      <!-- 手动填写选项 -->
+      <view class="option-card" @click="setActiveOption('manual')">
+        <view class="option-header">
+          <view class="icon-text-wrapper">
+            <image class="option-icon" src="@/static/images/hand.png" mode="widthFix"></image>
+            <view>
+              <text class="option-title">手动填写</text>
+              <text class="option-description">适合发布全新商品或定制珠宝。您可以详细设置商品分类、属性、价格和描述。</text>
+            </view>
+          </view>
+        </view>
+
+        <button class="publish-button" @click="handleManualPublish">点击发布</button>
+      </view>
+
+      <!-- 产品中心选择选项 -->
+      <view class="option-card" @click="setActiveOption('template')">
+        <view class="option-header">
+          <view class="icon-text-wrapper">
+            <image class="option-icon" src="@/static/images/product.png" mode="widthFix"></image>
+            <view>
+              <text class="option-title">从产品中心选择</text>
+              <text class="option-description">从已有产品库中选择商品模板,快速发布相似商品。适合发布系列产品或标准款珠宝。</text>
+            </view>
+          </view>
+        </view>
+        <button class="publish-button" @click.stop="handleTemplatePublish">点击发布</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const activeOption = ref('manual')
+
+const setActiveOption = (option) => {
+  activeOption.value = option
+}
+
+const handleManualPublish = () => {
+  uni.navigateTo({
+    url: '/pages/merchantCenter/releaseProduct'
+  })
+}
+
+const handleTemplatePublish = () => {
+  // uni.navigateTo({
+  //   url: '/pages/goods/publish-from-template'
+  // })
+}
+</script>
+
+<style scoped>
+.publish-type-page {
+  min-height: 100vh;
+  background: #ffffff;
+  padding: 100rpx 32rpx;
+}
+
+/* 页面标题 */
+.page-header {
+  text-align: center;
+  margin-bottom: 60rpx;
+}
+
+.main-title {
+  display: block;
+  font-size: 36rpx;
+  color: #333;
+  line-height: 50rpx;
+  margin-bottom: 12rpx;
+}
+
+.sub-title {
+  display: block;
+  font-size: 28rpx;
+  color: #666;
+  line-height: 50rpx;
+}
+
+/* 发布选项 */
+.publish-options {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+
+.option-card {
+  background: #F9F7F0;
+  border-radius: 12rpx;
+  padding: 32rpx;
+}
+
+.option-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 20rpx;
+}
+
+.icon-text-wrapper {
+  display: flex;
+  gap: 20rpx;
+}
+
+.option-icon {
+  width: 96rpx;
+}
+
+.option-title {
+  font-size: 32rpx;
+  color: #333;
+}
+
+.option-description {
+  display: block;
+  font-size: 24rpx;
+  color: #666666;
+  line-height: 36rpx;
+  margin-bottom: 24rpx;
+}
+
+.publish-button {
+  background: #F8C008;
+  border: none;
+  border-radius: 16rpx;
+  color: #333333;
+  font-size: 32rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  width: 100%;
+  text-align: center;
+}
+
+.publish-button:active {
+  background: #ffb300;
+  transform: scale(0.98);
+}
+</style>

+ 420 - 0
pages/merchantCenter/productManagement.vue

@@ -0,0 +1,420 @@
+<template>
+  <view class="container">
+    <!-- 搜索栏 -->
+    <view class="search-bar">
+<!--      <up-search placeholder="搜索商品名称" shape="square" :clearabled="true"-->
+<!--                 height="36" bgColor="#F9F7F0" :showAction="false" v-model="searchVal"-->
+<!--                 @clear="onClear" @search="onSearch"-->
+<!--                 :focus="isFocus" @focus="onFocus" @blur="onBlur" ></up-search>-->
+      <view class="search-bar-con">
+        <view class="search-input-wrapper">
+          <uni-icons class="search-icon" type="search" size="18" color="#999"></uni-icons>
+          <input
+              class="search-input"
+              v-model="searchVal"
+              placeholder="请输入搜索内容"
+              placeholder-class="placeholder"
+              @input="onSearch"
+              @confirm="onSearch"
+              :focus="isFocus"
+          />
+          <view v-if="searchVal" class="clear-btn" @click="onClear">
+            <uni-icons type="clear" size="18" color="#999"></uni-icons>
+          </view>
+        </view>
+      </view>
+      <!-- 分类标签 -->
+      <up-tabs :list="tabList"
+               @click="tabChange"
+               lineColor="#F8C008"
+               lineWidth="24"
+               lineHeight="2"
+               :itemStyle="{
+               flex:1,
+               height:'44px',
+               marginTop:'8px'
+             }"
+               :activeStyle="{
+                color: '#F8C008',
+                fontWeight: 'bold',
+                transform: 'scale(1.05)'
+            }"
+               :inactiveStyle="{
+              color: '#333333',
+              transform: 'scale(1)'
+        }"></up-tabs>
+
+    </view>
+
+
+    <!-- 商品列表 -->
+    <view class="product-list">
+      <view class="product-card" v-for="(item, index) in goodsList" :key="index">
+        <view class="product-header">
+          <image class="product-image" :src="item.image" mode="aspectFit" />
+          <view class="product-info">
+            <view class="nameweight">
+              <text class="product-name">{{ item.storeName }}</text>
+              <text class="product-weight">{{ item.weight }}g</text>
+            </view>
+            <view class="nameweight">
+              <view class="price-info">
+                <text class="label">工费</text>
+                <view class="value"><text class="unit">¥</text>{{ item.totalLaborCost }}<text class="unit">/g</text></view>
+              </view>
+              <view class="price-info">
+                <text class="label">附加费</text>
+                <view class="value"><text class="unit">¥</text>{{ item.additionalAmount }}</view>
+              </view>
+            </view>
+            <view class="product-stats">
+              <text class="stat">销量:{{ Number(item.sales || 0) + Number(item.ficti || 0) }}</text>
+              <text class="stat">库存:{{ item.stock }}</text>
+            </view>
+          </view>
+        </view>
+
+        <view class="action-buttons">
+          <button class="btn btn-offline" v-show="params.isShow==1" @click="OffShellFn(item,index)">下架</button>
+          <button class="btn btn-offline" v-show="params.isShow==0" @click="PutOnShellFn(item,index)">上架</button>
+          <button class="btn btn-edit" @click="toEditProduct(item)">编辑</button>
+        </view>
+      </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 { productsList,productPutOnShell,productOffShell } from "@/api/merchant.js";
+import { useAppStore } from "@/stores/app";
+
+const appStore = useAppStore();
+
+const tabList = ref([
+  {name:'销售中',code:1},
+  {name:'已下架',code:0}
+])
+const searchVal = ref('')
+const goodsList = ref([]);
+const goodType = ref(1);
+// Pagination
+const params = ref({
+  page: 1,
+  limit: 10,
+  isShow:1,
+});
+const loading = ref(false);
+const goodScroll = ref(true);
+const merchantInfo = ref({})
+
+const isFocus = ref(false)
+
+const isNoDataState = computed(() => {
+  return goodsList.value.length === 0 && !loading.value;
+});
+
+
+onShow(() => {
+  merchantInfo.value = appStore.userInfo.merchant;
+  getGroomList()
+})
+
+const onFocus = () => {
+  console.log('获取焦点')
+  isFocus.value = true
+}
+
+const onBlur = () => {
+  console.log('失去焦点')
+  // 不要立即设置为false,避免焦点闪烁
+  setTimeout(() => {
+    isFocus.value = false
+  }, 200)
+}
+const tabChange = (item) => {
+  console.log('item',item)
+  goodsList.value = [];
+  loading.value = false;
+  goodScroll.value = true;
+  params.value.isShow = item.code;
+  params.value.page = 1;
+  getGroomList();
+}
+const onSearch = () => {
+  goodsList.value = [];
+  loading.value = false;
+  goodScroll.value = true;
+  params.value.page = 1;
+  getGroomList();
+}
+// 清除搜索内容
+const onClear = () => {
+  console.log('清除搜索内容');
+  searchVal.value = ''; // 手动清空关键词
+  // 如果需要清除后立即刷新列表,可以调用搜索
+  onSearch();
+}
+const getGroomList = async () => {
+  if (!goodScroll.value) return;
+  try {
+    loading.value = true;
+    params.value.merchantId = merchantInfo.value.id;
+    params.value.keyword = searchVal.value;
+    const { data } = await productsList(params.value);
+    goodsList.value = [...goodsList.value, ...data.list] || [];
+    goodScroll.value = data.list.length >= params.value.limit;
+    params.value.page++;
+  } catch (err) {
+    console.error(err);
+  } finally {
+    loading.value = false;
+  }
+};
+async function OffShellFn(obj,index) {
+  const {code} = await productOffShell(obj.id);
+  if(code == 200){
+    uni.showToast({ title: "操作成功", icon: "none" });
+    goodsList.value.splice(index,1);
+  }
+}
+async function PutOnShellFn(obj,index) {
+  const {code} = await productPutOnShell(obj.id);
+  if(code == 200){
+    uni.showToast({ title: "操作成功", icon: "none" });
+    goodsList.value.splice(index,1);
+  }
+
+}
+function toEditProduct (obj){
+  uni.navigateTo({
+    url:`pages/merchantCenter/releaseProduct?id=${obj.id}`
+  })
+}
+onReachBottom(() => {
+  getGroomList();
+});
+</script>
+
+<style lang="scss" scoped>
+
+/* 搜索栏样式 */
+.search-bar {
+  background: #fff;
+  padding: 30rpx 30rpx 0;
+}
+
+.search-input {
+  width: 100%;
+  height: 60rpx;
+  font-size: 28rpx;
+}
+
+/* 标签页样式 */
+.tabs {
+  display: flex;
+  background: white;
+  border-radius: 10rpx;
+  margin-bottom: 20rpx;
+}
+
+.tab {
+  flex: 1;
+  text-align: center;
+  padding: 20rpx;
+  font-size: 28rpx;
+  color: #666;
+}
+
+.tab.active {
+  color: #333;
+  font-weight: bold;
+  border-bottom: 4rpx solid #333;
+}
+.product-list{
+  padding: 30rpx;
+  box-sizing: border-box;
+}
+/* 商品卡片样式 */
+.product-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+}
+
+.product-header {
+  display: flex;
+  margin-bottom: 20rpx;
+}
+
+.product-image {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 10rpx;
+  margin-right: 20rpx;
+}
+
+.product-info {
+  flex: 1;
+}
+
+.nameweight{
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.product-name {
+  font-size: 28rpx;
+  color: #333;
+  display: block;
+}
+.product-weight {
+    background-color: rgba(197, 128, 3, 0.10);
+    color: #C58003;
+    font-size: 24rpx;
+    padding: 8rpx 16rpx;
+    border-radius: 8rpx;
+    float: right;
+}
+
+.price-info {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 8rpx;
+}
+
+.label {
+  font-size: 24rpx;
+  color: #666666;
+}
+
+.value {
+  color: #FD5F3C;
+  font-size: 32rpx;
+  .unit{
+    font-size: 24rpx;
+  }
+}
+
+/* 销售数据样式 */
+.product-stats {
+  display: flex;
+  margin-bottom: 20rpx;
+  gap: 10rpx;
+}
+
+.stat {
+  font-size: 24rpx;
+  color: #666;
+  padding: 10rpx;
+  background-color: #F5F7FA;
+  border-radius: 8rpx;
+}
+
+/* 操作按钮样式 */
+.action-buttons {
+  display: flex;
+  justify-content: space-between;
+  gap: 30rpx;
+}
+
+.btn {
+  flex: 1;
+  height: 60rpx;
+  line-height: 60rpx;
+  border-radius: 8rpx;
+  font-size: 28rpx;
+  border: none;
+}
+
+
+.btn-offline {
+  background: #F5F7FA;
+  color: #333;
+}
+
+.btn-edit {
+  background: #F8C008;
+  color: #333;
+}
+.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;
+
+  .iconfont {
+    margin-top: 2rpx;
+    font-size: 20rpx;
+  }
+}
+.search-bar-con {
+  display: flex;
+  align-items: center;
+  background-color: #fff;
+  border-bottom: 1rpx solid #eee;
+}
+
+.search-input-wrapper {
+  flex: 1;
+  position: relative;
+  display: flex;
+  align-items: center;
+  background-color: #F9F7F0;
+  border-radius: 16rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  padding: 0 20rpx;
+  box-sizing: border-box;
+}
+
+.search-icon {
+  margin-right: 20rpx;
+}
+
+.search-input {
+  flex: 1;
+  height: 100%;
+  font-size: 28rpx;
+  color: #333;
+}
+
+.placeholder {
+  color: #999;
+  font-size: 28rpx;
+}
+
+.clear-btn {
+  padding: 10rpx;
+  margin-left: 10rpx;
+}
+
+</style>

文件差異過大導致無法顯示
+ 1004 - 0
pages/merchantCenter/releaseProduct.vue


+ 154 - 41
pages/order_addcart/order_addcart.vue

@@ -31,7 +31,7 @@
             <block v-for="(merchant, merchantIndex) in groupedCartList" :key="merchantIndex">
               <view class="merchant-group">
                 <!-- 商家头部 -->
-                <view class="merchant-header acea-row row-between-wrapper">
+                <view class="merchant-header acea-row row-between-wrapper" @click="toMerchant(merchant)">
                   <view class="merchant-info acea-row row-middle">
                     <image class="merchant-logo" :src="merchant.logo" mode="aspectFill"></image>
                     <text class="merchant-name">{{ merchant.name }}</text>
@@ -68,30 +68,36 @@
                           <view class="item-info">
                             <view class="text">
                               <view
-                                  class="line1"
+                                  class="line1 namAndNum"
                                   :class="item.attrStatus ? '' : 'reColor'"
                               >
-                                <view class="storeName">
+                                <view class="storeName line1 ">
                                   {{ item.storeName }}
                                 </view>
-                                <view
-                                    class="carnum acea-row row-center-wrapper"
-                                    v-if="item.attrStatus"
-                                >
-                                  <view
-                                      class="reduce"
-                                      :class="item.numSub ? 'on' : ''"
-                                      @click.stop="subCart(index)"
-                                  >-</view
-                                  >
-                                  <view class="num">{{ item.cartNum }}</view>
+                                <view v-if="item.attrStatus">
                                   <view
-                                      class="plus"
-                                      :class="item.numAdd ? 'on' : ''"
-                                      @click.stop="addCart(index)"
-                                  >+</view
+                                      class="carnum acea-row row-center-wrapper"
+                                      v-if="item.editNum"
                                   >
+                                    <view
+                                        class="reduce"
+                                        :class="item.numSub ? 'on' : ''"
+                                        @click.stop="subCart(index)"
+                                    >-</view
+                                    >
+                                    <view class="num">{{ item.cartNum }}</view>
+                                    <view
+                                        class="plus"
+                                        :class="item.numAdd ? 'on' : ''"
+                                        @click.stop="addCart(index)"
+                                    >+</view
+                                    >
+                                  </view>
+                                  <view class="carnumTag" v-if="!item.editNum" @click.stop="item.editNum = true">
+                                    X{{ item.cartNum }}
+                                  </view>
                                 </view>
+
                               </view>
                               <view class="attribute-box infor line1">
                                 <view class="attribute-suk" v-show="item.suk">属性:{{ item.suk }}</view>
@@ -102,8 +108,10 @@
                               >¥{{ item.storePrice }}</view
                               >
                               <view class="labor-costs line1" v-if="item.attrStatus"
-                              >工费:{{ item.price }}/克</view
                               >
+                                <text class="tag">工费:{{ item.price }}/克</text>
+                                <text class="tag" style="margin-left: 10rpx;">附加费:{{ item.additionalAmount }}</text>
+                              </view>
                               <!-- <view class='money' v-if="item.attrStatus">¥{{item.truePrice}}</view> -->
                               <view
                                   class="reElection acea-row row-between-wrapper"
@@ -290,9 +298,10 @@
       v-if="cartList.valid.length > 0"
     >
       <view>
-        <up-checkbox-group shape="circle" @change="checkboxAllChange">
+        <up-checkbox-group shape="circle" @change="checkboxAllChange" v-model="allCheckboxValue">
           <!-- <checkbox value="all" :checked="!!isAllSelect" /> -->
           <up-checkbox
+              :name="'all'"
             activeColor="#F8C008"
             :checked="!!isAllSelect"
           ></up-checkbox>
@@ -302,6 +311,7 @@
       <view class="money acea-row row-middle" v-if="footerswitch == true">
         <text style="font-size: 24rpx;color: #333;">合计:</text>
         <text class="font-color">¥{{ selectCountPrice }}</text>
+
         <form @submit="subOrder" report-submit="true">
           <button class="placeOrder bg-color" formType="submit">
             结算
@@ -404,6 +414,18 @@ const showProductPopup = ref(false);
 
 const isLogin = appStore.isLogin;
 
+// 添加全选的值控制
+const allCheckboxValue = ref([]);
+
+// 监听isAllSelect的变化,同步到allCheckboxValue
+watch(isAllSelect, (newVal) => {
+  if (newVal) {
+    allCheckboxValue.value = ['all'];
+  } else {
+    allCheckboxValue.value = [];
+  }
+});
+
 onLoad((options) => {
   if (!isLogin) {
     toLogin();
@@ -464,11 +486,11 @@ const groupedCartList = computed(() => {
         groups[merchantId] = {
           id: merchantId,
           name: merchant.merchantName || '未知商家',
-          logo: merchant.merchantLogo || '/static/images/default-store.png',
+          logo: merchant.merchantLogo || '/static/avator.png',
           products: []
         };
       }
-
+      item.editNum = false;
       groups[merchantId].products.push(item);
     } else {
       // 如果没有商家信息,放到默认分组
@@ -477,7 +499,7 @@ const groupedCartList = computed(() => {
         groups[defaultId] = {
           id: defaultId,
           name: '其他商家',
-          logo: '/static/images/default-store.png',
+          logo: '/static/avator.png',
           products: []
         };
       }
@@ -760,6 +782,12 @@ function subCollect(event) {
 function subOrder(event) {
   const selectValueArr = selectValue.value;
   if (selectValueArr.length > 0) {
+    // 检查是否选择了多个商家的商品
+    if (hasMultipleMerchants()) {
+      return Toast({
+        title: "只能结算同一商家的商品",
+      });
+    }
     getPreOrderFn();
   } else {
     return Toast({
@@ -767,6 +795,30 @@ function subOrder(event) {
     });
   }
 }
+// 检查是否选择了多个商家的商品
+function hasMultipleMerchants() {
+  const selectValueArr = selectValue.value;
+  const validList = cartList.valid;
+
+  // 获取选中的商品
+  const selectedItems = validList.filter(item =>
+      selectValueArr.includes(item.id) && item.attrStatus
+  );
+
+  // 提取商家ID
+  const merchantIds = new Set();
+  selectedItems.forEach(item => {
+    if (item.sbMerchant && item.sbMerchant.id) {
+      merchantIds.add(item.sbMerchant.id);
+    } else {
+      // 如果没有商家信息,也作为一个独立的商家
+      merchantIds.add('default');
+    }
+  });
+
+  return merchantIds.size > 1;
+}
+
 
 function getPreOrderFn() {
   const shoppingCartId = selectValue.value.map((item) => {
@@ -784,11 +836,41 @@ function getPreOrderFn() {
 
 function checkboxAllChange(value) {
   if (value.length > 0) {
-    setAllSelectValue(1);
+    // 检查全选是否会选择多个商家
+    if (footerswitch.value && willSelectMultipleMerchants()) {
+      Toast({
+        title: "只能选择同一商家的商品进行结算",
+      });
+      // 重置全选状态为未选中
+      isAllSelect.value = false;
+      allCheckboxValue.value = [];
+      // 清空shopCheckbox的值
+      shopCheckbox.value = [];
+      setAllSelectValue(0);
+      return;
+    }else{
+      setAllSelectValue(1);
+    }
+
   } else {
     setAllSelectValue(0);
   }
 }
+// 检查全选是否会选择多个商家
+function willSelectMultipleMerchants() {
+  const validList = cartList.valid.filter(item => item.attrStatus);
+  const merchantIds = new Set();
+
+  validList.forEach(item => {
+    if (item.sbMerchant && item.sbMerchant.id) {
+      merchantIds.add(item.sbMerchant.id);
+    } else {
+      merchantIds.add('default');
+    }
+  });
+
+  return merchantIds.size > 1;
+}
 
 function setAllSelectValue(status) {
   const valid = cartList.valid;
@@ -815,6 +897,7 @@ function setAllSelectValue(status) {
   cartList.valid = [...valid];
   selectValue.value = selectValueArr;
   isAllSelect.value = status === 1;
+  console.log(isAllSelect.value)
   switchSelect();
 }
 
@@ -848,11 +931,19 @@ function checkboxChange(value) {
   }
 
   cartList.valid = [...valid];
-  isAllSelect.value = valid.length === checkedItems.length + invalidItems.length;
+  // isAllSelect.value = valid.length === checkedItems.length + invalidItems.length;
   selectValue.value = value;
+  // 计算全选状态
+  if (footerswitch.value) {
+    // 结算模式下,只计算有效商品
+    const validItems = valid.filter(item => item.attrStatus);
+    isAllSelect.value = validItems.length > 0 && value.length === validItems.length;
+  } else {
+    // 管理模式下,计算所有商品
+    isAllSelect.value = valid.length > 0 && value.length === valid.length;
+  }
   switchSelect();
 }
-
 function inArray(search, array) {
   for (let i in array) {
     if (array[i] == search) {
@@ -1006,8 +1097,9 @@ async function getCartListFn() {
 
           // attrStatus 判断商品是否有效
           if (validList[index].attrStatus) {
-            validList[index].checked = true;
-            selectValueArr.push(validList[index].id);
+            // validList[index].checked = true;
+            // selectValueArr.push(validList[index].id);
+            validList[index].checked = false;
           } else {
             validList[index].checked = false;
           }
@@ -1018,8 +1110,9 @@ async function getCartListFn() {
       selectValue.value = selectValueArr;
       let newArr = validList.filter((item) => item.attrStatus);
       // 全选状态
-      isAllSelect.value =
-        newArr.length == selectValueArr.length && newArr.length;
+      // isAllSelect.value =
+        // newArr.length == selectValueArr.length && newArr.length;
+      isAllSelect.value = false;
       switchSelect();
     }
     loading.value = false;
@@ -1114,6 +1207,9 @@ function unsetCart() {
     })
     .catch((res) => {});
 }
+const toMerchant = (obj) => {
+  uni.navigateTo({ url:"/pages/merchant/index?merchantId="+obj.id });
+}
 </script>
 
 <style scoped lang="scss">
@@ -1201,6 +1297,7 @@ function unsetCart() {
         flex: 1;
         position: relative;
         display: flex;
+        align-items: center;
 
         .pictrue {
           width: 160rpx;
@@ -1216,14 +1313,11 @@ function unsetCart() {
 
         .item-info,
         .text {
-          width: 396rpx;
+          width: 402rpx;
           font-size: 28rpx;
           color: #282828;
-          display: flex;
-          justify-content: space-between;
-          align-items: center;
           .storeName{
-            width: 70%;
+            width: 40%;
           }
           .reColor {
             color: #999;
@@ -1252,25 +1346,32 @@ function unsetCart() {
           .infor {
             font-size: 24rpx;
             color: #999;
-            margin-top: 16rpx;
+            //margin-top: 10rpx;
           }
           .labor-costs {
             font-size: 24rpx;
-            color: #999;
+            color: #FD5F3C;
+            margin-top: 10rpx;
+            .tag{
+              padding: 10rpx 16rpx;
+              background-color: #FFEFEC;
+              border-radius: 8rpx;
+              display: inline-block;
+            }
           }
           .money {
             font-size: 32rpx;
             color: #e93323;
-            margin-top: 28rpx;
+            margin-top: 20rpx;
             font-weight: 600;
           }
         }
 
         .carnum {
           height: 47rpx;
-          position: absolute;
-          bottom: 7rpx;
-          right: 0;
+          //position: absolute;
+          //bottom: 7rpx;
+          //right: 0;
 
           view {
             border: 1rpx solid #a4a4a4;
@@ -1502,4 +1603,16 @@ function unsetCart() {
 .cartBox {
   margin-top: 20rpx;
 }
+.namAndNum{
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.carnumTag{
+  font-size: 24rpx;
+  color: #666666;
+  background: #F1F3F8;
+  border-radius: 8rpx;
+  padding: 10rpx 16rpx;
+}
 </style>

+ 974 - 0
pages/order_details/index.vue

@@ -0,0 +1,974 @@
+<template>
+  <view>
+    <view class="order-details">
+      <!-- 给header上与data上加on为退款订单-->
+      <view class="header" :class="isGoodsReturn ? 'on' : ''">
+        <view class="picTxt acea-row row-middle">
+          <!-- <view class="pictrue" v-if="isGoodsReturn == false">
+            <image :src="orderInfo.statusPic"></image>
+          </view> -->
+          <view class="data" :class="isGoodsReturn ? 'on' : ''">
+            <view class="state">{{ orderInfo.orderStatusMsg }}</view>
+            <view>{{ orderInfo.createTime }}</view>
+          </view>
+        </view>
+      </view>
+
+      <view v-if="!isGoodsReturn" class="pad30">
+        <view class="nav">
+          <uni-steps
+            :current="curOrderStep"
+            :options="orderSteps"
+            activeColor="#e9c279"
+          ></uni-steps>
+        </view>
+        <view
+          v-if="orderInfo.shippingType == 2 && orderInfo.paid"
+          class="writeOff borRadius14"
+        >
+          <view class="title">核销信息</view>
+          <view class="grayBg">
+            <view class="pictrue">
+              <!-- <div class="qrcode" ref="qrcode"></div> -->
+              <!-- <canvas canvas-id="qrcode" :style="{width: `${qrcodeSize}100%`, height: `${qrcodeSize}100%`}"/> -->
+              <image :src="codeImg"></image>
+            </view>
+          </view>
+          <view class="gear">
+            <image src="../../static/images/writeOff.jpg"></image>
+          </view>
+          <view class="num">{{ orderInfo.verifyCode }}</view>
+          <view class="rules" v-if="orderInfo.systemStore">
+            <view class="item">
+              <view class="rulesTitle acea-row row-middle">
+                <text class="iconfont icon-shijian"></text>核销时间
+              </view>
+              <view class="info">
+                每日:<text class="time">{{
+                  orderInfo.systemStore.dayTime.replace(",", "-")
+                }}</text>
+              </view>
+            </view>
+            <view class="item">
+              <view class="rulesTitle acea-row row-middle">
+                <text class="iconfont icon-shuoming1"></text>使用说明
+              </view>
+              <view class="info">可将二维码出示给店员扫描或提供数字核销码</view>
+            </view>
+          </view>
+        </view>
+        <view v-if="orderInfo.shippingType === 1" class="address borRadius14">
+          <view class="name"
+            >{{ orderInfo.realName
+            }}<text class="phone">{{ orderInfo.userPhone }}</text></view
+          >
+          <view>{{ orderInfo.userAddress }}</view>
+        </view>
+        <view v-else class="address" style="margin-top: 15rpx">
+          <view class="name" @tap="makePhone"
+            >{{ orderInfo.systemStore ? orderInfo.systemStore.name : ""
+            }}<text class="phone">{{
+              orderInfo.systemStore ? orderInfo.systemStore.phone : ""
+            }}</text
+            ><text class="iconfont icon-tonghua font-color"></text
+          ></view>
+          <view>{{
+            orderInfo.systemStore ? orderInfo.systemStore.detailedAddress : ""
+          }}</view>
+        </view>
+
+        <orderGoods
+          :mallType="orderInfo.mallType"
+          :evaluate="evaluate"
+          :productType="orderInfo.type"
+          :orderId="order_id"
+          :ids="id"
+          :uniId="uniId"
+          :cartInfo="cartInfo"
+          :jump="true"
+        ></orderGoods>
+        <!-- <div class="goodCall borRadius14" @click="kefuClick">
+          <span class="iconfont icon-kefu"></span><span>联系客服</span>
+        </div> -->
+      </view>
+
+      <view class="pad30">
+        <view class="nav refund" v-if="orderInfo.refundStatus > 0">
+          <view class="title">
+            <image src="/static/images/shuoming.png" mode=""></image>
+            {{ getRefundStatus() }}
+          </view>
+          <view class="con pad30">{{ getRefundReason() }}</view>
+        </view>
+        <view class="wrapper borRadius14">
+          <view class="item acea-row row-between">
+            <view>订单编号:</view>
+            <view class="conter acea-row row-middle row-right"
+              >{{ orderInfo.orderId }}
+              <!-- #ifndef H5 -->
+              <text class="copy" @tap="copy">复制</text>
+              <!-- #endif -->
+              <!-- #ifdef H5 -->
+              <text class="copy copy-data" @click="setOrderId">复制</text>
+              <!-- #endif -->
+            </view>
+          </view>
+          <view class="item acea-row row-between">
+            <view>下单时间:</view>
+            <view class="conter">{{ orderInfo.createTime || 0 }}</view>
+          </view>
+          <view class="item acea-row row-between">
+            <view>支付状态:</view>
+            <view class="conter" v-if="orderInfo.paid">已支付</view>
+            <view class="conter" v-else>未支付</view>
+          </view>
+          <view class="item acea-row row-between">
+            <view>支付方式:</view>
+            <view class="conter" v-if="orderInfo.mallType === 0">{{
+              orderInfo.payTypeStr
+            }}</view>
+            <view class="conter" v-else>贝币</view>
+          </view>
+          <view class="item acea-row row-between" v-if="orderInfo.mark">
+            <view>买家留言:</view>
+            <view class="conter">{{ orderInfo.mark }}</view>
+          </view>
+        </view>
+        <!-- 退款订单详情 "-->
+        <view v-if="isGoodsReturn" class="wrapper borRadius14">
+          <view class="item acea-row row-between">
+            <view>收货人:</view>
+            <view class="conter">{{ orderInfo.realName }}</view>
+          </view>
+          <view class="item acea-row row-between">
+            <view>联系电话:</view>
+            <view class="conter">{{ orderInfo.userPhone }}</view>
+          </view>
+          <view class="item acea-row row-between">
+            <view>收货地址:</view>
+            <view class="conter">{{ orderInfo.userAddress }}</view>
+          </view>
+          <view class="item acea-row row-between">
+            <view>退款快递单号:</view>
+            <view class="conter">{{ orderInfo.refundExpressNum }}</view>
+          </view>
+        </view>
+        <view v-if="orderInfo.status > 0">
+          <view
+            class="wrapper borRadius14"
+            v-if="orderInfo.deliveryType == 'express'"
+          >
+            <view class="item acea-row row-between">
+              <view>配送方式:</view>
+              <view class="conter">发货</view>
+            </view>
+            <view class="item acea-row row-between">
+              <view>快递公司:</view>
+              <view class="conter">{{ orderInfo.deliveryName || "" }}</view>
+            </view>
+            <view class="item acea-row row-between">
+              <view>快递号:</view>
+              <view class="conter">{{ orderInfo.deliveryId || "" }}</view>
+            </view>
+          </view>
+          <view
+            class="wrapper borRadius14"
+            v-else-if="orderInfo.deliveryType == 'send'"
+          >
+            <view class="item acea-row row-between">
+              <view>配送方式:</view>
+              <view class="conter">送货</view>
+            </view>
+            <view class="item acea-row row-between">
+              <view>配送人姓名:</view>
+              <view class="conter">{{ orderInfo.deliveryName || "" }}</view>
+            </view>
+            <view class="item acea-row row-between">
+              <view>联系电话:</view>
+              <view class="conter acea-row row-middle row-right"
+                >{{ orderInfo.deliveryId || ""
+                }}<text class="copy" @tap="goTel">拨打</text></view
+              >
+            </view>
+          </view>
+          <view
+            class="wrapper borRadius14"
+            v-else-if="orderInfo.deliveryType == 'fictitious'"
+          >
+            <view class="item acea-row row-between">
+              <view>虚拟发货:</view>
+              <view class="conter">已发货,请注意查收</view>
+            </view>
+          </view>
+        </view>
+        <view class="wrapper borRadius14">
+          <view class="item acea-row row-between">
+            <view>商品总价:</view>
+            <view class="conter" v-if="orderInfo.mallType === 0">
+              ¥{{ orderInfo.proTotalPrice }}
+            </view>
+            <view class="conter" v-else>{{ orderInfo.proTotalPrice }}</view>
+          </view>
+          <view
+            class="item acea-row row-between"
+            v-if="orderInfo.payPostage > 0"
+          >
+            <view>运费:</view>
+            <view class="conter">¥{{ orderInfo.payPostage }}</view>
+          </view>
+          <view
+            class="item acea-row row-between"
+            v-if="orderInfo.payPostage > 0 && orderInfo?.additionalAmount"
+          >
+            <view>附加费:</view>
+            <view class="conter">¥{{ orderInfo.additionalAmount }}</view>
+          </view>
+          <!-- <view class="item acea-row row-between" v-if="orderInfo.couponId">
+            <view>优惠券抵扣:</view>
+            <view class="conter">-¥{{ orderInfo.couponPrice }}</view>
+          </view> -->
+          <view
+            class="item acea-row row-between"
+            v-if="orderInfo.mallType === 0"
+          >
+            <view>会员折扣:</view>
+            <view class="conter">-¥{{ orderInfo.vipLevelDis }}</view>
+          </view>
+          <view
+            v-if="orderInfo.mallType === 0"
+            class="actualPay acea-row row-right"
+          >
+            实付款:<text class="money font-color">
+              ¥{{ orderInfo.payPrice }}
+            </text>
+          </view>
+          <view v-else class="actualPay acea-row row-right"
+            >实付款:<text class="money font-color">
+              {{ Number(orderInfo.proTotalPrice) }}贝币
+            </text>
+          </view>
+        </view>
+        <view style="height: 120rpx"></view>
+        <view
+          class="footer acea-row row-right row-middle"
+          v-if="isGoodsReturn == false"
+        >
+          <view class="qs-btn" v-if="!orderInfo.paid" @click.stop="cancelOrder"
+            >取消订单</view
+          >
+          <view
+            class="bnt bg-color"
+            v-if="!orderInfo.paid"
+            @tap="pay_open(orderInfo.orderId)"
+            >立即付款</view
+          >
+
+          <navigator
+            hover-class="none"
+            :url="
+              '/pages/users/goods_return/index?orderId=' + orderInfo.orderId
+            "
+            class="bnt cancel"
+            v-else-if="
+              orderInfo.paid === true &&
+              orderInfo.refundStatus === 0 &&
+              orderInfo.type !== 1 &&
+              orderInfo.mallType !== 1 &&
+              type === 'normal'
+            "
+          >
+            申请退款
+          </navigator>
+          <navigator
+            class="bnt cancel"
+            v-if="orderInfo.deliveryType == 'express' && orderInfo.status > 0"
+            hover-class="none"
+            :url="`/pages/users/goods_logistics/index?orderId=${orderInfo.orderId}`"
+            >查看物流
+          </navigator>
+          <view
+            class="bnt bg-color"
+            v-if="orderInfo.status == 1"
+            @tap="confirmOrder"
+            >确认收货</view
+          >
+          <view class="bnt cancel" v-if="orderInfo.status == 3" @tap="delOrder"
+            >删除订单</view
+          >
+          <view
+            class="bnt bg-color"
+            v-if="orderInfo.status == 3 && orderInfo.type !== 1"
+            @tap="goOrderConfirm"
+            >再次购买</view
+          >
+        </view>
+      </view>
+    </view>
+    <payment
+      :payMode="payMode"
+      :showPopup="showPopup"
+      @payComplete="payComplete"
+      @close="payClose"
+      :order_id="pay_order_id"
+      :totalPrice="totalPrice"
+      :mallType="orderInfo.mallType"
+    ></payment>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import { useAppStore } from "@/stores/app.js";
+import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
+import {
+  getOrderDetail,
+  orderTake,
+  orderDel,
+  orderCancel,
+  qrcodeApi,
+} from "@/api/order.js";
+import { openOrderRefundSubscribe } from "@/utils/SubscribeMessage.js";
+import payment from "@/components/payment";
+import orderGoods from "@/components/orderGoods";
+import { toLogin } from "@/libs/login.js";
+import { useToast } from "@/hooks/useToast";
+import { getPreOrder } from "@/libs/order";
+
+const appStore = useAppStore();
+const { Toast } = useToast();
+
+const codeImg = ref("");
+const order_id = ref("");
+const evaluate = ref(0);
+const cartInfo = ref([]);
+const orderInfo = reactive({
+  systemStore: {},
+  pstatus: {},
+});
+const system_store = ref({});
+const isGoodsReturn = ref(false);
+const status = ref({});
+const isClose = ref(false);
+const payMode = ref([
+  {
+    name: "支付宝支付",
+    icon: "icon-zhifubao",
+    value: "alipay",
+    title: "支付宝快捷支付",
+    payChannel: "appAliPay",
+  },
+  {
+    name: "微信支付",
+    icon: "icon-wechat",
+    value: "weixin",
+    title: "微信快捷支付",
+    payChannel: "weixinApp",
+  },
+  {
+    name: "余额支付",
+    icon: "icon-balance",
+    value: "yue",
+    title: "可用余额:",
+    payChannel: "yue",
+    number: 0,
+  },
+]);
+const showPopup = ref(false);
+const pay_order_id = ref("");
+const totalPrice = ref("0");
+const id = ref(0);
+const uniId = ref("");
+const type = ref("normal");
+
+function payComplete() {
+  showPopup.value = false;
+  pay_order_id.value = "";
+  getOrderInfo();
+}
+function openSubcribe(page) {
+  uni.showLoading({ title: "正在加载" });
+  openOrderRefundSubscribe()
+    .then(() => {
+      uni.hideLoading();
+      uni.navigateTo({ url: page });
+    })
+    .catch(() => {
+      uni.hideLoading();
+    });
+}
+function makePhone() {
+  uni.makePhoneCall({
+    phoneNumber: system_store.value.phone,
+  });
+}
+function payClose() {
+  showPopup.value = false;
+}
+function pay_open() {
+  showPopup.value = true;
+  pay_order_id.value = orderInfo.orderId;
+  totalPrice.value = orderInfo.payPrice;
+}
+
+function getRefundStatus() {
+  const statusMap = {
+    1: "商家审核中",
+    2: "商家已退款",
+    3: "商家退款中",
+  };
+  return orderInfo?.refundStatus ? statusMap[orderInfo.refundStatus] : "";
+}
+
+function getRefundReason() {
+  if (orderInfo.refundStatus == 1) {
+    return "您已成功发起退款申请,请耐心等待商家处理;退款前请与商家协商一致,有助于更好的处理售后问题";
+  } else if (orderInfo.refundStatus == 2) {
+    return "退款已成功受理,如商家已寄出商品请尽快退回;感谢您的支持";
+  } else if (orderInfo?.refundReason) {
+    return "拒绝原因:" + orderInfo?.refundReason;
+  }
+}
+
+// 订单步骤配置
+const orderSteps = ref([
+  { title: "待付款" },
+  { title: "待发货" },
+  { title: "待收货" },
+  { title: "已完成" },
+]);
+
+// 当前的订单状态
+const curOrderStep = computed(() => {
+  let step = 0;
+  if (!orderInfo.paid) {
+    step = 0; // 待付款
+  } else if (orderInfo.status === 0) {
+    step = 1; // 待发货
+  } else if (orderInfo.status === 1) {
+    step = 2; // 待收货
+  } else if (orderInfo.status === 2 || orderInfo.status === 3) {
+    step = 3; // 已完成
+  }
+  return step;
+});
+function getOrderInfo() {
+  uni.showLoading({ title: "正在加载中" });
+  getOrderDetail(order_id.value)
+    .then((res) => {
+      uni.hideLoading();
+      Object.assign(orderInfo, res.data);
+      evaluate.value = res.data.status == 2 ? 2 : 0;
+      system_store.value = res.data.systemStore;
+      id.value = res.data.id;
+      cartInfo.value = res.data.orderInfoList;
+      if (res.data.refundStatus != 0) isGoodsReturn.value = true;
+      if (orderInfo.shippingType == 2 && orderInfo.paid)
+        markCode(res.data.verifyCode);
+      if (orderInfo.refundStatus > 0) {
+        // uni.setNavigationBarColor({
+        //   frontColor: "#ffffff",
+        //   backgroundColor: "#666666",
+        // });
+      }
+    })
+    .catch((err) => {
+      uni.hideLoading();
+      Toast({ title: err }, "/pages/users/order_list/index");
+    });
+}
+function markCode(text) {
+  qrcodeApi({
+    height: "145",
+    text: text,
+    width: "145",
+  }).then((res) => {
+    codeImg.value = res.data.code;
+  });
+}
+// #ifndef H5
+function copy() {
+  uni.setClipboardData({
+    data: orderInfo.orderId,
+  });
+}
+// #endif
+function goTel() {
+  uni.makePhoneCall({
+    phoneNumber: orderInfo.deliveryId,
+  });
+}
+function getOrderStatus() {
+  let _orderInfo = orderInfo || {},
+    _status = _orderInfo.pstatus || { type: 0 },
+    _statusObj = {};
+  let typeVal = parseInt(_status.type),
+    delivery_type = _orderInfo.deliveryType,
+    seckill_id = _orderInfo.seckillId ? parseInt(_orderInfo.seckillId) : 0,
+    bargain_id = _orderInfo.bargainId ? parseInt(_orderInfo.bargainId) : 0,
+    combination_id = _orderInfo.combinationId
+      ? parseInt(_orderInfo.combinationId)
+      : 0;
+  _statusObj = {
+    type: typeVal == 9 ? -9 : typeVal,
+    class_status: 0,
+  };
+  if (typeVal == 1 && combination_id > 0) _statusObj.class_status = 1;
+  if (typeVal == 2 && delivery_type == "express") _statusObj.class_status = 2;
+  if (typeVal == 2) _statusObj.class_status = 3;
+  if (typeVal == 4 || typeVal == 0) _statusObj.class_status = 4;
+  if (
+    !seckill_id &&
+    !bargain_id &&
+    !combination_id &&
+    (typeVal == 3 || typeVal == 4)
+  )
+    _statusObj.class_status = 5;
+  status.value = _statusObj;
+}
+function goJoinPink() {
+  uni.navigateTo({
+    url:
+      "/pages/activity/goods_combination_status/index?id=" + orderInfo.pinkId,
+  });
+}
+function goOrderConfirm() {
+  const params = {
+    mallType: orderInfo.mallType,
+    preOrderType: "again",
+    orderDetails: [{ orderNo: order_id.value }],
+  };
+  getPreOrder(params);
+}
+function confirmOrder() {
+  uni.showModal({
+    title: "确认收货",
+    content: "为保障权益,请收到货确认无误后,再确认收货",
+    success: function (res) {
+      if (res.confirm) {
+        orderTake(id.value)
+          .then(() => {
+            Toast({ title: "操作成功", icon: "success" }, function () {
+              getOrderInfo();
+            });
+          })
+          .catch((err) => {
+            Toast({ title: err });
+          });
+      }
+    },
+  });
+}
+function delOrder() {
+  orderDel(id.value)
+    .then(() => {
+      Toast({ title: "删除成功", icon: "success" }, { tab: 3, url: 1 });
+    })
+    .catch((err) => {
+      Toast({ title: err });
+    });
+}
+function cancelOrder() {
+  uni.showModal({
+    title: "提示",
+    content: "确认取消该订单?",
+    success: function (res) {
+      if (res.confirm) {
+        orderCancel(orderInfo.id)
+          .then(() => {
+            Toast({ title: "取消成功" });
+            uni.navigateTo({ url: "/pages/order_list/index" });
+          })
+          .catch((err) => {
+            Toast({ title: err });
+            getOrderInfo();
+          });
+      }
+    },
+  });
+}
+
+// 生命周期
+onLoad((options) => {
+  type.value =
+    options.type == undefined || options.type == null ? "normal" : options.type;
+  if (!options.order_id && !options.uniId) {
+    Toast({ title: "缺少参数" }, { tab: 3, url: 1 });
+    return;
+  }
+  order_id.value = options.order_id;
+});
+onShow(() => {
+  if (appStore.isLogin) {
+    getOrderInfo();
+    payMode.value[2].number = appStore.$userInfo.nowMoney;
+    payMode.value = [...payMode.value];
+  } else {
+    toLogin();
+  }
+});
+onHide(() => {
+  isClose.value = true;
+});
+
+const setOrderId = () => {
+  uni.setClipboardData({
+    data: orderInfo.orderId,
+    success: function () {
+      Toast({ title: "复制成功" });
+    },
+  });
+};
+</script>
+
+<style scoped lang="scss">
+.order-details {
+  .header {
+    height: 250rpx;
+    padding: 0 30rpx;
+    background-color: $header-color;
+
+    &.on {
+      background-color: $header-color !important;
+    }
+
+    .picTxt {
+      height: 150rpx;
+
+      .pictrue {
+        width: 110rpx;
+        height: 110rpx;
+
+        image {
+          width: 100%;
+          height: 100%;
+        }
+      }
+
+      .data {
+        color: rgba(255, 255, 255, 0.8);
+        font-size: 24rpx;
+        margin-left: 27rpx;
+
+        &.on {
+          margin-left: 0;
+        }
+
+        .state {
+          font-size: 30rpx;
+          font-weight: bold;
+          color: #fff;
+          margin-bottom: 7rpx;
+        }
+        .time {
+          margin-left: 20rpx;
+        }
+      }
+    }
+  }
+
+  .nav {
+    background-color: #fff;
+    font-size: 26rpx;
+    color: #282828;
+    padding: 27rpx 0;
+    width: 100%;
+    border-radius: 14rpx;
+    margin: -100rpx auto 0 auto;
+  }
+
+  .address {
+    font-size: 26rpx;
+    color: #868686;
+    background-color: #fff;
+    margin-top: 15rpx;
+    padding: 30rpx 24rpx;
+
+    .name {
+      font-size: 30rpx;
+      color: #282828;
+      margin-bottom: 15rpx;
+
+      .phone {
+        margin-left: 40rpx;
+      }
+      .iconfont {
+        font-size: 34rpx;
+        margin-left: 10rpx;
+      }
+    }
+  }
+
+  .line {
+    width: 100%;
+    height: 3rpx;
+
+    image {
+      width: 100%;
+      height: 100%;
+      display: block;
+    }
+  }
+
+  .wrapper {
+    background-color: #fff;
+    margin-top: 12rpx;
+    padding: 30rpx 24rpx;
+
+    .item {
+      font-size: 28rpx;
+      color: #282828;
+
+      & ~ .item {
+        margin-top: 20rpx;
+      }
+
+      .conter {
+        color: #868686;
+        text-align: right;
+
+        .copy {
+          font-size: 20rpx;
+          color: #333;
+          border-radius: 20rpx;
+          border: 1rpx solid #666;
+          padding: 3rpx 15rpx;
+          margin-left: 24rpx;
+        }
+      }
+    }
+
+    .actualPay {
+      border-top: 1rpx solid #eee;
+      margin-top: 30rpx;
+      padding-top: 30rpx;
+
+      .money {
+        font-weight: bold;
+        font-size: 30rpx;
+      }
+    }
+  }
+
+  .footer {
+    width: 100%;
+    height: 100rpx;
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    background-color: #fff;
+    padding: 0 30rpx;
+    box-sizing: border-box;
+
+    .bnt {
+      width: 158rpx;
+      height: 54rpx;
+      text-align: center;
+      line-height: 54rpx;
+      border-radius: 50rpx;
+      color: #fff;
+      font-size: 27rpx;
+
+      &.cancel {
+        color: #aaa;
+        border: 1rpx solid #ddd;
+      }
+
+      & ~ .bnt {
+        margin-left: 18rpx;
+      }
+    }
+  }
+
+  .writeOff {
+    background-color: #fff;
+    margin-top: 15rpx;
+    padding-bottom: 50rpx;
+
+    .title {
+      font-size: 30rpx;
+      color: #282828;
+      height: 87rpx;
+      border-bottom: 1px solid #f0f0f0;
+      padding: 0 24rpx;
+      line-height: 87rpx;
+    }
+
+    .grayBg {
+      background-color: #f2f5f7;
+      width: 590rpx;
+      height: 384rpx;
+      border-radius: 20rpx 20rpx 0 0;
+      margin: 50rpx auto 0 auto;
+      padding-top: 55rpx;
+
+      .pictrue {
+        width: 290rpx;
+        height: 290rpx;
+        margin: 0 auto;
+
+        image {
+          width: 100%;
+          height: 100%;
+          display: block;
+        }
+      }
+    }
+
+    .gear {
+      width: 590rpx;
+      height: 30rpx;
+      margin: 0 auto;
+
+      image {
+        width: 100%;
+        height: 100%;
+        display: block;
+      }
+    }
+
+    .num {
+      background-color: #f0c34c;
+      width: 590rpx;
+      height: 84rpx;
+      color: #282828;
+      font-size: 48rpx;
+      margin: 0 auto;
+      border-radius: 0 0 20rpx 20rpx;
+      text-align: center;
+      padding-top: 4rpx;
+    }
+
+    .rules {
+      margin: 46rpx 30rpx 0 30rpx;
+      border-top: 1px solid #f0f0f0;
+      padding-top: 10rpx;
+
+      .item {
+        margin-top: 20rpx;
+
+        .rulesTitle {
+          font-size: 28rpx;
+          color: #282828;
+
+          .iconfont {
+            font-size: 30rpx;
+            color: #333;
+            margin-right: 8rpx;
+            margin-top: 5rpx;
+          }
+        }
+
+        .info {
+          font-size: 28rpx;
+          color: #999;
+          margin-top: 7rpx;
+
+          .time {
+            margin-left: 20rpx;
+          }
+        }
+      }
+    }
+  }
+
+  .map {
+    height: 86rpx;
+    font-size: 30rpx;
+    color: #282828;
+    line-height: 86rpx;
+    border-bottom: 1px solid #f0f0f0;
+    margin-top: 15rpx;
+    background-color: #fff;
+    padding: 0 24rpx;
+
+    .place {
+      font-size: 26rpx;
+      width: 176rpx;
+      height: 50rpx;
+      border-radius: 25rpx;
+      line-height: 50rpx;
+      text-align: center;
+
+      .iconfont {
+        font-size: 27rpx;
+        height: 27rpx;
+        line-height: 27rpx;
+        margin: 2rpx 3rpx 0 0;
+      }
+    }
+  }
+
+  .refund {
+    padding: 0 !important;
+    margin-top: 15rpx;
+    background-color: #fff;
+
+    .title {
+      display: flex;
+      align-items: center;
+      font-size: 30rpx;
+      color: #333;
+      height: 86rpx;
+      border-bottom: 1px solid #f5f5f5;
+      font-weight: 400;
+      padding: 0 24rpx;
+
+      image {
+        width: 32rpx;
+        height: 32rpx;
+        margin-right: 10rpx;
+      }
+    }
+
+    .con {
+      font-size: 25rpx;
+      color: #666666;
+      padding: 30rpx 24rpx;
+    }
+  }
+}
+
+.goodCall {
+  color: $theme-color;
+  text-align: center;
+  width: 100%;
+  height: 86rpx;
+  padding: 0 30rpx;
+  border-bottom: 1rpx solid #eee;
+  font-size: 30rpx;
+  line-height: 86rpx;
+  background: #fff;
+
+  .icon-kefu {
+    font-size: 36rpx;
+    margin-right: 15rpx;
+  }
+
+  /* #ifdef MP */
+  button {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 86rpx;
+    font-size: 30rpx;
+    color: $theme-color;
+  }
+  /* #endif */
+}
+</style>
+
+<style>
+.qs-btn {
+  width: auto;
+  height: 60rpx;
+  text-align: center;
+  line-height: 60rpx;
+  border-radius: 50rpx;
+  color: #fff;
+  font-size: 27rpx;
+  padding: 0 3%;
+  color: #aaa;
+  border: 1px solid #ddd;
+  margin-right: 20rpx;
+}
+</style>

+ 394 - 0
pages/order_list/index.vue

@@ -0,0 +1,394 @@
+<template>
+  <view>
+    <view class="my-order">
+      <view class="header">
+        <view class="picTxt acea-row row-between-wrapper">
+          <view class="text">
+            <view class="name">订单信息</view>
+            <view>
+              <text class="consumer-order"
+                >消费订单:{{ orderDataState.orderCount || 0 }}</text
+              >
+              <text
+                >总消费:¥{{
+                  Number(orderDataState.sumPrice).toFixed(2) || 0
+                }}</text
+              >
+            </view>
+          </view>
+          <view class="pictrue">
+            <image src="/static/images/orderTime.png"></image>
+          </view>
+        </view>
+      </view>
+      <view class="nav acea-row row-around">
+        <view
+          class="item"
+          :class="orderStatus == 0 ? 'on' : ''"
+          @click="statusClick(0)"
+        >
+          <view>待付款</view>
+          <view class="num">{{ orderDataState.unPaidCount || 0 }}</view>
+        </view>
+        <view
+          class="item"
+          :class="orderStatus == 1 ? 'on' : ''"
+          @click="statusClick(1)"
+        >
+          <view>待发货</view>
+          <view class="num">{{ orderDataState.unShippedCount || 0 }}</view>
+        </view>
+        <view
+          class="item"
+          :class="orderStatus == 2 ? 'on' : ''"
+          @click="statusClick(2)"
+        >
+          <view>待收货</view>
+          <view class="num">{{ orderDataState.receivedCount || 0 }}</view>
+        </view>
+        <!-- <view
+          class="item"
+          :class="orderStatus == 3 ? 'on' : ''"
+          @click="statusClick(3)"
+        >
+          <view>待评价</view>
+          <view class="num">{{ orderDataState.evaluatedCount || 0 }}</view>
+        </view> -->
+        <view
+          class="item"
+          :class="orderStatus == 4 ? 'on' : ''"
+          @click="statusClick(4)"
+        >
+          <view>已完成</view>
+          <view class="num">{{ orderDataState.completeCount || 0 }}</view>
+        </view>
+        <view class="item" @click="toReturnList">
+          <view>退货/退款</view>
+          <view class="num">{{ orderDataState.refundCount || 0 }}</view>
+        </view>
+      </view>
+      <view class="list">
+        <OrderListCard
+          v-for="(item, index) in orderList"
+          :key="index"
+          :order="item"
+          :index="index"
+          @cancelOrder="cancelOrder"
+          @goPay="goPay"
+          @goOrderDetails="goOrderDetails"
+          :mallType="0"
+          @delOrder="delOrder"
+        />
+      </view>
+      <view
+        class="loadingicon acea-row row-center-wrapper"
+        v-if="orderList.length > 0"
+      >
+        <text
+          class="loading iconfont icon-jiazai"
+          :hidden="loading == false"
+        ></text
+        >{{ loadTitle }}
+      </view>
+      <view v-if="orderList.length == 0">
+        <emptyPage title="暂无订单~"></emptyPage>
+      </view>
+    </view>
+    <!-- <view class='noCart' v-if="orderList.length == 0 && page > 1">
+      <view class='pictrue'>
+        <image src='/static/images/noOrder.png'></image>
+      </view>
+    </view> -->
+    <payment
+      @closePopup="closePayPopup"
+      :payMode="payMode"
+      :showPopup="showPopup"
+      @payComplete="payComplete"
+      @payFail="payFail"
+      @onChangeFun="onChangeFun"
+      :order_id="pay_order_id"
+      :totalPrice="totalPrice"
+    ></payment>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import { onShow,onLoad, onReachBottom } from "@dcloudio/uni-app";
+import OrderListCard from "@/components/OrderListCard";
+import { useAppStore } from "@/stores/app";
+import { getOrderList, orderData, orderCancel, orderDel } from "@/api/order.js";
+import payment from "@/components/payment";
+import { toLogin } from "@/libs/login.js";
+import emptyPage from "@/components/emptyPage.vue";
+import { useToast } from "@/hooks/useToast";
+
+const appStore = useAppStore();
+const { Toast } = useToast();
+
+// 状态
+const loading = ref(false);
+const loadend = ref(false);
+const loadTitle = ref("加载更多");
+const orderList = ref([]);
+const orderDataState = ref({});
+const orderStatus = ref(0);
+const page = ref(1);
+const limit = ref(10);
+const payMode = ref([
+  {
+    name: "微信支付",
+    icon: "icon-weixinzhifu",
+    value: "weixin",
+    title: "微信快捷支付",
+  },
+  {
+    name: "余额支付",
+    icon: "icon-yuezhifu",
+    value: "yue",
+    title: "可用余额:",
+    number: 0,
+  },
+]);
+const showPopup = ref(false);
+const pay_order_id = ref("");
+const totalPrice = ref("0");
+
+// 生命周期
+// onMounted(() => {
+//   if (appStore.isLogin) {
+//     loadend.value = false;
+//     page.value = 1;
+//     orderList.value = [];
+//     getOrderData();
+//     getOrderListFn();
+//   } else {
+//     toLogin();
+//   }
+// });
+
+// 页面显示时
+
+onShow(() => {
+  console.log('onShow')
+  if (appStore.isLogin) {
+    loadend.value = false;
+    page.value = 1;
+    orderList.value = [];
+    getOrderData();
+    getOrderListFn();
+  } else {
+    toLogin();
+  }
+});
+onLoad((options)=>{
+  if (appStore.isLogin) {
+    if(options.status  && options.status!= 5){
+      statusClick(options.status)
+    }else if(options.status  && options.status == 5){
+      toReturnList()
+    }
+  } else {
+    toLogin();
+  }
+})
+
+// 下拉加载
+onReachBottom(() => {
+  getOrderListFn();
+});
+
+function onChangeFun(state) {
+  let action = state.action || null;
+  let value = state.value !== undefined ? state.value : null;
+  if (action && typeof [action] === "function") {
+    [action](value);
+  }
+}
+function getOrderData() {
+  orderData().then((res) => {
+    orderDataState.value = res.data;
+  });
+}
+function cancelOrder(index, order_id) {
+  if (!order_id) return Toast({ title: "缺少订单号无法取消订单" });
+  uni.showLoading({ title: "正在删除中" });
+  orderCancel(order_id)
+    .then((res) => {
+      uni.hideLoading();
+      Toast({ title: "删除成功", icon: "success" }, () => {
+        orderList.value.splice(index, 1);
+        orderDataState.value.unpaid_count--;
+        getOrderData();
+      });
+    })
+    .catch((err) => {
+      Toast({ title: err });
+    });
+}
+
+function goPay(pay_price, order_id) {
+  appStore.USERINFO().then((res) => {
+    if (res.nowMoney) {
+      showPopup.value = true;
+      pay_order_id.value = order_id;
+      totalPrice.value = pay_price;
+    }
+  });
+}
+const closePayPopup = () => {
+  console.log("payClose");
+  showPopup.value = false;
+};
+function payComplete() {
+  console.log("payComplete");
+  loadend.value = false;
+  page.value = 1;
+  orderList.value = [];
+  showPopup.value = false;
+  getOrderData();
+  getOrderListFn();
+}
+function payFail() {
+  showPopup.value = false;
+}
+function goOrderDetails(order_id) {
+  if (!order_id) return Toast({ title: "缺少订单号无法查看订单详情" });
+  uni.navigateTo({ url: "/pages/order_details/index?order_id=" + order_id });
+}
+
+function toReturnList() {
+  uni.navigateTo({ url: "/pages/users/user_return_list/index" });
+}
+function statusClick(status) {
+  if (status === orderStatus.value) return;
+  orderStatus.value = status;
+  loadend.value = false;
+  page.value = 1;
+  orderList.value = [];
+  getOrderListFn();
+}
+function getOrderListFn() {
+  if (loadend.value || loading.value) return;
+  loading.value = true;
+  loadTitle.value = "加载更多";
+  getOrderList({
+    type: orderStatus.value,
+    page: page.value,
+    limit: limit.value,
+    mallType: 0,
+  })
+    .then((res) => {
+      let list = res.data.list || [];
+      let isLoadend = list.length < limit.value;
+      // orderList.value = uni.$util.SplitArray(list, orderList.value)
+      orderList.value = [...list, ...orderList.value];
+      loadend.value = isLoadend;
+      loading.value = false;
+      loadTitle.value = isLoadend ? "我也是有底线的" : "加载更多";
+      page.value += 1;
+    })
+    .catch(() => {
+      loading.value = false;
+      loadTitle.value = "加载更多";
+    });
+}
+function delOrder(order_id, index) {
+  orderDel(order_id)
+    .then((res) => {
+      orderList.value.splice(index, 1);
+      orderDataState.value.unpaid_count--;
+      getOrderData();
+      Toast({ title: "删除成功", icon: "success" });
+    })
+    .catch((err) => {
+      Toast({ title: err });
+    });
+}
+</script>
+
+<style scoped lang="scss">
+.my-order {
+  .header {
+    height: 250rpx;
+    padding: 0 30rpx;
+    background-color: $header-color;
+
+    .picTxt {
+      height: 190rpx;
+
+      .text {
+        color: #000;
+        font-size: 26rpx;
+        font-family: "Guildford Pro";
+
+        .name {
+          font-size: 34rpx;
+          font-weight: bold;
+          color: #000;
+          margin-bottom: 20rpx;
+        }
+
+        .consumer-order {
+          margin-right: 30rpx;
+        }
+      }
+
+      .pictrue {
+        width: 122rpx;
+        height: 109rpx;
+
+        image {
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+  }
+
+  .nav {
+    background-color: #fff;
+    width: 690rpx;
+    height: 140rpx;
+    border-radius: 14rpx;
+    margin: -60rpx auto 0 auto;
+
+    .item {
+      text-align: center;
+      font-size: 26rpx;
+      color: #282828;
+      padding: 26rpx 0;
+
+      &.on {
+        font-weight: bold;
+        border-bottom: 5rpx solid $border-color;
+      }
+
+      .num {
+        margin-top: 18rpx;
+      }
+    }
+  }
+
+  .list {
+    width: 690rpx;
+    margin: 14rpx auto 0 auto;
+  }
+}
+
+.noCart {
+  margin-top: 171rpx;
+  padding-top: 0.1rpx;
+
+  .pictrue {
+    width: 414rpx;
+    height: 336rpx;
+    margin: 78rpx auto 56rpx auto;
+
+    image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+</style>

+ 42 - 36
pages/personal_info/personal_info.vue

@@ -27,7 +27,7 @@
         <!-- </template> -->
       </up-upload>
     </view>
-		<view class="flex-center-between border-bottom personal-info-item">
+		<view class="flex-center-between personal-info-item">
 			<text class="label_width">昵称</text>
 			<up-input
           class="title-input"
@@ -43,18 +43,18 @@
           @blur="changeName"
         ></up-input>
 		</view>
-		<view class="flex-center-between border-bottom personal-info-item">
+		<view class="flex-center-between 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">
+		<view class="flex-center-between 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">
+		<view class="flex-center-between personal-info-item">
 			<text class="label_width">地址</text>
       <picker mode="multiSelector" @change="bindRegionChange" @columnchange="bindMultiPickerColumnChange"
         :value="valueRegion" :range="multiArray">
@@ -77,7 +77,7 @@
           @blur="change"
         ></up-input> -->
     </view>
-    <view class="flex-center-between border-bottom personal-info-item">
+    <view class="flex-center-between personal-info-item">
       <text class="label_width">年龄</text>
       <up-input
         class="title-input"
@@ -93,7 +93,7 @@
         @blur="change"
       ></up-input>
     </view>
-    <view class="flex-center-between border-bottom personal-info-item">
+    <view class="flex-center-between personal-info-item">
       <text class="label_width">个人简介</text>
       <up-input
         class="title-input"
@@ -109,29 +109,29 @@
         @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>
+<!--    <view-->
+<!--      class="flex-center-between border-bottom personal-info-item"-->
+<!--      @tap.stop="setPayword"-->
+<!--    >-->
+<!--      <text class="label_width">支付密码</text>-->
+<!--      &lt;!&ndash; <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> &ndash;&gt;-->
+<!--      <text>* * * * * *</text>-->
+<!--    </view>-->
     <up-picker
       @confirm="changeSex"
       @cancel="pickerSexShow = false"
@@ -347,19 +347,25 @@ function bindMultiPickerColumnChange(e) {
     padding: 80rpx 0;
   }
   .personal-info-item {
-    padding: 30rpx 0;
+    height: 100rpx;
+    line-height: 100rpx;
+    padding: 0 30rpx;
+    box-sizing: border-box;
+    border-radius: 16rpx;
+    background-color: #FFF;
+    margin-bottom: 30rpx;
     .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;
+    font-size: 32rpx;
+    color: #333;
+    height: 88rpx;
+    line-height: 88rpx;
+    border-radius: 16rpx;
+    background-color: #F8C008;
     border: none;
     margin-top: 20px;
   }

+ 47 - 51
pages/user/index.vue

@@ -30,7 +30,7 @@
               <text class="name">未登录</text>
             </view>
 
-            <text class="vip-expire" v-if="userInfo.svip">到期时间:{{ appStore.$userInfo?.svipExpireTime }}</text>
+            <text class="vip-expire" v-if="appStore.$userInfo?.svip">到期时间:{{ appStore.$userInfo?.svipExpireTime }}</text>
           </view>
         </view>
         <view class="btn-content" @click="handleEdit()">
@@ -80,7 +80,7 @@
       </view>
 
       <view class="functions">
-        <view class="function-item" v-for="item in mainFunctions" :key="item.name" @click="handleFunctionClick(item)">
+        <view class="function-item" v-for="item in mainFunctions" :key="item.name" @click="handleFunctionClick(item.pageUrl)">
           <view class="function-icon">
             <image class="img" :src="item.src" mode="widthFix"></image>
           </view>
@@ -121,7 +121,7 @@
       </view>
 
       <view class="order-status">
-        <view class="status-item" v-for="order in orderStatus" :key="order.name" @click="viewOrders(order)">
+        <view class="status-item" v-for="order in orderStatus" :key="order.name" @click="viewOrders(order.id)">
           <view class="status-icon">
             <image class="img" :src="order.src" mode="widthFix"></image>
           </view>
@@ -137,12 +137,15 @@
       </view>
 
       <view class="functions">
-        <view class="function-item" v-for="func in commonFunctions" :key="func.name" @click="handleFunctionClick(func.pageUrl)">
-          <view class="function-icon">
-            <image class="img" :src="func.src" mode="widthFix"></image>
+        <template v-for="func in commonFunctions" :key="func.name">
+          <view class="function-item" @click="handleFunctionClick(func.pageUrl)" v-if="func.show">
+            <view class="function-icon">
+              <image class="img" :src="func.src" mode="widthFix"></image>
+            </view>
+            <text class="function-name">{{ func.name }}</text>
           </view>
-          <text class="function-name">{{ func.name }}</text>
-        </view>
+        </template>
+
       </view>
     </view>
   </view>
@@ -157,16 +160,11 @@ import { useAppStore } from "@/stores/app";
 import { useToast } from "@/hooks/useToast";
 import { getMetalBalance } from "@/api/vault";
 import { footprintList } from "@/api/merchant.js";
+import { toLogin } from "@/libs/login.js";
 
 const appStore = useAppStore();
 const { Toast } = useToast();
-
-// 用户信息
-const userInfo = reactive({
-  nickname: '李元芳',
-  isVip: true,
-  vipExpire: '2026-11-12'
-})
+const isLogin = appStore.isLogin;
 
 // 钱包信息
 const wallet = ref({
@@ -180,10 +178,10 @@ const wallet = ref({
 
 // 主要功能列表
 const mainFunctions = ref([
-  { src: '/static/images/setting/mailiao.png', name: '买料' },
-  { src: '/static/images/setting/mailiao2.png', name: '卖料' },
-  { src: '/static/images/setting/cunliao.png', name: '存料' },
-  { src: '/static/images/setting/tiliao.png', name: '提料' }
+  { src: '/static/images/setting/mailiao.png', name: '买料',pageUrl:'/pages/users/vault/buy?type=buygold' },
+  { src: '/static/images/setting/mailiao2.png', name: '卖料',pageUrl:'/pages/users/vault/storeMetal/index?type=soldgold' },
+  { src: '/static/images/setting/cunliao.png', name: '存料',pageUrl:'/pages/users/vault/storeMetal/goldBullionStock?type=savegold' },
+  { src: '/static/images/setting/tiliao.png', name: '提料',pageUrl:'/pages/users/vault/storeMetal/metalExchange?type=materialdeduction' }
 ])
 
 // 最近访问的商家
@@ -191,22 +189,23 @@ const recentStores = ref([])
 
 // 订单状态
 const orderStatus = ref([
-  { src: '/static/images/setting/daifukuan.png', name: '待付款' },
-  { src: '/static/images/setting/daifahuo.png', name: '待发货' },
-  { src: '/static/images/setting/daishouhuo.png', name: '待收货' },
-  { src: '/static/images/setting/tuikuan.png', name: '退款/换货' },
-  { src: '/static/images/setting/yiwancheng.png', name: '已完成' },
+  { src: '/static/images/setting/daifukuan.png', name: '待付款',id:0 },
+  { src: '/static/images/setting/daifahuo.png', name: '待发货',id:1 },
+  { src: '/static/images/setting/daishouhuo.png', name: '待收货',id:2 },
+  { src: '/static/images/setting/tuikuan.png', name: '退款/换货' ,id:5},
+  { src: '/static/images/setting/yiwancheng.png', name: '已完成',id:4 },
 ])
 
 // 常用功能
 const commonFunctions = ref([
-  { src: '/static/images/setting/zuji.png', name: '浏览足迹',pageUrl:'/pages/users/browsing_history/index' },
+  { src: '/static/images/setting/zuji.png', name: '浏览足迹',pageUrl:'/pages/users/browsing_history/index',show:true },
   { src: '/static/images/setting/shoucang.png', name: '我的收藏' },
-  { src: '/static/images/setting/dingwei.png', name: '收货地址' },
-  { src: '/static/images/setting/dianpu.png', name: '联系商家' },
-  { src: '/static/images/setting/yijianjianyi.png', name: '意见建议' },
-  { src: '/static/images/setting/xiazaiapp.png', name: '下载APP' },
-  { src: '/static/images/setting/lianxikefu.png', name: '平台客服' }
+  { src: '/static/images/setting/dingwei.png', name: '收货地址',pageUrl:'/pages/users/user_address_list/index' ,show:true},
+  { src: '/static/images/setting/dianpu.png', name: '联系商家',pageUrl: '/pages/users/my_merchant/index' ,show:appStore.merchantId?true:false},
+  { src: '/static/images/setting/yijianjianyi.png', name: '意见建议' ,show:true},
+  { src: '/static/images/setting/xiazaiapp.png', name: '下载APP',show:true },
+  { src: '/static/images/setting/lianxikefu.png', name: '平台客服' ,show:true},
+  { src: '/static/images/setting/lianxikefu.png', name: '资产明细' ,pageUrl:'/pages/users/user_asset/asset_info/asset_info',show:true}
 ])
 const params = ref({
   page: 1,
@@ -238,26 +237,29 @@ const openVip = () => {
 
 // 查看交易明细
 const viewTransactionDetail = () => {
-  uni.showToast({
-    title: '查看交易明细',
-    icon: 'none'
-  })
+  if(!isLogin){
+    toLogin();
+  }else{
+    uni.navigateTo({ url:"/pages/users/vault/index" });
+  }
 }
 
 // 充值
 const recharge = () => {
-  uni.showToast({
-    title: '充值',
-    icon: 'none'
-  })
+  if(!isLogin){
+    toLogin();
+  }else{
+    uni.navigateTo({ url:"/pages/users/vault/recharge" });
+  }
 }
 
 // 提现
 const withdraw = () => {
-  uni.showToast({
-    title: '提现',
-    icon: 'none'
-  })
+  if(!isLogin){
+    toLogin();
+  }else{
+    uni.navigateTo({ url:"/pages/users/vault/withdraw" });
+  }
 }
 
 // 功能点击
@@ -277,18 +279,12 @@ const viewStore = (store) => {
 
 // 查看全部订单
 const viewAllOrders = () => {
-  uni.showToast({
-    title: '查看全部订单',
-    icon: 'none'
-  })
+  uni.navigateTo({ url:"/pages/order_list/index" });
 }
 
 // 查看订单
-const viewOrders = (order) => {
-  uni.showToast({
-    title: `查看订单:${order.name}`,
-    icon: 'none'
-  })
+const viewOrders = (status) => {
+  uni.navigateTo({ url:"/pages/order_list/index?status="+ status});
 }
 const navigateTo = (url) => {
   if (!url) return;

+ 4 - 1
pages/users/browsing_history/index.vue

@@ -13,7 +13,7 @@
           <view class="shop-name">{{ record.merchantName }}</view>
           <view class="browse-time">浏览时间:{{ record.createTime }}</view>
         </view>
-        <button class="action-btn">商城主页</button>
+        <button class="action-btn" @click="toMerchant(record)">商城主页</button>
       </view>
       <view class="loadingicon acea-row row-center-wrapper" v-if="goodScroll">
         <text
@@ -72,6 +72,9 @@ const getList = async () => {
     loading.value = false;
   }
 };
+const toMerchant = (obj) => {
+  uni.navigateTo({ url:"/pages/merchant/index?merchantId="+obj.merchantId });
+}
 </script>
 
 <style lang="scss" scoped>

+ 18 - 12
pages/users/login/index.vue

@@ -169,6 +169,7 @@ const appleUserInfo = ref(null);
 const appleShow = ref(false);
 const invite_code = ref("");
 const merchantId = ref('');
+const userInfo = ref({});
 
 // Watch formItem to update type
 watch(formItem, (newVal) => {
@@ -324,7 +325,7 @@ const getUserInfoFn = async (data) => {
     // await getWechatOpenid();
     const res = await getUserInfo();
     appStore.UPDATE_USERINFO(res.data);
-
+    userInfo.value = res.data;
     if(merchantId.value != ''){
       let obj ={
         merchantId:merchantId.value,
@@ -343,11 +344,7 @@ const getUserInfoFn = async (data) => {
 };
 
 onLoad((options) => {
-  const data = getSceneInfo(options);
-  if (data?.merchantId) {
-    merchantId.value = Number(data.merchantId);
-    uni.setStorageSync("merchantId", data.merchantId);
-  }
+  getSceneInfo(options)
   getLogoImage();
 });
 
@@ -375,9 +372,15 @@ onMounted(() => {
 });
 
 const backHome = () => {
-  uni.switchTab({
-    url: "/pages/index/index",
-  });
+  if(userInfo.value.merchant){
+    uni.navigateTo({
+      url:"/pages/merchantCenter/index"
+    })
+  }else{
+    uni.switchTab({
+      url: "/pages/index/index",
+    });
+  }
 };
 const change_password = () => {
   uni.navigateTo({
@@ -390,11 +393,13 @@ const change_password = () => {
     backHome();
   }
 </script>
-
-<style lang="scss" scoped>
+<style>
 page {
-  background: #fff;
+  background: #fff !important;
 }
+</style>
+<style lang="scss" scoped>
+
 .appLogin {
   margin-top: 60rpx;
 
@@ -512,6 +517,7 @@ page {
     image {
       width: 180rpx;
       height: 180rpx;
+      border-radius: 50%;
     }
   }
 

+ 206 - 0
pages/users/user_return_list/index.vue

@@ -0,0 +1,206 @@
+<template>
+  <view>
+    <view class="return-list pad30" v-if="orderList.length">
+      <view
+        class="goodWrapper borRadius14"
+        v-for="(item, index) in orderList"
+        :key="index"
+        @click="goOrderDetails(item.orderId)"
+      >
+        <view
+          class="iconfont icon-tuikuanzhong powder"
+          v-if="item.refundStatus == 1 || item.refundStatus == 3"
+        ></view>
+        <view
+          class="iconfont icon-yituikuan"
+          v-if="item.refundStatus == 2"
+        ></view>
+        <view class="orderNum">订单号:{{ item.orderId }}</view>
+        <view
+          class="item acea-row row-between-wrapper"
+          v-for="(items, idx) in item.orderInfoList"
+          :key="idx"
+        >
+          <view class="pictrue">
+            <image :src="items.image"></image>
+          </view>
+          <view class="text">
+            <view class="acea-row row-between-wrapper">
+              <view class="name line1">{{ items.storeName }}</view>
+              <view class="num">x {{ items.cartNum }}</view>
+            </view>
+            <view class="attr line1" v-if="items.suk">{{ items.suk }}</view>
+            <view class="attr line1" v-else>{{ items.storeName }}</view>
+            <view class="last-row" v-if="mallType === 0">
+              <view class="mony">¥{{ items.price }}</view>
+              <view class="status-msg">{{ item.orderStatus }}</view>
+            </view>
+          </view>
+        </view>
+        <view class="totalSum">
+          共{{ item.totalNum || 0 }}件商品,总金额
+          <text class="font-color price" v-if="mallType === 0">¥{{ item.payPrice }}</text>
+          <text class="font-color price" v-else>{{ item.useIntegral }}贝币</text>
+        </view>
+      </view>
+    </view>
+    <view
+      class="loadingicon acea-row row-center-wrapper"
+      v-if="orderList.length"
+    >
+      <text
+        class="loading iconfont icon-jiazai"
+        :hidden="loading == false"
+      ></text
+      >{{ loadTitle }}
+    </view>
+    <view v-if="orderList.length == 0">
+      <emptyPage title="暂无订单~"></emptyPage>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch } from "vue";
+import emptyPage from "@/components/emptyPage.vue";
+import { getOrderList } from "@/api/order.js";
+import { toLogin } from "@/libs/login.js";
+import { useAppStore } from "@/stores/app";
+import { onLoad, onReachBottom } from "@dcloudio/uni-app";
+
+const appStore = useAppStore();
+const loading = ref(false);
+const loadend = ref(false);
+const loadTitle = ref("加载更多");
+const orderList = ref([]);
+const orderStatus = ref(-3);
+const page = ref(1);
+const limit = ref(20);
+const isShowAuth = ref(false);
+const mallType = ref(0)
+
+watch(() => appStore.isLogin, (newV) => {
+  if (newV) {
+    console.log('watch')
+    getOrderListFn();
+  }
+});
+
+onLoad((options) => {
+  if (appStore.isLogin) {
+    mallType.value = options.mallType ? options.mallType : 0;
+    getOrderListFn();
+  } else {
+    toLogin();
+  }
+});
+
+onReachBottom(() => {
+  getOrderListFn();
+});
+
+function onLoadFun() {
+  getOrderListFn();
+}
+
+// 授权关闭
+function authColse(e) {
+  isShowAuth.value = e;
+}
+
+/**
+ * 去订单详情
+ */
+function goOrderDetails(order_id) {
+  if (!order_id) return uni.$u.toast("缺少订单号无法查看订单详情");
+  uni.navigateTo({
+    url: "/pages/order_details/index?order_id=" + order_id + "&isReturen=1",
+  });
+}
+
+/**
+ * 获取订单列表
+ */
+function getOrderListFn() {
+  if (loadend.value) return;
+  if (loading.value) return;
+  loading.value = true;
+  loadTitle.value = "";
+  getOrderList({
+    mallType: mallType.value, // 只有水贝商城有退货订单
+    type: orderStatus.value,
+    page: page.value,
+    limit: limit.value,
+  })
+    .then((res) => {
+      let list = res.data.list || [];
+      let isLoadend = list.length < limit.value;
+      orderList.value = uni.$u.deepMerge(orderList.value, list);
+      loadend.value = isLoadend;
+      loading.value = false;
+      loadTitle.value = isLoadend ? "我也是有底线的" : "加载更多";
+      page.value = page.value + 1;
+    })
+    .catch(() => {
+      loading.value = false;
+      loadTitle.value = "加载更多";
+    });
+}
+</script>
+
+<style scoped lang="scss">
+.return-list {
+  .goodWrapper {
+    background-color: #fff;
+    margin-top: 20rpx;
+    position: relative;
+    padding: 0rpx 24rpx;
+
+    .orderNum {
+      border-bottom: 1px solid #eee;
+      height: 87rpx;
+      line-height: 87rpx;
+      font-size: 30rpx;
+      color: #333333;
+    }
+
+    .item {
+      border-bottom: 0;
+      .last-row {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .status-msg {
+          font-size: 26rpx;
+          color: #999;
+        }
+      }
+    }
+
+    .totalSum {
+      padding: 0 30rpx 32rpx 30rpx;
+      text-align: right;
+      font-size: 26rpx;
+      color: #282828;
+      border-bottom: 1px solid #eee;
+
+      .price {
+        font-size: 28rpx;
+        font-weight: bold;
+      }
+    }
+
+    .iconfont {
+      position: absolute;
+      font-size: 109rpx;
+      top: 7rpx;
+      right: 22rpx;
+      color: #ccc;
+
+      &.powder {
+        color: #f8c1bd;
+      }
+    }
+  }
+}
+</style>

文件差異過大導致無法顯示
+ 485 - 598
pages/users/vault/buy.vue


+ 36 - 30
pages/users/vault/index.vue

@@ -1,37 +1,37 @@
 <template>
   <view class="container">
     <view v-if="currentTab === 'wallet'" class="wallet-page">
-      <view class="new-asset-box">
-        <view class="new-asset-title">
-          <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/an_20251011162829_903_6.png"
-            mode="widthFix"
-          ></image>
-          <text>资金安全有保障</text>
-        </view>
-        <view class="new-asset-content">
-          <view
-            class="asset-content-item"
-            v-for="item in metals"
-            :key="item.id"
-          >
-            <view class="content-item-left">
-              <image :src="item.icon" mode="widthFix"></image>
-              <text>{{ item.name }}</text>
-            </view>
-            <view class="content-item-right">{{ item.balance }}</view>
-          </view>
-        </view>
-      </view>
-      <view class="asset-tips">
-        <view class="zc">资产说明:</view>
-        <view
-          >贵金属账户里的金料随时可以在本平台内选购商品并抵扣金重,或以实时金价出售给本平台转换成现金</view
-        >
-      </view>
+<!--      <view class="new-asset-box">-->
+<!--        <view class="new-asset-title">-->
+<!--          <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/an_20251011162829_903_6.png"-->
+<!--            mode="widthFix"-->
+<!--          ></image>-->
+<!--          <text>资金安全有保障</text>-->
+<!--        </view>-->
+<!--        <view class="new-asset-content">-->
+<!--          <view-->
+<!--            class="asset-content-item"-->
+<!--            v-for="item in metals"-->
+<!--            :key="item.id"-->
+<!--          >-->
+<!--            <view class="content-item-left">-->
+<!--              <image :src="item.icon" mode="widthFix"></image>-->
+<!--              <text>{{ item.name }}</text>-->
+<!--            </view>-->
+<!--            <view class="content-item-right">{{ item.balance }}</view>-->
+<!--          </view>-->
+<!--        </view>-->
+<!--      </view>-->
+<!--      <view class="asset-tips">-->
+<!--        <view class="zc">资产说明:</view>-->
+<!--        <view-->
+<!--          >贵金属账户里的金料随时可以在本平台内选购商品并抵扣金重,或以实时金价出售给本平台转换成现金</view-->
+<!--        >-->
+<!--      </view>-->
 
       <view class="record-container">
-        <view class="record-title">交易记录</view>
+<!--        <view class="record-title">交易记录</view>-->
         <view class="record-tabs">
           <view
             class="record-tab"
@@ -392,7 +392,13 @@ async function fetchMetalBalance() {
 }
 
 // 不要在 onLoad 里请求 fetchMetalBalance
-// onLoad(() => {});
+onLoad((options) => {
+  console.log('options==============')
+  console.log(options)
+  if(options.type){
+    switchRecordTab(options.type)
+  }
+});
 
 const combineList = computed(() => {
   if (recordTab.value === "recharge") {

+ 887 - 0
pages/users/vault/recharge.vue

@@ -0,0 +1,887 @@
+<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 }}</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 style="width: 100%">
+                  用户名:{{ rechargeInfo.alipayUsername }}
+                </view>
+                <view>
+                  账&nbsp;&nbsp;&nbsp;户:{{ rechargeInfo.alipayAccount }}
+                </view>
+              </view>
+              <view class="end">
+                <view
+                  class="copy"
+                  @click="copy(`账户:${rechargeInfo.alipayAccount}`)"
+                >
+                  复制
+                </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>用户名:{{ rechargeInfo.bankCardBank }}</view>
+                <view>卡号:{{ rechargeInfo.bankCardNumber }}</view>
+                <view>开户行:{{ rechargeInfo.bankCardUsername }}</view>
+              </view>
+              <view class="end">
+                <view
+                  class="copy"
+                  @click="
+                    copy(
+                      `用户名:${rechargeInfo.bankCardBank},开户行:${rechargeInfo.bankCardNumber},卡号:${rechargeInfo.bankCardUsername}`
+                    )
+                  "
+                >
+                  复制
+                </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-box" @click="handleShowModel">
+            <image class="btn" src="/static/images/sb_btn.png"></image>
+            <text class="btn-text">提交申请</text>
+          </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" class="process-guidelines">
+          <view v-if="tabsIndex === 0" style="margin: 30rpx 0"
+            >支付宝充值流程指引</view
+          >
+          <image
+            @click="
+              showImage(
+                'https://mp-c7c90a6c-c53b-48dd-bd94-692abc111f89.cdn.bspapp.com/2025/08/07/65570280-62375873-0.png'
+              )
+            "
+            src="https://mp-c7c90a6c-c53b-48dd-bd94-692abc111f89.cdn.bspapp.com/2025/08/07/65570280-62375873-0.png"
+            width="100%"
+            height="100%"
+            mode="aspectFit"
+            class="preview-image"
+          ></image>
+        </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";
+import { getMiniProgramData } from "@/api/api";
+
+// 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({});
+// 充值账户
+const rechargeInfo = ref({
+  alipayAccount: "",
+  alipayUsername: "",
+  bankCardBank: "",
+  bankCardNumber: "",
+  bankCardUsername: "",
+});
+// tabs切换
+const tabsChange = (item) => {
+  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 (!imageList.value.length) {
+    uni.showToast({
+      title: "请上传转账图片",
+      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();
+};
+
+async function updateWxSettingInfo() {
+  const { data } = await getMiniProgramData();
+  rechargeInfo.value = {
+    alipayAccount: data.alipayAccount || "",
+    alipayUsername: data.alipayUsername || "",
+    bankCardBank: data.bankCardBank || "",
+    bankCardNumber: data.bankCardNumber || "",
+    bankCardUsername: data.bankCardUsername || "",
+  };
+}
+
+// 显示图片预览(模板调用但原代码未实现,补充空函数避免报错)
+const showImage = (url) => {
+  uni.previewImage({
+    current: url, // 当前显示图片的URL
+    urls: [url], // 需要预览的图片URL列表
+    success: () => {},
+    fail: (err) => {
+      console.error("预览图片失败:", err);
+    },
+  });
+};
+// 获取协议
+const content = ref("");
+function agreementGetoneFn() {
+  // 资产说明
+  agreementGetoneApi({ name: "recharge_rule" }).then((res) => {
+    content.value = res.data?.content;
+  });
+}
+// 5. 生命周期钩子(uni-app的onLoad,Vue3中直接使用)
+onLoad((options) => {
+  if (appStore?.$wxConfig?.auditModeEnabled) {
+    // uni.reLaunch({
+    //   url: `/pages/index/index`,
+    // });
+    // return;
+  }
+  agreementGetoneFn();
+  updateWxSettingInfo();
+  // 从Pinia Store获取用户信息(注意:Store中的userInfo若为响应式,需确保已加载)
+  userInfo.value = appStore.userInfo;
+
+  // 可选:监听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) => {
+  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: #000000;
+    font-size: 30rpx;
+
+    box-sizing: border-box;
+
+    .tabs-item {
+      width: 50%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      position: relative;
+      box-sizing: border-box;
+      color: #484848;
+      font-size: 30rpx;
+    }
+    .active {
+      color: #000;
+      font-weight: 500;
+    }
+
+    .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: 40rpx;
+    border-top-right-radius: 40rpx;
+    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: #f8c007;
+    }
+  }
+
+  .balance-box {
+    height: 300rpx;
+    padding-left: 60rpx;
+    color: #000;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    width: 100%;
+    box-sizing: border-box;
+
+    .balance-title {
+      width: 100%;
+      font-size: 30rpx;
+      // font-weight: 500;
+      margin-bottom: 10rpx;
+      // font-family: "微软雅黑";
+    }
+
+    .balance-rmb {
+      width: 100%;
+      display: flex;
+      width: 100%;
+      font-size: 56rpx;
+
+      .rmb {
+        font-size: 56rpx;
+        // 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;
+    position: relative;
+
+    .btn {
+      height: 80rpx;
+      width: 380rpx;
+    }
+    .btn-text {
+      font-size: 30rpx;
+      color: #000;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+
+  .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>

+ 44 - 39
pages/users/vault/storeMetal/GoldMailForm.vue

@@ -19,7 +19,15 @@
             </view>
           </view>
           <view class="end">
-            <view class="copy" @click="copyAddress">复制</view>
+            <view
+              class="copy"
+              @click="
+                copy(
+                  `${appStore.$wxConfig.mailerName}:${appStore.$wxConfig.mailerPhone} 地址: ${appStore.$wxConfig.mailingAddress}`
+                )
+              "
+              >复制</view
+            >
           </view>
         </view>
       </view>
@@ -122,8 +130,14 @@
           </view>
         </view>
         <view class="withdraw-body">
-          <view class="tx-active" style="margin-top: 10px">
-            <button @click="submitGoldOrder">点击提交</button>
+          <view
+            class="tx-active"
+            style="margin-top: 10px"
+            @click="submitGoldOrder"
+          >
+            <!-- <button @click="submitGoldOrder">点击提交</button> -->
+            <image class="btn" src="/static/images/sb_btn.png"></image>
+            <text class="btn-text">点击提交</text>
           </view>
           <view class="aggregate" @click="aggregate = !aggregate">
             <image
@@ -180,7 +194,7 @@ import { depositCreateAPI } from "@/api/functions";
 import { useImageUpload } from "@/hooks/useImageUpload";
 import { useAppStore } from "@/stores/app";
 const appStore = useAppStore();
-
+console.log("appStore.$wxConfig", appStore.$wxConfig);
 // 2. 响应式变量(保持不变,明确金属类型对应关系)
 const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
   pid: 9,
@@ -196,6 +210,7 @@ const expressNum = ref("");
 const cust_img = ref([]);
 const singPopup = ref(null);
 const props = defineProps(["agreement", "content"]);
+
 onLoad(() => {});
 const showAggre = () => {
   singPopup.value?.open();
@@ -232,23 +247,6 @@ const copy = (item) => {
   });
 };
 
-// 复制地址方法
-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
@@ -295,6 +293,10 @@ const submitGoldOrder = async () => {
     uni.showToast({ title: "请至少填写一个有效金属重量!", icon: "none" });
     return;
   }
+  if (!aggregate.value) {
+    uni.showToast({ title: "请阅读并同意《存金协议》!", icon: "none" });
+    return;
+  }
 
   // 步骤3:整合提交数据
   const submitData = {
@@ -352,7 +354,8 @@ page {
   height: 100%;
 }
 .icon-iconfontscan {
-  font-size: 20px;
+  font-size: 24px;
+  color: #f8c007;
 }
 .upload-box {
   .upload-block {
@@ -398,7 +401,7 @@ page {
       font-size: 30rpx;
 
       text {
-        color: #cc9933;
+        color: #000;
         padding-right: 20rpx;
         font-size: 25rpx;
       }
@@ -412,8 +415,7 @@ page {
     .input-label {
       font-size: 32rpx;
       font-weight: 500;
-
-      width: 135rpx;
+      width: 170rpx;
     }
 
     .weight-value {
@@ -516,7 +518,7 @@ page {
     /*伪元素宽度*/
     height: 15px;
     /*伪元素高度*/
-    background-color: #daa520;
+    background-color: #f8c007;
     /*伪元素颜色*/
   }
 
@@ -530,7 +532,7 @@ page {
 .withdraw {
   height: 100%;
   background-color: #f7f7f7;
-  border-radius: 10px 10px 0 0;
+  border-radius: 40rpx 40rpx 0 0;
   position: relative;
   // top: -20rpx;
   padding: 0 25rpx;
@@ -564,7 +566,8 @@ page {
         width: 35px;
         height: 35px;
         border-radius: 50%;
-        background-color: #cc9933;
+        // background-color: #cc9933;
+        background-color: #ffd034;
       }
 
       .address {
@@ -650,19 +653,21 @@ page {
 
 .tx-active {
   margin: 0 auto;
+  position: relative;
   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;
+  align-items: center;
+  .btn {
+    width: 267rpx;
+    height: 71rpx;
+  }
+  .btn-text {
+    font-size: 30rpx;
+    color: #000;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
   }
 }
 

+ 3 - 1
pages/users/vault/storeMetal/gmReport.vue

@@ -221,7 +221,9 @@ const deTotalWeight = computed(() => {
     flex-direction: column;
     justify-content: center;
     border-radius: 4px;
-
+    &::v-deep .u-steps--row {
+      flex: none !important;
+    }
     &::v-deep .u-text__value {
       font-size: 24rpx !important;
     }

+ 10 - 10
pages/users/vault/storeMetal/goldBullionStock.vue

@@ -59,9 +59,9 @@ function agreementGetoneFn() {
 
 // 黄金调整价
 const adjustGoldprice = computed(() => {
-  const res = rightsStore.userBenefits.nobleMeta?.find(
+  const res = rightsStore.userBenefits.nobleMeta.find(
     (gold) => gold.name == "黄金"
-  ) || 0;
+  );
   return res;
 });
 // 获取实时金价
@@ -69,7 +69,7 @@ const viprealGoldprice = computed(
   () =>
     Number(realGoldprice.value) -
     Number(rightsStore.userBenefits.buy) +
-    Number(adjustGoldprice.value?.sellPriceAdjust)
+    Number(adjustGoldprice.value.sellPriceAdjust)
 );
 onMounted((options) => {
   agreementGetoneFn();
@@ -92,7 +92,6 @@ onMounted((options) => {
 
 .tabs-wrap {
   height: 270rpx;
-  width: 100%;
 }
 
 .tab-item {
@@ -103,12 +102,13 @@ onMounted((options) => {
   align-items: center;
   position: relative;
   box-sizing: border-box;
-  color: #fff;
-}
-
-.tab-item.active {
-  color: #fff;
-  font-weight: bold;
+  color: #484848;
+  font-size: 30rpx;
+  &.active {
+    color: #000;
+    font-weight: 500;
+    color: #000000;
+  }
 }
 
 .tab-item.active::after {

+ 109 - 76
pages/users/vault/storeMetal/index.vue

@@ -94,7 +94,7 @@
           <view class="balance-info">
             <image
               class="balance-icon"
-              src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/static/Wallet.png"
+              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/xz1_20251008142738_748_6.png"
               mode="widthFix"
             ></image>
             <view class="balance-detail">
@@ -125,15 +125,17 @@
         </view>
 
         <!-- 确认按钮(禁用逻辑:克重不合法或余额不足) -->
-        <view class="btn-box">
-          <view
+        <view class="btn-box" @click="handleConfirmSell">
+          <!-- <view
             class="confirm-btn"
             @click="handleConfirmSell"
             :class="{ disabled: !isWeightValid || !isBalanceEnough }"
             :disabled="!isWeightValid || !isBalanceEnough"
           >
-            确认卖料
-          </view>
+          
+          </view> -->
+          <image class="confirm-btn" src="/static/images/sb_btn.png"></image>
+          <view class="confirm-btn-text">确认卖料</view>
         </view>
 
         <!-- 规则说明(若有配置则显示) -->
@@ -211,7 +213,7 @@
           <view class="balance-info">
             <image
               class="balance-icon"
-              src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/static/Wallet.png"
+              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/xz1_20251008142738_748_6.png"
               mode="widthFix"
             ></image>
             <view class="balance-detail">
@@ -244,21 +246,26 @@
             ></image>
             <view class="aggre">
               阅读并同意
-              <span class="aggre-text" @click="rmbShowAggre">《余料出售协议》</span>
+              <span class="aggre-text" @click="rmbShowAggre"
+                >《余料出售协议》</span
+              >
             </view>
           </view>
         </view>
 
         <!-- 确认按钮(禁用逻辑:未同意协议/克重不合法) -->
-        <view class="btn-box">
-          <view
+        <view class="btn-box" @click="rmbHandleConfirmSell()">
+          <!-- <view
             class="confirm-btn"
             @click="rmbHandleConfirmSell()"
             :class="{ disabled: !rmbAggregate || !rmbIsPost }"
             :disabled="!rmbAggregate || !rmbIsPost"
           >
             确认出售
-          </view>
+            
+          </view> -->
+          <image class="confirm-btn" src="/static/images/sb_btn.png"></image>
+          <view class="confirm-btn-text">确认出售</view>
         </view>
 
         <!-- 出售协议弹窗 -->
@@ -304,6 +311,7 @@ import useRealGoldPrice from "@/hooks/useRealGoldPrice";
 import { useStoreRights } from "@/stores/rights";
 import { exchangeGoldAPI, recycleCreateAPI } from "@/api/functions";
 import { agreementGetoneApi } from "@/api/user";
+import { getMiniProgramData } from "@/api/api";
 
 // 1. 全局状态与工具
 const appStore = useAppStore();
@@ -335,7 +343,7 @@ const goldRecyleCategoryList = ref([
 const activeGoldCategoryKey = ref("au");
 const goldName = ref("黄金");
 const setting = ref(appStore.setting || {}); // 全局配置(如回收规则)
-const wxConfig = appStore.$wxConfig;
+const wxConfig = ref(appStore.$wxConfig || {});
 
 // 3. 约价回收相关响应式数据
 const weight = ref("");
@@ -357,27 +365,27 @@ const rmbSingPopupRef = ref(null); // 协议弹窗ref
 // 5. 金料定金映射(核心规则)
 const GOLD_DEPOSIT_MAP = ref({
   au: {
-    depositPerGram: 40,
-    minimumWeight: 1,
-    minirecycleWeight: 1,
+    depositPerGram: 30,
+    minimumWeight: 5,
+    minirecycleWeight: 0.01,
     svipDiscountEnabled: false,
   },
   kau: {
-    depositPerGram: 40,
-    minimumWeight: 1,
+    depositPerGram: 30,
+    minimumWeight: 5,
     minirecycleWeight: 1,
     svipDiscountEnabled: false,
   },
   pt: {
-    depositPerGram: 40,
-    minimumWeight: 1,
-    minirecycleWeight: 1,
+    depositPerGram: 30,
+    minimumWeight: 5,
+    minirecycleWeight: 0.01,
     svipDiscountEnabled: false,
   },
   ag: {
-    depositPerGram: 40,
-    minimumWeight: 1,
-    minirecycleWeight: 1,
+    depositPerGram: 1,
+    minimumWeight: 10,
+    minirecycleWeight: 0.01,
     svipDiscountEnabled: false,
   },
 });
@@ -385,10 +393,11 @@ const GOLD_DEPOSIT_MAP = ref({
 onShow(() => {
   getAgreementContent();
   appStore.isLogin && updateUserInfo();
+  appStore.isLogin && updateWxSettingInfo();
 });
 
 onLoad(() => {
-  setGoldDeposit();
+  // setGoldDeposit();
   if (!appStore.$wxConfig?.auditModeEnabled) {
     tabList.value = [
       { id: 1, name: "约价回收" },
@@ -411,10 +420,8 @@ const currentDepositPerGram = computed(
 // 最小起收克重(统一1g)
 const currentMinWeight = computed(() => {
   const glodInfo = GOLD_DEPOSIT_MAP.value[activeGoldCategoryKey.value];
-  console.log("glodInfo");
-
   if (activeTabId.value === 1) return glodInfo.minimumWeight;
-  else return glodInfo.minirecycleWeight;
+  else return glodInfo.minirecycleWeight || 0.01;
 });
 // 当前金料回收价(约价/余料通用)
 const realprice = computed(() => {
@@ -445,12 +452,12 @@ const advanceDeposit = computed(
 );
 // 约价回收:余额是否充足
 const isBalanceEnough = computed(() => {
-  const userBalance = Number(appStore.userInfo.nowMoney) || 0;
-  return userBalance >= advanceDeposit.value;
+  const userBalance = Number(appStore.userInfo?.nowMoney) || 0;
+  return userBalance >= (advanceDeposit.value).toFixed(2);
 });
 // 约价回收:需充值金额
 const isNeedchargePrice = computed(() => {
-  return advanceDeposit.value - Number(appStore.userInfo.nowMoney) || 0;
+  return advanceDeposit.value - Number(appStore.userInfo?.nowMoney) || 0;
 });
 // 余料出售:当前可售克重(直接从用户信息读取,移除冗余的preciousMetal)
 const currentAvailableWeight = computed(() => {
@@ -493,33 +500,37 @@ const vipRealGoldRecyclePrice = computed(() => {
   const base = Number(realGoldRecyclePrice.value || 0);
   const benefit = Number(rightsStore.userBenefits?.sold || 0);
   const adjustBuy = Number(adjustGoldprice.value?.buyPriceAdjust || 0);
-  return appStore.userInfo?.svip
-    ? base - adjustBuy + benefit + 0.3
-    : base + benefit - adjustBuy;
+  const svipDiscount = appStore.userInfo?.svip ? 0.3 : 0;
+  // console.log("融通金价 ===========>", base);
+  // console.log("等级权益 ===========>", benefit);
+  // console.log("调整价 ===========>", adjustBuy);
+  // console.log("svip权益 ===========>", svipDiscount);
+
+  return base - adjustBuy + benefit + svipDiscount;
 });
 const viprealKGoldRecyclePrice = computed(() => {
   const base = Number(realKGoldRecyclePrice.value || 0);
   const benefit = Number(rightsStore.userBenefits?.sold || 0);
   const adjustBuy = Number(adjustKprice.value?.buyPriceAdjust || 0);
-  return appStore.userInfo?.svip
-    ? base - adjustBuy + benefit + 0.3
-    : base + benefit - adjustBuy;
+  const svipDiscount = appStore.userInfo?.svip ? 0.3 : 0;
+
+  return base - adjustBuy + benefit + svipDiscount;
 });
 const viprealPtRecyclePrice = computed(() => {
   const base = Number(realPtRecyclePrice.value || 0);
   const benefit = Number(rightsStore.userBenefits?.sold || 0);
   const adjustBuy = Number(adjustPtprice.value?.buyPriceAdjust || 0);
-  return appStore.userInfo?.svip
-    ? base - adjustBuy + benefit + 0.3
-    : base + benefit - adjustBuy;
+  const svipDiscount = appStore.userInfo?.svip ? 0.3 : 0;
+
+  return base - adjustBuy + benefit + svipDiscount;
 });
 const viprealAgRecyclePrice = computed(() => {
   const base = Number(realAgRecyclePrice.value || 0);
   const benefit = Number(rightsStore.userBenefits?.sold || 0);
   const adjustBuy = Number(adjustAgprice.value?.buyPriceAdjust || 0);
-  return appStore.userInfo?.svip
-    ? base - adjustBuy + benefit + 0.3
-    : base + benefit - adjustBuy;
+  const svipDiscount = appStore.userInfo?.svip ? 0.3 : 0;
+
+  return base - adjustBuy + benefit + svipDiscount;
 });
 
 // 8. 约价回收克重校验
@@ -636,6 +647,12 @@ const handlerPwd = async (e) => {
     uni.showLoading({
       title: "下单中",
     });
+    console.log("金价 ===========>", realprice);
+    console.log("融通金价 ===========>", realGoldRecyclePrice.value);
+    console.log("等级权益 ===========>", rightsStore.userBenefits?.sold);
+    console.log("调整价 ===========>", adjustGoldprice.value?.buyPriceAdjust);
+    console.log("svip权益 ===========>", appStore.userInfo?.svip);
+
     const res = await recycleCreateAPI({
       deposit:
         GOLD_DEPOSIT_MAP.value[activeGoldCategoryKey.value].depositPerGram,
@@ -654,7 +671,7 @@ const handlerPwd = async (e) => {
     // 延迟跳转确保提示可见
     setTimeout(() => {
       uni.navigateTo({
-        url: `/pages/users/vault/recycle/order_fill?order=${JSON.stringify(
+        url: `/pages/users/vault/recycle/index?order=${JSON.stringify(
           res.data
         )}`,
       });
@@ -688,7 +705,9 @@ const rmbHandleConfirmSell = async () => {
   if (res.code === 200) {
     uni.showToast({ title: "出售成功,余额已更新", icon: "none" });
     setTimeout(() => {
-      uni.navigateTo({ url: "/pages/users/vault/index" });
+      uni.navigateTo({
+        url: "/pages/users/vault/index",
+      });
     }, 500);
     // 重置输入
     rmbExtract.value = "";
@@ -707,6 +726,12 @@ async function updateUserInfo() {
   appStore.UPDATE_USERINFO(data);
 }
 
+// 16. 数据同步方法
+async function updateWxSettingInfo() {
+  const { data } = await getMiniProgramData();
+  setGoldDeposit(data);
+}
+
 // 获取协议内容
 function getAgreementContent() {
   // 约价回收规则
@@ -719,10 +744,10 @@ function getAgreementContent() {
   });
 }
 
-const setGoldDeposit = () => {
-  if (!wxConfig?.metalConfigs) return;
+const setGoldDeposit = (data) => {
+  if (!data?.metalConfigs) return;
   const initDate = {};
-  wxConfig.metalConfigs.map((item) => {
+  data.metalConfigs.map((item) => {
     initDate[item.metalType] = item;
   });
   GOLD_DEPOSIT_MAP.value = initDate;
@@ -748,7 +773,7 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
   min-height: 100vh;
   padding: 10rpx 30rpx;
   position: relative;
-  background-image: linear-gradient(360deg, #ffffff 50%, #e8c279 100%);
+  background: linear-gradient(180deg, #ffe079 0%, #ffffff 50%);
 
   .sell-postend-banner {
     position: absolute;
@@ -756,8 +781,7 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
     left: 0;
     width: 100%;
     height: 50vh;
-    // z-index: 0;
-    // background-image: linear-gradient(360deg, #ffffff 0%, #e8c279 100%);
+    z-index: -1;
   }
 
   .page-content {
@@ -767,13 +791,15 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
       justify-content: space-around;
       margin: 45rpx 0;
       font-size: 30rpx;
-      color: #000000;
-      font-weight: bold;
 
       .tabs-item {
         padding: 10rpx 0;
+        color: #484848;
+
         &.active {
-          border-bottom: 4rpx solid #cc9933;
+          border-bottom: 6rpx solid #f8c007;
+          color: #000000;
+          font-weight: bold;
         }
       }
     }
@@ -790,7 +816,6 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
         position: relative;
         font-size: 30rpx;
         color: #000000;
-
         &.active::after {
           position: absolute;
           bottom: -36rpx;
@@ -833,8 +858,8 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
           right: 0;
           padding: 2rpx 20rpx;
           font-size: 20rpx;
-          color: #fff;
-          background: #cd9933;
+          color: #000;
+          background: #ffd034;
           border-radius: 10rpx;
           border-top-left-radius: 60rpx;
           border-bottom-right-radius: 60rpx;
@@ -842,7 +867,7 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
 
         .current-price {
           font-size: 37rpx;
-          color: #cc9933;
+          color: #f96400;
           margin: 0 10rpx;
         }
       }
@@ -886,7 +911,7 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
           margin-bottom: 15rpx;
 
           .info-label {
-            color: #666;
+            color: #000000;
           }
 
           .info-value {
@@ -933,7 +958,7 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
 
             .balance-amount {
               font-size: 26rpx;
-              color: #7c7c7c;
+              color: #f96400;
 
               text {
                 margin-left: 10rpx;
@@ -950,15 +975,21 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
       }
 
       .recharge-btn {
-        width: 177rpx;
-        height: 59rpx;
-        background-color: #e8c279;
-        font-size: 26rpx;
-        border-radius: 30rpx;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        color: #ffffff;
+        width: 200rpx;
+        height: 55rpx;
+        position: relative;
+        .recharge-btn-img {
+          width: 100%;
+          height: 100%;
+        }
+        .recharge-btn-text {
+          color: #000000;
+          font-size: 22rpx;
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+        }
       }
     }
 
@@ -967,19 +998,21 @@ watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
       display: flex;
       justify-content: center;
       align-items: center;
+      position: relative;
 
       .confirm-btn {
-        width: 269rpx;
-        height: 66rpx;
-        background-color: #e8c279;
-        border-radius: 34rpx;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        font-size: 28rpx;
-        color: #ffffff;
+        width: 292rpx;
+        height: 80rpx;
         margin: 70rpx 0;
       }
+      .confirm-btn-text {
+        font-size: 33rpx;
+        color: #000000;
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      }
     }
 
     .recycle-container {

+ 214 - 338
pages/users/vault/storeMetal/metalExchange.vue

@@ -12,59 +12,12 @@
           {{ 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>
+            <h3 class="title">提料克重</h3>
           </view>
           <view class="input-box">
             <input
@@ -87,333 +40,258 @@
                   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 v-else class="infoMoneyNum">
+              <view> 手续费:{{ totalFee }}元,{{ feePerGram }}元/g </view>
+              <view>账户克重:{{ accountWeight }}克</view>
+            </view>
+            <!-- <view class="infoTip">*默认融成小圆饼寄出</view> -->
+          </view>
+
+          <view class="remark-box">
+            <view class="header">
+              <h3 class="title">备注</h3>
+            </view>
+            <view class="input-box" style="margin-top: 20rpx">
+              <input
+                v-model="remark"
+                placeholder="请输入备注(如特殊需求、收货说明等)"
+                type="text"
+                class="t-input"
+                maxlength="50"
+              />
+            </view>
           </view>
         </view>
       </view>
 
+      <view class="order-list" @click="gotoOrderList">
+        订单/预约列表 {{ ">" }}
+      </view>
+
+      <!-- 提交按钮 -->
       <view class="withdraw-bottom">
         <view
           :class="['submitBtn', is_post ? '' : 'submitBtnActive']"
           style="margin-top: 10px"
+          :style="{
+            opacity: isSubmitting ? 0.7 : 1,
+            pointerEvents: isSubmitting ? 'none' : 'auto',
+          }"
         >
-          <button @click="handleShowModel">提交兑换</button>
+          <button @click="handleShowModel">
+            <text v-if="!isSubmitting">前往预约</text>
+            <text v-if="isSubmitting">提交中...</text>
+          </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>
+        <up-parse :content="content"></up-parse>
       </view>
-    </uni-popup> -->
-    <!-- 收货地址组件 -->
-    <addressWindow
-      ref="addressWindowRef"
-      :address="address"
-      :pagesUrl="pagesUrl"
-      @OnDefaultAddress="OnDefaultAddress"
-      @OnChangeAddress="OnChangeAddress"
-      @changeClose="changeClose"
+    </view>
+
+    <!-- 预约日期选择弹窗 -->
+    <appointmentCalendar
+      ref="calendarRef"
+      :metalType="tabsIndex"
+      :reservedWeight="extract"
+      @confirm="onDateConfirm"
     />
   </view>
 </template>
 
 <script setup>
-import { ref, computed, nextTick } from "vue";
+import { ref, computed, nextTick, watch } from "vue";
 import { onLoad } from "@dcloudio/uni-app";
-// 导入用户地址详情API接口
-import { getAddressDetail, getAddressDefault } from "@/api/user.js";
-// 导入地址选择组件
-import addressWindow from "@/components/addressWindow";
+import { storeToRefs } from 'pinia';
+
+// 导入API/组件/Store
+import { quotaByWeight, createReservation } from "@/api/vault";
+import appointmentCalendar from "@/components/appointmentCalendar";
 import { useAppStore } from "@/stores/app";
+import { agreementGetoneApi } from "@/api/user";
+import { toLogin } from "@/libs/login.js";
+
+// 初始化Store
 const appStore = useAppStore();
-console.log(appStore.userInfo);
+const { wxConfig, isWxConfigReady } = storeToRefs(appStore);
 
+// 核心响应式数据
 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 isSubmitting = ref(false);
+const metalConfigs = ref([]);
+const remark = ref("");
+
+// 日历预约数据
+const calendarRef = ref(null);
+const selectedAppointmentDate = ref(null);
+
+// 金属类型映射
+const typeMap = { 1: "au", 2: "pt", 3: "ag" };
+
+// 获取协议
+const content = ref("");
+function agreementGetoneFn() {
+  // 资产说明
+  agreementGetoneApi({ name: "metal_exchange_content" }).then((res) => {
+    content.value = res.data?.content;
+  });
+}
 
-// 快递相关响应式数据
-const courierPrice = ref(15);
-const selectedCourierType = ref(2);
-const selectedGender = ref("gold");
+// 动态计算参数
+const feePerGram = computed(() => {
+  if (!metalConfigs.value.length) return "0.00";
+  const currentType = typeMap[tabsIndex.value];
+  const currentConfig = metalConfigs.value.find(
+    (item) => item.metalType === currentType
+  );
+  return currentConfig?.feePerGram
+    ? Number(currentConfig.feePerGram).toFixed(2)
+    : "0.00";
+});
 
-// 用户余额及手续费(响应式对象)
-const preciousMetal = ref({
-  gold: { name: "黄金", pool: 0, fee: 0 },
-  platinum: { name: "铂金", pool: 0, fee: 0 },
-  silver: { name: "白银", pool: 0, fee: 0 },
+const lowest = computed(() => {
+  if (!metalConfigs.value.length) return 0;
+  const currentType = typeMap[tabsIndex.value];
+  const currentConfig = metalConfigs.value.find(
+    (item) => item.metalType === currentType
+  );
+  return currentConfig?.minimumWithdrawalWeight
+    ? Number(currentConfig.minimumWithdrawalWeight)
+    : 0;
+});
+
+const totalFee = computed(() => {
+  const fee = Number(feePerGram.value);
+  const weight = Number(extract.value);
+  return (fee * weight).toFixed(2);
+});
+
+const accountWeight = computed(() => {
+  const weightMap = {
+    1: appStore.userInfo.goldBalance || 0,
+    2: appStore.userInfo.ptBalance || 0,
+    3: appStore.userInfo.agBalance || 0,
+  };
+  return Number(weightMap[tabsIndex.value]).toFixed(2);
 });
-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
+// 页面加载
+onLoad(() => {
+  if (appStore.isLogin) {
+    agreementGetoneFn();
+    metalConfigs.value = appStore.wxConfig?.metalConfigs || [];
+  } else {
+    toLogin();
   }
+});
+
+// 监听wxConfig就绪
+watch(
+  isWxConfigReady,
+  (isReady) => {
+    if (isReady) {
+      metalConfigs.value = appStore.wxConfig?.metalConfigs || [];
+    }
+  },
+  { immediate: true }
+);
+
+// 切换金属类型
+const tabsChange = (item) => {
+  tabsIndex.value = item.key;
+  onKeyInput();
 };
 
-// 输入框逻辑
-// 在<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; // 空输入时显示最低限制提示
+    extract.value = 0;
+    is_lowest.value = true;
     return;
   }
-
   const inputWeight = Number(extract.value);
-  const minWeight = lowest.value; // 最低1克(来自定义的lowest: ref(1))
-  const accountWeightNum = Number(accountWeight.value) || 0;
+  const minWeight = lowest.value;
+  const accountWeightNum = Number(accountWeight.value);
 
-  // 验证1:是否低于最低限制(1克)
   const isBelowMin = inputWeight < minWeight;
+  const hasMoreDecimals = /\.\d{3,}$/.test(extract.value.toString());
+  if (isBelowMin || hasMoreDecimals) is_lowest.value = true;
 
-  // 验证2:是否超过两位小数(通过正则判断)
-  const hasMoreThanTwoDecimals = /\.\d{3,}$/.test(extract.value.toString());
+  if (inputWeight > accountWeightNum) is_out.value = true;
+};
 
-  // 验证3:是否超过账户可用克重
-  const isOverAccount = inputWeight > accountWeightNum;
+// 订单列表跳转
+const gotoOrderList = () => {
+  uni.navigateTo({
+    url: "/pages/users/vault/storeMetal/metalExchangeList",
+  });
+};
 
-  // 设置提示状态
-  if (isBelowMin || hasMoreThanTwoDecimals) {
-    is_lowest.value = true; // 显示最低限制提示(包含兑换和购买)
-  }
-  if (isOverAccount) {
-    is_out.value = true; // 显示超过账户克重提示
-  }
+// 参数校验
+const validateSubmitParams = () => {
+  if (!appStore.isLogin) return { valid: false, msg: "请先登录" };
+  if (is_out.value || is_lowest.value || extract.value <= 0)
+    return { valid: false, msg: "请输入有效克重" };
+  return { valid: 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;
+// 点击提交按钮
+const handleShowModel = async () => {
+  if (!appStore.userInfo.realNameVerified) {
+    uni.showToast({ title: "请先进行实名认证", icon: "none", duration: 2000 });
+    uni.navigateTo({ url: "/pages/users/face_detect/index" });
+    return;
   }
 
-  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;
-      }
-    });
+  const check = validateSubmitParams();
+  if (!check.valid) {
+    uni.showToast({ title: check.msg, icon: "none", duration: 2000 });
+    return;
   }
+
+  calendarRef.value?.open();
 };
-onLoad(() => {
-  // 若已登录且非支付页面,则获取地址信息(注释掉的逻辑,预留)
-  if (appStore.isLogin) {
-    // console.log(1111);
 
-    getaddressInfo();
-    // 等待DOM更新后,调用地址组件的方法获取地址列表
-    nextTick(() => {
-      addressWindowRef.value.fetchAddressList();
-    });
+// 日期确认回调
+const onDateConfirm = async (date) => {
+  const params = {
+    metalType: tabsIndex.value,
+    reservationDate: date,
+    reservedWeight: extract.value,
+    remark: remark.value,
+  };
+
+  try {
+    isSubmitting.value = true;
+    uni.showLoading({ title: "提交预约...", mask: true });
+    await createReservation(params);
+    uni.hideLoading();
+
+    uni.showToast({ title: "预约成功", icon: "success" });
+    setTimeout(() => {
+      gotoOrderList();
+    }, 1000);
+    extract.value = 0;
+  } catch (err) {
+    isSubmitting.value = false;
+    uni.hideLoading();
+    uni.showToast({ title: err || "提交失败,请重试", icon: "none" });
+  } finally {
+    isSubmitting.value = false;
   }
-});
-// 关闭地址弹窗
-const changeClose = () => {
-  address.value.address = false; // 隐藏地址弹窗
 };
 </script>
 
@@ -422,8 +300,8 @@ $item-value-color: #dca537;
 $card-bcolor: #f8f8f8;
 
 page {
-  height: 100%;
-  background-color: #ededed;
+  min-height: 95vh;
+  background-color: #f7f7f7;
 }
 .tabs {
   display: flex;
@@ -433,14 +311,12 @@ page {
   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;
   }
@@ -451,16 +327,25 @@ page {
     left: 50%;
     transform: translateX(-50%);
     content: "";
-    z-index: 100;
+    z-index: 20;
     display: block;
     width: 65rpx;
     height: 6rpx;
-    background-color: #cc9933;
+    background-color: #f8c007;
   }
 }
+
+.order-list {
+  margin-top: 50rpx;
+  text-align: center;
+  color: #767676;
+  font-size: 30rpx;
+}
+
 .withdraw-bottom {
   width: 100%;
-  margin-top: 100rpx;
+  margin-top: 10rpx;
+  margin-bottom: 100rpx;
   display: flex;
   justify-content: center;
 }
@@ -473,8 +358,6 @@ page {
   justify-content: space-between;
   padding: 10px 10px;
   border-radius: 5px;
-  // background-color: #fff;
-  // font-weight: bold;
   font-size: 18px;
 
   .live-gold {
@@ -569,7 +452,7 @@ page {
     width: 90%;
   }
 }
-// 地址相关样式
+
 .address {
   width: 690rpx;
   max-height: 180rpx;
@@ -611,14 +494,10 @@ page {
 }
 .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;
@@ -637,10 +516,8 @@ page {
       }
     }
 
-    // 快递公司选择
     .courier-list {
       display: flex;
-      // overflow-x: auto;
       justify-content: space-between;
       padding-bottom: 16rpx;
 
@@ -660,7 +537,6 @@ page {
           position: absolute;
           width: 40rpx;
           height: 40rpx;
-          // background: #dca537;
           right: -7rpx;
           bottom: -7rpx;
           border-radius: 50%;
@@ -691,11 +567,11 @@ page {
   background-size: 100% 40%;
   background: $uni-bg-primary !important;
   .withdrawContent {
+    height: 90%;
     padding: 45rpx 40rpx;
     background-color: #f7f7f7;
-    // margin-top: ;
-    position: relative;
     top: 150rpx;
+    position: relative;
     border-radius: 50rpx 50rpx 0 0;
   }
 
@@ -713,7 +589,6 @@ page {
 
       .rmb {
         font-size: 16px;
-        // text-wrap: nowrap;
       }
 
       .tInput {
@@ -771,7 +646,7 @@ page {
   button {
     background-color: #dca12b;
     color: #fff;
-    width: 450rpx;
+    width: 380rpx;
     height: 72rpx;
     display: flex;
     font-size: 30rpx;
@@ -784,7 +659,8 @@ page {
 .submitBtnActive {
   button {
     color: #fff;
-    background: #c4bba6;
+    background: #ffe079;
+    box-shadow: 0 10rpx 8rpx rgba(207, 6, 6, 0.05);
   }
 }
 

+ 503 - 0
pages/users/vault/storeMetal/metalExchangeList.vue

@@ -0,0 +1,503 @@
+<template>
+  <view class="container">
+    <view class="tabs-container">
+      <view
+        class="tab-item"
+        :class="{ active: currentTab === 0 }"
+        @click="switchTab(0)"
+      >
+        预约订单
+      </view>
+      <view
+        class="tab-item"
+        :class="{ active: currentTab === 1 }"
+        @click="switchTab(1)"
+      >
+        提料订单
+      </view>
+    </view>
+
+    <z-paging
+      class="paging-box"
+      ref="pagingRef"
+      use-page-scroll
+      v-model="currentList"
+      @query="handleQuery"
+      @refresherTouchend="refresherTouchend"
+      :fixed="false"
+      :empty-view-text="getEmptyText()"
+    >
+      <view
+        class="list-item"
+        v-for="(item, index) in combineList"
+        :key="index"
+        :class="{ 'reservation-item': currentTab === 0 }"
+      >
+        <view class="item-top">
+          <view class="item-left">
+            <view class="item-detail">
+              <text class="detail-label">
+                金属类型:<span class="detail-value">{{
+                  item.metalTypeMsg
+                }}</span>
+              </text>
+              <text class="detail-label" style="margin-left: 20rpx">
+                克重:{{ item.weight }}g
+              </text>
+            </view>
+            <view v-if="currentTab === 0" class="item-time">
+              预约日期:{{ item.reservationDate }}
+            </view>
+            <view
+              v-if="currentTab === 1"
+              class="item-time"
+              @click="copy(item.expressNo)"
+            >
+              快递单号:{{ item.expressNo || "发货后将在此处显示快递单号" }}
+            </view>
+            <view class="item-time">创建时间:{{ item.createTime }}</view>
+          </view>
+          <view v-if="currentTab === 0" class="item-status-tag">
+            <view class="status-tag" :class="getStatusClass(item.status)">
+              {{ getStatusText(item.status) }}
+            </view>
+          </view>
+          <view v-if="currentTab === 1" class="item-amount">
+            金价¥{{ item.realTimePrice }}/g
+          </view>
+        </view>
+
+        <!-- 预约订单底部操作栏:取消订单(始终显示)+ 前往提料 -->
+        <view v-if="currentTab === 0" class="item-operation">
+          <!-- 取消订单:根据status控制可点击和颜色 -->
+          <button
+            class="cancel-btn"
+            :class="{ 'cancel-btn--disabled': item.status !== 1 }"
+            :disabled="item.status !== 1"
+            @click="handleCancelReservation(item.reservationId)"
+          >
+            取消订单
+          </button>
+          <!-- 前往提料:根据status + 时间控制可点击和颜色 -->
+          <button
+            class="withdraw-btn"
+            :class="{
+              'withdraw-btn--disabled':
+                item.status !== 1 || !canGoToWithdraw(item.reservationDate),
+            }"
+            :disabled="
+              item.status !== 1 || !canGoToWithdraw(item.reservationDate)
+            "
+            @click="gotoWithdraw(item)"
+          >
+            前往提料
+          </button>
+        </view>
+      </view>
+    </z-paging>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed } 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 { useToast } from "@/hooks/useToast";
+import { getUserInfo } from "@/api/user";
+import {
+  getMetalOrderList,
+  getMyReservations,
+  cancelReservation,
+} from "@/api/vault";
+
+const appStore = useAppStore();
+const { Toast } = useToast();
+const pagingRef = ref(null);
+useZPaging(pagingRef);
+
+// tabs状态
+const currentTab = ref(0); // 0-预约订单 1-提料订单
+const reservationList = ref([]); // 预约订单数据
+const recordList = ref([]); // 提料订单原始数据
+
+// 金属类型映射
+const metalTypeMap = {
+  1: "黄金",
+  2: "铂金",
+  3: "白银",
+};
+
+// 根据当前tab返回对应的数据列表
+const currentList = computed(() => {
+  return currentTab.value === 0 ? reservationList.value : recordList.value;
+});
+
+// 处理列表数据格式化
+const combineList = computed(() => {
+  if (currentTab.value === 0) {
+    return reservationList.value.map((item) => ({
+      ...item,
+      orderNo: item.reservationNo || item.id,
+      weight: item.reservedWeight,
+      metalTypeMsg: metalTypeMap[item.metalType] || "未知金属",
+      reservationDate: item.reservationDate,
+      createTime: item.createTime,
+      status: item.status,
+      reservationId: item.id || item.reservationId,
+      metalType: item.metalType,
+    }));
+  } else {
+    return recordList.value.map((item) => ({
+      ...item,
+      orderNo: item.orderNo,
+      amount: item.totalAmount,
+      weight: item.plateWeight,
+      metalTypeMsg:
+        item.metalTypeName || metalTypeMap[item.metalType] || "未知金属",
+      expressName: item.expressCompanyName || "未选择",
+      createTime: item.createTime,
+      realTimePrice: item.realTimePrice,
+      expressNo: item.expressNo,
+    }));
+  }
+});
+
+// 切换tabs
+const switchTab = (tabIndex) => {
+  currentTab.value = tabIndex;
+  pagingRef.value?.reload();
+};
+
+// 空状态文本
+const getEmptyText = () => {
+  return currentTab.value === 0 ? "暂无预约订单" : "暂无提料兑换订单";
+};
+
+// 预约状态文本映射
+const getStatusText = (status) => {
+  const statusMap = {
+    1: "已预约",
+    2: "已完成",
+    3: "已取消",
+  };
+  return statusMap[status] || "未知状态";
+};
+
+// 预约状态标签样式
+const getStatusClass = (status) => {
+  const classMap = {
+    1: "status-reserved", // 已预约-绿色
+    2: "status-completed", // 已完成-蓝色
+    3: "status-canceled", // 已取消-灰色
+  };
+  return classMap[status] || "";
+};
+
+// 判断是否可点击「前往提料」(仅status=1时判断时间)
+const canGoToWithdraw = (reservationDate) => {
+  if (!reservationDate) return false;
+  const reserveDate = new Date(reservationDate).getTime();
+  const endTime = reserveDate + 24 * 60 * 60 * 1000;
+  const now = new Date().getTime();
+  return now > reserveDate && now < endTime;
+};
+
+// 前往提料页面
+const gotoWithdraw = (item) => {
+  uni.redirectTo({
+    url: `/pages/users/vault/storeMetal/metalExchangeWithdraw?metalType=${item.metalType}&weight=${item.weight}&reservationId=${item.reservationId}`,
+  });
+};
+
+// 统一查询入口
+const handleQuery = async (page, pageSize) => {
+  if (currentTab.value === 0) {
+    await queryReservationList(page, pageSize);
+  } else {
+    await queryMaterialOrderList(page, pageSize);
+  }
+};
+
+// 查询预约订单列表
+const queryReservationList = async (page, pageSize) => {
+  try {
+    const params = { page, pageSize, uid: appStore.uid };
+    const res = await getMyReservations(params);
+    const newList = res?.data?.list || [];
+    reservationList.value =
+      page === 1 ? newList : [...reservationList.value, ...newList];
+    const total = res?.data?.total || 0;
+    pagingRef.value?.complete(reservationList.value, total);
+  } catch (error) {
+    console.error("预约订单查询失败:", error);
+    pagingRef.value?.complete(false);
+    Toast({ title: "查询失败,请稍后重试" });
+  }
+};
+
+// 查询提料订单列表
+const queryMaterialOrderList = async (page, pageSize) => {
+  try {
+    const params = { page, pageSize, uid: appStore.uid, userId: appStore.uid };
+    const res = await getMetalOrderList(params);
+    const newList = res?.data?.list || [];
+    recordList.value = page === 1 ? newList : [...recordList.value, ...newList];
+    const total = res?.data?.total || 0;
+    pagingRef.value?.complete(recordList.value, total);
+  } catch (error) {
+    console.error("提料兑换订单查询失败:", error);
+    pagingRef.value?.complete(false);
+    Toast({ title: "查询失败,请稍后重试" });
+  }
+};
+
+// 取消预约操作(仅status=1时触发,已通过按钮disabled控制)
+const handleCancelReservation = async (reservationId) => {
+  uni.showModal({
+    title: "提示",
+    content: "确定要取消该预约吗?取消后将无法恢复",
+    confirmText: "确认取消",
+    cancelText: "取消",
+    success: async (res) => {
+      if (res.confirm) {
+        try {
+          uni.showLoading({ title: "处理中...", mask: true });
+          await cancelReservation(reservationId);
+          uni.showToast({ title: "取消成功", icon: "success" });
+          pagingRef.value?.reload();
+        } catch (error) {
+          console.error("取消预约失败:", error);
+          uni.showToast({ title: "取消失败,请稍后重试", icon: "none" });
+        } finally {
+          uni.hideLoading();
+        }
+      }
+    },
+  });
+};
+
+onLoad(() => {
+  pagingRef.value?.reload();
+});
+
+// 复制快递单号
+const copy = (expressNo) => {
+  if (!expressNo) return;
+  uni.setClipboardData({
+    data: expressNo,
+    success: () => uni.showToast({ title: "复制成功", icon: "success" }),
+    fail: (err) => {
+      uni.showToast({ title: "复制失败", icon: "none" });
+      console.error("复制失败:", err);
+    },
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+$primary-color: #e9c279;
+$text-color: #333;
+$text-secondary: #666;
+$text-light: #999;
+$bg-color: #f5f5f5;
+$white: #fff;
+$cancel-color: #ff1e0f; // 取消按钮-启用色
+$cancel-disabled-color: #ccc; // 取消按钮-禁用色
+$withdraw-color: #007aff; // 提料按钮-启用色
+$withdraw-disabled-color: #ccc; // 提料按钮-禁用色
+
+// tabs样式
+.tabs-container {
+  display: flex;
+  height: 80rpx;
+  background-color: $white;
+  border-radius: 10rpx;
+  margin-bottom: 20rpx;
+  overflow: hidden;
+
+  .tab-item {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 30rpx;
+    color: $text-secondary;
+    position: relative;
+
+    &.active {
+      color: $primary-color;
+      font-weight: 500;
+
+      &::after {
+        content: "";
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        width: 100%;
+        height: 4rpx;
+        background-color: $primary-color;
+      }
+    }
+  }
+}
+
+// 页面容器
+.container {
+  min-height: 100vh;
+  overflow: hidden;
+  background: $bg-color;
+  padding: 20rpx;
+}
+
+// 分页列表容器
+.paging-box {
+  min-height: 60vh;
+}
+
+// 通用列表项样式
+.list-item {
+  background: $white;
+  margin-bottom: 20rpx;
+  border-radius: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+// 预约订单卡片专用样式
+.reservation-item {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+// 卡片顶部区域
+.item-top {
+  display: flex;
+  padding: 30rpx 25rpx;
+  padding-bottom: 0;
+  justify-content: space-between;
+  align-items: flex-start;
+}
+
+// 左侧信息区域
+.item-left {
+  flex: 1;
+
+  .item-detail {
+    margin-bottom: 15rpx;
+    font-size: 26rpx;
+
+    .detail-label {
+      color: $text-secondary;
+    }
+
+    .detail-value {
+      color: $primary-color !important;
+    }
+  }
+
+  .item-time {
+    font-size: 22rpx;
+    color: $text-light;
+    margin-bottom: 8rpx;
+  }
+}
+
+// 预约订单右上角状态标签
+.item-status-tag {
+  display: flex;
+  align-items: flex-start;
+}
+
+// 提料订单右上角金价
+.item-amount {
+  font-size: 26rpx;
+  font-weight: 500;
+  color: $primary-color;
+}
+
+// 预约状态标签样式
+.status-tag {
+  width: 120rpx;
+  text-align: center;
+  padding: 4rpx 0;
+  border-radius: 20rpx;
+  font-size: 22rpx;
+  color: $white;
+
+  &.status-reserved {
+    background-color: #4cd964;
+  }
+
+  &.status-completed {
+    background-color: #007aff;
+  }
+
+  &.status-canceled {
+    background-color: $text-light;
+  }
+}
+
+// 预约订单底部操作栏
+.item-operation {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 50rpx;
+  border-top: 1px solid #f5f5f5;
+}
+
+// 取消订单按钮-基础样式
+.cancel-btn {
+  flex: 1;
+  height: 100%;
+  background-color: $cancel-color;
+  color: $white;
+  font-size: 24rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+  padding: 0;
+  cursor: pointer;
+
+  &::after {
+    border: none;
+  }
+}
+
+// 取消订单按钮-禁用样式
+.cancel-btn--disabled {
+  background-color: $cancel-disabled-color;
+  color: #fff;
+  cursor: not-allowed; // 鼠标禁用样式
+  opacity: 0.8;
+}
+
+// 前往提料按钮-基础样式
+.withdraw-btn {
+  flex: 1;
+  height: 100%;
+  background-color: $withdraw-color;
+  color: $white;
+  font-size: 24rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+  padding: 0;
+  cursor: pointer;
+
+  &::after {
+    border: none;
+  }
+}
+
+// 前往提料按钮-禁用样式
+.withdraw-btn--disabled {
+  background-color: $withdraw-disabled-color;
+  color: #fff;
+  cursor: not-allowed;
+  opacity: 0.8;
+}
+</style>

文件差異過大導致無法顯示
+ 614 - 0
pages/users/vault/storeMetal/metalExchangeWithdraw.vue


+ 30 - 38
pages/users/vault/storeMetal/nonLogisticsGold.vue

@@ -22,7 +22,7 @@
                 placeholder-style="color: #999999; font-size: 28rpx;"
               />
             </view>
-            <!-- <view class="header">
+            <view class="header">
               <span class="title">淘宝订单号</span>
             </view>
             <view class="input-box">
@@ -34,7 +34,7 @@
                 @input="onKeyInput"
                 placeholder-style="color: #999999; font-size: 28rpx;"
               />
-            </view> -->
+            </view>
           </view>
         </view>
         <view class="info-money" style="font-size: 16px">
@@ -74,11 +74,12 @@
         </view>
         <view class="submit-box">
           <view
-            :class="'tx' + (is_post ? '' : '-active')"
             style="margin-top: 10px"
             class="submit"
+            @click="handleShowModel"
           >
-            <button @click="handleShowModel">提交存金</button>
+            <image class="btn" src="/static/images/sb_btn.png"></image>
+            <text class="btn-text">点击提交</text>
           </view>
           <view class="aggregate" @click="aggregate = !aggregate">
             <image
@@ -129,7 +130,6 @@ 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",
@@ -145,11 +145,7 @@ 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));
+const expressNo = ref("");
 
 // 获取协议
 function agreementGetoneFn() {
@@ -165,10 +161,6 @@ const showAggre = () => {
   singPopup.value?.open();
 };
 
-const onKeyInput = (e) => {
-  extract.value = e.target.value;
-};
-
 // 提交存金
 const handleShowModel = async () => {
   if (!extract.value) {
@@ -178,17 +170,17 @@ const handleShowModel = async () => {
       icon: "none",
     });
   }
-  // if (!expressNo.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,
+    taobaoOrderNo: expressNo.value,
   });
   uni.showToast({ title: "下单成功!" });
 
@@ -215,10 +207,6 @@ watch(
 </script>
 
 <style lang="scss" scoped>
-page {
-  height: 100%;
-  background-color: #ededed;
-}
 .upload-box {
   .upload-block {
     width: 160rpx;
@@ -247,16 +235,19 @@ page {
   width: 100%;
   display: flex;
   justify-content: center;
-  button {
-    background-color: #dca12b;
-    color: #fff;
-    width: 450rpx;
-    height: 72rpx;
-    display: flex;
+  align-items: center;
+  position: relative;
+  .btn {
+    width: 267rpx;
+    height: 71rpx;
+  }
+  .btn-text {
     font-size: 30rpx;
-    justify-content: center;
-    align-items: center;
-    border-radius: 30rpx;
+    color: #000;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
   }
 }
 
@@ -278,7 +269,8 @@ page {
     font-family: "黑体";
 
     .price {
-      color: #d0a34a;
+      // color: #d0a34a;
+      color: $txt-color;
       margin-left: 10rpx;
       font-weight: bold;
       font-size: 38rpx;
@@ -341,7 +333,7 @@ page {
     /*伪元素宽度*/
     height: 15px;
     /*伪元素高度*/
-    background-color: #daa520;
+    background-color: #f8c007;
     /*伪元素颜色*/
   }
 
@@ -406,7 +398,7 @@ page {
 .withdraw {
   height: 100%;
   background-color: #f7f7f7;
-  height: 100%;
+  min-height: 100%;
 
   border-radius: 10px 10px 0 0;
   position: relative;

+ 6 - 2
pages/users/vault/storeMetal/order.vue

@@ -77,7 +77,7 @@ const list = ref([
 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"
+  "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example1.png"
 );
 
 const params = ref({
@@ -143,6 +143,9 @@ const getOrderList = async (isRefresh = false) => {
 // 其他生命周期
 onPullDownRefresh(() => {
   // 下拉刷新逻辑(保持原有空实现,可根据需求补充)
+  params.value.page = 1;
+  getOrderList();
+  uni.stopPullDownRefresh();
 });
 
 onReachBottom(() => {
@@ -263,7 +266,8 @@ const previewImage = (urls) => {
   padding-top: 10px;
   border-radius: 5px;
   background-color: #fff;
-  border: 1px solid #cecece;
+  border: 2px solid #eee;
+  box-shadow: 0 10rpx 8rpx rgba(100, 88, 88, 0.05);
   .header {
     padding: 0 10px;
     position: relative;

+ 72 - 83
pages/users/vault/withdraw.vue

@@ -1,21 +1,25 @@
 <template>
   <view class="withdraw-container">
     <!-- 顶部余额展示 -->
-    <view class="balance-section">
-      <view class="balance-bg">
+    <view class="balance-box">
+      <view>
         <view class="balance-title">可提现余额</view>
-        <view class="balance-amount">¥ {{ appStore?.$userInfo?.nowMoney || 0 }}</view>
+        <view class="balance-rmb">
+          <view style="display: flex; align-items: flex-end">¥</view>
+          <view class="rmb">{{ appStore.$userInfo.nowMoney }}</view>
+        </view>
       </view>
     </view>
 
     <!-- 主要内容区域 -->
     <view class="main-content">
       <!-- 到账账户 -->
-      <view class="section" v-if="!appStore.$wxConfig?.auditModeEnabled">
+      <view class="section">
         <view class="section-title">到账账户</view>
         <view class="account-item" @click="goToBankManage">
           <view class="account-info">
             <view class="bank-icon">
+              <!-- <text class="iconfont icon-qianbao">🏦</text> -->
               <text
                 :style="{
                   color:
@@ -69,13 +73,10 @@
 
       <!-- 提交按钮 -->
       <view class="submit-section">
-        <button
-          class="submit-btn"
-          :class="{ disabled: !canSubmit }"
-          @click="submitWithdraw"
-        >
-          提交申请
-        </button>
+        <view class="btn-box" @click="submitWithdraw">
+          <image class="btn" src="/static/images/sb_btn.png"></image>
+          <text class="btn-text">提交申请</text>
+        </view>
       </view>
     </view>
     <!-- 协议窗口 -->
@@ -159,12 +160,19 @@ const withdrawAll = () => {
 };
 
 const goToBankManage = () => {
+  // 跳转到银行卡管理页面
   uni.navigateTo({
-    url: "/pages/users/card_page/index",
+    url: "/pages/users/bank_card_manage/index",
   });
 };
 
 const submitWithdraw = () => {
+  if (!appStore.userInfo.realNameVerified) {
+    uni.showToast({ title: "请先进行实名认证", icon: "none", duration: 2000 });
+    uni.navigateTo({ url: "/pages/users/face_detect/index" });
+    return;
+  }
+
   if (!canSubmit.value) return;
   if (!defaultAccount.value || !defaultAccount.value.id) {
     uni.showToast({
@@ -242,52 +250,38 @@ const submitWithdraw = () => {
 
 <style lang="scss" scoped>
 .withdraw-container {
-  min-height: 90vh;
+  min-height: 100%;
   width: 100%;
+  background: $uni-bg-primary;
 
   display: flex;
   flex-direction: column;
   align-items: center;
-  .balance-section {
+  .balance-box {
+    height: 300rpx;
+    padding-left: 60rpx;
+    color: #000;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
     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%;
-      }
+    box-sizing: border-box;
 
-      .balance-title {
-        color: rgba(255, 255, 255, 0.9);
-        font-size: 28rpx;
-        margin-bottom: 16rpx;
-        font-weight: 400;
-      }
+    .balance-title {
+      width: 100%;
+      font-size: 30rpx;
+      margin-bottom: 10rpx;
+    }
+
+    .balance-rmb {
+      width: 100%;
+      display: flex;
+      width: 100%;
+      font-size: 56rpx;
 
-      .balance-amount {
-        color: #fff;
+      .rmb {
         font-size: 56rpx;
-        font-weight: 600;
-        letter-spacing: 2rpx;
+        margin-left: 10rpx;
       }
     }
   }
@@ -297,18 +291,18 @@ const submitWithdraw = () => {
     padding: 40rpx 40rpx;
     box-sizing: border-box;
     background: #fff;
-    border-radius: 32rpx 32rpx 0 0;
+    border-radius: 40rpx 40rpx 0 0;
     margin-top: -20rpx;
-    box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
+    // box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
 
     .section {
       margin-bottom: 50rpx;
 
       .section-title {
-        color: #333;
+        color: #000;
         font-size: 32rpx;
-        font-weight: 600;
-        margin-bottom: 24rpx;
+        // font-weight: 600;
+        margin-bottom: 20rpx;
         position: relative;
 
         &::before {
@@ -317,9 +311,9 @@ const submitWithdraw = () => {
           left: -16rpx;
           top: 50%;
           transform: translateY(-50%);
-          width: 6rpx;
+          width: 4rpx;
           height: 32rpx;
-          background: #e9c279;
+          background-color: #f8c007;
           border-radius: 3rpx;
         }
       }
@@ -375,7 +369,7 @@ const submitWithdraw = () => {
         align-items: center;
         background-color: #ededed;
         border-radius: 15rpx;
-        padding: 25rpx 24rpx;
+        padding: 20rpx 24rpx;
 
         transition: border-color 0.3s ease;
 
@@ -386,7 +380,7 @@ const submitWithdraw = () => {
         .currency-symbol {
           color: #333;
           font-size: 32rpx;
-          font-weight: 600;
+          // font-weight: 600;
           margin-right: 16rpx;
         }
 
@@ -395,9 +389,9 @@ const submitWithdraw = () => {
           flex: 1;
           display: flex;
           align-items: center;
-          color: #333;
-          font-weight: 600;
-          font-size: 26rpx;
+          color: #000;
+
+          font-size: 28rpx;
 
           &::placeholder {
             color: #999;
@@ -431,33 +425,28 @@ const submitWithdraw = () => {
       display: flex;
       justify-content: center;
 
-      .submit-btn {
+      .btn-box {
+        height: 100rpx;
         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;
+        margin-bottom: 40rpx;
+        margin-top: 80rpx;
+        width: 100%;
+        box-sizing: border-box;
+        position: relative;
 
-        &:active {
-          transform: translateY(2rpx);
-          box-shadow: 0 4rpx 12rpx rgba(233, 194, 121, 0.2);
+        .btn {
+          height: 80rpx;
+          width: 380rpx;
         }
-
-        &.disabled {
-          background: #e9ecef;
-          color: #adb5bd;
-          box-shadow: none;
-
-          &:active {
-            transform: none;
-          }
+        .btn-text {
+          font-size: 30rpx;
+          color: #000;
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
         }
       }
     }

二進制
static/images/2-001.png


二進制
static/images/empty-box.png


二進制
static/images/hand.png


二進制
static/images/orderTime.png


二進制
static/images/product.png


二進制
static/images/sb_btn.png


二進制
static/images/setting/fabu.png


二進制
static/images/setting/jinqian.png


二進制
static/images/setting/kabao.png


二進制
static/images/setting/kucun.png


二進制
static/images/setting/mendian.png


二進制
static/images/setting/shangpin.png


二進制
static/images/writeOff.jpg


二進制
static/recycle/choose.png


二進制
static/recycle/kong.png


二進制
static/recycle/nochoose.png


二進制
static/recycle/pass.png


二進制
static/recycle/success.png


+ 6 - 1
stores/app.js

@@ -42,7 +42,8 @@ export const useAppStore = defineStore("app", {
       shopInfo:{
         name:"水贝001号店铺",
         shopId:"001"
-      }
+      },
+      merchantId:null,
     };
   },
   getters: {
@@ -57,6 +58,7 @@ export const useAppStore = defineStore("app", {
     userPanelInfoGetter: (state) => state.userPanelInfo,
     $wxConfig: (state) => state.wxConfig,
     shopInfoGetter: (state) => state.shopInfo,
+    merchantIdGetter: (state) => state.merchantId,
   },
   actions: {
     SET_REFRESH(val) {
@@ -136,5 +138,8 @@ export const useAppStore = defineStore("app", {
     UPDATE_userPanelInfo(userPanelInfo) {
       this.userPanelInfo = userPanelInfo;
     },
+    UPDATE_MERCHANT_ID(merchantId) {
+      this.merchantId = merchantId;
+    },
   },
 });

+ 131 - 0
uni_modules/sp-editor/changelog.md

@@ -0,0 +1,131 @@
+## 1.5.0(2024-11-01)
+1. 更新弹窗中使用的示例
+## 1.4.9(2024-08-30)
+1. 修复在app端和小程序端插入超链接可能会回显出部分特殊标识的bug
+## 1.4.8(2024-08-16)
+1. 有群友反馈,通过wangEditor添加的含有图片的富文本的宽高是在style内联样式中的,但是uni-editor不能解析img标签的内联宽高,因此我封装一个工具方法convertImgStylesToAttributes,处理一下富文本字符串即可,详见示例一
+## 1.4.7(2024-08-12)
+1. 新增标题、字体、字体大小、字间距段前后距等工具子级悬浮工具栏
+2. 对于字体样式:可在组件的data中的fabTools.fontFamily中自行引入添加你本地的自定义字体(app中对于部分css自带的字体例如微软雅黑等不支持,因为app中不自带这些字体,你可能需要自行引入字体文件)
+## 1.4.6(2024-08-08)
+1. 修复字体工具栏无法隐藏的bug
+## 1.4.5(2024-07-21)
+1. 解决video图标冲突问题
+## 1.4.4(2024-07-20)
+1. 新增视频插入功能
+2. 更新示例一(关于视频插入参考请见示例一)
+## 1.4.3(2024-07-16)
+1. 更新示例工程
+## 1.4.2(2024-07-16)
+1. 更新示例工程
+## 1.4.1(2024-06-14)
+1. 更新示例工程
+## 1.4.0(2024-05-31)
+1. 更新示例工程
+## 1.3.9(2024-05-31)
+1. 修复调色板无法正常选色的问题
+## 1.3.8(2024-05-13)
+1. 更新示例三(微信小程序上使用setContents造成聚焦滚动的处理)
+## 1.3.7(2024-05-10)
+1. 修复添加超链接后,不触发input更新当前最新内容的bug
+## 1.3.6(2024-05-09)
+1. 文档迁移
+## 1.3.5(2024-05-09)
+1. 所有事件携带编辑器id参数,以便循环时能区分处理
+2. 更新示例工程
+## 1.3.4(2024-05-08)
+1. 更新示例工程
+2. 新增editorId参数
+## 1.3.3(2024-03-22)
+1. 修复微信小程序长按无法粘贴的问题
+## 1.3.2(2024-03-14)
+1. 更新了toolbar样式与配置,见文档
+2. 更新示例工程,媒体查询响应式写法
+3. 优化了只读模式效果,开启只读模式后,文章内容的超链接可正常点击并跳转
+## 1.3.1(2024-03-14)
+1. 优化了只读功能,开启只读后自动隐藏工具栏
+2. 更新示例工程
+## 1.3.0(2024-03-07)
+1. 新增addLink的emit事件
+## 1.2.9(2024-02-23)
+1. 更新文档
+## 1.2.8(2024-02-23)
+1. 新增了添加超链接的工具,toolbar中link字段,默认开启
+2. 优化了部分逻辑
+3. 更新文档、更新示例工程
+## 1.2.7(2024-02-23)
+1. 更新文档,更新示例工程
+2. 添加toolbar中图标字体大小可配置项
+## 1.2.6(2024-02-22)
+1. 添加导出工具按钮,可将当前已编辑的html导出至页面解析
+2. 超链接工具按钮正在尝试开发中(貌似目前官方不支持)
+## 1.2.5(2024-02-19)
+1. 更新示例工程(吸顶写法)
+2. 完善调色板功能
+## 1.2.4(2024-02-18)
+1. 修复工具栏颜色按钮底色动态切换问题
+## 1.2.3(2024-02-18)
+1. 更新示例工程
+## 1.2.2(2024-02-18)
+1. 删除log调试打印
+## 1.2.1(2024-02-18)
+1. 修复了颜色图标不会动态切换的问题
+## 1.2.0(2024-02-18)
+1. 修复选择颜色时会将所选文字删除的bug
+## 1.1.9(2024-02-04)
+1. 更新示例工程
+## 1.1.8(2024-02-04)
+1. 文档修改
+## 1.1.7(2024-02-04)
+1. 新增toolbar配置项,可自由配置工具栏工具列表
+2. 移除组件内原templates属性,默认初始化编辑器内容请看文档使用方式示例
+3. 更新文档
+## 1.1.6(2024-01-31)
+1. 更好的兼容vue2了,修复在vue2下高度可能超出的问题
+2. 示例工程兼容vue2
+## 1.1.5(2024-01-30)
+1. 修复工具栏字体按钮无效的问题
+## 1.1.4(2024-01-30)
+1. 解决默认初始化内容时前缀空格或缩进无效的问题
+2. 解决点击工具栏高亮状态后输入内容时便失去高亮的bug
+3. 更新示例工程
+## 1.1.3(2024-01-23)
+1. 重写高度动态计算逻辑,现在对不同屏幕尺寸的适应性更强了
+## 1.1.2(2024-01-17)
+1. 修复分割线会生成多条的问题
+## 1.1.1(2024-01-15)
+1. 更新文档
+## 1.1.0(2024-01-15)
+1. insertText方法在插入内容的时候会移动光标聚焦,导致焦点回滚到视口处
+2. 更新示例工程
+## 1.0.9(2024-01-04)
+1. 更新文档
+## 1.0.8(2024-01-04)
+1. 修复h5端官方cdn请求失败的问题,详见问答贴:https://ask.dcloud.net.cn/article/40900
+## 1.0.7(2024-01-03)
+1.  移除v-bind="$attrs",该写法在微信小程序不支持
+## 1.0.6(2023-12-29)
+1. 更新文档
+## 1.0.5(2023-12-29)
+1. 更新了init方法,可以使用返回的editor实例尽情自定义
+2. 组件在<editor>上添加v-bind="$attrs"属性穿透,可以使用原editor参数,官方文档:https://uniapp.dcloud.net.cn/component/editor.html
+## 1.0.4(2023-12-29)
+1. 优化了切换文字和背景颜色是,可能会导致切换后不生效的问题
+2. 修复在部分设备上的微信小程序中可能会存在颜色版无法正常滑动的问题
+3. 更友好的交互体验:添加图标悬停字样描述、添加格式化文本弹窗确认
+4. 有插入视频的需求,暂时可能无法实现,官方给予的回复是:目前各端的eidtor组件都不能直接插入视频,编辑时可以采用视频封面占位,并在图片中保存视频信息,在预览时再还原为视频。
+## 1.0.3(2023-10-13)
+	1. 更新readme文档
+	2. 更新调整组件示例项目,添加插件代码中部分注释
+## 1.0.2(2023-10-13)
+	1. 更新uni_modules规范,可一键导入组件
+	2. 更新组件示例项目(包括使用uniCloud.uploadFile多选上传图片示例方法)
+## 1.0.1(2023-10-12)
+	1. 修复小程序中自动聚焦滚动到富文本组件区域的bug
+	2. 略微调整了富文本上方toolbar工具栏中按钮的大小尺寸
+## 1.0.0(2023-9-19)
+	1. 新增字体与背景颜色板
+	2. 可自定义预设内容模板
+	3. 解决官方样例在小程序和app部分报错不兼容的问题
+	4. 可配合云存储上传富文本中插入的图片 本质上是基于官方内置富文本editor组件改版封装,所以官方有的功能都有,官方能兼容的也都兼容
+

+ 825 - 0
uni_modules/sp-editor/components/sp-editor/color-picker.vue

@@ -0,0 +1,825 @@
+<template>
+  <view v-if="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
+    <view class="t-mask" :class="{ active: active }" @click.stop="close"></view>
+    <view class="t-box" :class="{ active: active }">
+      <view class="t-header">
+        <view class="t-header-button" @click="close">取消</view>
+        <view class="t-header-button" @click="confirm">确认</view>
+      </view>
+      <view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')' }">
+        <view
+          class="t-background boxs"
+          @touchstart="touchstart($event, 0)"
+          @touchmove="touchmove($event, 0)"
+          @touchend="touchend($event, 0)"
+        >
+          <view class="t-color-mask"></view>
+          <view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
+        </view>
+      </view>
+      <view class="t-control__box">
+        <view class="t-control__color">
+          <view
+            class="t-control__color-content"
+            :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"
+          ></view>
+        </view>
+        <view class="t-control-box__item">
+          <view
+            class="t-controller boxs"
+            @touchstart="touchstart($event, 1)"
+            @touchmove="touchmove($event, 1)"
+            @touchend="touchend($event, 1)"
+          >
+            <view class="t-hue">
+              <view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
+            </view>
+          </view>
+          <view
+            class="t-controller boxs"
+            @touchstart="touchstart($event, 2)"
+            @touchmove="touchmove($event, 2)"
+            @touchend="touchend($event, 2)"
+          >
+            <view class="t-transparency">
+              <view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
+            </view>
+          </view>
+        </view>
+      </view>
+      <view class="t-result__box">
+        <view v-if="mode" class="t-result__item">
+          <view class="t-result__box-input">{{ hex }}</view>
+          <view class="t-result__box-text">HEX</view>
+        </view>
+        <template v-else>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.r }}</view>
+            <view class="t-result__box-text">R</view>
+          </view>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.g }}</view>
+            <view class="t-result__box-text">G</view>
+          </view>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.b }}</view>
+            <view class="t-result__box-text">B</view>
+          </view>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.a }}</view>
+            <view class="t-result__box-text">A</view>
+          </view>
+        </template>
+
+        <view class="t-result__item t-select" @click="select">
+          <view class="t-result__box-input">
+            <view>切换</view>
+            <view>模式</view>
+          </view>
+        </view>
+      </view>
+      <view class="t-alternative">
+        <view class="t-alternative__item" v-for="(item, index) in colorList" :key="index">
+          <view
+            class="t-alternative__item-content"
+            :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
+            @click="selectColor(item)"
+          ></view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    color: {
+      type: Object,
+      default: () => {
+        return {
+          r: 0,
+          g: 0,
+          b: 0,
+          a: 0
+        }
+      }
+    },
+    spareColor: {
+      type: Array,
+      default() {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      show: false,
+      active: false,
+      // rgba 颜色
+      rgba: {
+        r: 0,
+        g: 0,
+        b: 0,
+        a: 1
+      },
+      // hsb 颜色
+      hsb: {
+        h: 0,
+        s: 0,
+        b: 0
+      },
+      site: [
+        {
+          top: 0,
+          left: 0
+        },
+        {
+          left: 0
+        },
+        {
+          left: 0
+        }
+      ],
+      index: 0,
+      bgcolor: {
+        r: 255,
+        g: 0,
+        b: 0,
+        a: 1
+      },
+      hex: '#000000',
+      mode: true,
+      colorList: [
+        {
+          r: 244,
+          g: 67,
+          b: 54,
+          a: 1
+        },
+        {
+          r: 233,
+          g: 30,
+          b: 99,
+          a: 1
+        },
+        {
+          r: 156,
+          g: 39,
+          b: 176,
+          a: 1
+        },
+        {
+          r: 103,
+          g: 58,
+          b: 183,
+          a: 1
+        },
+        {
+          r: 63,
+          g: 81,
+          b: 181,
+          a: 1
+        },
+        {
+          r: 33,
+          g: 150,
+          b: 243,
+          a: 1
+        },
+        {
+          r: 3,
+          g: 169,
+          b: 244,
+          a: 1
+        },
+        {
+          r: 0,
+          g: 188,
+          b: 212,
+          a: 1
+        },
+        {
+          r: 0,
+          g: 150,
+          b: 136,
+          a: 1
+        },
+        {
+          r: 76,
+          g: 175,
+          b: 80,
+          a: 1
+        },
+        {
+          r: 139,
+          g: 195,
+          b: 74,
+          a: 1
+        },
+        {
+          r: 205,
+          g: 220,
+          b: 57,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 235,
+          b: 59,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 193,
+          b: 7,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 152,
+          b: 0,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 87,
+          b: 34,
+          a: 1
+        },
+        {
+          r: 121,
+          g: 85,
+          b: 72,
+          a: 1
+        },
+        {
+          r: 158,
+          g: 158,
+          b: 158,
+          a: 1
+        },
+        {
+          r: 0,
+          g: 0,
+          b: 0,
+          a: 0.5
+        },
+        {
+          r: 0,
+          g: 0,
+          b: 0,
+          a: 0
+        }
+      ]
+    }
+  },
+  created() {
+    this.ready()
+  },
+  methods: {
+    ready() {
+      this.rgba = this.color
+      if (this.spareColor.length !== 0) {
+        this.colorList = this.spareColor
+      }
+    },
+    /**
+     * 初始化
+     */
+    init() {
+      // hsb 颜色
+      this.hsb = this.rgbToHex(this.rgba)
+      // this.setColor();
+      this.setValue(this.rgba)
+    },
+    moveHandle() {},
+    open() {
+      this.show = true
+      this.$nextTick(() => {
+        this.init()
+        setTimeout(() => {
+          this.active = true
+          setTimeout(() => {
+            this.getSelectorQuery()
+          }, 350)
+        }, 50)
+      })
+    },
+    close() {
+      this.active = false
+      this.$nextTick(() => {
+        setTimeout(() => {
+          this.show = false
+        }, 500)
+      })
+    },
+    confirm() {
+      this.close()
+      this.$emit('confirm', {
+        rgba: this.rgba,
+        hex: this.hex
+      })
+    },
+    // 选择模式
+    select() {
+      this.mode = !this.mode
+    },
+    // 常用颜色选择
+    selectColor(item) {
+      this.setColorBySelect(item)
+    },
+    touchstart(e, index) {
+      const { pageX, pageY, clientX, clientY } = e.touches[0]
+      // 部分机型可能没有pageX或clientX,因此此处需要做兼容
+      this.moveX = clientX || pageX
+      this.moveY = clientY || pageY
+      this.setPosition(this.moveX, this.moveY, index)
+    },
+    touchmove(e, index) {
+      const { pageX, pageY, clientX, clientY } = e.touches[0]
+      this.moveX = clientX || pageX
+      this.moveY = clientY || pageY
+      this.setPosition(this.moveX, this.moveY, index)
+    },
+    touchend(e, index) {},
+    /**
+     * 设置位置
+     */
+    setPosition(x, y, index) {
+      this.index = index
+      const { top, left, width, height } = this.position[index]
+      // 设置最大最小值
+
+      this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width))
+      if (index === 0) {
+        this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height))
+        // 设置颜色
+        this.hsb.s = parseInt((100 * this.site[index].left) / width)
+        this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height)
+        this.setColor()
+        this.setValue(this.rgba)
+      } else {
+        this.setControl(index, this.site[index].left)
+      }
+    },
+    /**
+     * 设置 rgb 颜色
+     */
+    setColor() {
+      const rgb = this.HSBToRGB(this.hsb)
+      this.rgba.r = rgb.r
+      this.rgba.g = rgb.g
+      this.rgba.b = rgb.b
+    },
+    /**
+     * 设置二进制颜色
+     * @param {Object} rgb
+     */
+    setValue(rgb) {
+      this.hex = '#' + this.rgbToHex(rgb)
+    },
+    setControl(index, x) {
+      const { top, left, width, height } = this.position[index]
+
+      if (index === 1) {
+        this.hsb.h = parseInt((360 * x) / width)
+        this.bgcolor = this.HSBToRGB({
+          h: this.hsb.h,
+          s: 100,
+          b: 100
+        })
+        this.setColor()
+      } else {
+        this.rgba.a = (x / width).toFixed(1)
+      }
+      this.setValue(this.rgba)
+    },
+    /**
+     * rgb 转 二进制 hex
+     * @param {Object} rgb
+     */
+    rgbToHex(rgb) {
+      let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)]
+      hex.map(function (str, i) {
+        if (str.length == 1) {
+          hex[i] = '0' + str
+        }
+      })
+      return hex.join('')
+    },
+    setColorBySelect(getrgb) {
+      const { r, g, b, a } = getrgb
+      let rgb = {}
+      rgb = {
+        r: r ? parseInt(r) : 0,
+        g: g ? parseInt(g) : 0,
+        b: b ? parseInt(b) : 0,
+        a: a ? a : 0
+      }
+      this.rgba = rgb
+      this.hsb = this.rgbToHsb(rgb)
+      this.changeViewByHsb()
+    },
+    changeViewByHsb() {
+      const [a, b, c] = this.position
+      this.site[0].left = parseInt((this.hsb.s * a.width) / 100)
+      this.site[0].top = parseInt(((100 - this.hsb.b) * a.height) / 100)
+      this.setColor(this.hsb.h)
+      this.setValue(this.rgba)
+      this.bgcolor = this.HSBToRGB({
+        h: this.hsb.h,
+        s: 100,
+        b: 100
+      })
+
+      this.site[1].left = (this.hsb.h / 360) * b.width
+      this.site[2].left = this.rgba.a * c.width
+    },
+    /**
+     * hsb 转 rgb
+     * @param {Object} 颜色模式  H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
+     */
+    HSBToRGB(hsb) {
+      let rgb = {}
+      let h = Math.round(hsb.h)
+      let s = Math.round((hsb.s * 255) / 100)
+      let v = Math.round((hsb.b * 255) / 100)
+      if (s == 0) {
+        rgb.r = rgb.g = rgb.b = v
+      } else {
+        let t1 = v
+        let t2 = ((255 - s) * v) / 255
+        let t3 = ((t1 - t2) * (h % 60)) / 60
+        if (h == 360) h = 0
+        if (h < 60) {
+          rgb.r = t1
+          rgb.b = t2
+          rgb.g = t2 + t3
+        } else if (h < 120) {
+          rgb.g = t1
+          rgb.b = t2
+          rgb.r = t1 - t3
+        } else if (h < 180) {
+          rgb.g = t1
+          rgb.r = t2
+          rgb.b = t2 + t3
+        } else if (h < 240) {
+          rgb.b = t1
+          rgb.r = t2
+          rgb.g = t1 - t3
+        } else if (h < 300) {
+          rgb.b = t1
+          rgb.g = t2
+          rgb.r = t2 + t3
+        } else if (h < 360) {
+          rgb.r = t1
+          rgb.g = t2
+          rgb.b = t1 - t3
+        } else {
+          rgb.r = 0
+          rgb.g = 0
+          rgb.b = 0
+        }
+      }
+      return {
+        r: Math.round(rgb.r),
+        g: Math.round(rgb.g),
+        b: Math.round(rgb.b)
+      }
+    },
+    rgbToHsb(rgb) {
+      let hsb = {
+        h: 0,
+        s: 0,
+        b: 0
+      }
+      let min = Math.min(rgb.r, rgb.g, rgb.b)
+      let max = Math.max(rgb.r, rgb.g, rgb.b)
+      let delta = max - min
+      hsb.b = max
+      hsb.s = max != 0 ? (255 * delta) / max : 0
+      if (hsb.s != 0) {
+        if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta
+        else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta
+        else hsb.h = 4 + (rgb.r - rgb.g) / delta
+      } else hsb.h = -1
+      hsb.h *= 60
+      if (hsb.h < 0) hsb.h = 0
+      hsb.s *= 100 / 255
+      hsb.b *= 100 / 255
+      return hsb
+    },
+    getSelectorQuery() {
+      const views = uni.createSelectorQuery().in(this)
+      views
+        .selectAll('.boxs')
+        .boundingClientRect((data) => {
+          if (!data || data.length === 0) {
+            setTimeout(() => this.getSelectorQuery(), 20)
+            return
+          }
+          this.position = data
+          // this.site[0].top = data[0].height;
+          // this.site[0].left = 0;
+          // this.site[1].left = data[1].width;
+          // this.site[2].left = data[2].width;
+          this.setColorBySelect(this.rgba)
+        })
+        .exec()
+    },
+    hex2Rgb(hexColor, alpha = 1) {
+      const color = hexColor.slice(1)
+      const r = parseInt(color.slice(0, 2), 16)
+      const g = parseInt(color.slice(2, 4), 16)
+      const b = parseInt(color.slice(4, 6), 16)
+      return {
+        r: r,
+        g: g,
+        b: b,
+        a: alpha
+      }
+    }
+  },
+  watch: {
+    spareColor(newVal) {
+      this.colorList = newVal
+    },
+    color(newVal) {
+      this.ready()
+    }
+  }
+}
+</script>
+
+<style>
+.t-wrapper {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  box-sizing: border-box;
+  z-index: 9999;
+}
+
+.t-box {
+  width: 100%;
+  position: absolute;
+  bottom: 0;
+  padding: 30upx 0;
+  padding-top: 0;
+  background: #fff;
+  transition: all 0.3s;
+  transform: translateY(100%);
+}
+
+.t-box.active {
+  transform: translateY(0%);
+}
+
+.t-header {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  height: 100upx;
+  border-bottom: 1px #eee solid;
+  box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
+  background: #fff;
+}
+
+.t-header-button {
+  display: flex;
+  align-items: center;
+  width: 150upx;
+  height: 100upx;
+  font-size: 30upx;
+  color: #666;
+  padding-left: 20upx;
+}
+
+.t-header-button:last-child {
+  justify-content: flex-end;
+  padding-right: 20upx;
+}
+
+.t-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.6);
+  z-index: -1;
+  transition: all 0.3s;
+  opacity: 0;
+}
+
+.t-mask.active {
+  opacity: 1;
+}
+
+.t-color__box {
+  position: relative;
+  height: 400upx;
+  background: rgb(255, 0, 0);
+  overflow: hidden;
+  box-sizing: border-box;
+  margin: 0 20upx;
+  margin-top: 20upx;
+  box-sizing: border-box;
+}
+
+.t-background {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
+}
+
+.t-color-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  width: 100%;
+  height: 400upx;
+  background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
+}
+
+.t-pointer {
+  position: absolute;
+  bottom: -8px;
+  left: -8px;
+  z-index: 2;
+  width: 15px;
+  height: 15px;
+  border: 1px #fff solid;
+  border-radius: 50%;
+}
+
+.t-show-color {
+  width: 100upx;
+  height: 50upx;
+}
+
+.t-control__box {
+  margin-top: 50upx;
+  width: 100%;
+  display: flex;
+  padding-left: 20upx;
+  box-sizing: border-box;
+}
+
+.t-control__color {
+  flex-shrink: 0;
+  width: 100upx;
+  height: 100upx;
+  border-radius: 50%;
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+  background-size: 36upx 36upx;
+  background-position: 0 0, 18upx 18upx;
+  border: 1px #eee solid;
+  overflow: hidden;
+}
+
+.t-control__color-content {
+  width: 100%;
+  height: 100%;
+}
+
+.t-control-box__item {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 100%;
+  padding: 0 30upx;
+}
+
+.t-controller {
+  position: relative;
+  width: 100%;
+  height: 16px;
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+  background-size: 32upx 32upx;
+  background-position: 0 0, 16upx 16upx;
+}
+
+.t-hue {
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
+}
+
+.t-transparency {
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
+}
+
+.t-circle {
+  position: absolute;
+  /* right: -10px; */
+  top: -2px;
+  width: 20px;
+  height: 20px;
+  box-sizing: border-box;
+  border-radius: 50%;
+  background: #fff;
+  box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
+}
+
+.t-result__box {
+  margin-top: 20upx;
+  padding: 10upx;
+  width: 100%;
+  display: flex;
+  box-sizing: border-box;
+}
+
+.t-result__item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 10upx;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.t-result__box-input {
+  padding: 10upx 0;
+  width: 100%;
+  font-size: 28upx;
+  box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
+  color: #999;
+  text-align: center;
+  background: #fff;
+}
+
+.t-result__box-text {
+  margin-top: 10upx;
+  font-size: 28upx;
+  line-height: 2;
+}
+
+.t-select {
+  flex-shrink: 0;
+  width: 150upx;
+  padding: 0 30upx;
+}
+
+.t-select .t-result__box-input {
+  border-radius: 10upx;
+  border: none;
+  color: #999;
+  box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
+  background: #fff;
+}
+
+.t-select .t-result__box-input:active {
+  box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
+}
+
+.t-alternative {
+  display: flex;
+  flex-wrap: wrap;
+  /* justify-content: space-between; */
+  width: 100%;
+  padding-right: 10upx;
+  box-sizing: border-box;
+}
+
+.t-alternative__item {
+  margin-left: 12upx;
+  margin-top: 10upx;
+  width: 50upx;
+  height: 50upx;
+  border-radius: 10upx;
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+  background-size: 36upx 36upx;
+  background-position: 0 0, 18upx 18upx;
+  border: 1px #eee solid;
+  overflow: hidden;
+}
+
+.t-alternative__item-content {
+  width: 50upx;
+  height: 50upx;
+  background: rgba(255, 0, 0, 0.5);
+}
+
+.t-alternative__item:active {
+  transition: all 0.3s;
+  transform: scale(1.1);
+}
+</style>

+ 140 - 0
uni_modules/sp-editor/components/sp-editor/fab-tool.vue

@@ -0,0 +1,140 @@
+<template>
+  <view class="fab-tool">
+    <view id="toolfab">
+      <slot></slot>
+    </view>
+    <view class="fab-tool-content" :style="placementStyle" id="placementfab">
+      <slot name="content" v-if="visible"></slot>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    placement: {
+      type: String,
+      default: 'auto' // 'auto' | 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end'
+    }
+  },
+  data() {
+    return {
+      placementHeight: '0',
+      placementType: ''
+    }
+  },
+  watch: {
+    visible(newVal) {
+      if (newVal) {
+        const { screenWidth } = uni.getSystemInfoSync()
+
+        this.$nextTick(() => {
+          let placementWidth = 0
+          uni
+            .createSelectorQuery()
+            .in(this)
+            .select('#placementfab')
+            .boundingClientRect((res) => {
+              this.placementHeight = -res.height + 'px'
+              placementWidth = res.width
+            })
+            .exec()
+          // 开启自动模式后
+          if (this.placement == 'auto') {
+            uni
+              .createSelectorQuery()
+              .in(this)
+              .select('#toolfab')
+              .boundingClientRect((res) => {
+                let leftRemain = res.left
+                let rightRemain = screenWidth - leftRemain
+                if (rightRemain > placementWidth) {
+                  this.placementType = 'bottom-start'
+                } else if (leftRemain > placementWidth) {
+                  this.placementType = 'bottom-end'
+                } else {
+                  this.placementType = 'bottom-center'
+                }
+              })
+              .exec()
+          }
+        })
+      }
+    }
+  },
+  mounted() {
+    this.placementType = this.placement
+  },
+  computed: {
+    placementStyle() {
+      let position = {}
+      switch (this.placementType) {
+        case 'top-start':
+          position = {
+            top: this.placementHeight,
+            left: 0
+          }
+          break
+        case 'top-center':
+          position = {
+            top: this.placementHeight,
+            left: '50%',
+            transform: 'translateX(-50%)'
+          }
+          break
+        case 'top-end':
+          position = {
+            top: this.placementHeight,
+            right: 0
+          }
+          break
+        case 'bottom-start':
+          position = {
+            bottom: this.placementHeight,
+            left: 0
+          }
+          break
+        case 'bottom-center':
+          position = {
+            bottom: this.placementHeight,
+            left: '50%',
+            transform: 'translateX(-50%)'
+          }
+          break
+        case 'bottom-end':
+          position = {
+            bottom: this.placementHeight,
+            right: 0
+          }
+          break
+        default:
+          break
+      }
+      return position
+    }
+  },
+  methods: {
+    //
+  }
+}
+</script>
+
+<style lang="scss">
+.fab-tool {
+  position: relative;
+
+  .fab-tool-content {
+    position: absolute;
+    z-index: 999;
+
+    background-color: #ffffff;
+    box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.05), 2px 2px 4px rgba(0, 0, 0, 0.05);
+    border-radius: 12rpx;
+    box-sizing: border-box;
+  }
+}
+</style>

+ 152 - 0
uni_modules/sp-editor/components/sp-editor/link-edit.vue

@@ -0,0 +1,152 @@
+<template>
+  <view class="link-edit-container" v-if="showPopup">
+    <view class="link-edit">
+      <view class="title">添加链接</view>
+      <view class="edit">
+        <view class="description">
+          链接描述:
+          <input v-model="descVal" type="text" class="input" placeholder="请输入链接描述" />
+        </view>
+        <view class="address">
+          链接地址:
+          <input v-model="addrVal" type="text" class="input" placeholder="请输入链接地址" />
+        </view>
+      </view>
+      <view class="control">
+        <view class="cancel" @click="close">取消</view>
+        <view class="confirm" @click="onConfirm">确认</view>
+      </view>
+    </view>
+    <view class="mask"></view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      showPopup: false,
+      descVal: '',
+      addrVal: ''
+    }
+  },
+  methods: {
+    open() {
+      this.showPopup = true
+      this.$emit('open')
+    },
+    close() {
+      this.showPopup = false
+      this.descVal = ''
+      this.addrVal = ''
+      this.$emit('close')
+    },
+    onConfirm() {
+      if (!this.descVal) {
+        uni.showToast({
+          title: '请输入链接描述',
+          icon: 'none'
+        })
+        return
+      }
+      if (!this.addrVal) {
+        uni.showToast({
+          title: '请输入链接地址',
+          icon: 'none'
+        })
+        return
+      }
+      this.$emit('confirm', {
+        text: this.descVal,
+        href: this.addrVal
+      })
+      this.close()
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.link-edit-container {
+  .link-edit {
+    width: 80%;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background-color: #ffffff;
+    box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.05), 2px 2px 4px rgba(0, 0, 0, 0.05);
+    border-radius: 12rpx;
+    box-sizing: border-box;
+    z-index: 999;
+    font-size: 14px;
+
+    .title {
+      height: 80rpx;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+
+    .edit {
+      padding: 24rpx;
+      border-top: 1px solid #eeeeee;
+      border-bottom: 1px solid #eeeeee;
+      box-sizing: border-box;
+
+      .input {
+        flex: 1;
+        padding: 4px;
+        font-size: 14px;
+        border: 1px solid #eeeeee;
+        border-radius: 8rpx;
+
+        .uni-input-placeholder {
+          color: #dddddd;
+        }
+      }
+
+      .description {
+        display: flex;
+        align-items: center;
+      }
+      .address {
+        display: flex;
+        align-items: center;
+        margin-top: 24rpx;
+      }
+    }
+
+    .control {
+      height: 80rpx;
+      display: flex;
+      cursor: pointer;
+
+      .cancel {
+        flex: 1;
+        color: #dd524d;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+      .confirm {
+        border-left: 1px solid #eeeeee;
+        flex: 1;
+        color: #007aff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+  }
+  .mask {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.05);
+    z-index: 998;
+  }
+}
+</style>

+ 852 - 0
uni_modules/sp-editor/components/sp-editor/sp-editor.vue

@@ -0,0 +1,852 @@
+<template>
+	<view class="sp-editor" :style="{ '--icon-size': iconSize, '--icon-columns': iconColumns }">
+		<view class="sp-editor-toolbar" v-if="!readOnly" @tap="format">
+			<!-- 标题栏 -->
+			<fab-tool v-if="toolbarList.includes('header')" :visible="curFab == 'header'">
+				<view
+					:class="formats.header ? 'ql-active' : ''"
+					class="iconfont icon-header"
+					title="标题"
+					data-name="header"
+					@click.stop="fabTap('header')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'header')">
+						<view v-for="item in fabTools.header" :key="item.value">
+							<view
+								v-if="toolbarList.includes(item.name)"
+								class="fab-sub"
+								:class="[formats.header === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]"
+								:title="item.title"
+								data-name="header"
+								:data-value="item.value"
+							></view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<view
+				v-if="toolbarList.includes('bold')"
+				:class="formats.bold ? 'ql-active' : ''"
+				class="iconfont icon-zitijiacu"
+				title="加粗"
+				data-name="bold"
+			></view>
+			<view
+				v-if="toolbarList.includes('italic')"
+				:class="formats.italic ? 'ql-active' : ''"
+				class="iconfont icon-zitixieti"
+				title="斜体"
+				data-name="italic"
+			></view>
+			<view
+				v-if="toolbarList.includes('underline')"
+				:class="formats.underline ? 'ql-active' : ''"
+				class="iconfont icon-zitixiahuaxian"
+				title="下划线"
+				data-name="underline"
+			></view>
+			<view
+				v-if="toolbarList.includes('strike')"
+				:class="formats.strike ? 'ql-active' : ''"
+				class="iconfont icon-zitishanchuxian"
+				title="删除线"
+				data-name="strike"
+			></view>
+			<!-- 对齐方式 -->
+			<fab-tool v-if="toolbarList.includes('align')" :visible="curFab == 'align'">
+				<view
+					:class="formats.align ? 'ql-active' : ''"
+					class="iconfont icon-zuoyouduiqi"
+					title="对齐方式"
+					data-name="align"
+					@click.stop="fabTap('align')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'align')">
+						<view v-for="item in fabTools.align" :key="item.value">
+							<view
+								v-if="toolbarList.includes(item.name)"
+								class="fab-sub"
+								:class="[formats.align === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]"
+								:title="item.title"
+								data-name="align"
+								:data-value="item.value"
+							></view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<!-- 行间距 -->
+			<fab-tool v-if="toolbarList.includes('lineHeight')" :visible="curFab == 'lineHeight'">
+				<view
+					:class="formats.lineHeight ? 'ql-active' : ''"
+					class="iconfont icon-line-height"
+					title="行间距"
+					data-name="lineHeight"
+					@click.stop="fabTap('lineHeight')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'lineHeight')">
+						<view v-for="item in fabTools.lineHeight" :key="item.value">
+							<view
+								class="fab-sub"
+								:class="[formats.lineHeight === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]"
+								:title="item.title"
+								data-name="lineHeight"
+								:data-value="item.value"
+							>
+								{{ item.name }}
+							</view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<!-- 字间距 -->
+			<fab-tool v-if="toolbarList.includes('letterSpacing')" :visible="curFab == 'letterSpacing'">
+				<view
+					:class="formats.letterSpacing ? 'ql-active' : ''"
+					class="iconfont icon-Character-Spacing"
+					title="字间距"
+					data-name="letterSpacing"
+					@click.stop="fabTap('letterSpacing')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'letterSpacing')">
+						<view v-for="item in fabTools.space" :key="item.value">
+							<view
+								class="fab-sub"
+								:class="[
+									formats.letterSpacing === item.value ? 'ql-active' : '',
+									item.icon ? 'iconfont' : '',
+									item.icon
+								]"
+								:title="item.title"
+								data-name="letterSpacing"
+								:data-value="item.value"
+							>
+								{{ item.name }}
+							</view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<!-- 段前距 -->
+			<fab-tool v-if="toolbarList.includes('marginTop')" :visible="curFab == 'marginTop'">
+				<view
+					:class="formats.marginTop ? 'ql-active' : ''"
+					class="iconfont icon-722bianjiqi_duanqianju"
+					title="段前距"
+					data-name="marginTop"
+					@click.stop="fabTap('marginTop')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'marginTop')">
+						<view v-for="item in fabTools.space" :key="item.value">
+							<view
+								class="fab-sub"
+								:class="[formats.marginTop === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]"
+								:title="item.title"
+								data-name="marginTop"
+								:data-value="item.value"
+							>
+								{{ item.name }}
+							</view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<!-- 段后距 -->
+			<fab-tool v-if="toolbarList.includes('marginBottom')" :visible="curFab == 'marginBottom'">
+				<view
+					:class="formats.marginBottom ? 'ql-active' : ''"
+					class="iconfont icon-723bianjiqi_duanhouju"
+					title="段后距"
+					data-name="marginBottom"
+					@click.stop="fabTap('marginBottom')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'marginBottom')">
+						<view v-for="item in fabTools.space" :key="item.value">
+							<view
+								class="fab-sub"
+								:class="[
+									formats.marginBottom === item.value ? 'ql-active' : '',
+									item.icon ? 'iconfont' : '',
+									item.icon
+								]"
+								:title="item.title"
+								data-name="marginBottom"
+								:data-value="item.value"
+							>
+								{{ item.name }}
+							</view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<!-- 字体栏 -->
+			<fab-tool v-if="toolbarList.includes('fontFamily')" :visible="curFab == 'fontFamily'">
+				<view
+					:class="formats.fontFamily ? 'ql-active' : ''"
+					class="iconfont icon-font"
+					title="字体"
+					data-name="fontFamily"
+					@click.stop="fabTap('fontFamily')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'fontFamily')">
+						<view v-for="item in fabTools.fontFamily" :key="item.value">
+							<view
+								class="fab-sub"
+								:class="[formats.fontFamily === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]"
+								:title="item.title"
+								data-name="fontFamily"
+								:data-value="item.value"
+							>
+								{{ item.name }}
+							</view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<!-- 字体大小栏 -->
+			<fab-tool v-if="toolbarList.includes('fontSize')" :visible="curFab == 'fontSize'">
+				<view
+					:class="formats.fontSize ? 'ql-active' : ''"
+					class="iconfont icon-fontsize"
+					title="字号"
+					data-name="fontSize"
+					@click.stop="fabTap('fontSize')"
+				></view>
+				<template #content>
+					<view class="fab-tools" @click.stop="fabTapSub($event, 'fontSize')">
+						<view v-for="item in fabTools.fontSize" :key="item.value">
+							<view
+								class="fab-sub"
+								:class="[formats.fontSize === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]"
+								:title="item.title"
+								data-name="fontSize"
+								:data-value="item.value"
+							>
+								{{ item.name }}
+							</view>
+						</view>
+					</view>
+				</template>
+			</fab-tool>
+			<view
+				v-if="toolbarList.includes('color')"
+				:style="{ color: formats.color ? textColor : 'initial' }"
+				class="iconfont icon-text_color"
+				title="文字颜色"
+				data-name="color"
+				:data-value="textColor"
+			></view>
+			<view
+				v-if="toolbarList.includes('backgroundColor')"
+				:style="{ color: formats.backgroundColor ? backgroundColor : 'initial' }"
+				class="iconfont icon-fontbgcolor"
+				title="背景颜色"
+				data-name="backgroundColor"
+				:data-value="backgroundColor"
+			></view>
+			<view v-if="toolbarList.includes('date')" class="iconfont icon-date" title="日期" @tap="insertDate"></view>
+			<view
+				v-if="toolbarList.includes('listCheck')"
+				class="iconfont icon--checklist"
+				title="待办"
+				data-name="list"
+				data-value="check"
+			></view>
+			<view
+				v-if="toolbarList.includes('listOrdered')"
+				:class="formats.list === 'ordered' ? 'ql-active' : ''"
+				class="iconfont icon-youxupailie"
+				title="有序列表"
+				data-name="list"
+				data-value="ordered"
+			></view>
+			<view
+				v-if="toolbarList.includes('listBullet')"
+				:class="formats.list === 'bullet' ? 'ql-active' : ''"
+				class="iconfont icon-wuxupailie"
+				title="无序列表"
+				data-name="list"
+				data-value="bullet"
+			></view>
+			<view
+				v-if="toolbarList.includes('divider')"
+				class="iconfont icon-fengexian"
+				title="分割线"
+				@click="insertDivider"
+			></view>
+			<view
+				v-if="toolbarList.includes('indentDec')"
+				class="iconfont icon-outdent"
+				title="减少缩进"
+				data-name="indent"
+				data-value="-1"
+			></view>
+			<view
+				v-if="toolbarList.includes('indentInc')"
+				class="iconfont icon-indent"
+				title="增加缩进"
+				data-name="indent"
+				data-value="+1"
+			></view>
+			<view
+				v-if="toolbarList.includes('scriptSub')"
+				:class="formats.script === 'sub' ? 'ql-active' : ''"
+				class="iconfont icon-zitixiabiao"
+				title="下标"
+				data-name="script"
+				data-value="sub"
+			></view>
+			<view
+				v-if="toolbarList.includes('scriptSuper')"
+				:class="formats.script === 'super' ? 'ql-active' : ''"
+				class="iconfont icon-zitishangbiao"
+				title="上标"
+				data-name="script"
+				data-value="super"
+			></view>
+			<view
+				v-if="toolbarList.includes('direction')"
+				:class="formats.direction === 'rtl' ? 'ql-active' : ''"
+				class="iconfont icon-direction-rtl"
+				title="文本方向"
+				data-name="direction"
+				data-value="rtl"
+			></view>
+			<view
+				v-if="toolbarList.includes('image')"
+				class="iconfont icon-charutupian"
+				title="图片"
+				@tap="insertImage"
+			></view>
+			<view v-if="toolbarList.includes('video')" class="iconfont icon-video" title="视频" @tap="insertVideo"></view>
+			<view
+				v-if="toolbarList.includes('link')"
+				class="iconfont icon-charulianjie"
+				title="超链接"
+				@tap="insertLink"
+			></view>
+			<view v-if="toolbarList.includes('undo')" class="iconfont icon-undo" title="撤销" @tap="undo"></view>
+			<view v-if="toolbarList.includes('redo')" class="iconfont icon-redo" title="重做" @tap="redo"></view>
+			<view
+				v-if="toolbarList.includes('removeFormat')"
+				class="iconfont icon-clearedformat"
+				title="清除格式"
+				@tap="removeFormat"
+			></view>
+			<view v-if="toolbarList.includes('clear')" class="iconfont icon-shanchu" title="清空" @tap="clear"></view>
+			<view v-if="toolbarList.includes('export')" class="iconfont icon-baocun" title="导出" @tap="exportHtml"></view>
+		</view>
+
+		<!-- 自定义功能组件 -->
+		<!-- 调色板 -->
+		<color-picker
+			v-if="toolbarList.includes('color') || toolbarList.includes('backgroundColor')"
+			ref="colorPickerRef"
+			:color="defaultColor"
+			@confirm="confirmColor"
+		></color-picker>
+		<!-- 添加链接的操作弹窗 -->
+		<link-edit v-if="toolbarList.includes('link') && !readOnly" ref="linkEditRef" @confirm="confirmLink"></link-edit>
+		<view class="sp-editor-wrapper" @longpress="eLongpress">
+			<editor
+				:id="editorId"
+				class="ql-editor editor-container"
+				:class="{ 'ql-image-overlay-none': readOnly }"
+				show-img-size
+				show-img-toolbar
+				show-img-resize
+				:placeholder="placeholder"
+				:read-only="readOnly"
+				@statuschange="onStatusChange"
+				@ready="onEditorReady"
+				@input="onEditorInput"
+			></editor>
+		</view>
+	</view>
+</template>
+
+<script>
+import ColorPicker from './color-picker.vue'
+import LinkEdit from './link-edit.vue'
+import FabTool from './fab-tool.vue'
+import { addLink, linkFlag } from '../../utils'
+
+export default {
+	components: {
+		ColorPicker,
+		LinkEdit,
+		FabTool
+	},
+	props: {
+		// 编辑器id可传入,以便循环组件使用,防止id重复
+		editorId: {
+			type: String,
+			default: 'editor'
+		},
+		placeholder: {
+			type: String,
+			default: '写点什么吧 ~'
+		},
+		// 是否只读
+		readOnly: {
+			type: Boolean,
+			default: false
+		},
+		// 最大字数限制,-1不限
+		maxlength: {
+			type: Number,
+			default: -1
+		},
+		// 工具栏配置
+		toolbarConfig: {
+			type: Object,
+			default: () => {
+				return {
+					keys: [], // 要显示的工具,优先级最大
+					excludeKeys: [], // 除这些指定的工具外,其他都显示
+					iconSize: '18px', // 工具栏字体大小
+					iconColumns: 10 // 工具栏列数
+				}
+			}
+		}
+	},
+	watch: {
+		toolbarConfig: {
+			deep: true,
+			immediate: true,
+			handler(newToolbar) {
+				/**
+				 * 若工具栏配置中keys存在,则以keys为准
+				 * 否则以excludeKeys向toolbarAllList中排查
+				 * 若keys与excludeKeys皆为空,则以toolbarAllList为准
+				 */
+				if (newToolbar.keys?.length > 0) {
+					this.toolbarList = newToolbar.keys
+				} else {
+					this.toolbarList =
+						newToolbar.excludeKeys?.length > 0
+							? this.toolbarAllList.filter((item) => !newToolbar.excludeKeys.includes(item))
+							: this.toolbarAllList
+				}
+				this.iconSize = newToolbar.iconSize || '18px'
+				this.iconColumns = newToolbar.iconColumns || 10
+			}
+		}
+	},
+	data() {
+		return {
+			formats: {},
+			curFab: '', // 当前悬浮工具栏
+			fabXY: {},
+			textColor: '',
+			backgroundColor: '',
+			curColor: '',
+			defaultColor: { r: 0, g: 0, b: 0, a: 1 }, // 调色板默认颜色
+			iconSize: '20px', // 工具栏图标字体大小
+			iconColumns: 10, // 工具栏列数
+			toolbarList: [],
+			toolbarAllList: [
+				'header', // 标题
+				'H1', // 一级标题
+				'H2', // 二级标题
+				'H3', // 三级标题
+				'H4', // 四级标题
+				'H5', // 五级标题
+				'H6', // 六级标题
+				'bold', // 加粗
+				'italic', // 斜体
+				'underline', // 下划线
+				'strike', // 删除线
+				'align', // 对齐方式
+				'alignLeft', // 左对齐
+				'alignCenter', // 居中对齐
+				'alignRight', // 右对齐
+				'alignJustify', // 两端对齐
+				'lineHeight', // 行间距
+				'letterSpacing', // 字间距
+				'marginTop', // 段前距
+				'marginBottom', // 段后距
+				'fontFamily', // 字体
+				'fontSize', // 字号
+				'color', // 文字颜色
+				'backgroundColor', // 背景颜色
+				'date', // 日期
+				'listCheck', // 待办
+				'listOrdered', // 有序列表
+				'listBullet', // 无序列表
+				'indentInc', // 增加缩进
+				'indentDec', // 减少缩进
+				'divider', // 分割线
+				'scriptSub', // 下标
+				'scriptSuper', // 上标
+				'direction', // 文本方向
+				'image', // 图片
+				'video', // 视频
+				'link', // 超链接
+				'undo', // 撤销
+				'redo', // 重做
+				'removeFormat', // 清除格式
+				'clear', // 清空
+				'export' // 导出
+			],
+			fabTools: {
+				header: [
+					{ title: '一级标题', name: 'H1', value: 1, icon: 'icon-format-header-1' },
+					{ title: '二级标题', name: 'H2', value: 2, icon: 'icon-format-header-2' },
+					{ title: '三级标题', name: 'H3', value: 3, icon: 'icon-format-header-3' },
+					{ title: '四级标题', name: 'H4', value: 4, icon: 'icon-format-header-4' },
+					{ title: '五级标题', name: 'H5', value: 5, icon: 'icon-format-header-5' },
+					{ title: '六级标题', name: 'H6', value: 6, icon: 'icon-format-header-6' }
+				],
+				fontFamily: [
+					{ title: '宋体', name: '宋', value: '宋体', icon: '' },
+					{ title: '黑体', name: '黑', value: '黑体', icon: '' },
+					{ title: '楷体', name: '楷', value: '楷体', icon: '' },
+					{ title: '仿宋', name: '仿', value: '仿宋', icon: '' },
+					{ title: '华文隶书', name: '隶', value: 'STLiti', icon: '' },
+					{ title: '华文行楷', name: '行', value: 'STXingkai', icon: '' },
+					{ title: '幼圆', name: '圆', value: 'YouYuan', icon: '' }
+				],
+				fontSize: [
+					{ title: '12', name: '12', value: '12px', icon: '' },
+					{ title: '14', name: '14', value: '14px', icon: '' },
+					{ title: '16', name: '16', value: '16px', icon: '' },
+					{ title: '18', name: '18', value: '18px', icon: '' },
+					{ title: '20', name: '20', value: '20px', icon: '' },
+					{ title: '22', name: '22', value: '22px', icon: '' },
+					{ title: '24', name: '24', value: '24px', icon: '' }
+				],
+				align: [
+					{ title: '左对齐', name: 'alignLeft', value: 'left', icon: 'icon-zuoduiqi' },
+					{ title: '居中对齐', name: 'alignCenter', value: 'center', icon: 'icon-juzhongduiqi' },
+					{ title: '右对齐', name: 'alignRight', value: 'right', icon: 'icon-youduiqi' },
+					{ title: '两端对齐', name: 'alignJustify', value: 'justify', icon: 'icon-zuoyouduiqi' }
+				],
+				lineHeight: [
+					{ title: '1倍', name: '1', value: '1', icon: '' },
+					{ title: '1.5倍', name: '1.5', value: '1.5', icon: '' },
+					{ title: '2倍', name: '2', value: '2', icon: '' },
+					{ title: '2.5倍', name: '2.5', value: '2.5', icon: '' },
+					{ title: '3倍', name: '3', value: '3', icon: '' }
+				],
+				// 字间距/段前距/段后距
+				space: [
+					{ title: '0.5倍', name: '0.5', value: '0.5em', icon: '' },
+					{ title: '1倍', name: '1', value: '1em', icon: '' },
+					{ title: '1.5倍', name: '1.5', value: '1.5em', icon: '' },
+					{ title: '2倍', name: '2', value: '2em', icon: '' },
+					{ title: '2.5倍', name: '2.5', value: '2.5em', icon: '' },
+					{ title: '3倍', name: '3', value: '3em', icon: '' }
+				]
+			}
+		}
+	},
+	methods: {
+		onEditorReady() {
+			uni
+				.createSelectorQuery()
+				.in(this)
+				.select('#' + this.editorId)
+				.context((res) => {
+					this.editorCtx = res.context
+					this.$emit('init', this.editorCtx, this.editorId)
+				})
+				.exec()
+		},
+		undo() {
+			this.editorCtx.undo()
+		},
+		redo() {
+			this.editorCtx.redo()
+		},
+		format(e) {
+			let { name, value } = e.target.dataset
+			if (!name) return
+			switch (name) {
+				case 'color':
+				case 'backgroundColor':
+					this.curColor = name
+					this.showPicker()
+					break
+				default:
+					this.editorCtx.format(name, value)
+					break
+			}
+		},
+		// 悬浮工具点击
+		fabTap(fabType) {
+			if (this.curFab != fabType) {
+				this.curFab = fabType
+			} else {
+				this.curFab = ''
+			}
+		},
+		// 悬浮工具子集点击
+		fabTapSub(e, fabType) {
+			this.format(e)
+			this.fabTap(fabType)
+		},
+		showPicker() {
+			switch (this.curColor) {
+				case 'color':
+					this.defaultColor = this.textColor
+						? this.$refs.colorPickerRef.hex2Rgb(this.textColor)
+						: { r: 0, g: 0, b: 0, a: 1 }
+					break
+				case 'backgroundColor':
+					this.defaultColor = this.backgroundColor
+						? this.$refs.colorPickerRef.hex2Rgb(this.backgroundColor)
+						: { r: 0, g: 0, b: 0, a: 0 }
+					break
+			}
+			this.$refs.colorPickerRef.open()
+		},
+		confirmColor(e) {
+			switch (this.curColor) {
+				case 'color':
+					this.textColor = e.hex
+					this.editorCtx.format('color', this.textColor)
+					break
+				case 'backgroundColor':
+					this.backgroundColor = e.hex
+					this.editorCtx.format('backgroundColor', this.backgroundColor)
+					break
+			}
+		},
+		onStatusChange(e) {
+			if (e.detail.color) {
+				this.textColor = e.detail.color
+			}
+			if (e.detail.backgroundColor) {
+				this.backgroundColor = e.detail.backgroundColor
+			}
+			this.formats = e.detail
+		},
+		insertDivider() {
+			this.editorCtx.insertDivider()
+		},
+		clear() {
+			uni.showModal({
+				title: '清空编辑器',
+				content: '确定清空编辑器吗?',
+				success: ({ confirm }) => {
+					if (confirm) {
+						this.editorCtx.clear()
+					}
+				}
+			})
+		},
+		removeFormat() {
+			uni.showModal({
+				title: '文本格式化',
+				content: '确定要清除所选择部分文本块格式吗?',
+				showCancel: true,
+				success: ({ confirm }) => {
+					if (confirm) {
+						this.editorCtx.removeFormat()
+					}
+				}
+			})
+		},
+		insertDate() {
+			const date = new Date()
+			const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
+			this.editorCtx.insertText({ text: formatDate })
+		},
+		insertLink() {
+			this.$refs.linkEditRef.open()
+		},
+		/**
+		 * 确认添加链接
+		 * @param {Object} e { text: '链接描述', href: '链接地址' }
+		 */
+		confirmLink(e) {
+			this.$refs.linkEditRef.close()
+			addLink(this.editorCtx, e, () => {
+				// 修复添加超链接后,不触发input更新当前最新内容的bug,这里做一下手动更新
+				this.editorCtx.getContents({
+					success: (res) => {
+						this.$emit('input', { html: res.html, text: res.text }, this.editorId)
+					}
+				})
+			})
+			this.$emit('addLink', e, this.editorId)
+		},
+		insertImage() {
+			// #ifdef APP-PLUS || H5
+			uni.chooseImage({
+				// count: 1, // 默认9
+				success: (res) => {
+					const { tempFiles } = res
+					// 将文件和编辑器示例抛出,由开发者自行上传和插入图片
+					this.$emit('upinImage', tempFiles, this.editorCtx, this.editorId)
+				},
+				fail() {
+					uni.showToast({
+						title: '未授权访问相册权限,请授权后使用',
+						icon: 'none'
+					})
+				}
+			})
+			// #endif
+
+			// #ifdef MP-WEIXIN
+			// 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
+			uni.chooseMedia({
+				// count: 1, // 默认9
+				mediaType: ['image'],
+				success: (res) => {
+					// 同上chooseImage处理
+					const { tempFiles } = res
+					this.$emit('upinImage', tempFiles, this.editorCtx, this.editorId)
+				},
+				fail() {
+					uni.showToast({
+						title: '未授权访问相册权限,请授权后使用',
+						icon: 'none'
+					})
+				}
+			})
+			// #endif
+		},
+		insertVideo() {
+			uni.chooseVideo({
+				sourceType: ['camera', 'album'],
+				success: (res) => {
+					const { tempFilePath } = res
+					// 将文件和编辑器示例抛出,由开发者自行上传和插入图片
+					this.$emit('upinVideo', tempFilePath, this.editorCtx, this.editorId)
+				},
+				fail() {
+					uni.showToast({
+						title: '未授权访问媒体权限,请授权后使用',
+						icon: 'none'
+					})
+				}
+			})
+		},
+		onEditorInput(e) {
+			// 注意不要使用getContents获取html和text,会导致重复触发onStatusChange从而失去toolbar工具的高亮状态
+			// 复制粘贴的时候detail会为空,此时应当直接return
+			if (Object.keys(e.detail).length <= 0) return
+			const { html, text } = e.detail
+			// 识别到标识立即return
+			if (text.indexOf(linkFlag) !== -1) return
+
+			const maxlength = parseInt(this.maxlength)
+			const textStr = text.replace(/[ \t\r\n]/g, '')
+			if (textStr.length > maxlength && maxlength != -1) {
+				uni.showModal({
+					content: `超过${maxlength}字数啦~`,
+					confirmText: '确定',
+					showCancel: false,
+					success: () => {
+						this.$emit('overMax', { html, text }, this.editorId)
+					}
+				})
+			} else {
+				this.$emit('input', { html, text }, this.editorId)
+			}
+		},
+		// 导出
+		exportHtml() {
+			this.editorCtx.getContents({
+				success: (res) => {
+					this.$emit('exportHtml', res.html, this.editorId)
+				}
+			})
+		},
+		eLongpress() {
+			/**
+			 * 微信小程序官方editor的长按事件有bug,需要重写覆盖,不需做任何逻辑,可见下面小程序社区问题链接
+			 * @tutorial https://developers.weixin.qq.com/community/develop/doc/000c04b3e1c1006f660065e4f61000
+			 */
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+@import '@/uni_modules/sp-editor/icons/editor-icon.css';
+@import '@/uni_modules/sp-editor/icons/custom-icon.css';
+
+.sp-editor {
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	position: relative;
+}
+
+.sp-editor-toolbar {
+	box-sizing: border-box;
+	padding: calc(var(--icon-size) / 4) 0;
+	border-bottom: 1px solid #e4e4e4;
+	font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+	display: grid;
+	grid-template-columns: repeat(var(--icon-columns), 1fr);
+}
+
+.iconfont {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+	height: calc(var(--icon-size) * 1.8);
+	cursor: pointer;
+	font-size: var(--icon-size);
+}
+
+.sp-editor-wrapper {
+	flex: 1;
+	overflow: hidden;
+	position: relative;
+}
+
+.editor-container {
+	padding: 8rpx 16rpx;
+	box-sizing: border-box;
+	width: 100%;
+	height: 100%;
+	font-size: 16px;
+	line-height: 1.5;
+}
+
+.ql-image-overlay-none {
+	::v-deep .ql-image-overlay {
+		pointer-events: none;
+		opacity: 0;
+	}
+}
+
+::v-deep .ql-editor.ql-blank::before {
+	font-style: normal;
+	color: #cccccc;
+}
+
+::v-deep .ql-container {
+	min-height: unset;
+}
+
+.ql-active {
+	color: #66ccff;
+}
+
+.fab-tools {
+	display: flex;
+	padding: 0 10rpx;
+	box-sizing: border-box;
+
+	.fab-sub {
+		width: auto;
+		height: auto;
+		margin: 10rpx;
+	}
+}
+</style>

文件差異過大導致無法顯示
+ 24 - 0
uni_modules/sp-editor/icons/custom-icon.css


文件差異過大導致無法顯示
+ 238 - 0
uni_modules/sp-editor/icons/editor-icon.css


+ 83 - 0
uni_modules/sp-editor/package.json

@@ -0,0 +1,83 @@
+{
+	"id": "sp-editor",
+	"displayName": "官方富文本编辑器editor组件改良扩展优化版",
+	"version": "1.5.0",
+	"description": "基于官方的富文本编辑器editor组件,进行改良扩展优化版,添加了调色板,添加超链接等功能,可自定义扩展工具,快来试试吧~",
+	"keywords": [
+        "富文本",
+        "editor",
+        "编辑器"
+    ],
+	"repository": "",
+    "engines": {
+	},
+	"dcloudext": {
+		"type": "component-vue",
+		"sale": {
+			"regular": {
+				"price": "0.00"
+			},
+			"sourcecode": {
+				"price": "0.00"
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "插件不采集任何数据",
+			"permissions": "无"
+		},
+		"npmurl": ""
+	},
+	"uni_modules": {
+		"dependencies": [],
+		"encrypt": [],
+		"platforms": {
+			"cloud": {
+				"tcb": "y",
+                "aliyun": "y",
+                "alipay": "n"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "u",
+					"百度": "u",
+					"字节跳动": "u",
+					"QQ": "u",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+	}
+}

+ 19 - 0
uni_modules/sp-editor/readme.md

@@ -0,0 +1,19 @@
+# sp-editor
+
+### 视频插入功能
+
+`于2024-7-20日v1.4.4版本更新视频插入功能(属实是鸽🕊了太久了)`
+
+- 实现方案:先以图片占位,在导出时,将携带视频链接的图片转换成视频标签。
+- 更多请参考示例一
+- 如果该插件有帮助到您,还望能点赞好评一下,谢谢!🌟
+
+### 文档迁移
+
+> 防止文档失效,提供下列五个地址,内容一致
+
+- [地址一](https://sonvee.github.io/sv-app-docs/docs-github/src/plugins/sp-editor/sp-editor.html)
+- [地址二](https://sv-app-docs.pages.dev/src/plugins/sp-editor/sp-editor.html)
+- [地址三](https://sv-app-docs.4everland.app/src/plugins/sp-editor/sp-editor.html)
+- [地址四](https://sv-app-docs.vercel.app/src/plugins/sp-editor/sp-editor.html) (需要梯子)
+- [地址五](https://static-mp-74bfcbac-6ba6-4f39-8513-8831390ff75a.next.bspapp.com/docs-uni/src/plugins/sp-editor/sp-editor.html) (有IP限制)

文件差異過大導致無法顯示
+ 1 - 0
uni_modules/sp-editor/static/image-resize.min.js


文件差異過大導致無法顯示
+ 8 - 0
uni_modules/sp-editor/static/quill.min.js


+ 132 - 0
uni_modules/sp-editor/utils/index.js

@@ -0,0 +1,132 @@
+// 标识必须独一无二 - 标识是为了使用insertText插入标识文本后,查找到标识所在delta位置的索引
+export const linkFlag = '#-*=*-*=*-*=*@-link超链接标识link-@*=*-*=*-*=*-#'
+
+export function addLink(editorCtx, attr, callback) {
+	// 先插入一段文本内容
+	editorCtx.insertText({
+		text: linkFlag
+	})
+	// 获取全文delta内容
+	editorCtx.getContents({
+		success(res) {
+			let options = res.delta.ops
+			const findex = options.findIndex(item => {
+				return item.insert && typeof item.insert !== 'object' && item.insert?.indexOf(linkFlag) !== -1
+			})
+			// 根据标识查找到插入的位置
+			if (findex > -1) {
+				const findOption = options[findex]
+				const findAttributes = findOption.attributes
+				// 将该findOption分成三部分:前内容 要插入的link 后内容
+				const [prefix, suffix] = findOption.insert.split(linkFlag);
+				const handleOps = []
+				// 前内容
+				if (prefix) {
+					const prefixOps = findAttributes ? {
+						insert: prefix,
+						attributes: findAttributes
+					} : {
+						insert: prefix
+					}
+					handleOps.push(prefixOps)
+				}
+				// 插入的link
+				const linkOps = {
+					insert: attr.text,
+					attributes: {
+						link: attr.href,
+						textDecoration: attr.textDecoration || 'none', // 下划线
+						color: attr.color || '#007aff'
+					}
+				}
+				handleOps.push(linkOps)
+				// 后内容
+				if (suffix) {
+					const suffixOps = findAttributes ? {
+						insert: suffix,
+						attributes: findAttributes
+					} : {
+						insert: suffix
+					}
+					handleOps.push(suffixOps)
+				}
+				// 删除原options[findex]并在findex位置插入上述三个ops
+				options.splice(findex, 1);
+				options.splice(findex, 0, ...handleOps);
+				// 最后重新初始化内容,注意该方法会导致光标重置到最开始位置
+				editorCtx.setContents({
+					delta: {
+						ops: options
+					}
+				})
+				// 所以最后建议使富文本光标失焦,让用户手动聚焦光标
+				editorCtx.blur()
+
+				// 后续回调操作
+				if (callback) callback()
+			}
+		}
+	})
+
+}
+
+/**
+ * 将含有特殊图片形式视频的富文本转换成正常视频的富文本
+ * @param {String} html 要进行处理的富文本字符串
+ * @returns {String} 返回处理结果
+ */
+export function handleHtmlWithVideo(html) {
+	// 正则表达式用于匹配img标签中带有alt属性且alt属性值为视频链接的模式
+	const regex = /<img\s+src="[^"]*"\s+alt="([^"]*)"[^>]*>/g
+	// 使用replace方法和一个函数回调来替换匹配到的内容
+	return html.replace(regex, (match, videoUrl) => {
+		// 替换为video标签,并添加controls属性以便用户可以控制播放
+		return `<video width="80%" controls><source src="${videoUrl}" type="video/mp4"></video>`
+	})
+}
+
+/**
+ * 将img标签中内联style属性中的宽高样式提取出标签width与height属性
+ * @param {Object} html 要处理的富文本字符串
+ * @returns {Object} 返回处理结果
+ */
+export function convertImgStylesToAttributes(html) {
+	return html.replace(/<img\s+([^>]+)\s*>/g, function(match, attributes) {
+		// 分割属性
+		const attrs = attributes.split(/\s+/);
+
+		// 找到style属性的位置
+		const styleIndex = attrs.findIndex(attr => attr.startsWith('style='));
+		if (styleIndex === -1) return match; // 如果没有找到style属性,则返回原样
+
+		// 提取style属性值
+		const styleAttr = attrs.splice(styleIndex, 1)[0];
+		const style = styleAttr.match(/"([^"]*)"/)[1];
+
+		// 解析 style 属性
+		const styleObj = {};
+		style.split(';').forEach(function(part) {
+			if (part) {
+				const [name, value] = part.split(':');
+				styleObj[name.trim()] = value.trim();
+			}
+		});
+
+		// 创建新的 img 标签
+		let newTag = '<img';
+		if (styleObj.width) {
+			newTag += ` width="${styleObj.width}"`;
+		}
+		if (styleObj.height) {
+			newTag += ` height="${styleObj.height}"`;
+		}
+
+		// 添加原有的属性,包括修改过的style属性
+		newTag += ` ${styleAttr} ${attrs.join(' ')}`;
+
+		// 关闭 img 标签
+		newTag += '>';
+
+		return newTag;
+	});
+}

+ 94 - 0
utils/SubscribeMessage.js

@@ -0,0 +1,94 @@
+const arrTemp =  ["beforePay","afterPay","refundApply", "beforeRecharge", "createBargain","pink"];
+
+// export function auth() {
+// 	let tmplIds = {};
+// 	let messageTmplIds = uni.getStorageSync(SUBSCRIBE_MESSAGE);
+// 	tmplIds = messageTmplIds ? JSON.parse(messageTmplIds) : {};
+// 	return tmplIds;
+// }
+
+/**
+ * 支付成功后订阅消息id
+ * 订阅  确认收货通知 订单支付成功  新订单管理员提醒 
+ */
+export function openPaySubscribe() {
+	let tmplIds = uni.getStorageSync('tempID' + arrTemp[0]);
+	return subscribe(tmplIds);
+}
+
+/**
+ * 订单相关订阅消息
+ * 送货 发货 取消订单
+ */
+export function openOrderSubscribe() {
+	let tmplIds = uni.getStorageSync('tempID' + arrTemp[1]);
+	return subscribe(tmplIds);
+}
+
+/**
+ * 提现消息订阅
+ * 成功 和 失败 消息
+ */
+// export function openExtrctSubscribe() {
+// 	let tmplIds = uni.getStorageSync('tempID' + arrTemp[2]);
+// 	return subscribe(tmplIds);
+// }
+
+/**
+ * 订单退款
+ */
+export function openOrderRefundSubscribe() {
+	let tmplIds = uni.getStorageSync('tempID' + arrTemp[2]);
+	return subscribe(tmplIds);
+}
+
+/**
+ * 充值成功
+ */
+export function openRechargeSubscribe() {
+	let tmplIds = uni.getStorageSync('tempID' + arrTemp[3]);
+	return subscribe(tmplIds);
+}
+
+/**
+ * 砍价成功
+ */
+export function openBargainSubscribe() {
+	let tmplIds = uni.getStorageSync('tempID' + arrTemp[4]);
+	return subscribe(tmplIds);
+}
+
+
+/**
+ * 拼团成功
+ */
+export function openPinkSubscribe() {
+	let tmplIds = uni.getStorageSync('tempID' + arrTemp[5]);
+	return subscribe(tmplIds);
+}
+// /**
+//  * 提现
+//  */
+// export function openEextractSubscribe() {
+// 	let tmplIds = JSON.parse(uni.getStorageSync('tempID' + paySubscribe));
+// 	return subscribe(tmplIds);
+// }
+
+/**
+ * 调起订阅界面
+ * array tmplIds 模板id
+ */
+export function subscribe(tmplIds) {
+	 let wecaht = wx;
+	return new Promise((reslove, reject) => {
+		wecaht.requestSubscribeMessage({
+			tmplIds: tmplIds,
+			success(res) {
+				return reslove(res);
+			},
+			fail(res) {
+				return reslove(res);
+			}
+		})
+	});
+}

+ 3 - 0
utils/util.js

@@ -4,6 +4,7 @@ import { useAppStore } from "@/stores/app.js";
 import { pathToBase64 } from "@/plugin/image-tools/index.js";
 import { useToast } from "@/hooks/useToast";
 import pageJson from '@/pages.json';
+const appStore = useAppStore(); // 调用函数获取实例
 export default {
   /**
    * 移除数组中的某个数组并组成新的数组返回
@@ -896,6 +897,8 @@ export function getSceneInfo(e) {
         }
       });
     }
+
+    if (params.merchantId) appStore.UPDATE_MERCHANT_ID(params.merchantId);
     console.log("获取邀请码-params", params)
     return params;
   }