ext.liuqiwen3 недель назад: 2
Родитель
Сommit
2c4266d058
45 измененных файлов с 13472 добавлено и 33 удалено
  1. 1 1
      App.vue
  2. 128 0
      api/book.js
  3. 91 0
      api/factory.js
  4. 71 0
      api/find_fund.js
  5. 45 0
      api/flashSale.js
  6. 326 0
      api/order.js
  7. 112 0
      api/public.js
  8. 232 0
      api/store.js
  9. 18 0
      api/svip.js
  10. 65 1
      api/user.js
  11. 162 0
      components/UserGrowthPanel/index.vue
  12. 375 0
      components/WPayment/index.vue
  13. 204 0
      components/addressWindow/index.vue
  14. 233 0
      components/articleCard/index.vue
  15. 177 0
      components/drag-button/drag-button.vue
  16. 271 0
      components/findFundsCard/index.vue
  17. 235 0
      components/login_mobile/index.vue
  18. 142 0
      components/login_mobile/routine_phone.vue
  19. 577 0
      components/sideBar/index.vue
  20. 80 0
      components/tabsView/index.vue
  21. 69 0
      libs/order.js
  22. 5 0
      main.js
  23. 66 7
      pages.json
  24. 227 0
      pages/change_password/change_password.vue
  25. 312 0
      pages/find_funds/findFunds.vue
  26. 1256 0
      pages/find_funds/fundsOrder.vue
  27. 0 0
      pages/find_funds/index.vue
  28. 1410 18
      pages/index/index.vue
  29. 1133 0
      pages/mall/dapan.vue
  30. 1004 0
      pages/mall/index.vue
  31. 1702 0
      pages/mall/newIndex.vue
  32. 0 6
      pages/order_addcart/order_addcart.vue
  33. 208 0
      pages/users/app_login/index.vue
  34. 575 0
      pages/users/login/index.vue
  35. 1541 0
      static/iconfont/iconfont-app.css
  36. 404 0
      static/iconfont/iconfont.css
  37. BIN
      static/iconfont/iconfont.ttf
  38. BIN
      static/iconfont/iconfont.woff
  39. BIN
      static/iconfont/iconfont.woff2
  40. BIN
      static/images/lingyhj.png
  41. BIN
      static/images/shandian.png
  42. BIN
      static/images/weiling.png
  43. BIN
      static/images/yhjsy.png
  44. BIN
      static/logo1.png
  45. 15 0
      stores/slider.js

+ 1 - 1
App.vue

