Compare commits
34 Commits
be96b28828
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6deef6cdce | |||
| e894689918 | |||
| 26e57e4891 | |||
| 32fa3e9bd2 | |||
| 4002f91a54 | |||
| a2fadc57b8 | |||
| b251c899ca | |||
| cdf0bc5a01 | |||
| 7a490f0ffc | |||
| 34148c6084 | |||
| 9b6b76f747 | |||
| 6ea50b824f | |||
| 72e481c6c5 | |||
| 124beb0666 | |||
| 078e0bc8ab | |||
| 1739401cb8 | |||
| 3afa2e8c10 | |||
| 7d1083bf5c | |||
| 41f6f612fa | |||
| 3b8c991e1a | |||
| 46bbce84aa | |||
| 73e2580c43 | |||
| 9312eceb94 | |||
| 982e78d21e | |||
| 6414902057 | |||
| 490fbb7399 | |||
| 15da975168 | |||
| 345b543cfd | |||
| a98fb14ae7 | |||
| 2c00299634 | |||
| 230bb76f63 | |||
| deb949600b | |||
| 3678cfe35f | |||
| 47594ed095 |
6836
package-lock.json
generated
6836
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -72,8 +72,11 @@
|
|||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"flyio": "^0.6.2",
|
"flyio": "^0.6.2",
|
||||||
|
"marked": "^4.3.0",
|
||||||
"moment": "^2.29.3",
|
"moment": "^2.29.3",
|
||||||
|
"mp-html": "^2.5.2",
|
||||||
"node-sass": "4.14.1",
|
"node-sass": "4.14.1",
|
||||||
|
"regenerator-runtime": "^0.14.1",
|
||||||
"sass": "1.26.2",
|
"sass": "1.26.2",
|
||||||
"sass-loader": "8.0.2",
|
"sass-loader": "8.0.2",
|
||||||
"sass-resources-loader": "^2.2.4",
|
"sass-resources-loader": "^2.2.4",
|
||||||
|
|||||||
@ -107,6 +107,88 @@ export function getHomeServices(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 AI 顾问工具列表(无参数)
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function getAiConsultServices() {
|
||||||
|
return request({
|
||||||
|
url: URL.AI_CONSULT_SERVICES,
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 AI 顾问概览(剩余积分、体验人数)(无参数)
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function getAiConsultOverview() {
|
||||||
|
return request({
|
||||||
|
url: URL.AI_CONSULT_OVERVIEW,
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 AI 顾问消息
|
||||||
|
* @param {Object} data - 请求体
|
||||||
|
* @param {number} [data.service_id] - AI 服务 ID,新会话时必传
|
||||||
|
* @param {number} [data.session_id] - 会话 ID,继续已有会话时传
|
||||||
|
* @param {Array} data.parts - 消息内容数组,至少 1 项
|
||||||
|
* @param {string} data.parts[].part_type - "text" | "image" | "audio"
|
||||||
|
* @param {string} [data.parts[].text_content] - part_type=text 时必填
|
||||||
|
* @param {string} [data.parts[].file_url] - part_type=image/audio 时必填
|
||||||
|
* @param {string} [data.parts[].mime_type] - 文件 MIME 类型
|
||||||
|
* @param {number} [data.parts[].duration_ms] - 音频时长,毫秒
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function sendAiConsultMessage(data) {
|
||||||
|
return request({
|
||||||
|
url: URL.AI_CONSULT_MESSAGES_SEND,
|
||||||
|
method: 'POST',
|
||||||
|
data: data || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 AI 顾问会话消息列表
|
||||||
|
* @param {Object} params - 请求参数,如 { session_id: string }
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function getAiConsultSessionMessages(params) {
|
||||||
|
return request({
|
||||||
|
url: URL.AI_CONSULT_SESSION_MESSAGES,
|
||||||
|
method: 'POST',
|
||||||
|
data: params || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 AI 顾问历史会话列表(按日期分组)
|
||||||
|
* @param {Object} params - 请求参数 { service_id: number, pet_id: number|string }
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function getAiConsultServiceSessions(params) {
|
||||||
|
return request({
|
||||||
|
url: URL.AI_CONSULT_SERVICE_SESSIONS,
|
||||||
|
method: 'POST',
|
||||||
|
data: params || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 AI 顾问最近一条会话的消息(根据 service_id + pet_id)
|
||||||
|
* @param {Object} params - { service_id: number, pet_id: number|string }
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function getAiConsultLatestSessionMessages(params) {
|
||||||
|
return request({
|
||||||
|
url: URL.AI_CONSULT_LATEST_SESSION_MESSAGES,
|
||||||
|
method: 'POST',
|
||||||
|
data: params || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取领养列表
|
* 获取领养列表
|
||||||
* @param {Object} data - 请求参数 { page: 1, page_size: 10 }
|
* @param {Object} data - 请求参数 { page: 1, page_size: 10 }
|
||||||
|
|||||||
@ -2,14 +2,15 @@ import request from "../utils/request";
|
|||||||
import { LOGIN, GET_PHONE, USER_SHARE,USER_WALLET,RECHARGE_WALLET,USER_WXPAY,USER_TRANSACTION,USER_ADDITIONAL,MEMBER_TYPES,
|
import { LOGIN, GET_PHONE, USER_SHARE,USER_WALLET,RECHARGE_WALLET,USER_WXPAY,USER_TRANSACTION,USER_ADDITIONAL,MEMBER_TYPES,
|
||||||
USER_RECHARGE,USER_REDEEM,USER_HolderList,USER_MEMBERSHIP,USER_BINDPETS,USER_PETBINDING,USER_DISCOUNTFEE,USER_COUPONLIST,CANCEL_PET_ORDER,
|
USER_RECHARGE,USER_REDEEM,USER_HolderList,USER_MEMBERSHIP,USER_BINDPETS,USER_PETBINDING,USER_DISCOUNTFEE,USER_COUPONLIST,CANCEL_PET_ORDER,
|
||||||
GET_VIP_PRICE,POINTS_RECHARGE_LIST,POINTS_DONATE,POINTS_RECORDS,POINTS_RANK,OSS_STS,DONATION_SUMMARY,
|
GET_VIP_PRICE,POINTS_RECHARGE_LIST,POINTS_DONATE,POINTS_RECORDS,POINTS_RANK,OSS_STS,DONATION_SUMMARY,
|
||||||
USER_DISPATCHFEE,CANCEL_MALL_ORDER
|
USER_DISPATCHFEE,CANCEL_MALL_ORDER,DOUY_REVIEW,CANCEL_NOMO_ORDER,LOGIN_PHONE
|
||||||
} from "./url";
|
} from "./url";
|
||||||
// 微信登陆
|
// 微信登陆
|
||||||
export const getCodeByWxLogin = () => {
|
export const getCodeByWxLogin = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.login({
|
tt.login({
|
||||||
provider: "weixin",
|
provider: "toutiao",
|
||||||
success: function (loginRes) {
|
success: function (loginRes) {
|
||||||
|
console.log(loginRes,'?1?')
|
||||||
resolve(loginRes.code);
|
resolve(loginRes.code);
|
||||||
},
|
},
|
||||||
fail: function (err) {
|
fail: function (err) {
|
||||||
@ -22,7 +23,7 @@ export const getCodeByWxLogin = () => {
|
|||||||
import Store from "../store";
|
import Store from "../store";
|
||||||
|
|
||||||
// 登录鉴权
|
// 登录鉴权
|
||||||
export const login = (phone) => {
|
export const login = ( phone) => {
|
||||||
return getCodeByWxLogin().then((code) => {
|
return getCodeByWxLogin().then((code) => {
|
||||||
// 从 vuex 中获取 referrerID(如果通过二维码扫描进入)
|
// 从 vuex 中获取 referrerID(如果通过二维码扫描进入)
|
||||||
const referrerID = Store.state.user?.referrerID || 0;
|
const referrerID = Store.state.user?.referrerID || 0;
|
||||||
@ -32,11 +33,10 @@ export const login = (phone) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
code: code,
|
code: code,
|
||||||
// yaoqing_code: inviteCode || null,
|
|
||||||
phone: phone || null,
|
phone: phone || null,
|
||||||
source: "wechat",
|
source: 'douyin',
|
||||||
referrerID: Number(referrerID) || 0,
|
referrerID: 0,
|
||||||
referrerType: "wechat"
|
referrerType: 'douyin'
|
||||||
},
|
},
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
// 登录接口使用完 referrerID 后,清除一次,避免重复使用
|
// 登录接口使用完 referrerID 后,清除一次,避免重复使用
|
||||||
@ -48,12 +48,13 @@ export const login = (phone) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 获取手机号
|
// 获取手机号
|
||||||
export const getPhone = (code) => {
|
export const getPhone = (phoneCode, loginCode) => {
|
||||||
return request({
|
return request({
|
||||||
url: GET_PHONE,
|
url: GET_PHONE,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
code: code
|
code: phoneCode,
|
||||||
|
loginCode: loginCode
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -68,6 +69,18 @@ export const userShare = (id) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const login_phone = (phone) => {
|
||||||
|
return request({
|
||||||
|
url: LOGIN_PHONE,
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
phone
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// 用户钱包
|
// 用户钱包
|
||||||
export const userWllet = (id) => {
|
export const userWllet = (id) => {
|
||||||
return request({
|
return request({
|
||||||
@ -157,6 +170,15 @@ export const getDonationSummary = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 抖音审核
|
||||||
|
export const getdouyReview = (data) => {
|
||||||
|
return request({
|
||||||
|
url: DOUY_REVIEW,
|
||||||
|
method: "POST",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 获取OSS配置
|
// 获取OSS配置
|
||||||
export const getOssConfig = () => {
|
export const getOssConfig = () => {
|
||||||
return request({
|
return request({
|
||||||
@ -211,6 +233,15 @@ export const cancelPetOrderMall = (data) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 未支付取消商城订单
|
||||||
|
export const cancelUnpaid = (data) => {
|
||||||
|
return request({
|
||||||
|
url: CANCEL_NOMO_ORDER,
|
||||||
|
method: "POST",
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 绑定宠物列表
|
// 绑定宠物列表
|
||||||
export const bindPets = (data) => {
|
export const bindPets = (data) => {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@ -115,12 +115,12 @@ export const updateCartSelect = ({ is_select,cart_id }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 删除购物车
|
// 删除购物车
|
||||||
export const deleteCart = ({ cart_id }) => {
|
export const deleteCart = ({ id }) => {
|
||||||
return request({
|
return request({
|
||||||
url: DELETE_CART,
|
url: DELETE_CART,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
cart_id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -156,37 +156,13 @@ export const createOrder = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建购物车订单
|
// 创建抖音订单
|
||||||
export const createCartOrder = ({
|
export const createCartOrder = ({ id }) => {
|
||||||
type = "" ,
|
|
||||||
original_price = "",
|
|
||||||
actual_price = "",
|
|
||||||
reduction_amount = 0,
|
|
||||||
pay_amount = "",
|
|
||||||
pay_type = "",
|
|
||||||
address_id = "",
|
|
||||||
address = "",
|
|
||||||
name = "",
|
|
||||||
phone = "",
|
|
||||||
note = "",
|
|
||||||
items = []
|
|
||||||
}) => {
|
|
||||||
return request({
|
return request({
|
||||||
url: CREATE_CART_ORDER,
|
url: CREATE_CART_ORDER,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
type,
|
id
|
||||||
original_price,
|
|
||||||
actual_price,
|
|
||||||
reduction_amount,
|
|
||||||
pay_amount,
|
|
||||||
pay_type,
|
|
||||||
address_id,
|
|
||||||
address,
|
|
||||||
name,
|
|
||||||
phone,
|
|
||||||
note,
|
|
||||||
items
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export const RECHARGE_WALLET = '/wallet/details'
|
|||||||
export const USER_WALLET = '/wallet/info'
|
export const USER_WALLET = '/wallet/info'
|
||||||
|
|
||||||
//钱包充值接口
|
//钱包充值接口
|
||||||
export const USER_WXPAY = '/wxpay/jsapi'
|
export const USER_WXPAY = '/ttpay/jsapi'
|
||||||
//钱包充值金额
|
//钱包充值金额
|
||||||
export const USER_RECHARGE = '/recharge/bonus-list'
|
export const USER_RECHARGE = '/recharge/bonus-list'
|
||||||
//积分充值列表
|
//积分充值列表
|
||||||
@ -53,12 +53,16 @@ export const ADDITIONAL_SERVICES_APPEND = '/order/additional_services/append'
|
|||||||
// 取消预约订单接口
|
// 取消预约订单接口
|
||||||
export const CANCEL_PET_ORDER = '/order/pet/cancel'
|
export const CANCEL_PET_ORDER = '/order/pet/cancel'
|
||||||
|
|
||||||
|
// 抖音审核接口
|
||||||
|
export const DOUY_REVIEW = '/douyin/goods/order/appointment'
|
||||||
|
|
||||||
|
// 抖音未支付订单取消接口
|
||||||
|
export const CANCEL_NOMO_ORDER = '/douyin/goods/order/cancel'
|
||||||
|
|
||||||
|
export const LOGIN_PHONE = '/api/v1/auth/login'
|
||||||
|
|
||||||
// 取消商城接口
|
// 取消商城接口
|
||||||
export const CANCEL_MALL_ORDER = '/product/order/cancel'
|
export const CANCEL_MALL_ORDER = '/douyin/goods/order/refund'
|
||||||
|
|
||||||
// 卡包列表接口
|
// 卡包列表接口
|
||||||
export const USER_HolderList = '/membership/instances'
|
export const USER_HolderList = '/membership/instances'
|
||||||
@ -186,7 +190,7 @@ export const HOME_ORDERS_CANCEL = "/home/orders/cancel";
|
|||||||
// 校验节假日费用
|
// 校验节假日费用
|
||||||
export const CHECK_HOLIDAY_FEE = "/check_holiday_fee";
|
export const CHECK_HOLIDAY_FEE = "/check_holiday_fee";
|
||||||
//支付订单
|
//支付订单
|
||||||
export const PAY_ORDER = "/wxpay/jsapi";
|
export const PAY_ORDER = "/ttpay/jsapi";
|
||||||
|
|
||||||
//取消订单
|
//取消订单
|
||||||
export const CANCEL_ORDER = "/app/chongwu_order/order_quxiao";
|
export const CANCEL_ORDER = "/app/chongwu_order/order_quxiao";
|
||||||
@ -197,9 +201,9 @@ export const ADD_SERVICE_FEE = "/app/chongwu_order/order_fuwufei";
|
|||||||
// 商品分类
|
// 商品分类
|
||||||
export const GET_GOODS_CATEGORY = "/product/type/list";
|
export const GET_GOODS_CATEGORY = "/product/type/list";
|
||||||
// 商品列表
|
// 商品列表
|
||||||
export const GET_GOODS_LIST = "/product/list";
|
export const GET_GOODS_LIST = "/douyin/goods/product/online/query";
|
||||||
// 商品详情
|
// 商品详情
|
||||||
export const GET_GOODS_DETAIL = "/product/detail";
|
export const GET_GOODS_DETAIL = "/douyin/goods/product/detail";
|
||||||
|
|
||||||
// 获取优惠券列表
|
// 获取优惠券列表
|
||||||
export const GET_COUPON_LIST = "/app/coupon/coupon_list";
|
export const GET_COUPON_LIST = "/app/coupon/coupon_list";
|
||||||
@ -228,18 +232,18 @@ export const UPDATE_CART_NUM = "/cart/update";
|
|||||||
// 选择购物车商品
|
// 选择购物车商品
|
||||||
export const UPDATE_CART_SELECT = "/cart/select/all";
|
export const UPDATE_CART_SELECT = "/cart/select/all";
|
||||||
// 删除购物车
|
// 删除购物车
|
||||||
export const DELETE_CART = "/product/cart/delete";
|
export const DELETE_CART = "/douyin/goods/order/refund";
|
||||||
|
|
||||||
// 创建订单
|
// 创建订单
|
||||||
export const CREATE_ORDER_NEW = "/product/order/create";
|
export const CREATE_ORDER_NEW = "/product/order/create";
|
||||||
// 创建购物车订单
|
// 创建订单
|
||||||
export const CREATE_CART_ORDER = "/product/order/create";
|
export const CREATE_CART_ORDER = "/douyin/goods/order/create";
|
||||||
// 订单支付
|
// 订单支付
|
||||||
export const PAY_ORDER_NEW = "/wxpay/jsapi";
|
export const PAY_ORDER_NEW = "/ttpay/jsapi";
|
||||||
// 订单列表
|
// 订单列表
|
||||||
export const SHOP_ORDER_LIST = "/product/order/list";
|
export const SHOP_ORDER_LIST = "/douyin/goods/order/list";
|
||||||
// 订单详情
|
// 订单详情
|
||||||
export const SHOP_ORDER_DETAILS = "/product/order/show";
|
export const SHOP_ORDER_DETAILS = "/douyin/goods/order/show";
|
||||||
// 取消订单
|
// 取消订单
|
||||||
export const SHOP_ORDER_CANCEL = '/app/order/order_quxiao'
|
export const SHOP_ORDER_CANCEL = '/app/order/order_quxiao'
|
||||||
// 提醒发货
|
// 提醒发货
|
||||||
@ -304,3 +308,23 @@ export const HOMETRAINING_ORDERS_CANCEL = '/hometraining/orders/cancel'
|
|||||||
// 撤销申请(领养申请)
|
// 撤销申请(领养申请)
|
||||||
export const ADOPTIONS_PET_APPLY_CANCEL = '/adoptions/pet/apply/cancel'
|
export const ADOPTIONS_PET_APPLY_CANCEL = '/adoptions/pet/apply/cancel'
|
||||||
|
|
||||||
|
// AI 顾问工具列表(无参数)
|
||||||
|
export const AI_CONSULT_SERVICES = '/ai/consult/services'
|
||||||
|
|
||||||
|
// AI 顾问概览:剩余积分 & 体验人数(无参数)
|
||||||
|
export const AI_CONSULT_OVERVIEW = '/ai/consult/summary'
|
||||||
|
|
||||||
|
// AI 顾问发送消息
|
||||||
|
export const AI_CONSULT_MESSAGES_SEND = '/ai/consult/messages/send'
|
||||||
|
|
||||||
|
// AI 顾问获取会话消息列表
|
||||||
|
export const AI_CONSULT_SESSION_MESSAGES = '/ai/consult/session/messages'
|
||||||
|
|
||||||
|
// AI 顾问获取历史会话列表(按日期分组)
|
||||||
|
export const AI_CONSULT_SERVICE_SESSIONS = '/ai/consult/service/sessions'
|
||||||
|
|
||||||
|
// AI 顾问获取最近一条会话消息(根据 service_id + pet_id)
|
||||||
|
export const AI_CONSULT_LATEST_SESSION_MESSAGES = '/ai/consult/service/latest-session/messages'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
188
src/components/ContactPopupModal.vue
Normal file
188
src/components/ContactPopupModal.vue
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<view class="contact-popup-modal" v-if="visible">
|
||||||
|
<view class="modal-mask" @click="handleCancel"></view>
|
||||||
|
<view class="modal-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">请添加客服</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-body">
|
||||||
|
<view class="wechat-info-box">
|
||||||
|
<text class="wechat-code">{{ weChatCode }}</text>
|
||||||
|
<view class="copy-btn" @click="handleCopy">
|
||||||
|
<text class="copy-text">复制</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<view class="cancel-btn" @click="handleCancel">取消</view>
|
||||||
|
<view class="confirm-btn" @click="handleConfirm">确认</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ContactPopupModal',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
weChatCode: {
|
||||||
|
type: String,
|
||||||
|
default: 'Wagoo2025'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['cancel', 'confirm', 'close'],
|
||||||
|
methods: {
|
||||||
|
handleCopy() {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: this.weChatCode,
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '微信号已复制',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('复制失败', err)
|
||||||
|
uni.showModal({
|
||||||
|
title: '复制失败',
|
||||||
|
content: '微信号:' + this.weChatCode + '\n\n请手动输入添加',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '知道了',
|
||||||
|
success: () => {
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$emit('cancel')
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.handleCopy()
|
||||||
|
this.$emit('confirm')
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.contact-popup-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
width: 600rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: modalFadeIn 0.3s ease-out;
|
||||||
|
|
||||||
|
@keyframes modalFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 40rpx 32rpx 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 0 32rpx 40rpx;
|
||||||
|
|
||||||
|
.wechat-info-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
|
||||||
|
.wechat-code {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
|
||||||
|
.copy-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ff19a0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 32rpx 40rpx;
|
||||||
|
gap: 24rpx;
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #ff19a0;
|
||||||
|
border: 2rpx solid #ff19a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #ff19a0;
|
||||||
|
color: #fff;
|
||||||
|
border: 2rpx solid #ff19a0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
490
src/components/ContactSelectModal.vue
Normal file
490
src/components/ContactSelectModal.vue
Normal file
@ -0,0 +1,490 @@
|
|||||||
|
<template>
|
||||||
|
<view class="contact-select-modal" v-if="visible">
|
||||||
|
<view class="modal-mask" @click="handleClose"></view>
|
||||||
|
<view class="modal-content">
|
||||||
|
<view class="service-time">
|
||||||
|
<text>客服时间:{{ serviceTime }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="option-list">
|
||||||
|
<!-- 在线咨询商家 -->
|
||||||
|
<button
|
||||||
|
v-if="!hasCustomCallback"
|
||||||
|
class="option-item im-btn"
|
||||||
|
open-type="im"
|
||||||
|
:data-im-id="imId"
|
||||||
|
@im="imCallback"
|
||||||
|
@error="onimError"
|
||||||
|
>
|
||||||
|
<view class="option-icon online-icon">
|
||||||
|
<text class="icon-text">💬</text>
|
||||||
|
</view>
|
||||||
|
<view class="option-info">
|
||||||
|
<text class="option-title">在线咨询商家</text>
|
||||||
|
<text class="option-desc">在线客服实时与您沟通</text>
|
||||||
|
</view>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-else
|
||||||
|
class="option-item"
|
||||||
|
@click="handleOnlineConsult"
|
||||||
|
>
|
||||||
|
<view class="option-icon online-icon">
|
||||||
|
<text class="icon-text">💬</text>
|
||||||
|
</view>
|
||||||
|
<view class="option-info">
|
||||||
|
<text class="option-title">在线咨询商家</text>
|
||||||
|
<text class="option-desc">在线客服实时与您沟通</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商家客服电话 -->
|
||||||
|
<view class="option-item" @click="handlePhoneCall">
|
||||||
|
<view class="option-icon phone-icon">
|
||||||
|
<text class="icon-text">📞</text>
|
||||||
|
</view>
|
||||||
|
<view class="option-info">
|
||||||
|
<text class="option-title">商家客服电话</text>
|
||||||
|
<text class="option-desc">下单咨询、服务投诉等反馈</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 隐私协议弹窗(新增) -->
|
||||||
|
<view class="privacy-modal" v-if="showPrivacyModal" @click.stop>
|
||||||
|
<view class="privacy-modal-content">
|
||||||
|
<view class="privacy-modal-title">隐私保护提示</view>
|
||||||
|
<view class="privacy-modal-text">
|
||||||
|
请阅读并同意
|
||||||
|
<text class="privacy-link" @click="openPrivacyContract">《用户隐私保护协议》</text>
|
||||||
|
后,方可使用拨打电话功能
|
||||||
|
</view>
|
||||||
|
<button
|
||||||
|
class="privacy-agree-btn"
|
||||||
|
open-type="agreePrivacyAuthorization"
|
||||||
|
@agreeprivacyauthorization="onAgreePrivacy"
|
||||||
|
>
|
||||||
|
同意并继续
|
||||||
|
</button>
|
||||||
|
<button class="privacy-cancel-btn" @click="onDisagreePrivacy">
|
||||||
|
暂不同意
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ContactSelectModal',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
serviceTime: {
|
||||||
|
type: String,
|
||||||
|
default: '9:00——18:30'
|
||||||
|
},
|
||||||
|
servicePhone: {
|
||||||
|
type: String,
|
||||||
|
default: '13641827576'
|
||||||
|
},
|
||||||
|
imId: {
|
||||||
|
type: String,
|
||||||
|
default: '42747510730'
|
||||||
|
},
|
||||||
|
hasCustomCallback: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['close', 'phoneCall', 'imSuccess', 'imError', 'onlineConsult'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showPrivacyModal: false, // 隐私协议弹窗显示状态
|
||||||
|
pendingPhoneNumber: null // 暂存的待拨打的号码
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 监听弹窗关闭,重置隐私弹窗状态
|
||||||
|
visible(newVal) {
|
||||||
|
if (!newVal) {
|
||||||
|
this.showPrivacyModal = false
|
||||||
|
this.pendingPhoneNumber = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose() {
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOnlineConsult() {
|
||||||
|
this.$emit('onlineConsult')
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
|
||||||
|
imCallback(e) {
|
||||||
|
console.log('跳转IM客服成功', e.detail)
|
||||||
|
this.$emit('imSuccess', e)
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
|
||||||
|
onimError(e) {
|
||||||
|
console.log('拉起IM客服失败', e.detail)
|
||||||
|
this.$emit('imError', e)
|
||||||
|
uni.showToast({
|
||||||
|
title: '客服功能暂不可用',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// ========== 隐私协议相关方法 ==========
|
||||||
|
|
||||||
|
// 打开隐私协议详情
|
||||||
|
openPrivacyContract() {
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
tt.openPrivacyContract({
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('打开隐私协议失败', err)
|
||||||
|
uni.showToast({
|
||||||
|
title: '无法打开协议',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-TOUTIAO
|
||||||
|
uni.showToast({
|
||||||
|
title: '请在设置中查看隐私协议',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
// 用户同意隐私协议
|
||||||
|
onAgreePrivacy(e) {
|
||||||
|
console.log('用户已同意隐私协议', e)
|
||||||
|
this.showPrivacyModal = false
|
||||||
|
uni.showToast({
|
||||||
|
title: '已授权',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
// 同意后,执行之前暂存的拨号请求
|
||||||
|
if (this.pendingPhoneNumber) {
|
||||||
|
this.doMakePhoneCall(this.pendingPhoneNumber)
|
||||||
|
this.pendingPhoneNumber = null
|
||||||
|
// 拨号后关闭选择弹窗
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 用户拒绝隐私协议
|
||||||
|
onDisagreePrivacy() {
|
||||||
|
this.showPrivacyModal = false
|
||||||
|
this.pendingPhoneNumber = null
|
||||||
|
uni.showToast({
|
||||||
|
title: '未授权,无法使用拨号功能',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查隐私授权状态(抖音专用)
|
||||||
|
checkPrivacyAuthorization(phoneNumber, callback) {
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
tt.getPrivacySetting({
|
||||||
|
success: (res) => {
|
||||||
|
console.log('隐私授权状态:', res)
|
||||||
|
if (res.needAuthorization) {
|
||||||
|
// 未授权,显示隐私弹窗,并暂存电话号码
|
||||||
|
this.pendingPhoneNumber = phoneNumber
|
||||||
|
this.showPrivacyModal = true
|
||||||
|
callback && callback(false)
|
||||||
|
} else {
|
||||||
|
// 已授权,直接拨号
|
||||||
|
callback && callback(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('获取隐私设置失败', err)
|
||||||
|
// 获取失败时,直接尝试拨号(降级处理)
|
||||||
|
callback && callback(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-TOUTIAO
|
||||||
|
// 非抖音平台,直接拨号
|
||||||
|
callback && callback(true)
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
// 实际拨号方法
|
||||||
|
doMakePhoneCall(phoneNumber) {
|
||||||
|
if (!phoneNumber) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '客服电话暂未设置',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber: phoneNumber,
|
||||||
|
success: () => {
|
||||||
|
console.log('拨打电话成功')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('拨打电话失败', err)
|
||||||
|
// 失败时显示客服电话供用户手动拨打
|
||||||
|
uni.showModal({
|
||||||
|
title: '客服电话',
|
||||||
|
content: phoneNumber,
|
||||||
|
confirmText: '复制',
|
||||||
|
cancelText: '知道了',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: phoneNumber,
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '电话已复制',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// ========== 修改后的拨号入口 ==========
|
||||||
|
|
||||||
|
handlePhoneCall() {
|
||||||
|
const phoneNumber = this.servicePhone
|
||||||
|
|
||||||
|
// 先触发父组件的 phoneCall 事件
|
||||||
|
this.$emit('phoneCall', phoneNumber)
|
||||||
|
|
||||||
|
if (!phoneNumber) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '客服电话暂未设置',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查隐私授权状态后再拨号
|
||||||
|
this.checkPrivacyAuthorization(phoneNumber, (authorized) => {
|
||||||
|
if (authorized) {
|
||||||
|
this.doMakePhoneCall(phoneNumber)
|
||||||
|
// 已授权拨号后,关闭选择弹窗
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
// 未授权时,checkPrivacyAuthorization 已经显示了隐私弹窗
|
||||||
|
// 此时不关闭选择弹窗,让隐私弹窗能够正常显示
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.contact-select-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
width: 600rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: modalFadeIn 0.3s ease-out;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
|
||||||
|
@keyframes modalFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-time {
|
||||||
|
padding: 40rpx 32rpx 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-list {
|
||||||
|
padding: 0 32rpx 20rpx;
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
background: none;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
border-top: none;
|
||||||
|
outline: none;
|
||||||
|
line-height: normal;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-icon {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
|
||||||
|
&.online-icon {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.phone-icon {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
font-size: 36rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.option-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 隐私协议弹窗样式(新增) ==========
|
||||||
|
.privacy-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-modal-content {
|
||||||
|
width: 560rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 48rpx 32rpx 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-modal-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-modal-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-link {
|
||||||
|
color: #007aff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-agree-btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-cancel-btn {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
164
src/components/DraggableContact.vue
Normal file
164
src/components/DraggableContact.vue
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view
|
||||||
|
class="draggable-contact"
|
||||||
|
:style="{ right: right + 'rpx', bottom: bottom + 'rpx' }"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<view class="click-area">
|
||||||
|
<image class="contact-icon" :src="iconSrc" mode="aspectFill" />
|
||||||
|
<view class="contact-btn fs-20" v-if="showText">
|
||||||
|
{{ text }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 联系客服弹窗 -->
|
||||||
|
<ContactSelectModal
|
||||||
|
:visible="showContactModalFlag"
|
||||||
|
@close="closeContactModal"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ContactSelectModal from './ContactSelectModal.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DraggableContact',
|
||||||
|
components: {
|
||||||
|
ContactSelectModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 图标 URL
|
||||||
|
iconSrc: {
|
||||||
|
type: String,
|
||||||
|
default: 'https://static.wagoo.pet/statics/supportStaff.png'
|
||||||
|
},
|
||||||
|
// 是否显示文字
|
||||||
|
showText: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 按钮文字
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: '联系客服'
|
||||||
|
},
|
||||||
|
// 初始底部距离(rpx)
|
||||||
|
initialBottom: {
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
|
},
|
||||||
|
// 点击回调,不传则使用默认的微信客服弹窗
|
||||||
|
onClick: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imId: "42747510730",
|
||||||
|
right: 0,
|
||||||
|
bottom: this.initialBottom,
|
||||||
|
showContactModalFlag: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 如果没有传入 initialBottom,使用默认计算值
|
||||||
|
if (this.initialBottom === 200) {
|
||||||
|
this.initPosition()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化位置
|
||||||
|
initPosition() {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
const windowHeight = systemInfo.windowHeight
|
||||||
|
// 计算底部距离:35vh 位置
|
||||||
|
const vh35InRpx = (windowHeight * 0.35 / windowHeight) * 750
|
||||||
|
this.bottom = vh35InRpx - 130 - 48 + 110 // 往上挪 50px
|
||||||
|
this.right = 20 // 往左边挪 10px
|
||||||
|
},
|
||||||
|
// 点击处理
|
||||||
|
handleClick(e) {
|
||||||
|
// console.log('[DraggableContact] handleClick called, event:', e)
|
||||||
|
// 优先使用传入的回调,否则使用默认弹窗
|
||||||
|
if (this.onClick) {
|
||||||
|
console.log('[DraggableContact] calling onClick callback')
|
||||||
|
try {
|
||||||
|
this.onClick()
|
||||||
|
} catch (err) {
|
||||||
|
// console.error('[DraggableContact] onClick callback error:', err)
|
||||||
|
// 回调执行失败时使用默认弹窗
|
||||||
|
this.showContactModal()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('[DraggableContact] showing default contact modal')
|
||||||
|
// 默认行为:显示添加客服弹窗
|
||||||
|
this.showContactModal()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 显示联系客服弹窗
|
||||||
|
showContactModal() {
|
||||||
|
this.showContactModalFlag = true
|
||||||
|
},
|
||||||
|
// 关闭联系客服弹窗
|
||||||
|
closeContactModal() {
|
||||||
|
this.showContactModalFlag = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.draggable-contact {
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
z-index: 100000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
touch-action: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
line-height: normal;
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-icon {
|
||||||
|
width: 66rpx;
|
||||||
|
height: 66rpx;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-btn {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #FF19A0;
|
||||||
|
border-radius: 257px;
|
||||||
|
padding: 6rpx 12rpx;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,11 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="nav-container">
|
<view class="nav-container">
|
||||||
<view :style="{ height: ststuaBarHeight + 'px' }"></view>
|
<!-- <view :style="{ height: ststuaBarHeight + 'px' }"></view> -->
|
||||||
<view
|
<view
|
||||||
class="flex-row-center nav-title ali-puhui-bold"
|
class="flex-row-center nav-title ali-puhui-bold"
|
||||||
:style="{ height: menuButtonHeight + 'px' }"
|
|
||||||
>
|
>
|
||||||
{{ title }}
|
<text style="margin-bottom:35px;">{{ title }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -31,25 +31,25 @@
|
|||||||
text: "首页",
|
text: "首页",
|
||||||
isSpecial: false,
|
isSpecial: false,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
pageId: "filesPage",
|
// pageId: "filesPage",
|
||||||
iconPath: `${imgPrefix}tabBar-record.png`,
|
// iconPath: `${imgPrefix}tabBar-record.png`,
|
||||||
selectedIconPath: `${imgPrefix}tabBar-selectedRecord.png`,
|
// selectedIconPath: `${imgPrefix}tabBar-selectedRecord.png`,
|
||||||
text: "档案",
|
// text: "档案",
|
||||||
isSpecial: false,
|
// isSpecial: false,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
pageId: "reservationPage",
|
// pageId: "reservationPage",
|
||||||
iconPath: `${imgPrefix}tabbar-reservation.gif`,
|
// iconPath: `${imgPrefix}tabbar-reservation.gif`,
|
||||||
selectedIconPath: `${imgPrefix}tabbar-bath.png`,
|
// selectedIconPath: `${imgPrefix}tabbar-bath.png`,
|
||||||
text: "立即预约",
|
// text: "立即预约",
|
||||||
isSpecial: true, // 特殊样式标记(中间突出的按钮)
|
// isSpecial: true, // 特殊样式标记(中间突出的按钮)
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
pageId: "shopPage",
|
pageId: "shopPage",
|
||||||
iconPath: `${imgPrefix}tabBar-mall.png`,
|
iconPath: `${imgPrefix}tabBar-mall.png`,
|
||||||
selectedIconPath: `${imgPrefix}tabBar-selectdMall.png`,
|
selectedIconPath: `${imgPrefix}tabBar-selectdMall.png`,
|
||||||
text: "商城",
|
text: "分类",
|
||||||
isSpecial: false,
|
isSpecial: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
208
src/components/WeChatCopyModal.vue
Normal file
208
src/components/WeChatCopyModal.vue
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<view class="wechat-copy-modal" v-if="visible">
|
||||||
|
<view class="modal-mask" @click="handleCancel"></view>
|
||||||
|
<view class="modal-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<view class="modal-title">{{ title }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-body">
|
||||||
|
<view class="wechat-code-wrapper">
|
||||||
|
<text class="wechat-code">{{ weChatCode }}</text>
|
||||||
|
<view class="copy-btn" @click="handleCopy">
|
||||||
|
<image class="copy-icon" :src="copyIcon" mode="aspectFit" />
|
||||||
|
<text class="copy-text">复制</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<view class="cancel-btn" @click="handleCancel">取消</view>
|
||||||
|
<view class="confirm-btn" @click="handleConfirm">确认</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { imgPrefix } from "@/utils/common";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "WeChatCopyModal",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
title: '请添加客服号',
|
||||||
|
weChatCode: 'Wagoo2025',
|
||||||
|
copyIcon: `${imgPrefix}home-copy.png`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show(options = {}) {
|
||||||
|
if (options.title) this.title = options.title;
|
||||||
|
if (options.weChatCode) this.weChatCode = options.weChatCode;
|
||||||
|
this.visible = true;
|
||||||
|
// 强制更新视图
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
handleCopy() {
|
||||||
|
// 执行复制
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: this.weChatCode,
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '微信号已复制',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
this.hide();
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('复制失败', err);
|
||||||
|
// 3. 降级方案:直接显示微信号,引导手动输入
|
||||||
|
uni.showModal({
|
||||||
|
title: '复制失败',
|
||||||
|
content: '微信号:' + this.weChatCode + '\n\n请手动输入添加',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '知道了'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancel() {
|
||||||
|
this.hide();
|
||||||
|
this.$emit('cancel');
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.handleCopy();
|
||||||
|
this.hide();
|
||||||
|
this.$emit('confirm');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wechat-copy-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
width: 600rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: modalSlideIn 0.3s ease-out;
|
||||||
|
|
||||||
|
@keyframes modalSlideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx 32rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 0 32rpx 40rpx;
|
||||||
|
|
||||||
|
.wechat-code-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
|
||||||
|
.wechat-code {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
|
||||||
|
.copy-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff19a0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 32rpx 40rpx;
|
||||||
|
gap: 24rpx;
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #ff19a0;
|
||||||
|
border: 2rpx solid #ff19a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #ff19a0;
|
||||||
|
color: #fff;
|
||||||
|
border: 2rpx solid #ff19a0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
343
src/components/ai-advisor/AiAdvisorView.vue
Normal file
343
src/components/ai-advisor/AiAdvisorView.vue
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
<template>
|
||||||
|
<view class="ai-advisor-container">
|
||||||
|
<scroll-view class="ai-scroll" scroll-y>
|
||||||
|
<!-- 头部背景图 -->
|
||||||
|
<view class="advisor-hero">
|
||||||
|
<image class="advisor-hero-img" :src="imgPrefix + 'ai-ground.png'" mode="widthFix" />
|
||||||
|
<view class="advisor-hero-overlay">
|
||||||
|
<text class="hero-subtitle">
|
||||||
|
已经有<text class="hero-subtitle-value">{{ experienceCount }}</text>位朋友体验过啦~
|
||||||
|
</text>
|
||||||
|
<view class="hero-bottom-row">
|
||||||
|
<view class="hero-btn" @click="goToRecharge">
|
||||||
|
<text class="hero-btn-text">充值中心</text>
|
||||||
|
<image :src="imgPrefix + 'recharge-whiteArrow.png'" class="rightArrow" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<view class="hero-points">
|
||||||
|
<image :src="imgPrefix + 'integral-icon.png'" class="hero-points-icon" mode="aspectFit" />
|
||||||
|
<text class="hero-points-label">剩余积分</text>
|
||||||
|
<text class="hero-points-value">{{ remainingPoints }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="hero-rules" @click="openRulesModal">
|
||||||
|
<text class="hero-rules-text">使用规则</text>
|
||||||
|
<image class="hero-rules-icon" :src="imgPrefix + 'trainingTips.png'" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="advisor-content">
|
||||||
|
<view class="tool-grid">
|
||||||
|
<view class="tool-card" v-for="tool in toolList" :key="tool.id" @click="goToChat(tool)">
|
||||||
|
<view class="tool-icon-wrap">
|
||||||
|
<image v-if="tool.service_icon" class="tool-icon" :src="tool.service_icon" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<text class="tool-title">{{ tool.service_name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 使用规则弹窗 -->
|
||||||
|
<view v-if="showRulesModal" class="rules-modal-mask" @click="closeRulesModal">
|
||||||
|
<view class="rules-modal" @click.stop="">
|
||||||
|
<text class="rules-title">使用规则</text>
|
||||||
|
<scroll-view scroll-y class="rules-content">
|
||||||
|
<text class="rules-text">{{ rulesText }}</text>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="rules-btn" @click.stop="closeRulesModal">
|
||||||
|
<text class="rules-btn-text">我知道了</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { imgPrefix } from '@/utils/common';
|
||||||
|
import { getAiConsultServices, getAiConsultOverview } from '@/api/common';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AiAdvisorView',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imgPrefix,
|
||||||
|
toolList: [
|
||||||
|
{ key: 'drug', title: '药品成分分析', icon: 'ai-tool-drug.png' },
|
||||||
|
{ key: 'vomit', title: '呕吐物分析', icon: 'ai-tool-vomit.png' },
|
||||||
|
],
|
||||||
|
remainingPoints: 0,
|
||||||
|
experienceCount: 0,
|
||||||
|
showRulesModal: false,
|
||||||
|
rulesText:
|
||||||
|
'本平台提供 AI 顾问服务,支持文字咨询与图片生成 / 解析。积分消耗标准:\n1. 文字咨询:50 积分 / 次\n2. 图片服务:100 积分 / 张',
|
||||||
|
petInfo: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
getAiConsultServices().then((res) => {
|
||||||
|
const list = res?.data?.toolList ?? res?.data ?? [];
|
||||||
|
this.toolList = Array.isArray(list) ? list : [];
|
||||||
|
});
|
||||||
|
getAiConsultOverview().then((res) => {
|
||||||
|
const data = res?.data ?? {};
|
||||||
|
this.remainingPoints = data?.current_points;
|
||||||
|
this.experienceCount = data?.consult_user_count;
|
||||||
|
this.petInfo = data.pet_info
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openRulesModal() {
|
||||||
|
this.showRulesModal = true;
|
||||||
|
},
|
||||||
|
closeRulesModal() {
|
||||||
|
this.showRulesModal = false;
|
||||||
|
},
|
||||||
|
goToChat(tool) {
|
||||||
|
if (this.petInfo == null) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pageHome/selectPet/index?from=aiAdvisor',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
uni.setStorageSync('aiAdvisorPetInfo', this.petInfo || {});
|
||||||
|
} catch (e) {}
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pageHome/ai-advisor/chat?key=${encodeURIComponent(tool.id)}&title=${encodeURIComponent(tool.service_name || '')}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
goToRecharge() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/client/recharge/index?tab=points',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ai-advisor-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-scroll {
|
||||||
|
height: calc(100vh - 176rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.advisor-hero {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advisor-hero-img {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advisor-hero-overlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 0rpx 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #7a7a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle-value {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ff19a0;
|
||||||
|
margin: 0rpx 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-bottom-row {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-btn {
|
||||||
|
background-color: #ff19a0;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
padding: 6rpx 12rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-btn-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightArrow {
|
||||||
|
width: 11rpx;
|
||||||
|
height: 18rpx;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-points {
|
||||||
|
margin-left: 16rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-points-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-points-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-right: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-points-value {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-rules {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 140rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-rules-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #767676;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-rules-icon {
|
||||||
|
width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advisor-content {
|
||||||
|
padding: 0 20rpx 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-icon-wrap {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: rgba(255, 25, 160, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-icon {
|
||||||
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-title {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #515a63;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 使用规则弹窗 */
|
||||||
|
.rules-modal-mask {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-modal {
|
||||||
|
width: 560rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 40rpx 40rpx 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #282828;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-content {
|
||||||
|
max-height: 360rpx;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9b939a;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-btn {
|
||||||
|
margin-top: 32rpx;
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
background: #ff19a0;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-btn-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -15,6 +15,28 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 隐私协议弹窗(抖音专用) -->
|
||||||
|
<view class="privacy-modal" v-if="showPrivacyModal" @click.stop>
|
||||||
|
<view class="privacy-modal-content">
|
||||||
|
<view class="privacy-modal-title">隐私保护提示</view>
|
||||||
|
<view class="privacy-modal-text">
|
||||||
|
请阅读并同意
|
||||||
|
<text class="privacy-link" @click.stop="openPrivacyContract">《用户隐私保护协议》</text>
|
||||||
|
后,方可使用拨打电话功能
|
||||||
|
</view>
|
||||||
|
<button
|
||||||
|
class="privacy-agree-btn"
|
||||||
|
open-type="agreePrivacyAuthorization"
|
||||||
|
@agreeprivacyauthorization="onAgreePrivacy"
|
||||||
|
>
|
||||||
|
同意并继续
|
||||||
|
</button>
|
||||||
|
<button class="privacy-cancel-btn" @click.stop="onDisagreePrivacy">
|
||||||
|
暂不同意
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -27,30 +49,134 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {
|
||||||
|
showPrivacyModal: false, // 隐私协议弹窗
|
||||||
|
pendingCall: false // 是否有待执行的拨号请求
|
||||||
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeAction() {
|
closeAction() {
|
||||||
|
this.showPrivacyModal = false
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
},
|
},
|
||||||
callAction() {
|
|
||||||
if (this.phoneNumber) {
|
// 打开隐私协议详情(供用户查看)
|
||||||
uni.makePhoneCall({
|
openPrivacyContract() {
|
||||||
phoneNumber: this.phoneNumber,
|
// #ifdef MP-TOUTIAO
|
||||||
success: (res) => {
|
tt.openPrivacyContract({
|
||||||
console.log(res);
|
fail: (err) => {
|
||||||
this.$emit('close');
|
console.error('打开隐私协议失败', err)
|
||||||
},
|
uni.showToast({
|
||||||
fail: (err) => {
|
title: '无法打开协议',
|
||||||
console.log(err);
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
// 用户同意隐私协议
|
||||||
|
onAgreePrivacy() {
|
||||||
|
console.log('用户已同意隐私协议')
|
||||||
|
this.showPrivacyModal = false
|
||||||
|
uni.showToast({
|
||||||
|
title: '已授权',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
// 同意后执行拨号
|
||||||
|
this.doMakePhoneCall()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 用户拒绝隐私协议
|
||||||
|
onDisagreePrivacy() {
|
||||||
|
this.showPrivacyModal = false
|
||||||
|
uni.showToast({
|
||||||
|
title: '未授权,无法拨号',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查隐私授权状态
|
||||||
|
checkPrivacyAndCall() {
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
tt.getPrivacySetting({
|
||||||
|
success: (res) => {
|
||||||
|
console.log('隐私授权状态:', res)
|
||||||
|
if (res.needAuthorization) {
|
||||||
|
// 未授权,显示隐私弹窗
|
||||||
|
this.showPrivacyModal = true
|
||||||
|
} else {
|
||||||
|
// 已授权,直接拨号
|
||||||
|
this.doMakePhoneCall()
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
} else {
|
fail: (err) => {
|
||||||
|
console.error('获取隐私设置失败', err)
|
||||||
|
// 降级处理:直接拨号
|
||||||
|
this.doMakePhoneCall()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-TOUTIAO
|
||||||
|
// 非抖音平台直接拨号
|
||||||
|
this.doMakePhoneCall()
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
// 实际拨号方法
|
||||||
|
doMakePhoneCall() {
|
||||||
|
if (!this.phoneNumber) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '电话号码为空',
|
title: '电话号码为空',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber: this.phoneNumber,
|
||||||
|
success: (res) => {
|
||||||
|
console.log('拨号成功', res);
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('拨号失败', err);
|
||||||
|
// 拨号失败时,让用户手动复制
|
||||||
|
uni.showModal({
|
||||||
|
title: '客服电话',
|
||||||
|
content: this.phoneNumber,
|
||||||
|
confirmText: '复制',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: this.phoneNumber,
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '电话已复制',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
callAction() {
|
||||||
|
if (!this.phoneNumber) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '电话号码为空',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查隐私授权并拨号
|
||||||
|
this.checkPrivacyAndCall()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -115,4 +241,72 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
// 隐私协议弹窗样式
|
||||||
|
.privacy-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-modal-content {
|
||||||
|
width: 560rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 48rpx 32rpx 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-modal-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-modal-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-link {
|
||||||
|
color: #007aff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-agree-btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-cancel-btn {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,11 +3,8 @@ export default {
|
|||||||
appName: "Wagoo",
|
appName: "Wagoo",
|
||||||
appShareName: "Wagoo",
|
appShareName: "Wagoo",
|
||||||
appId: "wx00e2dcdc7c02b23a",
|
appId: "wx00e2dcdc7c02b23a",
|
||||||
// apiBaseUrl: 'https://api.wagoo.cc', // 服务端生产地址
|
// apiBaseUrl: "https://api.wagoo.me/api/v1", // 服务端测试地址
|
||||||
apiBaseUrl: "https://api.wagoo.me/api/v1", // 服务端测试地址
|
apiBaseUrl: "https://api.wagoo.pet/api/v1", // 服务端生产地址
|
||||||
// apiBaseUrl: "https://api.wagoo.pet/api/v1", // 服务端生产地址
|
|
||||||
|
|
||||||
|
|
||||||
// apiBaseUrl: "http:192.168.30.79", //本地接口
|
// apiBaseUrl: "http:192.168.30.79", //本地接口
|
||||||
tencentMapKey: "WSBBZ-7OXK4-46QUC-KFB7B-4N3W7-M2BXM",
|
tencentMapKey: "WSBBZ-7OXK4-46QUC-KFB7B-4N3W7-M2BXM",
|
||||||
tencentSecret: "vb7D0PGj7xUvmOLuJz2Jd7ykTMpjiWRJ",
|
tencentSecret: "vb7D0PGj7xUvmOLuJz2Jd7ykTMpjiWRJ",
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name" : "Wagoo",
|
"name" : "Wagoo",
|
||||||
"appid" : "__UNI__F0AB43B",
|
"appid" : "tte57093cd7a7fbf2401",
|
||||||
"description" : "init",
|
"description" : "init",
|
||||||
"versionName" : "1.0.0",
|
"versionName" : "1.0.0",
|
||||||
"versionCode" : "100",
|
"versionCode" : "100",
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
/* 5+App特有相关 */
|
|
||||||
"compatible" : {
|
"compatible" : {
|
||||||
"ignoreVersion" : true
|
"ignoreVersion" : true
|
||||||
},
|
},
|
||||||
@ -18,11 +17,8 @@
|
|||||||
"delay" : 0
|
"delay" : 0
|
||||||
},
|
},
|
||||||
"modules" : {},
|
"modules" : {},
|
||||||
/* 模块配置 */
|
|
||||||
"distribute" : {
|
"distribute" : {
|
||||||
/* 应用发布信息 */
|
|
||||||
"android" : {
|
"android" : {
|
||||||
/* android打包配置 */
|
|
||||||
"permissions" : [
|
"permissions" : [
|
||||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
@ -49,16 +45,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ios" : {},
|
"ios" : {},
|
||||||
/* ios打包配置 */
|
|
||||||
"sdkConfigs" : {},
|
"sdkConfigs" : {},
|
||||||
"splashscreen" : {
|
"splashscreen" : {
|
||||||
"useOriginalMsgbox" : true
|
"useOriginalMsgbox" : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/* SDK配置 */
|
|
||||||
"quickapp" : {},
|
"quickapp" : {},
|
||||||
/* 快应用特有相关 */
|
|
||||||
"mp-weixin" : {
|
"mp-weixin" : {
|
||||||
"appid" : "wx00e2dcdc7c02b23a",
|
"appid" : "wx00e2dcdc7c02b23a",
|
||||||
"setting" : {
|
"setting" : {
|
||||||
@ -70,14 +63,14 @@
|
|||||||
"minifyWXML" : true,
|
"minifyWXML" : true,
|
||||||
"minifyWXSS" : true
|
"minifyWXSS" : true
|
||||||
},
|
},
|
||||||
|
"libVersion" : "2.0.0",
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"lazyCodeLoading" : "requiredComponents",
|
"lazyCodeLoading" : "requiredComponents",
|
||||||
"optimization" : {
|
"optimization" : {
|
||||||
"subPackages" : true
|
"subPackages" : true
|
||||||
},
|
},
|
||||||
"requiredPrivateInfos" : [],
|
"requiredPrivateInfos" : [],
|
||||||
"permission" : {},
|
"permission" : {}
|
||||||
"plugins" : {}
|
|
||||||
},
|
},
|
||||||
"mp-alipay" : {
|
"mp-alipay" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
@ -86,7 +79,28 @@
|
|||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"mp-toutiao" : {
|
"mp-toutiao" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true,
|
||||||
|
"appid" : "tte57093cd7a7fbf2401",
|
||||||
|
"libVersion" : "2.0.0",
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false,
|
||||||
|
"minified" : true,
|
||||||
|
"postcss" : true,
|
||||||
|
"es6" : true,
|
||||||
|
"minifyJS" : true,
|
||||||
|
"minifyWXML" : true,
|
||||||
|
"minifyWXSS" : true
|
||||||
|
},
|
||||||
|
"privacy" : {
|
||||||
|
"usePrivacyCheck" : true,
|
||||||
|
"requiredPrivateInfos" : [ "makePhoneCall" ]
|
||||||
|
},
|
||||||
|
"plugins" : {
|
||||||
|
"lifeServicePlugin" : {
|
||||||
|
"version" : "*",
|
||||||
|
"provider" : "tta5a3d31e3aecfb9b11"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"mp-qq" : {
|
"mp-qq" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
|
|||||||
@ -69,7 +69,7 @@
|
|||||||
<image class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix"></image>
|
<image class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<text class="nightFee">以下区域需收取调度费:奉贤区、嘉定区、青浦区、松江区、崇明区、金山区</text>
|
<!-- <text class="nightFee">以下区域需收取调度费:奉贤区、嘉定区、青浦区、松江区、崇明区、金山区</text> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 价格-->
|
<!-- 价格-->
|
||||||
|
|||||||
@ -551,13 +551,12 @@ export default {
|
|||||||
}
|
}
|
||||||
payOrder(this.orderId, chaJiaType).then((res) => {
|
payOrder(this.orderId, chaJiaType).then((res) => {
|
||||||
const payData = res?.info?.pay_data || {};
|
const payData = res?.info?.pay_data || {};
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
provider: 'wxpay',
|
orderInfo: {
|
||||||
timeStamp: payData.timeStamp,
|
order_id:res.data.orderInfo.order_id,
|
||||||
nonceStr: payData.nonceStr,
|
order_token:res.data.orderInfo.order_token,
|
||||||
package: payData.package,
|
},
|
||||||
signType: payData.signType,
|
service:5,
|
||||||
paySign: payData.sign,
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
console.log('success:' + JSON.stringify(res));
|
console.log('success:' + JSON.stringify(res));
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<view class="body-container">
|
<view class="body-container">
|
||||||
<scroll-view class="scroll-view" :scroll-y="true">
|
<scroll-view class="scroll-view" :scroll-y="true">
|
||||||
<view class="form-content">
|
<view class="form-content">
|
||||||
<view class="order-tab-list">
|
<!-- <view class="order-tab-list">
|
||||||
<view :class="orderType === ORDER_TYPE_RESERVATION ? 'activeItem' : 'tabItem'"
|
<view :class="orderType === ORDER_TYPE_RESERVATION ? 'activeItem' : 'tabItem'"
|
||||||
@click.stop="selectOrderType(ORDER_TYPE_RESERVATION)">
|
@click.stop="selectOrderType(ORDER_TYPE_RESERVATION)">
|
||||||
预约单
|
预约单
|
||||||
@ -13,13 +13,13 @@
|
|||||||
@click.stop="selectOrderType(ORDER_TYPE_SITE)">
|
@click.stop="selectOrderType(ORDER_TYPE_SITE)">
|
||||||
现场单
|
现场单
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="reservation-info-container">
|
<view class="reservation-info-container">
|
||||||
<!-- 其他内容 -->
|
<!-- 其他内容 -->
|
||||||
<view class="formWrapper">
|
<view class="formWrapper">
|
||||||
|
|
||||||
|
|
||||||
<view class="info-top-view" v-if="orderType != ORDER_TYPE_RESERVATION">
|
<!-- <view class="info-top-view" v-if="orderType != ORDER_TYPE_RESERVATION">
|
||||||
<view class="top">
|
<view class="top">
|
||||||
<view class="title-view">
|
<view class="title-view">
|
||||||
<text class="required">*</text>
|
<text class="required">*</text>
|
||||||
@ -32,9 +32,9 @@
|
|||||||
@click="scanCode" />
|
@click="scanCode" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<view class="form-section form-section-pet">
|
<!-- <view class="form-section form-section-pet">
|
||||||
<view class="form-label-row">
|
<view class="form-label-row">
|
||||||
<text class="required">*</text>
|
<text class="required">*</text>
|
||||||
<text class="form-label">选择宠物</text>
|
<text class="form-label">选择宠物</text>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
<text class="add-pet-text">添加宠物</text>
|
<text class="add-pet-text">添加宠物</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
<!-- <info-cell key="weight" cell-type="text" title="宠物重量区间" :info="petInfo.weight_name"
|
<!-- <info-cell key="weight" cell-type="text" title="宠物重量区间" :info="petInfo.weight_name"
|
||||||
placeholder='先选择宠物' :is-can-click="false" />
|
placeholder='先选择宠物' :is-can-click="false" />
|
||||||
<info-cell v-if="selectedPetType === PET_TYPE_CAT" key="mofa" cell-type="text" title="宠物毛发"
|
<info-cell v-if="selectedPetType === PET_TYPE_CAT" key="mofa" cell-type="text" title="宠物毛发"
|
||||||
@ -79,12 +79,12 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="payFooter">
|
<view class="payFooter">
|
||||||
<view class="leftPay">
|
<view class="leftPay">
|
||||||
<view class="priceWrapper">
|
<!-- <view class="priceWrapper">
|
||||||
<text class="text">预估:</text>
|
<text class="text">预估:</text>
|
||||||
<text class="unitText">
|
<text class="unitText">
|
||||||
¥<text class="price">{{ totalDisplayPrice }}</text>
|
¥<text class="price">{{ totalDisplayPrice }}</text>
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view> -->
|
||||||
<!-- <view class="vipPrice" v-if="discount === 0">
|
<!-- <view class="vipPrice" v-if="discount === 0">
|
||||||
<view>
|
<view>
|
||||||
<image :src="`${imgPrefix}vipPrice.png`" mode="widthFix"
|
<image :src="`${imgPrefix}vipPrice.png`" mode="widthFix"
|
||||||
@ -95,8 +95,8 @@
|
|||||||
</view>
|
</view>
|
||||||
</view> -->
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="payBtn" @click.stop="paymentConfirm">
|
<view class="payBtn" @click.stop="writeOff">
|
||||||
下一步
|
预约
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -119,7 +119,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import InfoCell from "@/page-reser/components/info-cell.vue";
|
import InfoCell from "@/page-reser/components/info-cell.vue";
|
||||||
import {
|
import {
|
||||||
gitDiscountfee
|
gitDiscountfee,
|
||||||
|
getdouyReview
|
||||||
} from "../../api/login";
|
} from "../../api/login";
|
||||||
import {
|
import {
|
||||||
ARTICLE_TYPE_RESERVATION_CAT,
|
ARTICLE_TYPE_RESERVATION_CAT,
|
||||||
@ -146,6 +147,12 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
InfoCell,
|
InfoCell,
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
orderId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
imgPrefix,
|
imgPrefix,
|
||||||
@ -155,7 +162,7 @@ export default {
|
|||||||
ORDER_TYPE_RESERVATION,
|
ORDER_TYPE_RESERVATION,
|
||||||
ORDER_TYPE_SITE,
|
ORDER_TYPE_SITE,
|
||||||
orderType: ORDER_TYPE_RESERVATION,
|
orderType: ORDER_TYPE_RESERVATION,
|
||||||
selectedPetType: PET_TYPE_CAT, // 默认选中“猫”
|
selectedPetType: PET_TYPE_CAT, // 默认选中"猫"
|
||||||
petInfo: {}, // 兼容下游,取 selectedPets[0]
|
petInfo: {}, // 兼容下游,取 selectedPets[0]
|
||||||
selectedPets: [], // 多选宠物列表
|
selectedPets: [], // 多选宠物列表
|
||||||
parkState: "",
|
parkState: "",
|
||||||
@ -167,11 +174,24 @@ export default {
|
|||||||
address: null,
|
address: null,
|
||||||
catHtmlData: "",
|
catHtmlData: "",
|
||||||
dogHtmlData: "",
|
dogHtmlData: "",
|
||||||
tip: ''
|
tip: '',
|
||||||
|
localOrderId: null, // 本地存储的 orderId
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initData();
|
this.initData();
|
||||||
|
// 如果有传入的 orderId,保存到本地
|
||||||
|
if (this.orderId) {
|
||||||
|
this.localOrderId = this.orderId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 监听 orderId 变化
|
||||||
|
orderId(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.localOrderId = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// 展示价格:discount_price 数组相加的总和
|
// 展示价格:discount_price 数组相加的总和
|
||||||
@ -185,6 +205,89 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 用于接收从父页面传递的数据
|
||||||
|
onShowFun(orderId) {
|
||||||
|
if (orderId) {
|
||||||
|
this.localOrderId = orderId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 核销按钮点击处理
|
||||||
|
async writeOff () {
|
||||||
|
// 校验必填项
|
||||||
|
if (Object.keys(this.reservationTime).length === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "请选择预约时间",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.address) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "请选择服务地址",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.parkState) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "请选择停车状况",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.localOrderId) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "订单ID缺失",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
goods_order_id: Number(this.localOrderId), //抖音商品订单id(整数)
|
||||||
|
order_date: this.reservationTime.date, //预约日期('2026-03-01'格式)
|
||||||
|
period_id: Number(this.reservationTime.id), //预约时间段id(整数)
|
||||||
|
address_id: Number(this.address.id), //地址id(整数)
|
||||||
|
recipient_name: this.address.name || '用户姓名', //用户姓名
|
||||||
|
phone: this.address.phone || '', //用户手机
|
||||||
|
park_desc: this.parkState || '', //停车描述
|
||||||
|
note: this.otherParkState || '' // 备注
|
||||||
|
};
|
||||||
|
|
||||||
|
uni.showLoading({
|
||||||
|
title: "处理中",
|
||||||
|
mask: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
getdouyReview(data).then((res) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
if (res.code == 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "预约成功",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
// 预约成功后清空数据并跳转
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: "/pages/client/index/index?activePageId=homePage",
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.msg || "预约失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error("预约失败:", err);
|
||||||
|
uni.showToast({
|
||||||
|
title: "预约失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getPetShortName(pet) {
|
getPetShortName(pet) {
|
||||||
const n = pet.name || pet.pet_name || pet.pet_nickname || '';
|
const n = pet.name || pet.pet_name || pet.pet_nickname || '';
|
||||||
return n.length > 2 ? n.slice(0, 2) + '…' : n.slice(0, 2);
|
return n.length > 2 ? n.slice(0, 2) + '…' : n.slice(0, 2);
|
||||||
|
|||||||
@ -272,14 +272,12 @@ export default {
|
|||||||
},
|
},
|
||||||
weixinPay(orderId) {
|
weixinPay(orderId) {
|
||||||
payOrder(orderId).then((res) => {
|
payOrder(orderId).then((res) => {
|
||||||
const payData = res?.info?.pay_data || {};
|
tt.pay({
|
||||||
uni.requestPayment({
|
orderInfo: {
|
||||||
provider: 'wxpay',
|
order_id:res.data.orderInfo.order_id,
|
||||||
timeStamp: payData.timeStamp,
|
order_token:res.data.orderInfo.order_token,
|
||||||
nonceStr: payData.nonceStr,
|
},
|
||||||
package: payData.package,
|
service:5,
|
||||||
signType: payData.signType,
|
|
||||||
paySign: payData.sign,
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
const eventChannel = this.getOpenerEventChannel();
|
const eventChannel = this.getOpenerEventChannel();
|
||||||
|
|||||||
1376
src/pageHome/ai-advisor/chat.vue
Normal file
1376
src/pageHome/ai-advisor/chat.vue
Normal file
File diff suppressed because it is too large
Load Diff
22
src/pageHome/ai-advisor/index.vue
Normal file
22
src/pageHome/ai-advisor/index.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<view class="ai-advisor-page">
|
||||||
|
<ai-advisor-view />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AiAdvisorView from '@/components/ai-advisor/AiAdvisorView.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AiAdvisorPage',
|
||||||
|
components: { AiAdvisorView },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ai-advisor-page {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -60,7 +60,8 @@
|
|||||||
<text v-if="item.item_cut_price" class="price-original">¥{{ item.item_price }}</text>
|
<text v-if="item.item_cut_price" class="price-original">¥{{ item.item_price }}</text>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
<image class="cart-icon" :src="item.selected ? require('@/static/images/xz.png') : ''" />
|
<image v-show="item.selected" class="cart-icon" src="/static/images/xz.png"></image>
|
||||||
|
<!-- <image class="cart-icon" :src="item.selected ? require('@/static/images/xz.png') : ''" /> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -109,8 +110,8 @@
|
|||||||
<text class="zf"> 请选择支付方式 </text>
|
<text class="zf"> 请选择支付方式 </text>
|
||||||
<view class="wechat" @click.stop="selectOption1('1')">
|
<view class="wechat" @click.stop="selectOption1('1')">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
||||||
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
||||||
@ -533,12 +534,12 @@ export default {
|
|||||||
};
|
};
|
||||||
walletWxpay(data).then((res) => {
|
walletWxpay(data).then((res) => {
|
||||||
// 使用获取的支付参数进行支付
|
// 使用获取的支付参数进行支付
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
orderInfo: {
|
||||||
nonceStr: res.data.nonceStr,
|
order_id:res.data.orderInfo.order_id,
|
||||||
package: res.data.package,
|
order_token:res.data.orderInfo.order_token,
|
||||||
signType: res.data.signType,
|
},
|
||||||
paySign: res.data.paySign,
|
service:5,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
// 关闭支付弹窗
|
// 关闭支付弹窗
|
||||||
this.additionalBom = false;
|
this.additionalBom = false;
|
||||||
|
|||||||
@ -192,8 +192,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="wechat" @click.stop="selectOption1('1')">
|
<view class="wechat" @click.stop="selectOption1('1')">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
||||||
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
||||||
@ -702,12 +702,12 @@ export default {
|
|||||||
};
|
};
|
||||||
payOrder(params)
|
payOrder(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
timeStamp: res.data.timeStamp,
|
orderInfo: {
|
||||||
nonceStr: res.data.nonceStr,
|
order_id:res.data.orderInfo.order_id,
|
||||||
package: res.data.package,
|
order_token:res.data.orderInfo.order_token,
|
||||||
signType: res.data.signType,
|
},
|
||||||
paySign: res.data.paySign,
|
service:5,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|||||||
@ -297,8 +297,8 @@
|
|||||||
<view class="recharge-method">
|
<view class="recharge-method">
|
||||||
<view class="wechat" @click.stop="selectOption1('1')">
|
<view class="wechat" @click.stop="selectOption1('1')">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
||||||
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
||||||
@ -322,14 +322,14 @@
|
|||||||
<text v-else class="money">¥{{ toAmount }}</text> -->
|
<text v-else class="money">¥{{ toAmount }}</text> -->
|
||||||
<view class="handle-info-cell">
|
<view class="handle-info-cell">
|
||||||
<view v-if="reservationInfo.estimatePrice != 0">
|
<view v-if="reservationInfo.estimatePrice != 0">
|
||||||
<text class="money">¥{{ (estimatedPri - couponMoney2).toFixed(2) }}</text>
|
<text class="money">¥{{ Math.max(0, estimatedPri - couponMoney2).toFixed(2) }}</text>
|
||||||
<text class="app-fc12">¥</text>
|
<text class="app-fc12">¥</text>
|
||||||
<text class="app-fc1">{{ toAmount3.toFixed(2) || "0.00" }}</text>
|
<text class="app-fc1">{{ Math.max(0, toAmount3).toFixed(2) || "0.00" }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-else>
|
<view v-else>
|
||||||
<text class="app-fc-main fs-30">¥</text>
|
<text class="app-fc-main fs-30">¥</text>
|
||||||
<text class="app-fc-main fs-40 app-font-bold">{{
|
<text class="app-fc-main fs-40 app-font-bold">{{
|
||||||
(toAmount2.toFixed(2) || "0.00") - couponMoney
|
Math.max(0, (toAmount2.toFixed(2) || "0.00") - couponMoney)
|
||||||
}}</text>
|
}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -369,8 +369,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="wechat">
|
<view class="wechat">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="select1" class="not-selected" @click.stop="selectOpt1('1')" src="@/static/images/w.png"
|
<image v-if="select1" class="not-selected" @click.stop="selectOpt1('1')" src="@/static/images/w.png"
|
||||||
mode="widthFix" />
|
mode="widthFix" />
|
||||||
@ -792,12 +792,12 @@ export default {
|
|||||||
};
|
};
|
||||||
memberWXPAY(data).then((res) => {
|
memberWXPAY(data).then((res) => {
|
||||||
// 使用获取的支付参数进行支付
|
// 使用获取的支付参数进行支付
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
orderInfo: {
|
||||||
nonceStr: res.data.nonceStr,
|
order_id:res.data.orderInfo.order_id,
|
||||||
package: res.data.package,
|
order_token:res.data.orderInfo.order_token,
|
||||||
signType: res.data.signType,
|
},
|
||||||
paySign: res.data.paySign,
|
service:5,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "支付成功",
|
title: "支付成功",
|
||||||
@ -1202,12 +1202,12 @@ export default {
|
|||||||
}
|
}
|
||||||
payOrder(params)
|
payOrder(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
orderInfo: {
|
||||||
nonceStr: res.data.nonceStr,
|
order_id:res.data.orderInfo.order_id,
|
||||||
package: res.data.package,
|
order_token:res.data.orderInfo.order_token,
|
||||||
signType: res.data.signType,
|
},
|
||||||
paySign: res.data.paySign,
|
service:5,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
const eventChannel = this.getOpenerEventChannel();
|
const eventChannel = this.getOpenerEventChannel();
|
||||||
|
|||||||
@ -118,7 +118,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<image class="right-icon" :src="imgPrefix + 'right-arrow.png'" mode="widthFix" />
|
<image class="right-icon" :src="imgPrefix + 'right-arrow.png'" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<text class="nightFee">以下区域需收取调度费:奉贤区、嘉定区、青浦区、松江区、崇明县、金山区</text>
|
<!-- <text class="nightFee">以下区域需收取调度费:奉贤区、嘉定区、青浦区、松江区、崇明县、金山区</text> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -115,8 +115,8 @@
|
|||||||
<!-- 支付方式 -->
|
<!-- 支付方式 -->
|
||||||
<view class="info-cell payment-method-cell">
|
<view class="info-cell payment-method-cell">
|
||||||
<view class="payment-option" @click="selectPaymentMethod('wechat')">
|
<view class="payment-option" @click="selectPaymentMethod('wechat')">
|
||||||
<image src="@/static/images/wx.png" mode="aspectFit" class="payment-icon" />
|
<image src="@/static/images/douy.png" mode="aspectFit" class="payment-icon" />
|
||||||
<text class="payment-name">微信支付</text>
|
<text class="payment-name">抖音支付</text>
|
||||||
<!-- 未选中状态 -->
|
<!-- 未选中状态 -->
|
||||||
<image v-if="paymentMethod !== 'wechat'" src="@/static/images/w.png" mode="widthFix" class="not-selected" />
|
<image v-if="paymentMethod !== 'wechat'" src="@/static/images/w.png" mode="widthFix" class="not-selected" />
|
||||||
<!-- 选中状态 -->
|
<!-- 选中状态 -->
|
||||||
@ -436,16 +436,12 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payData = res.data || res.info?.pay_data || {};
|
tt.pay({
|
||||||
|
orderInfo: {
|
||||||
// 调用微信支付
|
order_id:res.data.orderInfo.order_id,
|
||||||
uni.requestPayment({
|
order_token:res.data.orderInfo.order_token,
|
||||||
provider: 'wxpay',
|
},
|
||||||
timeStamp: payData.timeStamp,
|
service:5,
|
||||||
nonceStr: payData.nonceStr,
|
|
||||||
package: payData.package,
|
|
||||||
signType: payData.signType,
|
|
||||||
paySign: payData.paySign || payData.sign,
|
|
||||||
success: (payRes) => {
|
success: (payRes) => {
|
||||||
console.log('支付成功:', payRes);
|
console.log('支付成功:', payRes);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|||||||
@ -435,13 +435,12 @@ export default {
|
|||||||
order_no: orderNo,
|
order_no: orderNo,
|
||||||
total_fee: 200
|
total_fee: 200
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
provider: "wxpay",
|
orderInfo: {
|
||||||
timeStamp: res.data.timeStamp,
|
order_id:res.data.orderInfo.order_id,
|
||||||
nonceStr: res.data.nonceStr,
|
order_token:res.data.orderInfo.order_token,
|
||||||
package: res.data.package,
|
},
|
||||||
signType: res.data.signType,
|
service:5,
|
||||||
paySign: res.data.paySign,
|
|
||||||
complete: (res) => {
|
complete: (res) => {
|
||||||
// 支付成功后跳转到订单页面
|
// 支付成功后跳转到订单页面
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -47,8 +47,8 @@
|
|||||||
<!-- 支付方式 -->
|
<!-- 支付方式 -->
|
||||||
<view class="info-cell payment-method-cell">
|
<view class="info-cell payment-method-cell">
|
||||||
<view class="payment-option" @click="selectPaymentMethod('wechat')">
|
<view class="payment-option" @click="selectPaymentMethod('wechat')">
|
||||||
<image src="@/static/images/wx.png" mode="aspectFit" class="payment-icon" />
|
<image src="@/static/images/douy.png" mode="aspectFit" class="payment-icon" />
|
||||||
<text class="payment-name">微信支付</text>
|
<text class="payment-name">抖音支付</text>
|
||||||
<!-- 未选中状态 -->
|
<!-- 未选中状态 -->
|
||||||
<image v-if="paymentMethod !== 'wechat'" src="@/static/images/w.png" mode="widthFix" class="not-selected" />
|
<image v-if="paymentMethod !== 'wechat'" src="@/static/images/w.png" mode="widthFix" class="not-selected" />
|
||||||
<!-- 选中状态 -->
|
<!-- 选中状态 -->
|
||||||
@ -262,16 +262,12 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payData = res.data || res.info?.pay_data || {};
|
tt.pay({
|
||||||
|
orderInfo: {
|
||||||
// 调用微信支付
|
order_id:res.data.orderInfo.order_id,
|
||||||
uni.requestPayment({
|
order_token:res.data.orderInfo.order_token,
|
||||||
provider: 'wxpay',
|
},
|
||||||
timeStamp: payData.timeStamp,
|
service:5,
|
||||||
nonceStr: payData.nonceStr,
|
|
||||||
package: payData.package,
|
|
||||||
signType: payData.signType,
|
|
||||||
paySign: payData.paySign || payData.sign,
|
|
||||||
success: (payRes) => {
|
success: (payRes) => {
|
||||||
console.log('支付成功:', payRes);
|
console.log('支付成功:', payRes);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|||||||
@ -70,9 +70,16 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<canvas
|
||||||
|
id="myCanvas"
|
||||||
|
type="2d"
|
||||||
|
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
||||||
|
style="position: fixed; left: -9999px; top: -9999px;"
|
||||||
|
></canvas>
|
||||||
|
|
||||||
<!-- 隐藏的 wxml-to-canvas 组件用于生成证书图片 -->
|
<!-- 隐藏的 wxml-to-canvas 组件用于生成证书图片 -->
|
||||||
<wxml-to-canvas class="widget" :width="canvasWidth" :height="canvasHeight" style="position: fixed; left: -9999px; top: -9999px;"></wxml-to-canvas>
|
<!-- <wxml-to-canvas class="widget" :width="canvasWidth" :height="canvasHeight" style="position: fixed; left: -9999px; top: -9999px;"></wxml-to-canvas> -->
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -107,7 +114,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onReady() {
|
onReady() {
|
||||||
// 获取 wxml-to-canvas 组件实例
|
// 获取 wxml-to-canvas 组件实例
|
||||||
this.widget = this.$refs.widget || this.selectComponent('.widget');
|
// this.widget = this.$refs.widget || this.selectComponent('.widget');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getCertificates(data) {
|
getCertificates(data) {
|
||||||
@ -125,6 +132,27 @@ export default {
|
|||||||
closeModal() {
|
closeModal() {
|
||||||
this.showModal = false;
|
this.showModal = false;
|
||||||
},
|
},
|
||||||
|
wrapText(ctx, text, x, y, maxWidth, lineHeight) {
|
||||||
|
const words = text.split('');
|
||||||
|
let line = '';
|
||||||
|
let currentY = y;
|
||||||
|
|
||||||
|
for (let i = 0; i < words.length; i++) {
|
||||||
|
const testLine = line + words[i];
|
||||||
|
const metrics = ctx.measureText(testLine);
|
||||||
|
const testWidth = metrics.width;
|
||||||
|
|
||||||
|
if (testWidth > maxWidth && i > 0) {
|
||||||
|
ctx.fillText(line, x, currentY);
|
||||||
|
line = words[i];
|
||||||
|
currentY += lineHeight;
|
||||||
|
} else {
|
||||||
|
line = testLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillText(line, x, currentY);
|
||||||
|
},
|
||||||
onScroll(e) {
|
onScroll(e) {
|
||||||
// 获取滚动位置(单位:px)
|
// 获取滚动位置(单位:px)
|
||||||
const scrollLeft = e.detail.scrollLeft;
|
const scrollLeft = e.detail.scrollLeft;
|
||||||
@ -141,138 +169,175 @@ export default {
|
|||||||
this.currentPage = newPage;
|
this.currentPage = newPage;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async saveCurrentImage() {
|
async loadImage(src) {
|
||||||
// 保存当前图片 - 使用 wxml-to-canvas 生成
|
console.log('正在加载图片:', src);
|
||||||
const currentIndex = this.currentPage - 1;
|
return new Promise((resolve, reject) => {
|
||||||
const currentItem = this.certificateList[currentIndex];
|
tt.downloadFile({
|
||||||
if (!currentItem) {
|
url: src,
|
||||||
uni.showToast({
|
success: (res) => {
|
||||||
title: '证书信息加载中,请稍候',
|
if (res.statusCode === 200) {
|
||||||
icon: 'none'
|
console.log('图片加载成功:', res.tempFilePath);
|
||||||
});
|
resolve(res.tempFilePath);
|
||||||
return;
|
} else {
|
||||||
}
|
console.error('图片加载失败,状态码:', res.statusCode);
|
||||||
|
reject(new Error(`图片加载失败,状态码: ${res.statusCode}`));
|
||||||
// 存储当前证书信息到 data,供模板使用
|
|
||||||
this.currentCertificateItem = currentItem;
|
|
||||||
|
|
||||||
// 获取组件实例 - 多种方式尝试
|
|
||||||
if (!this.widget) {
|
|
||||||
this.widget = this.$refs.widget;
|
|
||||||
}
|
|
||||||
if (!this.widget) {
|
|
||||||
this.widget = this.selectComponent('.widget');
|
|
||||||
}
|
|
||||||
if (!this.widget) {
|
|
||||||
// 延迟重试一次
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
this.widget = this.$refs.widget || this.selectComponent('.widget');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.widget) {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: '组件初始化失败,请重试',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 3000
|
|
||||||
});
|
|
||||||
console.error('wxml-to-canvas 组件获取失败');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查组件方法是否可用
|
|
||||||
if (typeof this.widget.renderToCanvas !== 'function') {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: '组件方法不可用,请重试',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 3000
|
|
||||||
});
|
|
||||||
console.error('renderToCanvas 方法不存在');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({
|
|
||||||
title: '生成图片中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 固定画布尺寸
|
|
||||||
const canvasWidth = 317;
|
|
||||||
const canvasHeight = 224;
|
|
||||||
|
|
||||||
// 更新 canvas 尺寸
|
|
||||||
this.canvasWidth = canvasWidth;
|
|
||||||
this.canvasHeight = canvasHeight;
|
|
||||||
|
|
||||||
await this.$nextTick();
|
|
||||||
|
|
||||||
// 生成证书的 wxml 和 style(使用固定尺寸)
|
|
||||||
const { wxml, style } = this.generateCertificateTemplate();
|
|
||||||
|
|
||||||
// 渲染到 canvas
|
|
||||||
const container = await this.widget.renderToCanvas({ wxml, style });
|
|
||||||
|
|
||||||
// 转换为图片
|
|
||||||
const res = await this.widget.canvasToTempFilePath({
|
|
||||||
fileType: 'png',
|
|
||||||
quality: 1,
|
|
||||||
width: this.canvasWidth,
|
|
||||||
height: this.canvasHeight
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res || !res.tempFilePath) {
|
|
||||||
throw new Error('生成图片路径失败');
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
// 保存到相册
|
fail: (err) => {
|
||||||
uni.saveImageToPhotosAlbum({
|
console.error('图片加载失败:', err.errMsg);
|
||||||
filePath: res.tempFilePath,
|
reject(new Error(`图片加载失败: ${err.errMsg}`));
|
||||||
success: () => {
|
}
|
||||||
uni.hideLoading();
|
});
|
||||||
uni.showToast({
|
});
|
||||||
title: '保存成功',
|
},
|
||||||
icon: 'success'
|
|
||||||
});
|
async saveCurrentImage() {
|
||||||
},
|
const currentIndex = this.currentPage - 1;
|
||||||
fail: (err) => {
|
const currentItem = this.certificateList[currentIndex];
|
||||||
uni.hideLoading();
|
|
||||||
const errMsg = err?.errMsg || '未知错误';
|
if (!currentItem) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '保存失败,请检查相册权限',
|
title: '证书信息加载中,请稍候',
|
||||||
icon: 'none',
|
icon: 'none'
|
||||||
duration: 3000
|
});
|
||||||
});
|
return;
|
||||||
console.error('保存图片失败:', errMsg, err);
|
}
|
||||||
|
|
||||||
|
this.currentCertificateItem = currentItem;
|
||||||
|
|
||||||
|
uni.showLoading({
|
||||||
|
title: '生成图片中...',
|
||||||
|
mask: true
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// 获取 Canvas 节点
|
||||||
|
const query = tt.createSelectorQuery();
|
||||||
|
const canvasNode = await new Promise((resolve, reject) => {
|
||||||
|
query
|
||||||
|
.select('#myCanvas')
|
||||||
|
.fields({ node: true, size: true })
|
||||||
|
.exec((res) => {
|
||||||
|
if (res[0]) {
|
||||||
|
resolve(res[0].node);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Canvas 节点获取失败'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
});
|
||||||
|
|
||||||
|
// 获取 2D 上下文
|
||||||
|
const ctx = canvasNode.getContext('2d');
|
||||||
|
const dpr = tt.getSystemInfoSync().pixelRatio;
|
||||||
|
canvasNode.width = this.canvasWidth * dpr;
|
||||||
|
canvasNode.height = this.canvasHeight * dpr;
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
|
||||||
|
// 清空画布
|
||||||
|
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
|
||||||
|
// 加载并绘制背景图
|
||||||
|
const backgroundImgPath = await this.loadImage(`${this.imgPrefix}certificateGround.png`);
|
||||||
|
const backgroundImg = canvasNode.createImage();
|
||||||
|
backgroundImg.src = backgroundImgPath;
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
backgroundImg.onload = () => {
|
||||||
|
ctx.drawImage(backgroundImg, 0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置字体样式
|
||||||
|
ctx.font = '20px sans-serif';
|
||||||
|
ctx.fillStyle = '#FF19A0';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
|
||||||
|
// 绘制证书标题 - 增加顶部间距
|
||||||
|
const title = currentItem.title || '为爱续航证书';
|
||||||
|
ctx.fillText(title, this.canvasWidth / 2, 65); // 从50改为65,增加间距
|
||||||
|
|
||||||
|
// 绘制英文标题
|
||||||
|
ctx.font = '12px sans-serif';
|
||||||
|
const titleEn = currentItem.title_en || 'Certificate of Love Endurance';
|
||||||
|
ctx.fillText(titleEn, this.canvasWidth / 2, 85); // 从70改为85,增加间距
|
||||||
|
|
||||||
|
// 绘制用户名
|
||||||
|
ctx.font = '16px sans-serif';
|
||||||
|
const userName = this.userName || '周佳佳';
|
||||||
|
ctx.fillText(userName, this.canvasWidth / 2, 110); // 从100改为110,增加间距
|
||||||
|
|
||||||
|
// 绘制描述文本
|
||||||
|
ctx.font = '12px sans-serif';
|
||||||
|
ctx.fillStyle = '#333333';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
const description = `您累计捐赠${currentItem.source_value || 0}克粮,为毛孩子奉献爱心,点燃希望,感谢您的捐赠让世界变得更温暖。`;
|
||||||
|
this.wrapText(ctx, description, 20, 140, this.canvasWidth - 40, 16); // 从130改为140,增加间距
|
||||||
|
|
||||||
|
// 绘制结尾文字
|
||||||
|
ctx.font = '12px sans-serif';
|
||||||
|
ctx.fillStyle = '#333333';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.fillText('特发此证,以表谢忱!', 20, 180); // 从200改为180,向上移动
|
||||||
|
|
||||||
|
// 绘制证书编号
|
||||||
|
ctx.font = '10px sans-serif';
|
||||||
|
ctx.fillStyle = '#FF19A0';
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
const certificateNo = `证书编号: ${currentItem.certificate_no || currentItem.id || '234546678896666788'}`;
|
||||||
|
ctx.fillText(certificateNo, this.canvasWidth - 20, this.canvasHeight - 20);
|
||||||
|
|
||||||
|
// 绘制证书图标
|
||||||
|
const badgeImgPath = await this.loadImage(currentItem.certificate_url);
|
||||||
|
const badgeImg = canvasNode.createImage();
|
||||||
|
badgeImg.src = badgeImgPath;
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
badgeImg.onload = () => {
|
||||||
|
ctx.drawImage(badgeImg, this.canvasWidth - 60, this.canvasHeight - 80, 40, 40);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导出为 base64 数据
|
||||||
|
const dataURL = canvasNode.toDataURL('image/png');
|
||||||
|
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, '');
|
||||||
|
|
||||||
|
// 将 base64 数据写入临时文件
|
||||||
|
const tempFilePath = `${tt.env.USER_DATA_PATH}/temp_certificate.png`;
|
||||||
|
const fs = tt.getFileSystemManager();
|
||||||
|
fs.writeFileSync(tempFilePath, base64Data, 'base64');
|
||||||
|
|
||||||
|
// 保存到相册
|
||||||
|
uni.saveImageToPhotosAlbum({
|
||||||
|
filePath: tempFilePath,
|
||||||
|
success: () => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
const errorMsg = error?.message || error?.toString() || '未知错误';
|
|
||||||
const errorInfo = `生成图片失败: ${errorMsg}`;
|
|
||||||
console.log(errorInfo, '--=')
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: errorInfo.length > 20 ? '生成图片失败,请重试' : errorInfo,
|
title: '保存成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
const errMsg = err?.errMsg || '未知错误';
|
||||||
|
uni.showToast({
|
||||||
|
title: '保存失败,请检查相册权限',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 3000
|
duration: 3000
|
||||||
});
|
});
|
||||||
// 使用多种方式记录错误,确保线上环境能捕获
|
console.error('保存图片失败:', errMsg, err);
|
||||||
console.error('生成证书图片失败:', error);
|
|
||||||
console.error('错误详情:', {
|
|
||||||
message: error?.message,
|
|
||||||
stack: error?.stack,
|
|
||||||
error: error
|
|
||||||
});
|
|
||||||
// 如果 console 不可用,尝试通过其他方式记录
|
|
||||||
if (typeof uni.reportError === 'function') {
|
|
||||||
uni.reportError({
|
|
||||||
error: errorInfo,
|
|
||||||
errorInfo: JSON.stringify(error)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
const errorMsg = error?.message || error?.toString() || '未知错误';
|
||||||
|
uni.showToast({
|
||||||
|
title: errorMsg.length > 20 ? '生成图片失败,请重试' : errorMsg,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
console.error('生成证书图片失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
generateCertificateTemplate() {
|
generateCertificateTemplate() {
|
||||||
// 使用存储的证书信息
|
// 使用存储的证书信息
|
||||||
const item = this.currentCertificateItem;
|
const item = this.currentCertificateItem;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view v-if="show" :class="modalClassList" @click.stop="handleCancel">
|
<view v-if="show" class="donation-confirm-modal" :class="[type === 'dog' ? 'modal-dog' : 'modal-cat']" @click.stop="handleCancel">
|
||||||
<view class="modal-content" @click.stop="">
|
<view class="modal-content" @click.stop="">
|
||||||
<!-- 顶部图标 -->
|
<!-- 顶部图标 -->
|
||||||
<image class="bowl-icon" :src="bowlIcon" mode="aspectFit" />
|
<image class="bowl-icon" :src="bowlIcon" mode="aspectFit" />
|
||||||
@ -64,11 +64,6 @@ export default {
|
|||||||
? `${imgPrefix}welfare-dogFood.png`
|
? `${imgPrefix}welfare-dogFood.png`
|
||||||
: `${imgPrefix}welfare-catFood.png`;
|
: `${imgPrefix}welfare-catFood.png`;
|
||||||
},
|
},
|
||||||
modalClassList() {
|
|
||||||
const baseClass = 'donation-confirm-modal';
|
|
||||||
const typeClass = this.type === 'dog' ? 'modal-dog' : 'modal-cat';
|
|
||||||
return [baseClass, typeClass];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleCancel() {
|
handleCancel() {
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
<!-- 任务成就弹框 -->
|
<!-- 任务成就弹框 -->
|
||||||
<task-modal :show="showTaskModal" :task-list="taskList" @close="closeTaskModal" />
|
<task-modal :show="showTaskModal" :task-list="taskList" @close="closeTaskModal" />
|
||||||
<!-- 捐赠确认弹框 -->
|
<!-- 捐赠确认弹框 -->
|
||||||
<donation-confirm-modal :show="showDonationModal" :type="donationType"
|
<donation-confirm-modal :show="showDonationModal" :type="donationType"
|
||||||
:points="donationType === 'dog' ? donationData.dog.points : donationData.cat.points"
|
:points="donationType === 'dog' ? donationData.dog.points : donationData.cat.points"
|
||||||
:amount="donationType === 'dog' ? donationData.dog.amount : donationData.cat.amount" @cancel="closeDonationModal"
|
:amount="donationType === 'dog' ? donationData.dog.amount : donationData.cat.amount" @cancel="closeDonationModal"
|
||||||
@confirm="handleDonationConfirm" />
|
@confirm="handleDonationConfirm" />
|
||||||
@ -223,6 +223,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleDogCardClick() {
|
handleDogCardClick() {
|
||||||
|
console.log(this)
|
||||||
this.donationType = 'dog';
|
this.donationType = 'dog';
|
||||||
this.showDonationModal = true;
|
this.showDonationModal = true;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
{
|
{
|
||||||
"easycom": {},
|
"easycom": {},
|
||||||
|
"ttPlugins": {
|
||||||
|
"dependencies": {
|
||||||
|
"tta5a3d31e3aecfb9b11": {
|
||||||
|
"version": "0.0.51",
|
||||||
|
"isDynamic": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"pages": [
|
"pages": [
|
||||||
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
|
||||||
{
|
{
|
||||||
"path": "pages/client/index/index",
|
"path": "pages/client/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
@ -45,6 +52,12 @@
|
|||||||
"navigationBarTitleText": "我的公益"
|
"navigationBarTitleText": "我的公益"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/client/mine/qualification",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务资质"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/client/category/index",
|
"path": "pages/client/category/index",
|
||||||
"style": {
|
"style": {
|
||||||
@ -157,10 +170,7 @@
|
|||||||
{
|
{
|
||||||
"path": "welfare/certificate-list",
|
"path": "welfare/certificate-list",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的证书",
|
"navigationBarTitleText": "我的证书"
|
||||||
"usingComponents": {
|
|
||||||
"wxml-to-canvas": "/wxcomponents/wxml-to-canvas/index"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -222,6 +232,18 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "订单详情"
|
"navigationBarTitleText": "订单详情"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "ai-advisor/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "ai-advisor/chat",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -279,11 +301,14 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "申请售后"
|
"navigationBarTitleText": "申请售后"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "details",
|
"path": "details",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "订单详情"
|
"navigationBarTitleText": "订单详情",
|
||||||
|
"usingComponents": {
|
||||||
|
"pay-button-sdk": "tta5a3d31e3aecfb9b11://pay-button"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -295,7 +320,10 @@
|
|||||||
{
|
{
|
||||||
"path": "list",
|
"path": "list",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "商城订单"
|
"navigationBarTitleText": "商城订单",
|
||||||
|
"usingComponents": {
|
||||||
|
"pay-button": "tta5a3d31e3aecfb9b11://pay-button"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -513,7 +541,6 @@
|
|||||||
{
|
{
|
||||||
"path": "coupon-package",
|
"path": "coupon-package",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -538,14 +565,12 @@
|
|||||||
{
|
{
|
||||||
"path": "member-inter",
|
"path": "member-inter",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "member-interests",
|
"path": "member-interests",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
src/pages/client/address/components/ListPageTemp.vue
Normal file
235
src/pages/client/address/components/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||||
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
|
<slot name="top" />
|
||||||
|
<view class="list-content" v-if="list.length > 0">
|
||||||
|
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||||
|
class="flex-column-start news-item" @click="clickCell(item)">
|
||||||
|
<slot style="width: 100%" name="item" :data="{
|
||||||
|
...item,
|
||||||
|
...listExtraFields,
|
||||||
|
deleteSelect: !!item.deleteSelect,
|
||||||
|
}" />
|
||||||
|
</view>
|
||||||
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
|
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-container">
|
||||||
|
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||||
|
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="bottom" />
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListPageTemp",
|
||||||
|
props: {
|
||||||
|
getDataPromise: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
// 格式化请求结果的方法
|
||||||
|
resultFormatFunc: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
defult: () => { },
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
// 列表元素额外字段
|
||||||
|
listExtraFields: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
reloadFlag: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
// 是否分页
|
||||||
|
isPagiantion: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页数量, 不分页时有效
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 999,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disableScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
// 占位图片地址
|
||||||
|
emptyImage: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 空状态文本(传入时才显示)
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
refreshTriggered: false,
|
||||||
|
p: 1,
|
||||||
|
num: 10,
|
||||||
|
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
reloadFlag: {
|
||||||
|
handler(value) {
|
||||||
|
if (value) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
handler(data) {
|
||||||
|
if (data) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
handler(list) {
|
||||||
|
this.list = list;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRefresh() {
|
||||||
|
if (this.refreshTriggered) return;
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
// this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isPagiantion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLoading && this.total > this.list.length) {
|
||||||
|
this.p++;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickCell(item) {
|
||||||
|
this.$emit("clickCell", item);
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// TODO: 删除测试数据
|
||||||
|
if (!this.getDataPromise) {
|
||||||
|
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||||
|
return {
|
||||||
|
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||||
|
id: k,
|
||||||
|
len: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.total = 9;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.assign(
|
||||||
|
{},
|
||||||
|
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||||
|
this.requestData
|
||||||
|
);
|
||||||
|
this.getDataPromise(data)
|
||||||
|
.then((res) => {
|
||||||
|
const list =
|
||||||
|
this.p === 1
|
||||||
|
? res?.data || []
|
||||||
|
: [...this.list, ...(res?.data || [])];
|
||||||
|
this.list = this.resultFormatFunc
|
||||||
|
? this.resultFormatFunc(list)
|
||||||
|
: list;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
this.$emit("getList", this.list, res);
|
||||||
|
// console.log(this.list,'???')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-template-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.list-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 400rpx auto 0;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ration {
|
||||||
|
width: 160px;
|
||||||
|
height: 174px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -38,11 +38,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import AddressItem from "./components/AddressItem.vue";
|
import AddressItem from "./components/AddressItem.vue";
|
||||||
import PopUpModal from "../../../components/PopUpModal.vue";
|
import PopUpModal from "../../../components/PopUpModal.vue";
|
||||||
|
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||||
import { delAddress, getAddressList } from "../../../api/address";
|
import { delAddress, getAddressList } from "../../../api/address";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AddressItem,
|
AddressItem,
|
||||||
|
ListPageTemp,
|
||||||
PopUpModal,
|
PopUpModal,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@ -1,17 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="loginContainer">
|
<view class="loginContainer">
|
||||||
<view class="body">
|
<view class="body">
|
||||||
<image class="login-ground-img" :src="`${imgPrefix}loginGroundImg.png`" mode="widthFix" />
|
<image
|
||||||
|
class="login-ground-img"
|
||||||
|
:src="`${imgPrefix}loginGroundImg.png`"
|
||||||
|
mode="widthFix"
|
||||||
|
/>
|
||||||
<view class="btnConent">
|
<view class="btnConent">
|
||||||
<button v-show="!checked" class="loginBtn" @click="unCheckAndGetPhoneNumber">
|
<button
|
||||||
手机快捷登录
|
v-show="!checked"
|
||||||
|
class="loginBtn"
|
||||||
|
@click="unCheckAndGetPhoneNumber"
|
||||||
|
>
|
||||||
|
抖音用户信息授权登录
|
||||||
</button>
|
</button>
|
||||||
<button v-show="checked" class="loginBtn" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
|
<button
|
||||||
手机快捷登录
|
v-show="checked"
|
||||||
|
class="loginBtn"
|
||||||
|
open-type="getPhoneNumber"
|
||||||
|
@getphonenumber="getPhoneNumber"
|
||||||
|
>
|
||||||
|
抖音用户信息授权登录
|
||||||
</button>
|
</button>
|
||||||
<view class="notLoginBtn" @click="goBack">
|
<view class="notLoginBtn" @click="goBack"> 暂不登录 </view>
|
||||||
暂不登录
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -19,7 +30,11 @@
|
|||||||
<view class="radioWrapper">
|
<view class="radioWrapper">
|
||||||
<view class="checkbox-wrapper" @click.stop="changeChecked">
|
<view class="checkbox-wrapper" @click.stop="changeChecked">
|
||||||
<view class="checkbox" :class="{ 'checkbox-checked': checked }">
|
<view class="checkbox" :class="{ 'checkbox-checked': checked }">
|
||||||
<image v-if="checked" class="check-icon" :src="require('@/static/images/y.png')" />
|
<image
|
||||||
|
v-if="checked"
|
||||||
|
class="check-icon"
|
||||||
|
:src="require('@/static/images/y.png')"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
<!-- <view v-if="uncheckMessageDialog" class="tooltip">
|
<!-- <view v-if="uncheckMessageDialog" class="tooltip">
|
||||||
<view class="tooltip-content">请先勾选,同意后再进行登录</view>
|
<view class="tooltip-content">请先勾选,同意后再进行登录</view>
|
||||||
@ -28,7 +43,9 @@
|
|||||||
</view>
|
</view>
|
||||||
<text class="radioText">
|
<text class="radioText">
|
||||||
请阅读并同意
|
请阅读并同意
|
||||||
<text class="color" @click.stop="ptfwxy">《帮宠到家平台服务协议》</text>
|
<text class="color" @click.stop="ptfwxy"
|
||||||
|
>《帮宠到家平台服务协议》</text
|
||||||
|
>
|
||||||
<text class="color" @click.stop="ysxy">《隐私协议》</text>
|
<text class="color" @click.stop="ysxy">《隐私协议》</text>
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@ -49,8 +66,22 @@ export default {
|
|||||||
imgPrefix,
|
imgPrefix,
|
||||||
checked: false,
|
checked: false,
|
||||||
uncheckMessageDialog: false,
|
uncheckMessageDialog: false,
|
||||||
|
yaoqing_code: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
this.yaoqing_code = options.yaoqing_code
|
||||||
|
tt.login({
|
||||||
|
provider: "toutiao",
|
||||||
|
success: function (loginRes) {
|
||||||
|
|
||||||
|
console.log('获取 login code 成功:', loginRes);
|
||||||
|
},
|
||||||
|
fail: function (err) {
|
||||||
|
console.log('获取 login code 失败:', err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
mounted() { },
|
mounted() { },
|
||||||
methods: {
|
methods: {
|
||||||
ptfwxy() {
|
ptfwxy() {
|
||||||
@ -68,35 +99,6 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
changeChecked() {
|
changeChecked() {
|
||||||
uni.requestSubscribeMessage({
|
|
||||||
tmplIds: ['QoTeQwj4xw2UQMK5jI67MzAVOo6og76oqZ7BDIJW7cE', 'GPWlTkaNbi7JqvxltLKuZZMtKedSZfEKlirV7yOUu-0'],// TEMPLATE_ID替换为选用的模版id
|
|
||||||
success(res) {
|
|
||||||
uni.hideLoading()
|
|
||||||
if (res['QoTeQwj4xw2UQMK5jI67MzAVOo6og76oqZ7BDIJW7cE'] === 'accept') {
|
|
||||||
// setSubscribeStatus(true, '已订阅')
|
|
||||||
uni.showToast({
|
|
||||||
title: '订阅成功',
|
|
||||||
icon: 'success',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (res['GPWlTkaNbi7JqvxltLKuZZMtKedSZfEKlirV7yOUu-0'] === 'accept') {
|
|
||||||
// setSubscribeStatus(true, '已订阅')
|
|
||||||
uni.showToast({
|
|
||||||
title: '订阅成功',
|
|
||||||
icon: 'success',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail() {
|
|
||||||
uni.showToast({
|
|
||||||
title: '订阅失败',
|
|
||||||
icon: 'error',
|
|
||||||
})
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
// isSubscribing = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
this.checked = !this.checked;
|
this.checked = !this.checked;
|
||||||
},
|
},
|
||||||
unCheckAndGetPhoneNumber() {
|
unCheckAndGetPhoneNumber() {
|
||||||
@ -106,96 +108,190 @@ export default {
|
|||||||
duration: 2000
|
duration: 2000
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async getPhoneNumber(e) {
|
getmembeInfo(phone) {
|
||||||
// console.log(e,'?')
|
const inviteCode = this.yaoqing_code ? this.yaoqing_code : getApp().globalData.inviteCode
|
||||||
// return
|
|
||||||
// 检查是否有 code
|
|
||||||
if (!e.detail.code) {
|
|
||||||
uni.showToast({
|
|
||||||
title: "获取手机号失败,请重试",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({
|
|
||||||
title: "获取手机号中...",
|
|
||||||
icon: "none",
|
|
||||||
mask: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 登录前先记录是否有邀请码(referrerID),用于登录后导航判断
|
|
||||||
const hasReferrer =
|
|
||||||
this.$store.state?.user && this.$store.state.user.referrerID;
|
|
||||||
// 拿着code获取手机号
|
|
||||||
const codeRes = await getPhone(e.detail.code);
|
|
||||||
const phone = codeRes?.data?.phoneNumber || "";
|
|
||||||
|
|
||||||
if (!phone) {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: "获取手机号失败,请重试",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: "登录中...",
|
title: "登录中...",
|
||||||
icon: "none",
|
icon: "none",
|
||||||
mask: true,
|
mask: true,
|
||||||
});
|
});
|
||||||
|
login(phone).then((res) => {
|
||||||
// 手机号登录
|
uni.hideLoading()
|
||||||
login(phone)
|
this.$store.dispatch("user/setToken", res?.data?.token || "");
|
||||||
.then((res) => {
|
this.$store.dispatch("user/setUserInfo", res?.data || {});
|
||||||
uni.hideLoading();
|
var pages = getCurrentPages();
|
||||||
this.$store.dispatch("user/setToken", res?.data?.token || "");
|
console.log(pages,'sssss')
|
||||||
this.$store.dispatch("user/setUserInfo", res?.data || {});
|
if (inviteCode) {
|
||||||
|
uni.reLaunch({
|
||||||
// 如果是带邀请码(referrerID)的登录,统一跳转到首页
|
url: "/pages/client/index/index",
|
||||||
if (hasReferrer) {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: "/pages/client/index/index",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pages = getCurrentPages();
|
|
||||||
if (pages.length === 1) {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: "/pages/client/index/index",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.navigateBack();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: err || "登录失败,请重试",
|
|
||||||
icon: "none",
|
|
||||||
});
|
});
|
||||||
});
|
return
|
||||||
} catch (error) {
|
}
|
||||||
uni.hideLoading();
|
if (pages.length === 1) {
|
||||||
console.error("获取手机号失败:", error);
|
uni.reLaunch({
|
||||||
// 检查是否是 access_token 相关错误
|
url: "/pages/client/index/index",
|
||||||
const errorMsg = error?.message || error || "";
|
});
|
||||||
if (errorMsg.includes("access_token") || errorMsg.includes("invalid credential")) {
|
return;
|
||||||
|
}
|
||||||
|
uni.navigateBack();
|
||||||
|
}).catch((err) => {
|
||||||
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "服务暂时不可用,请稍后重试",
|
title: err || "登录失败,请重试",
|
||||||
icon: "none",
|
|
||||||
duration: 3000,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: errorMsg || "获取手机号失败,请重试",
|
|
||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// login( this.nickName, this.avatarUrl).then((res) => {
|
||||||
|
// uni.hideLoading()
|
||||||
|
// this.$store.dispatch("user/setToken", res?.data?.token || "");
|
||||||
|
// this.$store.dispatch("user/setUserInfo", res?.data || {});
|
||||||
|
// var pages = getCurrentPages();
|
||||||
|
// if (inviteCode) {
|
||||||
|
// uni.reLaunch({
|
||||||
|
// url: "/pages/client/index/index",
|
||||||
|
// });
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if (pages.length === 1) {
|
||||||
|
// uni.reLaunch({
|
||||||
|
// url: "/pages/client/index/index",
|
||||||
|
// });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// uni.navigateBack();
|
||||||
|
// }).catch((err) => {
|
||||||
|
// uni.hideLoading();
|
||||||
|
// uni.showToast({
|
||||||
|
// title: err || "登录失败,请重试",
|
||||||
|
// icon: "none",
|
||||||
|
// });
|
||||||
|
// }),
|
||||||
|
async getPhoneNumber(e) {
|
||||||
|
console.log('获取手机号事件:', e);
|
||||||
|
this.getmembeInfo(e.detail.code)
|
||||||
|
|
||||||
|
// // 检查是否有 code
|
||||||
|
// if (!e.detail || !e.detail.code) {
|
||||||
|
// uni.showToast({
|
||||||
|
// title: "用户取消授权或获取手机号失败",
|
||||||
|
// icon: "none",
|
||||||
|
// });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// uni.showLoading({
|
||||||
|
// title: "登录中...",
|
||||||
|
// icon: "none",
|
||||||
|
// mask: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const that = this;
|
||||||
|
// let loginCode = null;
|
||||||
|
|
||||||
|
// // 先获取 login code
|
||||||
|
// try {
|
||||||
|
// loginCode = await new Promise((resolve, reject) => {
|
||||||
|
// tt.login({
|
||||||
|
// provider: "toutiao",
|
||||||
|
// success: function (loginRes) {
|
||||||
|
// console.log('获取 login code 成功:', loginRes);
|
||||||
|
// resolve(loginRes.code);
|
||||||
|
// },
|
||||||
|
// fail: function (err) {
|
||||||
|
// console.log('获取 login code 失败:', err);
|
||||||
|
// reject(err);
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// } catch (loginErr) {
|
||||||
|
// throw new Error('获取登录凭证失败');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 拿着 phone code 和 login code 获取手机号
|
||||||
|
// let phone = null;
|
||||||
|
// try {
|
||||||
|
// const codeRes = await getPhone(e.detail.code, loginCode);
|
||||||
|
// phone = codeRes?.data?.phoneNumber || "";
|
||||||
|
// console.log('获取手机号成功:', phone);
|
||||||
|
// } catch (phoneErr) {
|
||||||
|
// console.error('获取手机号失败:', phoneErr);
|
||||||
|
// throw new Error('获取手机号失败');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!phone) {
|
||||||
|
// throw new Error('获取手机号失败,请重试');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
// tt.getUserProfile({
|
||||||
|
// success: async (profileRes) => {
|
||||||
|
// console.log('获取用户信息成功:', profileRes);
|
||||||
|
// const nickName = profileRes.userInfo?.nickName || '';
|
||||||
|
// const avatarUrl = profileRes.userInfo?.avatarUrl || '';
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const inviteCode = that.yaoqing_code ? that.yaoqing_code : getApp().globalData.inviteCode;
|
||||||
|
|
||||||
|
// // 登录
|
||||||
|
// const loginRes = await login(nickName, avatarUrl, phone);
|
||||||
|
// uni.hideLoading();
|
||||||
|
|
||||||
|
// that.$store.dispatch("user/setToken", loginRes?.data?.token || "");
|
||||||
|
// that.$store.dispatch("user/setUserInfo", loginRes?.data || {});
|
||||||
|
|
||||||
|
// var pages = getCurrentPages();
|
||||||
|
// if (inviteCode) {
|
||||||
|
// uni.reLaunch({
|
||||||
|
// url: "/pages/client/index/index",
|
||||||
|
// });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (pages.length === 1) {
|
||||||
|
// uni.reLaunch({
|
||||||
|
// url: "/pages/client/index/index",
|
||||||
|
// });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// uni.navigateBack();
|
||||||
|
// } catch (loginErr) {
|
||||||
|
// uni.hideLoading();
|
||||||
|
// console.error('登录失败:', loginErr);
|
||||||
|
// uni.showToast({
|
||||||
|
// title: loginErr || "登录失败,请重试",
|
||||||
|
// icon: "none",
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// fail: (profileErr) => {
|
||||||
|
// uni.hideLoading();
|
||||||
|
// console.log("getUserProfile 调用失败", profileErr);
|
||||||
|
// uni.showToast({
|
||||||
|
// title: "获取用户信息失败",
|
||||||
|
// icon: "none",
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// } catch (error) {
|
||||||
|
// uni.hideLoading();
|
||||||
|
// console.error("获取手机号失败:", error);
|
||||||
|
// const errorMsg = error?.message || error || "";
|
||||||
|
// if (errorMsg.includes("access_token") || errorMsg.includes("invalid credential")) {
|
||||||
|
// uni.showToast({
|
||||||
|
// title: "服务暂时不可用,请稍后重试",
|
||||||
|
// icon: "none",
|
||||||
|
// duration: 3000,
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// uni.showToast({
|
||||||
|
// title: errorMsg || "获取手机号失败,请重试",
|
||||||
|
// icon: "none",
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
loginAction() {
|
loginAction() {
|
||||||
if (!this.checked) {
|
if (!this.checked) {
|
||||||
@ -215,10 +311,6 @@ export default {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 登录前先记录是否有邀请码(referrerID),用于登录后导航判断
|
|
||||||
const hasReferrer =
|
|
||||||
this.$store.state?.user && this.$store.state.user.referrerID;
|
|
||||||
|
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: "加载中...",
|
title: "加载中...",
|
||||||
icon: "none",
|
icon: "none",
|
||||||
@ -228,15 +320,6 @@ export default {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.$store.dispatch("user/setToken", res?.data?.token || "");
|
this.$store.dispatch("user/setToken", res?.data?.token || "");
|
||||||
this.$store.dispatch("user/setUserInfo", res?.data || {});
|
this.$store.dispatch("user/setUserInfo", res?.data || {});
|
||||||
|
|
||||||
// 如果是带邀请码(referrerID)的登录,统一跳转到首页
|
|
||||||
if (hasReferrer) {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: "/pages/client/index/index",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pages = getCurrentPages();
|
var pages = getCurrentPages();
|
||||||
if (pages.length === 1) {
|
if (pages.length === 1) {
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
@ -295,8 +378,8 @@ export default {
|
|||||||
|
|
||||||
.notLoginBtn {
|
.notLoginBtn {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #FF19A0;
|
color: #ff19a0;
|
||||||
border: 1rpx solid #FF19A0;
|
border: 1rpx solid #ff19a0;
|
||||||
border-radius: 300rpx;
|
border-radius: 300rpx;
|
||||||
padding: 26rpx 0rpx;
|
padding: 26rpx 0rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -328,13 +411,13 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #FFFFFF;
|
background: #ffffff;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
&.checkbox-checked {
|
&.checkbox-checked {
|
||||||
border-color: #FF19A0;
|
border-color: #ff19a0;
|
||||||
background: #FF19A0;
|
background: #ff19a0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-icon {
|
.check-icon {
|
||||||
@ -357,7 +440,7 @@ export default {
|
|||||||
|
|
||||||
.tooltip-content {
|
.tooltip-content {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #FFFFFF;
|
color: #ffffff;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +463,7 @@ export default {
|
|||||||
color: #9b939a;
|
color: #9b939a;
|
||||||
|
|
||||||
.color {
|
.color {
|
||||||
color: #FF19A0;
|
color: #ff19a0;
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
src/pages/client/cart/components/ListPageTemp.vue
Normal file
235
src/pages/client/cart/components/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||||
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
|
<slot name="top" />
|
||||||
|
<view class="list-content" v-if="list.length > 0">
|
||||||
|
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||||
|
class="flex-column-start news-item" @click="clickCell(item)">
|
||||||
|
<slot style="width: 100%" name="item" :data="{
|
||||||
|
...item,
|
||||||
|
...listExtraFields,
|
||||||
|
deleteSelect: !!item.deleteSelect,
|
||||||
|
}" />
|
||||||
|
</view>
|
||||||
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
|
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-container">
|
||||||
|
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||||
|
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="bottom" />
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListPageTemp",
|
||||||
|
props: {
|
||||||
|
getDataPromise: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
// 格式化请求结果的方法
|
||||||
|
resultFormatFunc: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
defult: () => { },
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
// 列表元素额外字段
|
||||||
|
listExtraFields: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
reloadFlag: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
// 是否分页
|
||||||
|
isPagiantion: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页数量, 不分页时有效
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 999,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disableScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
// 占位图片地址
|
||||||
|
emptyImage: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 空状态文本(传入时才显示)
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
refreshTriggered: false,
|
||||||
|
p: 1,
|
||||||
|
num: 10,
|
||||||
|
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
reloadFlag: {
|
||||||
|
handler(value) {
|
||||||
|
if (value) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
handler(data) {
|
||||||
|
if (data) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
handler(list) {
|
||||||
|
this.list = list;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRefresh() {
|
||||||
|
if (this.refreshTriggered) return;
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
// this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isPagiantion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLoading && this.total > this.list.length) {
|
||||||
|
this.p++;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickCell(item) {
|
||||||
|
this.$emit("clickCell", item);
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// TODO: 删除测试数据
|
||||||
|
if (!this.getDataPromise) {
|
||||||
|
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||||
|
return {
|
||||||
|
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||||
|
id: k,
|
||||||
|
len: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.total = 9;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.assign(
|
||||||
|
{},
|
||||||
|
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||||
|
this.requestData
|
||||||
|
);
|
||||||
|
this.getDataPromise(data)
|
||||||
|
.then((res) => {
|
||||||
|
const list =
|
||||||
|
this.p === 1
|
||||||
|
? res?.data || []
|
||||||
|
: [...this.list, ...(res?.data || [])];
|
||||||
|
this.list = this.resultFormatFunc
|
||||||
|
? this.resultFormatFunc(list)
|
||||||
|
: list;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
this.$emit("getList", this.list, res);
|
||||||
|
// console.log(this.list,'???')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-template-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.list-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 400rpx auto 0;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ration {
|
||||||
|
width: 160px;
|
||||||
|
height: 174px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CartItem from "./components/CartItem.vue";
|
import CartItem from "./components/CartItem.vue";
|
||||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getCartList,
|
getCartList,
|
||||||
|
|||||||
@ -1,180 +1,146 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="flex-row-start goods-item" @click.stop="$emit('clickCard', data)">
|
<view class="goods-item" >
|
||||||
<image class="goods-img" :src="data.product_pic" mode="aspectFill" />
|
<image @click.stop="handleBuyNow" class="goods-img" :src="data.product_pic" mode="aspectFill" />
|
||||||
<view class="goods-content">
|
<view class="fs-24 app-fc-main goods-name">
|
||||||
<view class="text-multi-ellipse fs-28 app-fc-main app-font-bold goods-name">
|
{{ data.product_name || "" }}
|
||||||
{{ data.product_name || "" }}
|
</view>
|
||||||
</view>
|
<view class="flex-row-start label">
|
||||||
<view class="flex-row-start label">
|
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
|
||||||
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
|
<view class="fs-20 app-fc-main label-name">32人买过</view>
|
||||||
<view class="fs-20 app-fc-main label-name">{{data.sales}}人买过</view>
|
</view>
|
||||||
</view>
|
<view class="flex-row-between" style="margin-top: 12rpx; align-items: baseline;">
|
||||||
<view class="price-row">
|
<view class="price-wrapper">
|
||||||
<view class="flex-row-start">
|
<text class="fs-28" style="color: #FF19A0;">
|
||||||
<text class="fs-28 price-text">
|
¥
|
||||||
¥
|
<text class="fs-28">{{
|
||||||
<text class="fs-28">{{data.prices[0].original_price || 0 }}</text>
|
data.prices[0].original_price / 100 || 0
|
||||||
</text>
|
}}</text>
|
||||||
<text class="fs-20 price-label">到手价</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="fs-24 origin-price" v-if="minPrice.price_shichang">
|
<view class="buy-now-btn" @click.stop="handleBuyNow">
|
||||||
¥{{ minPrice.price_shichang || 0 }}
|
立即购买
|
||||||
</text>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<image
|
</view>
|
||||||
class="add-cart-icon"
|
|
||||||
:class="{ 'add-cart-icon-animate': isAnimating }"
|
|
||||||
:src="`${imgPrefix}mall-addCar.png`"
|
|
||||||
@click.stop="$emit('addToCar', data)"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { imgPrefix } from '@/utils/common';
|
import {
|
||||||
|
imgPrefix
|
||||||
|
} from '@/utils/common';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
index: {
|
index: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
imgPrefix,
|
imgPrefix,
|
||||||
isAnimating: false,
|
};
|
||||||
};
|
},
|
||||||
},
|
computed: {
|
||||||
computed: {
|
labelList() {
|
||||||
// minPrice() {
|
return (this.data?.label || "").split(",").filter((v) => !!v);
|
||||||
// let minPrice = {};
|
},
|
||||||
// let minPriceValue = 0;
|
},
|
||||||
// this.data.price_list.map((v) => {
|
mounted() {},
|
||||||
// if (!minPriceValue || minPriceValue > +v.price) {
|
methods: {
|
||||||
// minPriceValue = +v.price;
|
handleBuyNow() {
|
||||||
// minPrice = { ...v };
|
// 触发购买事件,通知父组件
|
||||||
// }
|
this.$emit('addToCar', this.data);
|
||||||
// });
|
},
|
||||||
// return minPrice;
|
triggerAddCartAnimation() {
|
||||||
// },
|
// 保留方法以兼容父组件调用
|
||||||
labelList() {
|
}
|
||||||
return this.data.label.split(",").filter((v) => !!v);
|
},
|
||||||
},
|
};
|
||||||
},
|
|
||||||
mounted() {},
|
|
||||||
methods: {
|
|
||||||
// 触发添加购物车动画
|
|
||||||
triggerAddCartAnimation() {
|
|
||||||
this.isAnimating = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.isAnimating = false;
|
|
||||||
}, 600);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.goods-item {
|
.goods-item {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 40rpx;
|
border-radius: 16rpx;
|
||||||
width: 100%;
|
padding: 20rpx;
|
||||||
padding: 20rpx;
|
box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
margin-bottom: 22rpx;
|
||||||
margin-bottom: 20rpx;
|
position: relative;
|
||||||
position: relative;
|
width: 100%;
|
||||||
border-bottom: 1rpx solid #F5F5F5;
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.goods-img {
|
.goods-img {
|
||||||
border-radius: 20rpx;
|
display: block;
|
||||||
width: 160rpx;
|
width: 100%;
|
||||||
height: 160rpx;
|
max-width: 100%;
|
||||||
}
|
height: 260rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-content {
|
.goods-name {
|
||||||
flex: 1;
|
margin: 16rpx 0 12rpx 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-left: 20rpx;
|
text-overflow: ellipsis;
|
||||||
}
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #3D3D3D;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-name {
|
.label {
|
||||||
margin: 0 0 12rpx;
|
background-color: #ffecf3;
|
||||||
}
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
padding: 4rpx 8rpx;
|
||||||
|
|
||||||
.label {
|
.hot-icon {
|
||||||
background-color: #ffecf3;
|
width: 28rpx;
|
||||||
display: inline-flex;
|
height: 28rpx;
|
||||||
align-items: center;
|
}
|
||||||
border-radius: 4rpx;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
|
|
||||||
.hot-icon {
|
.label-name {
|
||||||
width: 28rpx;
|
padding-left: 4rpx;
|
||||||
height: 28rpx;
|
color: #FF19A0;
|
||||||
margin-right: 6rpx;
|
font-size: 20rpx;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.label-name {
|
.flex-row-between {
|
||||||
padding: 4rpx 8rpx;
|
display: flex;
|
||||||
color: #FF19A0;
|
justify-content: space-between;
|
||||||
}
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-row {
|
.flex-row-start {
|
||||||
margin-top: 12rpx;
|
display: flex;
|
||||||
display: flex;
|
justify-content: flex-start;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-text {
|
.price-wrapper {
|
||||||
color: #3D3D3D;
|
display: flex;
|
||||||
}
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
.price-label {
|
.buy-now-btn {
|
||||||
color: #999;
|
background: #FF19A0;
|
||||||
margin-left: 8rpx;
|
color: #fff;
|
||||||
}
|
font-size: 20rpx;
|
||||||
|
padding: 10rpx 20rpx;
|
||||||
.origin-price {
|
border-radius: 30rpx;
|
||||||
color: #999;
|
font-weight: 500;
|
||||||
margin-top: 8rpx;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.add-cart-icon {
|
</style>
|
||||||
width: 44rpx;
|
|
||||||
height: 44rpx;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 20rpx;
|
|
||||||
right: 20rpx;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
|
|
||||||
&.add-cart-icon-animate {
|
|
||||||
animation: addCartBounce 0.6s ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes addCartBounce {
|
|
||||||
0% {
|
|
||||||
transform: scale(1) rotate(0deg);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
transform: scale(1.2) rotate(-10deg);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.3) rotate(10deg);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
transform: scale(1.1) rotate(-5deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1) rotate(0deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -2,12 +2,12 @@
|
|||||||
<view class="flex-column-start category-container">
|
<view class="flex-column-start category-container">
|
||||||
<!-- 自定义导航栏 -->
|
<!-- 自定义导航栏 -->
|
||||||
<view class="custom-navbar">
|
<view class="custom-navbar">
|
||||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
<view class="status-bar" style="height:60px;"></view>
|
||||||
<view class="navbar-content">
|
<view class="navbar-content">
|
||||||
<view class="back-btn" @click="handleBack">
|
<view class="back-btn" @click="handleBack">
|
||||||
<view class="back-icon"></view>
|
<view class="back-icon"></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="search-input-wrapper" :style="{ width: searchInputWidth + 'rpx' }">
|
<view class="search-input-wrapper" style="width:80%;">
|
||||||
<input class="search-input" type="text" placeholder="搜索商品" v-model="searchKeyword"
|
<input class="search-input" type="text" placeholder="搜索商品" v-model="searchKeyword"
|
||||||
@confirm="handleSearch" @focus="handleSearchFocus" />
|
@confirm="handleSearch" @focus="handleSearchFocus" />
|
||||||
</view>
|
</view>
|
||||||
@ -70,8 +70,14 @@
|
|||||||
<scroll-view class="category-right" scroll-y :refresher-enabled="true"
|
<scroll-view class="category-right" scroll-y :refresher-enabled="true"
|
||||||
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
<view class="goods-list">
|
<view class="goods-list">
|
||||||
<good-item v-for="good in goodsList" :key="good.product_id" :ref="`goodItem_${good.product_id}`"
|
<view class="goods-list-item left">
|
||||||
:data="good" @addToCar="addToCar" @clickCard="jumpToDetail" />
|
<good-item v-for="(good, i) in leftColumnGoods" :key="2 * i" :ref="`goodItem_${good.product_id}`"
|
||||||
|
:data="good" @addToCar="addToCar" />
|
||||||
|
</view>
|
||||||
|
<view class="goods-list-item right">
|
||||||
|
<good-item v-for="(good, i) in rightColumnGoods" :key="2 * i + 1" :ref="`goodItem_${good.product_id}`"
|
||||||
|
:data="good" @addToCar="addToCar" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@ -109,6 +115,7 @@ import CategoryModal from "./components/CategoryModal.vue";
|
|||||||
import {
|
import {
|
||||||
getGoodsListData
|
getGoodsListData
|
||||||
} from "../../../api/shop";
|
} from "../../../api/shop";
|
||||||
|
import { getCategoryGoodsWithCache } from "@/utils/goodsCache";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -152,6 +159,8 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
|
nextCursor: '',
|
||||||
|
hasMore: true,
|
||||||
refreshTriggered: false,
|
refreshTriggered: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
petOrderId: '',
|
petOrderId: '',
|
||||||
@ -179,6 +188,12 @@ export default {
|
|||||||
) || {}
|
) || {}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
leftColumnGoods() {
|
||||||
|
return this.goodsList.filter((v, i) => i % 2 === 0);
|
||||||
|
},
|
||||||
|
rightColumnGoods() {
|
||||||
|
return this.goodsList.filter((v, i) => i % 2 === 1);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getCategoryList();
|
this.getCategoryList();
|
||||||
@ -195,6 +210,10 @@ export default {
|
|||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
this.getCartListData();
|
this.getCartListData();
|
||||||
|
// 页面显示时检查是否需要刷新商品数据
|
||||||
|
if (this.selectCategoryId) {
|
||||||
|
this.getShopList(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectCategoryId(val) {
|
selectCategoryId(val) {
|
||||||
@ -224,27 +243,70 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
changeCateg(item) {
|
changeCateg(item) {
|
||||||
this.changeId = item.id
|
this.changeId = item.id
|
||||||
this.selectCategoryId = item.id;
|
this.selectCategoryId = item.id;
|
||||||
|
this.page = 1; // 切换分类时重置分页
|
||||||
|
this.nextCursor = '';
|
||||||
|
this.hasMore = true;
|
||||||
|
this.goodsList = []; // 清空当前商品列表
|
||||||
this.showAllCategory = false;
|
this.showAllCategory = false;
|
||||||
// console.log(item,'--')
|
this.getShopList(true); // 切换分类时强制刷新
|
||||||
|
|
||||||
},
|
},
|
||||||
// 商品列表
|
// 商品列表
|
||||||
getShopList() {
|
getShopList(forceRefresh = false) {
|
||||||
getGoodsListData({
|
// 如果不是第一页且没有更多数据,直接返回
|
||||||
|
if (this.page > 1 && !this.hasMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
type: this.changeId,
|
type: this.changeId,
|
||||||
p: this.page,
|
p: this.page,
|
||||||
num: this.size,
|
num: this.size,
|
||||||
keyword: "",
|
keyword: "",
|
||||||
is_tui: 0,
|
is_tui: 0,
|
||||||
})
|
cursor: this.nextCursor,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 第一页使用缓存,后续页直接请求
|
||||||
|
if (this.page > 1) {
|
||||||
|
getGoodsListData(params)
|
||||||
|
.then((res) => {
|
||||||
|
const list = res?.data.data?.products || res?.data || [];
|
||||||
|
const hasMore = res?.data.data?.has_more;
|
||||||
|
const nextCursor = res?.data.data?.next_cursor || '';
|
||||||
|
|
||||||
|
this.goodsList = [...this.goodsList, ...list];
|
||||||
|
this.hasMore = hasMore;
|
||||||
|
this.nextCursor = nextCursor;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一页使用缓存
|
||||||
|
getCategoryGoodsWithCache(params, forceRefresh)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const list = res?.data || [];
|
const list = res?.data?.products || res?.data || [];
|
||||||
this.goodsList =
|
const hasMore = res?.data?.has_more;
|
||||||
this.page === 1 ? list : [...this.goodsList, ...list];
|
const nextCursor = res?.data?.next_cursor || '';
|
||||||
this.total = res?.count || 0;
|
|
||||||
|
// 只有当数据有变化时才更新商品列表,避免不必要的刷新
|
||||||
|
if (res.hasChanged !== false || this.goodsList.length === 0) {
|
||||||
|
this.goodsList = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasMore = hasMore;
|
||||||
|
this.nextCursor = nextCursor;
|
||||||
|
this.total = res?.count || list.length || 0;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('获取商品列表失败', err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
@ -349,15 +411,17 @@ export default {
|
|||||||
url: `/pages/client/shop/details?product_id=${details.product_id}&petOrderId=${this.petOrderId}&petOrderAddressId=${this.petOrderAddressId}`,
|
url: `/pages/client/shop/details?product_id=${details.product_id}&petOrderId=${this.petOrderId}&petOrderAddressId=${this.petOrderAddressId}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onRefresh() {
|
onRefresh() {
|
||||||
this.refreshTriggered = true;
|
this.refreshTriggered = true;
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
this.size = 10;
|
this.size = 10;
|
||||||
this.total = 0;
|
this.total = 0;
|
||||||
this.getShopList();
|
this.nextCursor = '';
|
||||||
|
this.hasMore = true;
|
||||||
|
this.getShopList(true); // 下拉刷新时强制刷新
|
||||||
},
|
},
|
||||||
onLoadMore() {
|
onLoadMore() {
|
||||||
if (!this.isLoading && this.total > this.goodsList.length) {
|
if (!this.isLoading && this.hasMore) {
|
||||||
this.page++;
|
this.page++;
|
||||||
this.getShopList();
|
this.getShopList();
|
||||||
}
|
}
|
||||||
@ -385,7 +449,7 @@ export default {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: calc(var(--status-bar-height, 0px) + 88rpx + 20rpx);
|
padding-top: 0;
|
||||||
|
|
||||||
.custom-navbar {
|
.custom-navbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -394,7 +458,7 @@ export default {
|
|||||||
right: 0;
|
right: 0;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
background: #ff19a0;
|
background: #ff19a0;
|
||||||
border-radius: 0px 0px 16px 16px;
|
border-radius: 0px;
|
||||||
|
|
||||||
.status-bar {
|
.status-bar {
|
||||||
background: #ff19a0;
|
background: #ff19a0;
|
||||||
@ -406,6 +470,7 @@ export default {
|
|||||||
padding: 0 32rpx;
|
padding: 0 32rpx;
|
||||||
height: 88rpx;
|
height: 88rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
width: 60rpx;
|
width: 60rpx;
|
||||||
@ -504,7 +569,7 @@ export default {
|
|||||||
.category-content {
|
.category-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-top: 50rpx;
|
padding-top: 90rpx;
|
||||||
|
|
||||||
// 左侧:一级分类列表
|
// 左侧:一级分类列表
|
||||||
.category-list-left {
|
.category-list-left {
|
||||||
@ -588,6 +653,24 @@ export default {
|
|||||||
.category-right {
|
.category-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
.goods-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
padding: 0 20rpx 120rpx;
|
||||||
|
|
||||||
|
.goods-list-item {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
src/pages/client/coupon/ListPageTemp.vue
Normal file
235
src/pages/client/coupon/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||||
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
|
<slot name="top" />
|
||||||
|
<view class="list-content" v-if="list.length > 0">
|
||||||
|
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||||
|
class="flex-column-start news-item" @click="clickCell(item)">
|
||||||
|
<slot style="width: 100%" name="item" :data="{
|
||||||
|
...item,
|
||||||
|
...listExtraFields,
|
||||||
|
deleteSelect: !!item.deleteSelect,
|
||||||
|
}" />
|
||||||
|
</view>
|
||||||
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
|
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-container">
|
||||||
|
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||||
|
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="bottom" />
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListPageTemp",
|
||||||
|
props: {
|
||||||
|
getDataPromise: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
// 格式化请求结果的方法
|
||||||
|
resultFormatFunc: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
defult: () => { },
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
// 列表元素额外字段
|
||||||
|
listExtraFields: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
reloadFlag: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
// 是否分页
|
||||||
|
isPagiantion: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页数量, 不分页时有效
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 999,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disableScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
// 占位图片地址
|
||||||
|
emptyImage: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 空状态文本(传入时才显示)
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
refreshTriggered: false,
|
||||||
|
p: 1,
|
||||||
|
num: 10,
|
||||||
|
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
reloadFlag: {
|
||||||
|
handler(value) {
|
||||||
|
if (value) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
handler(data) {
|
||||||
|
if (data) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
handler(list) {
|
||||||
|
this.list = list;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRefresh() {
|
||||||
|
if (this.refreshTriggered) return;
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
// this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isPagiantion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLoading && this.total > this.list.length) {
|
||||||
|
this.p++;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickCell(item) {
|
||||||
|
this.$emit("clickCell", item);
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// TODO: 删除测试数据
|
||||||
|
if (!this.getDataPromise) {
|
||||||
|
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||||
|
return {
|
||||||
|
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||||
|
id: k,
|
||||||
|
len: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.total = 9;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.assign(
|
||||||
|
{},
|
||||||
|
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||||
|
this.requestData
|
||||||
|
);
|
||||||
|
this.getDataPromise(data)
|
||||||
|
.then((res) => {
|
||||||
|
const list =
|
||||||
|
this.p === 1
|
||||||
|
? res?.data || []
|
||||||
|
: [...this.list, ...(res?.data || [])];
|
||||||
|
this.list = this.resultFormatFunc
|
||||||
|
? this.resultFormatFunc(list)
|
||||||
|
: list;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
this.$emit("getList", this.list, res);
|
||||||
|
// console.log(this.list,'???')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-template-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.list-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 400rpx auto 0;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ration {
|
||||||
|
width: 160px;
|
||||||
|
height: 174px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListPageTemp from "@/components/ListPageTemp";
|
import ListPageTemp from "./ListPageTemp";
|
||||||
import CouponItem from "@/components/coupon/CouponItem";
|
import CouponItem from "@/components/coupon/CouponItem";
|
||||||
import { getCouponData, receiveCoupon } from "@/api/coupon";
|
import { getCouponData, receiveCoupon } from "@/api/coupon";
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TabsList from "@/components/TabsList.vue";
|
import TabsList from "@/components/TabsList.vue";
|
||||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
import ListPageTemp from "./ListPageTemp.vue";
|
||||||
import ServiceCouponItem from "@/components/coupon/ServiceCouponItem";
|
import ServiceCouponItem from "@/components/coupon/ServiceCouponItem";
|
||||||
|
|
||||||
import { getMyServiceCouponList } from "@/api/coupon";
|
import { getMyServiceCouponList } from "@/api/coupon";
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="home-page">
|
<view class="home-page">
|
||||||
<scroll-view class="homeContainer" scroll-y :show-scrollbar="false" :enhanced="true">
|
<!-- 微信复制弹窗 -->
|
||||||
|
<WeChatCopyModal ref="wechatCopyModal" />
|
||||||
|
|
||||||
|
<!-- 可拖动联系客服组件 -->
|
||||||
|
<DraggableContact ref="draggableContact" :onClick="handleContactClick" />
|
||||||
|
|
||||||
|
<scroll-view class="homeContainer" scroll-y :show-scrollbar="false" :enhanced="true" @scrolltolower="onLoadMore" :refresher-enabled="true" :refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh">
|
||||||
<view class="swiperWrapper">
|
<view class="swiperWrapper">
|
||||||
<swiper indicator-dots="true" autoplay="true" interval="3000" duration="500" circular="true"
|
<swiper indicator-dots="true" autoplay="true" interval="3000" duration="500" circular="true"
|
||||||
class="swiper">
|
class="swiper">
|
||||||
@ -9,23 +15,21 @@
|
|||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
|
|
||||||
<view class="userInfoWrapper">
|
<view class="userInfoWrapper">
|
||||||
<view class="userInfo" @click="handleUserInfo">
|
<view class="userInfo" @click="handleUserInfo">
|
||||||
<image class="avatar-img"
|
<view class="userContent">
|
||||||
:src="userInfo.avatar ? userInfo.avatar : `${imgPrefix}defaultHeadImg.png`"
|
<image
|
||||||
mode="aspectFill" />
|
:src=" userInfo.avatar ? userInfo.avatar : `${imgPrefix}home-head.png`"
|
||||||
<view class="userContent">
|
class="userAvatar" mode="aspectFill" />
|
||||||
|
<view class="user-info-wrapper">
|
||||||
<view class="userName">
|
<view class="userName">
|
||||||
{{ userInfo.username ? userInfo.username : "嗨,你好呀" }}
|
{{ userInfo.username ? userInfo.username : "嗨,你好呀" }}
|
||||||
</view>
|
</view>
|
||||||
<view class="userTips" v-if="!userInfo.userID">
|
<view class="userTips" v-if="!userInfo.userID">
|
||||||
登陆享受更多精彩内容
|
登陆享受更多精彩内容
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="user-membership-row">
|
<!-- <view v-else class="user-membership-row">
|
||||||
<view class="vipWrapper">
|
|
||||||
<image class="lableImg" :src="`${imgPrefix}home-vipLabel.png`" />
|
|
||||||
v{{ userInfo.vipLevel ? userInfo.vipLevel : 0 }}会员
|
|
||||||
</view>
|
|
||||||
<view
|
<view
|
||||||
v-if="userInfo.membershipTier && userInfo.membershipTier > 0"
|
v-if="userInfo.membershipTier && userInfo.membershipTier > 0"
|
||||||
class="membership-tier-badge"
|
class="membership-tier-badge"
|
||||||
@ -37,98 +41,41 @@
|
|||||||
>
|
>
|
||||||
<text class="membership-tier-text">{{ getMembershipTierText(userInfo.membershipTier) }}</text>
|
<text class="membership-tier-text">{{ getMembershipTierText(userInfo.membershipTier) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
<view class="loginBtn" @click="toLogin" v-if="!userInfo.userID">
|
<view class="loginBtn" @click="toLogin" v-if="!userInfo.userID">
|
||||||
注册/登陆
|
注册/登陆
|
||||||
</view>
|
</view>
|
||||||
<view class="loginBtn flexClass" v-else @click="toCouponList">
|
<view class="logoutBtn" @click="logout" v-if="userInfo.userID">
|
||||||
<view class="couponText">{{ userInfo.couponCount ? userInfo.couponCount : 0 }}张优惠券</view>
|
退出登录
|
||||||
<image :src="`${imgPrefix}home-rightWhite Arrow.png`" class="discountCoupon" />
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
<view class="shadowBackground" />
|
<view class="shadowBackground" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="menuBody">
|
<view class="recommand-goods-wrapper">
|
||||||
<view class="firstMenu">
|
<!-- <view class="fs-36 app-font-bold app-fc-main recommand-title">
|
||||||
<view class="itemWrapper" @click="toReservation">
|
推荐商品
|
||||||
<text class="titlWrapper" style="transform: translateY(-4rpx);">你们在哪我们去哪</text>
|
</view> -->
|
||||||
<view class="content">预约洗护</view>
|
<view class="goods-list">
|
||||||
<view class="tips">随时随地上车洗澡</view>
|
<view class="goods-list-item left">
|
||||||
<view class="itemImg">
|
<good-item v-for="(good, i) in leftColumnGoods" :index="2 * i" :key="2 * i" :data="good" :isHome="true"
|
||||||
<image class="menu-img-lg" :src="`${imgPrefix}home-menuBath.png`" mode="aspectFill" />
|
@addToCar="addToCar" />
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="line" />
|
<view class="goods-list-item right">
|
||||||
<view class="itemWrapper" @click="toDogTraining">
|
<good-item v-for="(good, i) in rightColumnGoods" :index="2 * i + 1" :key="2 * i + 1" :data="good" :isHome="true"
|
||||||
<text class="titlWrapper">狗狗训练</text>
|
@addToCar="addToCar" />
|
||||||
<view class="content">上门服务</view>
|
|
||||||
<view class="tips">上门训犬、寄养、喂猫、遛狗</view>
|
|
||||||
<view class="itemImg">
|
|
||||||
<image class="menu-img-sm" :src="`${imgPrefix}home-dogTraining.png`" mode="aspectFill" />
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="isLoadingGoods" class="loading-wrapper flex-center">
|
||||||
<view class="secondMenu">
|
<uni-load-more status="loading" :show-text="false" />
|
||||||
<scroll-view class="scrollWrapper" :scroll-x="secondMenuItemList.length > 4" enable-flex
|
|
||||||
:scroll-with-animation="false"
|
|
||||||
@scroll="onSecondMenuScroll">
|
|
||||||
<view class="secondMenuInner" :class="{ 'no-scroll': secondMenuItemList.length <= 4 }">
|
|
||||||
<view class="itemWrapper" v-for="(item, i) in secondMenuItemList" :key="i"
|
|
||||||
@click="handleNav(item)">
|
|
||||||
<view class="imgWrapper">
|
|
||||||
<image class="second-icon" :src="item.img" mode="aspectFill" />
|
|
||||||
</view>
|
|
||||||
<view class="itemTitle">
|
|
||||||
{{ item.title }}
|
|
||||||
</view>
|
|
||||||
<view class="itemTips">
|
|
||||||
{{ item.tips }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<view class="custom-indicator" v-if="secondMenuItemList.length > 4">
|
|
||||||
<view class="itemLine" :class="{ active: secondMenuIndicatorIndex === 0 }"></view>
|
|
||||||
<view class="itemLine" :class="{ active: secondMenuIndicatorIndex === 1 }"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="thirdMenu">
|
|
||||||
<view class="itemWrapper" @click="toPublicBenefit">
|
|
||||||
<view>
|
|
||||||
<view class="title">公益助力</view>
|
|
||||||
<view class="tips">帮我找个家</view>
|
|
||||||
</view>
|
|
||||||
<image class="third-icon" :src="`${imgPrefix}home-publicBenefit.png`" mode="aspectFill" />
|
|
||||||
</view>
|
|
||||||
<view class="itemWrapper" @click="toJoin">
|
|
||||||
<view>
|
|
||||||
<view class="title">加盟咨询</view>
|
|
||||||
<view class="tips">立即加盟咨询</view>
|
|
||||||
</view>
|
|
||||||
<image class="third-icon" :src="`${imgPrefix}home-joinIn.png`" mode="aspectFill" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="serviceMenu" @click="jumpToWeChat">
|
|
||||||
<view class="left">
|
|
||||||
<image class="service-icon" :src="`${imgPrefix}supportStaff.png`" mode="aspectFill" />
|
|
||||||
<view class="content">
|
|
||||||
<view class="title">在线客服</view>
|
|
||||||
<view class="tips">遇到什么问题您尽管说哦~</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view>
|
|
||||||
<image :src="`${imgPrefix}right-arrow.png`" class="rightArrow" />
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@ -136,28 +83,51 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
imgPrefix,
|
imgPrefix,
|
||||||
jumpToWeChat,
|
|
||||||
showLoginConfirmModal
|
showLoginConfirmModal
|
||||||
} from "@/utils/common";
|
} from "@/utils/common";
|
||||||
import {
|
import {
|
||||||
userWllet
|
userWllet
|
||||||
} from "../../../api/login";
|
} from "../../../api/login";
|
||||||
|
import {
|
||||||
|
loginOut,
|
||||||
|
} from "../../../api/user";
|
||||||
|
import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
|
||||||
|
import GoodItem from "../shop/components/GoodItem.vue";
|
||||||
|
import DraggableContact from "@/components/DraggableContact.vue";
|
||||||
|
import {
|
||||||
|
getGoodsClassify,
|
||||||
|
getGoodsListData
|
||||||
|
} from "@/api/shop";
|
||||||
|
import { getHomeGoodsWithCache } from "@/utils/goodsCache";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "HomePage",
|
name: "HomePage",
|
||||||
data() {
|
components: {
|
||||||
return {
|
GoodItem,
|
||||||
imgPrefix,
|
WeChatCopyModal,
|
||||||
couponCount: 1,
|
DraggableContact
|
||||||
swiperDataList: [
|
},
|
||||||
{
|
data() {
|
||||||
image: `${imgPrefix}banner1.png`
|
return {
|
||||||
},
|
imgPrefix,
|
||||||
{
|
couponCount: 1,
|
||||||
image: `${imgPrefix}bannerShare.png`
|
goodsList: [], // 商品列表
|
||||||
}
|
goodsTotal: 0, // 商品总数
|
||||||
],
|
goodPage: 1, // 当前页码
|
||||||
secondMenuItemList: [{
|
nextCursor: '', // 下一页游标
|
||||||
|
hasMore: true, // 是否还有更多数据
|
||||||
|
isLoadingGoods: false, // 是否正在加载商品
|
||||||
|
refreshTriggered: false, // 刷新是否已触发
|
||||||
|
cartCount: 0, // 购物车数量
|
||||||
|
swiperDataList: [
|
||||||
|
{
|
||||||
|
image: `${imgPrefix}banner1.png`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: `${imgPrefix}bannerShare.png`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
secondMenuItemList: [{
|
||||||
title: "邀请有礼",
|
title: "邀请有礼",
|
||||||
tips: "邀请好友得好礼",
|
tips: "邀请好友得好礼",
|
||||||
img: `${imgPrefix}home-invite .png`,
|
img: `${imgPrefix}home-invite .png`,
|
||||||
@ -199,18 +169,101 @@ export default {
|
|||||||
},
|
},
|
||||||
userInfo() {
|
userInfo() {
|
||||||
return this.$store.state?.user?.userInfo || {};
|
return this.$store.state?.user?.userInfo || {};
|
||||||
}
|
},
|
||||||
|
leftColumnGoods() {
|
||||||
|
return this.goodsList.filter((v, i) => i % 2 === 0);
|
||||||
|
},
|
||||||
|
rightColumnGoods() {
|
||||||
|
return this.goodsList.filter((v, i) => i % 2 === 1);
|
||||||
|
},
|
||||||
|
cartShowCount() {
|
||||||
|
return this.cartCount > 9 ? "9+" : this.cartCount;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.buyService()
|
this.goodPage = 1;
|
||||||
|
this.nextCursor = '';
|
||||||
|
this.hasMore = true;
|
||||||
|
this.goodsList = [];
|
||||||
|
this.getGoodsList()
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
// 页面显示时检查是否需要刷新商品数据
|
||||||
|
this.getGoodsList(false);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getGoodsList(forceRefresh = false) {
|
||||||
|
if (this.isLoadingGoods) return;
|
||||||
|
if (!this.hasMore && this.goodPage > 1) return;
|
||||||
|
this.isLoadingGoods = true;
|
||||||
|
const params = {
|
||||||
|
type: 0,
|
||||||
|
cursor: this.nextCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多时直接请求,不使用缓存
|
||||||
|
if (this.goodPage > 1) {
|
||||||
|
getGoodsListData(params)
|
||||||
|
.then((res) => {
|
||||||
|
const list = res?.data.data?.products || res?.data || [];
|
||||||
|
const hasMore = res?.data.data?.has_more;
|
||||||
|
const nextCursor = res?.data.data?.next_cursor || '';
|
||||||
|
|
||||||
|
this.goodsList = [...this.goodsList, ...list];
|
||||||
|
this.hasMore = hasMore;
|
||||||
|
this.nextCursor = nextCursor;
|
||||||
|
this.goodsTotal = res?.count || 0;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('获取商品列表失败', err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoadingGoods = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一页使用缓存
|
||||||
|
getHomeGoodsWithCache(params, forceRefresh)
|
||||||
|
.then((res) => {
|
||||||
|
const list = res?.data.data?.products || res?.data.data || [];
|
||||||
|
const hasMore = res?.data.data?.has_more;
|
||||||
|
const nextCursor = res?.data.data?.next_cursor || '';
|
||||||
|
|
||||||
|
// 只有当数据有变化时才更新商品列表,避免不必要的刷新
|
||||||
|
if (res.hasChanged !== false || this.goodsList.length === 0) {
|
||||||
|
this.goodsList = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasMore = hasMore;
|
||||||
|
this.nextCursor = nextCursor;
|
||||||
|
this.goodsTotal = res?.count || list.length || 0;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('获取商品列表失败', err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoadingGoods = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
toLogin() {
|
toLogin() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/client/auth/index",
|
url: "/pages/client/auth/index",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
logout() {
|
||||||
|
loginOut();
|
||||||
|
// 清除Vuex中的用户状态
|
||||||
|
this.$store.dispatch('user/deleteToken');
|
||||||
|
this.$store.dispatch('user/clearUserInfo');
|
||||||
|
// 清除本地缓存
|
||||||
|
uni.clearStorageSync();
|
||||||
|
uni.reLaunch({
|
||||||
|
url: "/pages/client/auth/index",
|
||||||
|
});
|
||||||
|
},
|
||||||
// 统一的 token 检查方法,未登录时弹窗让用户自主选择
|
// 统一的 token 检查方法,未登录时弹窗让用户自主选择
|
||||||
async checkTokenAndExecute(callback) {
|
async checkTokenAndExecute(callback) {
|
||||||
const token = uni.getStorageSync('token');
|
const token = uni.getStorageSync('token');
|
||||||
@ -245,27 +298,15 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
toDogTraining() {
|
toDogTraining() {
|
||||||
uni.navigateTo({
|
uni.showToast({
|
||||||
url: '/pageHome/service/index'
|
title: '暂未开放',
|
||||||
});
|
icon: 'none',
|
||||||
},
|
duration: 2000
|
||||||
// 我的钱包
|
});
|
||||||
buyService() {
|
return;
|
||||||
const value = JSON.parse(uni.getStorageSync('vuex'));
|
// uni.navigateTo({
|
||||||
console.log(value, '??')
|
// url: '/pageHome/service/index'
|
||||||
this.userId = value.user.userInfo.userID
|
// });
|
||||||
this.nick_name = value.user.userInfo.username
|
|
||||||
// this.yaoqing_code = value.user.userInfo.yaoqing_code
|
|
||||||
// console.log(value,'--')
|
|
||||||
userWllet(value.user.userInfo.userID).then((res) => {
|
|
||||||
uni.setStorage({ //存入Storage
|
|
||||||
key: 'userInfo',
|
|
||||||
data: { //存的数据(可以是多条)
|
|
||||||
'user_id': res.data.user_id,
|
|
||||||
'wallet_id': res.data.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onSecondMenuScroll(e) {
|
onSecondMenuScroll(e) {
|
||||||
const scrollLeft = e.detail.scrollLeft || 0;
|
const scrollLeft = e.detail.scrollLeft || 0;
|
||||||
@ -343,10 +384,75 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
jumpToWeChat() {
|
// 立即购买
|
||||||
this.checkTokenAndExecute(() => {
|
addToCar(goodsData) {
|
||||||
jumpToWeChat();
|
console.log(goodsData,'--=')
|
||||||
|
// Parse image_list and get the first image URL
|
||||||
|
let firstImageUrl = '';
|
||||||
|
const imageList = goodsData.product.attr_key_value_map.image_list;
|
||||||
|
if (imageList) {
|
||||||
|
try {
|
||||||
|
// Try to parse as JSON if it's a string
|
||||||
|
const parsedList = typeof imageList === 'string' ? JSON.parse(imageList) : imageList;
|
||||||
|
if (Array.isArray(parsedList) && parsedList.length > 0) {
|
||||||
|
firstImageUrl = parsedList[0].url || parsedList[0] || '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing image_list:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/client/order/create?product_id=${goodsData.product.product_id}`,
|
||||||
|
// success: (res) => {
|
||||||
|
// // 通过eventChannel向被打开页面传送数据
|
||||||
|
// res.eventChannel.emit("createOrder", {
|
||||||
|
// goodList: [{
|
||||||
|
// ...this.goodsData,
|
||||||
|
// goods_id:goodsData.product.out_id,
|
||||||
|
// // price_id:goodsData.prices[0].price_id,
|
||||||
|
// product_pic: firstImageUrl,
|
||||||
|
// number:1,
|
||||||
|
// goods_name: goodsData.product.product_name,
|
||||||
|
// price_name: goodsData?.product.product_name,
|
||||||
|
// goods_price: goodsData.sku.actual_amount / 100
|
||||||
|
// }, ],
|
||||||
|
// });
|
||||||
|
// },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// 跳转微信客服 - 使用自定义弹窗
|
||||||
|
jumpToWeChat() {
|
||||||
|
if (this.$refs.wechatCopyModal) {
|
||||||
|
this.$refs.wechatCopyModal.show({
|
||||||
|
title: '请添加客服号',
|
||||||
|
weChatCode: 'Wagoo2025'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 可拖动联系客服组件点击回调
|
||||||
|
handleContactClick() {
|
||||||
|
if (this.$refs.wechatCopyModal) {
|
||||||
|
this.$refs.wechatCopyModal.show({
|
||||||
|
title: '请添加客服号',
|
||||||
|
weChatCode: 'Wagoo2025'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isLoadingGoods && this.hasMore) {
|
||||||
|
this.goodPage++;
|
||||||
|
this.getGoodsList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRefresh() {
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.goodPage = 1;
|
||||||
|
this.nextCursor = '';
|
||||||
|
this.hasMore = true;
|
||||||
|
this.getGoodsList(true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -356,20 +462,43 @@ export default {
|
|||||||
.home-page {
|
.home-page {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #ffecf3;
|
background-color: #ffecf3;
|
||||||
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.homeContainer {
|
.recommand-goods-wrapper {
|
||||||
height: calc(100vh - #{$app_tabbar_height + 36});
|
.recommand-title {
|
||||||
background-color: #ffecf3;
|
margin-bottom: 10rpx;
|
||||||
overflow: scroll;
|
}
|
||||||
|
|
||||||
.avatar-img {
|
.goods-list {
|
||||||
width: 92rpx;
|
display: flex;
|
||||||
height: 92rpx;
|
flex-direction: row;
|
||||||
border-radius: 50%;
|
justify-content: flex-start;
|
||||||
}
|
align-items: flex-start;
|
||||||
|
margin-top: 50rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
|
||||||
.menu-img-lg {
|
.goods-list-item {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wrapper {
|
||||||
|
padding: 10rpx 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.homeContainer {
|
||||||
|
height: calc(100vh - #{$app_tabbar_height + 36});
|
||||||
|
background-color: #ffecf3;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
.menu-img-lg {
|
||||||
width: 160rpx;
|
width: 160rpx;
|
||||||
height: 160rpx;
|
height: 160rpx;
|
||||||
}
|
}
|
||||||
@ -383,7 +512,7 @@ export default {
|
|||||||
.third-icon,
|
.third-icon,
|
||||||
.service-icon {
|
.service-icon {
|
||||||
width: 96rpx;
|
width: 96rpx;
|
||||||
height: 96rpx;
|
height: 120rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-icon {
|
.service-icon {
|
||||||
@ -393,10 +522,13 @@ export default {
|
|||||||
|
|
||||||
.swiperWrapper {
|
.swiperWrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-top: -88rpx;
|
||||||
|
|
||||||
.swiper {
|
.swiper {
|
||||||
height: 552rpx;
|
height: 552rpx;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-top: 88rpx;
|
||||||
|
box-sizing: content-box;
|
||||||
|
|
||||||
.swiper-img {
|
.swiper-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -424,7 +556,22 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.userContent {
|
.userContent {
|
||||||
margin-left: 16rpx;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.userAvatar {
|
||||||
|
width: 92rpx;
|
||||||
|
height: 92rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.userName {
|
.userName {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -523,6 +670,16 @@ export default {
|
|||||||
margin-left: 8rpx;
|
margin-left: 8rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logoutBtn {
|
||||||
|
background: #FF19A0;
|
||||||
|
border-radius: 218px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 23rpx;
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadowBackground {
|
.shadowBackground {
|
||||||
@ -735,6 +892,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flexClass {
|
.flexClass {
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
<reservation
|
<reservation
|
||||||
v-show="activePageId === 'reservationPage'"
|
v-show="activePageId === 'reservationPage'"
|
||||||
ref="reservationPage"
|
ref="reservationPage"
|
||||||
|
:orderId="orderId"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<tab-bar
|
<tab-bar
|
||||||
@ -60,6 +61,7 @@ export default {
|
|||||||
path: "/pages/client/index/index",
|
path: "/pages/client/index/index",
|
||||||
},
|
},
|
||||||
getUserInfoPromise: null, // 用于防止重复调用 getUserInfo
|
getUserInfoPromise: null, // 用于防止重复调用 getUserInfo
|
||||||
|
orderId: null, // 存储从订单页面传递过来的 orderId
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -165,12 +167,17 @@ export default {
|
|||||||
this.$store.dispatch('user/setReferrerID', Number(option.referrerID) || 0);
|
this.$store.dispatch('user/setReferrerID', Number(option.referrerID) || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理从订单页面传递的 orderId
|
||||||
|
if (option?.orderId) {
|
||||||
|
this.orderId = option.orderId;
|
||||||
|
}
|
||||||
|
|
||||||
// 处理页面跳转参数
|
// 处理页面跳转参数
|
||||||
if (option?.activePageId) {
|
if (option?.activePageId) {
|
||||||
const targetPageId = option.activePageId;
|
const targetPageId = option.activePageId;
|
||||||
// 如果目标页面与当前页面不同,触发切换
|
// 如果目标页面与当前页面不同,触发切换
|
||||||
if (this.activePageId !== targetPageId) {
|
if (this.activePageId !== targetPageId) {
|
||||||
this.handleTabChange([targetPageId]);
|
this.handleTabChange([targetPageId], option.orderId);
|
||||||
} else {
|
} else {
|
||||||
this.activePageId = targetPageId;
|
this.activePageId = targetPageId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,13 +14,19 @@
|
|||||||
{{ userInfo.userID && userInfo.username ? userInfo.username : '嗨,你好呀' }}
|
{{ userInfo.userID && userInfo.username ? userInfo.username : '嗨,你好呀' }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="vipWrapper">
|
<view class="userPhone" v-if="userInfo.phone">
|
||||||
|
{{ userInfo.phone }}
|
||||||
|
</view>
|
||||||
|
<!-- <view class="vipWrapper">
|
||||||
<image class="lableImg" :src="`${imgPrefix}home-vipLabel.png`" mode=""></image>
|
<image class="lableImg" :src="`${imgPrefix}home-vipLabel.png`" mode=""></image>
|
||||||
v{{ userInfo.vipLevel || 1 }}会员
|
v{{ userInfo.vipLevel || 1 }}会员
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="userRight">
|
<view class="logoutBtn" @click="showLogoutModal = true">
|
||||||
|
退出登录
|
||||||
|
</view>
|
||||||
|
<!-- <view class="userRight">
|
||||||
<view class="userRgihtItemView" @click="jumpTo('/pages/client/recharge/index?tab=points')">
|
<view class="userRgihtItemView" @click="jumpTo('/pages/client/recharge/index?tab=points')">
|
||||||
<view class="num">
|
<view class="num">
|
||||||
{{ displayTotalPoints }}
|
{{ displayTotalPoints }}
|
||||||
@ -45,11 +51,11 @@
|
|||||||
券包
|
券包
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="myPriceView">
|
<!-- <view class="myPriceView">
|
||||||
<view style="display: flex;align-items: center;">
|
<view style="display: flex;align-items: center;">
|
||||||
<image :src="`${imgPrefix}accountPrice.png`" class="priceImg"></image>
|
<image :src="`${imgPrefix}accountPrice.png`" class="priceImg"></image>
|
||||||
<text class="fs-24">我的余额:{{ displayWalletBalance !== '' ? displayWalletBalance : 0 }}元</text>
|
<text class="fs-24">我的余额:{{ displayWalletBalance !== '' ? displayWalletBalance : 0 }}元</text>
|
||||||
@ -60,9 +66,9 @@
|
|||||||
去充值
|
去充值
|
||||||
<image :src="`${imgPrefix}right-arrow.png`" class="arrowImg"></image>
|
<image :src="`${imgPrefix}right-arrow.png`" class="arrowImg"></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<view class="menuView">
|
<!-- <view class="menuView">
|
||||||
<view class="menuItem" @click="jumpTo('/pages/client/petOrder/index')">
|
<view class="menuItem" @click="jumpTo('/pages/client/petOrder/index')">
|
||||||
<image class="menuImg" :src="`${imgPrefix}mine-resaOrder.png`"></image>
|
<image class="menuImg" :src="`${imgPrefix}mine-resaOrder.png`"></image>
|
||||||
<view class="menuTitle">
|
<view class="menuTitle">
|
||||||
@ -82,7 +88,7 @@
|
|||||||
邀请有礼
|
邀请有礼
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
<!-- <view class="mine-slide">
|
<!-- <view class="mine-slide">
|
||||||
<view class="recharge-slide">
|
<view class="recharge-slide">
|
||||||
<view class="scroll">
|
<view class="scroll">
|
||||||
@ -122,62 +128,55 @@
|
|||||||
<!-- <view class="meber-m" @click="jumpTo(`/pages/richText/member-interests?userId=${userInfo.id}&nick_name=${userInfo.nick_name}`)">
|
<!-- <view class="meber-m" @click="jumpTo(`/pages/richText/member-interests?userId=${userInfo.id}&nick_name=${userInfo.nick_name}`)">
|
||||||
<image class="meb-im" :src="`${imgPrefix}member-vip.png`" />
|
<image class="meb-im" :src="`${imgPrefix}member-vip.png`" />
|
||||||
</view> -->
|
</view> -->
|
||||||
|
|
||||||
|
|
||||||
<view class="navView">
|
<view class="navView">
|
||||||
<view class="navItem" @click="jumpTo('/pageHome/selectPet/index')">
|
<view class="navItem" @click="jumpTo('/pages/client/order/list')">
|
||||||
|
<view class="navTitle">
|
||||||
|
商城订单
|
||||||
|
</view>
|
||||||
|
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
||||||
|
</view>
|
||||||
|
<!-- <view class="navItem" @click="jumpTo('/pageHome/selectPet/index')">
|
||||||
<view class="navTitle">
|
<view class="navTitle">
|
||||||
我的宠物
|
我的宠物
|
||||||
</view>
|
</view>
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="navItem" @click="jumpTo('/pageHome/selectAddress/index')">
|
<view class="navItem" @click="jumpTo('/pageHome/selectAddress/index')">
|
||||||
<view class="navTitle">
|
<view class="navTitle">
|
||||||
收货地址
|
收货地址
|
||||||
</view>
|
</view>
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="navItem" @click="jumpTo('/pages/client/mine/welfare')">
|
|
||||||
<view class="navTitle">
|
|
||||||
我的公益
|
|
||||||
</view>
|
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
|
||||||
</view>
|
|
||||||
<view class="navItem" @click="jumpTo(`/pages/richText/exchange-coupons?userId=${userInfo.id}`)">
|
|
||||||
<view class="navTitle">
|
|
||||||
兑换码
|
|
||||||
</view>
|
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
|
||||||
</view>
|
|
||||||
<view class="navItem" @click="jumpTo(`/pages/client/butler/edit`)">
|
|
||||||
<view class="navTitle">
|
|
||||||
成为小哇
|
|
||||||
</view>
|
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
|
||||||
</view>
|
|
||||||
<view class="navItem" @click="showContact">
|
<view class="navItem" @click="showContact">
|
||||||
<view class="navTitle">
|
<view class="navTitle">
|
||||||
反馈建议
|
反馈建议
|
||||||
</view>
|
</view>
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="navItem" @click="jumpTo(`/pages/client/mine/help`)">
|
|
||||||
<view class="navTitle">
|
|
||||||
使用帮助
|
|
||||||
</view>
|
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
|
||||||
</view>
|
|
||||||
<view class="navItem" @click="jumpTo('/pages/client/news/index')">
|
<view class="navItem" @click="jumpTo('/pages/client/news/index')">
|
||||||
<view class="navTitle">
|
<view class="navTitle">
|
||||||
系统消息
|
系统消息
|
||||||
</view>
|
</view>
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="navItem" @click="jumpTo(`/pages/client/mine/aboutus`)">
|
<view class="navItem" @click="jumpTo('/pages/client/mine/qualification')">
|
||||||
|
<view class="navTitle">
|
||||||
|
服务资质
|
||||||
|
</view>
|
||||||
|
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
||||||
|
</view>
|
||||||
|
<!-- <view class="navItem" @click="jumpTo(`/pages/client/mine/aboutus`)">
|
||||||
<view class="navTitle">
|
<view class="navTitle">
|
||||||
关于平台
|
关于平台
|
||||||
</view>
|
</view>
|
||||||
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
<image class="navArrow" :src="`${imgPrefix}right-arrow.png`"></image>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="navItem" style="border: none;" v-if="userInfo.phone" @click="showLogoutModal = true">
|
<view class="navItem" style="border: none;" v-if="userInfo.phone" @click="showLogoutModal = true">
|
||||||
<view class="navTitle">
|
<view class="navTitle">
|
||||||
退出登录
|
退出登录
|
||||||
@ -186,16 +185,23 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<pop-up-modal v-if="showLogoutModal" content="确定要退出登录吗?" @cancel="showLogoutModal = false" @confirm="logout" />
|
<pop-up-modal v-if="showLogoutModal" content="确定要退出登录吗?" @cancel="showLogoutModal = false" @confirm="logout" />
|
||||||
|
|
||||||
<contact-modal v-if="showContactModal" :data="configInfo" @close="showContactModal = false" />
|
<contact-modal v-if="showContactModal" :data="configInfo" @close="showContactModal = false" />
|
||||||
|
|
||||||
|
<!-- 可拖动联系客服组件 -->
|
||||||
|
<DraggableContact ref="draggableContact" :onClick="handleContactClick" />
|
||||||
|
|
||||||
|
<WeChatCopyModal ref="wechatCopyModal" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CommonCell from "@/components/CommonCell.vue";
|
import CommonCell from "@/components/CommonCell.vue";
|
||||||
import PopUpModal from "../../../components/PopUpModal.vue";
|
import PopUpModal from "../../../components/PopUpModal.vue";
|
||||||
import ContactModal from "@/components/ContactModal.vue";
|
import ContactModal from "@/components/ContactModal.vue";
|
||||||
|
import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
|
||||||
|
import DraggableContact from "@/components/DraggableContact.vue";
|
||||||
import {
|
import {
|
||||||
loginOut,
|
loginOut,
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
@ -207,10 +213,12 @@
|
|||||||
} from "@/utils/common";
|
} from "@/utils/common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CommonCell,
|
CommonCell,
|
||||||
PopUpModal,
|
PopUpModal,
|
||||||
ContactModal,
|
ContactModal,
|
||||||
|
WeChatCopyModal,
|
||||||
|
DraggableContact,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -298,16 +306,29 @@
|
|||||||
logout() {
|
logout() {
|
||||||
loginOut();
|
loginOut();
|
||||||
this.showLogoutModal = false;
|
this.showLogoutModal = false;
|
||||||
|
// 清除Vuex中的用户状态
|
||||||
|
this.$store.dispatch('user/deleteToken');
|
||||||
|
this.$store.dispatch('user/clearUserInfo');
|
||||||
|
// 清除本地缓存
|
||||||
uni.clearStorageSync();
|
uni.clearStorageSync();
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: "/pages/client/auth/index",
|
url: "/pages/client/auth/index",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showContact() {
|
showContact() {
|
||||||
jumpToWeChat();
|
jumpToWeChat();
|
||||||
// this.getConfig();
|
// this.getConfig();
|
||||||
// this.showContactModal = true;
|
// this.showContactModal = true;
|
||||||
},
|
},
|
||||||
|
// 跳转微信客服 - 使用自定义弹窗(与商城页面一致)
|
||||||
|
handleContactClick() {
|
||||||
|
if (this.$refs.wechatCopyModal) {
|
||||||
|
this.$refs.wechatCopyModal.show({
|
||||||
|
title: '请添加客服号',
|
||||||
|
weChatCode: 'Wagoo2025'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -319,13 +340,17 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
height: calc(100vh - #{$app_tabbar_height + 26});
|
height: calc(100vh - #{$app_tabbar_height + 26});
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
.mineGroundImgView {
|
.mineGroundImgView {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-top: -88rpx;
|
||||||
|
|
||||||
.groundImg {
|
.groundImg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 552rpx;
|
height: 552rpx;
|
||||||
|
padding-top: 88rpx;
|
||||||
|
box-sizing: content-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +372,7 @@
|
|||||||
.userLeft {
|
.userLeft {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.userAvatar {
|
.userAvatar {
|
||||||
width: 92rpx;
|
width: 92rpx;
|
||||||
@ -374,6 +400,21 @@
|
|||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userPhone {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoutBtn {
|
||||||
|
background: #FF19A0;
|
||||||
|
color: #fff;
|
||||||
|
padding: 16rpx 32rpx;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userRight {
|
.userRight {
|
||||||
@ -448,11 +489,11 @@
|
|||||||
width: calc(100vw - 40rpx);
|
width: calc(100vw - 40rpx);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
margin-top: 20rpx;
|
margin-top:46rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
margin-bottom: 26rpx;
|
margin-bottom: 26rpx;
|
||||||
|
|
||||||
.navItem {
|
.navItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -472,6 +513,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 可拖动联系客服组件无需额外样式 */
|
||||||
|
|
||||||
.arrowImg {
|
.arrowImg {
|
||||||
width: 11rpx;
|
width: 11rpx;
|
||||||
height: 18rpx;
|
height: 18rpx;
|
||||||
|
|||||||
42
src/pages/client/mine/qualification.vue
Normal file
42
src/pages/client/mine/qualification.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<view class="qualification-container">
|
||||||
|
<image
|
||||||
|
src="/static/images/yyzz.png"
|
||||||
|
mode="widthFix"
|
||||||
|
class="qualification-image"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
jumpTo(url) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.qualification-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.qualification-image {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,7 +3,7 @@
|
|||||||
<view class="edit-content">
|
<view class="edit-content">
|
||||||
<view class="flex-row-between edit-cell">
|
<view class="flex-row-between edit-cell">
|
||||||
<text class="title">头像</text>
|
<text class="title">头像</text>
|
||||||
<button class="flex-row-end user-avator" open-type="chooseAvatar" @chooseavatar="chooseavatar">
|
<button class="flex-row-end user-avator" @click="chooseavatar">
|
||||||
<image class="avator-icon" :src="userInfo.head_pic_url" />
|
<image class="avator-icon" :src="userInfo.head_pic_url" />
|
||||||
<!-- <image class="arrow-icon" :src="`${imgPrefix}right-arrow.png`" /> -->
|
<!-- <image class="arrow-icon" :src="`${imgPrefix}right-arrow.png`" /> -->
|
||||||
</button>
|
</button>
|
||||||
@ -174,30 +174,41 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 更新头像
|
// 更新头像
|
||||||
async chooseavatar(e) {
|
chooseavatar() {
|
||||||
const {
|
let that = this;
|
||||||
avatarUrl
|
|
||||||
} = e.detail;
|
|
||||||
|
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: "上传中..."
|
title: "上传中..."
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
uni.chooseImage({
|
||||||
const { url, objectKey } = await uploadImageToOSS_PUT(avatarUrl);
|
count: 1, // 只选一张图片
|
||||||
console.log(url, objectKey, 'url, objectKey')
|
sourceType: ['album', 'camera'], // 来源:相册、相机
|
||||||
this.userInfo.head_pic_url = url; // 同时更新 head_pic_url,因为模板使用的是这个字段
|
success: async function(res) {
|
||||||
this.userInfo.head_pic = objectKey;
|
try {
|
||||||
this.$forceUpdate();
|
const { url, objectKey } = await uploadImageToOSS_PUT(res.tempFilePaths[0]);
|
||||||
uni.hideLoading();
|
that.userInfo.head_pic_url = url; // 同时更新 head_pic_url,因为模板使用的是这个字段
|
||||||
} catch (error) {
|
that.userInfo.head_pic = objectKey;
|
||||||
console.error('头像上传失败:', error);
|
that.$forceUpdate();
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: error?.message || "头像上传失败",
|
title: "上传成功",
|
||||||
icon: "none"
|
icon: "success",
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error('头像上传失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || "头像上传失败",
|
||||||
|
icon: "none"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: function(err) {
|
||||||
|
console.log('选择图片失败:', err.errMsg);
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onChange(value, key) {
|
onChange(value, key) {
|
||||||
this.userInfo[key] = value;
|
this.userInfo[key] = value;
|
||||||
|
|||||||
235
src/pages/client/news/components/ListPageTemp.vue
Normal file
235
src/pages/client/news/components/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||||
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
|
<slot name="top" />
|
||||||
|
<view class="list-content" v-if="list.length > 0">
|
||||||
|
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||||
|
class="flex-column-start news-item" @click="clickCell(item)">
|
||||||
|
<slot style="width: 100%" name="item" :data="{
|
||||||
|
...item,
|
||||||
|
...listExtraFields,
|
||||||
|
deleteSelect: !!item.deleteSelect,
|
||||||
|
}" />
|
||||||
|
</view>
|
||||||
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
|
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-container">
|
||||||
|
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||||
|
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="bottom" />
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListPageTemp",
|
||||||
|
props: {
|
||||||
|
getDataPromise: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
// 格式化请求结果的方法
|
||||||
|
resultFormatFunc: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
defult: () => { },
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
// 列表元素额外字段
|
||||||
|
listExtraFields: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
reloadFlag: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
// 是否分页
|
||||||
|
isPagiantion: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页数量, 不分页时有效
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 999,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disableScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
// 占位图片地址
|
||||||
|
emptyImage: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 空状态文本(传入时才显示)
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
refreshTriggered: false,
|
||||||
|
p: 1,
|
||||||
|
num: 10,
|
||||||
|
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
reloadFlag: {
|
||||||
|
handler(value) {
|
||||||
|
if (value) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
handler(data) {
|
||||||
|
if (data) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
handler(list) {
|
||||||
|
this.list = list;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRefresh() {
|
||||||
|
if (this.refreshTriggered) return;
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
// this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isPagiantion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLoading && this.total > this.list.length) {
|
||||||
|
this.p++;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickCell(item) {
|
||||||
|
this.$emit("clickCell", item);
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// TODO: 删除测试数据
|
||||||
|
if (!this.getDataPromise) {
|
||||||
|
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||||
|
return {
|
||||||
|
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||||
|
id: k,
|
||||||
|
len: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.total = 9;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.assign(
|
||||||
|
{},
|
||||||
|
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||||
|
this.requestData
|
||||||
|
);
|
||||||
|
this.getDataPromise(data)
|
||||||
|
.then((res) => {
|
||||||
|
const list =
|
||||||
|
this.p === 1
|
||||||
|
? res?.data || []
|
||||||
|
: [...this.list, ...(res?.data || [])];
|
||||||
|
this.list = this.resultFormatFunc
|
||||||
|
? this.resultFormatFunc(list)
|
||||||
|
: list;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
this.$emit("getList", this.list, res);
|
||||||
|
// console.log(this.list,'???')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-template-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.list-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 400rpx auto 0;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ration {
|
||||||
|
width: 160px;
|
||||||
|
height: 174px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -14,10 +14,12 @@
|
|||||||
getNoticeList
|
getNoticeList
|
||||||
} from "../../../api/notice";
|
} from "../../../api/notice";
|
||||||
import NewsItem from "./components/NewsItem.vue";
|
import NewsItem from "./components/NewsItem.vue";
|
||||||
|
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||||
import { imgPrefix } from "@/utils/common";
|
import { imgPrefix } from "@/utils/common";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NewsItem,
|
NewsItem,
|
||||||
|
ListPageTemp
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TabsList from "@/components/TabsList.vue";
|
import TabsList from "@/components/TabsList.vue";
|
||||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||||
import AfterSaleOrderItem from "./components/AfterSaleOrderItem.vue";
|
import AfterSaleOrderItem from "./components/AfterSaleOrderItem.vue";
|
||||||
|
|
||||||
import { getShopOrderList } from '@/api/shop'
|
import { getShopOrderList } from '@/api/shop'
|
||||||
|
|||||||
@ -4,15 +4,12 @@
|
|||||||
<image class="good-icon" :src="data[0].product_pic" mode="aspectFill" @click="$emit('clickGoodImg', data[0])" />
|
<image class="good-icon" :src="data[0].product_pic" mode="aspectFill" @click="$emit('clickGoodImg', data[0])" />
|
||||||
<view class="good-content" @click="$emit('clickGoodInfo', data[0])">
|
<view class="good-content" @click="$emit('clickGoodInfo', data[0])">
|
||||||
<view class="goods-row-first">
|
<view class="goods-row-first">
|
||||||
<view class="goods-name">{{ data[0].product_name || "" }}</view>
|
<view class="goods-name">{{ data[0].goods_name || "" }}</view>
|
||||||
<text class="goods-price">¥{{ data[0].goods_price || actual_price }}</text>
|
<view class="fs-28 app-fc-main goods-price">
|
||||||
</view>
|
¥{{ data[0].goods_price || data[0].product_actual_price }}
|
||||||
<view class="goods-row-second">
|
|
||||||
<view class="goods-spec">
|
|
||||||
{{ data[0].shuxing_name || "" }}{{ data[0].shuxing_name && data[0].price_name ? ";" : "" }}{{ data[0].price_name || "" }}
|
|
||||||
</view>
|
</view>
|
||||||
<text class="goods-count">共{{ data[0].number || 1 }}件</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="fs-24 app-fc-normal">共{{ data[0].number || 1 }}件</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -24,7 +21,7 @@
|
|||||||
<view class="good-info-right">
|
<view class="good-info-right">
|
||||||
<text class="good-total-price">¥{{ actual_price }}</text>
|
<text class="good-total-price">¥{{ actual_price }}</text>
|
||||||
<text class="fs-24 app-fc-normal good-num">
|
<text class="fs-24 app-fc-normal good-num">
|
||||||
共{{ goodsImgs.length }}件
|
共{{ totalCount }}件
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -56,6 +53,11 @@
|
|||||||
return sum + price * number;
|
return sum + price * number;
|
||||||
}, 0).toFixed(2);
|
}, 0).toFixed(2);
|
||||||
},
|
},
|
||||||
|
totalCount() {
|
||||||
|
return this.data.reduce((sum, item) => {
|
||||||
|
return sum + parseInt(item.number || 1);
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
console.log(this.data,'--')
|
console.log(this.data,'--')
|
||||||
@ -76,68 +78,43 @@
|
|||||||
|
|
||||||
&.good-info {
|
&.good-info {
|
||||||
padding-top: 20rpx;
|
padding-top: 20rpx;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
|
||||||
.good-icon {
|
.good-icon {
|
||||||
width: 100rpx;
|
width: 160rpx;
|
||||||
height: 100rpx;
|
height: 160rpx;
|
||||||
border-radius: 8rpx;
|
border-radius: 16rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
margin-right: 20rpx;
|
margin-right: 20rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.good-content {
|
.good-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
height: 160rpx;
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100rpx;
|
justify-content: space-between;
|
||||||
|
|
||||||
.goods-row-first {
|
.goods-row-first {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 8rpx;
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.goods-name {
|
.goods-name {
|
||||||
flex: 1;
|
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #3D3D3D;
|
color: #3D3D3D;
|
||||||
line-height: 40rpx;
|
line-height: 40rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-price {
|
.goods-price {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 28rpx;
|
|
||||||
color: #3D3D3D;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.goods-row-second {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.goods-spec {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goods-count {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
src/pages/client/order/components/ListPageTemp.vue
Normal file
235
src/pages/client/order/components/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||||
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
|
<slot name="top" />
|
||||||
|
<view class="list-content" v-if="list.length > 0">
|
||||||
|
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||||
|
class="flex-column-start news-item" @click="clickCell(item)">
|
||||||
|
<slot style="width: 100%" name="item" :data="{
|
||||||
|
...item,
|
||||||
|
...listExtraFields,
|
||||||
|
deleteSelect: !!item.deleteSelect,
|
||||||
|
}" />
|
||||||
|
</view>
|
||||||
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
|
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-container">
|
||||||
|
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||||
|
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="bottom" />
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListPageTemp",
|
||||||
|
props: {
|
||||||
|
getDataPromise: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
// 格式化请求结果的方法
|
||||||
|
resultFormatFunc: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
defult: () => { },
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
// 列表元素额外字段
|
||||||
|
listExtraFields: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
reloadFlag: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
// 是否分页
|
||||||
|
isPagiantion: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页数量, 不分页时有效
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 999,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disableScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
// 占位图片地址
|
||||||
|
emptyImage: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 空状态文本(传入时才显示)
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
refreshTriggered: false,
|
||||||
|
p: 1,
|
||||||
|
num: 10,
|
||||||
|
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
reloadFlag: {
|
||||||
|
handler(value) {
|
||||||
|
if (value) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
handler(data) {
|
||||||
|
if (data) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
handler(list) {
|
||||||
|
this.list = list;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRefresh() {
|
||||||
|
if (this.refreshTriggered) return;
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
// this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isPagiantion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLoading && this.total > this.list.length) {
|
||||||
|
this.p++;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickCell(item) {
|
||||||
|
this.$emit("clickCell", item);
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// TODO: 删除测试数据
|
||||||
|
if (!this.getDataPromise) {
|
||||||
|
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||||
|
return {
|
||||||
|
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||||
|
id: k,
|
||||||
|
len: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.total = 9;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.assign(
|
||||||
|
{},
|
||||||
|
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||||
|
this.requestData
|
||||||
|
);
|
||||||
|
this.getDataPromise(data)
|
||||||
|
.then((res) => {
|
||||||
|
const list =
|
||||||
|
this.p === 1
|
||||||
|
? res?.data || []
|
||||||
|
: [...this.list, ...(res?.data || [])];
|
||||||
|
this.list = this.resultFormatFunc
|
||||||
|
? this.resultFormatFunc(list)
|
||||||
|
: list;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
this.$emit("getList", this.list, res);
|
||||||
|
// console.log(this.list,'???')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-template-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.list-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 400rpx auto 0;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ration {
|
||||||
|
width: 160px;
|
||||||
|
height: 174px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,21 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="order-item">
|
<view class="order-item">
|
||||||
<view class="flex-row-between order-title" @click="jumpToDetails">
|
<view class="flex-row-between order-title" @click="jumpToDetails">
|
||||||
<text class="fs-24 app-fc-normal">
|
<text class="fs-24 app-fc-normal">
|
||||||
订单编号:{{ data.order_no || "-" }}
|
订单编号:{{ data.order_no || "-" }}
|
||||||
</text>
|
</text>
|
||||||
<!-- 待支付状态:显示倒计时横幅 -->
|
<!-- 待支付状态:显示倒计时横幅 -->
|
||||||
<view v-if="data.status === SHOP_ORDER_UNPAY && !data.tui_status && countDownTime > 0"
|
<view
|
||||||
class="order-status-banner">
|
v-if="
|
||||||
<view class="status-banner-left">
|
data.status === SHOP_ORDER_UNPAY &&
|
||||||
<text class="status-text">等待付款</text>
|
!data.tui_status &&
|
||||||
</view>
|
countDownTime > 0
|
||||||
<view class="status-banner-right">
|
"
|
||||||
<text class="countdown-text">{{ formatCountdown(countDownTime) }}</text>
|
class="order-status-banner"
|
||||||
</view>
|
>
|
||||||
</view>
|
<view class="status-banner-left">
|
||||||
<!-- 其他状态:显示原有样式 -->
|
<text class="status-text">等待付款</text>
|
||||||
<view v-else-if="
|
</view>
|
||||||
|
<view class="status-banner-right">
|
||||||
|
<text class="countdown-text">{{
|
||||||
|
formatCountdown(countDownTime)
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 其他状态:显示原有样式 -->
|
||||||
|
<view
|
||||||
|
v-else-if="
|
||||||
([
|
([
|
||||||
SHOP_ORDER_CANCEL,
|
SHOP_ORDER_CANCEL,
|
||||||
SHOP_ORDER_UNPAY,
|
SHOP_ORDER_UNPAY,
|
||||||
@ -26,367 +35,548 @@
|
|||||||
].includes(data.status) &&
|
].includes(data.status) &&
|
||||||
!data.tui_status) ||
|
!data.tui_status) ||
|
||||||
[SHOP_ORDER_AFTERSALE_REJECT].includes(data.tui_status)
|
[SHOP_ORDER_AFTERSALE_REJECT].includes(data.tui_status)
|
||||||
" class="flex-center fs-24 order-btn" :class="[
|
"
|
||||||
|
class="flex-center fs-24 order-btn"
|
||||||
|
:class="[
|
||||||
![SHOP_ORDER_DONE, SHOP_ORDER_CANCEL, SHOP_ORDER_UNREMARK].includes(
|
![SHOP_ORDER_DONE, SHOP_ORDER_CANCEL, SHOP_ORDER_UNREMARK].includes(
|
||||||
data.status
|
data.status
|
||||||
)
|
)
|
||||||
? 'app-fc-mark confirm'
|
? 'app-fc-mark confirm'
|
||||||
: 'cancel',
|
: 'cancel',
|
||||||
]">
|
]"
|
||||||
{{ orderStatus }}
|
>
|
||||||
</view>
|
{{ orderStatus }}
|
||||||
<view v-if="
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="
|
||||||
[SHOP_ORDER_AFTERSALE, SHOP_ORDER_AFTERSALE_DONE].includes(
|
[SHOP_ORDER_AFTERSALE, SHOP_ORDER_AFTERSALE_DONE].includes(
|
||||||
data.tui_status
|
data.tui_status
|
||||||
)
|
)
|
||||||
" class="flex-center fs-24 order-btn cancel">
|
"
|
||||||
{{ refundOrderStatus }}
|
class="flex-center fs-24 order-btn cancel"
|
||||||
</view>
|
>
|
||||||
</view>
|
{{ refundOrderStatus }}
|
||||||
<view class="order-content" :class="{ 'split-border': showStatusBtn }" @click="jumpToDetails">
|
</view>
|
||||||
<good-info :data="data.items" :actual_price="data.actual_price" />
|
</view>
|
||||||
<view v-if="data.type && data.type !== 1" class="fs-24 app-fc-mark order-type">
|
<view
|
||||||
随车订单
|
class="order-content"
|
||||||
</view>
|
:class="{ 'split-border': showStatusBtn }"
|
||||||
</view>
|
@click="jumpToDetails"
|
||||||
<view v-if="showStatusBtn" class="order-btns">
|
>
|
||||||
<!-- 待支付 -->
|
<good-info :data="data.items" :actual_price="data.actual_price" />
|
||||||
<template v-if="[SHOP_ORDER_UNPAY].includes(data.status)">
|
<view
|
||||||
<view class="flex-center fs-24 app-fc-main cancel-order-btn" @click.stop="$emit('cancelOrder', data)">
|
v-if="data.type && data.type !== 1"
|
||||||
取消订单
|
class="fs-24 app-fc-mark order-type"
|
||||||
</view>
|
>
|
||||||
<view class="order-btns-right">
|
随车订单
|
||||||
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="showStatusBtn" class="order-btns">
|
||||||
|
<!-- 待支付 -->
|
||||||
|
<template v-if="[SHOP_ORDER_UNPAY].includes(data.status)">
|
||||||
|
<view
|
||||||
|
class="flex-center fs-24 app-fc-main cancel-order-btn"
|
||||||
|
@click.stop="$emit('cancelOrder', data)"
|
||||||
|
>
|
||||||
|
取消订单
|
||||||
|
</view>
|
||||||
|
<view class="order-btns-right">
|
||||||
|
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
||||||
联系客服
|
联系客服
|
||||||
</view> -->
|
</view> -->
|
||||||
<view class="flex-center fs-24 app-fc-white status-btn confirm" @click.stop="$emit('pay', data)">
|
<view
|
||||||
立即支付
|
class="flex-center fs-24 app-fc-white status-btn confirm"
|
||||||
</view>
|
@click.stop="$emit('pay', data)"
|
||||||
</view>
|
>
|
||||||
</template>
|
立即支付
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 待发货 -->
|
<!-- 待预约 -->
|
||||||
<template v-if="[SHOP_ORDER_UNSLIVER].includes(data.status)">
|
<template v-if="[SHOP_ORDER_UNSLIVER].includes(data.status)">
|
||||||
<view class="flex-center fs-24 app-fc-main cancel-order-btn" @click.stop="$emit('cancelOrder', data)">
|
<view class="order-btns-right">
|
||||||
取消订单
|
<!-- <view
|
||||||
</view>
|
class="flex-center fs-24 app-fc-main status-btn"
|
||||||
<view class="order-btns-right">
|
@click.stop="remindRefund(data.order_id)"
|
||||||
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
>
|
||||||
联系客服
|
申请退款
|
||||||
</view> -->
|
</view> -->
|
||||||
<!-- <view
|
<view
|
||||||
|
class="flex-center fs-24 app-fc-white status-btn confirm"
|
||||||
|
@click.stop="jumpToReservation(data.order_id)"
|
||||||
|
>
|
||||||
|
立即预约
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 待收货 -->
|
||||||
|
<template v-if="[SHOP_ORDER_UNRECEIVE].includes(data.status)">
|
||||||
|
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
||||||
|
联系客服
|
||||||
|
</view> -->
|
||||||
|
|
||||||
|
<!-- <view
|
||||||
class="flex-center fs-24 app-fc-main status-btn"
|
class="flex-center fs-24 app-fc-main status-btn"
|
||||||
@click.stop="$emit('afterSale', data)"
|
@click.stop="$emit('afterSale', data)"
|
||||||
>
|
>
|
||||||
申请售后
|
申请售后
|
||||||
</view> -->
|
</view> -->
|
||||||
<view class="flex-center fs-24 app-fc-white status-btn confirm"
|
<!-- 随车订单不显示物流 -->
|
||||||
@click.stop="$emit('remindSilver', data)">
|
<!-- <view v-if="data.pay_type !== pay_type_BYCAR" class="flex-center fs-24 app-fc-main status-btn"
|
||||||
提醒发货
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 待收货 -->
|
|
||||||
<template v-if="[SHOP_ORDER_UNRECEIVE].includes(data.status)">
|
|
||||||
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
|
||||||
联系客服
|
|
||||||
</view> -->
|
|
||||||
<view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('afterSale', data)">
|
|
||||||
申请售后
|
|
||||||
</view>
|
|
||||||
<!-- 随车订单不显示物流 -->
|
|
||||||
<!-- <view v-if="data.pay_type !== pay_type_BYCAR" class="flex-center fs-24 app-fc-main status-btn"
|
|
||||||
@click.stop="$emit('checkSliver', data)">
|
@click.stop="$emit('checkSliver', data)">
|
||||||
查看物流
|
查看物流
|
||||||
</view> -->
|
</view> -->
|
||||||
<view class="flex-center fs-24 app-fc-white status-btn confirm"
|
<!-- <view
|
||||||
@click.stop="$emit('confirmSliver', data)">
|
class="flex-center fs-24 app-fc-white status-btn confirm"
|
||||||
确认收货
|
@click.stop="$emit('confirmSliver', data)"
|
||||||
</view>
|
>
|
||||||
</template>
|
确认收货
|
||||||
|
</view> -->
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 已签收未评价 -->
|
<!-- 已签收未评价 -->
|
||||||
<template v-if="[SHOP_ORDER_UNREMARK].includes(data.status)">
|
<template v-if="[SHOP_ORDER_UNREMARK].includes(data.status)">
|
||||||
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
||||||
联系客服
|
联系客服
|
||||||
</view> -->
|
</view> -->
|
||||||
<view class="flex-center fs-24 app-fc-white status-btn confirm" @click.stop="$emit('remark', data)">
|
<view
|
||||||
立即评价
|
class="flex-center fs-24 app-fc-white status-btn confirm"
|
||||||
</view>
|
@click.stop="$emit('remark', data)"
|
||||||
</template>
|
>
|
||||||
|
立即评价
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 已完成 -->
|
<!-- 已完成 -->
|
||||||
<template v-if="[SHOP_ORDER_DONE].includes(data.status)">
|
<template v-if="[SHOP_ORDER_DONE].includes(data.status)">
|
||||||
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
||||||
联系客服
|
联系客服
|
||||||
</view> -->
|
</view> -->
|
||||||
<view class="flex-center fs-24 app-fc-white status-btn confirm "
|
<view
|
||||||
style="visibility: hidden;"
|
class="flex-center fs-24 app-fc-white status-btn confirm"
|
||||||
>
|
style="visibility: hidden"
|
||||||
|
>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="[SHOP_ORDER_DONE].includes(data.status)">
|
<template v-if="[SHOP_ORDER_DONE].includes(data.status)">
|
||||||
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
|
||||||
联系客服
|
联系客服
|
||||||
</view> -->
|
</view> -->
|
||||||
<view class="flex-center fs-24 app-fc-white status-btn confirm"
|
<view
|
||||||
@click.stop="$emit('remarkDetails', data)">
|
class="flex-center fs-24 app-fc-white status-btn confirm"
|
||||||
查看评价
|
@click.stop="$emit('remarkDetails', data)"
|
||||||
</view>
|
>
|
||||||
</template>
|
查看评价
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PopUpModal from "@/components/PopUpModal.vue";
|
import PopUpModal from "@/components/PopUpModal.vue";
|
||||||
import GoodInfo from "./GoodInfo.vue";
|
import GoodInfo from "./GoodInfo.vue";
|
||||||
import {
|
import { walletTransaction, cancelPetOrderMall } from "../../../../api/login";
|
||||||
SHOP_ORDER_STATUS,
|
|
||||||
SHOP_ORDER_UNPAY,
|
|
||||||
SHOP_ORDER_UNSLIVER,
|
|
||||||
SHOP_ORDER_UNRECEIVE,
|
|
||||||
SHOP_ORDER_DONE,
|
|
||||||
SHOP_ORDER_CANCEL,
|
|
||||||
SHOP_ORDER_AFTERSALE,
|
|
||||||
SHOP_ORDER_AFTERSALE_DONE,
|
|
||||||
SHOP_ORDER_AFTERSALE_REJECT,
|
|
||||||
SHOP_ORDER_AFTERSALE_STATUS,
|
|
||||||
SHOP_ORDER_UNREMARK,
|
|
||||||
pay_type_ADDRESS,
|
|
||||||
pay_type_BYCAR,
|
|
||||||
pay_type_BYPET
|
|
||||||
} from "@/constants/app.business";
|
|
||||||
|
|
||||||
export default {
|
import {
|
||||||
props: {
|
SHOP_ORDER_STATUS,
|
||||||
data: {
|
SHOP_ORDER_UNPAY,
|
||||||
type: Object,
|
SHOP_ORDER_UNSLIVER,
|
||||||
default: () => {},
|
SHOP_ORDER_UNRECEIVE,
|
||||||
},
|
SHOP_ORDER_DONE,
|
||||||
},
|
SHOP_ORDER_CANCEL,
|
||||||
data() {
|
SHOP_ORDER_AFTERSALE,
|
||||||
return {
|
SHOP_ORDER_AFTERSALE_DONE,
|
||||||
pay_type_ADDRESS,
|
SHOP_ORDER_AFTERSALE_REJECT,
|
||||||
pay_type_BYCAR,
|
SHOP_ORDER_AFTERSALE_STATUS,
|
||||||
pay_type_BYPET,
|
SHOP_ORDER_UNREMARK,
|
||||||
SHOP_ORDER_STATUS,
|
pay_type_ADDRESS,
|
||||||
SHOP_ORDER_UNPAY,
|
pay_type_BYCAR,
|
||||||
SHOP_ORDER_UNSLIVER,
|
pay_type_BYPET,
|
||||||
SHOP_ORDER_UNRECEIVE,
|
} from "@/constants/app.business";
|
||||||
SHOP_ORDER_DONE,
|
|
||||||
SHOP_ORDER_CANCEL,
|
export default {
|
||||||
SHOP_ORDER_AFTERSALE,
|
props: {
|
||||||
SHOP_ORDER_AFTERSALE_DONE,
|
data: {
|
||||||
SHOP_ORDER_AFTERSALE_REJECT,
|
type: Object,
|
||||||
SHOP_ORDER_UNREMARK,
|
default: () => {},
|
||||||
showCancelModal: false,
|
},
|
||||||
countDownTime: 0,
|
},
|
||||||
countDownTimer: null,
|
data() {
|
||||||
};
|
return {
|
||||||
},
|
pay_type_ADDRESS,
|
||||||
components: {
|
pay_type_BYCAR,
|
||||||
GoodInfo,
|
pay_type_BYPET,
|
||||||
PopUpModal,
|
SHOP_ORDER_STATUS,
|
||||||
},
|
SHOP_ORDER_UNPAY,
|
||||||
options: {
|
SHOP_ORDER_UNSLIVER,
|
||||||
styleIsolation: "shared",
|
SHOP_ORDER_UNRECEIVE,
|
||||||
},
|
SHOP_ORDER_DONE,
|
||||||
computed: {
|
SHOP_ORDER_CANCEL,
|
||||||
orderStatus() {
|
SHOP_ORDER_AFTERSALE,
|
||||||
return SHOP_ORDER_STATUS[this.data.status] || "";
|
SHOP_ORDER_AFTERSALE_DONE,
|
||||||
},
|
SHOP_ORDER_AFTERSALE_REJECT,
|
||||||
refundOrderStatus() {
|
SHOP_ORDER_UNREMARK,
|
||||||
return SHOP_ORDER_AFTERSALE_STATUS[this.data.tui_status] || "";
|
showCancelModal: false,
|
||||||
},
|
countDownTime: 0,
|
||||||
showStatusBtn() {
|
countDownTimer: null,
|
||||||
return (
|
};
|
||||||
([
|
},
|
||||||
SHOP_ORDER_UNPAY,
|
components: {
|
||||||
SHOP_ORDER_UNSLIVER,
|
GoodInfo,
|
||||||
SHOP_ORDER_UNRECEIVE,
|
PopUpModal,
|
||||||
SHOP_ORDER_DONE,
|
},
|
||||||
SHOP_ORDER_UNREMARK,
|
options: {
|
||||||
].includes(this.data.status) &&
|
styleIsolation: "shared",
|
||||||
!this.data.tui_status) || [SHOP_ORDER_AFTERSALE_REJECT].includes(this.data.tui_status)
|
},
|
||||||
);
|
computed: {
|
||||||
},
|
// 抖音退款组件需要的订单ID
|
||||||
},
|
orderId() {
|
||||||
watch: {
|
return this.data.order_id || this.data.order_no || "";
|
||||||
showCancelModal(val) {
|
},
|
||||||
this.$emit("disableScroll", val);
|
// 抖音退款组件需要的订单状态(1=待核销/待发货,2=已核销/已发货)
|
||||||
},
|
orderStatusForSDK() {
|
||||||
'data.daojishi'(newVal) {
|
// 根据你的业务状态映射到抖音SDK需要的状态
|
||||||
if (this.data.status === this.SHOP_ORDER_UNPAY && newVal) {
|
// SHOP_ORDER_UNSLIVER = 待发货 -> 映射为 1
|
||||||
this.countDownTime = newVal;
|
if (this.data.status === SHOP_ORDER_UNSLIVER) {
|
||||||
this.startCountDown();
|
return 1;
|
||||||
}
|
}
|
||||||
},
|
// SHOP_ORDER_UNRECEIVE = 待收货 -> 映射为 2
|
||||||
'data.status'(newVal) {
|
if (this.data.status === SHOP_ORDER_UNRECEIVE) {
|
||||||
if (newVal === this.SHOP_ORDER_UNPAY && this.data.daojishi) {
|
return 2;
|
||||||
this.countDownTime = this.data.daojishi;
|
}
|
||||||
this.startCountDown();
|
return 1;
|
||||||
} else {
|
},
|
||||||
this.stopCountDown();
|
// 订单总金额(单位:分)
|
||||||
}
|
orderTotalAmount() {
|
||||||
},
|
const price = this.data.actual_price || 0;
|
||||||
},
|
// 转换为分
|
||||||
mounted() {
|
return Math.round(parseFloat(price) * 100);
|
||||||
// console.log(this.data,'--=')
|
},
|
||||||
if (this.data.status === SHOP_ORDER_UNPAY && this.data.daojishi) {
|
// UniApp 需要传入的退款参数对象
|
||||||
this.countDownTime = this.data.daojishi;
|
refundParams() {
|
||||||
this.startCountDown();
|
return {
|
||||||
}
|
reasonCode: [410],
|
||||||
},
|
note: "用户申请退款",
|
||||||
beforeDestroy() {
|
applySource: 101,
|
||||||
this.stopCountDown();
|
afterSaleType: 3,
|
||||||
},
|
needRefundPackFee: true,
|
||||||
methods: {
|
};
|
||||||
startCountDown() {
|
},
|
||||||
this.stopCountDown();
|
orderStatus() {
|
||||||
if (this.countDownTime > 0) {
|
return SHOP_ORDER_STATUS[this.data.status] || "";
|
||||||
this.countDownTimer = setInterval(() => {
|
},
|
||||||
if (this.countDownTime > 0) {
|
refundOrderStatus() {
|
||||||
this.countDownTime--;
|
return SHOP_ORDER_AFTERSALE_STATUS[this.data.tui_status] || "";
|
||||||
} else {
|
},
|
||||||
this.stopCountDown();
|
showStatusBtn() {
|
||||||
// 倒计时结束,可以触发刷新
|
return (
|
||||||
this.$emit('countdownEnd', this.data);
|
([
|
||||||
}
|
SHOP_ORDER_UNPAY,
|
||||||
}, 1000);
|
SHOP_ORDER_UNSLIVER,
|
||||||
}
|
SHOP_ORDER_UNRECEIVE,
|
||||||
},
|
SHOP_ORDER_DONE,
|
||||||
stopCountDown() {
|
SHOP_ORDER_UNREMARK,
|
||||||
if (this.countDownTimer) {
|
].includes(this.data.status) &&
|
||||||
clearInterval(this.countDownTimer);
|
!this.data.tui_status) ||
|
||||||
this.countDownTimer = null;
|
[SHOP_ORDER_AFTERSALE_REJECT].includes(this.data.tui_status)
|
||||||
}
|
);
|
||||||
},
|
},
|
||||||
formatCountdown(seconds) {
|
},
|
||||||
const hour = Math.floor(seconds / 3600);
|
watch: {
|
||||||
const minutes = Math.floor((seconds - hour * 3600) / 60);
|
showCancelModal(val) {
|
||||||
return `${hour}小时${minutes}分钟`;
|
this.$emit("disableScroll", val);
|
||||||
},
|
},
|
||||||
orderCancel() {
|
"data.daojishi"(newVal) {
|
||||||
this.showCancelModal = false;
|
if (this.data.status === this.SHOP_ORDER_UNPAY && newVal) {
|
||||||
},
|
this.countDownTime = newVal;
|
||||||
cancel() {
|
this.startCountDown();
|
||||||
this.showCancelModal = false;
|
}
|
||||||
},
|
},
|
||||||
jumpToDetails() {
|
"data.status"(newVal) {
|
||||||
this.$emit('jumpToDetails', this.data)
|
if (newVal === this.SHOP_ORDER_UNPAY && this.data.daojishi) {
|
||||||
},
|
this.countDownTime = this.data.daojishi;
|
||||||
},
|
this.startCountDown();
|
||||||
};
|
} else {
|
||||||
|
this.stopCountDown();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// console.log(this.data,'--=')
|
||||||
|
if (this.data.status === SHOP_ORDER_UNPAY && this.data.daojishi) {
|
||||||
|
this.countDownTime = this.data.daojishi;
|
||||||
|
this.startCountDown();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopCountDown();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
remindRefund(id) {
|
||||||
|
const data = {
|
||||||
|
id: Number(id),
|
||||||
|
};
|
||||||
|
uni.showLoading({
|
||||||
|
icon: "none",
|
||||||
|
title: "处理中",
|
||||||
|
mask: true,
|
||||||
|
});
|
||||||
|
cancelPetOrderMall(data)
|
||||||
|
.then((res) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
const plugin = tt.requirePlugin("lifeServicePlugin");
|
||||||
|
let res1 = {
|
||||||
|
code: 0,
|
||||||
|
result: "success",
|
||||||
|
msg: "success",
|
||||||
|
data: {
|
||||||
|
outOrderNo: "DYG177556196373493862814838179",
|
||||||
|
refundInfo: {
|
||||||
|
reason: ["计划有变,暂时不需要了"]
|
||||||
|
},
|
||||||
|
// refundInfo: {
|
||||||
|
// reason:["计划有变,暂时不需要了"],
|
||||||
|
// reasonCode:[401],
|
||||||
|
// },
|
||||||
|
itemOrderList:[
|
||||||
|
{
|
||||||
|
"itemOrderId": "1093391915337944382",
|
||||||
|
"refundAmount": 9000
|
||||||
|
}],
|
||||||
|
// goodsList: [
|
||||||
|
// { goodsId: "7625832097692813354", goodsType: 1, quantity: 1 },
|
||||||
|
// ],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// console.log('applyRefund options', JSON.stringify(options))
|
||||||
|
console.log(res1.data,'??')
|
||||||
|
plugin.applyRefund({
|
||||||
|
itemOrderList:res1.data.itemOrderList,
|
||||||
|
// goodsList: res1.data.goodsList,
|
||||||
|
outOrderNo: res1.data.outOrderNo,
|
||||||
|
refundInfo: res1.data.refundInfo,
|
||||||
|
success: (res) => {
|
||||||
|
uni.showToast({
|
||||||
|
title: "退款申请已提交",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
this.$emit("refund", this.data);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log("退款失败:", err);
|
||||||
|
// 处理session过期错误
|
||||||
|
if (err && err.errMsg && err.errMsg.includes("session已过期")) {
|
||||||
|
uni.showModal({
|
||||||
|
title: "提示",
|
||||||
|
content: "会话已过期,请重新登录后再试",
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: "确定",
|
||||||
|
success: () => {
|
||||||
|
// 清除当前用户信息并跳转到登录页
|
||||||
|
this.$store.dispatch("user/deleteToken");
|
||||||
|
this.$store.dispatch("user/clearUserInfo");
|
||||||
|
uni.clearStorageSync();
|
||||||
|
uni.reLaunch({
|
||||||
|
url: "/pages/client/auth/index",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: err?.errMsg || err?.msg || "退款失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error("获取退款信息失败:", err);
|
||||||
|
uni.showToast({
|
||||||
|
title: err?.msg || "获取退款信息失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 退款回调(组件触发)
|
||||||
|
handleRefund(event) {
|
||||||
|
const { status, result, outOrderNo } = event.detail;
|
||||||
|
console.log("退款回调:", { status, result, outOrderNo });
|
||||||
|
|
||||||
|
if (status === "success") {
|
||||||
|
uni.showToast({
|
||||||
|
title: "退款申请已提交",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
this.$emit("refund", this.data);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: result?.errMsg || "退款失败,请稍后重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
handleError(event) {
|
||||||
|
console.error("退款组件报错:", event.detail);
|
||||||
|
uni.showToast({
|
||||||
|
title: "组件加载失败,请稍后重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
startCountDown() {
|
||||||
|
this.stopCountDown();
|
||||||
|
if (this.countDownTime > 0) {
|
||||||
|
this.countDownTimer = setInterval(() => {
|
||||||
|
if (this.countDownTime > 0) {
|
||||||
|
this.countDownTime--;
|
||||||
|
} else {
|
||||||
|
this.stopCountDown();
|
||||||
|
// 倒计时结束,可以触发刷新
|
||||||
|
this.$emit("countdownEnd", this.data);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stopCountDown() {
|
||||||
|
if (this.countDownTimer) {
|
||||||
|
clearInterval(this.countDownTimer);
|
||||||
|
this.countDownTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatCountdown(seconds) {
|
||||||
|
const hour = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds - hour * 3600) / 60);
|
||||||
|
return `${hour}小时${minutes}分钟`;
|
||||||
|
},
|
||||||
|
orderCancel() {
|
||||||
|
this.showCancelModal = false;
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.showCancelModal = false;
|
||||||
|
},
|
||||||
|
jumpToDetails() {
|
||||||
|
this.$emit("jumpToDetails", this.data);
|
||||||
|
},
|
||||||
|
jumpToReservation(orderId) {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: `/pages/client/index/index?activePageId=reservationPage&orderId=${orderId}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.order-item {
|
.order-item {
|
||||||
width: calc(100vw - 40rpx);
|
width: calc(100vw - 40rpx);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin: 20rpx;
|
margin: 20rpx;
|
||||||
border-radius: 30rpx;
|
border-radius: 30rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
.order-title {
|
.order-title {
|
||||||
padding-bottom: 20rpx;
|
padding-bottom: 20rpx;
|
||||||
border-bottom: 1rpx solid #ececec;
|
border-bottom: 1rpx solid #ececec;
|
||||||
|
|
||||||
.order-btn {
|
.order-btn {
|
||||||
width: 104rpx;
|
width: 104rpx;
|
||||||
height: 48rpx;
|
height: 48rpx;
|
||||||
border-radius: 48rpx;
|
border-radius: 48rpx;
|
||||||
|
|
||||||
&.confirm {
|
&.confirm {
|
||||||
background: #fef6ff;
|
background: #fef6ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.cancel {
|
&.cancel {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
color: #afa5ae;
|
color: #afa5ae;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-status-banner {
|
.order-status-banner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 48rpx;
|
border-radius: 48rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.status-banner-left {
|
.status-banner-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #FF19A0;
|
background: #ff19a0;
|
||||||
padding: 8rpx;
|
padding: 8rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 10px 0px 10px 10px;
|
border-radius: 10px 0px 10px 10px;
|
||||||
|
|
||||||
.status-text {
|
.status-text {
|
||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
color: #FFFFFF;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-banner-right {
|
.status-banner-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #FFF0F8;
|
background: #fff0f8;
|
||||||
padding: 8rpx;
|
padding: 8rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.countdown-text {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #ff19a0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.countdown-text {
|
.order-btns {
|
||||||
font-size: 20rpx;
|
padding-top: 20rpx;
|
||||||
color: #FF19A0;
|
display: flex;
|
||||||
}
|
justify-content: flex-end;
|
||||||
}
|
align-items: center;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
.cancel-order-btn {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9b939a;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
.order-btns {
|
.order-btns-right {
|
||||||
padding-top: 20rpx;
|
display: flex;
|
||||||
display: flex;
|
align-items: center;
|
||||||
justify-content: space-between;
|
}
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.cancel-order-btn {
|
.status-btn {
|
||||||
font-size: 24rpx;
|
width: 70px;
|
||||||
color: #9B939A;
|
height: 34px;
|
||||||
padding: 16rpx 0;
|
// padding: 16rpx 20rpx;
|
||||||
}
|
border-radius: 64rpx;
|
||||||
|
border: 1px solid #ff19a0;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
|
||||||
.order-btns-right {
|
&.confirm {
|
||||||
display: flex;
|
color: $app_color_main;
|
||||||
align-items: center;
|
border: 1px solid $app_color_main;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status-btn {
|
::v-deep {
|
||||||
padding: 16rpx 20rpx;
|
.good-info-multi {
|
||||||
border-radius: 64rpx;
|
padding-top: 20rpx;
|
||||||
border: 1px solid #9b939a;
|
}
|
||||||
margin-left: 20rpx;
|
}
|
||||||
|
}
|
||||||
&.confirm {
|
</style>
|
||||||
color: $app_color_main;
|
|
||||||
border: 1px solid $app_color_main;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep {
|
|
||||||
.good-info-multi {
|
|
||||||
padding-top: 20rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -9,8 +9,8 @@
|
|||||||
<view v-for="(data, index) in list" :key="data.order_id" :class="{ left: index % 2 === 0 }"
|
<view v-for="(data, index) in list" :key="data.order_id" :class="{ left: index % 2 === 0 }"
|
||||||
class="flex-column-start news-item">
|
class="flex-column-start news-item">
|
||||||
<order-item :data="data" @disableScroll="disableScrollAction" @cancelOrder="cancelOrderAction"
|
<order-item :data="data" @disableScroll="disableScrollAction" @cancelOrder="cancelOrderAction"
|
||||||
@concactService="jumpToWeChat" @confirmSliver="confirmSliver" @remindSilver="remindSliver" @pay="pay"
|
@refund="handleRefundFromItem" @concactService="jumpToWeChat" @confirmSliver="confirmSliver" @remindSilver="remindSliver" @pay="pay"
|
||||||
@afterSale="afterSale" @checkSliver="checkSliver" @remark="remark" @remarkDetails="remarkDetails"
|
@checkSliver="checkSliver" @remark="remark" @remarkDetails="remarkDetails"
|
||||||
@jumpToDetails="jumpToDetails" />
|
@jumpToDetails="jumpToDetails" />
|
||||||
</view>
|
</view>
|
||||||
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
@ -42,15 +42,8 @@
|
|||||||
<view v-if="elasticLayer" class="elastic-layer" />
|
<view v-if="elasticLayer" class="elastic-layer" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 右侧浮动联系客服按钮 -->
|
<!-- 可拖动联系客服组件 -->
|
||||||
<view class="contact-float-btn" :style="{ right: contactBtnRight + 'rpx', bottom: contactBtnBottom + 'rpx' }"
|
<DraggableContact ref="draggableContact" :onClick="handleContactBtnClick" />
|
||||||
@touchstart="onContactBtnTouchStart" @touchmove="onContactBtnTouchMove" @touchend="onContactBtnTouchEnd"
|
|
||||||
@click="handleContactBtnClick">
|
|
||||||
<image class="contact-icon" :src="`${imgPrefix}supportStaff.png`" />
|
|
||||||
<view class="contact-btn fs-20">
|
|
||||||
联系客服
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="additional-bottom" v-if="additionalBom">
|
<view class="additional-bottom" v-if="additionalBom">
|
||||||
<view class="recharge-method">
|
<view class="recharge-method">
|
||||||
@ -61,8 +54,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="wechat" @click.stop="selectOption1('1')">
|
<view class="wechat" @click.stop="selectOption1('1')">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
|
||||||
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
|
||||||
@ -81,10 +74,11 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<pop-up-modal v-if="showCancelModal" content="确定要取消该订单吗?" @confirm="orderCancel"
|
<pop-up-modal v-if="showCancelModal" :content="cancelModalContent" @confirm="orderCancel"
|
||||||
@cancel="showCancelModal = false" />
|
@cancel="showCancelModal = false" />
|
||||||
|
|
||||||
<contact-modal v-if="showConcact" @close="showConcact = false" />
|
<contact-modal v-if="showConcact" @close="showConcact = false" />
|
||||||
|
<WeChatCopyModal ref="wechatCopyModal" />
|
||||||
|
|
||||||
<pop-up-modal v-if="showSliverModal" content="是否要确认收货?" @confirm="confirmReceiveOrder"
|
<pop-up-modal v-if="showSliverModal" content="是否要确认收货?" @confirm="confirmReceiveOrder"
|
||||||
@cancel="showSliverModal = false" />
|
@cancel="showSliverModal = false" />
|
||||||
@ -99,13 +93,15 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TabsList from "@/components/TabsList.vue";
|
import TabsList from "@/components/TabsList.vue";
|
||||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||||
import OrderItem from "./components/OrderItem.vue";
|
import OrderItem from "./components/OrderItem.vue";
|
||||||
import PopUpModal from "@/components/PopUpModal.vue";
|
import PopUpModal from "@/components/PopUpModal.vue";
|
||||||
import SuccessModal from "@/components/SuccessModal.vue";
|
import SuccessModal from "@/components/SuccessModal.vue";
|
||||||
import ContactModal from "@/components/ContactModal.vue";
|
import ContactModal from "@/components/ContactModal.vue";
|
||||||
import SliverInfo from "./components/SliverInfo.vue";
|
import SliverInfo from "./components/SliverInfo.vue";
|
||||||
import { walletTransaction, cancelPetOrderRefund, cancelPetOrderMall } from "../../../api/login";
|
import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
|
||||||
|
import DraggableContact from "@/components/DraggableContact.vue";
|
||||||
|
import { walletTransaction, cancelPetOrderRefund, cancelPetOrderMall,cancelUnpaid } from "../../../api/login";
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -134,6 +130,8 @@ export default {
|
|||||||
ContactModal,
|
ContactModal,
|
||||||
SuccessModal,
|
SuccessModal,
|
||||||
SliverInfo,
|
SliverInfo,
|
||||||
|
WeChatCopyModal,
|
||||||
|
DraggableContact,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -151,6 +149,7 @@ export default {
|
|||||||
elasticLayer: false,
|
elasticLayer: false,
|
||||||
disableScroll: false,
|
disableScroll: false,
|
||||||
showCancelModal: false,
|
showCancelModal: false,
|
||||||
|
cancelModalContent: "确定要取消该订单吗?",
|
||||||
showConcact: false,
|
showConcact: false,
|
||||||
showRemindSliver: false,
|
showRemindSliver: false,
|
||||||
showSliverModal: false,
|
showSliverModal: false,
|
||||||
@ -163,14 +162,6 @@ export default {
|
|||||||
p: 1,
|
p: 1,
|
||||||
num: 10,
|
num: 10,
|
||||||
imgPrefix,
|
imgPrefix,
|
||||||
// 联系客服按钮拖拽相关
|
|
||||||
contactBtnRight: 0, // 默认右侧距离,始终为0
|
|
||||||
contactBtnBottom: 0, // 默认底部距离,需要计算
|
|
||||||
isDragging: false, // 是否正在拖拽
|
|
||||||
touchStartX: 0, // 触摸开始X坐标
|
|
||||||
touchStartY: 0, // 触摸开始Y坐标
|
|
||||||
initialRight: 0, // 初始右侧距离
|
|
||||||
initialBottom: 0, // 初始底部距离
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -185,17 +176,17 @@ export default {
|
|||||||
id: SHOP_ORDER_UNPAY,
|
id: SHOP_ORDER_UNPAY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "待发货",
|
name: "待预约",
|
||||||
id: SHOP_ORDER_UNSLIVER,
|
id: SHOP_ORDER_UNSLIVER,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
name: "待收货",
|
// name: "待收货",
|
||||||
id: SHOP_ORDER_UNRECEIVE,
|
// id: SHOP_ORDER_UNRECEIVE,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: "待评价",
|
// name: "待评价",
|
||||||
id: SHOP_ORDER_UNREMARK,
|
// id: SHOP_ORDER_UNREMARK,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
name: "已取消",
|
name: "已取消",
|
||||||
id: SHOP_ORDER_CANCEL,
|
id: SHOP_ORDER_CANCEL,
|
||||||
@ -208,14 +199,6 @@ export default {
|
|||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.reloadData();
|
this.reloadData();
|
||||||
|
|
||||||
// 初始化联系客服按钮位置
|
|
||||||
const systemInfo = uni.getSystemInfoSync();
|
|
||||||
const windowHeight = systemInfo.windowHeight;
|
|
||||||
// 计算底部距离:calc(35vh - 130rpx - 48rpx) 转换为rpx
|
|
||||||
// 35vh = windowHeight * 0.35,转换为rpx (750rpx = windowHeight px)
|
|
||||||
const vh35InRpx = (windowHeight * 0.35 / windowHeight) * 750;
|
|
||||||
this.contactBtnBottom = vh35InRpx - 130 - 48;
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
jumpToWeChat,
|
jumpToWeChat,
|
||||||
@ -267,10 +250,16 @@ export default {
|
|||||||
mask: true,
|
mask: true,
|
||||||
});
|
});
|
||||||
const data = {
|
const data = {
|
||||||
order_id: this.orderInfo.order_id,
|
id: Number(this.orderInfo.order_id),
|
||||||
// business_type:1
|
// business_type:1
|
||||||
}
|
}
|
||||||
cancelPetOrderMall(data).then((res) => {
|
|
||||||
|
// 判断是取消订单还是退款
|
||||||
|
const apiPromise = this.cancelModalContent.includes('退款')
|
||||||
|
? cancelUnpaid(data)
|
||||||
|
: cancelUnpaid(data);
|
||||||
|
|
||||||
|
apiPromise.then((res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
this.showCancelModal = false;
|
this.showCancelModal = false;
|
||||||
this.reloadData();
|
this.reloadData();
|
||||||
@ -314,10 +303,75 @@ export default {
|
|||||||
},
|
},
|
||||||
// 支付
|
// 支付
|
||||||
pay(data) {
|
pay(data) {
|
||||||
|
uni.showLoading({
|
||||||
|
icon: "none",
|
||||||
|
title: "支付中",
|
||||||
|
mask: true,
|
||||||
|
});
|
||||||
|
console.log(data,'-?')
|
||||||
|
|
||||||
|
const plugin = tt.requirePlugin('lifeServicePlugin');
|
||||||
|
// 先隐藏loading,让插件显示自己的弹窗
|
||||||
|
uni.hideLoading();
|
||||||
|
plugin.continueToPay({
|
||||||
|
orderId:data.douyin_order_id, // 内部订单号
|
||||||
|
// outOrderNo: "outOrderNo", // 外部订单号 2个订单号必填一个
|
||||||
|
success: (res) => {
|
||||||
|
const { orderId, outOrderNo } = res;
|
||||||
|
console.log("success res", res);
|
||||||
|
console.log("orderId", orderId, "outOrderNo", outOrderNo);
|
||||||
|
this.reloadData();
|
||||||
|
},
|
||||||
|
fail: (res) => {
|
||||||
|
const { orderId, outOrderNo, errNo, errMsg, errLogId } = res;
|
||||||
|
if (errLogId) {
|
||||||
|
console.log("查询订单信息失败", errNo, errMsg, errLogId);
|
||||||
|
}
|
||||||
|
if (orderId || outOrderNo) {
|
||||||
|
console.log("支付失败", errNo, errMsg, orderId, outOrderNo);
|
||||||
|
}
|
||||||
|
// 隐藏抖音的报错信息,显示自定义中文提示
|
||||||
|
uni.showToast({
|
||||||
|
title: "支付失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// payOrder({
|
||||||
|
// type: 4,
|
||||||
|
// total_fee: Number(data.actual_price),
|
||||||
|
// order_id: data.order_id,
|
||||||
|
// order_no: data.order_no
|
||||||
|
// }).then((res) => {
|
||||||
|
// tt.pay({
|
||||||
|
// orderInfo: {
|
||||||
|
// order_id:res.data.orderInfo.order_id,
|
||||||
|
// order_token:res.data.orderInfo.order_token,
|
||||||
|
// },
|
||||||
|
// service:5,
|
||||||
|
// success: (res) => {
|
||||||
|
// uni.hideLoading();
|
||||||
|
// uni.showToast({
|
||||||
|
// title: "支付成功",
|
||||||
|
// icon: "none",
|
||||||
|
// });
|
||||||
|
// this.additionalBom = false,
|
||||||
|
// this.elasticLayer = false,
|
||||||
|
// this.reloadData();
|
||||||
|
// },
|
||||||
|
// fail: (err) => {
|
||||||
|
// uni.hideLoading();
|
||||||
|
// uni.showToast({
|
||||||
|
// title: err?.msg || "支付失败",
|
||||||
|
// icon: "none",
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
this.additionalBom = true;
|
// this.additionalBom = true;
|
||||||
this.elasticLayer = true;
|
// this.elasticLayer = true;
|
||||||
this.dataList = data;
|
|
||||||
},
|
},
|
||||||
async rechargeNow() {
|
async rechargeNow() {
|
||||||
// console.log(this.wallet, "???");
|
// console.log(this.wallet, "???");
|
||||||
@ -340,14 +394,12 @@ export default {
|
|||||||
order_id: this.dataList.order_id,
|
order_id: this.dataList.order_id,
|
||||||
order_no: this.dataList.order_no
|
order_no: this.dataList.order_no
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
const payData = res?.data || {};
|
tt.pay({
|
||||||
uni.requestPayment({
|
orderInfo: {
|
||||||
provider: "wxpay",
|
order_id:res.data.orderInfo.order_id,
|
||||||
timeStamp: payData.timeStamp,
|
order_token:res.data.orderInfo.order_token,
|
||||||
nonceStr: payData.nonceStr,
|
},
|
||||||
package: payData.package,
|
service:5,
|
||||||
signType: payData.signType,
|
|
||||||
paySign: payData.paySign,
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@ -418,10 +470,6 @@ export default {
|
|||||||
},
|
},
|
||||||
// 申请售后
|
// 申请售后
|
||||||
afterSale() {
|
afterSale() {
|
||||||
// 如果正在拖拽,不触发点击事件
|
|
||||||
if (this.isDragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.jumpToWeChat();
|
this.jumpToWeChat();
|
||||||
},
|
},
|
||||||
// afterSale(data) {
|
// afterSale(data) {
|
||||||
@ -471,63 +519,59 @@ export default {
|
|||||||
// 取消订单
|
// 取消订单
|
||||||
cancelOrderAction(data) {
|
cancelOrderAction(data) {
|
||||||
this.showCancelModal = true;
|
this.showCancelModal = true;
|
||||||
|
this.cancelModalContent = "确定要取消该订单吗?";
|
||||||
this.orderInfo = data;
|
this.orderInfo = data;
|
||||||
},
|
},
|
||||||
// 联系客服按钮触摸开始
|
// 退款
|
||||||
onContactBtnTouchStart(e) {
|
refundAction(data) {
|
||||||
this.isDragging = false;
|
this.showCancelModal = true;
|
||||||
const touch = e.touches[0];
|
this.cancelModalContent = "确定要退款吗?";
|
||||||
this.touchStartX = touch.clientX;
|
this.orderInfo = data;
|
||||||
this.touchStartY = touch.clientY;
|
|
||||||
this.initialRight = this.contactBtnRight;
|
|
||||||
this.initialBottom = this.contactBtnBottom;
|
|
||||||
},
|
},
|
||||||
// 联系客服按钮触摸移动
|
// 处理从 OrderItem 传来的退款事件
|
||||||
onContactBtnTouchMove(e) {
|
handleRefundFromItem(data) {
|
||||||
const touch = e.touches[0];
|
// 跳转到订单详情页处理退款,因为插件组件只能在页面中使用
|
||||||
const deltaX = touch.clientX - this.touchStartX;
|
uni.navigateTo({
|
||||||
const deltaY = touch.clientY - this.touchStartY;
|
url: `/pages/client/order/details?id=${data?.order_id}`,
|
||||||
|
events: {
|
||||||
// 如果移动距离超过10px,认为是拖拽
|
refreshData: () => this.reloadData(),
|
||||||
if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
|
},
|
||||||
this.isDragging = true;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isDragging) {
|
|
||||||
const systemInfo = uni.getSystemInfoSync();
|
|
||||||
const windowHeight = systemInfo.windowHeight;
|
|
||||||
|
|
||||||
// 只处理上下移动,将px转换为rpx
|
|
||||||
const deltaYRpx = (deltaY / windowHeight) * 750;
|
|
||||||
|
|
||||||
// 计算新位置(只改变bottom,right始终保持为0)
|
|
||||||
// 向上移动时deltaY为负,但bottom值应该减小
|
|
||||||
let newBottom = this.initialBottom - deltaYRpx;
|
|
||||||
|
|
||||||
// 限制在屏幕范围内
|
|
||||||
// 按钮高度约120rpx
|
|
||||||
const btnHeight = 120;
|
|
||||||
|
|
||||||
newBottom = Math.max(0, Math.min(newBottom, windowHeight * 2 - btnHeight));
|
|
||||||
|
|
||||||
// right始终保持为0
|
|
||||||
this.contactBtnRight = 0;
|
|
||||||
this.contactBtnBottom = newBottom;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// 联系客服按钮触摸结束
|
// 联系客服按钮点击 - 使用自定义弹窗(与首页在线客服功能一致)
|
||||||
onContactBtnTouchEnd(e) {
|
|
||||||
// 松手后,right重置为0
|
|
||||||
this.contactBtnRight = 0;
|
|
||||||
this.isDragging = false;
|
|
||||||
},
|
|
||||||
// 联系客服按钮点击
|
|
||||||
handleContactBtnClick() {
|
handleContactBtnClick() {
|
||||||
// 如果正在拖拽,不触发点击事件
|
console.log('[list.vue] handleContactBtnClick called')
|
||||||
if (this.isDragging) {
|
console.log('[list.vue] wechatCopyModal ref:', this.$refs.wechatCopyModal)
|
||||||
return;
|
if (this.$refs.wechatCopyModal) {
|
||||||
|
this.$refs.wechatCopyModal.show({
|
||||||
|
title: '请添加客服号',
|
||||||
|
weChatCode: 'Wagoo2025'
|
||||||
|
});
|
||||||
|
console.log('[list.vue] wechatCopyModal.show called')
|
||||||
|
} else {
|
||||||
|
console.error('[list.vue] wechatCopyModal ref is null')
|
||||||
|
// 降级方案:直接显示系统弹窗
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '请添加客服号:Wagoo2025',
|
||||||
|
confirmText: '复制微信号',
|
||||||
|
showCancel: true,
|
||||||
|
cancelText: '取消',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: 'Wagoo2025',
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '已复制微信号',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
this.jumpToWeChat();
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -675,24 +719,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-float-btn {
|
/* 可拖动联系客服组件无需额外样式 */
|
||||||
position: fixed;
|
|
||||||
text-align: center;
|
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
.contact-icon {
|
|
||||||
width: 66rpx;
|
|
||||||
height: 66rpx;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-btn {
|
|
||||||
color: #FFFFFF;
|
|
||||||
background-color: #FF19A0;
|
|
||||||
border-radius: 257px;
|
|
||||||
padding: 6rpx 8rpx;
|
|
||||||
transform: translateY(-8px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -58,13 +58,18 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 取消预约确认弹窗 - 放在父组件中避免被 overflow: hidden 裁剪 -->
|
<!-- 取消预约确认弹窗 - 放在父组件中避免被 overflow: hidden 裁剪 -->
|
||||||
<pop-up-modal
|
<pop-up-modal
|
||||||
v-if="isShowCancelModal"
|
v-if="isShowCancelModal"
|
||||||
content="确定要取消预约吗?"
|
content="确定要取消预约吗?"
|
||||||
@confirm="confirmCancelOrder"
|
@confirm="confirmCancelOrder"
|
||||||
@cancel="isShowCancelModal = false"
|
@cancel="isShowCancelModal = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 可拖动联系客服组件 -->
|
||||||
|
<DraggableContact ref="draggableContact" :onClick="handleContactClick" />
|
||||||
|
|
||||||
|
<WeChatCopyModal ref="wechatCopyModal" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -76,6 +81,8 @@ import { showOrderStatus } from "@/pageHome/constants/home";
|
|||||||
import { getOrderList, cancelHomeOrder, cancelTrainingOrder } from "@/api/order";
|
import { getOrderList, cancelHomeOrder, cancelTrainingOrder } from "@/api/order";
|
||||||
import { cancelPetOrderRefund } from "@/api/login";
|
import { cancelPetOrderRefund } from "@/api/login";
|
||||||
import PopUpModal from "@/components/PopUpModal.vue";
|
import PopUpModal from "@/components/PopUpModal.vue";
|
||||||
|
import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
|
||||||
|
import DraggableContact from "@/components/DraggableContact.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'index',
|
name: 'index',
|
||||||
@ -83,7 +90,9 @@ export default {
|
|||||||
Reservation,
|
Reservation,
|
||||||
HomeServiceOrderItem,
|
HomeServiceOrderItem,
|
||||||
HomeTrainingOrderItem,
|
HomeTrainingOrderItem,
|
||||||
PopUpModal
|
PopUpModal,
|
||||||
|
WeChatCopyModal,
|
||||||
|
DraggableContact
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -305,6 +314,15 @@ export default {
|
|||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// 跳转微信客服 - 使用自定义弹窗(与首页在线客服功能一致)
|
||||||
|
handleContactClick() {
|
||||||
|
if (this.$refs.wechatCopyModal) {
|
||||||
|
this.$refs.wechatCopyModal.show({
|
||||||
|
title: '请添加客服号',
|
||||||
|
weChatCode: 'Wagoo2025'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
handleGotoEvaluate(orderInfo) {
|
handleGotoEvaluate(orderInfo) {
|
||||||
// 处理去评价
|
// 处理去评价
|
||||||
const orderId = orderInfo.order_id || orderInfo.home_training_order?.order_id;
|
const orderId = orderInfo.order_id || orderInfo.home_training_order?.order_id;
|
||||||
@ -401,6 +419,8 @@ export default {
|
|||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding-bottom: 32rpx;
|
padding-bottom: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 可拖动联系客服组件无需额外样式 */
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -21,8 +21,8 @@
|
|||||||
<view class="recharge-method">
|
<view class="recharge-method">
|
||||||
<view class="wechat">
|
<view class="wechat">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected" @click.stop="selectOption1('1')" src="@/static/images/w.png"
|
<image v-if="selected1" class="not-selected" @click.stop="selectOption1('1')" src="@/static/images/w.png"
|
||||||
mode="widthFix" />
|
mode="widthFix" />
|
||||||
@ -212,14 +212,12 @@
|
|||||||
if (this.WeChat == 1) {
|
if (this.WeChat == 1) {
|
||||||
payOrder(this.dataList.order_id, PRICE_DIFF_TYPE_SERVICE)
|
payOrder(this.dataList.order_id, PRICE_DIFF_TYPE_SERVICE)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const payData = res?.info?.pay_data || {};
|
tt.pay({
|
||||||
uni.requestPayment({
|
orderInfo: {
|
||||||
provider: "wxpay",
|
order_id:res.data.orderInfo.order_id,
|
||||||
timeStamp: payData.timeStamp,
|
order_token:res.data.orderInfo.order_token,
|
||||||
nonceStr: payData.nonceStr,
|
},
|
||||||
package: payData.package,
|
service:5,
|
||||||
signType: payData.signType,
|
|
||||||
paySign: payData.sign,
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|||||||
235
src/pages/client/recharge/components/ListPageTemp.vue
Normal file
235
src/pages/client/recharge/components/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||||
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
|
<slot name="top" />
|
||||||
|
<view class="list-content" v-if="list.length > 0">
|
||||||
|
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||||
|
class="flex-column-start news-item" @click="clickCell(item)">
|
||||||
|
<slot style="width: 100%" name="item" :data="{
|
||||||
|
...item,
|
||||||
|
...listExtraFields,
|
||||||
|
deleteSelect: !!item.deleteSelect,
|
||||||
|
}" />
|
||||||
|
</view>
|
||||||
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
|
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-container">
|
||||||
|
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||||
|
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="bottom" />
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListPageTemp",
|
||||||
|
props: {
|
||||||
|
getDataPromise: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
// 格式化请求结果的方法
|
||||||
|
resultFormatFunc: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
defult: () => { },
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
// 列表元素额外字段
|
||||||
|
listExtraFields: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
reloadFlag: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
// 是否分页
|
||||||
|
isPagiantion: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页数量, 不分页时有效
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 999,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disableScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
// 占位图片地址
|
||||||
|
emptyImage: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 空状态文本(传入时才显示)
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
refreshTriggered: false,
|
||||||
|
p: 1,
|
||||||
|
num: 10,
|
||||||
|
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
reloadFlag: {
|
||||||
|
handler(value) {
|
||||||
|
if (value) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
handler(data) {
|
||||||
|
if (data) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
handler(list) {
|
||||||
|
this.list = list;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRefresh() {
|
||||||
|
if (this.refreshTriggered) return;
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
// this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isPagiantion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLoading && this.total > this.list.length) {
|
||||||
|
this.p++;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickCell(item) {
|
||||||
|
this.$emit("clickCell", item);
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// TODO: 删除测试数据
|
||||||
|
if (!this.getDataPromise) {
|
||||||
|
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||||
|
return {
|
||||||
|
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||||
|
id: k,
|
||||||
|
len: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.total = 9;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.assign(
|
||||||
|
{},
|
||||||
|
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||||
|
this.requestData
|
||||||
|
);
|
||||||
|
this.getDataPromise(data)
|
||||||
|
.then((res) => {
|
||||||
|
const list =
|
||||||
|
this.p === 1
|
||||||
|
? res?.data || []
|
||||||
|
: [...this.list, ...(res?.data || [])];
|
||||||
|
this.list = this.resultFormatFunc
|
||||||
|
? this.resultFormatFunc(list)
|
||||||
|
: list;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
this.$emit("getList", this.list, res);
|
||||||
|
// console.log(this.list,'???')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-template-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.list-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 400rpx auto 0;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ration {
|
||||||
|
width: 160px;
|
||||||
|
height: 174px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -32,8 +32,8 @@
|
|||||||
<view class="recharge-method">
|
<view class="recharge-method">
|
||||||
<view class="wechat" @click.stop="selectOption1">
|
<view class="wechat" @click.stop="selectOption1">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected"
|
<image v-if="selected1" class="not-selected"
|
||||||
src="@/static/images/w.png" mode="widthFix" />
|
src="@/static/images/w.png" mode="widthFix" />
|
||||||
@ -141,13 +141,12 @@
|
|||||||
walletWxpay(data).then((res) => {
|
walletWxpay(data).then((res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
// 使用获取的支付参数进行支付
|
// 使用获取的支付参数进行支付
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
provider: 'wxpay',
|
orderInfo: {
|
||||||
timeStamp: res.data.timeStamp,
|
order_id:res.data.orderInfo.order_id,
|
||||||
nonceStr: res.data.nonceStr,
|
order_token:res.data.orderInfo.order_token,
|
||||||
package: res.data.package,
|
},
|
||||||
signType: res.data.signType,
|
service:5,
|
||||||
paySign: res.data.paySign,
|
|
||||||
success: (payRes) => {
|
success: (payRes) => {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '支付成功',
|
title: '支付成功',
|
||||||
|
|||||||
@ -63,8 +63,8 @@
|
|||||||
<view class="recharge-method">
|
<view class="recharge-method">
|
||||||
<view class="wechat" @click.stop="selectOption1">
|
<view class="wechat" @click.stop="selectOption1">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected"
|
<image v-if="selected1" class="not-selected"
|
||||||
src="@/static/images/w.png" mode="widthFix" />
|
src="@/static/images/w.png" mode="widthFix" />
|
||||||
@ -188,12 +188,12 @@
|
|||||||
};
|
};
|
||||||
walletWxpay(data).then((res) => {
|
walletWxpay(data).then((res) => {
|
||||||
// 使用获取的支付参数进行支付
|
// 使用获取的支付参数进行支付
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
orderInfo: {
|
||||||
nonceStr: res.data.nonceStr,
|
order_id:res.data.orderInfo.order_id,
|
||||||
package: res.data.package,
|
order_token:res.data.orderInfo.order_token,
|
||||||
signType: res.data.signType,
|
},
|
||||||
paySign: res.data.paySign,
|
service:5,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
this.buyService(this.user_id);
|
this.buyService(this.user_id);
|
||||||
// console.log('支付成功:', res);
|
// console.log('支付成功:', res);
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TabsList from "@/components/TabsList.vue";
|
import TabsList from "@/components/TabsList.vue";
|
||||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||||
import CouponItem from "@/components/coupon/CouponItem";
|
import CouponItem from "@/components/coupon/CouponItem";
|
||||||
import { getOwnCouponData } from "@/api/coupon";
|
import { getOwnCouponData } from "@/api/coupon";
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view v-if="objNew.third_party_sn " class="rech-row">
|
<view v-if="objNew.third_party_sn " class="rech-row">
|
||||||
<text class="c">充值方式</text>
|
<text class="c">充值方式</text>
|
||||||
<text v-if="objNew.payment_method == 1" class="m">微信</text>
|
<text v-if="objNew.payment_method == 1" class="m">抖音</text>
|
||||||
<text v-else class="m">支付宝</text>
|
<text v-else class="m">支付宝</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="rech-row">
|
<view class="rech-row">
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
<view class="edit-form">
|
<view class="edit-form">
|
||||||
<form-cell title="宠物头像" type="custom" :showRightArrow="false">
|
<form-cell title="宠物头像" type="custom" :showRightArrow="false">
|
||||||
<view class="uploadImgWrapper" slot="right">
|
<view class="uploadImgWrapper" slot="right">
|
||||||
<button class="upload-btn" open-type="chooseAvatar" @chooseavatar="changeAvator">
|
<button class="upload-btn" @click="changeAvator">
|
||||||
<image v-if="recordInfo.avatar" class="uploaded-img" :src="recordInfo.avatar"
|
<image v-if="recordInfo.avatar" class="uploaded-img" :src="recordInfo.avatar"
|
||||||
mode="aspectFill" />
|
mode="aspectFill" />
|
||||||
<image v-else :src="`${imgPrefix}record-cameraImg.png`" class="img" />
|
<image v-else :src="`${imgPrefix}record-cameraImg.png`" class="img" />
|
||||||
@ -255,26 +255,43 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
moment,
|
moment,
|
||||||
// 更新头像
|
// 更新头像
|
||||||
async changeAvator(e) {
|
changeAvator() {
|
||||||
const {
|
let that = this
|
||||||
avatarUrl
|
// const {
|
||||||
} = e.detail;
|
// avatarUrl
|
||||||
|
// } = e.detail;
|
||||||
|
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: "上传中..."
|
title: "上传中..."
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { url, objectKey } = await uploadImageToOSS_PUT(avatarUrl);
|
tt.chooseImage({
|
||||||
console.log(url, objectKey, 'url, objectKey');
|
count: 1, // 最多可选图片数量,默认为9
|
||||||
this.recordInfo.avatar = url; // 完整URL
|
sourceType: ['album', 'camera'], // 来源:相册、相机或两者都有
|
||||||
this.recordInfo.chongwu_pic = objectKey; // 对象键
|
success : async function(res) {
|
||||||
this.$forceUpdate();
|
const { url, objectKey } = await uploadImageToOSS_PUT(res.tempFilePaths[0]);
|
||||||
|
// console.log(url, objectKey, 'url, objectKey');
|
||||||
|
that.recordInfo.avatar = url; // 完整URL
|
||||||
|
that.recordInfo.chongwu_pic = objectKey; // 对象键
|
||||||
|
that.$forceUpdate();
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "上传成功",
|
title: "上传成功",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
});
|
});
|
||||||
|
// 调用成功,res.tempFilePaths 为图片本地路径数组
|
||||||
|
// console.log('选中的图片路径:', res.tempFilePaths);
|
||||||
|
},
|
||||||
|
fail: function(err) {
|
||||||
|
// 调用失败,例如用户取消、未授权或参数错误
|
||||||
|
console.log('选择图片失败:', err.errMsg);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
// 无论成功或失败都会执行
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('头像上传失败:', error);
|
console.error('头像上传失败:', error);
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
@ -299,7 +316,6 @@ export default {
|
|||||||
changeHair(value) {
|
changeHair(value) {
|
||||||
this.recordInfo.hair = value;
|
this.recordInfo.hair = value;
|
||||||
this.recordInfo.weight_id = "";
|
this.recordInfo.weight_id = "";
|
||||||
this.getRecordWeightList();
|
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
},
|
},
|
||||||
onBreedChange(val) {
|
onBreedChange(val) {
|
||||||
|
|||||||
235
src/pages/client/remark/ListPageTemp.vue
Normal file
235
src/pages/client/remark/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||||
|
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||||
|
<slot name="top" />
|
||||||
|
<view class="list-content" v-if="list.length > 0">
|
||||||
|
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||||
|
class="flex-column-start news-item" @click="clickCell(item)">
|
||||||
|
<slot style="width: 100%" name="item" :data="{
|
||||||
|
...item,
|
||||||
|
...listExtraFields,
|
||||||
|
deleteSelect: !!item.deleteSelect,
|
||||||
|
}" />
|
||||||
|
</view>
|
||||||
|
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||||
|
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-container">
|
||||||
|
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||||
|
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="bottom" />
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListPageTemp",
|
||||||
|
props: {
|
||||||
|
getDataPromise: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
// 格式化请求结果的方法
|
||||||
|
resultFormatFunc: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
defult: () => { },
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
// 列表元素额外字段
|
||||||
|
listExtraFields: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
reloadFlag: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
// 是否分页
|
||||||
|
isPagiantion: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页数量, 不分页时有效
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 999,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disableScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
// 占位图片地址
|
||||||
|
emptyImage: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
// 空状态文本(传入时才显示)
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
total: 0,
|
||||||
|
list: [],
|
||||||
|
refreshTriggered: false,
|
||||||
|
p: 1,
|
||||||
|
num: 10,
|
||||||
|
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
reloadFlag: {
|
||||||
|
handler(value) {
|
||||||
|
if (value) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
requestData: {
|
||||||
|
handler(data) {
|
||||||
|
if (data) {
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
defaultList: {
|
||||||
|
handler(list) {
|
||||||
|
this.list = list;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRefresh() {
|
||||||
|
if (this.refreshTriggered) return;
|
||||||
|
this.refreshTriggered = true;
|
||||||
|
this.p = 1;
|
||||||
|
this.num = 10;
|
||||||
|
// this.list = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadMore() {
|
||||||
|
if (!this.isPagiantion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLoading && this.total > this.list.length) {
|
||||||
|
this.p++;
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickCell(item) {
|
||||||
|
this.$emit("clickCell", item);
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// TODO: 删除测试数据
|
||||||
|
if (!this.getDataPromise) {
|
||||||
|
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||||
|
return {
|
||||||
|
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||||
|
id: k,
|
||||||
|
len: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.total = 9;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.assign(
|
||||||
|
{},
|
||||||
|
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||||
|
this.requestData
|
||||||
|
);
|
||||||
|
this.getDataPromise(data)
|
||||||
|
.then((res) => {
|
||||||
|
const list =
|
||||||
|
this.p === 1
|
||||||
|
? res?.data || []
|
||||||
|
: [...this.list, ...(res?.data || [])];
|
||||||
|
this.list = this.resultFormatFunc
|
||||||
|
? this.resultFormatFunc(list)
|
||||||
|
: list;
|
||||||
|
this.total = res?.count || 0;
|
||||||
|
this.$emit("getList", this.list, res);
|
||||||
|
// console.log(this.list,'???')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.refreshTriggered = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-template-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.list-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 400rpx auto 0;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-ration {
|
||||||
|
width: 160px;
|
||||||
|
height: 174px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RemarkItem from "@/components/goods/RemarkItem.vue";
|
import RemarkItem from "@/components/goods/RemarkItem.vue";
|
||||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
import ListPageTemp from "./ListPageTemp.vue";
|
||||||
import { getRemarkList } from "@/api/shop";
|
import { getRemarkList } from "@/api/shop";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -220,14 +220,13 @@ export default {
|
|||||||
// 支付
|
// 支付
|
||||||
pay(orderId) {
|
pay(orderId) {
|
||||||
serviceCouponOrderPay(orderId).then((res) => {
|
serviceCouponOrderPay(orderId).then((res) => {
|
||||||
const payData = res?.info?.pay_data || {};
|
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
provider: "wxpay",
|
orderInfo: {
|
||||||
timeStamp: payData.timeStamp,
|
order_id:res.data.orderInfo.order_id,
|
||||||
nonceStr: payData.nonceStr,
|
order_token:res.data.orderInfo.order_token,
|
||||||
package: payData.package,
|
},
|
||||||
signType: payData.signType,
|
service:5,
|
||||||
paySign: payData.sign,
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
this.showSuccessModal = true;
|
this.showSuccessModal = true;
|
||||||
|
|||||||
@ -1,26 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="goods-item" @click.stop="jumpToDetails">
|
<view class="goods-item" :class="{ 'goods-item-home': isHome }">
|
||||||
<image class="goods-img" :src="data.product_pic" mode="aspectFill" />
|
<image @click.stop="handleBuyNow" class="goods-img" :src="getProductImage(data)" mode="aspectFill" />
|
||||||
<view class=" fs-24 app-fc-main goods-name">
|
<view class=" fs-24 app-fc-main goods-name">
|
||||||
{{ data.product_name || "" }}
|
{{ data.product.product_name || "" }}
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-row-start label">
|
<!-- <view class="flex-row-start label">
|
||||||
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
|
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
|
||||||
<view class="fs-20 app-fc-main label-name">{{data.sales}}人买过</view>
|
<view class="fs-20 app-fc-main label-name">{{data.sales}}人买过</view>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="flex-row-start" style="margin-top: 12rpx;">
|
<view class="flex-row-between" style="margin-top: 12rpx; align-items: baseline;">
|
||||||
<text class="fs-28" style="color: #3D3D3D;">
|
<view class="price-wrapper">
|
||||||
¥
|
<text class="fs-28" style="color: #3D3D3D;">
|
||||||
<text class="fs-28">{{
|
¥
|
||||||
data.prices[0].actual_price || 0
|
<text class="fs-28">{{
|
||||||
}}</text>
|
data.sku.actual_amount / 100 || 0
|
||||||
</text>
|
}}</text>
|
||||||
<!-- <text class="fs-24 origin-price">
|
</text>
|
||||||
¥{{ minPrice.price_shichang || 0 }}
|
<!-- <text class="fs-24 origin-price">
|
||||||
</text> -->
|
¥{{ minPrice.price_shichang || 0 }}
|
||||||
|
</text> -->
|
||||||
|
</view>
|
||||||
|
<view class="buy-now-btn" @click.stop="handleBuyNow">
|
||||||
|
立即购买
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- <text class="fs-20 good-salenum">已售{{ data.xiaoliang || 0 }}</text> -->
|
<!-- <text class="fs-20 good-salenum">已售{{ data.xiaoliang || 0 }}</text> -->
|
||||||
<!-- <image class="add-cart-icon" src="@/static/images/cart-icon.png" @click.stop="$emit('addToCar', data)" /> -->
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -30,7 +34,7 @@
|
|||||||
} from '@/utils/common';
|
} from '@/utils/common';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
index: {
|
index: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
@ -39,6 +43,10 @@
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
isHome: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -46,30 +54,41 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// minPrice() {
|
|
||||||
// let minPrice = {};
|
|
||||||
// let minPriceValue = 0;
|
|
||||||
// this.data.price_list.map((v) => {
|
|
||||||
// if (!minPriceValue || minPriceValue > +v.price) {
|
|
||||||
// minPriceValue = +v.price;
|
|
||||||
// minPrice = {
|
|
||||||
// ...v
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// return minPrice;
|
|
||||||
// },
|
|
||||||
labelList() {
|
labelList() {
|
||||||
return (this.data?.label || "").split(",").filter((v) => !!v);
|
return (this.data?.label || "").split(",").filter((v) => !!v);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {},
|
mounted() {},
|
||||||
methods: {
|
methods: {
|
||||||
jumpToDetails() {
|
// jumpToDetails() {
|
||||||
uni.navigateTo({
|
// uni.navigateTo({
|
||||||
url: `/pages/client/shop/details?product_id=${this.data.product_id}`,
|
// url: `/pages/client/shop/details?product_id=${this.data.product_id}`,
|
||||||
});
|
// });
|
||||||
|
// },
|
||||||
|
handleBuyNow() {
|
||||||
|
// 触发购买事件,通知父组件
|
||||||
|
this.$emit('addToCar', this.data);
|
||||||
},
|
},
|
||||||
|
getProductImage(data) {
|
||||||
|
// Try to get product_pic first
|
||||||
|
if (data.product_pic) {
|
||||||
|
return data.product_pic;
|
||||||
|
}
|
||||||
|
// Try to parse image_list from attr_key_value_map
|
||||||
|
if (data.product?.attr_key_value_map?.image_list) {
|
||||||
|
try {
|
||||||
|
const imageList = data.product.attr_key_value_map.image_list;
|
||||||
|
const parsedList = typeof imageList === 'string' ? JSON.parse(imageList) : imageList;
|
||||||
|
if (Array.isArray(parsedList) && parsedList.length > 0) {
|
||||||
|
return parsedList[0].url || parsedList[0] || '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing image_list:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -78,7 +97,7 @@
|
|||||||
.goods-item {
|
.goods-item {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 20rpx;
|
padding: 2rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-bottom: 22rpx;
|
margin-bottom: 22rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -86,24 +105,33 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.goods-item-home {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-img {
|
.goods-img {
|
||||||
display: block;
|
display: block;
|
||||||
|
// width: 260rpx;
|
||||||
|
height: 260rpx;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
// max-width: 100%;
|
||||||
height: 320rpx;
|
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.goods-item-home .goods-img {
|
||||||
|
height: 145px;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-name {
|
.goods-name {
|
||||||
margin: 20rpx 0;
|
margin: 20rpx 0;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
// text-overflow: ellipsis;
|
||||||
display: -webkit-box;
|
// display: -webkit-box;
|
||||||
-webkit-line-clamp: 1;
|
// -webkit-line-clamp: 1;
|
||||||
-webkit-box-orient: vertical;
|
// -webkit-box-orient: vertical;
|
||||||
word-wrap: break-word;
|
// word-wrap: break-word;
|
||||||
word-break: break-all;
|
// word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@ -153,6 +181,26 @@
|
|||||||
right: 20rpx;
|
right: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-row-between {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-now-btn {
|
||||||
|
background: #FF19A0;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20rpx;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
border-radius:30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -14,14 +14,14 @@
|
|||||||
|
|
||||||
<view class="good-info">
|
<view class="good-info">
|
||||||
<view class="info-cell">
|
<view class="info-cell">
|
||||||
<view class="flex-row-start label">
|
<!-- <view class="flex-row-start label">
|
||||||
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
|
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
|
||||||
<view class="fs-20 app-fc-main label-name">{{details.sales}}人买过</view>
|
<view class="fs-20 app-fc-main label-name">{{details.sales}}人买过</view>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="flex-row-between" style="margin-top: 20rpx">
|
<view class="flex-row-between" style="margin-top: 20rpx">
|
||||||
<view class="">
|
<view class="">
|
||||||
<text class="app-fc-mark fs-28">¥</text>
|
<text class="app-fc-mark fs-28">¥</text>
|
||||||
<text class="app-fc-mark fs-28">{{picList[0].actual_price || 0 }}</text>
|
<text class="app-fc-mark fs-28">{{picList[0].actual_price / 100 || 0 }}</text>
|
||||||
<!-- <text class="fs-24 origin-price">
|
<!-- <text class="fs-24 origin-price">
|
||||||
¥{{ minPrice.price_shichang || 0 }}
|
¥{{ minPrice.price_shichang || 0 }}
|
||||||
</text> -->
|
</text> -->
|
||||||
@ -46,10 +46,6 @@
|
|||||||
<text class="selected-value">{{ selectedSpec || "请选择规格" }}</text>
|
<text class="selected-value">{{ selectedSpec || "请选择规格" }}</text>
|
||||||
<image class="arrow-right-icon" :src="require('./static/arrow_right.png')" />
|
<image class="arrow-right-icon" :src="require('./static/arrow_right.png')" />
|
||||||
</view>
|
</view>
|
||||||
<view class="shipping-row">
|
|
||||||
<text class="shipping-label">运费</text>
|
|
||||||
<text class="shipping-value">{{ sliverFee ? `¥${sliverFee}` : "待下单时候确认" }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="service-features">
|
<view class="service-features">
|
||||||
<view class="feature-item">
|
<view class="feature-item">
|
||||||
<image :src="`${imgPrefix}mall-detailTips.png`" class="mallPng"></image>
|
<image :src="`${imgPrefix}mall-detailTips.png`" class="mallPng"></image>
|
||||||
@ -398,16 +394,6 @@
|
|||||||
price_id,
|
price_id,
|
||||||
number
|
number
|
||||||
}) {
|
}) {
|
||||||
// console.log(this.details,'--?')
|
|
||||||
// const petList = {
|
|
||||||
// original_price:this.details.prices[0].actual_price,
|
|
||||||
// actual_price:this.details.prices[0].original_price,
|
|
||||||
// reduction_amount:0,
|
|
||||||
// // pay_amount:, //数量乘实际单价
|
|
||||||
// // note:''
|
|
||||||
// }
|
|
||||||
// console.log(petList,'=-=')
|
|
||||||
console.log(number, 'number')
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/client/order/create`,
|
url: `/pages/client/order/create`,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
@ -424,7 +410,7 @@
|
|||||||
number,
|
number,
|
||||||
goods_name: this.details.goods_name,
|
goods_name: this.details.goods_name,
|
||||||
price_name: priceInfo?.price_name,
|
price_name: priceInfo?.price_name,
|
||||||
goods_price: +(+priceInfo?.actual_price * number).toFixed(2),
|
goods_price: +((priceInfo?.actual_price / 100) * number).toFixed(2),
|
||||||
}, ],
|
}, ],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -47,8 +47,8 @@
|
|||||||
<text class="zf"> 请选择支付方式 </text>
|
<text class="zf"> 请选择支付方式 </text>
|
||||||
<view class="wechat">
|
<view class="wechat">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image
|
<image
|
||||||
v-if="selected1"
|
v-if="selected1"
|
||||||
|
|||||||
@ -31,10 +31,32 @@ export default {
|
|||||||
this.getDetail(this.id);
|
this.getDetail(this.id);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 清理格式错误的 HTML 属性
|
||||||
|
cleanHtml(html) {
|
||||||
|
if (!html) return '';
|
||||||
|
|
||||||
|
let cleaned = html;
|
||||||
|
|
||||||
|
// 移除格式错误的属性名(包含非法字符如 , = " 等)
|
||||||
|
// 匹配类似 times="" new="" roman",="" serif;"=" 这样的错误属性
|
||||||
|
cleaned = cleaned.replace(/\s+[a-zA-Z0-9_-]*\s*=\s*""\s*[a-zA-Z0-9_-]*\s*=\s*""\s*[^>]*?=\s*""/g, '');
|
||||||
|
|
||||||
|
// 移除残留的错误属性片段(包含 ="" 的)
|
||||||
|
cleaned = cleaned.replace(/\s+[^>\s]*=\s*""/g, '');
|
||||||
|
|
||||||
|
// 移除孤立的 ="" 片段
|
||||||
|
cleaned = cleaned.replace(/\s*=\s*""/g, '');
|
||||||
|
|
||||||
|
// 移除 <o:p></o:p> 标签(Office 专有标签,小程序不支持)
|
||||||
|
cleaned = cleaned.replace(/<o:p[^>]*>[\s\S]*?<\/o:p>/g, '');
|
||||||
|
cleaned = cleaned.replace(/<o:p[^>]*\/?>/g, '');
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
},
|
||||||
getDetail(id) {
|
getDetail(id) {
|
||||||
getArticleDetail(id).then(
|
getArticleDetail(id).then(
|
||||||
res => {
|
res => {
|
||||||
this.content = res.data.content
|
this.content = this.cleanHtml(res.data.content)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -202,8 +202,8 @@
|
|||||||
<text class="zf"> 请选择支付方式 </text>
|
<text class="zf"> 请选择支付方式 </text>
|
||||||
<view class="wechat" @click.stop="selectOption1('1')">
|
<view class="wechat" @click.stop="selectOption1('1')">
|
||||||
<view class="select">
|
<view class="select">
|
||||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||||
<text class="x">微信</text>
|
<text class="x">抖音</text>
|
||||||
</view>
|
</view>
|
||||||
<image v-if="selected1" class="not-selected" src="@/static/images/w.png"
|
<image v-if="selected1" class="not-selected" src="@/static/images/w.png"
|
||||||
mode="widthFix" />
|
mode="widthFix" />
|
||||||
@ -471,12 +471,12 @@ export default {
|
|||||||
};
|
};
|
||||||
memberWXPAY(data).then((res) => {
|
memberWXPAY(data).then((res) => {
|
||||||
// 使用获取的支付参数进行支付
|
// 使用获取的支付参数进行支付
|
||||||
uni.requestPayment({
|
tt.pay({
|
||||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
orderInfo: {
|
||||||
nonceStr: res.data.nonceStr,
|
order_id:res.data.orderInfo.order_id,
|
||||||
package: res.data.package,
|
order_token:res.data.orderInfo.order_token,
|
||||||
signType: res.data.signType,
|
},
|
||||||
paySign: res.data.paySign,
|
service:5,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "支付成功",
|
title: "支付成功",
|
||||||
|
|||||||
@ -275,14 +275,12 @@ export default {
|
|||||||
// 支付
|
// 支付
|
||||||
pay(orderId) {
|
pay(orderId) {
|
||||||
serviceCouponOrderPay(orderId).then((res) => {
|
serviceCouponOrderPay(orderId).then((res) => {
|
||||||
const payData = res?.info?.pay_data || {};
|
tt.pay({
|
||||||
uni.requestPayment({
|
orderInfo: {
|
||||||
provider: "wxpay",
|
order_id:res.data.orderInfo.order_id,
|
||||||
timeStamp: payData.timeStamp,
|
order_token:res.data.orderInfo.order_token,
|
||||||
nonceStr: payData.nonceStr,
|
},
|
||||||
package: payData.package,
|
service:5,
|
||||||
signType: payData.signType,
|
|
||||||
paySign: payData.sign,
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
this.showSuccessModal = true
|
this.showSuccessModal = true
|
||||||
|
|||||||
BIN
src/static/images/douy.png
Normal file
BIN
src/static/images/douy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/static/images/yyzz.png
Normal file
BIN
src/static/images/yyzz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 540 KiB |
@ -90,19 +90,31 @@ export const getWeekDayName = (weekday) => {
|
|||||||
return weekdayName[weekday];
|
return weekdayName[weekday];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳转企微客服
|
/**
|
||||||
export const jumpToWeChat = () => {
|
* 跳转在线客服 - 显示弹窗提示添加企业微信
|
||||||
wx.openCustomerServiceChat({
|
*/
|
||||||
corpId: appConfig.qiWeId, // 企业id,必填
|
export const jumpToCustomerService = () => {
|
||||||
extInfo: { // 客服信息
|
uni.showModal({
|
||||||
url: appConfig.qiWeLink, // 客服链接
|
title: "提示",
|
||||||
},
|
content: "请添加客服号:Wagoo2025",
|
||||||
fail: (err) => {
|
confirmText: "复制微信号",
|
||||||
console.log(err);
|
showCancel: true,
|
||||||
uni.showModal({
|
cancelText: "取消",
|
||||||
content: err || "跳转客服微信失败",
|
success: (res) => {
|
||||||
showCancel: false,
|
if (res.confirm) {
|
||||||
});
|
uni.setClipboardData({
|
||||||
|
data: "Wagoo2025",
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: "已复制微信号",
|
||||||
|
icon: "success"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 兼容旧版本,保留 jumpToWeChat 别名
|
||||||
|
export const jumpToWeChat = jumpToCustomerService;
|
||||||
|
|||||||
335
src/utils/goodsCache.js
Normal file
335
src/utils/goodsCache.js
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
/**
|
||||||
|
* 商品数据缓存工具
|
||||||
|
* 用于首页和分类页的商品列表缓存,避免重复请求相同数据
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getGoodsListData } from '@/api/shop'
|
||||||
|
|
||||||
|
// 缓存存储对象
|
||||||
|
const goodsCache = {
|
||||||
|
// 首页商品缓存
|
||||||
|
home: null,
|
||||||
|
// 分类页商品缓存,key为分类ID
|
||||||
|
category: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存过期时间(5分钟,单位:毫秒)
|
||||||
|
const CACHE_EXPIRE_TIME = 5 * 60 * 1000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算数据的哈希值,用于判断数据是否有变化
|
||||||
|
* @param {Array} data - 商品数据列表
|
||||||
|
* @returns {string} 数据哈希值
|
||||||
|
*/
|
||||||
|
function calculateDataHash(data) {
|
||||||
|
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||||
|
return 'empty'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用商品ID、价格、更新时间等关键信息计算哈希
|
||||||
|
const keyData = data.map(item => {
|
||||||
|
const product = item.product || item
|
||||||
|
return `${product.product_id || product.id}-${product.updated_at || Date.now()}-${item.sku?.actual_amount || product.price || 0}`
|
||||||
|
}).join('|')
|
||||||
|
|
||||||
|
// 简单的字符串哈希函数
|
||||||
|
let hash = 0
|
||||||
|
for (let i = 0; i < keyData.length; i++) {
|
||||||
|
const char = keyData.charCodeAt(i)
|
||||||
|
hash = ((hash << 5) - hash) + char
|
||||||
|
hash = hash & hash // 转换为32位整数
|
||||||
|
}
|
||||||
|
return hash.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存的首页商品数据
|
||||||
|
* @returns {Object|null} 缓存的商品数据,包含数据、哈希值和时间戳
|
||||||
|
*/
|
||||||
|
export function getHomeGoodsCache() {
|
||||||
|
return goodsCache.home
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置首页商品缓存
|
||||||
|
* @param {Array} data - 商品数据列表
|
||||||
|
*/
|
||||||
|
export function setHomeGoodsCache(data) {
|
||||||
|
goodsCache.home = {
|
||||||
|
data,
|
||||||
|
hash: calculateDataHash(data),
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定分类的商品缓存
|
||||||
|
* @param {Number|String} categoryId - 分类ID
|
||||||
|
* @returns {Object|null} 缓存的商品数据
|
||||||
|
*/
|
||||||
|
export function getCategoryGoodsCache(categoryId) {
|
||||||
|
return goodsCache.category[categoryId] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置指定分类的商品缓存
|
||||||
|
* @param {Number|String} categoryId - 分类ID
|
||||||
|
* @param {Array} data - 商品数据列表
|
||||||
|
*/
|
||||||
|
export function setCategoryGoodsCache(categoryId, data) {
|
||||||
|
goodsCache.category[categoryId] = {
|
||||||
|
data,
|
||||||
|
hash: calculateDataHash(data),
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查缓存是否有效
|
||||||
|
* @param {Object} cache - 缓存对象
|
||||||
|
* @returns {Boolean} 缓存是否有效
|
||||||
|
*/
|
||||||
|
function isCacheValid(cache) {
|
||||||
|
if (!cache) return false
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
// 检查缓存是否过期
|
||||||
|
if (now - cache.timestamp > CACHE_EXPIRE_TIME) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取首页商品数据(带缓存)
|
||||||
|
* @param {Object} params - 请求参数
|
||||||
|
* @param {Boolean} forceRefresh - 是否强制刷新
|
||||||
|
* @returns {Promise} 商品数据
|
||||||
|
*/
|
||||||
|
export function getHomeGoodsWithCache(params, forceRefresh = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const cache = getHomeGoodsCache()
|
||||||
|
|
||||||
|
// 如果有有效缓存且不强制刷新,先返回缓存数据
|
||||||
|
if (!forceRefresh && isCacheValid(cache)) {
|
||||||
|
// 先从缓存返回数据
|
||||||
|
resolve({
|
||||||
|
data: { data: { products: cache.data } },
|
||||||
|
fromCache: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无论是否有缓存,都请求最新数据进行对比
|
||||||
|
getGoodsListData(params).then(res => {
|
||||||
|
const newData = res?.data.data?.products || res?.data.data || []
|
||||||
|
const hasMore = res?.data.data?.has_more
|
||||||
|
const nextCursor = res?.data.data?.next_cursor || ''
|
||||||
|
const newHash = calculateDataHash(newData)
|
||||||
|
|
||||||
|
// 检查数据是否有变化
|
||||||
|
const hasChanged = !cache || cache.hash !== newHash
|
||||||
|
|
||||||
|
if (hasChanged || forceRefresh) {
|
||||||
|
// 更新缓存
|
||||||
|
setHomeGoodsCache(newData)
|
||||||
|
|
||||||
|
// 如果没有缓存或者数据有变化,返回最新数据
|
||||||
|
if (!cache || forceRefresh) {
|
||||||
|
resolve({
|
||||||
|
...res,
|
||||||
|
data: {
|
||||||
|
...res.data,
|
||||||
|
data: {
|
||||||
|
...res.data.data,
|
||||||
|
products: newData,
|
||||||
|
has_more: hasMore,
|
||||||
|
next_cursor: nextCursor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromCache: false,
|
||||||
|
hasChanged: true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 如果已有缓存但数据有变化,通知更新
|
||||||
|
resolve({
|
||||||
|
...res,
|
||||||
|
data: {
|
||||||
|
...res.data,
|
||||||
|
data: {
|
||||||
|
...res.data.data,
|
||||||
|
products: newData,
|
||||||
|
has_more: hasMore,
|
||||||
|
next_cursor: nextCursor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromCache: false,
|
||||||
|
hasChanged: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 数据没有变化,更新缓存时间戳
|
||||||
|
cache.timestamp = Date.now()
|
||||||
|
resolve({
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
products: cache.data,
|
||||||
|
has_more: hasMore,
|
||||||
|
next_cursor: nextCursor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromCache: true,
|
||||||
|
hasChanged: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
// 如果请求失败但有缓存,返回缓存
|
||||||
|
if (cache) {
|
||||||
|
resolve({
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
products: cache.data,
|
||||||
|
has_more: false,
|
||||||
|
next_cursor: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromCache: true,
|
||||||
|
error: err
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分类页商品数据(带缓存)
|
||||||
|
* @param {Object} params - 请求参数
|
||||||
|
* @param {Boolean} forceRefresh - 是否强制刷新
|
||||||
|
* @returns {Promise} 商品数据
|
||||||
|
*/
|
||||||
|
export function getCategoryGoodsWithCache(params, forceRefresh = false) {
|
||||||
|
const categoryId = params.type || params.category_id || ''
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const cache = getCategoryGoodsCache(categoryId)
|
||||||
|
|
||||||
|
// 如果有有效缓存且不强制刷新,先返回缓存数据
|
||||||
|
if (!forceRefresh && isCacheValid(cache)) {
|
||||||
|
resolve({
|
||||||
|
data: cache.data,
|
||||||
|
count: cache.data.length,
|
||||||
|
fromCache: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无论是否有缓存,都请求最新数据进行对比
|
||||||
|
getGoodsListData(params).then(res => {
|
||||||
|
const newData = res?.data.data?.products || res?.data || []
|
||||||
|
const hasMore = res?.data.data?.has_more
|
||||||
|
const nextCursor = res?.data.data?.next_cursor || ''
|
||||||
|
const newHash = calculateDataHash(newData)
|
||||||
|
|
||||||
|
// 检查数据是否有变化
|
||||||
|
const hasChanged = !cache || cache.hash !== newHash
|
||||||
|
|
||||||
|
if (hasChanged || forceRefresh) {
|
||||||
|
// 更新缓存
|
||||||
|
setCategoryGoodsCache(categoryId, newData)
|
||||||
|
|
||||||
|
if (!cache || forceRefresh) {
|
||||||
|
resolve({
|
||||||
|
...res,
|
||||||
|
data: {
|
||||||
|
...res.data,
|
||||||
|
data: {
|
||||||
|
...res.data.data,
|
||||||
|
products: newData,
|
||||||
|
has_more: hasMore,
|
||||||
|
next_cursor: nextCursor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromCache: false,
|
||||||
|
hasChanged: true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve({
|
||||||
|
...res,
|
||||||
|
data: {
|
||||||
|
...res.data,
|
||||||
|
data: {
|
||||||
|
...res.data.data,
|
||||||
|
products: newData,
|
||||||
|
has_more: hasMore,
|
||||||
|
next_cursor: nextCursor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromCache: false,
|
||||||
|
hasChanged: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 数据没有变化,更新缓存时间戳
|
||||||
|
cache.timestamp = Date.now()
|
||||||
|
resolve({
|
||||||
|
data: newData,
|
||||||
|
data: {
|
||||||
|
products: cache.data,
|
||||||
|
has_more: hasMore,
|
||||||
|
next_cursor: nextCursor
|
||||||
|
},
|
||||||
|
count: cache.data.length,
|
||||||
|
fromCache: true,
|
||||||
|
hasChanged: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
// 如果请求失败但有缓存,返回缓存
|
||||||
|
if (cache) {
|
||||||
|
resolve({
|
||||||
|
data: cache.data,
|
||||||
|
data: {
|
||||||
|
products: cache.data,
|
||||||
|
has_more: false,
|
||||||
|
next_cursor: ''
|
||||||
|
},
|
||||||
|
count: cache.data.length,
|
||||||
|
fromCache: true,
|
||||||
|
error: err
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有商品缓存
|
||||||
|
*/
|
||||||
|
export function clearGoodsCache() {
|
||||||
|
goodsCache.home = null
|
||||||
|
goodsCache.category = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除指定分类的商品缓存
|
||||||
|
* @param {Number|String} categoryId - 分类ID
|
||||||
|
*/
|
||||||
|
export function clearCategoryGoodsCache(categoryId) {
|
||||||
|
if (goodsCache.category[categoryId]) {
|
||||||
|
delete goodsCache.category[categoryId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getHomeGoodsCache,
|
||||||
|
setHomeGoodsCache,
|
||||||
|
getCategoryGoodsCache,
|
||||||
|
setCategoryGoodsCache,
|
||||||
|
getHomeGoodsWithCache,
|
||||||
|
getCategoryGoodsWithCache,
|
||||||
|
clearGoodsCache,
|
||||||
|
clearCategoryGoodsCache
|
||||||
|
}
|
||||||
795
src/wxcomponents/wxml-to-canvas/index.vue
Normal file
795
src/wxcomponents/wxml-to-canvas/index.vue
Normal file
@ -0,0 +1,795 @@
|
|||||||
|
<template>
|
||||||
|
<uni-shadow-root class="wxml-to-canvas-index"><canvas v-if="use2dCanvas" id="weui-canvas" type="2d" :style="'width: '+(width)+'px; height: '+(height)+'px;'"></canvas>
|
||||||
|
<canvas v-else canvas-id="weui-canvas" :style="'width: '+(width)+'px; height: '+(height)+'px;'"></canvas></uni-shadow-root>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
global['__wxVueOptions'] = {components:{}}
|
||||||
|
|
||||||
|
global['__wxRoute'] = 'wxml-to-canvas/index'
|
||||||
|
(function webpackUniversalModuleDefinition(root, factory) {
|
||||||
|
if(typeof exports === 'object' && typeof module === 'object')
|
||||||
|
module.exports = factory();
|
||||||
|
else if(typeof define === 'function' && define.amd)
|
||||||
|
define([], factory);
|
||||||
|
else {
|
||||||
|
var a = factory();
|
||||||
|
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
|
||||||
|
}
|
||||||
|
})(window, function() {
|
||||||
|
return /******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId]) {
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ i: moduleId,
|
||||||
|
/******/ l: false,
|
||||||
|
/******/ exports: {}
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // Load entry module and return exports
|
||||||
|
/******/ return __webpack_require__(__webpack_require__.s = 1);
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ([
|
||||||
|
/* 0 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
const hex = (color) => {
|
||||||
|
let result = null
|
||||||
|
|
||||||
|
if (/^#/.test(color) && (color.length === 7 || color.length === 9)) {
|
||||||
|
return color
|
||||||
|
// eslint-disable-next-line no-cond-assign
|
||||||
|
} else if ((result = /^(rgb|rgba)\((.+)\)/.exec(color)) !== null) {
|
||||||
|
return '#' + result[2].split(',').map((part, index) => {
|
||||||
|
part = part.trim()
|
||||||
|
part = index === 3 ? Math.floor(parseFloat(part) * 255) : parseInt(part, 10)
|
||||||
|
part = part.toString(16)
|
||||||
|
if (part.length === 1) {
|
||||||
|
part = '0' + part
|
||||||
|
}
|
||||||
|
return part
|
||||||
|
}).join('')
|
||||||
|
} else {
|
||||||
|
return '#00000000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitLineToCamelCase = (str) => str.split('-').map((part, index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
return part[0].toUpperCase() + part.slice(1)
|
||||||
|
}).join('')
|
||||||
|
|
||||||
|
const compareVersion = (v1, v2) => {
|
||||||
|
v1 = v1.split('.')
|
||||||
|
v2 = v2.split('.')
|
||||||
|
const len = Math.max(v1.length, v2.length)
|
||||||
|
while (v1.length < len) {
|
||||||
|
v1.push('0')
|
||||||
|
}
|
||||||
|
while (v2.length < len) {
|
||||||
|
v2.push('0')
|
||||||
|
}
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const num1 = parseInt(v1[i], 10)
|
||||||
|
const num2 = parseInt(v2[i], 10)
|
||||||
|
|
||||||
|
if (num1 > num2) {
|
||||||
|
return 1
|
||||||
|
} else if (num1 < num2) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hex,
|
||||||
|
splitLineToCamelCase,
|
||||||
|
compareVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 1 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
|
||||||
|
const xmlParse = __webpack_require__(2)
|
||||||
|
const {Widget} = __webpack_require__(3)
|
||||||
|
const {Draw} = __webpack_require__(5)
|
||||||
|
const {compareVersion} = __webpack_require__(0)
|
||||||
|
|
||||||
|
const canvasId = 'weui-canvas'
|
||||||
|
|
||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
value: 400
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
value: 300
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
use2dCanvas: false, // 2.9.2 后可用canvas 2d 接口
|
||||||
|
},
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
const {SDKVersion, pixelRatio: dpr} = wx.getSystemInfoSync()
|
||||||
|
const use2dCanvas = compareVersion(SDKVersion, '2.9.2') >= 0
|
||||||
|
this.dpr = dpr
|
||||||
|
this.setData({use2dCanvas}, () => {
|
||||||
|
if (use2dCanvas) {
|
||||||
|
const query = this.createSelectorQuery()
|
||||||
|
query.select(`#${canvasId}`)
|
||||||
|
.fields({node: true, size: true})
|
||||||
|
.exec(res => {
|
||||||
|
const canvas = res[0].node
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
canvas.width = res[0].width * dpr
|
||||||
|
canvas.height = res[0].height * dpr
|
||||||
|
ctx.scale(dpr, dpr)
|
||||||
|
this.ctx = ctx
|
||||||
|
this.canvas = canvas
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.ctx = wx.createCanvasContext(canvasId, this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async renderToCanvas(args) {
|
||||||
|
const {wxml, style} = args
|
||||||
|
const ctx = this.ctx
|
||||||
|
const canvas = this.canvas
|
||||||
|
const use2dCanvas = this.data.use2dCanvas
|
||||||
|
|
||||||
|
if (use2dCanvas && !canvas) {
|
||||||
|
return Promise.reject(new Error('renderToCanvas: fail canvas has not been created'))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, this.data.width, this.data.height)
|
||||||
|
const {root: xom} = xmlParse(wxml)
|
||||||
|
|
||||||
|
const widget = new Widget(xom, style)
|
||||||
|
const container = widget.init()
|
||||||
|
this.boundary = {
|
||||||
|
top: container.layoutBox.top,
|
||||||
|
left: container.layoutBox.left,
|
||||||
|
width: container.computedStyle.width,
|
||||||
|
height: container.computedStyle.height,
|
||||||
|
}
|
||||||
|
const draw = new Draw(ctx, canvas, use2dCanvas)
|
||||||
|
await draw.drawNode(container)
|
||||||
|
|
||||||
|
if (!use2dCanvas) {
|
||||||
|
await this.canvasDraw(ctx)
|
||||||
|
}
|
||||||
|
return Promise.resolve(container)
|
||||||
|
},
|
||||||
|
|
||||||
|
canvasDraw(ctx, reserve) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
ctx.draw(reserve, () => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
canvasToTempFilePath(args = {}) {
|
||||||
|
const use2dCanvas = this.data.use2dCanvas
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const {
|
||||||
|
top, left, width, height
|
||||||
|
} = this.boundary
|
||||||
|
|
||||||
|
const copyArgs = {
|
||||||
|
x: left,
|
||||||
|
y: top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
destWidth: width * this.dpr,
|
||||||
|
destHeight: height * this.dpr,
|
||||||
|
canvasId,
|
||||||
|
fileType: args.fileType || 'png',
|
||||||
|
quality: args.quality || 1,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use2dCanvas) {
|
||||||
|
delete copyArgs.canvasId
|
||||||
|
copyArgs.canvas = this.canvas
|
||||||
|
}
|
||||||
|
wx.canvasToTempFilePath(copyArgs, this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 2 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `parse`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given string of `xml`.
|
||||||
|
*
|
||||||
|
* @param {String} xml
|
||||||
|
* @return {Object}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parse(xml) {
|
||||||
|
xml = xml.trim()
|
||||||
|
|
||||||
|
// strip comments
|
||||||
|
xml = xml.replace(/<!--[\s\S]*?-->/g, '')
|
||||||
|
|
||||||
|
return document()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML document.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function document() {
|
||||||
|
return {
|
||||||
|
declaration: declaration(),
|
||||||
|
root: tag()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declaration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function declaration() {
|
||||||
|
const m = match(/^<\?xml\s*/)
|
||||||
|
if (!m) return
|
||||||
|
|
||||||
|
// tag
|
||||||
|
const node = {
|
||||||
|
attributes: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attributes
|
||||||
|
while (!(eos() || is('?>'))) {
|
||||||
|
const attr = attribute()
|
||||||
|
if (!attr) return node
|
||||||
|
node.attributes[attr.name] = attr.value
|
||||||
|
}
|
||||||
|
|
||||||
|
match(/\?>\s*/)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function tag() {
|
||||||
|
const m = match(/^<([\w-:.]+)\s*/)
|
||||||
|
if (!m) return
|
||||||
|
|
||||||
|
// name
|
||||||
|
const node = {
|
||||||
|
name: m[1],
|
||||||
|
attributes: {},
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// attributes
|
||||||
|
while (!(eos() || is('>') || is('?>') || is('/>'))) {
|
||||||
|
const attr = attribute()
|
||||||
|
if (!attr) return node
|
||||||
|
node.attributes[attr.name] = attr.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// self closing tag
|
||||||
|
if (match(/^\s*\/>\s*/)) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
match(/\??>\s*/)
|
||||||
|
|
||||||
|
// content
|
||||||
|
node.content = content()
|
||||||
|
|
||||||
|
// children
|
||||||
|
let child
|
||||||
|
while (child = tag()) {
|
||||||
|
node.children.push(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
// closing
|
||||||
|
match(/^<\/[\w-:.]+>\s*/)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text content.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function content() {
|
||||||
|
const m = match(/^([^<]*)/)
|
||||||
|
if (m) return m[1]
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function attribute() {
|
||||||
|
const m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/)
|
||||||
|
if (!m) return
|
||||||
|
return {name: m[1], value: strip(m[2])}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip quotes from `val`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function strip(val) {
|
||||||
|
return val.replace(/^['"]|['"]$/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match `re` and advance the string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function match(re) {
|
||||||
|
const m = xml.match(re)
|
||||||
|
if (!m) return
|
||||||
|
xml = xml.slice(m[0].length)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End-of-source.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function eos() {
|
||||||
|
return xml.length == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for `prefix`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function is(prefix) {
|
||||||
|
return xml.indexOf(prefix) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parse
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 3 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
const Block = __webpack_require__(4)
|
||||||
|
const {splitLineToCamelCase} = __webpack_require__(0)
|
||||||
|
|
||||||
|
class Element extends Block {
|
||||||
|
constructor(prop) {
|
||||||
|
super(prop.style)
|
||||||
|
this.name = prop.name
|
||||||
|
this.attributes = prop.attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Widget {
|
||||||
|
constructor(xom, style) {
|
||||||
|
this.xom = xom
|
||||||
|
this.style = style
|
||||||
|
|
||||||
|
this.inheritProps = ['fontSize', 'lineHeight', 'textAlign', 'verticalAlign', 'color']
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.container = this.create(this.xom)
|
||||||
|
this.container.layout()
|
||||||
|
|
||||||
|
this.inheritStyle(this.container)
|
||||||
|
return this.container
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继承父节点的样式
|
||||||
|
inheritStyle(node) {
|
||||||
|
const parent = node.parent || null
|
||||||
|
const children = node.children || {}
|
||||||
|
const computedStyle = node.computedStyle
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
this.inheritProps.forEach(prop => {
|
||||||
|
computedStyle[prop] = computedStyle[prop] || parent.computedStyle[prop]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.values(children).forEach(child => {
|
||||||
|
this.inheritStyle(child)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
create(node) {
|
||||||
|
let classNames = (node.attributes.class || '').split(' ')
|
||||||
|
classNames = classNames.map(item => splitLineToCamelCase(item.trim()))
|
||||||
|
const style = {}
|
||||||
|
classNames.forEach(item => {
|
||||||
|
Object.assign(style, this.style[item] || {})
|
||||||
|
})
|
||||||
|
|
||||||
|
const args = {name: node.name, style}
|
||||||
|
|
||||||
|
const attrs = Object.keys(node.attributes)
|
||||||
|
const attributes = {}
|
||||||
|
for (const attr of attrs) {
|
||||||
|
const value = node.attributes[attr]
|
||||||
|
const CamelAttr = splitLineToCamelCase(attr)
|
||||||
|
|
||||||
|
if (value === '' || value === 'true') {
|
||||||
|
attributes[CamelAttr] = true
|
||||||
|
} else if (value === 'false') {
|
||||||
|
attributes[CamelAttr] = false
|
||||||
|
} else {
|
||||||
|
attributes[CamelAttr] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributes.text = node.content
|
||||||
|
args.attributes = attributes
|
||||||
|
const element = new Element(args)
|
||||||
|
node.children.forEach(childNode => {
|
||||||
|
const childElement = this.create(childNode)
|
||||||
|
element.add(childElement)
|
||||||
|
})
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {Widget}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 4 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
module.exports = require("../widget-ui/index.js")
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 5 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
class Draw {
|
||||||
|
constructor(context, canvas, use2dCanvas = false) {
|
||||||
|
this.ctx = context
|
||||||
|
this.canvas = canvas || null
|
||||||
|
this.use2dCanvas = use2dCanvas
|
||||||
|
}
|
||||||
|
|
||||||
|
roundRect(x, y, w, h, r, fill = true, stroke = false) {
|
||||||
|
if (r < 0) return
|
||||||
|
const ctx = this.ctx
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2)
|
||||||
|
ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0)
|
||||||
|
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2)
|
||||||
|
ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI)
|
||||||
|
ctx.lineTo(x, y + r)
|
||||||
|
if (stroke) ctx.stroke()
|
||||||
|
if (fill) ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
drawView(box, style) {
|
||||||
|
const ctx = this.ctx
|
||||||
|
const {
|
||||||
|
left: x, top: y, width: w, height: h
|
||||||
|
} = box
|
||||||
|
const {
|
||||||
|
borderRadius = 0,
|
||||||
|
borderWidth = 0,
|
||||||
|
borderColor,
|
||||||
|
color = '#000',
|
||||||
|
backgroundColor = 'transparent',
|
||||||
|
} = style
|
||||||
|
ctx.save()
|
||||||
|
// 外环
|
||||||
|
if (borderWidth > 0) {
|
||||||
|
ctx.fillStyle = borderColor || color
|
||||||
|
this.roundRect(x, y, w, h, borderRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内环
|
||||||
|
ctx.fillStyle = backgroundColor
|
||||||
|
const innerWidth = w - 2 * borderWidth
|
||||||
|
const innerHeight = h - 2 * borderWidth
|
||||||
|
const innerRadius = borderRadius - borderWidth >= 0 ? borderRadius - borderWidth : 0
|
||||||
|
this.roundRect(x + borderWidth, y + borderWidth, innerWidth, innerHeight, innerRadius)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
async drawImage(img, box, style) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const ctx = this.ctx
|
||||||
|
const canvas = this.canvas
|
||||||
|
|
||||||
|
const {
|
||||||
|
borderRadius = 0
|
||||||
|
} = style
|
||||||
|
const {
|
||||||
|
left: x, top: y, width: w, height: h
|
||||||
|
} = box
|
||||||
|
ctx.save()
|
||||||
|
this.roundRect(x, y, w, h, borderRadius, false, false)
|
||||||
|
ctx.clip()
|
||||||
|
|
||||||
|
const _drawImage = (img) => {
|
||||||
|
if (this.use2dCanvas) {
|
||||||
|
const Image = canvas.createImage()
|
||||||
|
Image.onload = () => {
|
||||||
|
ctx.drawImage(Image, x, y, w, h)
|
||||||
|
ctx.restore()
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
Image.onerror = () => { reject(new Error(`createImage fail: ${img}`)) }
|
||||||
|
Image.src = img
|
||||||
|
} else {
|
||||||
|
ctx.drawImage(img, x, y, w, h)
|
||||||
|
ctx.restore()
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTempFile = /^wxfile:\/\//.test(img)
|
||||||
|
const isNetworkFile = /^https?:\/\//.test(img)
|
||||||
|
|
||||||
|
if (isTempFile) {
|
||||||
|
_drawImage(img)
|
||||||
|
} else if (isNetworkFile) {
|
||||||
|
wx.downloadFile({
|
||||||
|
url: img,
|
||||||
|
success(res) {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
_drawImage(res.tempFilePath)
|
||||||
|
} else {
|
||||||
|
reject(new Error(`downloadFile:fail ${img}`))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail() {
|
||||||
|
reject(new Error(`downloadFile:fail ${img}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
reject(new Error(`image format error: ${img}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
|
drawText(text, box, style) {
|
||||||
|
const ctx = this.ctx
|
||||||
|
let {
|
||||||
|
left: x, top: y, width: w, height: h
|
||||||
|
} = box
|
||||||
|
let {
|
||||||
|
color = '#000',
|
||||||
|
lineHeight = '1.4em',
|
||||||
|
fontSize = 14,
|
||||||
|
textAlign = 'left',
|
||||||
|
verticalAlign = 'top',
|
||||||
|
backgroundColor = 'transparent'
|
||||||
|
} = style
|
||||||
|
|
||||||
|
if (typeof lineHeight === 'string') { // 2em
|
||||||
|
lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize)
|
||||||
|
}
|
||||||
|
if (!text || (lineHeight > h)) return
|
||||||
|
|
||||||
|
ctx.save()
|
||||||
|
ctx.textBaseline = 'top'
|
||||||
|
ctx.font = `${fontSize}px sans-serif`
|
||||||
|
ctx.textAlign = textAlign
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
ctx.fillStyle = backgroundColor
|
||||||
|
this.roundRect(x, y, w, h, 0)
|
||||||
|
|
||||||
|
// 文字颜色
|
||||||
|
ctx.fillStyle = color
|
||||||
|
|
||||||
|
// 水平布局
|
||||||
|
switch (textAlign) {
|
||||||
|
case 'left':
|
||||||
|
break
|
||||||
|
case 'center':
|
||||||
|
x += 0.5 * w
|
||||||
|
break
|
||||||
|
case 'right':
|
||||||
|
x += w
|
||||||
|
break
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
const textWidth = ctx.measureText(text).width
|
||||||
|
const actualHeight = Math.ceil(textWidth / w) * lineHeight
|
||||||
|
let paddingTop = Math.ceil((h - actualHeight) / 2)
|
||||||
|
if (paddingTop < 0) paddingTop = 0
|
||||||
|
|
||||||
|
// 垂直布局
|
||||||
|
switch (verticalAlign) {
|
||||||
|
case 'top':
|
||||||
|
break
|
||||||
|
case 'middle':
|
||||||
|
y += paddingTop
|
||||||
|
break
|
||||||
|
case 'bottom':
|
||||||
|
y += 2 * paddingTop
|
||||||
|
break
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2)
|
||||||
|
|
||||||
|
// 不超过一行
|
||||||
|
if (textWidth <= w) {
|
||||||
|
ctx.fillText(text, x, y + inlinePaddingTop)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多行文本
|
||||||
|
const chars = text.split('')
|
||||||
|
const _y = y
|
||||||
|
|
||||||
|
// 逐行绘制
|
||||||
|
let line = ''
|
||||||
|
for (const ch of chars) {
|
||||||
|
const testLine = line + ch
|
||||||
|
const testWidth = ctx.measureText(testLine).width
|
||||||
|
|
||||||
|
if (testWidth > w) {
|
||||||
|
ctx.fillText(line, x, y + inlinePaddingTop)
|
||||||
|
y += lineHeight
|
||||||
|
line = ch
|
||||||
|
if ((y + lineHeight) > (_y + h)) break
|
||||||
|
} else {
|
||||||
|
line = testLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免溢出
|
||||||
|
if ((y + lineHeight) <= (_y + h)) {
|
||||||
|
ctx.fillText(line, x, y + inlinePaddingTop)
|
||||||
|
}
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
async drawNode(element) {
|
||||||
|
const {layoutBox, computedStyle, name} = element
|
||||||
|
const {src, text} = element.attributes
|
||||||
|
if (name === 'view') {
|
||||||
|
this.drawView(layoutBox, computedStyle)
|
||||||
|
} else if (name === 'image') {
|
||||||
|
await this.drawImage(src, layoutBox, computedStyle)
|
||||||
|
} else if (name === 'text') {
|
||||||
|
this.drawText(text, layoutBox, computedStyle)
|
||||||
|
}
|
||||||
|
const childs = Object.values(element.children)
|
||||||
|
for (const child of childs) {
|
||||||
|
await this.drawNode(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Draw
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
/******/ ]);
|
||||||
|
});
|
||||||
|
export default global['__wxComponents']['wxml-to-canvas/index']
|
||||||
|
</script>
|
||||||
|
<style platform="mp-weixin">
|
||||||
|
|
||||||
|
</style>
|
||||||
794
src/wxcomponents/wxml-to-canvas/miniprogram_dist/index.vue
Normal file
794
src/wxcomponents/wxml-to-canvas/miniprogram_dist/index.vue
Normal file
@ -0,0 +1,794 @@
|
|||||||
|
<template>
|
||||||
|
<uni-shadow-root class="wxml-to-canvas-miniprogram_dist-index"><canvas v-if="use2dCanvas" id="weui-canvas" type="2d" :style="'width: '+(width)+'px; height: '+(height)+'px;'"></canvas>
|
||||||
|
<canvas v-else canvas-id="weui-canvas" :style="'width: '+(width)+'px; height: '+(height)+'px;'"></canvas></uni-shadow-root>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
global['__wxVueOptions'] = {components:{}}
|
||||||
|
|
||||||
|
global['__wxRoute'] = 'wxml-to-canvas/miniprogram_dist/index'
|
||||||
|
(function webpackUniversalModuleDefinition(root, factory) {
|
||||||
|
if(typeof exports === 'object' && typeof module === 'object')
|
||||||
|
module.exports = factory();
|
||||||
|
else if(typeof define === 'function' && define.amd)
|
||||||
|
define([], factory);
|
||||||
|
else {
|
||||||
|
var a = factory();
|
||||||
|
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
|
||||||
|
}
|
||||||
|
})(window, function() {
|
||||||
|
return /******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId]) {
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ i: moduleId,
|
||||||
|
/******/ l: false,
|
||||||
|
/******/ exports: {}
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // Load entry module and return exports
|
||||||
|
/******/ return __webpack_require__(__webpack_require__.s = 1);
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ([
|
||||||
|
/* 0 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
const hex = (color) => {
|
||||||
|
let result = null
|
||||||
|
|
||||||
|
if (/^#/.test(color) && (color.length === 7 || color.length === 9)) {
|
||||||
|
return color
|
||||||
|
// eslint-disable-next-line no-cond-assign
|
||||||
|
} else if ((result = /^(rgb|rgba)\((.+)\)/.exec(color)) !== null) {
|
||||||
|
return '#' + result[2].split(',').map((part, index) => {
|
||||||
|
part = part.trim()
|
||||||
|
part = index === 3 ? Math.floor(parseFloat(part) * 255) : parseInt(part, 10)
|
||||||
|
part = part.toString(16)
|
||||||
|
if (part.length === 1) {
|
||||||
|
part = '0' + part
|
||||||
|
}
|
||||||
|
return part
|
||||||
|
}).join('')
|
||||||
|
} else {
|
||||||
|
return '#00000000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitLineToCamelCase = (str) => str.split('-').map((part, index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
return part[0].toUpperCase() + part.slice(1)
|
||||||
|
}).join('')
|
||||||
|
|
||||||
|
const compareVersion = (v1, v2) => {
|
||||||
|
v1 = v1.split('.')
|
||||||
|
v2 = v2.split('.')
|
||||||
|
const len = Math.max(v1.length, v2.length)
|
||||||
|
while (v1.length < len) {
|
||||||
|
v1.push('0')
|
||||||
|
}
|
||||||
|
while (v2.length < len) {
|
||||||
|
v2.push('0')
|
||||||
|
}
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const num1 = parseInt(v1[i], 10)
|
||||||
|
const num2 = parseInt(v2[i], 10)
|
||||||
|
|
||||||
|
if (num1 > num2) {
|
||||||
|
return 1
|
||||||
|
} else if (num1 < num2) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hex,
|
||||||
|
splitLineToCamelCase,
|
||||||
|
compareVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 1 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
|
||||||
|
const xmlParse = __webpack_require__(2)
|
||||||
|
const {Widget} = __webpack_require__(3)
|
||||||
|
const {Draw} = __webpack_require__(5)
|
||||||
|
const {compareVersion} = __webpack_require__(0)
|
||||||
|
|
||||||
|
const canvasId = 'weui-canvas'
|
||||||
|
|
||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
value: 400
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
value: 300
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
use2dCanvas: false, // 2.9.2 后可用canvas 2d 接口
|
||||||
|
},
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
const {SDKVersion, pixelRatio: dpr} = wx.getSystemInfoSync()
|
||||||
|
const use2dCanvas = compareVersion(SDKVersion, '2.9.2') >= 0
|
||||||
|
this.dpr = dpr
|
||||||
|
this.setData({use2dCanvas}, () => {
|
||||||
|
if (use2dCanvas) {
|
||||||
|
const query = this.createSelectorQuery()
|
||||||
|
query.select(`#${canvasId}`)
|
||||||
|
.fields({node: true, size: true})
|
||||||
|
.exec(res => {
|
||||||
|
const canvas = res[0].node
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
canvas.width = res[0].width * dpr
|
||||||
|
canvas.height = res[0].height * dpr
|
||||||
|
ctx.scale(dpr, dpr)
|
||||||
|
this.ctx = ctx
|
||||||
|
this.canvas = canvas
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.ctx = wx.createCanvasContext(canvasId, this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async renderToCanvas(args) {
|
||||||
|
const {wxml, style} = args
|
||||||
|
const ctx = this.ctx
|
||||||
|
const canvas = this.canvas
|
||||||
|
const use2dCanvas = this.data.use2dCanvas
|
||||||
|
|
||||||
|
if (use2dCanvas && !canvas) {
|
||||||
|
return Promise.reject(new Error('renderToCanvas: fail canvas has not been created'))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, this.data.width, this.data.height)
|
||||||
|
const {root: xom} = xmlParse(wxml)
|
||||||
|
|
||||||
|
const widget = new Widget(xom, style)
|
||||||
|
const container = widget.init()
|
||||||
|
this.boundary = {
|
||||||
|
top: container.layoutBox.top,
|
||||||
|
left: container.layoutBox.left,
|
||||||
|
width: container.computedStyle.width,
|
||||||
|
height: container.computedStyle.height,
|
||||||
|
}
|
||||||
|
const draw = new Draw(ctx, canvas, use2dCanvas)
|
||||||
|
await draw.drawNode(container)
|
||||||
|
|
||||||
|
if (!use2dCanvas) {
|
||||||
|
await this.canvasDraw(ctx)
|
||||||
|
}
|
||||||
|
return Promise.resolve(container)
|
||||||
|
},
|
||||||
|
|
||||||
|
canvasDraw(ctx, reserve) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
ctx.draw(reserve, () => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
canvasToTempFilePath(args = {}) {
|
||||||
|
const use2dCanvas = this.data.use2dCanvas
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const {
|
||||||
|
top, left, width, height
|
||||||
|
} = this.boundary
|
||||||
|
|
||||||
|
const copyArgs = {
|
||||||
|
x: left,
|
||||||
|
y: top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
destWidth: width * this.dpr,
|
||||||
|
destHeight: height * this.dpr,
|
||||||
|
canvasId,
|
||||||
|
fileType: args.fileType || 'png',
|
||||||
|
quality: args.quality || 1,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use2dCanvas) {
|
||||||
|
delete copyArgs.canvasId
|
||||||
|
copyArgs.canvas = this.canvas
|
||||||
|
}
|
||||||
|
wx.canvasToTempFilePath(copyArgs, this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 2 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `parse`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given string of `xml`.
|
||||||
|
*
|
||||||
|
* @param {String} xml
|
||||||
|
* @return {Object}
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parse(xml) {
|
||||||
|
xml = xml.trim()
|
||||||
|
|
||||||
|
// strip comments
|
||||||
|
xml = xml.replace(/<!--[\s\S]*?-->/g, '')
|
||||||
|
|
||||||
|
return document()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML document.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function document() {
|
||||||
|
return {
|
||||||
|
declaration: declaration(),
|
||||||
|
root: tag()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declaration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function declaration() {
|
||||||
|
const m = match(/^<\?xml\s*/)
|
||||||
|
if (!m) return
|
||||||
|
|
||||||
|
// tag
|
||||||
|
const node = {
|
||||||
|
attributes: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attributes
|
||||||
|
while (!(eos() || is('?>'))) {
|
||||||
|
const attr = attribute()
|
||||||
|
if (!attr) return node
|
||||||
|
node.attributes[attr.name] = attr.value
|
||||||
|
}
|
||||||
|
|
||||||
|
match(/\?>\s*/)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function tag() {
|
||||||
|
const m = match(/^<([\w-:.]+)\s*/)
|
||||||
|
if (!m) return
|
||||||
|
|
||||||
|
// name
|
||||||
|
const node = {
|
||||||
|
name: m[1],
|
||||||
|
attributes: {},
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// attributes
|
||||||
|
while (!(eos() || is('>') || is('?>') || is('/>'))) {
|
||||||
|
const attr = attribute()
|
||||||
|
if (!attr) return node
|
||||||
|
node.attributes[attr.name] = attr.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// self closing tag
|
||||||
|
if (match(/^\s*\/>\s*/)) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
match(/\??>\s*/)
|
||||||
|
|
||||||
|
// content
|
||||||
|
node.content = content()
|
||||||
|
|
||||||
|
// children
|
||||||
|
let child
|
||||||
|
while (child = tag()) {
|
||||||
|
node.children.push(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
// closing
|
||||||
|
match(/^<\/[\w-:.]+>\s*/)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text content.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function content() {
|
||||||
|
const m = match(/^([^<]*)/)
|
||||||
|
if (m) return m[1]
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function attribute() {
|
||||||
|
const m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/)
|
||||||
|
if (!m) return
|
||||||
|
return {name: m[1], value: strip(m[2])}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip quotes from `val`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function strip(val) {
|
||||||
|
return val.replace(/^['"]|['"]$/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match `re` and advance the string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function match(re) {
|
||||||
|
const m = xml.match(re)
|
||||||
|
if (!m) return
|
||||||
|
xml = xml.slice(m[0].length)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End-of-source.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function eos() {
|
||||||
|
return xml.length == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for `prefix`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function is(prefix) {
|
||||||
|
return xml.indexOf(prefix) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parse
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 3 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
const Block = __webpack_require__(4)
|
||||||
|
const {splitLineToCamelCase} = __webpack_require__(0)
|
||||||
|
|
||||||
|
class Element extends Block {
|
||||||
|
constructor(prop) {
|
||||||
|
super(prop.style)
|
||||||
|
this.name = prop.name
|
||||||
|
this.attributes = prop.attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Widget {
|
||||||
|
constructor(xom, style) {
|
||||||
|
this.xom = xom
|
||||||
|
this.style = style
|
||||||
|
|
||||||
|
this.inheritProps = ['fontSize', 'lineHeight', 'textAlign', 'verticalAlign', 'color']
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.container = this.create(this.xom)
|
||||||
|
this.container.layout()
|
||||||
|
|
||||||
|
this.inheritStyle(this.container)
|
||||||
|
return this.container
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继承父节点的样式
|
||||||
|
inheritStyle(node) {
|
||||||
|
const parent = node.parent || null
|
||||||
|
const children = node.children || {}
|
||||||
|
const computedStyle = node.computedStyle
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
this.inheritProps.forEach(prop => {
|
||||||
|
computedStyle[prop] = computedStyle[prop] || parent.computedStyle[prop]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.values(children).forEach(child => {
|
||||||
|
this.inheritStyle(child)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
create(node) {
|
||||||
|
let classNames = (node.attributes.class || '').split(' ')
|
||||||
|
classNames = classNames.map(item => splitLineToCamelCase(item.trim()))
|
||||||
|
const style = {}
|
||||||
|
classNames.forEach(item => {
|
||||||
|
Object.assign(style, this.style[item] || {})
|
||||||
|
})
|
||||||
|
|
||||||
|
const args = {name: node.name, style}
|
||||||
|
|
||||||
|
const attrs = Object.keys(node.attributes)
|
||||||
|
const attributes = {}
|
||||||
|
for (const attr of attrs) {
|
||||||
|
const value = node.attributes[attr]
|
||||||
|
const CamelAttr = splitLineToCamelCase(attr)
|
||||||
|
|
||||||
|
if (value === '' || value === 'true') {
|
||||||
|
attributes[CamelAttr] = true
|
||||||
|
} else if (value === 'false') {
|
||||||
|
attributes[CamelAttr] = false
|
||||||
|
} else {
|
||||||
|
attributes[CamelAttr] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributes.text = node.content
|
||||||
|
args.attributes = attributes
|
||||||
|
const element = new Element(args)
|
||||||
|
node.children.forEach(childNode => {
|
||||||
|
const childElement = this.create(childNode)
|
||||||
|
element.add(childElement)
|
||||||
|
})
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {Widget}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 4 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
module.exports = require("widget-ui");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 5 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
class Draw {
|
||||||
|
constructor(context, canvas, use2dCanvas = false) {
|
||||||
|
this.ctx = context
|
||||||
|
this.canvas = canvas || null
|
||||||
|
this.use2dCanvas = use2dCanvas
|
||||||
|
}
|
||||||
|
|
||||||
|
roundRect(x, y, w, h, r, fill = true, stroke = false) {
|
||||||
|
if (r < 0) return
|
||||||
|
const ctx = this.ctx
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2)
|
||||||
|
ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0)
|
||||||
|
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2)
|
||||||
|
ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI)
|
||||||
|
ctx.lineTo(x, y + r)
|
||||||
|
if (stroke) ctx.stroke()
|
||||||
|
if (fill) ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
drawView(box, style) {
|
||||||
|
const ctx = this.ctx
|
||||||
|
const {
|
||||||
|
left: x, top: y, width: w, height: h
|
||||||
|
} = box
|
||||||
|
const {
|
||||||
|
borderRadius = 0,
|
||||||
|
borderWidth = 0,
|
||||||
|
borderColor,
|
||||||
|
color = '#000',
|
||||||
|
backgroundColor = 'transparent',
|
||||||
|
} = style
|
||||||
|
ctx.save()
|
||||||
|
// 外环
|
||||||
|
if (borderWidth > 0) {
|
||||||
|
ctx.fillStyle = borderColor || color
|
||||||
|
this.roundRect(x, y, w, h, borderRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内环
|
||||||
|
ctx.fillStyle = backgroundColor
|
||||||
|
const innerWidth = w - 2 * borderWidth
|
||||||
|
const innerHeight = h - 2 * borderWidth
|
||||||
|
const innerRadius = borderRadius - borderWidth >= 0 ? borderRadius - borderWidth : 0
|
||||||
|
this.roundRect(x + borderWidth, y + borderWidth, innerWidth, innerHeight, innerRadius)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
async drawImage(img, box, style) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const ctx = this.ctx
|
||||||
|
const canvas = this.canvas
|
||||||
|
|
||||||
|
const {
|
||||||
|
borderRadius = 0
|
||||||
|
} = style
|
||||||
|
const {
|
||||||
|
left: x, top: y, width: w, height: h
|
||||||
|
} = box
|
||||||
|
ctx.save()
|
||||||
|
this.roundRect(x, y, w, h, borderRadius, false, false)
|
||||||
|
ctx.clip()
|
||||||
|
|
||||||
|
const _drawImage = (img) => {
|
||||||
|
if (this.use2dCanvas) {
|
||||||
|
const Image = canvas.createImage()
|
||||||
|
Image.onload = () => {
|
||||||
|
ctx.drawImage(Image, x, y, w, h)
|
||||||
|
ctx.restore()
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
Image.onerror = () => { reject(new Error(`createImage fail: ${img}`)) }
|
||||||
|
Image.src = img
|
||||||
|
} else {
|
||||||
|
ctx.drawImage(img, x, y, w, h)
|
||||||
|
ctx.restore()
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTempFile = /^wxfile:\/\//.test(img)
|
||||||
|
const isNetworkFile = /^https?:\/\//.test(img)
|
||||||
|
|
||||||
|
if (isTempFile) {
|
||||||
|
_drawImage(img)
|
||||||
|
} else if (isNetworkFile) {
|
||||||
|
wx.downloadFile({
|
||||||
|
url: img,
|
||||||
|
success(res) {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
_drawImage(res.tempFilePath)
|
||||||
|
} else {
|
||||||
|
reject(new Error(`downloadFile:fail ${img}`))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail() {
|
||||||
|
reject(new Error(`downloadFile:fail ${img}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
reject(new Error(`image format error: ${img}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
|
drawText(text, box, style) {
|
||||||
|
const ctx = this.ctx
|
||||||
|
let {
|
||||||
|
left: x, top: y, width: w, height: h
|
||||||
|
} = box
|
||||||
|
let {
|
||||||
|
color = '#000',
|
||||||
|
lineHeight = '1.4em',
|
||||||
|
fontSize = 14,
|
||||||
|
textAlign = 'left',
|
||||||
|
verticalAlign = 'top',
|
||||||
|
backgroundColor = 'transparent'
|
||||||
|
} = style
|
||||||
|
|
||||||
|
if (typeof lineHeight === 'string') { // 2em
|
||||||
|
lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize)
|
||||||
|
}
|
||||||
|
if (!text || (lineHeight > h)) return
|
||||||
|
|
||||||
|
ctx.save()
|
||||||
|
ctx.textBaseline = 'top'
|
||||||
|
ctx.font = `${fontSize}px sans-serif`
|
||||||
|
ctx.textAlign = textAlign
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
ctx.fillStyle = backgroundColor
|
||||||
|
this.roundRect(x, y, w, h, 0)
|
||||||
|
|
||||||
|
// 文字颜色
|
||||||
|
ctx.fillStyle = color
|
||||||
|
|
||||||
|
// 水平布局
|
||||||
|
switch (textAlign) {
|
||||||
|
case 'left':
|
||||||
|
break
|
||||||
|
case 'center':
|
||||||
|
x += 0.5 * w
|
||||||
|
break
|
||||||
|
case 'right':
|
||||||
|
x += w
|
||||||
|
break
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
const textWidth = ctx.measureText(text).width
|
||||||
|
const actualHeight = Math.ceil(textWidth / w) * lineHeight
|
||||||
|
let paddingTop = Math.ceil((h - actualHeight) / 2)
|
||||||
|
if (paddingTop < 0) paddingTop = 0
|
||||||
|
|
||||||
|
// 垂直布局
|
||||||
|
switch (verticalAlign) {
|
||||||
|
case 'top':
|
||||||
|
break
|
||||||
|
case 'middle':
|
||||||
|
y += paddingTop
|
||||||
|
break
|
||||||
|
case 'bottom':
|
||||||
|
y += 2 * paddingTop
|
||||||
|
break
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2)
|
||||||
|
|
||||||
|
// 不超过一行
|
||||||
|
if (textWidth <= w) {
|
||||||
|
ctx.fillText(text, x, y + inlinePaddingTop)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多行文本
|
||||||
|
const chars = text.split('')
|
||||||
|
const _y = y
|
||||||
|
|
||||||
|
// 逐行绘制
|
||||||
|
let line = ''
|
||||||
|
for (const ch of chars) {
|
||||||
|
const testLine = line + ch
|
||||||
|
const testWidth = ctx.measureText(testLine).width
|
||||||
|
|
||||||
|
if (testWidth > w) {
|
||||||
|
ctx.fillText(line, x, y + inlinePaddingTop)
|
||||||
|
y += lineHeight
|
||||||
|
line = ch
|
||||||
|
if ((y + lineHeight) > (_y + h)) break
|
||||||
|
} else {
|
||||||
|
line = testLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免溢出
|
||||||
|
if ((y + lineHeight) <= (_y + h)) {
|
||||||
|
ctx.fillText(line, x, y + inlinePaddingTop)
|
||||||
|
}
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
async drawNode(element) {
|
||||||
|
const {layoutBox, computedStyle, name} = element
|
||||||
|
const {src, text} = element.attributes
|
||||||
|
if (name === 'view') {
|
||||||
|
this.drawView(layoutBox, computedStyle)
|
||||||
|
} else if (name === 'image') {
|
||||||
|
await this.drawImage(src, layoutBox, computedStyle)
|
||||||
|
} else if (name === 'text') {
|
||||||
|
this.drawText(text, layoutBox, computedStyle)
|
||||||
|
}
|
||||||
|
const childs = Object.values(element.children)
|
||||||
|
for (const child of childs) {
|
||||||
|
await this.drawNode(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Draw
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
/******/ ]);
|
||||||
|
});
|
||||||
|
export default global['__wxComponents']['wxml-to-canvas/miniprogram_dist/index']
|
||||||
|
</script>
|
||||||
|
<style platform="mp-weixin">
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -19,6 +19,10 @@ module.exports = {
|
|||||||
// 忽略 moment.js 的 locale 文件,减小体积
|
// 忽略 moment.js 的 locale 文件,减小体积
|
||||||
config.plugin('ignore')
|
config.plugin('ignore')
|
||||||
.use(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)); //忽略/moment/locale下的所有文件
|
.use(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)); //忽略/moment/locale下的所有文件
|
||||||
|
|
||||||
|
// 忽略 qiun-data-charts 中非小程序平台的文件,减小体积
|
||||||
|
config.plugin('ignore-qiun-echarts')
|
||||||
|
.use(new webpack.IgnorePlugin(/echarts\.min\.js$/, /qiun-data-charts[\\/]static/));
|
||||||
|
|
||||||
// 生产环境优化
|
// 生产环境优化
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
|||||||
Reference in New Issue
Block a user