1
This commit is contained in:
@ -7,9 +7,10 @@ import { LOGIN, GET_PHONE, USER_SHARE,USER_WALLET,RECHARGE_WALLET,USER_WXPAY,USE
|
||||
// 微信登陆
|
||||
export const getCodeByWxLogin = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: "weixin",
|
||||
tt.login({
|
||||
provider: "toutiao",
|
||||
success: function (loginRes) {
|
||||
// console.log(loginRes,'?1?')
|
||||
resolve(loginRes.code);
|
||||
},
|
||||
fail: function (err) {
|
||||
@ -22,7 +23,7 @@ export const getCodeByWxLogin = () => {
|
||||
import Store from "../store";
|
||||
|
||||
// 登录鉴权
|
||||
export const login = (phone) => {
|
||||
export const login = (nickName, avatarUrl) => {
|
||||
return getCodeByWxLogin().then((code) => {
|
||||
// 从 vuex 中获取 referrerID(如果通过二维码扫描进入)
|
||||
const referrerID = Store.state.user?.referrerID || 0;
|
||||
@ -31,12 +32,17 @@ export const login = (phone) => {
|
||||
url: LOGIN,
|
||||
method: "POST",
|
||||
data: {
|
||||
nickName:nickName,
|
||||
avatarUrl:avatarUrl,
|
||||
code: code,
|
||||
// yaoqing_code: inviteCode || null,
|
||||
phone: phone || null,
|
||||
source: "wechat",
|
||||
referrerID: Number(referrerID) || 0,
|
||||
referrerType: "wechat"
|
||||
source:'douyin',
|
||||
referrerID:0,
|
||||
referrerType:'douyin'
|
||||
// // yaoqing_code: inviteCode || null,
|
||||
// phone: phone || null,
|
||||
// source: "wechat",
|
||||
// referrerID: Number(referrerID) || 0,
|
||||
// referrerType: "wechat"
|
||||
},
|
||||
}).then((res) => {
|
||||
// 登录接口使用完 referrerID 后,清除一次,避免重复使用
|
||||
|
||||
@ -26,7 +26,7 @@ export const RECHARGE_WALLET = '/wallet/details'
|
||||
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'
|
||||
//积分充值列表
|
||||
@ -186,7 +186,7 @@ export const HOME_ORDERS_CANCEL = "/home/orders/cancel";
|
||||
// 校验节假日费用
|
||||
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";
|
||||
@ -235,7 +235,7 @@ export const CREATE_ORDER_NEW = "/product/order/create";
|
||||
// 创建购物车订单
|
||||
export const CREATE_CART_ORDER = "/product/order/create";
|
||||
// 订单支付
|
||||
export const PAY_ORDER_NEW = "/wxpay/jsapi";
|
||||
export const PAY_ORDER_NEW = "/ttpay/jsapi";
|
||||
// 订单列表
|
||||
export const SHOP_ORDER_LIST = "/product/order/list";
|
||||
// 订单详情
|
||||
@ -304,3 +304,5 @@ export const HOMETRAINING_ORDERS_CANCEL = '/hometraining/orders/cancel'
|
||||
// 撤销申请(领养申请)
|
||||
export const ADOPTIONS_PET_APPLY_CANCEL = '/adoptions/pet/apply/cancel'
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="nav-container">
|
||||
<view :style="{ height: ststuaBarHeight + 'px' }"></view>
|
||||
<!-- <view :style="{ height: ststuaBarHeight + 'px' }"></view> -->
|
||||
<view
|
||||
class="flex-row-center nav-title ali-puhui-bold"
|
||||
:style="{ height: menuButtonHeight + 'px' }"
|
||||
|
||||
@ -551,13 +551,12 @@ export default {
|
||||
}
|
||||
payOrder(this.orderId, chaJiaType).then((res) => {
|
||||
const payData = res?.info?.pay_data || {};
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.sign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
console.log('success:' + JSON.stringify(res));
|
||||
uni.hideLoading();
|
||||
|
||||
@ -272,14 +272,12 @@ export default {
|
||||
},
|
||||
weixinPay(orderId) {
|
||||
payOrder(orderId).then((res) => {
|
||||
const payData = res?.info?.pay_data || {};
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.sign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
uni.hideLoading();
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
|
||||
@ -60,7 +60,8 @@
|
||||
<text v-if="item.item_cut_price" class="price-original">¥{{ item.item_price }}</text>
|
||||
|
||||
</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>
|
||||
@ -109,8 +110,8 @@
|
||||
<text class="zf"> 请选择支付方式 </text>
|
||||
<view class="wechat" @click.stop="selectOption1('1')">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<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" />
|
||||
@ -533,12 +534,12 @@ export default {
|
||||
};
|
||||
walletWxpay(data).then((res) => {
|
||||
// 使用获取的支付参数进行支付
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
// 关闭支付弹窗
|
||||
this.additionalBom = false;
|
||||
|
||||
@ -192,8 +192,8 @@
|
||||
</view>
|
||||
<view class="wechat" @click.stop="selectOption1('1')">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<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" />
|
||||
@ -702,12 +702,12 @@ export default {
|
||||
};
|
||||
payOrder(params)
|
||||
.then((res) => {
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp,
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
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({
|
||||
|
||||
@ -297,8 +297,8 @@
|
||||
<view class="recharge-method">
|
||||
<view class="wechat" @click.stop="selectOption1('1')">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<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" />
|
||||
@ -369,8 +369,8 @@
|
||||
</view>
|
||||
<view class="wechat">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<image v-if="select1" class="not-selected" @click.stop="selectOpt1('1')" src="@/static/images/w.png"
|
||||
mode="widthFix" />
|
||||
@ -792,12 +792,12 @@ export default {
|
||||
};
|
||||
memberWXPAY(data).then((res) => {
|
||||
// 使用获取的支付参数进行支付
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
uni.showToast({
|
||||
title: "支付成功",
|
||||
@ -1202,12 +1202,12 @@ export default {
|
||||
}
|
||||
payOrder(params)
|
||||
.then((res) => {
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
uni.hideLoading();
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
|
||||
@ -115,8 +115,8 @@
|
||||
<!-- 支付方式 -->
|
||||
<view class="info-cell payment-method-cell">
|
||||
<view class="payment-option" @click="selectPaymentMethod('wechat')">
|
||||
<image src="@/static/images/wx.png" mode="aspectFit" class="payment-icon" />
|
||||
<text class="payment-name">微信支付</text>
|
||||
<image src="@/static/images/douy.png" mode="aspectFit" class="payment-icon" />
|
||||
<text class="payment-name">抖音支付</text>
|
||||
<!-- 未选中状态 -->
|
||||
<image v-if="paymentMethod !== 'wechat'" src="@/static/images/w.png" mode="widthFix" class="not-selected" />
|
||||
<!-- 选中状态 -->
|
||||
@ -436,16 +436,12 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const payData = res.data || res.info?.pay_data || {};
|
||||
|
||||
// 调用微信支付
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.paySign || payData.sign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (payRes) => {
|
||||
console.log('支付成功:', payRes);
|
||||
uni.showToast({
|
||||
|
||||
@ -435,13 +435,12 @@ export default {
|
||||
order_no: orderNo,
|
||||
total_fee: 200
|
||||
}).then((res) => {
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: res.data.timeStamp,
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
complete: (res) => {
|
||||
// 支付成功后跳转到订单页面
|
||||
setTimeout(() => {
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
<!-- 支付方式 -->
|
||||
<view class="info-cell payment-method-cell">
|
||||
<view class="payment-option" @click="selectPaymentMethod('wechat')">
|
||||
<image src="@/static/images/wx.png" mode="aspectFit" class="payment-icon" />
|
||||
<text class="payment-name">微信支付</text>
|
||||
<image src="@/static/images/douy.png" mode="aspectFit" class="payment-icon" />
|
||||
<text class="payment-name">抖音支付</text>
|
||||
<!-- 未选中状态 -->
|
||||
<image v-if="paymentMethod !== 'wechat'" src="@/static/images/w.png" mode="widthFix" class="not-selected" />
|
||||
<!-- 选中状态 -->
|
||||
@ -262,16 +262,12 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const payData = res.data || res.info?.pay_data || {};
|
||||
|
||||
// 调用微信支付
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.paySign || payData.sign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (payRes) => {
|
||||
console.log('支付成功:', payRes);
|
||||
uni.showToast({
|
||||
|
||||
@ -71,8 +71,15 @@
|
||||
</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 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>
|
||||
</template>
|
||||
|
||||
@ -107,7 +114,7 @@ export default {
|
||||
},
|
||||
onReady() {
|
||||
// 获取 wxml-to-canvas 组件实例
|
||||
this.widget = this.$refs.widget || this.selectComponent('.widget');
|
||||
// this.widget = this.$refs.widget || this.selectComponent('.widget');
|
||||
},
|
||||
methods: {
|
||||
getCertificates(data) {
|
||||
@ -124,6 +131,27 @@ export default {
|
||||
},
|
||||
closeModal() {
|
||||
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) {
|
||||
// 获取滚动位置(单位:px)
|
||||
@ -141,10 +169,32 @@ export default {
|
||||
this.currentPage = newPage;
|
||||
}
|
||||
},
|
||||
async loadImage(src) {
|
||||
console.log('正在加载图片:', src);
|
||||
return new Promise((resolve, reject) => {
|
||||
tt.downloadFile({
|
||||
url: src,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('图片加载成功:', res.tempFilePath);
|
||||
resolve(res.tempFilePath);
|
||||
} else {
|
||||
console.error('图片加载失败,状态码:', res.statusCode);
|
||||
reject(new Error(`图片加载失败,状态码: ${res.statusCode}`));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('图片加载失败:', err.errMsg);
|
||||
reject(new Error(`图片加载失败: ${err.errMsg}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async saveCurrentImage() {
|
||||
// 保存当前图片 - 使用 wxml-to-canvas 生成
|
||||
const currentIndex = this.currentPage - 1;
|
||||
const currentItem = this.certificateList[currentIndex];
|
||||
|
||||
if (!currentItem) {
|
||||
uni.showToast({
|
||||
title: '证书信息加载中,请稍候',
|
||||
@ -153,82 +203,109 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// 存储当前证书信息到 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
|
||||
// 获取 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 节点获取失败'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!res || !res.tempFilePath) {
|
||||
throw new 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, 50);
|
||||
|
||||
// 绘制英文标题
|
||||
ctx.font = '12px sans-serif';
|
||||
const titleEn = currentItem.title_en || 'Certificate of Love Endurance';
|
||||
ctx.fillText(titleEn, this.canvasWidth / 2, 70);
|
||||
|
||||
// 绘制用户名
|
||||
ctx.font = '16px sans-serif';
|
||||
const userName = this.userName || '周佳佳';
|
||||
ctx.fillText(userName, this.canvasWidth / 2, 100);
|
||||
|
||||
// 绘制描述文本
|
||||
ctx.font = '12px sans-serif';
|
||||
ctx.fillStyle = '#333333';
|
||||
ctx.textAlign = 'left';
|
||||
const description = `您累计捐赠${currentItem.source_value || 0}克粮,为毛孩子奉献爱心,点燃希望,感谢您的捐赠让世界变得更温暖。`;
|
||||
this.wrapText(ctx, description, 20, 130, this.canvasWidth - 40, 16);
|
||||
|
||||
// 绘制结尾文字
|
||||
ctx.fillText('特发此证,以表谢忱!', 20, 200);
|
||||
|
||||
// 绘制证书编号
|
||||
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: res.tempFilePath,
|
||||
filePath: tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
@ -250,27 +327,12 @@ export default {
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
const errorMsg = error?.message || error?.toString() || '未知错误';
|
||||
const errorInfo = `生成图片失败: ${errorMsg}`;
|
||||
console.log(errorInfo, '--=')
|
||||
uni.showToast({
|
||||
title: errorInfo.length > 20 ? '生成图片失败,请重试' : errorInfo,
|
||||
title: errorMsg.length > 20 ? '生成图片失败,请重试' : errorMsg,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
// 使用多种方式记录错误,确保线上环境能捕获
|
||||
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)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
generateCertificateTemplate() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<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="">
|
||||
<!-- 顶部图标 -->
|
||||
<image class="bowl-icon" :src="bowlIcon" mode="aspectFit" />
|
||||
@ -64,11 +64,6 @@ export default {
|
||||
? `${imgPrefix}welfare-dogFood.png`
|
||||
: `${imgPrefix}welfare-catFood.png`;
|
||||
},
|
||||
modalClassList() {
|
||||
const baseClass = 'donation-confirm-modal';
|
||||
const typeClass = this.type === 'dog' ? 'modal-dog' : 'modal-cat';
|
||||
return [baseClass, typeClass];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCancel() {
|
||||
|
||||
@ -223,6 +223,7 @@ export default {
|
||||
});
|
||||
},
|
||||
handleDogCardClick() {
|
||||
console.log(this)
|
||||
this.donationType = 'dog';
|
||||
this.showDonationModal = true;
|
||||
},
|
||||
|
||||
@ -157,10 +157,7 @@
|
||||
{
|
||||
"path": "welfare/certificate-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的证书",
|
||||
"usingComponents": {
|
||||
"wxml-to-canvas": "/wxcomponents/wxml-to-canvas/index"
|
||||
}
|
||||
"navigationBarTitleText": "我的证书"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
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>
|
||||
import AddressItem from "./components/AddressItem.vue";
|
||||
import PopUpModal from "../../../components/PopUpModal.vue";
|
||||
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||
import { delAddress, getAddressList } from "../../../api/address";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AddressItem,
|
||||
ListPageTemp,
|
||||
PopUpModal,
|
||||
},
|
||||
data() {
|
||||
|
||||
@ -4,10 +4,10 @@
|
||||
<image class="login-ground-img" :src="`${imgPrefix}loginGroundImg.png`" mode="widthFix" />
|
||||
<view class="btnConent">
|
||||
<button v-show="!checked" class="loginBtn" @click="unCheckAndGetPhoneNumber">
|
||||
手机快捷登录
|
||||
抖音用户信息授权登录
|
||||
</button>
|
||||
<button v-show="checked" class="loginBtn" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
|
||||
手机快捷登录
|
||||
<button v-show="checked" class="loginBtn" @tap="getPhoneNumber" >
|
||||
抖音用户信息授权登录
|
||||
</button>
|
||||
<view class="notLoginBtn" @click="goBack">
|
||||
暂不登录
|
||||
@ -49,8 +49,12 @@ export default {
|
||||
imgPrefix,
|
||||
checked: false,
|
||||
uncheckMessageDialog: false,
|
||||
yaoqing_code: ''
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.yaoqing_code = options.yaoqing_code
|
||||
},
|
||||
mounted() { },
|
||||
methods: {
|
||||
ptfwxy() {
|
||||
@ -68,35 +72,6 @@ export default {
|
||||
});
|
||||
},
|
||||
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;
|
||||
},
|
||||
unCheckAndGetPhoneNumber() {
|
||||
@ -106,63 +81,25 @@ export default {
|
||||
duration: 2000
|
||||
});
|
||||
},
|
||||
async getPhoneNumber(e) {
|
||||
// console.log(e,'?')
|
||||
// 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;
|
||||
}
|
||||
|
||||
getmembeInfo(nickName,avatarUrl) {
|
||||
const inviteCode = this.yaoqing_code ? this.yaoqing_code : getApp().globalData.inviteCode
|
||||
uni.showLoading({
|
||||
title: "登录中...",
|
||||
icon: "none",
|
||||
mask: true,
|
||||
});
|
||||
|
||||
// 手机号登录
|
||||
login(phone)
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
login(nickName,avatarUrl).then((res) => {
|
||||
uni.hideLoading()
|
||||
this.$store.dispatch("user/setToken", res?.data?.token || "");
|
||||
this.$store.dispatch("user/setUserInfo", res?.data || {});
|
||||
|
||||
// 如果是带邀请码(referrerID)的登录,统一跳转到首页
|
||||
if (hasReferrer) {
|
||||
var pages = getCurrentPages();
|
||||
console.log(pages,'sssss')
|
||||
if (inviteCode) {
|
||||
uni.reLaunch({
|
||||
url: "/pages/client/index/index",
|
||||
});
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
var pages = getCurrentPages();
|
||||
if (pages.length === 1) {
|
||||
uni.reLaunch({
|
||||
url: "/pages/client/index/index",
|
||||
@ -170,14 +107,95 @@ export default {
|
||||
return;
|
||||
}
|
||||
uni.navigateBack();
|
||||
})
|
||||
.catch((err) => {
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: err || "登录失败,请重试",
|
||||
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,'?')
|
||||
// return
|
||||
// 检查是否有 code
|
||||
// if (!e.detail.code) {
|
||||
// uni.showToast({
|
||||
// title: "获取手机号失败,请重试",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// uni.showLoading({
|
||||
// title: "获取手机号中...",
|
||||
// icon: "none",
|
||||
// mask: true,
|
||||
// });
|
||||
|
||||
try {
|
||||
let that = this
|
||||
tt.getUserProfile({
|
||||
|
||||
success(res) {
|
||||
console.log(res,'???')
|
||||
// this.nickName = res.res.userInfo.nickName
|
||||
// this.avatarUrl = res.userInfo.avatarUrl
|
||||
that.getmembeInfo(res.userInfo.nickName,res.userInfo.avatarUrl)
|
||||
// console.log(res,'--=')
|
||||
},
|
||||
fail(res) {
|
||||
console.log("getUserProfile 调用失败", res);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
// const inviteCode = this.yaoqing_code ? this.yaoqing_code : getApp().globalData.inviteCode
|
||||
// console.log('获取到邀请码----->', inviteCode)
|
||||
// 手机号登录
|
||||
|
||||
// 拿着code获取手机号
|
||||
// const codeRes = await getPhone(e.detail.code);
|
||||
// const phone = codeRes?.data?.phoneNumber || "";
|
||||
|
||||
// if (!phone) {
|
||||
// uni.hideLoading();
|
||||
// uni.showToast({
|
||||
// title: "获取手机号失败,请重试",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error("获取手机号失败:", error);
|
||||
@ -215,10 +233,6 @@ export default {
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 登录前先记录是否有邀请码(referrerID),用于登录后导航判断
|
||||
const hasReferrer =
|
||||
this.$store.state?.user && this.$store.state.user.referrerID;
|
||||
|
||||
uni.showLoading({
|
||||
title: "加载中...",
|
||||
icon: "none",
|
||||
@ -228,15 +242,6 @@ export default {
|
||||
.then((res) => {
|
||||
this.$store.dispatch("user/setToken", res?.data?.token || "");
|
||||
this.$store.dispatch("user/setUserInfo", res?.data || {});
|
||||
|
||||
// 如果是带邀请码(referrerID)的登录,统一跳转到首页
|
||||
if (hasReferrer) {
|
||||
uni.reLaunch({
|
||||
url: "/pages/client/index/index",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var pages = getCurrentPages();
|
||||
if (pages.length === 1) {
|
||||
uni.reLaunch({
|
||||
|
||||
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>
|
||||
import CartItem from "./components/CartItem.vue";
|
||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
||||
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||
|
||||
import {
|
||||
getCartList,
|
||||
|
||||
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>
|
||||
|
||||
<script>
|
||||
import ListPageTemp from "@/components/ListPageTemp";
|
||||
import ListPageTemp from "./ListPageTemp";
|
||||
import CouponItem from "@/components/coupon/CouponItem";
|
||||
import { getCouponData, receiveCoupon } from "@/api/coupon";
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
|
||||
<script>
|
||||
import TabsList from "@/components/TabsList.vue";
|
||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
||||
import ListPageTemp from "./ListPageTemp.vue";
|
||||
import ServiceCouponItem from "@/components/coupon/ServiceCouponItem";
|
||||
|
||||
import { getMyServiceCouponList } from "@/api/coupon";
|
||||
|
||||
@ -62,21 +62,21 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="line" />
|
||||
<view class="itemWrapper" @click="toDogTraining">
|
||||
<text class="titlWrapper">狗狗训练</text>
|
||||
<view class="content">上门服务</view>
|
||||
<view class="tips">上门训犬、寄养、喂猫、遛狗</view>
|
||||
<view class="itemWrapper" @click="toPublicBenefit">
|
||||
<text class="titlWrapper">关爱宠物,传递温情</text>
|
||||
<view class="content">公益助力</view>
|
||||
<view class="tips">帮我找个家</view>
|
||||
<view class="itemImg">
|
||||
<image class="menu-img-sm" :src="`${imgPrefix}home-dogTraining.png`" mode="aspectFill" />
|
||||
<image class="menu-img-sm" :src="`${imgPrefix}h-public.png`" mode="aspectFill" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="secondMenu">
|
||||
<scroll-view class="scrollWrapper" :scroll-x="secondMenuItemList.length > 4" enable-flex
|
||||
<scroll-view class="scrollWrapper" scroll-x enable-flex
|
||||
:scroll-with-animation="false"
|
||||
@scroll="onSecondMenuScroll">
|
||||
<view class="secondMenuInner" :class="{ 'no-scroll': secondMenuItemList.length <= 4 }">
|
||||
<view class="secondMenuInner">
|
||||
<view class="itemWrapper" v-for="(item, i) in secondMenuItemList" :key="i"
|
||||
@click="handleNav(item)">
|
||||
<view class="imgWrapper">
|
||||
@ -92,19 +92,19 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="custom-indicator" v-if="secondMenuItemList.length > 4">
|
||||
<view class="custom-indicator">
|
||||
<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 class="itemWrapper" @click="toDogTraining">
|
||||
<view>
|
||||
<view class="title">公益助力</view>
|
||||
<view class="tips">帮我找个家</view>
|
||||
<view class="title">上门服务</view>
|
||||
<view class="tips">训犬寄养、喂猫遛狗</view>
|
||||
</view>
|
||||
<image class="third-icon" :src="`${imgPrefix}home-publicBenefit.png`" mode="aspectFill" />
|
||||
<image class="third-icon1" :src="`${imgPrefix}h-door.png`" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="itemWrapper" @click="toJoin">
|
||||
<view>
|
||||
@ -188,6 +188,11 @@ export default {
|
||||
img: `${imgPrefix}home-openVip.png`,
|
||||
naviUrl: "/pages/richText/member-interests"
|
||||
},
|
||||
{
|
||||
title: "健康顾问",
|
||||
tips: "宠物健康智能助手",
|
||||
img: `${imgPrefix}healthConsultant.png`,
|
||||
},
|
||||
],
|
||||
walletInfo: {}, // 钱包信息
|
||||
secondMenuIndicatorIndex: 0, // 第二排菜单指示器当前页 0=第一页 1=第二页
|
||||
@ -245,9 +250,14 @@ export default {
|
||||
});
|
||||
},
|
||||
toDogTraining() {
|
||||
uni.navigateTo({
|
||||
url: '/pageHome/service/index'
|
||||
uni.showToast({
|
||||
title: '敬请期待',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
// uni.navigateTo({
|
||||
// url: '/pageHome/service/index'
|
||||
// });
|
||||
},
|
||||
// 我的钱包
|
||||
buyService() {
|
||||
@ -375,8 +385,13 @@ export default {
|
||||
}
|
||||
|
||||
.menu-img-sm {
|
||||
width: 120rpx;
|
||||
height: 164rpx;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
.third-icon1{
|
||||
width: 96rpx;
|
||||
height: 120rpx;
|
||||
|
||||
}
|
||||
|
||||
.second-icon,
|
||||
@ -601,13 +616,9 @@ export default {
|
||||
|
||||
.scrollWrapper {
|
||||
width: 100%;
|
||||
height: auto; /* 由内容撑开;小程序 scroll-view 横向滚动时默认可能不随内容计算高度 */
|
||||
min-height: 220rpx; /* 兜底:约等于 padding-top + 图标 + 标题 + 副标题 */
|
||||
display: flex;
|
||||
align-items: flex-start; /* 不拉伸子项,高度由内容决定 */
|
||||
white-space: nowrap;
|
||||
padding-top: 24rpx;
|
||||
padding-bottom: 24rpx;
|
||||
padding: 32rpx 0 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.secondMenuInner {
|
||||
@ -615,16 +626,6 @@ export default {
|
||||
flex-wrap: nowrap;
|
||||
flex-shrink: 0;
|
||||
width: 937.5rpx; /* 5 * 187.5 一屏4个共5项 */
|
||||
|
||||
&.no-scroll {
|
||||
width: 100%; /* 四项时一屏排满,不滚动 */
|
||||
|
||||
.itemWrapper {
|
||||
flex: 1;
|
||||
width: 0; /* 均分宽度,避免被 menuBody padding 裁切 */
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.itemWrapper {
|
||||
@ -653,7 +654,7 @@ export default {
|
||||
|
||||
.custom-indicator {
|
||||
display: flex;
|
||||
margin-top: 12rpx;
|
||||
margin-top:-150rpx;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
gap: 12rpx;
|
||||
|
||||
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
|
||||
} from "../../../api/notice";
|
||||
import NewsItem from "./components/NewsItem.vue";
|
||||
import ListPageTemp from "./components/ListPageTemp.vue";
|
||||
import { imgPrefix } from "@/utils/common";
|
||||
export default {
|
||||
components: {
|
||||
NewsItem,
|
||||
ListPageTemp
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
<script>
|
||||
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 { getShopOrderList } from '@/api/shop'
|
||||
|
||||
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>
|
||||
@ -93,8 +93,8 @@
|
||||
<view class="info-cell payment-method-cell">
|
||||
<view class="payment-item" @click.stop="selectOption1('1')">
|
||||
<view class="payment-left">
|
||||
<image class="payment-icon" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="payment-text">微信支付</text>
|
||||
<image class="payment-icon" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="payment-text">抖音支付</text>
|
||||
</view>
|
||||
<image class="payment-check"
|
||||
:src="selected2 ? require('@/static/images/cart_checked.png') : require('@/static/images/unchecked.png')"
|
||||
@ -573,15 +573,12 @@
|
||||
order_no:orderId.order_no
|
||||
})
|
||||
.then((res) => {
|
||||
const payData = res?.data || {};
|
||||
console.log(payData,'--=')
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
console.log(res,'--==')
|
||||
uni.hideLoading();
|
||||
|
||||
@ -227,8 +227,8 @@
|
||||
<view class="info-cell payment-method-cell" v-if="orderData.status === SHOP_ORDER_UNPAY">
|
||||
<view class="payment-item" @click.stop="selectOption1('1')">
|
||||
<view class="payment-left">
|
||||
<image class="payment-icon" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="payment-text">微信支付</text>
|
||||
<image class="payment-icon" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="payment-text">抖音支付</text>
|
||||
</view>
|
||||
<image class="payment-check"
|
||||
:src="selected2 ? require('@/static/images/cart_checked.png') : require('@/static/images/unchecked.png')"
|
||||
@ -619,14 +619,12 @@
|
||||
order_id:this.orderData.order_id,
|
||||
order_no:this.orderData.order_no
|
||||
}).then((res) => {
|
||||
const payData = res?.data || {};
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.paySign,
|
||||
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({
|
||||
|
||||
@ -61,8 +61,8 @@
|
||||
</view>
|
||||
<view class="wechat" @click.stop="selectOption1('1')">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<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" />
|
||||
@ -99,7 +99,7 @@
|
||||
|
||||
<script>
|
||||
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 PopUpModal from "@/components/PopUpModal.vue";
|
||||
import SuccessModal from "@/components/SuccessModal.vue";
|
||||
@ -340,14 +340,12 @@ export default {
|
||||
order_id: this.dataList.order_id,
|
||||
order_no: this.dataList.order_no
|
||||
}).then((res) => {
|
||||
const payData = res?.data || {};
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.paySign,
|
||||
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({
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
<view class="recharge-method">
|
||||
<view class="wechat">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<image v-if="selected1" class="not-selected" @click.stop="selectOption1('1')" src="@/static/images/w.png"
|
||||
mode="widthFix" />
|
||||
@ -212,14 +212,12 @@
|
||||
if (this.WeChat == 1) {
|
||||
payOrder(this.dataList.order_id, PRICE_DIFF_TYPE_SERVICE)
|
||||
.then((res) => {
|
||||
const payData = res?.info?.pay_data || {};
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.sign,
|
||||
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({
|
||||
|
||||
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="wechat" @click.stop="selectOption1">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<image v-if="selected1" class="not-selected"
|
||||
src="@/static/images/w.png" mode="widthFix" />
|
||||
@ -141,13 +141,12 @@
|
||||
walletWxpay(data).then((res) => {
|
||||
uni.hideLoading();
|
||||
// 使用获取的支付参数进行支付
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: res.data.timeStamp,
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (payRes) => {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
|
||||
@ -63,8 +63,8 @@
|
||||
<view class="recharge-method">
|
||||
<view class="wechat" @click.stop="selectOption1">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<image v-if="selected1" class="not-selected"
|
||||
src="@/static/images/w.png" mode="widthFix" />
|
||||
@ -188,12 +188,12 @@
|
||||
};
|
||||
walletWxpay(data).then((res) => {
|
||||
// 使用获取的支付参数进行支付
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
this.buyService(this.user_id);
|
||||
// console.log('支付成功:', res);
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
<script>
|
||||
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 { getOwnCouponData } from "@/api/coupon";
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
</view>
|
||||
<view v-if="objNew.third_party_sn " class="rech-row">
|
||||
<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>
|
||||
</view>
|
||||
<view class="rech-row">
|
||||
|
||||
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>
|
||||
import RemarkItem from "@/components/goods/RemarkItem.vue";
|
||||
import ListPageTemp from "@/components/ListPageTemp.vue";
|
||||
import ListPageTemp from "./ListPageTemp.vue";
|
||||
import { getRemarkList } from "@/api/shop";
|
||||
|
||||
export default {
|
||||
|
||||
@ -220,14 +220,13 @@ export default {
|
||||
// 支付
|
||||
pay(orderId) {
|
||||
serviceCouponOrderPay(orderId).then((res) => {
|
||||
const payData = res?.info?.pay_data || {};
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.sign,
|
||||
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
uni.hideLoading();
|
||||
this.showSuccessModal = true;
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
<text class="zf"> 请选择支付方式 </text>
|
||||
<view class="wechat">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="selected1"
|
||||
|
||||
@ -202,8 +202,8 @@
|
||||
<text class="zf"> 请选择支付方式 </text>
|
||||
<view class="wechat" @click.stop="selectOption1('1')">
|
||||
<view class="select">
|
||||
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
|
||||
<text class="x">微信</text>
|
||||
<image class="w" src="@/static/images/douy.png" mode="widthFix" />
|
||||
<text class="x">抖音</text>
|
||||
</view>
|
||||
<image v-if="selected1" class="not-selected" src="@/static/images/w.png"
|
||||
mode="widthFix" />
|
||||
@ -471,12 +471,12 @@ export default {
|
||||
};
|
||||
memberWXPAY(data).then((res) => {
|
||||
// 使用获取的支付参数进行支付
|
||||
uni.requestPayment({
|
||||
timeStamp: res.data.timeStamp, // 确保这些字段都正确
|
||||
nonceStr: res.data.nonceStr,
|
||||
package: res.data.package,
|
||||
signType: res.data.signType,
|
||||
paySign: res.data.paySign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
uni.showToast({
|
||||
title: "支付成功",
|
||||
|
||||
@ -275,14 +275,12 @@ export default {
|
||||
// 支付
|
||||
pay(orderId) {
|
||||
serviceCouponOrderPay(orderId).then((res) => {
|
||||
const payData = res?.info?.pay_data || {};
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.sign,
|
||||
tt.pay({
|
||||
orderInfo: {
|
||||
order_id:res.data.orderInfo.order_id,
|
||||
order_token:res.data.orderInfo.order_token,
|
||||
},
|
||||
service:5,
|
||||
success: (res) => {
|
||||
uni.hideLoading();
|
||||
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 |
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>
|
||||
Reference in New Issue
Block a user