@@ -19,7 +19,7 @@ onLaunch(async () => {
 @import "@/uni_modules/uview-plus/index.scss";
 @import url("@/plugin/animate/animate.min.css");
 @import "static/css/base.css";
-// @import "static/iconfont/iconfont.css";
+@import "static/iconfont/iconfont.css";
 @import "static/css/guildford.css";
 @import "static/css/style.scss";
   /* 隐藏滚动条,但依旧具备可以滚动的功能 */

+ 128 - 0
api/book.js

@@ -0,0 +1,128 @@
+import request from "@/utils/request.js";
+
+/**
+ * 获取文章列表
+ * @param keyword 关键字
+ * @param range 0-发现 1-关注用户 2-我的
+ */
+export function getArticleList(data) {
+	return request.get('book/page', data);
+}
+
+/**
+ * 当前用户的文章获赞与收藏数
+ * @param keyword 关键字
+ * @param type 0-点赞 1-收藏 2-我的
+ */
+export function getUserArticleList(data) {
+	return request.get('book/likeCollect/page', data);
+}
+
+/**
+ * 其他用户的文章获赞与收藏数
+ * @param userId 用户id
+ */
+export function getOtherUserCount(id) {
+	return request.get(`book/like/Num/${id}`);
+}
+
+/**
+ * 设置文章 关注/收藏/点赞
+ * @param keyword 关键字
+ * @param range 0-发现 1-关注用户 2-我的1
+ */
+export function setUserState(data) {
+	return request.post('book/toggle', data);
+}
+
+/**
+ * 新增评论
+ * @param bookId 文章id 
+ * @param content 评论内容
+ * @param parentId 父评论id
+ * @param replyId 回复的用户id
+ */
+export function addComment(data) {
+	return request.post(`book/comment/add`, data);
+}
+
+/**
+ * 获取粉丝列表
+ * @param keyword 搜索关键字
+ * @param range 0-发现(默认) 1-关注用户
+ * @param type 0-点赞 1-收藏 2-我的
+ * @param userId 用户id
+ */
+export function getFollowList(data) {
+	return request.get(`book/follow/list`, data);
+}
+
+/**
+ * 获取一级评论
+ * @param id 文章id
+ */
+export function getCommentOne(data) {
+	return request.get(`book/commentOne`, data);
+}
+
+/**
+ * 获取二级评论
+ * @param id 文章id
+ */
+export function getCommentTwo(data) {
+	return request.get(`book/commentTwo`, data);
+}
+
+/**
+ * 删除评论
+ * @param id 评论id
+ */
+export function deleteComment(id) {
+	return request.post(`book/comment/del/${id}`);
+}
+
+/**
+ * 获取文章详情
+ * @param id 文章id
+ */
+export function getArticleDetail(id) {
+	return request.get(`book/detail/${id}`);
+}
+
+/**
+ * 添加文章
+ * @param content 文章内容
+ * @param coverImage 封面图片URL
+ * @param id 文章id
+ * @param status 状态(0-草稿 1-待审核)
+ * @param title 文章标题
+ */
+export function addArticle(data) {
+	return request.post('book/save', data);
+}
+
+/**
+ * 删除文章
+ */
+export function deleteArticle(id) {
+	return request.post(`book/delete/${id}`);
+}
+
+/**
+ * 获取视频列表
+ */
+export function getBookVideos(data) {
+	return request.get(`book/videopage/`, data);
+}
+
+/**
+ * 修改购物车数量
+ * @param int cartId  购物车id
+ * @param int number 修改数量
+ */
+export function changeCartNum(cartId, number) {
+	return request.post("cart/num", {
+		id: cartId,
+		number: number
+	}, {}, 1);
+}

+ 91 - 0
api/factory.js

@@ -0,0 +1,91 @@
+import request from "@/utils/request.js";
+
+// 获取所有上架的分类列表
+export function getCategoryListAPI() {
+  return request.get("custom/factory/category/list");
+}
+
+// 获取所有上架的商品列表
+export function getProductListAPI(categoryId) {
+  const url =
+    categoryId === 0
+      ? "custom/factory/product/list"
+      : `custom/factory/product/list?categoryId=${categoryId}`;
+  return request.get(url);
+}
+
+/**
+ * 获取定制工厂商品详情
+ * @param {number} id - 商品ID(必填)
+ * @returns {Promise} - 接口响应Promise对象
+ * @response {Object} data - 响应数据
+ * @response {number} data.code - 状态码(200表示成功)
+ * @response {Object} data.data - 商品详情对象(CustomFactoryProductResponse)
+ * @response {number} data.data.id - 商品ID
+ * @response {string} data.data.name - 商品名称
+ * @response {number} data.data.categoryId - 分类ID
+ * @response {string} data.data.categoryName - 分类名称
+ * @response {Array} data.data.images - 商品图片列表
+ * @response {string} data.data.metalType - 黄金属类型
+ * @response {number} data.data.minWeight - 最小克重限制
+ * @response {number} data.data.maxWeight - 最大克重限制
+ * @response {number} data.data.weightTolerance - 克重误差
+ * @response {number} data.data.deliveryDays - 发货工作日
+ * @response {boolean} data.data.enableQuantitySelection - 是否开启数量选择(0=否,1=是)
+ * @response {Array} data.data.processList - 工艺列表
+ * @response {number} data.data.processList[].id - 工艺ID
+ * @response {string} data.data.processList[].name - 工艺名称
+ * @response {number} data.data.processList[].feePerGram - 每克工艺工费
+ * @response {Array} data.data.formItemList - 商品描述表单项列表
+ * @response {string} data.message - 响应信息
+ */
+export function getProductDetailAPI(id) {
+  return request.get(`custom/factory/product/detail/${id}`);
+}
+
+/**
+ * 创建定制工厂订单
+ * @param {Object} data - 订单请求参数
+ */
+export function createOrderAPI(data) {
+  return request.post("custom/factory/order/create", data);
+}
+
+/**
+ * 获取定制工厂我的订单列表(支持分页、状态筛选)
+ * @param {Object} [options] - 请求配置参数(所有参数均为可选)
+ * @param {number} [options.page=1] - 页码(默认1,integer类型)
+ * @param {number} [options.limit=20] - 每页数量(默认20,integer类型)
+ * @param {number} [options.status=-1] - 订单状态(默认-1表示全部,0=待生产,1=待确认,2=已确认,3=待发货,4=等待收货,5=已完成,6=已取消)
+ */
+export function customFactoryOrderListAPI(options = {}) {
+  // 解构参数并设置默认值(符合接口文档的非必须参数要求)
+  const {
+    page = 1,
+    limit = 20,
+    status = -1, // -1 表示默认查询全部状态(接口未传status时也为全部,此处显式设为-1更清晰)
+  } = options;
+
+  const url =
+    status === -1
+      ? `custom/factory/order/my/list?page=${page}&limit=${limit}`
+      : `custom/factory/order/my/list?page=${page}&limit=${limit}&status=${status}`;
+  return request.get(url);
+}
+
+/**
+ * 获取定制工厂订单详情
+ * @param {number} orderId - 订单ID(必传)
+ * @returns {Promise} - 请求Promise对象
+ */
+export function getOrderDetailAPI(orderId) {
+  return request.get(`custom/factory/order/detail/${orderId}`);
+}
+
+/**
+ * 定制工厂订单确认支付
+ * @param {Object} data - 订单请求参数
+ */
+export function confirmOrder(data) {
+  return request.post("custom/factory/order/confirm/payment", data);
+}

+ 71 - 0
api/find_fund.js

@@ -0,0 +1,71 @@
+import request from "@/utils/request.js";
+/**
+ *
+ * @param {*} data
+ * @returns
+ */
+// 发布找款
+export function findFundsAPI(data) {
+  return request.post("findmoney/create", data);
+}
+// 找款列表
+export function findFundsListAPI(data) {
+  return request.get("findmoney/list/pending", data);
+}
+// 点赞/取消点赞找款订单
+export function likeFundsAPI(orderId) {
+  return request.get(`findmoney/like/${orderId}`);
+}
+// 获取用户找款订单
+export function getUserFindFundsAPI(params) {
+  return request.get(`findmoney/list/user`, params);
+}
+// 接单功能
+export function acceptFindFundsAPI(orderId) {
+  return request.get(`findmoney/accept/${orderId}`);
+}
+
+// 提交找款订单
+export function submitFindFundsAPI(data) {
+  return request.post(`findmoney/submit`, data);
+}
+// 取消找款订单
+export function cancelFindFundsAPI(orderId) {
+  return request.get(`findmoney/cancel/${orderId}`);
+}
+// 余料支付
+export function paylFindFundsBalanceAPI(orderId, type = false, transport) {
+  return request.get(`findmoney/pay/balance/${orderId}/${type}/${transport}`);
+}
+// 余额支付
+export function BalanceMoneyAPI(orderId, addressId, isGold) {
+  return request.get(`findmoney/pay/money/${orderId}/${addressId}/${isGold}`);
+}
+// 找款订单评分
+export function submitFindFundsRateAPI(data) {
+  return request.post(`findmoney/rate`, data);
+}
+// 获取找款订单详情
+export function getFindFundsDetailAPI(orderId) {
+  return request.get(`findmoney/detail/${orderId}`);
+}
+// 找款创建评论
+export function addFundsComment(data) {
+  return request.post(`find-money-order-comment/create`, data);
+}
+// 查询评论列表
+export function getCommentOne(data) {
+  return request.post(`find-money-order-comment/list`, data);
+}
+// 点爱心
+export function setUserState(data) {
+  return request.post(`find-money-order-comment/like`, data);
+}
+// 删除评论
+export function deleteComment(commentId) {
+  return request.get(`find-money-order-comment/delete/${commentId}`);
+}
+// 关注用户
+export function followPublisher(orderId) {
+  return request.get(`findmoney/follow/publisher/${orderId}`);
+}

+ 45 - 0
api/flashSale.js

@@ -0,0 +1,45 @@
+import request from "@/utils/request.js";
+/**
+ *
+ * @param {*} data
+ * @returns
+ */
+//按时间分组查询秒杀商品
+export function seckillProductGroupAPI(data) {
+  return request.post("seckill/product/group", data);
+}
+export function getProductDetail(id) {
+  return request.get(`seckill/product/detail/${id}`);
+}
+// 秒杀商品预下单
+export function seckillOrderPreAPI(data) {
+  return request.post(`seckill/order/pre/order`, data);
+}
+// 加载秒杀预下单信息
+export function seckillOrderLoadPreAPI(preOrderNo) {
+  return request.get(`seckill/order/load/pre/${preOrderNo}`);
+}
+// 设置秒杀订单地址和快递费用
+export function seckillOrderSetAddressAPI(data) {
+  return request.post(`seckill/order/set/address`, data);
+}
+// 创建秒杀商品订单
+export function seckillOrderCreateAPI(data) {
+  return request.post(`seckill/order/create`, data);
+}
+// 秒杀订单支付
+export function seckillOrderPaymentAPI(data) {
+  return request.post(`seckill/order/payment`, data);
+}
+// 支付成功回调
+export function seckillOrderSuccessfulAPI(data) {
+  return request.post(`seckill/order/successful/callback`, data);
+}
+// 查询用户秒杀订单列表
+export function seckillOrderListAPI(data) {
+  return request.post(`seckill/order/list`, data);
+}
+// 回退库存
+export function seckillProductSeckillOrdersAPI(id) {
+  return request.get(`seckill/product/seckillOrders/${id}`);
+}

+ 326 - 0
api/order.js

@@ -0,0 +1,326 @@
+import request from "@/utils/request.js";
+
+/**
+ * 获取购物车列表
+ * @param numType boolean true 购物车数量,false=购物车产品数量
+ */
+export function getCartCounts(numType, type) {
+  return request.get("cart/count?numType=" + numType + "&type=" + type);
+}
+/**
+ * 获取购物车列表
+ *
+ */
+export function getCartList(data) {
+  return request.get("cart/list", data);
+}
+
+/**
+ * 修改购物车数量
+ * @param int cartId  购物车id
+ * @param int number 修改数量
+ */
+export function changeCartNum(cartId, number) {
+  return request.post(
+    "cart/num",
+    {
+      id: cartId,
+      number: number,
+    },
+    {},
+    1
+  );
+}
+/**
+ * 清除购物车
+ * @param object ids join(',') 切割成字符串
+ */
+export function cartDel(ids) {
+  if (typeof ids === "object") ids = ids.join(",");
+  return request.post(
+    "cart/delete",
+    {
+      ids: ids,
+    },
+    {},
+    1
+  );
+}
+
+/**
+ * 购物车重选提交
+ *
+ */
+export function getResetCart(data) {
+  return request.post("cart/resetcart", data);
+}
+
+/**
+ * 订单列表
+ * @param object data
+ */
+export function getOrderList(data) {
+  return request.get("order/list", data);
+}
+
+/**
+ * 订单产品信息
+ * @param string unique
+ */
+export function orderProduct(data) {
+  return request.post("order/product", data);
+}
+
+/**
+ * 订单评价
+ * @param object data
+ *
+ */
+export function orderComment(data) {
+  return request.post("order/comment", data);
+}
+
+/**
+ * 订单支付
+ * @param object data
+ */
+export function orderPay(data) {
+  return request.post("order/pay", data);
+}
+
+/**
+ * 订单统计数据
+ */
+export function orderData(mallType = 0) {
+  return request.get(`order/data/${mallType}`);
+}
+
+/**
+ * 订单取消
+ * @param string id
+ *
+ */
+export function orderCancel(id) {
+  return request.post(
+    "order/cancel",
+    {
+      id: id,
+    },
+    {},
+    1
+  );
+}
+
+/**
+ * 删除已完成订单
+ * @param string uni
+ *
+ */
+export function orderDel(uni) {
+  return request.post(
+    "order/del",
+    {
+      id: uni,
+    },
+    {},
+    1
+  );
+}
+
+/**
+ * 订单详情
+ * @param string uni
+ */
+export function getOrderDetail(uni) {
+  return request.get("order/detail/" + uni);
+}
+
+/**
+ * 再次下单
+ * @param string uni
+ *
+ */
+export function orderAgain(uni) {
+  return request.post("order/again", {
+    orderNo: uni,
+  });
+}
+
+/**
+ * 订单收货
+ * @param string uni
+ *
+ */
+export function orderTake(uni) {
+  return request.post(
+    "order/take",
+    {
+      id: uni,
+    },
+    {},
+    1
+  );
+}
+
+/**
+ * 订单查询物流信息
+ * @returns {*}
+ */
+export function getExpressInfo(data) {
+  return request.post("express/query-track", data);
+}
+
+/**
+ * 订单查询物流信息
+ * @returns {*}
+ */
+export function getExpressList(data) {
+  return request.get("express/list", data);
+}
+// export function express(uni) {
+// 	return request.get("order/express/" + uni);
+// }
+
+/**
+ * 获取退款理由
+ *
+ */
+export function ordeRefundReason() {
+  return request.get("order/refund/reason");
+}
+
+/**
+ * 订单退款审核
+ * @param object data
+ */
+export function orderRefundVerify(data) {
+  return request.post("order/refund", data);
+}
+
+/**
+ * 订单确认获取订单详细信息
+ * @param string cartId
+ */
+export function orderConfirm(
+  cartId,
+  isNew,
+  addAgain,
+  secKill,
+  combination,
+  bargain
+) {
+  return request.post("order/confirm", {
+    cartIds: cartId,
+    isNew: isNew,
+    addAgain: addAgain,
+    secKill: secKill,
+    combination: combination,
+    bargain: bargain,
+  });
+}
+
+/**
+ * 获取当前金额能使用的优惠卷
+ * @param string price
+ *
+ */
+export function getCouponsOrderPrice(preOrderNo) {
+  return request.get(`coupons/order/${preOrderNo}`);
+}
+
+/**
+ * 订单创建
+ * @param string key
+ * @param object data
+ *
+ */
+export function orderCreate(data) {
+  return request.post("order/create", data);
+}
+
+/**
+ * 查询支付结果
+ * @param string key
+ *
+ */
+export function alipayPaymentResult(data) {
+  return request.get("pay/queryPayResult", data);
+}
+
+/**
+ * 计算订单金额
+ * @param key
+ * @param data
+ * @returns {*}
+ */
+export function postOrderComputed(data) {
+  return request.post("order/computed/price", data);
+}
+
+/**
+ * 将字符串 转base64
+ * @param object data
+ */
+export function qrcodeApi(data) {
+  return request.post("qrcode/str2base64", data, {}, 1);
+}
+
+/**
+ * 微信订单支付
+ * @param object data
+ */
+export function wechatOrderPay(data) {
+  return request.post("pay/payment", data);
+}
+
+/**
+ * 微信查询支付结果
+ * @param object data
+ */
+export function wechatQueryPayResult(data) {
+  return request.get("pay/queryPayResult?orderNo=" + data);
+}
+
+/**
+ * 申请退款商品详情
+ * @param object data
+ */
+export function applyRefund(orderId) {
+  return request.get(`order/apply/refund/${orderId}`);
+}
+
+/**
+ * 预下单
+ * @param object data
+ */
+export function preOrderApi(data) {
+  return request.post("order/pre/order", data);
+}
+
+/**
+ * 加载预下单
+ * @param object preOrderNo
+ */
+export function loadPreOrderApi(preOrderNo) {
+  return request.get(`order/load/pre/${preOrderNo}`);
+}
+
+/**
+ * 苹果内购
+ * @param chooseEnv
+ * @param classify
+ * @param payClassifyId
+ * @param receipt
+ * @param userId
+ */
+export function setIapCertificateApi(data) {
+  return request.post("applePay/setIapCertificate", data);
+}
+// 兑换贝币优惠券;
+export function exchangeCouponApi(data) {
+  return request.post("pay/purchaseCouponProduct", data);
+}
+
+export function validateApplePaymentResult(data) {
+  return request.post("applePay/setIapCertificate", data);
+}

+ 112 - 0
api/public.js

@@ -0,0 +1,112 @@
+import request from "@/utils/request.js";
+import wechat from "@/libs/wechat.js";
+/**
+ * 获取微信公众号js配置
+ * @returns {*}
+ */
+export function getWechatConfig() {
+  return request.get("wechat/config",{ url: encodeURIComponent(wechat.signLink()) },{ noAuth: true });
+}
+
+/**
+ * 获取微信sdk配置
+ * @returns {*}
+ */
+export function wechatAuth(code, spread) {
+	var reg=/^[0-9]+.?[0-9]*$/; //判断字符串是否为数字 ,判断正整数用/^[1-9]+[0-9]*]*$/
+	spread = reg.test(spread) ? spread : 0;
+  return request.get(
+    "wechat/authorize/login?code=" + code + "&spread_spid=" + spread, {},
+    { noAuth: true }
+  );
+}
+
+/**
+ * 获取登录授权login
+ * 
+*/
+export function getLogo()
+{
+	// wechat/get_logo
+  return request.get('wechat/getLogo', {}, { noAuth : true});
+}
+
+/**
+ * 小程序用户登录
+ * @param data object 小程序用户登录信息
+ */
+export function login(code,data) {
+  return request.post("wechat/authorize/program/login?code="+code, data, { noAuth : true });
+}
+/**
+ * 分享
+ * @returns {*}
+ */
+export function getShare() {
+  return request.get("share", {}, { noAuth: true });
+}
+
+/**
+ * 获取关注海报
+ * @returns {*}
+ */
+export function follow() {
+  return request.get("wechat/follow", {}, { noAuth: true });
+}
+
+/**
+ * 获取图片base64
+ * @retins {*}
+ * */
+export function imageBase64(image) {
+  return request.post("qrcode/base64",image,{ noAuth: true },1);
+}
+
+/**
+ * 自动复制口令功能
+ * @returns {*}
+ */
+export function copyWords() {
+  return request.get("copy_words", {}, { noAuth: true });
+}
+
+/**
+ * 首页 获取客服地址
+ * @returns {*}
+ */
+export function kefuConfig() {
+  return request.get("config", {}, { noAuth: true });
+}
+
+/**
+ * 微信(公众号,小程序)绑定手机号
+ * @param {Object} data
+ */
+export function getUserPhone(data){
+	return request.post('wechat/register/binding/phone',data,{noAuth : true});
+}
+
+/**
+ * APP微信登录
+ * @param {Object} data
+ */
+export function appAuth(data) {
+	return request.post("wechat/authorize/app/login", data, { noAuth : true });
+}
+
+/**
+ * 苹果登录
+ * @param {Object} data
+ */
+export function appleLogin(data) {
+	return request.post("ios/login", data, { noAuth : true });
+}
+
+
+/**
+ * 苹果绑定手机号
+ * @param {Object} data
+ */
+export function iosBinding(data) {
+	return request.post("ios/binding/phone", data, { noAuth : true });
+}

+ 232 - 0
api/store.js

@@ -0,0 +1,232 @@
+import request from "@/utils/request.js";
+
+/**
+ * 获取产品详情
+ * @param int id
+ *
+ */
+export function getProductDetail(id, type) {
+  return request.get(
+    "product/detail/" + id + "?type=" + type,
+    {},
+    {
+      noAuth: true,
+    }
+  );
+}
+
+/**
+ * 产品分享二维码 推广员
+ * @param int id
+ */
+// #ifndef MP
+export function getProductCode(id) {
+  return request.get("product/code/" + id, {});
+}
+// #endif
+// #ifdef MP
+export function getProductCode(id) {
+  return request.get("product/code/" + id, {
+    user_type: "routine",
+  });
+}
+// #endif
+
+/**
+ * 添加收藏
+ * @param int id
+ * @param string category product=普通产品,product_seckill=秒杀产品
+ */
+export function collectAdd(id, category) {
+  return request.post("collect/add", {
+    id: id,
+    category: category === undefined ? "product" : category,
+  });
+}
+
+/**
+ * 取消收藏产品
+ * @param int id
+ */
+export function collectDel(proId) {
+  return request.post(`collect/cancel/${proId}`);
+}
+
+/**
+ * 删除收藏产品
+ * @param string id
+ */
+export function collectDelete(ids) {
+  return request.post(`collect/delete`, ids);
+}
+
+/**
+ * 购车添加
+ *
+ */
+export function postCartAdd(data) {
+  return request.post("cart/save", data, {});
+}
+
+/**
+ * 获取分类列表
+ *
+ */
+export function getCategoryList() {
+  return request.get(
+    "category",
+    {},
+    {
+      noAuth: true,
+    }
+  );
+}
+
+/**
+ * 获取产品列表
+ * @param object data
+ */
+export function getProductslist(data) {
+  return request.get("products", data, {
+    noAuth: true,
+  });
+}
+
+/**
+ * 获取推荐产品
+ *
+ */
+export function getProductHot(page, limit) {
+  return request.get(
+    "product/hot",
+    {
+      page: page === undefined ? 1 : page,
+      limit: limit === undefined ? 4 : limit,
+    },
+    {
+      noAuth: true,
+    }
+  );
+}
+/**
+ * 批量收藏
+ *
+ * @param object id  产品编号 join(',') 切割成字符串
+ * @param string category
+ */
+export function collectAll(id, category) {
+  return request.post("collect/all", {
+    id: id,
+    category: category === undefined ? "product" : category,
+  });
+}
+
+/**
+ * 首页产品的轮播图和产品信息
+ * @param int type
+ *
+ */
+export function getGroomList(type, data) {
+  return request.get("index/product/backup/" + type, data, {
+    noAuth: true,
+  });
+}
+
+/**
+ * 获取二级分类商品
+ * @param int type
+ *
+ */
+export function getProductByCategoryId(categoryId, params) {
+  // console.log("getProductByCategoryId====>", categoryId, page, limit);
+
+  return request.get(`index/product/category/${categoryId}`, params, {
+    noAuth: true,
+  });
+}
+
+export function getBbMallList(data) {
+  return request.get("index/product/backup/5", data, {
+    noAuth: true,
+  });
+}
+
+/**
+ * 获取收藏列表
+ * @param object data
+ */
+export function getCollectUserList(data) {
+  return request.get("collect/user", data);
+}
+
+/**
+ * 获取产品评论
+ * @param int id
+ * @param object data
+ *
+ */
+export function getReplyList(id, data) {
+  return request.get("reply/list/" + id, data, {
+    noAuth: true,
+  });
+}
+
+/**
+ * 产品评价数量和好评度
+ * @param int id
+ */
+export function getReplyConfig(id) {
+  return request.get(
+    "reply/config/" + id,
+    {},
+    {
+      noAuth: true,
+    }
+  );
+}
+
+/**
+ * 获取搜索关键字获取
+ *
+ */
+export function getSearchKeyword() {
+  return request.get(
+    "search/keyword",
+    {},
+    {
+      noAuth: true,
+    }
+  );
+}
+
+/**
+ * 门店列表
+ * @returns {*}
+ */
+export function storeListApi(data) {
+  return request.post("store/list", data, {}, 1);
+}
+
+/**
+ * 优品推荐
+ * @param object data
+ */
+export function getProductGood() {
+  return request.get("product/good", {}, { noAuth: true });
+}
+
+/**
+ * 详情页产品评论
+ * @param int id
+ * @param object data
+ *
+ */
+export function getReplyProduct(id) {
+  return request.get(
+    "reply/product/" + id,
+    {},
+    {
+      noAuth: true,
+    }
+  );
+}

+ 18 - 0
api/svip.js

@@ -0,0 +1,18 @@
+import request from "@/utils/request.js";
+
+/**
+ * 获取svip价格
+ *
+ */
+export function svipPrice(data) {
+  return request.get("svip/price", data);
+}
+/**
+ * 购买svip
+ *
+ */
+export function svipBuy(data) {
+  console.log("data", data);
+
+  return request.post("svip/buy", data);
+}

+ 65 - 1
api/user.js

@@ -16,7 +16,6 @@ export function loginMobile(data) {
   return request.post("login/mobile", data, { noAuth : true });
 }
 
-
 // 根据用户ID查询用户等级详细信息
 export function getUserLevelInfo(userId) {
   return request.get(`user/level/detail/${userId}`);
@@ -30,6 +29,14 @@ export function spread(puid) {
   return request.get("user/bindSpread?spreadPid=" + puid);
 }
 
+/**
+ * h5用户登录
+ * @param data object 用户账号密码
+ */
+export function loginH5(data) {
+  return request.post("login", data, { noAuth: true });
+}
+
 /**
  * 获取他人用户信息
  *
@@ -37,3 +44,60 @@ export function spread(puid) {
 export function getOtherUserInfo(id) {
   return request.get(`other/${id}`);
 }
+/**
+ * 获取用户openId
+ *
+ */
+export function getUserOpenId(data) {
+  return request.get("wxpay/v2/mini/openid", data);
+}
+/**
+ * h5用户手机号注册
+ * @param data object 用户手机号 验证码 密码
+ */
+export function register(data) {
+  return request.post("register", data, { noAuth: true });
+}
+/**
+ * h5用户发送验证码
+ * @param data object 用户手机号
+ */
+export function registerVerify(phone) {
+  return request.post("sendCode", { phone: phone }, { noAuth: true }, 1);
+}
+/**
+ * 用户手机号修改密码
+ * @param data object 用户手机号 验证码 密码
+ */
+export function registerReset(data) {
+  return request.post("register/reset", data, { noAuth: true });
+}
+
+/**
+ * 获取单个地址
+ * @param int id
+ */
+export function getAddressDetail(id) {
+  return request.get("address/detail/" + id);
+}
+/**
+ * 获取默认地址
+ *
+ */
+export function getAddressDefault() {
+  return request.get("address/default");
+}
+/**
+ *
+ * 地址列表
+ * @param object data
+ */
+export function getAddressList(data) {
+  return request.get("address/list", data);
+}
+/**
+ * 验证码key
+ */
+export function getCodeApi() {
+  return request.get("verify_code", {}, { noAuth: true });
+}

+ 162 - 0
components/UserGrowthPanel/index.vue

@@ -0,0 +1,162 @@
+<template>
+  <view class="balance-box">
+    <view class="first-row">
+      <view class="vip" v-if="appStore.isLogin">
+        VIP会员<text>{{ appStore?.$userInfo?.level || 0 }}级</text>
+      </view>
+      <view v-else @click="toLogin">未登录</view>
+      <view class="gold-price">
+        <text class="text">实时金价&nbsp;</text>
+        <up-loading-icon v-if="realGoldprice === 0"></up-loading-icon>
+        <text class="real-price" v-else>{{ realGoldprice }}</text>
+      </view>
+    </view>
+    <view class="growth">
+      <div class="row-value">
+        <view class="current">
+          成长值{{ appStore?.$userInfo?.experience || 0 }}
+        </view>
+        <view class="total">
+          {{ appStore?.$userInfo?.experience || 0 }}/{{
+            appStore?.$userInfo?.experienceCount || 0
+          }}
+        </view>
+      </div>
+      <up-line-progress
+        :showText="false"
+        :percentage="percentExperience"
+        activeColor="#C99A30"
+      ></up-line-progress>
+    </view>
+    <view class="wealth">
+      <view class="item" @click="toVaultPage('vault')">
+        <uni-icons
+          class="item-icon"
+          size="28"
+          color="#CF9834"
+          customPrefix="iconfont"
+          type="icon-jinku"
+        ></uni-icons>
+        <text class="text">{{ appStore.$userInfo?.goldBalance || 0 }}g</text>
+      </view>
+      <view class="item" @click="toVaultPage('balance')">
+        <uni-icons
+          class="item-icon"
+          size="30"
+          color="#CF9834"
+          customPrefix="iconfont"
+          type="icon-qianbao"
+        ></uni-icons>
+        <text class="text">¥{{ appStore.$userInfo?.nowMoney || 0 }}</text>
+      </view>
+      <view class="item">
+        <uni-icons
+          class="item-icon"
+          size="28"
+          color="#CF9834"
+          customPrefix="iconfont"
+          type="icon-beikehui"
+        ></uni-icons>
+        <text class="text">{{ appStore.$userInfo?.integral || 0 }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+import useRealGoldPrice from "@/hooks/useRealGoldPrice";
+import { toLogin } from "@/libs/login";
+import { getUserInfo } from "@/api/user";
+
+const appStore = useAppStore();
+
+const { realGoldprice, fetchGoldPrice } = useRealGoldPrice("RTJ_Au");
+
+onShow(() => {
+  fetchUserInfo();
+  fetchGoldPrice(undefined, 3);
+});
+
+function toVaultPage(type) {
+  uni.navigateTo({ url: `/pages/users/vault/index?type=${type}` })
+}
+
+// 获取用户信息
+async function fetchUserInfo() {
+  const { data } = await getUserInfo();
+  appStore.UPDATE_USERINFO(data);
+}
+
+// 计算成长值百分比
+const percentExperience = computed(() => {
+  if (appStore.isLogin) {
+    const percent = Math.floor(
+      (appStore?.$userInfo?.experience / appStore?.$userInfo?.experienceCount) *
+        100
+    );
+    return percent > 100 ? 100 : percent;
+  }
+  return 0;
+});
+</script>
+
+<style lang="scss" scoped>
+.balance-box {
+  margin: 25rpx 30rpx 15rpx;
+  padding: 30rpx;
+  background-color: #ffffff;
+  border-radius: $uni-border-radius-default;
+  box-shadow: 0 4rpx 16rpx 0 rgba(0, 0, 0, 0.08);
+
+  .first-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .vip {
+    }
+
+    .gold-price {
+      display: flex;
+      align-items: center;
+      .text {
+      }
+      .real-price {
+        font-size: 35rpx;
+        color: $header-color;
+        font-weight: 600;
+      }
+    }
+  }
+
+  .growth {
+    margin: 30rpx 0 30rpx;
+
+    .row-value {
+      margin: 0 0 10rpx;
+      font-size: 20rpx;
+      display: flex;
+      justify-content: space-between;
+      color: $theme-color;
+    }
+  }
+
+  .wealth {
+    display: flex;
+    justify-content: space-between;
+
+    .item {
+      display: flex;
+      align-items: center;
+      .item-icon {
+        margin-right: 10rpx;
+      }
+      .text {
+      }
+    }
+  }
+}
+</style>

+ 375 - 0
components/WPayment/index.vue

@@ -0,0 +1,375 @@
+<template>
+  <up-popup :show="showPopup" :closeOnClickOverlay="true" @close="close">
+    <view class="payment">
+      <view class="title acea-row row-center-wrapper">
+        <view class="lable">选择付款方式</view>
+        <view class="value"
+          >实付金额:<text class="price">{{ orderUserInfo.totalCount }}</text
+          >元</view
+        >
+      </view>
+
+      <view class="materials">
+        <view class="iconfont-box">
+          <view class="iconfont icon-balance"></view>
+          <view class="text">
+            <view class="name">余料支付</view>
+            <view class="info" v-if="goldType === 'au'">
+              黄金余料:
+              <span class="money">¥{{ orderUserInfo.auBalance || 0 }}g</span>
+            </view>
+            <view class="info" v-if="goldType === 'ag'">
+              白银余料:
+              <span class="money">¥{{ orderUserInfo.agBalance || 0 }}g</span>
+            </view>
+            <view class="info" v-if="goldType === 'pt'">
+              铂金余料:
+              <span class="money">¥{{ orderUserInfo.ptBalance || 0 }}g</span>
+            </view>
+          </view>
+        </view>
+        <!-- <up-switch
+          activeColor="#eb6623"
+          v-model="materials"
+          @change="handleChange"
+        ></up-switch> -->
+      </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">¥{{ orderUserInfo.nowMoney }}</span>
+            </view>
+            <view class="info" v-else>{{ item.title }}</view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </up-popup>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { svipBuy } from "@/api/svip";
+import { BalanceMoneyAPI } from "@/api/find_fund.js";
+import { alipayPaymentResult } from "@/api/order.js";
+import { useAppStore } from "@/stores/app";
+import { useToast } from "@/hooks/useToast";
+import { usePayment } from "@/hooks/usePayment.js";
+import { getUserInfo } from "@/api/user";
+
+// 余料支付开关状态
+const materials = ref(false);
+
+// 定义可从外部触发的事件
+const emit = defineEmits(["closePopup", "payComplete", "payFail", "change"]);
+
+const { submitPayment, paymentConfig } = usePayment();
+const { Toast } = useToast();
+
+const props = defineProps({
+  payMode: {
+    type: Array,
+    default: () => [],
+  },
+  showPopup: {
+    type: Boolean,
+    default: false,
+  },
+  totalPrice: {
+    type: String,
+    default: "0",
+  },
+  goldType: {
+    type: String,
+    default: "au",
+  },
+  orderId: {
+    type: Number,
+    default: 0,
+  },
+  orderUserInfo: {
+    type: Object,
+    default: () => ({}),
+  },
+  addresssId: {
+    type: Number,
+    default: 0,
+  },
+  switchStatus: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+const appStore = useAppStore();
+// 关闭弹窗
+function close() {
+  emit("closePopup");
+}
+
+// 处理开关变化,同时触发外部事件
+const handleChange = (e) => {
+  emit("change", e);
+};
+
+// 暴露给父组件的方法,用于外部触发change事件
+const triggerChange = (value) => {
+  materials.value = value;
+  // handleChange(value);
+};
+
+// 暴露方法给父组件
+defineExpose({
+  triggerChange,
+});
+
+// 处理支付
+async function goPay(item) {
+  console.log("goPay", item);
+  try {
+    const paytype = item.value;
+    if (
+      paytype === "yue" &&
+      parseFloat(props.orderUserInfo.nowMoney) <
+        parseFloat(props.orderUserInfo.totalCount)
+    ) {
+      return Toast({ title: "余额不足!" });
+    }
+    uni.showLoading({ title: "支付中" });
+
+    switch (paytype) {
+      case "alipay":
+        await handleAlipayPayment(paytype);
+
+        break;
+      case "weixin":
+        handleWechatPayment();
+        break;
+      case "yue":
+        const res = await BalanceMoneyAPI(
+          props.orderId,
+          props.addresssId,
+          true
+        );
+        console.log("res==>", res);
+        // 充值成功后获取用户信息更新余额
+        const { data } = await getUserInfo();
+        appStore.UPDATE_USERINFO(data);
+        uni.hideLoading();
+        Toast({ title: "余额支付成功" });
+        emit("payComplete");
+        break;
+      case "weixinh5":
+        uni.hideLoading();
+        // 注意:这里的jsConfig和goPages需要在实际环境中定义
+        location.replace(
+          jsConfig.mwebUrl +
+            "&redirect_url=" +
+            window.location.protocol +
+            "//" +
+            window.location.host +
+            goPages +
+            "&status=1"
+        );
+        Toast({ title: "支付中" });
+        emit("payComplete");
+        break;
+    }
+  } catch (error) {
+    console.error("goPay error", error);
+    uni.hideLoading();
+    return Toast({
+      title: error.message || "支付失败,余料不足,请充值!",
+    });
+  }
+}
+
+// 支付宝支付处理
+async function handleAlipayPayment(payType) {
+  try {
+    const res = await svipBuy({
+      payType,
+      price: props.totalPrice,
+      userId: appStore.$userInfo.userId,
+    });
+    const result = await submitPayment({
+      type: "alipay",
+      orderInfo: res.data.alipayRequest,
+    });
+    console.log("handleAlipayPayment", result, res);
+
+    if (result.status === paymentConfig.PAYMENT_STATUS.SUCCESS) {
+      const params = {
+        orderNo: res.data.orderNo,
+        payType: "alipay",
+      };
+      // 查询后端支付状态接口
+      await alipayPaymentResult(params);
+
+      // 充值成功后获取用户信息更新余额
+      const { data } = await getUserInfo();
+      appStore.UPDATE_USERINFO(data);
+
+      Toast({
+        title: "支付成功",
+        icon: "success",
+      });
+      emit("payComplete");
+    } else if (result.status === paymentConfig.PAYMENT_STATUS.FAIL) {
+      Toast({ title: "支付失败" });
+      emit("payFail");
+    } else if (result.status === paymentConfig.PAYMENT_STATUS.CANCEL) {
+      Toast({ title: "已取消支付" });
+    }
+  } catch (error) {
+    let msg = "支付失败,请稍后再试";
+    if (typeof error === "string") {
+      msg = error;
+    } else if (typeof error === "object" && error.message) {
+      msg = error.message;
+    }
+    Toast({ title: msg });
+    emit("payFail");
+    console.error("handleAlipayPayment", error);
+  } finally {
+    uni.hideLoading();
+  }
+}
+
+// 微信支付处理
+const handleWechatPayment = () => {
+  Toast({
+    title: `准备使用微信充值 ¥${props.totalPrice}`,
+    icon: "none",
+  });
+  // 这里调用微信支付相关接口
+  // 例如:uni.requestPayment({ provider: 'wxpay', ... })
+};
+</script>
+
+<style scoped lang="scss">
+.materials .iconfont.icon-balance {
+  color: #eb6623;
+  font-size: 50rpx;
+}
+.materials {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 30rpx;
+  height: 130rpx;
+  border-bottom: 1rpx solid #eee;
+
+  .iconfont-box {
+    display: flex;
+    align-items: center;
+    .text {
+      margin-left: 18rpx;
+      .name {
+        font-size: 32rpx;
+        color: #282828;
+      }
+      .info {
+        font-size: 24rpx;
+        color: #999;
+        .money {
+          color: #ff9900;
+        }
+      }
+    }
+  }
+}
+.payment {
+  background-color: #fff;
+}
+
+.payment .title {
+  height: 123rpx;
+  font-size: 32rpx;
+  color: #282828;
+  font-weight: bold;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  padding: 0 30rpx;
+  border-bottom: 1rpx solid #eee;
+  .value {
+    .price {
+      margin: 0 8rpx;
+      color: #eb6623;
+    }
+  }
+}
+
+.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>

+ 204 - 0
components/addressWindow/index.vue

@@ -0,0 +1,204 @@
+<template>
+	<view>
+		<view class="address-window" :class="address.address == true ? 'on' : ''">
+			<view class='title'>选择地址<text class='iconfont icon-guanbi' @tap='close'></text></view>
+			<view class='list'>
+				<view class='item acea-row row-between-wrapper' :class='active == index ? "font-color" : ""'
+					v-for="(item, index) in addressList" @tap='tapAddress(index, item.id)' :key='index'>
+					<text class='iconfont icon-ditu' :class='active == index ? "font-color" : ""'></text>
+					<view class='address'>
+						<view class='name' :class='active == index ? "font-color" : ""'>{{ item.realName }}<text class='phone'>{{
+							item.phone }}</text></view>
+						<view class='line1'>{{ item.province }}{{ item.city }}{{ item.district }}{{ item.detail }}</view>
+					</view>
+					<text class='iconfont icon-complete' :class='active == index ? "font-color" : ""'></text>
+				</view>
+			</view>
+			<!-- 无地址 -->
+			<view class='pictrue' v-if="!is_loading && !addressList.length">
+				<image src='/static/images/noAddress.png'></image>
+			</view>
+			<view class='addressBnt bg-color' @tap='goAddressPages'>添加地址</view>
+		</view>
+		<view class='mask' catchtouchmove="true" :hidden='address.address == false' @tap='close'></view>
+	</view>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { getAddressList } from '@/api/user.js';
+
+const props = defineProps({
+  pagesUrl: {
+    type: String,
+    default: '',
+  },
+  address: {
+    type: Object,
+    default: () => ({
+      address: true,
+      addressId: 0,
+    }),
+  },
+  isLog: {
+    type: Boolean,
+    default: false,
+  }
+});
+
+const emit = defineEmits(['OnChangeAddress', 'changeClose', 'changeTextareaStatus', 'OnDefaultAddress']);
+
+const active = ref(0);
+const is_loading = ref(true);
+const addressList = ref([]);
+
+// 对外暴露获取地址的方法
+defineExpose({
+	fetchAddressList
+});
+
+function tapAddress(e, addressid) {
+  active.value = e;
+  let a = {};
+  for (let i = 0, leng = addressList.value.length; i < leng; i++) {
+    if (addressList.value[i].id == addressid) {
+      a = addressList.value[i];
+    }
+  }
+  emit('OnChangeAddress', a);
+}
+
+function close() {
+  emit('changeClose');
+  emit('changeTextareaStatus');
+}
+
+function goAddressPages() {
+  emit('changeClose');
+  emit('changeTextareaStatus');
+  uni.navigateTo({
+    url: props.pagesUrl
+  });
+}
+
+function fetchAddressList() {
+  getAddressList({
+    page: 1,
+    limit: 5
+  }).then(res => {
+    let list = res.data.list;
+    addressList.value = list;
+    is_loading.value = false;
+    let defaultAddress = {};
+    // 处理默认选中项
+    if (!props.address.addressId) return;
+    for (let i = 0, leng = list.length; i < leng; i++) {
+      if (list[i].id == props.address.addressId) {
+        active.value = i;
+        defaultAddress = list[i];
+      }
+    }
+    emit('OnDefaultAddress', defaultAddress);
+  }).catch((error) => {
+		console.error('fetchAddressList', error)
+	})
+}
+
+// 监听 address.address 打开时刷新列表
+// watch(() => props.address.address, (val) => {
+//   if (val) {
+//     fetchAddressListFn();
+//   }
+// }, { immediate: true });
+
+</script>
+
+<style scoped lang="scss">
+.address-window {
+	background-color: #fff;
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	width: 100%;
+	z-index: 101;
+	transform: translate3d(0, 100%, 0);
+	transition: all .3s cubic-bezier(.25, .5, .5, .9);
+}
+
+.address-window.on {
+	transform: translate3d(0, 0, 0);
+}
+
+.address-window .title {
+	font-size: 32rpx;
+	font-weight: bold;
+	text-align: center;
+	height: 123rpx;
+	line-height: 123rpx;
+	position: relative;
+}
+
+.address-window .title .iconfont {
+	position: absolute;
+	right: 30rpx;
+	color: #8a8a8a;
+	font-size: 35rpx;
+}
+
+.address-window .list .item {
+	margin-left: 30rpx;
+	padding-right: 30rpx;
+	border-bottom: 1px solid #eee;
+	height: 129rpx;
+	font-size: 25rpx;
+	color: #333;
+}
+
+.address-window .list .item .iconfont {
+	font-size: 37rpx;
+	color: #2c2c2c;
+}
+
+.address-window .list .item .iconfont.icon-complete {
+	font-size: 30rpx;
+	color: #fff;
+}
+
+.address-window .list .item .address {
+	width: 560rpx;
+}
+
+.address-window .list .item .address .name {
+	font-size: 28rpx;
+	font-weight: bold;
+	color: #282828;
+	margin-bottom: 4rpx;
+}
+
+.address-window .list .item .address .name .phone {
+	margin-left: 18rpx;
+}
+
+.address-window .addressBnt {
+	font-size: 30rpx;
+	font-weight: bold;
+	color: #fff;
+	width: 690rpx;
+	height: 86rpx;
+	border-radius: 43rpx;
+	text-align: center;
+	line-height: 86rpx;
+	margin: 85rpx auto;
+}
+
+.address-window .pictrue {
+	width: 414rpx;
+	height: 336rpx;
+	margin: 0 auto;
+}
+
+.address-window .pictrue image {
+	width: 100%;
+	height: 100%;
+}
+</style>

+ 233 - 0
components/articleCard/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <view class="article-card">
+    <view @click="toDetail(localItem)" class="article-content">
+      <view class="image-container">
+        <image :src="localItem.coverImage" mode="aspectFill" class="main-image"></image>
+        <view class="video-image-right-icon"v-if="item.type === 2">
+          <text class='iconfont icon-sanjiaoxing'></text>
+        </view>
+        <view class="status-box" v-if="item.status !== 2">
+          <view class="item-status">
+            <uni-icons v-if="item.status === 1" color="#fff" type="eye" size="30"></uni-icons>
+            <uni-icons v-if="item.status === 3" color="#fff" type="eye-slash" size="30"></uni-icons>
+            <uni-icons v-if="item.status === 0" customPrefix="iconfont" color="#fff" type="icon-caogaoxiang"
+              size="30"></uni-icons>
+            <view class="text">{{ articleStatusMap[item.status] }}</view>
+          </view>
+        </view>
+      </view>
+      <view class="content-box">
+        <view class="title-container">
+          <text class="title line2">{{ localItem.title }}</text>
+        </view>
+      </view>
+    </view>
+    <view class="article-footer">
+      <view class="author-detail" @click="toPersonal">
+        <image :src="localItem.usePicture" class="small-avatar"></image>
+        <text class="author-name">{{ localItem.userName }}</text>
+      </view>
+      <view class="interaction" @click.stop="handleLike">
+        <uni-icons customPrefix="iconfont" type="icon-dianzan" size="16" v-show="!localItem.likeMark"
+          class="unlike"></uni-icons>
+        <uni-icons customPrefix="iconfont" type="icon-dianzanxuanzhong" size="16" color="red"
+          v-show="localItem.likeMark" :class="['liked', { 'animated heartBeat': showAnimation }]"></uni-icons>
+        <text class="like-count">{{ localItem.likeCount }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, watch } from "vue";
+import { setUserState } from "@/api/book"
+import { useDebounce } from '@/hooks/useDebounceThrottle'
+import { useSafeNavigate } from '@/hooks/useSafeNavigate'
+import { useAppStore } from "@/stores/app";
+
+const { safeNavigateTo } = useSafeNavigate()
+
+const props = defineProps({
+  item: { type: Object, required: true },
+  likeAnimationIds: { type: Array, default: () => [] },
+});
+const emit = defineEmits(["like", "detail"]);
+const appStore = useAppStore();
+
+// 创建本地响应式副本
+const localItem = ref({ ...props.item });
+
+// 控制动画显示
+const showAnimation = ref(false);
+
+const articleStatusMap = {
+  0: "草稿",
+  1: "审核中",
+  2: "已发布",
+  3: "审核被拒绝",
+};
+
+// 监听外部 item 变化,同步到本地
+watch(
+  () => props.item,
+  (newItem) => {
+    localItem.value = { ...newItem };
+  },
+  { deep: true }
+);
+
+// 跳转其它用户
+function toPersonal() {
+  // 点自己的头像直接到我的tabbar页
+  if (Number(localItem.value.userId) === Number(appStore.uid)) {
+    return uni.switchTab({ url: '/pages/user/index' })
+  }
+  // 其它用户跳到 用户个人页
+  uni.navigateTo({ url: `/pages/user/personal?id=${localItem.value.userId}` });
+}
+
+// 点赞
+async function handleLike() {
+  try {
+    const { code } = await setUserState({
+      type: 0,
+      bookId: localItem.value.id,
+    });
+
+    const wasLiked = localItem.value.likeMark;
+    localItem.value.likeMark = !wasLiked;
+
+    if (localItem.value.likeMark) {
+      showAnimation.value = true;
+      localItem.value.likeCount++;
+    } else {
+      showAnimation.value = false;
+      localItem.value.likeCount--;
+    }
+  } catch (error) {
+    console.error("handleLike", error);
+  }
+}
+
+// 跳转详情
+const toDetail = (item) => {
+  // 草稿和审核被拒绝跳到编辑页
+  if (item?.status === 0 || item?.status === 3) {
+    return uni.navigateTo(`/pages/article_create/edit?id=${item.id}`)
+  }
+  // console.log('点击详情', item)
+  if (item.type === 2) {
+    safeNavigateTo(`/pages/video/video_details?id=${item.id}&videoType=list`)
+  } else {
+    safeNavigateTo(`/pages/article_details/index?id=${item.id}`)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.article-card {
+  width: 100%;
+  background-color: #fff;
+  border-radius: 12rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+  .article-content {
+    position: relative;
+
+    .image-container {
+      position: relative;
+      width: 100%;
+
+      .main-image {
+        width: 100%;
+        max-height: 400rpx;
+        aspect-ratio: 4 / 3;
+        object-fit: cover;
+        display: block;
+        position: relative;
+      }
+
+      .status-box {
+        position: absolute;
+        left: 0;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        background-color: rgba(0, 0, 0, 0.4);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        z-index: 2;
+
+        .item-status {
+          color: #fff;
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+          align-items: center;
+        }
+      }
+    }
+
+    .content-box {
+      padding: 16rpx;
+
+      .title-container {
+        margin-bottom: 8rpx;
+
+        .title {
+          font-size: 14px;
+          color: #333;
+          font-weight: bold;
+          line-height: 1.4;
+        }
+      }
+    }
+  }
+
+  .article-footer {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16rpx;
+
+    .author-detail {
+      display: flex;
+      align-items: center;
+
+      .small-avatar {
+        width: 40rpx;
+        height: 40rpx;
+        border-radius: 50%;
+        margin-right: 8rpx;
+      }
+
+      .author-name {
+        font-size: 12px;
+        color: #666;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: 120rpx;
+      }
+    }
+
+    .interaction {
+      display: flex;
+      align-items: center;
+
+      .like-count {
+        font-size: 26rpx;
+        color: #999;
+        margin-left: 6rpx;
+      }
+
+      .unlike {}
+
+      .liked {}
+    }
+  }
+}
+</style>

+ 177 - 0
components/drag-button/drag-button.vue

@@ -0,0 +1,177 @@
+<template>
+  <view>
+    <view
+      id="_drag_button"
+      class="drag"
+      :style="'left: ' + left + 'px; top:' + top + 'px;'"
+      @touchstart="touchstart"
+      @touchmove.stop.prevent="touchmove"
+      @touchend="touchend"
+      @click.stop.prevent="click"
+      :class="{ transition: isDock && !isMove }"
+    >
+      <text class="title">{{ text }}</text>
+      <text class="price">{{ price }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: "drag-button",
+  props: {
+    // 是否开启贴边(拖拽结束后自动贴左右边缘)
+    isDock: {
+      type: Boolean,
+      default: false,
+    },
+    // 显示的价格数值
+    price: {
+      type: Number,
+      default: 0,
+    },
+    // 是否存在底部TabBar(存在时需调整屏幕可用高度)
+    existTabBar: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      top: 0, // 按钮顶部距离屏幕顶部的距离
+      left: 0, // 按钮左侧距离屏幕左侧的距离
+      width: 0, // 按钮宽度
+      height: 0, // 按钮高度
+      offsetWidth: 0, // 按钮宽度的一半(用于拖拽时定位)
+      offsetHeight: 0, // 按钮高度的一半(用于拖拽时定位)
+      windowWidth: 0, // 屏幕可用宽度
+      windowHeight: 0, // 屏幕可用高度
+      isMove: true, // 是否处于拖拽中(控制过渡动画)
+      edge: 15, // 按钮与屏幕边缘的间距(左右/上下通用)
+      text: "实时金价", // 按钮顶部文本
+    };
+  },
+  mounted() {
+    // 获取系统信息(屏幕尺寸、状态栏等)
+    const sys = uni.getSystemInfoSync();
+    this.windowWidth = sys.windowWidth;
+    this.windowHeight = sys.windowHeight;
+
+    // APP端有TabBar时,减去TabBar高度(默认50px,可根据实际调整)
+    // #ifdef APP-PLUS
+    this.existTabBar && (this.windowHeight -= 50);
+    // #endif
+
+    // 处理部分设备顶部状态栏偏移(确保屏幕高度计算完整)
+    if (sys.windowTop) {
+      this.windowHeight += sys.windowTop;
+    }
+
+    // 获取按钮自身的尺寸(宽度/高度)
+    const query = uni.createSelectorQuery().in(this);
+    query
+      .select("#_drag_button")
+      .boundingClientRect((data) => {
+        this.width = data.width;
+        this.height = data.height;
+        this.offsetWidth = data.width / 2;
+        this.offsetHeight = data.height / 2;
+
+        // 初始left:靠右显示,距离右侧边缘edge距离
+        this.left = this.windowWidth - this.width - this.edge;
+
+        // 核心修改:初始top = 屏幕高度的一半 - 按钮高度的一半(实现垂直居中)
+        // 确保按钮的垂直中心与屏幕中心对齐,而非按钮顶部对齐屏幕中心
+        this.top = this.windowHeight / 2 - this.height / 2;
+      })
+      .exec();
+  },
+  methods: {
+    // 按钮点击事件(向父组件传参)
+    click() {
+      this.$emit("btnClick");
+    },
+    // 触摸开始事件(向父组件传参)
+    touchstart(e) {
+      this.$emit("btnTouchstart");
+    },
+    // 触摸移动事件(控制按钮拖拽位置)
+    touchmove(e) {
+      // 仅允许单指拖拽
+      if (e.touches.length !== 1) {
+        return false;
+      }
+
+      this.isMove = true; // 拖拽中关闭过渡动画
+
+      // 水平方向定位:触摸点X坐标 - 按钮宽度的一半(确保触摸点在按钮中心)
+      this.left = e.touches[0].clientX - this.offsetWidth;
+
+      // 垂直方向定位:触摸点Y坐标 - 按钮高度的一半
+      let clientY = e.touches[0].clientY - this.offsetHeight;
+      // H5端特殊处理:补偿高度偏移(避免拖拽时位置偏移)
+      // #ifdef H5
+      clientY += this.height;
+      // #endif
+
+      // 计算垂直方向边界:底部最大可移动距离(避免按钮超出屏幕底部)
+      let edgeBottom = this.windowHeight - this.height - this.edge;
+
+      // 垂直方向边界限制:不允许超出屏幕上下边缘
+      if (clientY < this.edge) {
+        this.top = this.edge; // 上边界
+      } else if (clientY > edgeBottom) {
+        this.top = edgeBottom; // 下边界
+      } else {
+        this.top = clientY; // 正常拖拽位置
+      }
+    },
+    // 触摸结束事件(控制贴边逻辑 + 向父组件传参)
+    touchend(e) {
+      // 开启贴边功能时,拖拽结束后自动贴左右边缘
+      if (this.isDock) {
+        const edgeRight = this.windowWidth - this.width - this.edge; // 右侧边缘位置
+        // 判断按钮中心是否在屏幕左半区:是则贴左,否则贴右
+        if (this.left < this.windowWidth / 2 - this.offsetWidth) {
+          this.left = this.edge; // 贴左边缘
+        } else {
+          this.left = edgeRight; // 贴右边缘
+        }
+      }
+
+      this.isMove = false; // 拖拽结束开启过渡动画
+      this.$emit("btnTouchend"); // 向父组件传参
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+.drag {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  background: linear-gradient(180deg, #fefcf9 0%, #fff2df 100%);
+  box-shadow: 0rpx 3rpx 12rpx 0rpx rgba(0, 0, 0, 0.16);
+  width: 105upx;
+  height: 105upx;
+  border-radius: 50%; // 圆形按钮
+  position: fixed; // 固定定位(不随页面滚动)
+  z-index: 999999; // 最高层级,避免被其他元素遮挡
+  .title {
+    font-size: 19rpx;
+    color: #000000;
+    margin-bottom: 4rpx; // 文本与价格间距
+  }
+  .price {
+    font-size: 26rpx;
+    color: #cc0000;
+    font-weight: bold;
+  }
+  // 贴边/停止拖拽时的过渡动画
+  &.transition {
+    transition: left 0.3s ease, top 0.3s ease;
+  }
+}
+</style>

+ 271 - 0
components/findFundsCard/index.vue

@@ -0,0 +1,271 @@
+<template>
+  <view class="article-card">
+    <view @click="toDetail(localItem)" class="article-content">
+      <view class="article-content">
+        <view class="image-container">
+          <image
+            :src="localItem.images[0]"
+            mode="aspectFill"
+            class="main-image"
+          ></image>
+          <!-- {{ localItem.images[0] }} -->
+          <!-- <view class="status-box" v-if="item.status !== 2">
+            <view class="item-status">
+            <uni-icons
+              v-if="item.status === 1"
+              color="#fff"
+              type="eye"
+              size="30"
+            ></uni-icons>
+            <uni-icons
+              v-if="item.status === 3"
+              color="#fff"
+              type="eye-slash"
+              size="30"
+            ></uni-icons>
+            <uni-icons
+              v-if="item.status === 0"
+              customPrefix="iconfont"
+              color="#fff"
+              type="icon-caogaoxiang"
+              size="30"
+            ></uni-icons>
+            <view class="text">{{ articleStatusMap[item.status] }}</view>
+          </view>
+          </view> -->
+        </view>
+        <view class="content-box">
+          <view class="title-container">
+            <text class="title line2">{{ localItem.description }}</text>
+          </view>
+        </view>
+      </view>
+      <view class="article-footer">
+        <view class="author-detail" @click="toPersonal">
+          <image
+            :src="`${localItem.contactAvatar}`"
+            class="small-avatar"
+          ></image>
+
+          <text class="author-name">{{ localItem.contactName }}</text>
+        </view>
+        <view class="interaction" @click.stop="handleLike">
+          <uni-icons
+            customPrefix="iconfont"
+            type="icon-dianzan"
+            size="16"
+            v-show="!localItem.likeMark"
+            class="unlike"
+          ></uni-icons>
+          <uni-icons
+            customPrefix="iconfont"
+            type="icon-dianzanxuanzhong"
+            size="16"
+            color="#f73842"
+            v-show="localItem.likeMark"
+            :class="['liked', { 'animated heartBeat': showAnimation }]"
+          ></uni-icons>
+          <text class="like-count">{{ localItem.likeCount || 0 }}</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, watch } from "vue";
+import { likeFundsAPI } from "@/api/find_fund";
+import { useDebounce } from "@/hooks/useDebounceThrottle";
+import { useSafeNavigate } from "@/hooks/useSafeNavigate";
+import { useAppStore } from "@/stores/app";
+// 头像baseUrl
+const baseUrl = "http://sb-admin.oss-cn-shenzhen.aliyuncs.com/";
+const { safeNavigateTo } = useSafeNavigate();
+
+const props = defineProps({
+  item: { type: Object, required: true },
+  likeAnimationIds: { type: Array, default: () => [] },
+});
+const emit = defineEmits(["like", "detail"]);
+const appStore = useAppStore();
+
+// 创建本地响应式副本
+const localItem = ref({ ...props.item });
+
+// 控制动画显示
+const showAnimation = ref(false);
+
+const articleStatusMap = {
+  0: "草稿",
+  1: "审核中",
+  2: "已发布",
+  3: "审核被拒绝",
+};
+
+// 监听外部 item 变化,同步到本地
+watch(
+  () => props.item,
+  (newItem) => {
+    localItem.value = { ...newItem };
+  },
+  { deep: true }
+);
+
+// 跳转其它用户
+function toPersonal() {
+  // 点自己的头像直接到我的tabbar页
+  if (Number(localItem.value.uid) === Number(appStore.uid)) {
+    return uni.switchTab({ url: "/pages/user/index" });
+  }
+  // 其它用户跳到 用户个人页
+  uni.navigateTo({ url: `/pages/user/personal?id=${localItem.value.uid}` });
+}
+
+// 点赞
+async function handleLike() {
+  try {
+    const { code } = await likeFundsAPI(localItem.value.id);
+
+    const wasLiked = localItem.value.likeMark;
+    localItem.value.likeMark = !wasLiked;
+
+    if (localItem.value.likeMark) {
+      showAnimation.value = true;
+      localItem.value.likeCount++;
+    } else {
+      showAnimation.value = false;
+      localItem.value.likeCount--;
+    }
+  } catch (error) {
+    console.error("handleLike", error);
+  }
+}
+
+// 跳转详情
+const toDetail = (item) => {
+  // 草稿和审核被拒绝跳到编辑页
+  // if (item?.status === 0 || item?.status === 3) {
+  //   return safeNavigateTo(`/pages/article_create/edit?id=${item.id}`);
+  // }
+  console.log("点击详情");
+  safeNavigateTo(`/pages/users/funds_detail/index?id=${item.id}`);
+};
+// 预览图片
+const previewImage = (item) => {
+  console.log(item[0]);
+
+  // 预览图片,支持多张图片传数组
+  uni.previewImage({
+    urls: item,
+    current: item[0],
+    indicator: "default",
+    loop: true,
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.article-card {
+  width: 100%;
+  background-color: #fff;
+  border-radius: 12rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+  .article-content {
+    position: relative;
+
+    .image-container {
+      position: relative;
+      width: 100%;
+
+      .main-image {
+        width: 100%;
+        max-height: 400rpx;
+        aspect-ratio: 4 / 3;
+        object-fit: cover;
+        display: block;
+        position: relative;
+      }
+      .status-box {
+        position: absolute;
+        left: 0;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        background-color: rgba(0, 0, 0, 0.4);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        z-index: 2;
+        .item-status {
+          color: #fff;
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+          align-items: center;
+        }
+      }
+    }
+
+    .content-box {
+      padding: 16rpx;
+
+      .title-container {
+        margin-bottom: 8rpx;
+
+        .title {
+          font-size: 14px;
+          color: #333;
+          font-weight: bold;
+          line-height: 1.4;
+        }
+      }
+    }
+  }
+
+  .article-footer {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16rpx;
+
+    .author-detail {
+      display: flex;
+      align-items: center;
+
+      .small-avatar {
+        width: 40rpx;
+        height: 40rpx;
+        border-radius: 50%;
+        margin-right: 8rpx;
+      }
+
+      .author-name {
+        font-size: 12px;
+        color: #666;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: 120rpx;
+      }
+    }
+
+    .interaction {
+      display: flex;
+      align-items: center;
+
+      .like-count {
+        font-size: 26rpx;
+        color: #999;
+        margin-left: 6rpx;
+      }
+      .unlike {
+      }
+      .liked {
+      }
+    }
+  }
+}
+</style>

+ 235 - 0
components/login_mobile/index.vue

@@ -0,0 +1,235 @@
+<template>
+  <view v-if="isUp">
+    <view class="mobile-bg" v-if="isShow" @click="close"></view>
+    <view
+      class="mobile-mask animated"
+      :class="{ slideInUp: isUp }"
+      :style="{ position: isPos ? 'fixed' : 'static' }"
+    >
+      <view class="input-item">
+        <input type="text" v-model="account" placeholder="输入手机号" />
+      </view>
+      <view class="input-item">
+        <input type="text" v-model="codeNum" placeholder="输入验证码" />
+        <button class="code" :disabled="disabled" @click="code">
+          {{ text }}
+        </button>
+      </view>
+      <view class="sub_btn" @click="loginBtn">{{
+        (!appStore.userInfo.phone && isLogin) ||
+        (appStore.userInfo.phone && isLogin)
+          ? "立即绑定"
+          : "立即登录"
+      }}</view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useAppStore } from "@/stores/app.js";
+import { onMounted } from "vue";
+import { useSendCode } from "@/hooks/useSendCode";
+import { useToast } from "@/hooks/useToast";
+import { registerVerify, getCodeApi, getUserInfo } from "@/api/user";
+import { getUserPhone, iosBinding } from "@/api/public";
+
+const app = getApp();
+const appStore = useAppStore();
+const { Toast } = useToast();
+const emit = defineEmits(["close", "wechatPhone"]);
+
+const props = defineProps({
+  isUp: { type: Boolean, default: false },
+  authKey: { type: String, default: "" },
+  isShow: { type: Boolean, default: true },
+  isPos: { type: Boolean, default: true },
+  appleShow: { type: String, default: "" },
+  platform: { type: String, default: "" },
+});
+
+// 状态
+const keyCode = ref("");
+const account = ref("");
+const codeNum = ref("");
+const isApp = ref(0);
+
+// 验证码倒计时等逻辑
+const { disabled, text, sendCode } = useSendCode();
+
+// 获取验证码
+async function code() {
+  if (!account.value) return Toast({ title: "请填写手机号码" });
+  if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
+    return Toast({ title: "请输入正确的手机号码" });
+  try {
+    const res = await registerVerify(account.value);
+    Toast({ title: res.msg });
+    sendCode();
+  } catch (err) {
+    Toast({ title: err });
+  }
+}
+
+// 获取验证码api
+function getCode() {
+  getCodeApi()
+    .then((res) => {
+      keyCode.value = res.data.key;
+    })
+    .catch((res) => {
+      Toast({ title: res });
+    });
+}
+
+// 关闭弹窗
+function close() {
+  emit("close", false);
+}
+
+// 登录/绑定
+function loginBtn() {
+  if (!account.value) return Toast({ title: "请填写手机号码" });
+  if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
+    return Toast({ title: "请输入正确的手机号码" });
+  if (!codeNum.value) return Toast({ title: "请填写验证码" });
+  if (!/^[\w\d]+$/i.test(codeNum.value))
+    return Toast({ title: "请输入正确的验证码" });
+  uni.showLoading({
+    title:
+      !appStore.userInfo.phone && appStore.isLogin
+        ? "正在绑定中"
+        : "正在登录中",
+  });
+  if (!appStore.userInfo.phone && appStore.isLogin) {
+    iosBinding({
+      captcha: codeNum.value,
+      phone: account.value,
+    })
+      .then((res) => {
+        Toast({ title: "绑定手机号成功" });
+        isApp.value = 1;
+        getUserInfoFn();
+      })
+      .catch((error) => {
+        uni.hideLoading();
+        Toast({ title: error });
+      });
+  } else {
+    getUserPhone({
+      captcha: codeNum.value,
+      phone: account.value,
+      // #ifdef H5
+      type: "public",
+      // #endif
+      key: props.authKey,
+    })
+      .then((res) => {
+        appStore.LOGIN({ token: res.data.token });
+        appStore.SETUID(res.data.uid);
+        getUserInfoFn();
+      })
+      .catch((error) => {
+        uni.hideLoading();
+        Toast({ title: error });
+      });
+  }
+}
+
+// 获取用户信息
+function getUserInfoFn() {
+  getUserInfo().then((res) => {
+    console.log("getUserInfoFn", res.data);
+
+    uni.hideLoading();
+    appStore.UPDATE_USERINFO(res.data);
+    // #ifdef MP
+    Toast({ title: "登录成功" });
+    close();
+    // #endif
+    // #ifdef H5
+    emit("wechatPhone", true);
+    // #endif
+  });
+}
+
+onMounted(() => {
+  // getCode() // 如需自动获取验证码可打开
+});
+</script>
+
+<style lang="scss" scoped>
+.mobile-bg {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.isPos {
+  position: static;
+}
+
+.mobile-mask {
+  z-index: 20;
+  // position: fixed;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  padding: 67rpx 30rpx;
+  background: #fff;
+
+  .input-item {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    height: 86rpx;
+    margin-bottom: 38rpx;
+
+    input {
+      flex: 1;
+      display: block;
+      height: 100%;
+      padding-left: 40rpx;
+      border-radius: 43rpx;
+      border: 1px solid #dcdcdc;
+    }
+
+    .code {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 220rpx;
+      height: 86rpx;
+      margin-left: 30rpx;
+      background: rgba(233, 51, 35, 0.05);
+      font-size: 28rpx;
+      color: $theme-color;
+      border-radius: 43rpx;
+
+      &[disabled] {
+        background: rgba(0, 0, 0, 0.05);
+        color: #999;
+      }
+    }
+  }
+
+  .sub_btn {
+    width: 690rpx;
+    height: 86rpx;
+    line-height: 86rpx;
+    margin-top: 60rpx;
+    background: #e93323;
+    border-radius: 43rpx;
+    color: #fff;
+    font-size: 28rpx;
+    text-align: center;
+  }
+}
+
+.animated {
+  animation-duration: 0.4s;
+}
+</style>

+ 142 - 0
components/login_mobile/routine_phone.vue

@@ -0,0 +1,142 @@
+<template>
+  <view v-if="isPhoneBox">
+    <view class="mobile-bg" @click="close"></view>
+    <view class="mobile-mask animated" :class="{ slideInUp: isUp }">
+      <view class="info-box">
+        <image :src="logoUrl"></image>
+        <view class="title">获取授权</view>
+        <view class="txt">获取微信的手机号授权</view>
+      </view>
+      <button class="sub_btn" open-type="getPhoneNumber" @getphonenumber="getphonenumber">获取微信手机号</button>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import Routine from '@/libs/routine'
+import { getUserInfo } from "@/api/user"
+import { getUserPhone } from '@/api/public'
+import { useAppStore } from '@/stores/app'
+import { useToast } from "@/hooks/useToast"
+
+const emit = defineEmits(['close'])
+const props = defineProps({
+  isPhoneBox: { type: Boolean, default: false },
+  logoUrl: { type: String, default: '' },
+  authKey: { type: String, default: '' }
+})
+
+const isStatus = ref(false)
+const appStore = useAppStore()
+const { Toast } = useToast()
+
+// #ifdef MP
+// 小程序获取手机号码
+function getphonenumber(e) {
+  uni.showLoading({ title: '加载中' })
+  Routine.getCode()
+    .then(code => {
+      getUserPhoneNumber(e.detail.encryptedData, e.detail.iv, code)
+    })
+    .catch(() => {
+      uni.hideLoading()
+    })
+}
+
+// 小程序获取手机号码回调
+function getUserPhoneNumber(encryptedData, iv, code) {
+  getUserPhone({
+    encryptedData,
+    iv,
+    code,
+    key: props.authKey,
+    type: 'routine'
+  })
+    .then(res => {
+      appStore.LOGIN({ token: res.data.token })
+      appStore.SETUID(res.data.uid)
+      getUserInfoFn()
+    })
+    .catch(res => {
+      uni.hideLoading()
+      Toast({title: res})
+    })
+}
+
+// 获取个人用户信息
+function getUserInfoFn() {
+  getUserInfo().then(res => {
+    uni.hideLoading()
+    appStore.UPDATE_USERINFO(res.data)
+    isStatus.value = true
+    close()
+  })
+}
+// #endif
+
+function close() {
+  emit('close', { isStatus: isStatus.value })
+}
+</script>
+
+<style lang="scss">
+.mobile-bg {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.mobile-mask {
+  z-index: 20;
+  position: fixed;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  padding: 67rpx 30rpx;
+  background: #fff;
+
+  .info-box {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+
+    image {
+      width: 150rpx;
+      height: 150rpx;
+      border-radius: 10rpx;
+    }
+
+    .title {
+      margin-top: 30rpx;
+      margin-bottom: 20rpx;
+      font-size: 36rpx;
+    }
+
+    .txt {
+      font-size: 30rpx;
+      color: #868686;
+    }
+  }
+
+  .sub_btn {
+    width: 690rpx;
+    height: 86rpx;
+    line-height: 86rpx;
+    margin-top: 60rpx;
+    background: $theme-color;
+    border-radius: 43rpx;
+    color: #fff;
+    font-size: 28rpx;
+    text-align: center;
+  }
+}
+
+.animated {
+  animation-duration: .4s
+}
+</style>

+ 577 - 0
components/sideBar/index.vue

@@ -0,0 +1,577 @@
+<template>
+  <view class="sidebar-wrap">
+    <up-popup
+      mode="left"
+      :show="show"
+      :safeAreaInsetTop="false"
+      :safeAreaInsetBottom="false"
+      @close="close"
+      @open="open"
+      zIndex="99999"
+      :customStyle="{ backgroundColor: '#fafafa' }"
+    >
+      <view class="list-wrap">
+        <up-list height="100%">
+          <up-list-item
+            height="50"
+            v-for="(item, index) in indexList"
+            :key="index"
+          >
+            <view :class="['navigator-box', item.open ? 'open' : '']">
+              <!-- 跳转链接行 -->
+              <navigator
+                v-if="item.type === 'navigator'"
+                :render-link="false"
+                class="navigator-link"
+                hover-class="none"
+                :url="item.url"
+              >
+                <view class="title">
+                  <uni-icons
+                    customPrefix="iconfont"
+                    :class="[item.icon, 'item-icon']"
+                    size="20"
+                    color="#f8c007"
+                  ></uni-icons>
+                  <text class="item-text">{{ item.name }}</text>
+                </view>
+                <view v-if="item.type === 'collapse'" class="arrow-icon">
+                  <uni-icons type="down" size="20"></uni-icons>
+                </view>
+              </navigator>
+
+              <!-- 折叠行 -->
+              <view
+                v-else
+                class="navigator-link collapse-row"
+                @click="handleCollapse(item)"
+              >
+                <view class="title">
+                  <uni-icons
+                    customPrefix="iconfont"
+                    :class="[item.icon, 'item-icon']"
+                    size="20"
+                    color="#f8c007"
+                  ></uni-icons>
+                  <text class="item-text">{{ item.name }}</text>
+                </view>
+                <view v-if="item.type === 'collapse'" class="arrow-icon">
+                  <uni-icons type="down" size="20"></uni-icons>
+                </view>
+              </view>
+
+              <!-- 折叠内容 -->
+              <view class="collapse-content" v-if="item.type === 'collapse'">
+                <navigator
+                  v-for="subItem in item.child"
+                  :render-link="false"
+                  class="navigator-link sub-link"
+                  hover-class="none"
+                  :url="subItem.url"
+                  :key="subItem.url"
+                >
+                  <view class="title">
+                    <text class="item-text subItem-text">{{
+                      subItem.name
+                    }}</text>
+                  </view>
+                </navigator>
+              </view>
+            </view>
+          </up-list-item>
+          <view class="white-box"></view>
+        </up-list>
+      </view>
+      <view class="footer-box">
+        <navigator
+          :url="item.url"
+          class="item-box"
+          v-for="item in footerList"
+          :key="item.url"
+        >
+          <view class="icon-box">
+            <uni-icons
+              :size="item.size"
+              color="#ffffff"
+              customPrefix="iconfont"
+              :type="item.icon"
+            ></uni-icons>
+          </view>
+          <view class="name">{{ item.label }}</view>
+        </navigator>
+      </view>
+    </up-popup>
+  </view>
+</template>
+<script setup>
+import { ref, nextTick, computed, watchEffect, onMounted, watch } from "vue";
+// import { onHide } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+import { useSliderStore } from "@/stores/slider";
+const appStore = useAppStore();
+// 获取slider 的状态
+const sliderStore = useSliderStore();
+const indexList = ref([]);
+
+const isSvip = computed(() => {
+  return appStore.isLogin && appStore?.$userInfo?.svip;
+});
+watchEffect(() => {
+  getIndexList();
+});
+
+let timer = null;
+// slider显示逻辑
+const show = ref(sliderStore.sidebarShow);
+onMounted(() => {
+  show.value = sliderStore.sidebarShow;
+});
+watch(
+  show,
+  (newVal) => {
+    sliderStore.updateSidebarShow(newVal);
+  },
+  { immediate: true }
+);
+watch(
+  () => sliderStore.sidebarShow,
+  (newVal) => {
+    show.value = newVal;
+  }
+);
+
+const emit = defineEmits(["open", "close"]);
+const footerList = [
+  {
+    url: "/pages/users/customer_service_message/index",
+    icon: "icon-kefu2",
+    label: "客服",
+    size: 30,
+  },
+  {
+    url: "/pages/users/setting/index",
+    icon: "icon-shezhi",
+    label: "设置",
+    size: 24,
+  },
+];
+
+function getIndexList() {
+  indexList.value = [
+    // {
+    //   name: "设置主题色",
+    //   icon: "icon-wodedingdan",
+    //   type: "navigator",
+    //   url: "/pages/user/themeDebug",
+    // },
+    // {
+    //   name: "新商城",
+    //   icon: "icon-gouwuche",
+    //   type: "navigator",
+    //   url: "/pages/mall/index",
+    // },
+    {
+      name: "我的订单",
+      type: "collapse",
+      icon: "icon-changyonggongneng",
+      open: false,
+      child: [
+        {
+          name: "商城订单",
+          icon: "",
+          type: "navigator",
+          url: "/pages/order_list/index",
+        },
+        {
+          name: "邮寄存金",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/storeMetal/order",
+        },
+        {
+          name: "约价回收",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/recycle/recyle_order",
+        },
+        {
+          name: "秒杀订单",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/utils/flashSale/order",
+        },
+        {
+          name: "定制订单",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/utils/factory/orderList",
+        },
+        // {
+        //   name: "换款订单",
+        //   icon: "",
+        //   type: "navigator",
+        //   url: "/pages/users/new_live_exchange/tradeOrderFormOrder",
+        // },
+        // {
+        //   name: "团购订单",
+        //   icon: "",
+        //   type: "navigator",
+        //   url: "/pages/group_buying/order",
+        // },
+      ],
+    },
+
+    {
+      name: "购物车",
+      icon: "icon-gouwuche1",
+      type: "navigator",
+      url: "/pages/order_addcart/order_addcart",
+    },
+    {
+      name: "买卖料",
+      icon: "icon-jinku",
+      type: "collapse",
+      open: false,
+      child: [
+        {
+          name: "充值",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/recharge",
+        },
+        {
+          name: "提现",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/withdraw",
+        },
+        {
+          name: "买料",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/buy",
+        },
+        {
+          name: "卖料",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/storeMetal/index",
+        },
+        {
+          name: "存料",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/storeMetal/goldBullionStock",
+        },
+        {
+          name: "提料",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/vault/storeMetal/metalExchange",
+        },
+      ],
+    },
+    {
+      name: "钱包",
+      icon: "icon-licai",
+      type: "navigator",
+      url: "/pages/users/vault/index",
+    },
+    // {
+    //   name: "钱包",
+    //   icon: "icon-licai",
+    //   type: "navigator",
+    //   url: "/pages/users/vault/newIndex",
+    // },
+    // {
+    //   name: "金价预警",
+    //   icon: "icon-yujing",
+    //   type: "navigator",
+    //   url: "/pages/users/gold_price_warning/index",
+    // },
+
+    {
+      name: "常用功能",
+      type: "collapse",
+      icon: "icon-changyonggongneng",
+      open: false,
+      child: [
+        // {
+        //   name: "每日一攒",
+        //   icon: "",
+        //   type: "navigator",
+        //   url: "/pages/users/accumulate_gold/index",
+        // },
+        // {
+        //   name: "直播换款",
+        //   icon: "",
+        //   type: "navigator",
+        //   url: "/pages/users/new_live_exchange/index",
+        // },
+        {
+          name: "每日一攒",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/accumulate_gold/newIndex",
+        },
+        // {
+        //   name: "福利秒杀",
+        //   icon: "",
+        //   type: "navigator",
+        //   url: "/pages/users/utils/flashSale/newIndex",
+        // },
+      ],
+    },
+    {
+      name: "超级工具",
+      type: "collapse",
+      icon: "icon-chaojigongju",
+      open: false,
+      child: [
+        {
+          name: "金价预警",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/gold_price_warning/index",
+        },
+        {
+          name: "黄金算盘",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/record_note/index",
+        },
+        {
+          name: "自动买卖",
+          icon: "",
+          type: "navigator",
+          url: "/pages/users/auto_buy/index",
+        },
+      ],
+    },
+
+    // {
+    //   name: "排行榜",
+    //   icon: "icon-a-ziyuan87",
+    //   type: "navigator",
+    //   url: "/pages/users/ranking_list/index",
+    // },
+    // {
+    //   name: "每日一攒",
+    //   icon: "icon-jicunjinxianxing",
+    //   type: "navigator",
+    //   url: "/pages/users/accumulate_gold/index",
+    // },
+    // {
+    //   name: "VIP会员",
+    //   icon: "icon-viphuiyuan1",
+    //   type: "navigator",
+    //   url: "/pages/VIP/VIP",
+    // },
+    // {
+    //   name: "SVIP会员",
+    //   icon: "icon-viphuiyuan1",
+    //   type: "navigator",
+    //   url: "/pages/SVIP/SVIP",
+    // },
+    // {
+    //   name: "贝币商城",
+    //   icon: "icon-beibishangcheng",
+    //   type: "navigator",
+    //   url: "/pages/bb_mall/index",
+    // },
+    // {
+    //   name: "客服消息",
+    //   icon: "icon-kefu2",
+    //   type: "navigator",
+    //   url: "/pages/message_create/message_create",
+    // },
+    // {
+    //   name: "加盟我们",
+    //   icon: "icon-jiamengwomen",
+    //   type: "navigator",
+    //   url: "/pages/join_us/index",
+    // },
+    // {
+    //   name: "找款订单",
+    //   icon: "icon-zhaokuan",
+    //   type: "navigator",
+    //   url: "/pages/find_funds/fundsOrder",
+    // },
+  ];
+}
+
+function open() {
+  show.value = true;
+  uni.hideTabBar();
+  emit("open");
+}
+
+// onHide(() => {
+//   show.value = false;
+//   hideTabBar();
+// });
+
+defineExpose({
+  open,
+  close,
+  show,
+});
+
+function close() {
+  show.value = false;
+  // hideTabBar();
+  emit("close");
+}
+
+function hideTabBar() {
+  // uni.showTabBar({
+  // async complete() {
+  //   await nextTick()
+  //   fixTabBarGap()
+  // }
+  // });
+  // timer && clearTimeout(timer)
+  // timer = setTimeout(() => {
+  //   uni.showTabBar();
+  // }, 250)
+}
+
+function fixTabBarGap() {
+  const info = uni.getSystemInfoSync();
+  // 页面真实高度 = windowHeight + tabBar 高度
+  const page = uni.createSelectorQuery().select(".page-container");
+  page
+    .boundingClientRect((rect) => {
+      const gap = info.screenHeight - rect.height;
+      if (gap > 0) {
+        // 用 CSS 动态修正底部多余空白
+        document.body.style.height = `${info.windowHeight}px`;
+      }
+    })
+    .exec();
+}
+
+function handleCollapse(item) {
+  item.open = !item.open;
+}
+</script>
+
+<style lang="scss" scoped>
+.sidebar-wrap {
+  position: relative;
+}
+::v-deep .u-popup__content {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-items: space-between;
+  height: 100vh;
+}
+.navigator-box {
+  overflow: hidden;
+  &.open {
+    .arrow-icon {
+      transform: rotate(-180deg);
+    }
+    .collapse-content {
+      max-height: 500px !important;
+    }
+  }
+  .collapse-row {
+    &:active {
+      background-color: #f8f8f8;
+    }
+  }
+}
+.list-wrap {
+  width: 70vw;
+  height: 88%;
+  background-color: #fafafa;
+  padding: 30rpx 20rpx 0;
+  overflow: auto;
+  margin: var(--status-bar-height) 0 0;
+  // ::v-deep .u-list-item {
+  //   height: 95rpx;
+  // }
+
+  .navigator-link {
+    padding: 0 20rpx 0 25rpx;
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    line-height: 100rpx;
+    &.sub-link {
+      line-height: 70rpx;
+      .title {
+        padding-left: 54rpx;
+      }
+    }
+  }
+  .arrow-icon {
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  }
+  .collapse-content {
+    // background-color: #FAFAFA;
+    transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    max-height: 0;
+  }
+  .u-list-item {
+    background-color: #fff;
+    margin-bottom: 15rpx;
+    border-radius: 15rpx;
+    overflow: hidden;
+
+    .item-icon {
+      vertical-align: middle;
+      margin-right: 20rpx;
+    }
+    .item-text {
+      font-size: 32rpx;
+      vertical-align: middle;
+    }
+  }
+  .white-box {
+    min-height: 30rpx;
+    background-color: #fafafa;
+  }
+}
+.footer-box {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  z-index: 1000;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 0.9375rem;
+  gap: 1.25rem;
+  margin-bottom: 0.9375rem;
+
+  .item-box {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    .icon-box {
+      width: 80rpx;
+      height: 80rpx;
+      border-radius: 100rpx;
+      background-color: $header-color;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+    .name {
+      margin: 10rpx 0 0;
+      font-size: 28rpx;
+      text-align: center;
+    }
+  }
+}
+.uni-cover-view {
+  width: 100vw;
+  height: 100vh;
+  background-color: #000;
+}
+</style>

+ 80 - 0
components/tabsView/index.vue

@@ -0,0 +1,80 @@
+<template>
+  <view
+    class="tabs-view"
+    :style="{
+      '--active-line-color': activeLineColor,
+      '--active-line-height': activeLineHeight,
+      '--active-line-width': activeLineWidth,
+    }"
+  >
+    <view
+      class="tabs-item"
+      v-for="item in tabsList"
+      :key="item.name"
+      @click="$emit('tab-click', item)"
+    >
+      <text :class="{ active: item.name === activeName }">
+        {{ item.name }}</text
+      >
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: "Tabs",
+  // 接收父组件传递的参数
+  props: {
+    // 标签列表数据
+    tabsList: {
+      type: Array,
+      required: true,
+      validator: (value) => {
+        // 验证每个标签项是否包含name属性
+        return value.every((item) => item.hasOwnProperty("name"));
+      },
+    },
+    // 当前激活的标签名
+    activeName: {
+      type: String,
+      required: true,
+    },
+    // 激活线颜色
+    activeLineColor: {
+      type: String,
+      default: "#f8c20f", // 默认颜色
+    },
+    // 激活线高度
+    activeLineHeight: {
+      type: String,
+      default: "4rpx", // 默认高度
+    },
+    // 激活线宽度
+    activeLineWidth: {
+      type: String,
+      default: "50rpx", // 默认宽度
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.tabs-view {
+  display: flex;
+  align-items: center;
+  width: 100%;
+
+  .tabs-item {
+    flex: 1;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    text {
+      &.active {
+        border-bottom: 4rpx solid var(--active-line-color);
+      }
+    }
+  }
+}
+</style>

+ 69 - 0
libs/order.js

@@ -0,0 +1,69 @@
+import { preOrderApi } from "@/api/order.js";
+import { seckillOrderPreAPI } from "@/api/flashSale.js";
+import util from "@/utils/util";
+import { useToast } from "@/hooks/useToast";
+
+/**
+ * 去商品详情
+ */
+export function goShopDetail(item, uid) {
+  return new Promise((resolve) => {
+    if (item.activityH5 && item.activityH5.type === "1") {
+      uni.navigateTo({
+        url: `/pages/activity/goods_seckill_details/index?id=${item.activityH5.id}`,
+      });
+    } else if (item.activityH5 && item.activityH5.type === "2") {
+      uni.navigateTo({
+        url: `/pages/activity/goods_bargain_details/index?id=${item.activityH5.id}&startBargainUid=${uid}`,
+      });
+    } else if (item.activityH5 && item.activityH5.type === "3") {
+      uni.navigateTo({
+        url: `/pages/activity/goods_combination_details/index?id=${item.activityH5.id}`,
+      });
+    } else {
+      resolve(item);
+    }
+  });
+}
+
+/**
+ * 活动商品、普通商品、购物车、再次购买预下单
+ */
+export function getPreOrder(data) {
+  const { Toast } = useToast();
+  return new Promise((resolve, reject) => {
+    preOrderApi(data)
+      .then((res) => {
+        uni.navigateTo({
+          url:
+            "/pages/users/order_confirm/index?preOrderNo=" +
+            res.data.preOrderNo,
+        });
+      })
+      .catch((err) => {
+        return Toast({
+          title: err.message,
+        });
+      });
+  });
+}
+
+export function getMsPreOrder(data) {
+  const { Toast } = useToast();
+  return new Promise((resolve, reject) => {
+    seckillOrderPreAPI(data)
+      .then((res) => {
+        uni.navigateTo({
+          url:
+            "/pages/users/utils/flashSale/confirmOrder?preOrderNo=" +
+            res.data.preOrderNo,
+        });
+      })
+      .catch((err) => {
+        return Toast({
+          title: err.message,
+        });
+      });
+  });
+}
+// 兑换贝币优惠券

+ 5 - 0
main.js

@@ -14,6 +14,11 @@ export function createApp() {
         config: {
           // 修改默认单位为rpx,相当于执行 uni.$u.config.unit = 'rpx'
           unit: "rpx",
+		  interceptor: {
+		    navbarLeftClick: () => {
+		      console.log("全局拦截");
+		    },
+		  },
           // customIcon: {
           // family: 'iconfont',
           // url: '//at.alicdn.com/t/c/font_4946742_e8oa3t01rkk.css'

+ 66 - 7
pages.json

@@ -14,17 +14,61 @@
 				"navigationBarTitleText": "首页"
 			}
 		},
-		{
-			"path": "pages/order_addcart/order_addcart",
-			"style": {
-				"navigationBarTitleText": "购物车"
-			}
-		},
 		{
 			"path": "pages/user/index",
 			"style": {
 				"navigationBarTitleText": "个人中心"
 			}
+		},
+		{
+		  "path": "pages/change_password/change_password",
+		  "style": {
+		    "navigationBarTitleText": "修改密码",
+		    "navigationBarBackgroundColor": "#ffe079",
+		    "navigationBarTextStyle": "black",
+		    "enablePullDownRefresh": false
+		  }
+		},
+		{
+		  "path": "pages/find_funds/findFunds",
+		  "style": {
+		    "navigationBarTitleText": "找款",
+		    "navigationBarBackgroundColor": "#ffe079",
+		    "navigationBarTextStyle": "black",
+		    "app-plus": {
+		      "titleNView": {
+		        "type": "default"
+		      }
+		    }
+		  }
+		},
+		{
+		  "path": "pages/find_funds/fundsOrder",
+		  "style": {
+		    "navigationBarTitleText": "找款订单",
+		    "navigationBarBackgroundColor": "#ffe079",
+		    "navigationBarTextStyle": "black",
+		    "app-plus": {
+		      "titleNView": {
+		        "type": "default"
+		      }
+		    }
+		  }
+		},
+		{
+		  "path": "pages/mall/index",
+		  "style": {
+		    "navigationBarTitleText": "商城",
+		    "navigationStyle": "custom"
+		  }
+		},
+		{
+		  "path": "pages/mall/newIndex",
+		  "style": {
+		    "navigationBarTitleText": "商城",
+		    "navigationStyle": "custom",
+		    "onReachBottomDistance": 100
+		  }
 		}
 	],
 	"subPackages": [
@@ -39,6 +83,21 @@
 						"navigationBarBackgroundColor": "#e93323",
 						"navigationBarTextStyle": "black"
 					}
+				},
+				{
+					"path": "login/index",
+					"style": {
+						"navigationBarTitleText": "登录",
+						"navigationBarBackgroundColor": "#ffe079",
+						"navigationBarTextStyle": "black"
+					}
+				},
+				{
+					"path": "app_login/index",
+					"style": {
+						"navigationBarTitleText": "绑定手机号",
+						"navigationStyle": "custom"
+					}
 				}
 			]
 		}
@@ -62,7 +121,7 @@
 				"text": "首页"
 			},
 			{
-				"pagePath": "pages/order_addcart/order_addcart",
+				"pagePath": "pages/mall/newIndex",
 				"iconPath": "static/images/tabbar/3-001.png",
 				"selectedIconPath": "static/images/tabbar/3-002.png",
 				"text": "购物车"

+ 227 - 0
pages/change_password/change_password.vue

@@ -0,0 +1,227 @@
+<template>
+  <view class="login-wrapper">
+    <view class="whiteBg">
+      <view class="list">
+        <form @submit.prevent="submit">
+          <view class="item">
+            <view class="acea-row row-middle">
+              <input
+                type="text"
+                class="texts"
+                placeholder="填写手机号"
+                v-model="phone"
+                required
+              />
+            </view>
+          </view>
+          <view class="item">
+            <view class="acea-row row-middle">
+              <image
+                src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/1cfaad5f37a64884b3eef985d254606eqzlswf1m9g.png"
+                style="width: 28rpx; height: 32rpx"
+              ></image>
+              <input
+                type="text"
+                placeholder="填写验证码"
+                class="codeIput"
+                v-model="captcha"
+                style="width: 80%"
+              />
+              <view
+                class="code"
+                :disabled="disabled"
+                :class="disabled === true ? 'on' : ''"
+                @click="code"
+              >
+                {{ text }}
+              </view>
+            </view>
+          </view>
+          <view class="item">
+            <view class="acea-row row-middle">
+              <image
+                src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/1cfaad5f37a64884b3eef985d254606eqzlswf1m9g.png"
+                style="width: 28rpx; height: 32rpx"
+              ></image>
+              <input
+                type="password"
+                class="texts"
+                placeholder="填写新密码"
+                v-model="password"
+                required
+              />
+            </view>
+          </view>
+          <view class="item">
+            <view class="acea-row row-middle">
+              <image
+                src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/1cfaad5f37a64884b3eef985d254606eqzlswf1m9g.png"
+                style="width: 28rpx; height: 32rpx"
+              ></image>
+              <input
+                type="password"
+                class="texts"
+                placeholder="重复新密码"
+                v-model="passwordRP"
+                required
+              />
+            </view>
+          </view>
+        </form>
+      </view>
+      <view class="logon" @click="submit">确认修改</view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+import { registerVerify, registerReset } from "@/api/user";
+import { useSendCode } from "@/hooks/useSendCode";
+import { useToast } from "@/hooks/useToast.js";
+import { telEncrypt } from "@/utils/util.js";
+
+const appStore = useAppStore();
+const { Toast } = useToast();
+
+// Reactive state
+const phone = ref("");
+const password = ref("");
+const passwordRP = ref("");
+const captcha = ref("");
+
+const { disabled, text, sendCode } = useSendCode();
+
+onLoad(() => {
+  phone.value = appStore.$userInfo?.phone || "";
+});
+
+// Send verification code
+const code = async () => {
+  if (!phone.value) return Toast({ title: "请填写手机号", icon: "none" });
+  try {
+    const res = await registerVerify(phone.value);
+    Toast({ title: res.message, icon: "none" });
+    sendCode();
+  } catch (err) {
+    Toast({ title: err.message, icon: "none" });
+  }
+};
+
+// h5 login
+const submit = async () => {
+  if (!phone.value) return Toast({ title: "请填写手机号", icon: "none" });
+  if (!captcha.value) return Toast({ title: "请填写验证码", icon: "none" });
+  if (!password.value)
+    return uni.showToast({ title: "请填写密码", icon: "none" });
+  // if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$/i.test(password.value))
+  //   return uni.showToast({ title: "您输入的密码过于简单", icon: "none" });
+  if (password.value !== passwordRP.value)
+    return Toast({ title: "两次密码不一致" });
+
+  try {
+    const { data } = await registerReset({
+      password: password.value,
+      captcha: captcha.value,
+      account: phone.value,
+    });
+    Toast({ title: "修改成功", icon: "none" });
+    setTimeout(() => {
+      uni.switchTab({
+        url: "/pages/index/index",
+      });
+    }, 1000);
+  } catch (err) {
+    console.log("submit error", err);
+    Toast({ title: err, icon: "none" });
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+page {
+  background: #fff;
+}
+.appLogin {
+  margin-top: 60rpx;
+}
+
+.code img {
+  width: 100%;
+  height: 100%;
+}
+.acea-row.row-middle {
+  input {
+    margin-left: 20rpx;
+    display: block;
+  }
+}
+.login-wrapper {
+  padding: 30rpx;
+  .whiteBg {
+    margin-top: 100rpx;
+
+    .list {
+      border-radius: 16rpx;
+      overflow: hidden;
+
+      .item {
+        border-bottom: 1px solid #f0f0f0;
+        background: #fff;
+
+        .row-middle {
+          position: relative;
+          padding: 16rpx 45rpx;
+
+          .texts {
+            flex: 1;
+            font-size: 28rpx;
+            height: 80rpx;
+            line-height: 80rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+          }
+
+          input {
+            flex: 1;
+            font-size: 28rpx;
+            height: 80rpx;
+            line-height: 80rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+          }
+
+          .code {
+            position: absolute;
+            right: 30rpx;
+            top: 50%;
+            color: $theme-color;
+            font-size: 26rpx;
+            transform: translateY(-50%);
+            border: 1px solid $theme-color;
+            padding: 6rpx 20rpx;
+            border-radius: 12rpx;
+            z-index: 99;
+          }
+        }
+      }
+    }
+    .logon {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 86rpx;
+      margin-top: 80rpx;
+      background-color: $theme-color;
+      border-radius: 120rpx;
+      color: #ffffff;
+      font-size: 30rpx;
+    }
+  }
+}
+</style>

+ 312 - 0
pages/find_funds/findFunds.vue

@@ -0,0 +1,312 @@
+<template>
+  <view class="page-container">
+    <view class="banner"></view>
+    <view class="find-funds-info">
+      <view class="title-box">
+        <view class="title">请上传1~3张产品图</view>
+        <view class="f-title">图片越清晰越容易识别款式哦~</view>
+      </view>
+      <!-- 上传 -->
+      <view class="upload-box">
+        <up-upload
+          :fileList="imageList"
+          uploadIcon="plus"
+          @afterRead="afterRead"
+          @delete="deletePic"
+          name="1"
+          multiple
+          :maxCount="3"
+        >
+          <template #trigger>
+            <view class="upload-block">
+              <uni-icons size="38" color="#ccc" type="plusempty"></uni-icons>
+            </view>
+          </template>
+        </up-upload>
+      </view>
+      <!-- 信息 -->
+      <view class="info-box">
+        <view class="info-item">
+          <view class="info-label">预估重量(g)</view>
+          <view class="info-input">
+            <input type="number" v-model="findFunds.estimatedWeight" />
+          </view>
+        </view>
+        <view class="info-item">
+          <view class="info-label">理想工费(g)</view>
+          <view class="info-input">
+            <input type="number" v-model="findFunds.idealWage" />
+          </view>
+        </view>
+        <view class="info-item">
+          <view class="info-label">购买件数</view>
+          <view class="info-input">
+            <input type="number" v-model="findFunds.numberOfUnits" />
+          </view>
+        </view>
+        <view class="info-item">
+          <view class="info-label">金属类型</view>
+
+          <!-- <up-radio-group v-model="goldType" @change="selectGoldType">
+            <up-radio
+              v-for="item in goldList"
+              :key="item.title"
+              :name="item.name"
+              activeColor="#e9c279"
+              :label="item.title"
+            ></up-radio>
+          </up-radio-group> -->
+          <radio-group
+            @change="selectGoldType"
+            class="radio-box"
+            :value="goldType"
+          >
+            <label class="lable-box" v-for="item in goldList" :key="item.title">
+              <view>
+                <radio
+                  :value="item.value"
+                  activeBackgroundColor="#e9c279"
+                  :checked="goldType == item.value"
+                />
+              </view>
+              <view>{{ item.title }}</view>
+            </label>
+          </radio-group>
+        </view>
+        <view class="info-item">
+          <view class="info-label">买家留言</view>
+        </view>
+        <view class="info-input info-item-message">
+          <up-textarea
+            v-model="findFunds.description"
+            placeholder="请输入您的留言,例如:l件3克的戒指"
+            :maxlength="30"
+            :count="true"
+          ></up-textarea>
+        </view>
+      </view>
+    </view>
+    <!-- 发布找款 -->
+    <view class="btn-box" @click="publish">
+      <image class="sb-btn-img" src="/static/images/sb_btn.png"></image>
+      <text>发布找款</text>
+    </view>
+    <!-- 提示 -->
+    <view class="find-finish-tips">
+      *发布找款后,正常48小时内会有代购接单,如超时代表未找到该订单,
+      请关注订单状态。
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { useToast } from "@/hooks/useToast";
+import { useImageUpload } from "@/hooks/useImageUpload";
+import { ref, reactive } from "vue";
+import { findFundsAPI } from "@/api/find_fund";
+const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
+  pid: 10,
+  model: "Refund",
+});
+const { Toast } = useToast();
+// 选择金料类型
+const goldList = reactive([
+  {
+    title: "黄金",
+    value: "au",
+  },
+
+  {
+    title: "白银",
+    value: "ag",
+  },
+  {
+    title: "铂金",
+    value: "pt",
+  },
+  // {
+  //   title: "K金",
+  //   name: "kau",
+  // },
+]);
+const goldType = ref("au");
+const selectGoldType = (e) => {
+  goldType.value = e.detail.value;
+};
+const findFunds = ref({
+  estimatedWeight: "",
+  idealWage: "",
+  metalType: "",
+  numberOfUnits: "",
+  purchaserImgs: [],
+  description: "",
+});
+// 发布找款
+const publish = async () => {
+  if (checkData()) {
+    const res = await findFundsAPI({
+      ...findFunds.value,
+      metalType: goldType.value,
+      purchaserImgs: imageList.value.map((v) => v.info.url),
+    });
+    Toast({ title: "发布成功", icon: "success" });
+    uni.redirectTo({ url: "/pages/find_funds/fundsOrder" });
+  }
+};
+const checkData = () => {
+  if (findFunds.value.estimatedWeight == "") {
+    Toast({ title: "请填写预估重量" });
+    return false;
+  }
+  if (findFunds.value.idealWage == "") {
+    Toast({ title: "请填写理想工费" });
+    return false;
+  }
+  if (findFunds.value.numberOfUnits == "") {
+    Toast({ title: "请填写购买件数" });
+    return false;
+  }
+  if (findFunds.value.description == "") {
+    Toast({ title: "请填写买家留言" });
+    return false;
+  }
+  if (imageList.value.length == 0) {
+    Toast({ title: "请上传产品图" });
+    return false;
+  }
+  return true;
+};
+// 请输入您的留言
+// const message = ref("");
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  width: 750rpx;
+  height: 100vh;
+  padding: 20rpx 30rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+  min-height: 100vh;
+  background: $uni-bg-primary;
+
+  //   .banner {
+  //     position: absolute;
+  //     left: 0;
+  //     top: 0;
+  //     width: 100%;
+  //     height: 50vh;
+  //     background-image: linear-gradient(
+  //       360deg,
+  //       #ffffff 0%,
+  //       #e8c279 100%
+  //     ) !important;
+  //     z-index: -1;
+  //   }
+  .find-funds-info {
+    width: 682rpx;
+    min-height: 1069rpx;
+    background-color: #ffffff;
+    box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
+    border-radius: 20rpx;
+    box-sizing: border-box;
+    padding: 40rpx;
+    .title-box {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      //   padding: 30rpx;
+      box-sizing: border-box;
+      .title {
+        font-size: 28rpx;
+        color: #000000;
+      }
+      .f-title {
+        font-size: 21rpx;
+        color: #848484;
+      }
+    }
+    .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;
+      }
+    }
+    .info-box {
+      .info-item {
+        display: flex;
+        align-items: center;
+        margin: 40rpx 0rpx;
+        position: relative;
+        .info-label {
+          font-size: 26rpx;
+          color: #000000;
+          width: 200rpx;
+        }
+        .info-input {
+          width: 437rpx;
+          height: 69rpx;
+          background-color: #f3f3f3;
+          border-radius: 10rpx;
+          display: flex;
+          align-items: center;
+          input {
+            width: 100%;
+            height: 100%;
+            padding-left: 10rpx;
+          }
+        }
+      }
+    }
+  }
+  .btn-box {
+    width: 270rpx;
+    height: 66rpx;
+    font-size: 28rpx;
+    color: #000;
+    position: relative;
+    margin-top: 40rpx;
+    .sb-btn-img {
+      width: 100%;
+      height: 100%;
+    }
+    text {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      white-space: nowrap;
+    }
+  }
+  .find-finish-tips {
+    margin-top: 50rpx;
+    font-family: Source Han Sans CN;
+    font-size: 21rpx;
+    line-height: 30rpx;
+    color: #7c7c7c;
+  }
+  .radio-box {
+    width: 437rpx;
+    display: flex;
+    justify-content: space-between;
+    .lable-box {
+      display: flex;
+      align-items: center;
+
+      margin-right: 10rpx;
+    }
+  }
+}
+</style>

Разница между файлами не показана из-за своего большого размера
+ 1256 - 0
pages/find_funds/fundsOrder.vue


+ 0 - 0
pages/find_funds/index.vue


Разница между файлами не показана из-за своего большого размера
+ 1410 - 18
pages/index/index.vue


Разница между файлами не показана из-за своего большого размера
+ 1133 - 0
pages/mall/dapan.vue


Разница между файлами не показана из-за своего большого размера
+ 1004 - 0
pages/mall/index.vue


Разница между файлами не показана из-за своего большого размера
+ 1702 - 0
pages/mall/newIndex.vue


+ 0 - 6
pages/order_addcart/order_addcart.vue

@@ -1,6 +0,0 @@
-<template>
-    <view>购物车</view>
-</template>
-<script setup>
-    
-</script>

+ 208 - 0
pages/users/app_login/index.vue

@@ -0,0 +1,208 @@
+<template>
+	<view class="appBox">
+		<div class="shading">
+			<image :src="logoUrl" v-if="logoUrl" />
+			<image src="/static/images/logo2.png" v-else />
+		</div>
+		<mobileLogin :isUp="isUp" :isShow="isShow" :platform="platform" :isPos="isPos" :appleShow="appleShow" :authKey="authKey" @wechatPhone="wechatPhone"></mobileLogin>
+	</view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { useAppStore } from '@/stores/app'
+import { onLoad } from '@dcloudio/uni-app'
+import { useSendCode } from '@/hooks/useSendCode'
+import { useToast } from '@/hooks/useToast'
+import Cache from '@/utils/cache'
+import {
+  registerVerify,
+  getCodeApi,
+  getUserInfo,
+} from "@/api/user"
+import mobileLogin from '@/components/login_mobile/index.vue'
+
+const appStore = useAppStore()
+const emit = defineEmits(['close', 'wechatPhone'])
+const { Toast } = useToast()
+
+const options = ref({})
+const keyCode = ref('')
+const account = ref('')
+const codeNum = ref('')
+const isUp = ref(true)
+const authKey = ref('')
+const logoUrl = ref('')
+const isShow = ref(false)
+const isPos = ref(false)
+const platform = ref('') // 手机平台
+const appleShow = ref('') //是否是苹果登录
+const userInfo = ref({})
+
+const { sendCode } = useSendCode()
+
+function wechatPhone() {
+  Cache.clear('snsapiKey')
+  if (options.value.back_url) {
+    let url = uni.getStorageSync('snRouter')
+    url = url.indexOf('/pages/index/index') != -1 ? '/' : url
+    if (url.indexOf('/pages/users/wechat_login/index') !== -1) {
+      url = '/'
+    }
+    if (!url) {
+      url = '/pages/index/index'
+    }
+    isUp.value = false
+    uni.showToast({
+      title: '登录成功',
+      icon: 'none'
+    })
+    // setTimeout(() => {
+    //   location.href = url
+    // }, 800)
+  } else {
+    uni.navigateBack()
+  }
+}
+
+// 获取验证码
+async function code() {
+  if (!account.value) return Toast({ title: '请填写手机号码' })
+  if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value)) return Toast({ title: '请输入正确的手机号码' })
+  try {
+    const res = await registerVerify(account.value)
+    Toast({ title: res.msg })
+    sendCode()
+  } catch (err) {
+    Toast({ title: err })
+  }
+}
+
+// 获取验证码api
+function getCode() {
+  getCodeApi().then(res => {
+    keyCode.value = res.data.key
+  }).catch(res => {
+    Toast({ title: res })
+  })
+}
+
+function close() {
+  emit('close', false)
+}
+
+// 获取个人用户信息
+function getUserInfoFn() {
+  getUserInfo().then(res => {
+    uni.hideLoading()
+    userInfo.value = res.data
+    appStore.UPDATE_USERINFO(res.data)
+    // #ifdef MP
+    Toast({ title: '登录成功', icon: 'success' }, { tab: 3 })
+    close()
+    // #endif
+    // #ifdef H5
+    emit('wechatPhone', true)
+    // #endif
+  })
+}
+
+// 页面加载
+onLoad((opts) => {
+  // uni.getSystemInfo({
+  //   success(res) {
+  //     platform.value = res.platform
+  //   }
+  // })
+  // options.value = opts
+  // if (opts.authKey) authKey.value = opts.authKey
+  // if (opts.appleShow) appleShow.value = opts.appleShow
+})
+
+</script>
+<style>
+	page {
+		height: 100%;
+	}
+</style>
+<style lang="scss" scoped>
+	.appBox {
+		background-color: #fff;
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		overflow: hidden;
+	}
+
+	.shading {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 100%;
+
+
+
+
+		image {
+			width: 180rpx;
+			height: 180rpx;
+		}
+	}
+
+	page {
+		background-color: #fff !important;
+	}
+
+	.ChangePassword .phone {
+		font-size: 32rpx;
+		font-weight: bold;
+		text-align: center;
+		margin-top: 55rpx;
+	}
+
+	.ChangePassword .list {
+		width: 580rpx;
+		margin: 53rpx auto 0 auto;
+	}
+
+	.ChangePassword .list .item {
+		width: 100%;
+		height: 110rpx;
+		border-bottom: 2rpx solid #f0f0f0;
+	}
+
+	.ChangePassword .list .item input {
+		width: 100%;
+		height: 100%;
+		font-size: 32rpx;
+	}
+
+	.ChangePassword .list .item .placeholder {
+		color: #b9b9bc;
+	}
+
+	.ChangePassword .list .item input.codeIput {
+		width: 340rpx;
+	}
+
+	.ChangePassword .list .item .code {
+		font-size: 32rpx;
+		background-color: #fff;
+	}
+
+	.ChangePassword .list .item .code.on {
+		color: #b9b9bc !important;
+	}
+
+	.ChangePassword .confirmBnt {
+		font-size: 32rpx;
+		width: 580rpx;
+		height: 90rpx;
+		border-radius: 45rpx;
+		color: #fff;
+		margin: 92rpx auto 0 auto;
+		text-align: center;
+		line-height: 90rpx;
+	}
+</style>

+ 575 - 0
pages/users/login/index.vue

@@ -0,0 +1,575 @@
+<template>
+  <div class="login-wrapper">
+    <div
+      class="back_extend"
+      v-if="appStore?.$wxConfig?.auditModeEnabled"
+      @click="backHome"
+    >
+      {{ "<" }} 回到首页
+    </div>
+    <div class="shading">
+      <image :src="logoUrl" />
+      <!-- <image src="/static/images/logo2.png" v-if="!logoUrl" /> -->
+    </div>
+    <div class="whiteBg" v-if="formItem === 1">
+      <div class="list" v-if="current !== 1">
+        <form @submit.prevent="submit">
+          <div class="item">
+            <div class="acea-row row-middle">
+              <image
+                src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/phone_1.png"
+                style="width: 24rpx; height: 34rpx"
+              ></image>
+              <input
+                type="text"
+                class="texts"
+                placeholder="输入手机号码"
+                v-model="account"
+                required
+              />
+            </div>
+          </div>
+          <div class="item">
+            <div class="acea-row row-middle">
+              <image
+                src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/1cfaad5f37a64884b3eef985d254606eqzlswf1m9g.png"
+                style="width: 28rpx; height: 32rpx"
+              ></image>
+              <input
+                type="password"
+                class="texts"
+                placeholder="填写登录密码"
+                v-model="password"
+                required
+              />
+            </div>
+          </div>
+        </form>
+      </div>
+      <div
+        class="list"
+        v-if="current !== 0 || appLoginStatus || appleLoginStatus"
+      >
+        <div class="item">
+          <div class="acea-row row-middle">
+            <image
+              src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/phone_1.png"
+              style="width: 24rpx; height: 34rpx"
+            ></image>
+            <input
+              type="text"
+              class="texts"
+              placeholder="输入手机号码"
+              v-model="account"
+            />
+          </div>
+        </div>
+        <div class="item">
+          <div class="acea-row row-middle">
+            <image
+              src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/1cfaad5f37a64884b3eef985d254606eqzlswf1m9g.png"
+              style="width: 28rpx; height: 32rpx"
+            ></image>
+            <input
+              type="text"
+              placeholder="填写验证码"
+              class="codeIput"
+              v-model="captcha"
+            />
+            <div
+              class="code"
+              :disabled="disabled"
+              :class="disabled === true ? 'on' : ''"
+              @click="getCode"
+            >
+              {{ text }}
+            </div>
+          </div>
+        </div>
+        <div class="item">
+          <div class="text-row row-middle">
+            新用户可使用验证码登录,将自动注册账号
+          </div>
+          <div class="text-row row-middle" v-if="invite_code">
+            邀请码:{{ invite_code }}
+          </div>
+        </div>
+        <div class="item" v-if="isShowCode">
+          <div class="acea-row row-middle">
+            <image
+              src="https://sb-admin.oss-cn-shenzhen.aliyuncs.com/crmebimage/public/maintain/2025/08/28/1cfaad5f37a64884b3eef985d254606eqzlswf1m9g.png"
+              style="width: 28rpx; height: 32rpx"
+            ></image>
+            <input
+              type="text"
+              placeholder="填写验证码"
+              class="codeIput"
+              v-model="codeVal"
+            />
+            <div class="code" @click="again"><img :src="codeUrl" /></div>
+          </div>
+        </div>
+      </div>
+      <div class="tips">
+        <div v-if="current == 0" @click="current = 1">快速登录</div>
+        <div v-if="current == 1" @click="current = 0">账号登录</div>
+        <div @click="change_password">忘记密码</div>
+      </div>
+      <div class="logon" @click="loginMobile" v-if="current !== 0">登录</div>
+      <div class="logon" @click="submit" v-if="current === 0">登录</div>
+    </div>
+    <div class="bottom"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch, onMounted } from "vue";
+import { onLoad, onBackPress as uniOnBackPress } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+import {
+  loginH5,
+  loginMobile as loginMobileApi,
+  registerVerify,
+  register,
+  getUserInfo,
+  getUserOpenId,
+} from "@/api/user";
+import { getLogo, appAuth, appleLogin } from "@/api/public";
+import { VUE_APP_API_URL } from "@/utils";
+import { useSendCode } from "@/hooks/useSendCode";
+import Cache from "@/utils/cache";
+import { EXPIRES_TIME } from "@/config/cache";
+import { useToast } from "@/hooks/useToast.js";
+
+const appStore = useAppStore();
+const { Toast } = useToast();
+
+const BACK_URL = "login_back_url";
+
+// Reactive state
+const navList = ref(["快速登录", "账号登录"]);
+const current = ref(1);
+const account = ref("");
+const password = ref("");
+const captcha = ref("");
+const formItem = ref(1);
+const type = ref("login");
+const logoUrl = ref("");
+const keyCode = ref("");
+const codeUrl = ref("");
+const codeVal = ref("");
+const isShowCode = ref(false);
+const platform = ref("");
+const appLoginStatus = ref(false);
+const appUserInfo = ref(null);
+const appleLoginStatus = ref(false);
+const appleUserInfo = ref(null);
+const appleShow = ref(false);
+const invite_code = ref("");
+
+// Watch formItem to update type
+watch(formItem, (newVal) => {
+  type.value = newVal === 1 ? "login" : "register";
+});
+
+const { disabled, text, sendCode } = useSendCode();
+
+// Refresh captcha image
+const again = () => {
+  codeUrl.value = `${VUE_APP_API_URL}/sms_captcha?key=${
+    keyCode.value
+  }${Date.parse(new Date())}`;
+};
+
+// Get logo image
+const getLogoImage = async () => {
+  try {
+    const res = await getLogo();
+    console.log('=========')
+    logoUrl.value = res.data.logoUrl || "/static/images/logo2.png";
+  } catch (err) {
+    console.error(err);
+  }
+};
+
+const getWechatOpenid = async () => {
+  try {
+    const { code, errMsg } = await uni.login();
+    if (!errMsg) return uni.showToast({ title: errMsg, icon: "none" }); // debug
+    // if (!code) return uni.showToast({ title: "无code", icon: "none" }); // debug
+    console.log(code)
+    const res = await getUserOpenId({ code });
+    if (res.data?.code === 200 && res.data?.data?.openid) {
+      const openid = res.data.data.openid;
+
+      Cache.set("wxOpenid", openid);
+      appStore.SET_WX_OPENID(openid);
+    }
+  } catch (err) {
+    console.error("获取openid过程出错:", err);
+  }
+};
+
+// Mobile login
+const loginMobile = async () => {
+  if (!account.value)
+    return uni.showToast({ title: "请填写手机号码", icon: "none" });
+  if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
+    return uni.showToast({ title: "请输入正确的手机号码", icon: "none" });
+  if (!captcha.value)
+    return uni.showToast({ title: "请填写验证码", icon: "none" });
+  if (!/^[\w\d]+$/i.test(captcha.value))
+    return uni.showToast({ title: "请输入正确的验证码", icon: "none" });
+
+  try {
+    const res = await loginMobileApi({
+      phone: account.value,
+      captcha: captcha.value,
+      spread_spid: Cache.get("spread"),
+      invite_code: invite_code.value,
+    });
+    // 使用 LOGIN action 保存 token 到缓存
+    appStore.LOGIN({ token: res.data.token });
+    // 保存过期时间(7天后过期)
+    const expiresTime = Math.round(new Date() / 1000) + 7 * 24 * 60 * 60;
+    Cache.set(EXPIRES_TIME, expiresTime, 0);
+    await getUserInfoFn(res.data);
+  } catch (res) {
+    uni.showToast({ title: res.message, icon: "none" });
+  }
+};
+
+// Register
+const registerFn = async () => {
+  if (!account.value)
+    return uni.showToast({ title: "请填写手机号码", icon: "none" });
+  if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
+    return uni.showToast({ title: "请输入正确的手机号码", icon: "none" });
+  if (!captcha.value)
+    return uni.showToast({ title: "请填写验证码", icon: "none" });
+  if (!/^[\w\d]+$/i.test(captcha.value))
+    return uni.showToast({ title: "请输入正确的验证码", icon: "none" });
+  if (!password.value)
+    return uni.showToast({ title: "请填写密码", icon: "none" });
+  if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$/i.test(password.value))
+    return uni.showToast({ title: "您输入的密码过于简单", icon: "none" });
+
+  try {
+    const res = await register({
+      account: account.value,
+      captcha: captcha.value,
+      password: password.value,
+      spread: Cache.get("spread"),
+    });
+    uni.showToast({ title: res.message, icon: "none" });
+    formItem.value = 1;
+  } catch (res) {
+    uni.showToast({ title: res.message, icon: "none" });
+  }
+};
+
+// Send verification code
+const getCode = async () => {
+  if (!account.value)
+    return uni.showToast({ title: "请填写手机号码", icon: "none" });
+  if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
+    return uni.showToast({ title: "请输入正确的手机号码", icon: "none" });
+
+  if (formItem.value === 2) type.value = "register";
+  try {
+    const res = await registerVerify(account.value);
+    uni.showToast({ title: res.message, icon: "none" });
+    sendCode();
+  } catch (err) {
+    uni.showToast({ title: err.message, icon: "none" });
+  }
+};
+
+// Navigation tab switch
+const navTap = (index) => {
+  current.value = index;
+};
+
+// h5 login
+const submit = async () => {
+  if (!account.value) return Toast({ title: "请填写账号", icon: "none" });
+  if (!/^[\w\d]{5,16}$/i.test(account.value))
+    return Toast({ title: "请输入正确的账号", icon: "none" });
+  if (!password.value) return Toast({ title: "请填写密码", icon: "none" });
+
+  try {
+    const { data } = await loginH5({
+      account: account.value,
+      password: password.value,
+      spread: Cache.get("spread"),
+    });
+    appStore.LOGIN({ token: data.token });
+    // 保存过期时间(7天后过期)
+    const expiresTime = Math.round(new Date() / 1000) + 7 * 24 * 60 * 60;
+    Cache.set(EXPIRES_TIME, expiresTime, 0);
+
+    await getUserInfoFn(data);
+  } catch (err) {
+    console.log("submit error", err);
+    Toast({ title: err, icon: "none" });
+  }
+};
+
+const getUserInfoFn = async (data) => {
+  try {
+    appStore.SETUID(data.uid);
+    await getWechatOpenid();
+    const res = await getUserInfo();
+    appStore.UPDATE_USERINFO(res.data);
+    Toast({ title: "登录成功" });
+    backHome();
+  } catch (err) {
+    console.error(err);
+    uni.showToast({ title: err.msg, icon: "none" });
+  }
+};
+
+onLoad((options) => {
+  if (options?.invite_code) {
+    invite_code.value = options.invite_code;
+    uni.setStorageSync("inviteCode", options.invite_code);
+  } else {
+    invite_code.value = uni.getStorageSync("inviteCode") || "";
+  }
+  getLogoImage();
+});
+
+onMounted(() => {
+  uni.getDeviceInfo({
+    success(res) {
+      // 获取平台(如 "ios"、"android")
+      platform.value = res.platform.toLowerCase();
+      // 解析系统版本(以 iOS 为例,res.system 格式如 "iOS 16.5")
+      if (platform.value === "ios") {
+        const systemParts = res.system.split(" ");
+        if (systemParts.length > 1) {
+          const iosVersion = parseFloat(systemParts[1]);
+          // iOS 版本 >= 13 时显示 Apple 登录相关内容
+          if (iosVersion >= 13) {
+            appleShow.value = true;
+          }
+        }
+      }
+    },
+    fail(err) {
+      console.error("获取设备信息失败:", err);
+    },
+  });
+});
+
+const backHome = () => {
+  uni.switchTab({
+    url: "/pages/index/index",
+  });
+};
+const change_password = () => {
+  uni.navigateTo({
+    url: "/pages/change_password/change_password",
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+page {
+  background: #fff;
+}
+.appLogin {
+  margin-top: 60rpx;
+
+  .hds {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: 24rpx;
+    color: #b4b4b4;
+
+    .line {
+      width: 68rpx;
+      height: 1rpx;
+      background: #cccccc;
+    }
+
+    p {
+      margin: 0 20rpx;
+    }
+  }
+
+  .btn-wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-top: 30rpx;
+
+    .btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 68rpx;
+      height: 68rpx;
+      border-radius: 50%;
+    }
+
+    .apple-btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-left: 30rpx;
+      background: #000;
+      border-radius: 34rpx;
+      font-size: 40rpx;
+
+      .icon-s-pingguo {
+        color: #fff;
+        font-size: 40rpx;
+      }
+    }
+
+    .iconfont {
+      font-size: 40rpx;
+      color: #fff;
+    }
+
+    .wx {
+      margin-right: 30rpx;
+      background-color: #61c64f;
+    }
+
+    .mima {
+      background-color: #28b3e9;
+    }
+
+    .yanzheng {
+      background-color: #f89c23;
+    }
+  }
+}
+
+.code img {
+  width: 100%;
+  height: 100%;
+}
+
+.acea-row.row-middle {
+  input {
+    margin-left: 20rpx;
+    display: block;
+  }
+}
+
+.text-row {
+  font-size: 22rpx;
+  color: #999;
+}
+
+.login-wrapper {
+  padding: 0 30rpx;
+  height: 100vh;
+
+  .back_extend {
+    position: fixed;
+    top: 50px;
+    left: 10px;
+    font-size: 26rpx;
+    color: #999;
+  }
+
+  .shading {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+
+    /* #ifdef APP-VUE */
+    margin-top: 50rpx;
+    /* #endif */
+    /* #ifndef APP-VUE */
+
+    margin-top: 200rpx;
+    /* #endif */
+
+    image {
+      width: 180rpx;
+      height: 180rpx;
+    }
+  }
+
+  .whiteBg {
+    margin-top: 100rpx;
+
+    .list {
+      border-radius: 16rpx;
+      overflow: hidden;
+
+      .item {
+        border-bottom: 1px solid #f0f0f0;
+        background: #fff;
+
+        .row-middle {
+          position: relative;
+          padding: 16rpx 45rpx;
+
+          .texts {
+            flex: 1;
+            font-size: 28rpx;
+            height: 80rpx;
+            line-height: 80rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+          }
+
+          input {
+            flex: 1;
+            font-size: 28rpx;
+            height: 80rpx;
+            line-height: 80rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+          }
+
+          .code {
+            position: absolute;
+            right: 30rpx;
+            top: 50%;
+            color: #cd9933;
+            font-size: 26rpx;
+            z-index: 999;
+            transform: translateY(-50%);
+            border: 1px solid;
+            border-radius: 14rpx;
+            padding: 8rpx 10rpx;
+          }
+        }
+      }
+    }
+
+    .logon {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 86rpx;
+      margin-top: 80rpx;
+      background-color: $theme-color;
+      border-radius: 120rpx;
+      color: #ffffff;
+      font-size: 30rpx;
+    }
+
+    .tips {
+      margin: 0.9375rem;
+      text-align: center;
+      color: #999;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+    }
+  }
+}
+</style>

Разница между файлами не показана из-за своего большого размера
+ 1541 - 0
static/iconfont/iconfont-app.css


+ 404 - 0
static/iconfont/iconfont.css

@@ -0,0 +1,404 @@
+/* @font-face {
+  font-family: "iconfont";
+  src: url('//at.alicdn.com/t/c/font_4946742_sm8oydw8wql.woff2?t=1750991898915') format('woff2'),
+       url('//at.alicdn.com/t/c/font_4946742_sm8oydw8wql.woff?t=1750991898915') format('woff'),
+       url('//at.alicdn.com/t/c/font_4946742_sm8oydw8wql.ttf?t=1750991898915') format('truetype');
+} */
+
+@font-face {
+  font-family: "iconfont"; /* Project id 4946742 */
+  src: url('iconfont.woff2?t=1760592226193') format('woff2'),
+       url('iconfont.woff?t=1760592226193') format('woff'),
+       url('iconfont.ttf?t=1760592226193') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.icon-youjiantou:before {
+  content: "\e6e0";
+}
+.icon-iconfontscan:before {
+  content: "\e62f";
+}
+
+.icon-tixing:before {
+  content: "\e62e";
+}
+
+.icon-fanhui:before {
+  content: "\e606";
+}
+
+.icon-shouye1:before {
+  content: "\e62c";
+}
+
+.icon-24gf-clockCircle:before {
+  content: "\eb40";
+}
+
+.icon-sanjiaoxing:before {
+  content: "\e62b";
+}
+
+.icon-VIPhuiyuan:before {
+  content: "\e62a";
+}
+
+.icon-zhaokuan:before {
+  content: "\e628";
+}
+
+.icon-licai:before {
+  content: "\e602";
+}
+
+.icon-jiamengwomen:before {
+  content: "\e627";
+}
+
+.icon-bofang:before {
+  content: "\e633";
+}
+
+.icon-bofang1:before {
+  content: "\e636";
+}
+
+.icon-pingguozhifu:before {
+  content: "\e63f";
+}
+
+.icon-APPlepay:before {
+  content: "\e652";
+}
+
+.icon-apay:before {
+  content: "\e68c";
+}
+
+.icon-pingguozhifu1:before {
+  content: "\e630";
+}
+
+.icon-icon-pingguozhifu:before {
+  content: "\e632";
+}
+
+.icon-pingguozhifu2:before {
+  content: "\e826";
+}
+
+.icon-naozhong-F:before {
+  content: "\e635";
+}
+
+.icon-youjiantou:before {
+  content: "\e6e0";
+}
+
+.icon-iconfontscan:before {
+  content: "\e62f";
+}
+
+.icon-tixing:before {
+  content: "\e62e";
+}
+
+.icon-fanhui:before {
+  content: "\e606";
+}
+
+.icon-shouye1:before {
+  content: "\e62c";
+}
+
+.icon-24gf-clockCircle:before {
+  content: "\eb40";
+}
+
+.icon-sanjiaoxing:before {
+  content: "\e62b";
+}
+
+.icon-VIPhuiyuan:before {
+  content: "\e62a";
+}
+
+.icon-zhaokuan:before {
+  content: "\e628";
+}
+
+.icon-licai:before {
+  content: "\e602";
+}
+
+.icon-jiamengwomen:before {
+  content: "\e627";
+}
+
+.icon-shenhejujue:before {
+  content: "\e727";
+}
+
+.icon-changyonggongneng:before {
+  content: "\e672";
+}
+
+.icon-yujing:before {
+  content: "\e626";
+}
+
+.icon-beikehui:before {
+  content: "\e64c";
+}
+
+.icon-beike:before {
+  content: "\e6e7";
+}
+
+.icon-bank-card-fill:before {
+  content: "\e7ae";
+}
+
+.icon-yinhangqia1:before {
+  content: "\e936";
+}
+
+.icon-yinhangqia:before {
+  content: "\e873";
+}
+
+.icon-huangguan:before {
+  content: "\e6a7";
+}
+
+.icon-qianbao:before {
+  content: "\e625";
+}
+
+.icon-kefu2:before {
+  content: "\e65a";
+}
+
+.icon-yonghu:before {
+  content: "\e668";
+}
+
+.icon-caogaoxiang:before {
+  content: "\e717";
+}
+
+.icon-jiahao:before {
+  content: "\e624";
+}
+
+.icon-xinzeng:before {
+  content: "\e631";
+}
+
+.icon-shoucangxuanzhong:before {
+  content: "\e622";
+}
+
+.icon-shoucang:before {
+  content: "\e623";
+}
+
+.icon-fail:before {
+  content: "\e6d7";
+}
+
+.icon-success:before {
+  content: "\e65c";
+}
+
+.icon-zhifubao:before {
+  content: "\e621";
+}
+
+.icon-delivery-:before {
+  content: "\e629";
+}
+
+.icon-wechat:before {
+  content: "\e634";
+}
+
+.icon-balance:before {
+  content: "\e63d";
+}
+
+.icon-kefu1:before {
+  content: "\ec2e";
+}
+
+.icon-a-ziyuan97:before {
+  content: "\e619";
+}
+
+.icon-touzijintiao1:before {
+  content: "\e61c";
+}
+
+.icon-yuanbao:before {
+  content: "\e696";
+}
+
+.icon-qita:before {
+  content: "\e685";
+}
+
+.icon-youhuiquan1:before {
+  content: "\f37e";
+}
+
+.icon-tubiao_lipin:before {
+  content: "\e620";
+}
+
+.icon-a-ziyuan87:before {
+  content: "\e61f";
+}
+
+.icon-huiyuan:before {
+  content: "\e61d";
+}
+
+.icon-jinku:before {
+  content: "\e61e";
+}
+
+.icon-jicunjinxianxing:before {
+  content: "\e61b";
+}
+
+.icon-huiyuanzunxiangx:before {
+  content: "\e62d";
+}
+
+.icon-baoxianxiang:before {
+  content: "\e6cb";
+}
+
+.icon-huangjinshoushi:before {
+  content: "\e618";
+}
+
+.icon-xinpintuijian:before {
+  content: "\e61a";
+}
+
+.icon-wodedingdan1:before {
+  content: "\e662";
+}
+
+.icon-kabao:before {
+  content: "\e6e8";
+}
+
+.icon-youhuiquan:before {
+  content: "\e643";
+}
+
+.icon-icon-wodedingdan-shangpinxinxi:before {
+  content: "\e6f8";
+}
+
+.icon-wodedingdan-daitihuo:before {
+  content: "\e76d";
+}
+
+.icon-fenxiang:before {
+  content: "\e600";
+}
+
+.icon-xiaojinku:before {
+  content: "\e617";
+}
+
+.icon-viphuiyuan1:before {
+  content: "\e616";
+}
+
+.icon-kefu:before {
+  content: "\e614";
+}
+
+.icon-shezhi:before {
+  content: "\e615";
+}
+
+.icon-wode:before {
+  content: "\e613";
+}
+
+.icon-beibishangcheng:before {
+  content: "\e607";
+}
+
+.icon-jicunjin:before {
+  content: "\e608";
+}
+
+.icon-paihangbang:before {
+  content: "\e609";
+}
+
+.icon-chaojigongju:before {
+  content: "\e60a";
+}
+
+.icon-cebianlan:before {
+  content: "\e60b";
+}
+
+.icon-kefuxiaoxi:before {
+  content: "\e60c";
+}
+
+.icon-kajuan:before {
+  content: "\e60d";
+}
+
+.icon-dianzanxuanzhong:before {
+  content: "\e60e";
+}
+
+.icon-sousuo:before {
+  content: "\e60f";
+}
+
+.icon-gouwuche1:before {
+  content: "\e610";
+}
+
+.icon-dianzan:before {
+  content: "\e611";
+}
+
+.icon-wodedingdan:before {
+  content: "\e612";
+}
+
+.icon-shouye:before {
+  content: "\e601";
+}
+
+.icon-zhongjiantubiao:before {
+  content: "\e603";
+}
+
+.icon-gouwuche:before {
+  content: "\e604";
+}
+
+.icon-xiaoxi:before {
+  content: "\e605";
+}
+

BIN
static/iconfont/iconfont.ttf


BIN
static/iconfont/iconfont.woff


BIN
static/iconfont/iconfont.woff2


BIN
static/images/lingyhj.png


BIN
static/images/shandian.png


BIN
static/images/weiling.png


BIN
static/images/yhjsy.png


BIN
static/logo1.png


+ 15 - 0
stores/slider.js

@@ -0,0 +1,15 @@
+// 定义侧边栏显示与否
+import { defineStore } from "pinia"; // 若用Pinia,若用Vuex类似
+
+export const useSliderStore = defineStore("slider", {
+  state: () => ({
+    // 其他原有状态...
+    sidebarShow: false, // 新增:侧边栏显示状态(全局存储)
+  }),
+  actions: {
+    // 新增:更新侧边栏显示状态的方法
+    updateSidebarShow(show) {
+      this.sidebarShow = show;
+    },
+  },
+});