Merge branch 'ymf' of https://newgitea.sxczgkj.cn/czg_team/cashier_wx into gyq
11
common/api/market/birthdayGift.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// 引入 request 文件
|
||||
import request from "@/common/api/request.js";
|
||||
import { prveUrl } from "./config.js";
|
||||
|
||||
export const config = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/birthdayGift",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
11
common/api/market/discountActivity.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// 引入 request 文件
|
||||
import request from "@/common/api/request.js";
|
||||
import { prveUrl } from "./config.js";
|
||||
|
||||
export const config = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/discountActivity",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
149
common/api/market/distribution.js
Normal file
@@ -0,0 +1,149 @@
|
||||
// 引入 request 文件
|
||||
import request from "@/common/api/request.js";
|
||||
import { prveUrl } from "./config.js";
|
||||
|
||||
export const pay = (data) => {
|
||||
let platformType = "";
|
||||
let payType = "";
|
||||
// #ifdef APP-PLUS
|
||||
platformType = "APP";
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
platformType = "H5";
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
platformType = "WX";
|
||||
platformType = "wechat";
|
||||
payType = "wechatPay";
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
platformType = "alipay";
|
||||
payType = "aliPay";
|
||||
// #endif
|
||||
|
||||
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/pay",
|
||||
method: "post",
|
||||
data: { platformType, payType, ...data },
|
||||
});
|
||||
};
|
||||
|
||||
export const centerUser = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/centerUser",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
export const activates = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/centerUser/activates",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
export const unActivates = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/centerUser/unActivates",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
export const centerConfig = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/centerConfig",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 绑定邀请用户
|
||||
export const bindInviteUser = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/bindInviteUser",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const childUser = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/childUser",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
export const inviteUser = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/inviteUser",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 提现
|
||||
export const withdraw = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/withdraw",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 提现详情
|
||||
export const withdrawDetail = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/withdraw/detail",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
//提现记录
|
||||
export const withdrawFlow = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/withdraw/flow",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 实名认证
|
||||
export const realNameAuth = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/realNameAuth",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 收益明细
|
||||
export const getIncomeDetails = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/distributionFlow",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 获取邀请码
|
||||
export const getInviteCode = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/getInviteCode",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 获取配置
|
||||
export const getConfig = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/distribution/getConfig",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
11
common/api/market/drainageConfig.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// 引入 request 文件
|
||||
import request from "@/common/api/request.js";
|
||||
import { prveUrl } from "./config.js";
|
||||
|
||||
export const config = (data) => {
|
||||
return request({
|
||||
url: prveUrl + "/user/drainageConfig",
|
||||
method: "get",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
18
common/api/market/exchange.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// 引入 request 文件
|
||||
import request from '@/common/api/request.js'
|
||||
import {prveUrl} from './config.js'
|
||||
|
||||
export const exchange = (data) => {
|
||||
return request({
|
||||
url: prveUrl + '/user/redemption/exchange',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
export const redemption = (data) => {
|
||||
return request({
|
||||
url: prveUrl + '/user/redemption',
|
||||
method: 'get',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
11
common/api/market/limitTimeDiscount.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// 引入 request 文件
|
||||
import request from '@/common/api/request.js'
|
||||
import {prveUrl} from './config.js'
|
||||
|
||||
export const getConfig = (data) => {
|
||||
return request({
|
||||
url: prveUrl + '/user/limitTimeDiscount',
|
||||
method: 'get',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
11
common/api/market/suggest.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// 引入 request 文件
|
||||
import request from '@/common/api/request.js'
|
||||
import {prveUrl} from './config.js'
|
||||
|
||||
export const getGoods = (data) => {
|
||||
return request({
|
||||
url: prveUrl + '/user/suggest',
|
||||
method: 'get',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
@@ -135,4 +135,18 @@ export const rechargePayOrder = (data) => {
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//分销员支付订单
|
||||
export const distributionLtPayOrder = (data) => {
|
||||
return request({
|
||||
url: url + '/pay/distribution/ltPayOrder',
|
||||
method: 'post',
|
||||
data: {
|
||||
platformType,
|
||||
payType,
|
||||
openId: uni.cache.get('userInfo').wechatOpenId,
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,120 +1,138 @@
|
||||
export default (params) => {
|
||||
let url = params.url;
|
||||
let method = params.method || "get";
|
||||
let data = params.data || {};
|
||||
let type = params.type || 1;
|
||||
let toast = params.toast || true;
|
||||
let token=uni.cache.get('token') || '';
|
||||
const shopId=uni.cache.get('shopId')*1;
|
||||
const userInfo=uni.cache.get('userInfo')||{};
|
||||
// #ifdef H5
|
||||
token="21f0a0b10e1d40ce9c6464037fedb792"
|
||||
// #endif
|
||||
let header = {
|
||||
version: uni.conf.version,
|
||||
type: uni.getSystemInfoSync().platform,
|
||||
// #ifdef APP-PLUS
|
||||
platformType: 'APP',
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
platformType: 'H5',
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
platformType: 'WX',
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
platformType: 'ALI',
|
||||
// #endif
|
||||
token,
|
||||
id: userInfo.id || '',
|
||||
shopId:shopId || '',
|
||||
userId: userInfo.id || '',
|
||||
}
|
||||
if (toast) {
|
||||
uni.showLoading({
|
||||
title: '加载中',
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutDuration = params.timeout || 10000; // 可以通过 params 传入超时时间,默认 10 秒
|
||||
uni.request({
|
||||
url: uni.conf.baseUrl + url,
|
||||
method: method,
|
||||
header: header,
|
||||
data: data,
|
||||
timeout: timeoutDuration,
|
||||
success(response) {
|
||||
const res = response.data
|
||||
// 根据返回的状态码做出对应的操作
|
||||
//获取成功
|
||||
if (res.code == 200) {
|
||||
uni.hideLoading();
|
||||
uni.hideToast();
|
||||
resolve(res.data ? res.data : true);
|
||||
} else {
|
||||
switch (res.code) {
|
||||
case '501':
|
||||
uni.cache.remove('shopId')
|
||||
// uni.showToast({
|
||||
// title: '',
|
||||
// icon: "none",
|
||||
// success: () => {
|
||||
let url = params.url;
|
||||
let method = params.method || "get";
|
||||
let data = params.data || {};
|
||||
let type = params.type || 1;
|
||||
let toast = params.toast || true;
|
||||
let token = uni.cache.get("token") || "";
|
||||
const shopId = uni.cache.get("shopId") * 1;
|
||||
const userInfo = uni.cache.get("userInfo") || {};
|
||||
// #ifdef H5
|
||||
token = "21f0a0b10e1d40ce9c6464037fedb792";
|
||||
// #endif
|
||||
let header = {
|
||||
version: uni.conf.version,
|
||||
type: uni.getSystemInfoSync().platform,
|
||||
// #ifdef APP-PLUS
|
||||
platformType: "APP",
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
platformType: "H5",
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
platformType: "WX",
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
platformType: "ALI",
|
||||
// #endif
|
||||
token,
|
||||
id: userInfo.id || "",
|
||||
shopId: shopId || "",
|
||||
userId: userInfo.id || "",
|
||||
};
|
||||
if (toast) {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
mask: true,
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutDuration = params.timeout || 10000; // 可以通过 params 传入超时时间,默认 10 秒
|
||||
uni.request({
|
||||
url: uni.conf.baseUrl + url,
|
||||
method: method,
|
||||
header: header,
|
||||
data: data,
|
||||
timeout: timeoutDuration,
|
||||
success(response) {
|
||||
const res = response.data;
|
||||
// 根据返回的状态码做出对应的操作
|
||||
//获取成功
|
||||
if (res.code == 200) {
|
||||
uni.hideLoading();
|
||||
uni.hideToast();
|
||||
resolve(res.data ? res.data : true);
|
||||
} else {
|
||||
switch (res.code) {
|
||||
case "501":
|
||||
uni.cache.remove("shopId");
|
||||
// uni.showToast({
|
||||
// title: '',
|
||||
// icon: "none",
|
||||
// success: () => {
|
||||
|
||||
// }
|
||||
// })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/index/index",
|
||||
})
|
||||
}, 1000);
|
||||
break;
|
||||
case 404:
|
||||
uni.showToast({
|
||||
title: '请求地址不存在...',
|
||||
duration: 2000,
|
||||
})
|
||||
break;
|
||||
default:
|
||||
// 是否提示
|
||||
if (toast) {
|
||||
uni.showToast({
|
||||
title: res.message || res.msg || res.error,
|
||||
icon: "none",
|
||||
success: () => {
|
||||
setTimeout(res => {
|
||||
reject(false);
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
if (err.errMsg.indexOf('request:fail') !== -1) {
|
||||
if (err.errMsg.indexOf('timeout') !== -1) {
|
||||
if (toast) {
|
||||
uni.showToast({
|
||||
title: `请求超时,请稍后重试`,
|
||||
icon: "error",
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
// 不管成功还是失败都会执行
|
||||
setTimeout(res => {
|
||||
uni.hideLoading();
|
||||
uni.hideToast();
|
||||
}, 10000)
|
||||
|
||||
}
|
||||
});
|
||||
}).catch((e) => {
|
||||
});
|
||||
};
|
||||
// }
|
||||
// })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/index/index",
|
||||
});
|
||||
}, 1000);
|
||||
break;
|
||||
case 404:
|
||||
uni.showToast({
|
||||
title: "请求地址不存在...",
|
||||
duration: 2000,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// 是否提示
|
||||
if (toast) {
|
||||
uni.showToast({
|
||||
title: (() => {
|
||||
// 1. 获取原始提示文本(兜底空字符串避免报错)
|
||||
const originMsg = res.message || res.msg || res.error || "";
|
||||
// 2. 定义要匹配的前缀
|
||||
const exceptionPrefix = "Exception:";
|
||||
// 3. 判断是否包含目标前缀
|
||||
if (originMsg.includes(exceptionPrefix)) {
|
||||
// 截取前缀后的内容 → 去除首尾空格 → 限制最大20个字符
|
||||
return originMsg
|
||||
.slice(
|
||||
originMsg.indexOf(exceptionPrefix) +
|
||||
exceptionPrefix.length
|
||||
)
|
||||
.trim()
|
||||
.slice(0, 20);
|
||||
} else {
|
||||
// 不包含则按原逻辑截取前20个字符
|
||||
return originMsg.slice(0, 20);
|
||||
}
|
||||
})(),
|
||||
icon: "none",
|
||||
success: () => {
|
||||
// 修复:去掉多余的 res 参数(避免覆盖外层 res)
|
||||
setTimeout(() => {
|
||||
reject(false);
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
if (err.errMsg.indexOf("request:fail") !== -1) {
|
||||
if (err.errMsg.indexOf("timeout") !== -1) {
|
||||
if (toast) {
|
||||
uni.showToast({
|
||||
title: `请求超时,请稍后重试`,
|
||||
icon: "error",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
// 不管成功还是失败都会执行
|
||||
setTimeout((res) => {
|
||||
uni.hideLoading();
|
||||
uni.hideToast();
|
||||
}, 10000);
|
||||
},
|
||||
});
|
||||
}).catch((e) => {});
|
||||
};
|
||||
|
||||
54
common/config.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// const debug = process.env.NODE_ENV == 'development' ? true : false;
|
||||
const debug = true;
|
||||
// #ifdef H5
|
||||
const proxyApi = "/api";
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN || APP || MP-ALIPAY
|
||||
const proxyApi = "http://192.168.1.42"; // 调试地址
|
||||
const proxyApiwws = "ws://192.168.1.42:2348"; // 调试地址
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
const baseUrl = debug ? proxyApi : "http://192.168.1.42";
|
||||
const baseUrlwws = "ws://192.168.1.42:2348";
|
||||
// #endif
|
||||
|
||||
// #ifdef APP || MP-WEIXIN || MP-ALIPAY
|
||||
const baseUrl = debug ? proxyApi : "https://cashier.sxczgkj.com"; // 线上
|
||||
const baseUrlwws = debug ? proxyApiwws : "wss://czgeatws.sxczgkj.com/wss"; // 线上
|
||||
// #endif
|
||||
|
||||
const version = "100";
|
||||
const autoRemoveCache = {
|
||||
count: 100000,
|
||||
size: 100000,
|
||||
};
|
||||
uni.conf = {
|
||||
debug,
|
||||
baseUrl,
|
||||
version,
|
||||
autoRemoveCache,
|
||||
baseUrlwws,
|
||||
};
|
||||
|
||||
export const changeEnv = (env) => {
|
||||
if (env === "test") {
|
||||
uni.conf = {
|
||||
debug: true,
|
||||
baseUrl: "http://192.168.1.42",
|
||||
version: 100,
|
||||
autoRemoveCache,
|
||||
baseUrlwws: "ws://192.168.1.42:2348",
|
||||
};
|
||||
}
|
||||
if (env === "prod") {
|
||||
uni.conf = {
|
||||
debug: false,
|
||||
baseUrl: "https://cashier.sxczgkj.com",
|
||||
version: 100,
|
||||
autoRemoveCache,
|
||||
baseUrlwws: "wss://czgeatws.sxczgkj.com/wss",
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -74,6 +74,9 @@ page,
|
||||
.font-700{
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-bold{
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-14{
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -94,4 +97,22 @@ page,
|
||||
}
|
||||
.u-col-center{
|
||||
align-items: center;
|
||||
}
|
||||
.justify-between{
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justify-center{
|
||||
justify-content: center;
|
||||
}
|
||||
.u-flex-col{
|
||||
flex-direction: column!important;
|
||||
}
|
||||
.min-h-100vh{
|
||||
min-height: 100vh;
|
||||
}
|
||||
.bg-gray{
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
.align-center{
|
||||
align-items: center;
|
||||
}
|
||||
545
components/birthday-modal.vue
Normal file
@@ -0,0 +1,545 @@
|
||||
<template>
|
||||
<view>
|
||||
<up-popup
|
||||
:show="show"
|
||||
bgColor="transparent"
|
||||
:safeAreaInsetBottom="false"
|
||||
@close="close"
|
||||
mode="center"
|
||||
>
|
||||
<view class="container">
|
||||
<view
|
||||
class="content"
|
||||
:style="contentStyle"
|
||||
:class="`content${currentPage}`"
|
||||
>
|
||||
<!-- 顶部标题图 -->
|
||||
<view class="top">
|
||||
<image class="image" :src="imageList.gift" mode="widthFix"></image>
|
||||
</view>
|
||||
|
||||
<!-- 优惠券列表 -->
|
||||
<view class="list">
|
||||
<view
|
||||
class="item"
|
||||
:style="kuangStyle"
|
||||
v-for="(item, index) in couponList"
|
||||
:key="index"
|
||||
>
|
||||
<!-- <view class="kuang">
|
||||
<image
|
||||
class="kuang-img"
|
||||
:src="imageList.kuang"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view> -->
|
||||
<!-- 优惠券金额 -->
|
||||
<view>
|
||||
<template
|
||||
v-if="
|
||||
item.couponInfo.couponType == 3 ||
|
||||
item.couponInfo.couponType == 1
|
||||
"
|
||||
>
|
||||
<text class="big-title">{{
|
||||
returnTitle(item.couponInfo)
|
||||
}}</text>
|
||||
<text class="u-m-l-12 small-title">
|
||||
{{ returnSmallTitle(item.couponInfo) }}
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="big-title" style="font-size: 48rpx">{{
|
||||
returnTitle(item.couponInfo)
|
||||
}}</text>
|
||||
</template>
|
||||
</view>
|
||||
<!-- 优惠券名称与领取按钮 -->
|
||||
<view class="u-flex u-row-between u-m-t-16">
|
||||
<view>
|
||||
<text class="title">{{ item.couponInfo.title }}</text>
|
||||
<text class="num">x{{ item.num }}</text>
|
||||
</view>
|
||||
<button class="lingqu" @click="handleReceive(item)">
|
||||
立即领取
|
||||
</button>
|
||||
</view>
|
||||
<!-- 有效期(替换硬编码,用接口返回数据) -->
|
||||
<view class="u-m-t-10 time">
|
||||
有效期至:{{ formatDate(item.couponInfo.validStartTime) }}-{{
|
||||
formatDate(item.couponInfo.validEndTime)
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页控制(左右箭头+页码) -->
|
||||
<view
|
||||
class="u-flex u-row-center u-m-t-16"
|
||||
v-if="allCoupons.length > 3"
|
||||
>
|
||||
<!-- 左箭头:当前页>1可点击 -->
|
||||
<view
|
||||
@click="handlePrevPage"
|
||||
class="page-arrow"
|
||||
:class="{ disabled: currentPage === 1 }"
|
||||
>
|
||||
<up-icon
|
||||
name="arrow-left"
|
||||
size="12"
|
||||
:color="currentPage === 1 ? '#999' : '#000'"
|
||||
></up-icon>
|
||||
</view>
|
||||
<!-- 页码列表:点击切换+当前页高亮 -->
|
||||
<view class="u-flex page-nums">
|
||||
<view
|
||||
v-for="(num, index) in pageNums"
|
||||
:key="index"
|
||||
class="item"
|
||||
:class="{ active: currentPage === num }"
|
||||
@click="handleClickPage(num)"
|
||||
>
|
||||
{{ num }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 右箭头:当前页<总页数可点击 -->
|
||||
<view
|
||||
@click="handleNextPage"
|
||||
class="page-arrow"
|
||||
:class="{ disabled: currentPage === totalPages }"
|
||||
>
|
||||
<up-icon
|
||||
name="arrow-right"
|
||||
size="12"
|
||||
:color="currentPage === totalPages ? '#999' : '#000'"
|
||||
></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部一键领取按钮 -->
|
||||
<view class="btn-wrap" :class="{ 'u-m-t-18': allCoupons.length > 3 }">
|
||||
<image
|
||||
@click="handleReceiveAll"
|
||||
class="btn-img"
|
||||
:src="imageList.btn"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import _ from "lodash";
|
||||
import dayjs from "dayjs";
|
||||
import { onMounted, ref, reactive, computed } from "vue";
|
||||
import * as birthdayGiftApi from "@/common/api/market/birthdayGift.js";
|
||||
|
||||
// 1. 分页核心配置
|
||||
const currentPage = ref(1); // 当前页码
|
||||
const pageSize = ref(3); // 每页显示条数
|
||||
const allCoupons = ref([]); // 存储全部优惠券(用于分页截取)
|
||||
const couponList = ref([]); // 当前页优惠券列表
|
||||
const pageNums = ref([]); // 页码数组(如[1,2,3])
|
||||
const totalPages = computed(() => {
|
||||
// 计算总页数:向上取整(如5条数据→2页)
|
||||
return Math.ceil(allCoupons.value.length / pageSize.value);
|
||||
});
|
||||
|
||||
// 2. 图片资源(修复bg2地址缺失的"g")
|
||||
const imageList = reactive({
|
||||
btn: "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/493b459f8c944057be72750c12c4cd1a.png",
|
||||
gift: "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/112d2433378349b4a26ab311b8d3bac4.png",
|
||||
kuang:
|
||||
"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/bb5e2d5ed73c455d9b6e9b4ac0e86192.png",
|
||||
bg1: "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/847f9aea44d64b15b35caf8967f3d63f.png",
|
||||
bg2: "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/2f704eef2e3d484d8862673131ae3989.png", // 修复:.pn→.png
|
||||
bg3: "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/7ab43d1ef2fc490d80165d18795a93f3.png",
|
||||
});
|
||||
|
||||
// 3. 背景图(随当前页切换)
|
||||
const bgUrl = ref(imageList.bg1);
|
||||
const contentStyle = computed(() => ({
|
||||
backgroundImage: `url(${bgUrl.value})`,
|
||||
backgroundSize: "100% 100%", // 确保背景图铺满容器
|
||||
}));
|
||||
const kuangStyle = computed(() => ({
|
||||
backgroundImage: `url(${imageList.kuang})`,
|
||||
}));
|
||||
|
||||
// 4. 弹窗状态与props
|
||||
const show = ref(false);
|
||||
const props = defineProps({
|
||||
getMode: {
|
||||
type: String,
|
||||
default: "eat",
|
||||
},
|
||||
});
|
||||
|
||||
// ---------------------- 分页核心逻辑 ----------------------
|
||||
/**
|
||||
* 1. 生成页码数组(如总数据5条→[1,2])
|
||||
*/
|
||||
const generatePageNums = () => {
|
||||
pageNums.value = Array.from({ length: totalPages.value }, (_, i) => i + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. 更新当前页优惠券列表(根据当前页截取数据)
|
||||
*/
|
||||
const updateCouponList = () => {
|
||||
const startIdx = (currentPage.value - 1) * pageSize.value;
|
||||
const endIdx = startIdx + pageSize.value;
|
||||
couponList.value = allCoupons.value.slice(startIdx, endIdx);
|
||||
// 同步切换背景图(当前页对应bg1/bg2/bg3)
|
||||
bgUrl.value = imageList[`bg${currentPage.value}`] || imageList.bg1;
|
||||
};
|
||||
|
||||
/**
|
||||
* 3. 上一页切换
|
||||
*/
|
||||
const handlePrevPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--;
|
||||
updateCouponList();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 4. 下一页切换
|
||||
*/
|
||||
const handleNextPage = () => {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++;
|
||||
updateCouponList();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 5. 点击页码切换
|
||||
*/
|
||||
const handleClickPage = (num) => {
|
||||
currentPage.value = num;
|
||||
updateCouponList();
|
||||
};
|
||||
|
||||
// ---------------------- 数据与交互逻辑 ----------------------
|
||||
/**
|
||||
* 1. 格式化日期(YYYY.MM.DD)
|
||||
*/
|
||||
const formatDate = (dateStr) => {
|
||||
return dateStr ? dayjs(dateStr).format("YYYY.MM.DD") : ""; // 默认兜底日期
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. 单个优惠券领取
|
||||
*/
|
||||
const handleReceive = async (item) => {
|
||||
close();
|
||||
uni.showToast({ title: "领取成功!去「我的优惠券」查看", icon: "none" });
|
||||
};
|
||||
|
||||
/**
|
||||
* 3. 底部一键领取(领取当前页所有优惠券)
|
||||
*/
|
||||
const handleReceiveAll = async () => {
|
||||
if (couponList.value.length === 0) {
|
||||
uni.showToast({ title: "当前页无优惠券可领", icon: "none" });
|
||||
return;
|
||||
}
|
||||
close();
|
||||
uni.showToast({ title: "领取成功!去「我的优惠券」查看", icon: "none" });
|
||||
};
|
||||
|
||||
/**
|
||||
* 4. 初始化获取优惠券数据
|
||||
*/
|
||||
const getCouponPopupAjax = async () => {
|
||||
try {
|
||||
const shopId = uni.cache.get("shopId");
|
||||
const res = await birthdayGiftApi.config({ shopId });
|
||||
if (res&&res.length) {
|
||||
// 处理有效期格式(固定有效期规则)
|
||||
allCoupons.value = res.map((item) => {
|
||||
if (item.validType === "fixed") {
|
||||
item.validStartTime = dayjs()
|
||||
.add(item.daysToTakeEffect, "day")
|
||||
.format("YYYY-MM-DD HH:mm:ss");
|
||||
item.validEndTime = dayjs()
|
||||
.add(+item.daysToTakeEffect + +item.validDays, "day")
|
||||
.format("YYYY-MM-DD HH:mm:ss");
|
||||
}
|
||||
return item;
|
||||
});
|
||||
// 初始化分页:生成页码→更新列表→显示弹窗
|
||||
generatePageNums();
|
||||
updateCouponList();
|
||||
show.value = true;
|
||||
}else{
|
||||
close();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取优惠券失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 页面挂载时初始化
|
||||
onMounted(() => {
|
||||
getCouponPopupAjax();
|
||||
});
|
||||
|
||||
function returnTitle(coupon) {
|
||||
if (coupon.couponType == 1) {
|
||||
return `¥${coupon.discountAmount}`;
|
||||
}
|
||||
if (coupon.couponType == 2) {
|
||||
return `商品兑换券`;
|
||||
}
|
||||
if (coupon.couponType == 3) {
|
||||
const discountRate = coupon.discountRate / 10;
|
||||
return `${discountRate}折券`;
|
||||
}
|
||||
if (coupon.couponType == 4) {
|
||||
return `第二件半价券`;
|
||||
}
|
||||
if (coupon.couponType == 6) {
|
||||
return `买一送一券`;
|
||||
}
|
||||
}
|
||||
|
||||
function returnSmallTitle(coupon) {
|
||||
if (coupon.couponType == 1) {
|
||||
return `满${coupon.fullAmount || 0}可用`;
|
||||
}
|
||||
if (coupon.couponType == 3) {
|
||||
return `满${coupon.fullAmount || 0}可用`;
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(["close"]);
|
||||
|
||||
function close() {
|
||||
show.value = false;
|
||||
emit("close");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 基础容器样式
|
||||
.container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 52rpx;
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
min-height: 836rpx;
|
||||
padding: 0 28rpx 62rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
// 分页箭头样式(添加禁用态)
|
||||
.page-arrow {
|
||||
cursor: pointer;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
box-sizing: border-box;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
font-size: 14px;
|
||||
line-height: 60rpx;
|
||||
padding: 0 14rpx;
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin: 0 16rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// 页码样式(当前页高亮)
|
||||
.page-nums {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
.item {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
box-sizing: border-box;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
font-size: 14px;
|
||||
line-height: 64rpx;
|
||||
padding: 0 4rpx;
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
text-align: center;
|
||||
&.active {
|
||||
border-color: #1890ff;
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
&:active:not(.active) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部按钮容器
|
||||
.btn-wrap {
|
||||
margin: 28rpx 0 0;
|
||||
margin-top: 30rpx;
|
||||
.btn-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 顶部标题图
|
||||
.top {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 优惠券列表样式
|
||||
.list {
|
||||
margin-top: 332rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
.item {
|
||||
position: relative;
|
||||
padding: 32rpx 12rpx 36rpx 36rpx;
|
||||
background-size: 100% 100%;
|
||||
background-position: center;
|
||||
overflow: hidden;
|
||||
margin-top: 36rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
// 优惠券边框图
|
||||
.kuang {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
.kuang-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 金额样式
|
||||
.big-title {
|
||||
color: #f05a82;
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.small-title {
|
||||
color: #f05a82;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// 名称与数量
|
||||
.title {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
padding-bottom: 10rpx;
|
||||
border-bottom: 4rpx dashed #fd8293;
|
||||
}
|
||||
.num {
|
||||
color: #f05a82;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
// 有效期
|
||||
.time {
|
||||
color: #666; // 调整颜色更柔和
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 领取按钮样式
|
||||
.lingqu {
|
||||
padding: 12rpx 20rpx; // 加宽内边距,点击区域更大
|
||||
border-radius: 14rpx;
|
||||
background: #fd8293;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
&:active {
|
||||
background: #f07080; // 点击深色反馈
|
||||
}
|
||||
}
|
||||
|
||||
// 工具类样式(补充缺失的flex基础样式)
|
||||
.u-flex {
|
||||
display: flex;
|
||||
}
|
||||
.u-row-center {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.u-row-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.u-m-t-10 {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
.u-m-t-16 {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.u-m-t-20 {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.u-m-t-30 {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.u-m-x-16 {
|
||||
margin-left: 16rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
.u-m-l-12 {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- 首页优惠券弹窗 -->
|
||||
<template>
|
||||
<up-popup :show="show" bgColor="transparent">
|
||||
<up-popup :show="show" bgColor="transparent" @close="close">
|
||||
<view class="container">
|
||||
<view class="content" :class="`content${currentNum}`">
|
||||
<image class="bg" :src="bgUrl" mode="widthFix"></image>
|
||||
@@ -141,6 +141,8 @@ async function getCouponPopupAjax() {
|
||||
bgUrl.value = bgUrlList.value[1];
|
||||
currentNum.value = 1;
|
||||
}
|
||||
}else{
|
||||
close()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -157,7 +159,7 @@ async function getHandle() {
|
||||
const res = await receivePopUp({
|
||||
getMode: props.getMode
|
||||
});
|
||||
show.value = false;
|
||||
close()
|
||||
uni.showToast({
|
||||
title: '已领取,请在我的优惠券中查看',
|
||||
icon: 'none'
|
||||
@@ -167,6 +169,12 @@ async function getHandle() {
|
||||
}
|
||||
uni.hideLoading();
|
||||
}
|
||||
const emit=defineEmits(['close'])
|
||||
|
||||
function close() {
|
||||
show.value = false;
|
||||
emit('close');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCouponPopupAjax();
|
||||
|
||||
52
components/date-range-picker/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
#时间范围选择器
|
||||
#### 参数文档
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 其他 |
|
||||
| :---- | :---- | :---- | :---- | :---- |
|
||||
| show | 显示选择器 | Boolean | false | - |
|
||||
| defaultDate | 默认日期 | String | - | 不传则默认今天 |
|
||||
| minYear | 最小年份 | Number | 1990 | - |
|
||||
| themeColor | 主题色 | String | #43b983 | - |
|
||||
| startText | 开始时间文字 | String | 开始时间 | - |
|
||||
| endText | 结束时间文字 | String | 结束时间 | - |
|
||||
|
||||
#### case
|
||||
```vue
|
||||
<template>
|
||||
<view style="padding: 30rpx;">
|
||||
<view style="margin-top: 30rpx" @click="show=true">
|
||||
显示日期选择器
|
||||
</view>
|
||||
<view style="margin-top: 30rpx">
|
||||
所选日期 {{ date.join(',') }}
|
||||
</view>
|
||||
<dateRangePicker
|
||||
:show="show"
|
||||
:minYear="2022"
|
||||
@close="show=false"
|
||||
@confirm="confirm"
|
||||
>
|
||||
</dateRangePicker>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dateRangePicker from '@/components/date-range-picker/date-range-picker.vue'
|
||||
export default {
|
||||
components: {dateRangePicker},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
date: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirm(v) {
|
||||
console.log(v);
|
||||
this.date = v
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```
|
||||
342
components/date-range-picker/date-range-picker.vue
Normal file
@@ -0,0 +1,342 @@
|
||||
<template>
|
||||
<view :class="{'remark':show}" :style="{'--theme-color': themeColor}" @click="close" @touchmove.stop.prevent="returnHandle">
|
||||
<view class="picker-box" :class="{show: show}">
|
||||
<view class="operate-box" @touchmove.stop.prevent="returnHandle" @tap.stop="returnHandle">
|
||||
<view @click="touchSelect(0)" class="time-item" :style="{color:touchIndex?'#303030':themeColor}">
|
||||
<view class="label">{{ startText }}</view>
|
||||
<view class="date">{{ resultDate[0] }}</view>
|
||||
</view>
|
||||
<view>至</view>
|
||||
<view @click="touchSelect(1)" class="time-item" :style="{color:touchIndex?themeColor:'#303030'}">
|
||||
<view class="label">{{ endText }}</view>
|
||||
<view class="date">{{ resultDate[1] }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<picker-view
|
||||
:value="pickerValue"
|
||||
@change="pickerChange"
|
||||
class="picker-view"
|
||||
:immediate-change="true"
|
||||
indicator-class="select-line"
|
||||
:indicator-style="indicatorStyle"
|
||||
mask-style="background: transparent"
|
||||
@tap.stop="returnHandle"
|
||||
>
|
||||
<picker-view-column class="column-left">
|
||||
<view class="picker-item" :class="index == pickerValue[0] ? 'picker-select' : ''" v-for="(item, index) in years" :key="index">
|
||||
{{ item }}年
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column class="column-center">
|
||||
<view class="picker-item" :class="index == pickerValue[1] ? 'picker-select' : ''" v-for="(item, index) in months" :key="index">
|
||||
{{ item }}月
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column class="column-right" v-if="days.length > 0">
|
||||
<view class="picker-item" :class="index == pickerValue[2] ? 'picker-select' : ''" v-for="(item, index) in days" :key="index">
|
||||
{{ item }}日
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<view class="button-group">
|
||||
<view class="item cancel" @click.stop="close">取消</view>
|
||||
<view class="item confirm" @click.stop="pickerConfirm">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
const date = new Date();
|
||||
const years = [];
|
||||
const currentYear = date.getFullYear();
|
||||
const months = [];
|
||||
const currentMonth = date.getMonth() + 1;
|
||||
const currentDay = date.getDate();
|
||||
|
||||
export default {
|
||||
name: 'dateRangePicker',
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
defaultDate: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
minYear: {
|
||||
type: Number,
|
||||
default: 1990,
|
||||
},
|
||||
themeColor: {
|
||||
type: String,
|
||||
default: '#43b983'
|
||||
},
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始时间'
|
||||
},
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束时间'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
for (let i = this.minYear; i <= currentYear; i++) {
|
||||
years.push(i);
|
||||
}
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
months.push(this.padStart(i));
|
||||
}
|
||||
return {
|
||||
indicatorStyle: `height: ${uni.upx2px(84)}px`,
|
||||
touchIndex: 0,
|
||||
year: currentYear,
|
||||
month: currentMonth,
|
||||
day: currentDay,
|
||||
years,
|
||||
months,
|
||||
days: [],
|
||||
pickerValue: [],
|
||||
resultDate: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.setDate()
|
||||
},
|
||||
methods: {
|
||||
returnHandle() {},
|
||||
setDate() {
|
||||
if (this.defaultDate.length > 0) {
|
||||
const date = this.defaultDate[0]
|
||||
this.resultDate = this.defaultDate
|
||||
this.setPicker(date)
|
||||
} else {
|
||||
const month = this.month.toString().padStart(2, 0)
|
||||
const day = this.day.toString().padStart(2, 0)
|
||||
const nowTime = `${this.year}-${month}-${day}`
|
||||
this.resultDate = [nowTime, nowTime]
|
||||
this.setPicker(nowTime)
|
||||
}
|
||||
},
|
||||
setPicker(date) {
|
||||
const splitVal = date.split('-')
|
||||
const year = this.years.indexOf(Number(splitVal[0]))
|
||||
const month = Number(splitVal[1]) - 1
|
||||
const day = Number(splitVal[2]) - 1
|
||||
this.pickerChange({
|
||||
detail: {
|
||||
value: [year, month, day]
|
||||
}
|
||||
})
|
||||
},
|
||||
touchSelect(val) {
|
||||
const date = this.resultDate[val]
|
||||
this.touchIndex = val
|
||||
this.setPicker(date)
|
||||
},
|
||||
getDateTime(date) {
|
||||
const year = this.years[date[0]]
|
||||
const month = this.months[Number(date[1])] || this.padStart(currentMonth)
|
||||
const day = this.days[Number(date[2])] || this.padStart(currentDay)
|
||||
|
||||
this.resultDate[this.touchIndex] = `${year}-${month}-${day}`
|
||||
},
|
||||
pickerChange(e) {
|
||||
const currents = e.detail.value
|
||||
|
||||
// 月份处理,限制到当前月份
|
||||
if (this.years[currents[0]] === currentYear) {
|
||||
const allmonths = JSON.parse(JSON.stringify(months))
|
||||
const m = allmonths.splice(0, currentMonth)
|
||||
this.months = m
|
||||
if(currents[1] > currentMonth - 1) {
|
||||
currents[1] = currentMonth - 1
|
||||
}
|
||||
} else {
|
||||
this.months = months
|
||||
}
|
||||
|
||||
// 日期天数处理
|
||||
let days = []
|
||||
if (currents[1] + 1 === 2) {
|
||||
if (
|
||||
((currents[0] + this.minYear) % 4 === 0 &&
|
||||
(currents[0] + this.minYear) % 100 !== 0) ||
|
||||
(currents[0] + this.minYear) % 400 === 0
|
||||
) {
|
||||
for (let i = 1; i < 30; i++) {
|
||||
days.push(this.padStart(i))
|
||||
}
|
||||
} else {
|
||||
for (let i = 1; i < 29; i++) {
|
||||
days.push(this.padStart(i))
|
||||
}
|
||||
}
|
||||
} else if ([4, 6, 9, 11].some((item) => currents[1] + 1 === item)) {
|
||||
for (let i = 1; i < 31; i++) {
|
||||
days.push(this.padStart(i))
|
||||
}
|
||||
} else if ([1, 3, 5, 7, 8, 10, 12].some((item) => currents[1] + 1 === item)) {
|
||||
for (let i = 1; i < 32; i++) {
|
||||
days.push(this.padStart(i))
|
||||
}
|
||||
}
|
||||
// 限制到当前日期
|
||||
if (this.years[currents[0]] === currentYear && this.months[currents[1]]*1 === currentMonth) {
|
||||
days = days.splice(0, currentDay)
|
||||
if(currents[2] > currentDay - 1) {
|
||||
currents[2] = currentDay - 1
|
||||
}
|
||||
}
|
||||
this.days = days
|
||||
this.pickerValue = currents
|
||||
this.getDateTime(currents)
|
||||
},
|
||||
close() {
|
||||
this.$emit('close', false)
|
||||
},
|
||||
pickerConfirm() {
|
||||
const { resultDate } = this
|
||||
let startTime = new Date(resultDate[0]).getTime()
|
||||
let endTime = new Date(resultDate[1]).getTime()
|
||||
let nowTime = endTime
|
||||
if (startTime <= endTime && endTime <= nowTime) {
|
||||
this.$emit('confirm', resultDate)
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
if (startTime > endTime) {
|
||||
uni.showToast({
|
||||
title: '开始时间应小于结束时间',
|
||||
icon: 'none',
|
||||
duration: 3500
|
||||
})
|
||||
}
|
||||
if (endTime > nowTime) {
|
||||
uni.showToast({
|
||||
title: '请正确选择时间范围',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
padStart(val) {
|
||||
return val.toString().padStart(2, 0)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep.column-left,
|
||||
::v-deep.column-center,
|
||||
::v-deep.column-right {
|
||||
.select-line {
|
||||
background: #F9FAFC;
|
||||
z-index: -1;
|
||||
&::before, &::after {
|
||||
border: none ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.column-left .select-line {
|
||||
border-radius: 42rpx 0 0 42rpx;
|
||||
}
|
||||
|
||||
::v-deep.column-right .select-line {
|
||||
border-radius: 0 42rpx 42rpx 0;
|
||||
}
|
||||
|
||||
.remark {
|
||||
position: fixed;
|
||||
z-index: 998;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.picker-box {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateY(100%);
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
z-index: 998;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: calc(40rpx + constant(safe-area-inset-bottom)/2) !important;
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom)/2) !important;
|
||||
&.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.operate-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 34rpx 30rpx 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
text-align: center;
|
||||
border-bottom: 2rpx solid #f6f6f6;
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.date {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
width: 100%;
|
||||
height: 420rpx;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
.picker-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
height: 84rpx;
|
||||
line-height: 84rpx;
|
||||
font-size: 32rpx;
|
||||
color: rgba(94, 104, 128, 0.6);
|
||||
&.picker-select {
|
||||
color: var(--theme-color);
|
||||
font-size: 38rpx;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 30rpx;
|
||||
.item {
|
||||
width: 280rpx;
|
||||
height: 84rpx;
|
||||
text-align: center;
|
||||
line-height: 84rpx;
|
||||
border-radius: 42rpx;
|
||||
&.cancel {
|
||||
background: #f8f8f8;
|
||||
color: #333;
|
||||
}
|
||||
&.confirm {
|
||||
background: var(--theme-color);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
382
components/devetools.vue
Normal file
@@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<view class="floating-widget" v-if="show">
|
||||
<!-- 悬浮按钮 -->
|
||||
<view
|
||||
class="floating-btn"
|
||||
@click="togglePopup"
|
||||
:style="{
|
||||
backgroundColor: btnColor,
|
||||
width: `${btnSize}px`,
|
||||
height: `${btnSize}px`,
|
||||
transform: `translateX(${currentOffsetX}px) translateY(${currentOffsetY}px)`,
|
||||
}"
|
||||
:class="{ active: isPopupVisible }"
|
||||
@touchstart="touchstart"
|
||||
@touchmove="touchmove"
|
||||
@touchend="touchend"
|
||||
>
|
||||
<up-icon
|
||||
name="plus"
|
||||
size="24"
|
||||
color="#ffffff"
|
||||
:class="{ rotate: isPopupVisible }"
|
||||
></up-icon>
|
||||
</view>
|
||||
|
||||
<!-- 操作弹窗 -->
|
||||
<view
|
||||
class="popup"
|
||||
v-if="isPopupVisible"
|
||||
:class="{ show: isPopupVisible }"
|
||||
:style="{
|
||||
transform: `translateX(${currentOffsetX}px) translateY(${currentOffsetY}px)`,
|
||||
}"
|
||||
>
|
||||
<view class="popup-arrow"></view>
|
||||
<view class="popup-content">
|
||||
<view
|
||||
class="popup-item"
|
||||
>
|
||||
<text class="item-text">
|
||||
<text> 当前环境:</text>
|
||||
<text style="color: rgb(255, 0, 0);"> {{ uni.conf.debug ? "测试环境" : "生产环境" }}</text>
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="popup-item"
|
||||
v-for="(item, index) in operations"
|
||||
:key="index"
|
||||
@click="handleOperation(item.action)"
|
||||
>
|
||||
<text class="item-text">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 点击外部关闭遮罩 -->
|
||||
<view class="overlay" v-if="isPopupVisible" @click="closePopup"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, defineEmits, computed } from "vue";
|
||||
import { changeEnv } from "@/common/config.js";
|
||||
import { Storelogin } from "@/stores/user.js";
|
||||
const show = computed(() => {
|
||||
// trial
|
||||
const sysInfo = uni.getAccountInfoSync();
|
||||
if (
|
||||
sysInfo &&
|
||||
sysInfo.miniProgram &&
|
||||
sysInfo.miniProgram.envVersion == "develop"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// 核心修改1:新增“历史累积偏移量”变量(保存上一次拖拽后的最终位置)
|
||||
const lastOffsetX = ref(0); // 历史X偏移累积值
|
||||
const lastOffsetY = ref(0); // 历史Y偏移累积值
|
||||
const currentOffsetX = ref(0); // 当前拖拽的实时偏移(基于历史值)
|
||||
const currentOffsetY = ref(0); // 当前拖拽的实时偏移(基于历史值)
|
||||
|
||||
const startX = ref(0);
|
||||
const startY = ref(0);
|
||||
|
||||
// 核心修改2:touchstart - 基于历史偏移量初始化当前拖拽起点
|
||||
function touchstart(e) {
|
||||
const touch = e.touches[0];
|
||||
startX.value = touch.clientX;
|
||||
startY.value = touch.clientY;
|
||||
// 关键:当前拖拽的起点 = 上一次拖拽的终点(历史累积偏移量)
|
||||
currentOffsetX.value = lastOffsetX.value;
|
||||
currentOffsetY.value = lastOffsetY.value;
|
||||
}
|
||||
|
||||
// 核心修改3:touchmove - 实时计算“历史偏移量 + 当前拖拽距离”
|
||||
function touchmove(e) {
|
||||
const touch = e.touches[0];
|
||||
// 当前拖拽的相对距离 = 现在触摸点 - 拖拽起点
|
||||
const moveX = touch.clientX - startX.value;
|
||||
const moveY = touch.clientY - startY.value;
|
||||
// 实时偏移 = 历史累积偏移 + 当前相对移动距离(位置连续)
|
||||
currentOffsetX.value = lastOffsetX.value + moveX;
|
||||
currentOffsetY.value = lastOffsetY.value + moveY;
|
||||
}
|
||||
|
||||
// 核心修改4:touchend - 保存当前拖拽终点为历史偏移量(供下次使用)
|
||||
function touchend() {
|
||||
// 关键:将本次拖拽的最终位置保存为历史值
|
||||
lastOffsetX.value = currentOffsetX.value;
|
||||
lastOffsetY.value = currentOffsetY.value;
|
||||
}
|
||||
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
// 操作选项列表
|
||||
operations: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{
|
||||
name: "切换为测试环境",
|
||||
icon: "arrowup",
|
||||
action: "test",
|
||||
color: "#007aff",
|
||||
},
|
||||
{
|
||||
name: "切换为正式环境",
|
||||
icon: "arrowup",
|
||||
action: "prod",
|
||||
color: "#007aff",
|
||||
},
|
||||
{
|
||||
name: "复制token",
|
||||
icon: "arrowup",
|
||||
action: "token",
|
||||
color: "#007aff",
|
||||
},
|
||||
{
|
||||
name: "复制用户信息",
|
||||
icon: "userInfo",
|
||||
action: "userInfo",
|
||||
color: "#52c41a",
|
||||
},
|
||||
{
|
||||
name: "获取登录code",
|
||||
icon: "userInfo",
|
||||
action: "getLoginCode",
|
||||
color: "#52c41a",
|
||||
},
|
||||
{
|
||||
name: "复制当前门店信息",
|
||||
icon: "userInfo",
|
||||
action: "copyStoreInfo",
|
||||
color: "#52c41a",
|
||||
},
|
||||
{
|
||||
name: "复制当前门店用户信息",
|
||||
icon: "userInfo",
|
||||
action: "copyStoreUserInfo",
|
||||
color: "#52c41a",
|
||||
},
|
||||
],
|
||||
},
|
||||
// 悬浮按钮颜色
|
||||
btnColor: {
|
||||
type: String,
|
||||
default: "#007aff",
|
||||
},
|
||||
// 悬浮按钮大小(px)
|
||||
btnSize: {
|
||||
type: Number,
|
||||
default: 60,
|
||||
},
|
||||
// 弹窗距离底部的距离
|
||||
bottomDistance: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
// 弹窗距离右侧的距离
|
||||
rightDistance: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
});
|
||||
|
||||
// 定义组件事件
|
||||
const emit = defineEmits(["onOperation", "onOpen", "onClose"]);
|
||||
|
||||
// 弹窗显示状态
|
||||
const isPopupVisible = ref(false);
|
||||
|
||||
// 切换弹窗显示/隐藏
|
||||
const togglePopup = () => {
|
||||
isPopupVisible.value = !isPopupVisible.value;
|
||||
if (isPopupVisible.value) {
|
||||
emit("onOpen");
|
||||
} else {
|
||||
emit("onClose");
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const closePopup = () => {
|
||||
if (isPopupVisible.value) {
|
||||
isPopupVisible.value = false;
|
||||
emit("onClose");
|
||||
}
|
||||
};
|
||||
|
||||
async function getWxloginCode() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
success: (res) => {
|
||||
if (res.code) {
|
||||
resolve(res.code);
|
||||
} else {
|
||||
console.log("获取登录凭证(code)失败!" + res.errMsg);
|
||||
reject(res.errMsg);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
const storelogin = Storelogin();
|
||||
|
||||
// 处理操作选择
|
||||
const handleOperation = async (action) => {
|
||||
let data = "";
|
||||
|
||||
if (action == "prod" || action == "test") {
|
||||
changeEnv(action);
|
||||
emit("onOperation", action);
|
||||
uni.showToast({
|
||||
title: "切换成功",
|
||||
icon: "success",
|
||||
});
|
||||
uni.clearStorageSync();
|
||||
|
||||
await storelogin.actionslogin();
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/index/index",
|
||||
});
|
||||
}, 1500);
|
||||
|
||||
closePopup();
|
||||
return;
|
||||
}
|
||||
if (action == "token") {
|
||||
data = uni.cache.get("token");
|
||||
}
|
||||
if (action == "userInfo") {
|
||||
data = JSON.stringify(uni.cache.get("userInfo"));
|
||||
}
|
||||
if (action == "getLoginCode") {
|
||||
data = await getWxloginCode();
|
||||
}
|
||||
if (action == "copyStoreInfo") {
|
||||
data = JSON.stringify(uni.cache.get("shopInfo"));
|
||||
}
|
||||
if (action == "copyStoreUserInfo") {
|
||||
data = JSON.stringify(uni.cache.get("shopUserInfo"));
|
||||
}
|
||||
console.log("data", data);
|
||||
if (data) {
|
||||
uni.setClipboardData({
|
||||
data: data,
|
||||
success: function () {},
|
||||
});
|
||||
}
|
||||
|
||||
emit("onOperation", action);
|
||||
closePopup();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.floating-widget {
|
||||
position: fixed;
|
||||
right: v-bind(rightDistance + "px");
|
||||
bottom: v-bind(bottomDistance + "px");
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* 悬浮按钮样式 */
|
||||
.floating-btn {
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(98deg, #fe6d1100 40.64%, #ffd1b4 105.2%),
|
||||
linear-gradient(259deg, #fe6d11 50.14%, #ffd1b4 114.93%);
|
||||
box-shadow: 0 0.4375rem 0.95rem 0 #fe8b435e;
|
||||
cursor: pointer;
|
||||
/*transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); */
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.floating-btn.active {
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.floating-btn .rotate {
|
||||
transform: rotate(45deg);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.popup {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 15px);
|
||||
right: 0;
|
||||
min-width: 180px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
opacity: 0;
|
||||
transform: translateY(10px) scale(0.95);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.popup.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* 弹窗箭头 */
|
||||
.popup-arrow {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: #ffffff;
|
||||
transform: rotate(45deg);
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 8px 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 操作选项样式 */
|
||||
.popup-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.popup-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 遮罩层样式 */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
112
components/drainage.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 私域引流 -->
|
||||
<up-popup
|
||||
:show="showPreview"
|
||||
mode="center"
|
||||
round="16rpx"
|
||||
closeOnClickOverlay
|
||||
@close="close"
|
||||
:safeAreaInsetBottom="false"
|
||||
>
|
||||
<view class="preview-box">
|
||||
<view class="u-flex" style="align-items: stretch">
|
||||
<view
|
||||
class="u-flex-1 u-p-r-24 u-flex u-flex-col"
|
||||
style="align-items: start; justify-content: space-between"
|
||||
>
|
||||
<view>
|
||||
<view class="font-14 font-bold color-333">{{
|
||||
drainageConfig.title
|
||||
}}</view>
|
||||
<view class="u-m-t-16 font-12 color-666">{{
|
||||
drainageConfig.content
|
||||
}}</view>
|
||||
</view>
|
||||
|
||||
<view class="color-999 font-12 u-m-t-16">{{
|
||||
drainageConfig.note
|
||||
}}</view>
|
||||
</view>
|
||||
|
||||
<image
|
||||
:show-menu-by-longpress="true"
|
||||
:src="drainageConfig.qrCode"
|
||||
style="width: 240rpx; height: 240rpx"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
|
||||
<view class="close" @click="close">
|
||||
<up-icon name="close-circle" size="34" color="#fff"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as drainageConfigApi from "@/common/api/market/drainageConfig.js";
|
||||
import { ref, reactive, computed, watch, onMounted } from "vue";
|
||||
|
||||
const showPreview = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const emit = defineEmits(["close"]);
|
||||
|
||||
function close() {
|
||||
showPreview.value = false;
|
||||
emit("close");
|
||||
}
|
||||
const drainageConfig = ref({});
|
||||
async function getDrainageConfig() {
|
||||
const shopId = uni.cache.get("shopId");
|
||||
const drainageConfigRes = await drainageConfigApi.config({
|
||||
shopId: shopId,
|
||||
});
|
||||
drainageConfig.value = drainageConfigRes;
|
||||
if (drainageConfig.value.isEnable) {
|
||||
showPreview.value = true;
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
// onMounted(() => {
|
||||
// getDrainageConfig();
|
||||
// });
|
||||
watch(
|
||||
() => showPreview.value,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
getDrainageConfig();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.u-flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.preview {
|
||||
padding: 8rpx 32rpx;
|
||||
border-radius: 12rpx;
|
||||
background: $my-main-color;
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
.preview-box {
|
||||
width: 700rpx;
|
||||
padding: 32rpx 28rpx;
|
||||
position: relative;
|
||||
.close {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -100rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
components/goods-price.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<block v-if="limitDiscount && limitDiscount.id" class="limit-price">
|
||||
<text>
|
||||
{{ returnPrice() }}
|
||||
</text>
|
||||
</block>
|
||||
|
||||
<text v-else>
|
||||
<block
|
||||
v-if="
|
||||
shopUserInfo.isMemberPrice == 1 &&
|
||||
shopUserInfo.isVip == 1 &&
|
||||
cart.memberPrice * 1 > 0
|
||||
"
|
||||
class="memberPrice"
|
||||
>
|
||||
<text>
|
||||
{{ cart.memberPrice }}
|
||||
</text>
|
||||
</block>
|
||||
|
||||
<text v-else class="salePrice">{{ cart.salePrice }}</text>
|
||||
</text>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import BigNumber from "bignumber.js";
|
||||
import * as orderUtils from "@/utils/order-utils.js";
|
||||
|
||||
function returnPrice() {
|
||||
return orderUtils.returnPrice({
|
||||
goods: props.cart,
|
||||
shopInfo: props.shopInfo,
|
||||
limitTimeDiscountRes: props.limitDiscount,
|
||||
shopUserInfo: props.shopUserInfo,
|
||||
idKey: props.idKey,
|
||||
});
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
//购物车
|
||||
cart: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
idKey: {
|
||||
type: String,
|
||||
default: "id",
|
||||
},
|
||||
//限时折扣
|
||||
limitDiscount: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
//店铺用户信息
|
||||
shopUserInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
//店铺信息
|
||||
shopInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.old-price {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
text-decoration: line-through;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
57
components/modal-list.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 生成公众号二维码 -->
|
||||
<we-qrcode @generate="(e) => qrcodeResult(e)"></we-qrcode>
|
||||
<officialAccount
|
||||
followIndex="eat"
|
||||
:wechatAcQrcode="wechatAcQrcode"
|
||||
v-if="showOfficialAccount"
|
||||
@close="modelClose($event, 'officialAccount')"
|
||||
/>
|
||||
<couponModal
|
||||
v-if="showCoupon"
|
||||
getMode="eat"
|
||||
@close="modelClose($event, 'coupon')"
|
||||
/>
|
||||
<birthdayGift
|
||||
v-if="showBirthdayGift"
|
||||
@close="modelClose($event, 'birthdayGift')"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import weQrcode from "@/components/wechat-ac-qrcode.vue";
|
||||
|
||||
import { ref, watch, computed, reactive, toRaw } from "vue";
|
||||
import couponModal from "@/components/coupon-modal.vue";
|
||||
import birthdayGift from "@/components/birthday-modal.vue";
|
||||
import officialAccount from "@/components/official-account.vue";
|
||||
//弹窗列表
|
||||
const list = ref([]);
|
||||
|
||||
const showBirthdayGift = ref(true);
|
||||
const showCoupon = ref(false);
|
||||
const showOfficialAccount = ref(false);
|
||||
|
||||
function modelClose(e, type) {
|
||||
console.log("modelClose", type);
|
||||
if (type == "birthdayGift") {
|
||||
showCoupon.value = true;
|
||||
return;
|
||||
}
|
||||
if (type == "coupon") {
|
||||
showOfficialAccount.value = true;
|
||||
return;
|
||||
}
|
||||
if (type == "officialAccount") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const wechatAcQrcode = ref("");
|
||||
const userinfo = uni.cache.get("userInfo") || {};
|
||||
const codeVal = ref(userinfo.wechatAcQrcode || "");
|
||||
function qrcodeResult(e) {
|
||||
wechatAcQrcode.value = e;
|
||||
}
|
||||
</script>
|
||||
|
||||
133
components/official-account.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<view>
|
||||
<up-popup :show="show" bgColor="transparent" mode="center" @close="close">
|
||||
<view class="container">
|
||||
<view class="content">
|
||||
<image class="bg" :src="bgUrl" mode="widthFix"></image>
|
||||
<view class="info">
|
||||
<view class="u-flex u-row-center">
|
||||
<image
|
||||
:show-menu-by-longpress="true"
|
||||
:src="wechatAcQrcode"
|
||||
style="height: 240rpx"
|
||||
mode="heightFix"
|
||||
></image>
|
||||
</view>
|
||||
<view
|
||||
class="color-999 font-12 text-center u-m-t-10"
|
||||
style="line-height: 36rpx"
|
||||
>长按识别关注,更多优惠不能错过</view
|
||||
>
|
||||
</view>
|
||||
<view class="close" @click="close">
|
||||
<up-icon name="close-circle" size="34" color="#fff"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import _ from "lodash";
|
||||
import dayjs from "dayjs";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { string } from "../uni_modules/uview-plus/libs/function/test";
|
||||
|
||||
const bgUrl = ref(
|
||||
"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/41239c8852874aa39d1f106e45456e10.png"
|
||||
);
|
||||
|
||||
const show = ref(false);
|
||||
const code = ref(
|
||||
"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240408/4d6e818a01f145a898d8c2368f4b5ad1.jpg"
|
||||
);
|
||||
const props = defineProps({
|
||||
followIndex: {
|
||||
type: string,
|
||||
default: "", // 公众号关注位置 mine-我的 order-订单 eat-就餐
|
||||
},
|
||||
wechatAcQrcode: {
|
||||
type: string,
|
||||
default: "", // 微信公众号二维码
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["close"]);
|
||||
|
||||
function close() {
|
||||
show.value = false;
|
||||
emit("close");
|
||||
}
|
||||
function init() {
|
||||
const followIndex = uni.cache.get("followIndex");
|
||||
if (props.wechatAcQrcode && props.followIndex == followIndex) {
|
||||
show.value = true;
|
||||
}
|
||||
}
|
||||
watch(() => props.wechatAcQrcode, init);
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.u-row-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 54rpx;
|
||||
.content {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.close {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -100rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.info {
|
||||
position: absolute;
|
||||
bottom: 86rpx;
|
||||
left: 60rpx;
|
||||
right: 60rpx;
|
||||
}
|
||||
.btn-wrap {
|
||||
width: 60%;
|
||||
position: absolute;
|
||||
left: 20%;
|
||||
&.btn-wrap1 {
|
||||
bottom: 74upx;
|
||||
}
|
||||
&.btn-wrap2 {
|
||||
bottom: 74upx;
|
||||
}
|
||||
&.btn-wrap3 {
|
||||
bottom: 38upx;
|
||||
}
|
||||
.btn-img {
|
||||
width: 100%;
|
||||
}
|
||||
.t {
|
||||
font-size: 42upx;
|
||||
font-weight: bold;
|
||||
color: #b43a14;
|
||||
position: absolute;
|
||||
top: 44%;
|
||||
left: 54%;
|
||||
white-space: nowrap;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
43
components/order-finish-modal.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 生成公众号二维码 -->
|
||||
<we-qrcode @generate="(e) => qrcodeResult(e)"></we-qrcode>
|
||||
|
||||
<officialAccount
|
||||
followIndex="order"
|
||||
:wechatAcQrcode="wechatAcQrcode"
|
||||
v-if="showOfficialAccount"
|
||||
@close="modelClose($event, 'officialAccount')"
|
||||
/>
|
||||
<Drainage v-model="showDrainage" @close="modelClose($event, 'drainage')" />
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import weQrcode from "@/components/wechat-ac-qrcode.vue";
|
||||
|
||||
import { ref, watch, computed, reactive, toRaw } from "vue";
|
||||
import officialAccount from "@/components/official-account.vue";
|
||||
import Drainage from "@/components/drainage.vue";
|
||||
|
||||
const showDrainage = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const showOfficialAccount = ref(false);
|
||||
|
||||
function modelClose(e, type) {
|
||||
console.log("modelClose", type);
|
||||
if (type == "drainage") {
|
||||
showOfficialAccount.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const wechatAcQrcode = ref("");
|
||||
const userinfo = uni.cache.get("userInfo") || {};
|
||||
const codeVal = ref(userinfo.wechatAcQrcode || "");
|
||||
console.log("codeVal", codeVal.value);
|
||||
function qrcodeResult(e) {
|
||||
wechatAcQrcode.value = e;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
password.value += num;
|
||||
}
|
||||
if (password.value.length === 6) {
|
||||
console.log('密码输入完成:', password.value);
|
||||
emits('inputComplete', password.value);
|
||||
password.value = ''
|
||||
}
|
||||
@@ -56,7 +57,7 @@
|
||||
// 关闭模态框
|
||||
const closeModal = () => {
|
||||
emits('close');
|
||||
password = '';
|
||||
password.value = '';
|
||||
};
|
||||
// 将方法暴露给父组件
|
||||
defineExpose({
|
||||
|
||||
260
components/paymentMethod copy.vue
Normal file
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<!-- 支付方式 -->
|
||||
<view class="paymentMethod">
|
||||
<view class="paymentMethod_content">
|
||||
<view class="paymentMethod_title">支付方式</view>
|
||||
<up-radio-group v-model="radiovalue" iconPlacement="right" @change="groupChanges" :size="28"
|
||||
placement="column">
|
||||
<block v-for="(item,index) in paymentMethodList" :key="index">
|
||||
<view class="method_list" @click="groupChanges(item.type)" :class="{disabled:returnDisabled(item)}"
|
||||
v-if="(index+1) == radiovalue?!changeFreeenable:true">
|
||||
<view class="method_list_top">
|
||||
<view class="method_list_top_left">
|
||||
<image class="icon" :src="item.url" mode="aspectFill" />
|
||||
<view class="method_list_top_cen">
|
||||
<view class="name"> {{ item.name }} </view>
|
||||
<view class="method_list_bom" v-if="item.type == 1">
|
||||
<text class="balance">
|
||||
当前余额¥{{orderVIP?(orderVIP.amount||0):0}}</text>
|
||||
<text class="topUpNow" @click="goRecharge">去充值</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<up-radio :disabled="returnDisabled(item)" activeColor="#E8AD7B" icon-size="18" size="18" :name="item.type">
|
||||
</up-radio>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</up-radio-group>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
defineProps,
|
||||
computed,
|
||||
defineEmits,
|
||||
watch,
|
||||
watchEffect,
|
||||
defineExpose
|
||||
} from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
rechargeFreeChecked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
payAmount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
freeCheck: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
changeFreeenable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disablePayType: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function returnDisabled(item) {
|
||||
if (props.disablePayType.includes(item.name)) {
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const orderVIP = ref(null)
|
||||
const emits = defineEmits(['customevent', 'groupChange']);
|
||||
watchEffect(() => {
|
||||
orderVIP.value = uni.cache.get('orderVIP')
|
||||
})
|
||||
|
||||
const orderVIPfun = (data) => {
|
||||
orderVIP.value = data
|
||||
}
|
||||
|
||||
const paymentMethodList = ref([
|
||||
// #ifdef MP-WEIXIN
|
||||
{
|
||||
name: "微信支付",
|
||||
type: 2,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/weChat.png",
|
||||
payType: 'wechatPay'
|
||||
},
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
{
|
||||
name: "支付宝支付",
|
||||
type: 3,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/alipay.png",
|
||||
payType: 'aliPay'
|
||||
},
|
||||
// #endif
|
||||
{
|
||||
name: "余额支付",
|
||||
type: 1,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/drder/wechat.png",
|
||||
payType: 'accountPay'
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
const paymentMethodName = ref([{
|
||||
name: "余额支付",
|
||||
type: 1,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/drder/wechat.png",
|
||||
payType: 'accountPay'
|
||||
},
|
||||
{
|
||||
name: "微信支付",
|
||||
type: 2,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/weChat.png",
|
||||
payType: 'wechatPay'
|
||||
},
|
||||
{
|
||||
name: "支付宝支付",
|
||||
type: 3,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/alipay.png",
|
||||
payType: 'aliPay'
|
||||
},
|
||||
])
|
||||
|
||||
const radiovalue = ref(2) // 支付方式
|
||||
|
||||
const ispws = ref(false) // 输入支付密码
|
||||
|
||||
const storeInfo = ref({})
|
||||
|
||||
// * 监听支付方式切换
|
||||
const groupChanges = (type) => {
|
||||
if (props.freeCheck && type == 1) {
|
||||
return;
|
||||
}
|
||||
const item=paymentMethodList.value.find(v=>v.type==type)
|
||||
if(item&&returnDisabled(item)){
|
||||
uni.showToast({
|
||||
title:"当前支付方式不可用",
|
||||
icon:'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
// if (props.payAmount <= 0 && type != 1) {
|
||||
// return;
|
||||
// }
|
||||
radiovalue.value = type;
|
||||
let name = paymentMethodName.value[type - 1].name;
|
||||
|
||||
emits("groupChange", paymentMethodName.value[type - 1])
|
||||
}
|
||||
|
||||
// 去充值
|
||||
const goRecharge = () => {
|
||||
if (orderVIP.value.isVip) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/member/czzx?shopId=' + orderVIP.value.shopId
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/user/vip/buy-vip?shopId=' + orderVIP.value.shopId
|
||||
})
|
||||
|
||||
// uni.pro.navigateTo('user/member/index', {
|
||||
// shopId: orderVIP.value.shopId
|
||||
// })
|
||||
}
|
||||
// 将方法暴露给父组件
|
||||
defineExpose({
|
||||
groupChanges,
|
||||
orderVIPfun
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.paymentMethod {
|
||||
box-sizing: border-box;
|
||||
margin-top: 30rpx;
|
||||
border-radius: 18rpx;
|
||||
|
||||
.paymentMethod_content {
|
||||
background-color: #fff;
|
||||
border-radius: 22rpx;
|
||||
padding: 30rpx 30rpx 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.paymentMethod_title {
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.method_list {
|
||||
padding: 40rpx 0;
|
||||
box-sizing: border-box;
|
||||
&.disabled{
|
||||
opacity: .6;
|
||||
}
|
||||
.method_list_top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.method_list_top_left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
width: 54.67rpx !important;
|
||||
height: 48rpx !important;
|
||||
margin-right: 22rpx;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.method_list_top_cen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.method_list_bom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.balance {
|
||||
margin-right: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.topUpNow {
|
||||
color: #FF803D;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.method_list:nth-child(odd) {
|
||||
border-bottom: 2rpx solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,260 +1,302 @@
|
||||
<template>
|
||||
<!-- 支付方式 -->
|
||||
<view class="paymentMethod">
|
||||
<view class="paymentMethod_content">
|
||||
<view class="paymentMethod_title">支付方式</view>
|
||||
<up-radio-group v-model="radiovalue" iconPlacement="right" @change="groupChanges" :size="28"
|
||||
placement="column">
|
||||
<block v-for="(item,index) in paymentMethodList" :key="index">
|
||||
<view class="method_list" @click="groupChanges(item.type)" :class="{disabled:returnDisabled(item)}"
|
||||
v-if="(index+1) == radiovalue?!changeFreeenable:true">
|
||||
<view class="method_list_top">
|
||||
<view class="method_list_top_left">
|
||||
<image class="icon" :src="item.url" mode="aspectFill" />
|
||||
<view class="method_list_top_cen">
|
||||
<view class="name"> {{ item.name }} </view>
|
||||
<view class="method_list_bom" v-if="item.type == 1">
|
||||
<text class="balance">
|
||||
当前余额¥{{orderVIP?(orderVIP.amount||0):0}}</text>
|
||||
<text class="topUpNow" @click="goRecharge">去充值</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<up-radio :disabled="returnDisabled(item)" activeColor="#E8AD7B" icon-size="18" size="18" :name="item.type">
|
||||
</up-radio>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</up-radio-group>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 支付方式 -->
|
||||
<view class="paymentMethod">
|
||||
<view class="paymentMethod_content">
|
||||
<view class="paymentMethod_title">支付方式</view>
|
||||
<up-radio-group
|
||||
v-model="radiovalue.type"
|
||||
iconPlacement="right"
|
||||
@change="groupChanges"
|
||||
:size="28"
|
||||
placement="column"
|
||||
>
|
||||
<block v-for="(item, index) in paymentMethodList" :key="index">
|
||||
<view
|
||||
class="method_list"
|
||||
@click="groupChanges(item.type)"
|
||||
:class="{ disabled: returnDisabled(item) }"
|
||||
>
|
||||
<view class="method_list_top">
|
||||
<view class="method_list_top_left">
|
||||
<image class="icon" :src="item.url" mode="aspectFill" />
|
||||
<view class="method_list_top_cen">
|
||||
<view class="name"> {{ item.name }} </view>
|
||||
<view class="method_list_bom" v-if="item.type == 1">
|
||||
<text class="balance">
|
||||
当前余额¥{{ orderVIP ? orderVIP.amount || 0 : 0 }}</text
|
||||
>
|
||||
<text class="topUpNow" @click="goRecharge">去充值</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<up-radio
|
||||
:disabled="returnDisabled(item)"
|
||||
activeColor="#E8AD7B"
|
||||
icon-size="18"
|
||||
size="18"
|
||||
:name="item.type"
|
||||
>
|
||||
</up-radio>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</up-radio-group>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
defineProps,
|
||||
computed,
|
||||
defineEmits,
|
||||
watch,
|
||||
watchEffect,
|
||||
defineExpose
|
||||
} from 'vue'
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
defineProps,
|
||||
computed,
|
||||
defineEmits,
|
||||
watch,
|
||||
watchEffect,
|
||||
defineExpose,
|
||||
} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
rechargeFreeChecked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
payAmount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
freeCheck: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
changeFreeenable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disablePayType: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
rechargeFreeChecked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
payAmount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
freeCheck: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
changeFreeenable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disablePayType: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
function returnDisabled(item) {
|
||||
if (props.disablePayType.includes(item.name)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function returnDisabled(item) {
|
||||
if (props.disablePayType.includes(item.name)) {
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
const orderVIP = ref(null);
|
||||
const emits = defineEmits(["customevent", "groupChange"]);
|
||||
watchEffect(() => {
|
||||
orderVIP.value = uni.cache.get("orderVIP");
|
||||
});
|
||||
|
||||
const orderVIP = ref(null)
|
||||
const emits = defineEmits(['customevent', 'groupChange']);
|
||||
watchEffect(() => {
|
||||
orderVIP.value = uni.cache.get('orderVIP')
|
||||
})
|
||||
const orderVIPfun = (data) => {
|
||||
orderVIP.value = data;
|
||||
};
|
||||
|
||||
const orderVIPfun = (data) => {
|
||||
orderVIP.value = data
|
||||
}
|
||||
const paymentMethodList = ref([
|
||||
// #ifdef MP-WEIXIN
|
||||
{
|
||||
name: "微信支付",
|
||||
type: 2,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/weChat.png",
|
||||
payType: "wechatPay",
|
||||
},
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
{
|
||||
name: "支付宝支付",
|
||||
type: 3,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/alipay.png",
|
||||
payType: "aliPay",
|
||||
},
|
||||
// #endif
|
||||
{
|
||||
name: "余额支付",
|
||||
type: 1,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/drder/wechat.png",
|
||||
payType: "accountPay",
|
||||
},
|
||||
]);
|
||||
|
||||
const paymentMethodList = ref([
|
||||
// #ifdef MP-WEIXIN
|
||||
{
|
||||
name: "微信支付",
|
||||
type: 2,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/weChat.png",
|
||||
payType: 'wechatPay'
|
||||
},
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
{
|
||||
name: "支付宝支付",
|
||||
type: 3,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/alipay.png",
|
||||
payType: 'aliPay'
|
||||
},
|
||||
// #endif
|
||||
{
|
||||
name: "余额支付",
|
||||
type: 1,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/drder/wechat.png",
|
||||
payType: 'accountPay'
|
||||
}
|
||||
])
|
||||
const paymentMethodName = ref([
|
||||
{
|
||||
name: "余额支付",
|
||||
type: 1,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/drder/wechat.png",
|
||||
payType: "accountPay",
|
||||
},
|
||||
{
|
||||
name: "微信支付",
|
||||
type: 2,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/weChat.png",
|
||||
payType: "wechatPay",
|
||||
},
|
||||
{
|
||||
name: "支付宝支付",
|
||||
type: 3,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/alipay.png",
|
||||
payType: "aliPay",
|
||||
},
|
||||
]);
|
||||
|
||||
// const radiovalue = ref(2); // 支付方式
|
||||
const radiovalue = defineModel({
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
name: "微信支付",
|
||||
type: 2,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/weChat.png",
|
||||
payType: "wechatPay",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const paymentMethodName = ref([{
|
||||
name: "余额支付",
|
||||
type: 1,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/drder/wechat.png",
|
||||
payType: 'accountPay'
|
||||
},
|
||||
{
|
||||
name: "微信支付",
|
||||
type: 2,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/weChat.png",
|
||||
payType: 'wechatPay'
|
||||
},
|
||||
{
|
||||
name: "支付宝支付",
|
||||
type: 3,
|
||||
url: "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/confirmOrder/alipay.png",
|
||||
payType: 'aliPay'
|
||||
},
|
||||
])
|
||||
watch(
|
||||
() => props.disablePayType,
|
||||
(newval) => {
|
||||
|
||||
const radiovalue = ref(2) // 支付方式
|
||||
const canUsePayType = paymentMethodList.value.filter((item) => {
|
||||
return !newval.includes(item.name);
|
||||
});
|
||||
if (canUsePayType.find((v) => v.type == radiovalue.value.type)) {
|
||||
return;
|
||||
}
|
||||
radiovalue.value = canUsePayType[0];
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const ispws = ref(false) // 输入支付密码
|
||||
// * 监听支付方式切换
|
||||
const groupChanges = (type) => {
|
||||
if (props.freeCheck && type == 1) {
|
||||
return;
|
||||
}
|
||||
const item = paymentMethodList.value.find((v) => v.type == type);
|
||||
|
||||
const storeInfo = ref({})
|
||||
if (item && returnDisabled(item)) {
|
||||
uni.showToast({
|
||||
title: "当前支付方式不可用",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// if (props.payAmount <= 0 && type != 1) {
|
||||
// return;
|
||||
// }
|
||||
radiovalue.value = item;
|
||||
emits("groupChange", paymentMethodName.value[type - 1]);
|
||||
};
|
||||
|
||||
// * 监听支付方式切换
|
||||
const groupChanges = (type) => {
|
||||
if (props.freeCheck && type == 1) {
|
||||
return;
|
||||
}
|
||||
const item=paymentMethodList.value.find(v=>v.type==type)
|
||||
if(item&&returnDisabled(item)){
|
||||
uni.showToast({
|
||||
title:"当前支付方式不可用",
|
||||
icon:'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
// if (props.payAmount <= 0 && type != 1) {
|
||||
// return;
|
||||
// }
|
||||
radiovalue.value = type;
|
||||
let name = paymentMethodName.value[type - 1].name;
|
||||
// 去充值
|
||||
const goRecharge = () => {
|
||||
if (props.disablePayType.includes("余额支付")) {
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: "/pages/user/member/czzx?shopId=" + orderVIP.value.shopId,
|
||||
});
|
||||
// if (orderVIP.value.isVip) {
|
||||
// uni.navigateTo({
|
||||
// url: "/pages/user/member/czzx?shopId=" + orderVIP.value.shopId,
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// uni.navigateTo({
|
||||
// url: "/user/vip/buy-vip?shopId=" + orderVIP.value.shopId,
|
||||
// });
|
||||
|
||||
emits("groupChange", paymentMethodName.value[type - 1])
|
||||
}
|
||||
|
||||
// 去充值
|
||||
const goRecharge = () => {
|
||||
if (orderVIP.value.isVip) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/member/czzx?shopId=' + orderVIP.value.shopId
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/user/vip/buy-vip?shopId=' + orderVIP.value.shopId
|
||||
})
|
||||
|
||||
// uni.pro.navigateTo('user/member/index', {
|
||||
// shopId: orderVIP.value.shopId
|
||||
// })
|
||||
}
|
||||
// 将方法暴露给父组件
|
||||
defineExpose({
|
||||
groupChanges,
|
||||
orderVIPfun
|
||||
});
|
||||
// uni.pro.navigateTo('user/member/index', {
|
||||
// shopId: orderVIP.value.shopId
|
||||
// })
|
||||
};
|
||||
// 将方法暴露给父组件
|
||||
defineExpose({
|
||||
groupChanges,
|
||||
orderVIPfun,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.paymentMethod {
|
||||
box-sizing: border-box;
|
||||
margin-top: 30rpx;
|
||||
border-radius: 18rpx;
|
||||
.paymentMethod {
|
||||
box-sizing: border-box;
|
||||
margin-top: 30rpx;
|
||||
border-radius: 18rpx;
|
||||
|
||||
.paymentMethod_content {
|
||||
background-color: #fff;
|
||||
border-radius: 22rpx;
|
||||
padding: 30rpx 30rpx 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
.paymentMethod_content {
|
||||
background-color: #fff;
|
||||
border-radius: 22rpx;
|
||||
padding: 30rpx 30rpx 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.paymentMethod_title {
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.paymentMethod_title {
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.method_list {
|
||||
padding: 40rpx 0;
|
||||
box-sizing: border-box;
|
||||
&.disabled{
|
||||
opacity: .6;
|
||||
}
|
||||
.method_list_top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.method_list {
|
||||
padding: 40rpx 0;
|
||||
box-sizing: border-box;
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.method_list_top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.method_list_top_left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.method_list_top_left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
width: 54.67rpx !important;
|
||||
height: 48rpx !important;
|
||||
margin-right: 22rpx;
|
||||
}
|
||||
.icon {
|
||||
width: 54.67rpx !important;
|
||||
height: 48rpx !important;
|
||||
margin-right: 22rpx;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.method_list_top_cen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.method_list_top_cen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.method_list_bom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.method_list_bom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.balance {
|
||||
margin-right: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.balance {
|
||||
margin-right: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.topUpNow {
|
||||
color: #ff803d;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.topUpNow {
|
||||
color: #FF803D;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.method_list:nth-child(odd) {
|
||||
border-bottom: 2rpx solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
.method_list:nth-child(odd) {
|
||||
border-bottom: 2rpx solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
3
components/poster-maker.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
52
components/wechat-ac-qrcode.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 生成公众号二维码 -->
|
||||
|
||||
<view class="qrcode-box" v-if="codeOptions.code">
|
||||
<w-qrcode
|
||||
:options="codeOptions"
|
||||
ref="wQrcode"
|
||||
@generate="(e) => qrcodeResult(e)"
|
||||
></w-qrcode>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import wQrcode from "@/uni_modules/wmf-code/components/w-qrcode/w-qrcode.vue";
|
||||
import { onMounted ,ref} from "vue";
|
||||
const codeOptions = ref({
|
||||
size: 200,
|
||||
code: "",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 页面加载完成后生成二维码
|
||||
const userInfo = uni.cache.get("userInfo") || {};
|
||||
const wechatAcQrcode = userInfo.wechatAcQrcode || "";
|
||||
if(!userInfo.isAc){
|
||||
codeOptions.value.code = wechatAcQrcode;
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(["generate"]);
|
||||
function qrcodeResult(e) {
|
||||
const userInfo = uni.cache.get("userInfo") || {};
|
||||
if(!userInfo.isAc){
|
||||
emit("generate", e.img.tempFilePath);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.qrcode-box {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
left: -400px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
BIN
components/xb-swiper-preview/images/icon-back.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
385
components/xb-swiper-preview/index.vue
Normal file
@@ -0,0 +1,385 @@
|
||||
<template>
|
||||
<view class="swiper-content" v-if="visable">
|
||||
<view class="hander-top p-lr-32 w-s flex-j-sb--a-ct">
|
||||
<view class="icon-back-wrap" @click="onBack">
|
||||
<image class="icon-back" src="./images/icon-back.png" mode="widthFix" />
|
||||
</view>
|
||||
<view class="count">{{ currentImg + 1 }} / {{ imgs.length }}</view>
|
||||
</view>
|
||||
|
||||
<swiper
|
||||
class="swiper-img"
|
||||
:current="currentImg"
|
||||
:duration="300"
|
||||
@change="changeSwiper"
|
||||
>
|
||||
<swiper-item
|
||||
class="swiper-item"
|
||||
v-for="(item, index) in imgs"
|
||||
:key="index"
|
||||
>
|
||||
<view class="img-page">
|
||||
<movable-area scale-area>
|
||||
<movable-view
|
||||
direction="all"
|
||||
scale="true"
|
||||
scale-min="1"
|
||||
scale-max="4"
|
||||
@click.stop="onBack"
|
||||
|
||||
>
|
||||
<image
|
||||
@click.stop="()=>{}"
|
||||
class="max-img"
|
||||
:src="item"
|
||||
:lazy-load="true"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="item-bottom" v-if="false">
|
||||
<scroll-view
|
||||
class="scroll-view_H"
|
||||
:scroll-x="true"
|
||||
:scroll-into-view="scrollTopIndex"
|
||||
:scroll-with-animation="true"
|
||||
scroll-left="20"
|
||||
>
|
||||
<view
|
||||
class="img-page-box scroll-view-item_H"
|
||||
:class="currentImg == index ? 'img-page-checked' : ''"
|
||||
v-for="(item, index) in imgs"
|
||||
:key="index"
|
||||
:id="`scrollToIndex${index}`"
|
||||
@click.stop="toImg(index)"
|
||||
>
|
||||
<image class="img" :src="item" mode="aspectFill" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view class="pop" v-if="isPop">
|
||||
<view class="item" @click.stop="share()">分享图片</view>
|
||||
<!-- #ifndef H5 -->
|
||||
<view class="item" @click.stop="saveImg(false)">保存图片</view>
|
||||
<view class="item" @click.stop="saveImg(true)">保存全部图片</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
imgs: {
|
||||
type: [],
|
||||
default: () => [],
|
||||
},
|
||||
visable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:visable'],
|
||||
data() {
|
||||
return {
|
||||
currentImg: 0,
|
||||
isPop: false,
|
||||
scrollTopIndex: "",
|
||||
|
||||
isScale: false,
|
||||
isCollect: false,
|
||||
isLike: false,
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
// let { imgs, current } = options;
|
||||
// this.imgs = JSON.parse(imgs);
|
||||
// this.currentImg = current;
|
||||
},
|
||||
methods: {
|
||||
changeSwiper(e) {
|
||||
this.currentImg = e.detail.current;
|
||||
console.log("e", e);
|
||||
|
||||
this.scrollTopIndex = `scrollToIndex${e.detail.current}`;
|
||||
},
|
||||
toImg(index) {
|
||||
this.currentImg = index;
|
||||
},
|
||||
onBack() {
|
||||
this.$emit('update:visable', false);
|
||||
},
|
||||
share() {
|
||||
uni.downloadFile({
|
||||
// 下面一行时拼接预览PDF的地址!!!
|
||||
url: this.imgs[this.currentImg],
|
||||
success: function (res) {
|
||||
var filePath = res.tempFilePath;
|
||||
if (!filePath) return;
|
||||
uni.openDocument({
|
||||
filePath: filePath,
|
||||
success: function (res) {
|
||||
console.log(res);
|
||||
console.log("打开文档成功");
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
saveImg(isAll = false) {
|
||||
const that = this;
|
||||
if (!isAll) {
|
||||
uni.downloadFile({
|
||||
url: this.imgs[this.currentImg],
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "保存成功",
|
||||
});
|
||||
that.isPop = false;
|
||||
},
|
||||
fail: function () {},
|
||||
});
|
||||
} else {
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.imgs.forEach((item) => {
|
||||
uni.downloadFile({
|
||||
url: item,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "保存全部成功",
|
||||
});
|
||||
that.isPop = false;
|
||||
},
|
||||
fail: function () {},
|
||||
});
|
||||
} else {
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.swiper-content {
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
background: #000;
|
||||
z-index: 99999;
|
||||
}
|
||||
movable-view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
movable-area {
|
||||
position: fixed;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
transform: scale();
|
||||
}
|
||||
|
||||
movable-view image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
uni-image > img {
|
||||
z-index: -1 !important;
|
||||
}
|
||||
|
||||
.hander-top {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
top: 48rpx;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
.icon-back-wrap {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
.icon-back {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
box-sizing: border-box;
|
||||
padding: 8rpx 24rpx;
|
||||
|
||||
color: #fff;
|
||||
border-radius: 24rpx;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-img {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
background-color: #000;
|
||||
|
||||
.swiper-item {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
.img-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
.max-img {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.handle-wrap {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
bottom: 40rpx;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
image {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
bottom: 110rpx;
|
||||
left: 0rpx;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100vw;
|
||||
|
||||
// height: 200rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
transition: ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
.small-list-page {
|
||||
min-height: 60rpx;
|
||||
}
|
||||
|
||||
.scroll-Y {
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.scroll-view_H {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 32rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scroll-view-item_H {
|
||||
display: inline-block;
|
||||
|
||||
margin-right: 10rpx;
|
||||
|
||||
transition: ease-in 0.1s;
|
||||
transform: scale(0.8);
|
||||
|
||||
border-radius: 11rpx;
|
||||
background: #c2c2c2;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.img {
|
||||
display: block;
|
||||
|
||||
width: 160rpx;
|
||||
height: 272rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.img-page-checked {
|
||||
transform: translateY(-28rpx) scale(1);
|
||||
}
|
||||
|
||||
.pop {
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 500rpx;
|
||||
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
border-radius: 20rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.item {
|
||||
height: 100rpx;
|
||||
padding: 0 50rpx;
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
border-radius: 20rpx;
|
||||
|
||||
line-height: 100rpx;
|
||||
|
||||
&:active {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
distribution/components/commission.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<view>
|
||||
<up-popup
|
||||
:show="show"
|
||||
@close="close"
|
||||
mode="center"
|
||||
:safe-area-inset-bottom="false"
|
||||
>
|
||||
<view class="u-flex top justify-between u-p-32">
|
||||
<text class="title">{{ tipsType }}</text>
|
||||
<up-icon name="close" @click="close"></up-icon>
|
||||
</view>
|
||||
<scroll-view style="width: 500rpx; max-height: 60vh">
|
||||
<view class="u-p-30 font-14" style="width: 500rpx">
|
||||
<template v-if="tipsType == '等级分成比例'">
|
||||
<view class="u-flex justify-between font-bold u-m-b-20">
|
||||
<view>等级</view>
|
||||
<view>分成比例</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-m-b-10"
|
||||
v-for="(item, index) in props.levelConfigList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="u-flex justify-between">
|
||||
<view> {{ item.level }}({{ item.name }}) </view>
|
||||
<view class="color-666"> {{ item.levelOneCommission }}% </view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="tipsType == '等级升级条件'">
|
||||
<view class="u-flex justify-between font-bold u-m-b-20">
|
||||
<view>等级</view>
|
||||
<view v-if="config.upgradeType != 'not_upgrade'">升级条件</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-m-b-10"
|
||||
v-for="(item, index) in props.levelConfigList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="u-flex justify-between">
|
||||
<view>
|
||||
<text>
|
||||
{{ item.level }}
|
||||
</text>
|
||||
<text>({{ item.name }}) </text>
|
||||
</view>
|
||||
<view v-if="config.upgradeType != 'not_upgrade'"
|
||||
>{{ returnTiaojain(item) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const show = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const props = defineProps({
|
||||
tipsType: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
levelConfigList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
function close() {
|
||||
show.value = false;
|
||||
}
|
||||
function returnTiaojain(item) {
|
||||
if (props.config.upgradeType === "not_upgrade") {
|
||||
return "";
|
||||
}
|
||||
if (props.config.upgradeType == "cost") {
|
||||
return `订单金额满${item.costAmount}元`;
|
||||
}
|
||||
if (props.config.upgradeType == "invite") {
|
||||
return `邀请${item.inviteCount}人`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.title {
|
||||
color: #000000;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.top {
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
}
|
||||
</style>
|
||||
137
distribution/components/rule.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<view>
|
||||
<up-popup
|
||||
:show="show"
|
||||
@close="close"
|
||||
mode="center"
|
||||
:safe-area-inset-bottom="false"
|
||||
>
|
||||
<view class="u-flex top justify-between u-p-32">
|
||||
<text class="title">规则说明</text>
|
||||
<up-icon name="close" @click="close"></up-icon>
|
||||
</view>
|
||||
<scroll-view style="width: 500rpx; max-height: 60vh">
|
||||
<view class="u-p-30 font-14" style="width: 500rpx">
|
||||
<view class="font-12 color-666 u-m-t-16">
|
||||
<view>
|
||||
<view> 我的收益什么时候可以到账?</view>
|
||||
<view> 分销的结算时长为{{ config.settlementDay || 0 }}天</view>
|
||||
</view>
|
||||
|
||||
<view class="u-m-t-40" v-if="nextLvMoney">
|
||||
<view>怎么样才能升级分销员等级?</view>
|
||||
<template
|
||||
v-if="
|
||||
config.upgradeType != 'not_upgrade' &&
|
||||
config.levelConfigList &&
|
||||
config.levelConfigList.length >= 2
|
||||
"
|
||||
>
|
||||
<template v-if="config.upgradeType == 'invite'">
|
||||
<view
|
||||
>邀请的有效人数达到{{
|
||||
config.inviteCount || 0
|
||||
}}人即可升级</view
|
||||
>
|
||||
<view class="u-m-t-40"> 什么是有效邀请人数?</view>
|
||||
<view> 被邀请人在店铺消费过,即有一笔订单完成才算有效</view>
|
||||
</template>
|
||||
|
||||
<template v-if="config.upgradeType == 'cost'&&nextLvMoney">
|
||||
<view> 消费金额总计达到{{ nextLvMoney }}元即可升级</view>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view>请联系商家</view>
|
||||
</template>
|
||||
</view>
|
||||
<view class="u-m-t-40" v-if="config.upgradeType == 'cost'">
|
||||
<view>消费金额如何计算?</view>
|
||||
<view
|
||||
>消费金额是计算您和您邀请的人在店铺消费的总金额,但退款订单不计入</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const show = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const props = defineProps({
|
||||
tipsType: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
distributionUser: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
function close() {
|
||||
show.value = false;
|
||||
}
|
||||
const nextLvMoney = computed(() => {
|
||||
let nextLv = undefined;
|
||||
if (
|
||||
!props.distributionUser ||
|
||||
!props.config ||
|
||||
!props.config.levelConfigList ||
|
||||
!props.config.levelConfigList.length
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
if (!props.distributionUser || !props.distributionUser.distributionId) {
|
||||
nextLv = props.config.levelConfigList[0];
|
||||
} else {
|
||||
nextLv = props.config.levelConfigList.find(
|
||||
(v) => v.level == props.distributionUser.level + 1
|
||||
);
|
||||
}
|
||||
|
||||
if (nextLv) {
|
||||
if (props.config.upgradeType == "cost") {
|
||||
return nextLv.costAmount;
|
||||
}
|
||||
if (props.config.upgradeType == "invite") {
|
||||
return nextLv.inviteCount;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const juNextLvMoney = computed(() => {
|
||||
if (!nextLvMoney.value) {
|
||||
return "";
|
||||
}
|
||||
if (props.config.upgradeType == "cost" && props.distributionUser) {
|
||||
return nextLvMoney.value - (props.distributionUser.consumeAmount || 0);
|
||||
}
|
||||
if (props.config.upgradeType == "invite" && props.distributionUser) {
|
||||
return nextLvMoney.value - (props.config.inviteCount || 0);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.title {
|
||||
color: #000000;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.top {
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
}
|
||||
</style>
|
||||
57
distribution/components/tips-popup.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<view>
|
||||
<up-popup
|
||||
:show="show"
|
||||
@close="close"
|
||||
mode="center"
|
||||
:safe-area-inset-bottom="false"
|
||||
>
|
||||
<view class="u-flex top justify-between u-p-32">
|
||||
<text class="title">{{props.tipsType}}</text>
|
||||
<up-icon name="close" @click="close"></up-icon>
|
||||
</view>
|
||||
<view class="u-p-30 font-14" style="width: 500rpx">
|
||||
{{ popupText }}
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const show = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const props = defineProps({
|
||||
tipsType: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
function close() {
|
||||
show.value = false;
|
||||
}
|
||||
|
||||
const popupText = computed(() => {
|
||||
if (props.tipsType === "总收益") {
|
||||
return "总收益:即您在所有店铺通过分销获得总金额,包括待入账金额,但不包含已退款订单";
|
||||
}
|
||||
if (props.tipsType === "待入账") {
|
||||
return "待入账:即已通过订单分销获得但未达到结算时间的金额,结算时间达到后将会计入可提现金额";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.title {
|
||||
color: #000000;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.top {
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
}
|
||||
</style>
|
||||
660
distribution/income-details/index.vue
Normal file
@@ -0,0 +1,660 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header-wrap">
|
||||
<view class="u-flex" style="justify-content: flex-end"> </view>
|
||||
<view class="search-wrap">
|
||||
<view class="input-wrap" @click="showStatus = true">
|
||||
<view class="icon right">
|
||||
<u-icon name="arrow-down" size="12"></u-icon>
|
||||
</view>
|
||||
<input
|
||||
v-model="querForm.statusName"
|
||||
class="ipt right"
|
||||
type="text"
|
||||
placeholder="全部"
|
||||
placeholder-style="font-size: 28rpx"
|
||||
disabled
|
||||
/>
|
||||
</view>
|
||||
<view class="input-wrap" @click="showTimeArea = true">
|
||||
<view class="icon right">
|
||||
<u-icon name="arrow-down" size="12"></u-icon>
|
||||
</view>
|
||||
<input
|
||||
v-model="querForm.timeArea"
|
||||
class="ipt right"
|
||||
type="text"
|
||||
placeholder="选择日期范围"
|
||||
placeholder-style="font-size: 28rpx"
|
||||
disabled
|
||||
/>
|
||||
</view>
|
||||
<view class="input-wrap" @click="show = true">
|
||||
<view class="icon right">
|
||||
<u-icon name="arrow-down" size="12"></u-icon>
|
||||
</view>
|
||||
<input
|
||||
v-model="querForm.shopName"
|
||||
class="ipt right"
|
||||
type="text"
|
||||
placeholder="全部店铺"
|
||||
placeholder-style="font-size: 28rpx"
|
||||
disabled
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<view
|
||||
class="u-p-t-16 u-p-b-20 u-flex u-p-l-28 u-p-r-28"
|
||||
style="align-items: baseline; justify-content: flex-end"
|
||||
>
|
||||
<text class="color-666 font-12"> 总计:</text>
|
||||
<text class="font-16 color-333 font-700" v-if="centerUserInfo">
|
||||
{{ centerUserInfo.totalIncome }}</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="list">
|
||||
<view v-for="(item, index) in state.records" :key="index" class="item">
|
||||
<view class="u-flex justify-between">
|
||||
<view>
|
||||
<text class="color-666">来源:</text>
|
||||
<text class="color-333 font-700">{{ item.shopName }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="color-666" v-if="item.status == 'pending'"
|
||||
>待入账</text
|
||||
>
|
||||
<text class="color-666" v-if="item.status == 'success'"
|
||||
>已入账</text
|
||||
>
|
||||
<text class="color-666" v-if="item.status == 'refund'"
|
||||
>已退款</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex justify-between u-m-t-16">
|
||||
<view>
|
||||
<text class="color-666">订单:</text>
|
||||
<text class="color-333 font-700">{{ item.orderNo }}</text>
|
||||
</view>
|
||||
<view class="money">
|
||||
<text class="money reduce" v-if="item.status == 'refund'"
|
||||
>-{{ item.rewardAmount }}</text
|
||||
>
|
||||
|
||||
<text class="money" v-else>+{{ item.rewardAmount }}</text>
|
||||
<text class="tag" v-if="item.status == 'refund'">订单退款</text>
|
||||
<text class="tag" v-else-if="item.level == 1"
|
||||
>(订单一级分成)</text
|
||||
>
|
||||
<text class="tag" v-else-if="item.level == 2"
|
||||
>(订单二级分成)</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex justify-between u-m-t-16">
|
||||
<view>
|
||||
<text class="color-666">时间:</text>
|
||||
<text class="color-333 font-700">{{ item.createTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-loadmore :status="isEnd ? 'nomore' : 'loading'"></u-loadmore>
|
||||
<u-popup :show="show" round="20" closeable @close="show = false">
|
||||
<view class="shoplist-popup">
|
||||
<view class="title">
|
||||
<text class="t">店铺列表</text>
|
||||
</view>
|
||||
<scroll-view
|
||||
class="popup-list"
|
||||
direction="vertical"
|
||||
@scrollend="scrollBottom"
|
||||
>
|
||||
<view
|
||||
class="item"
|
||||
v-for="item in shopList"
|
||||
:key="item.shopId"
|
||||
@click="selectShopHandle(item)"
|
||||
>
|
||||
<text class="t">{{ item.shopName }}</text>
|
||||
<text class="intro">地址:{{ item.shopAddress }}</text>
|
||||
</view>
|
||||
<u-loadmore status="nomore"></u-loadmore>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<up-action-sheet
|
||||
cancelText="取消"
|
||||
:actions="statusList"
|
||||
title="选择状态"
|
||||
:show="showStatus"
|
||||
closeOnClickAction
|
||||
@close="showStatus = false"
|
||||
@select="selectStatusHandle"
|
||||
round="16"
|
||||
></up-action-sheet>
|
||||
|
||||
<dateAreaSel
|
||||
:show="showTimeArea"
|
||||
:minYear="2022"
|
||||
@close="showTimeArea = false"
|
||||
@confirm="confirmTimeArea"
|
||||
></dateAreaSel>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
import dateAreaSel from "@/components/date-range-picker/date-range-picker.vue";
|
||||
import { ref, reactive, onMounted, computed, watch } from "vue";
|
||||
import * as distributionApi from "@/common/api/market/distribution.js";
|
||||
|
||||
import {
|
||||
onLoad,
|
||||
onReady,
|
||||
onShow,
|
||||
onPageScroll,
|
||||
onReachBottom,
|
||||
} from "@dcloudio/uni-app";
|
||||
import {
|
||||
APIcouponfindByUserId,
|
||||
APIfindCoupon,
|
||||
getCouponShops,
|
||||
} from "@/common/api/member.js";
|
||||
|
||||
const show = ref(false);
|
||||
const showTimeArea = ref(false);
|
||||
|
||||
const querForm = ref({
|
||||
shopId: "",
|
||||
shopName: "",
|
||||
statusActiveIndex: 0,
|
||||
status: "",
|
||||
statusName: "",
|
||||
startTime: "",
|
||||
timeArea: "",
|
||||
endTime: "",
|
||||
page: 1,
|
||||
});
|
||||
|
||||
function confirmTimeArea(e) {
|
||||
querForm.value.date = e;
|
||||
querForm.value.startTime = e[0];
|
||||
querForm.value.endTime = e[1];
|
||||
querForm.value.timeArea = e[0] + "-" + e[1];
|
||||
}
|
||||
|
||||
// 状态
|
||||
const statusList = ref([
|
||||
{
|
||||
value: "pending",
|
||||
name: "待入账",
|
||||
color: "#333",
|
||||
fontSize: "16",
|
||||
},
|
||||
{
|
||||
value: "success",
|
||||
name: "已入账",
|
||||
color: "#333",
|
||||
fontSize: "16",
|
||||
},
|
||||
{
|
||||
value: "refund",
|
||||
name: "已退款",
|
||||
color: "#333",
|
||||
fontSize: "16",
|
||||
},
|
||||
]);
|
||||
const returnStatusName = () => {
|
||||
let name = "";
|
||||
statusList.value.forEach((item) => {
|
||||
if (item.value == querForm.value.status) {
|
||||
name = item.name;
|
||||
}
|
||||
});
|
||||
return name;
|
||||
};
|
||||
function selectStatusHandle(e) {
|
||||
console.log(e);
|
||||
querForm.value.status = e.value;
|
||||
querForm.value.statusName = returnStatusName();
|
||||
}
|
||||
|
||||
onReachBottom(() => {
|
||||
console.log("到底了");
|
||||
if (!isEnd.value) {
|
||||
console.log("没有跟多了");
|
||||
querForm.value.page++;
|
||||
getIncomeDetailsAjax();
|
||||
}
|
||||
});
|
||||
|
||||
const showStatus = ref(false);
|
||||
|
||||
// 店铺列表滚动到底部了
|
||||
function scrollBottom() {
|
||||
console.log("店铺列表滚动到底部了");
|
||||
}
|
||||
|
||||
// 选择店铺
|
||||
function selectShopHandle(item) {
|
||||
querForm.value.shopId = item.shopId;
|
||||
querForm.value.shopName = item.shopName;
|
||||
show.value = false;
|
||||
}
|
||||
// 获取当前店铺会员信息
|
||||
const shopList = ref([]);
|
||||
async function getCouponShopsAjax() {
|
||||
try {
|
||||
const res = await distributionApi.activates({ page: 1, size: 999 });
|
||||
shopList.value = res.records || [];
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取收益明细
|
||||
const state = reactive({
|
||||
records: [],
|
||||
totalRow: 0,
|
||||
});
|
||||
const isEnd = ref(false);
|
||||
async function getIncomeDetailsAjax() {
|
||||
try {
|
||||
const ajaxQuery = {
|
||||
shopId: querForm.value.shopId,
|
||||
startTime: querForm.value.startTime
|
||||
? `${querForm.value.startTime} 00:00:00`
|
||||
: "",
|
||||
endTime: querForm.value.endTime
|
||||
? `${querForm.value.endTime} 23:59:59`
|
||||
: "",
|
||||
status: querForm.value.status,
|
||||
page: querForm.value.page,
|
||||
};
|
||||
console.log("ajaxQuery,", ajaxQuery);
|
||||
const res = await distributionApi.getIncomeDetails(ajaxQuery);
|
||||
if (res) {
|
||||
if (querForm.value.page == 1) {
|
||||
Object.assign(state, res);
|
||||
} else {
|
||||
state.totalPage = res.totalPage * 1;
|
||||
state.records.push(...(res.records || []));
|
||||
}
|
||||
isEnd.value = querForm.value.page >= res.totalPage * 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const centerUserInfo = ref({});
|
||||
async function centerUser() {
|
||||
const res = await distributionApi.centerUser();
|
||||
centerUserInfo.value = res;
|
||||
}
|
||||
onShow(() => {});
|
||||
|
||||
onLoad(async (opt) => {
|
||||
await getCouponShopsAjax();
|
||||
|
||||
console.log(opt);
|
||||
if (opt.name) {
|
||||
const findItem = statusList.value.find((item) => item.name == opt.name);
|
||||
if (findItem) {
|
||||
querForm.value.status = findItem.value;
|
||||
querForm.value.statusName = findItem.name;
|
||||
}
|
||||
}
|
||||
if (opt.shopId) {
|
||||
querForm.value.shopId = opt.shopId;
|
||||
const findItem = couponShops.value.find((item) => item.id == opt.shopId);
|
||||
querForm.value.shopName = findItem.shopName || "";
|
||||
}
|
||||
centerUser();
|
||||
getIncomeDetailsAjax();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => querForm.value.status,
|
||||
(newVal, oldVal) => {
|
||||
querForm.value.page = 1;
|
||||
isEnd.value = false;
|
||||
getIncomeDetailsAjax();
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => querForm.value.shopId,
|
||||
(newVal, oldVal) => {
|
||||
querForm.value.page = 1;
|
||||
isEnd.value = false;
|
||||
getIncomeDetailsAjax();
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => querForm.value.startTime,
|
||||
(newVal, oldVal) => {
|
||||
querForm.value.page = 1;
|
||||
isEnd.value = false;
|
||||
getIncomeDetailsAjax();
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => querForm.value.endTime,
|
||||
(newVal, oldVal) => {
|
||||
querForm.value.page = 1;
|
||||
isEnd.value = false;
|
||||
getIncomeDetailsAjax();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
padding: 130rpx 0 28upx;
|
||||
}
|
||||
.list {
|
||||
font-size: 28rpx;
|
||||
.item {
|
||||
background-color: #fff;
|
||||
padding: 32rpx 28rpx 32rpx 36rpx;
|
||||
margin-bottom: 16rpx;
|
||||
.money {
|
||||
line-height: 44rpx;
|
||||
color: #fe7e00;
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
position: relative;
|
||||
&.reduce {
|
||||
color: #ff1c1c;
|
||||
}
|
||||
.tag {
|
||||
position: absolute;
|
||||
font-weight: 400;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
font-size: 28rpx;
|
||||
white-space: nowrap;
|
||||
color: #999;
|
||||
transform: translateY(10rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
padding: 28upx;
|
||||
.search-wrap {
|
||||
display: flex;
|
||||
gap: 20upx;
|
||||
.input-wrap {
|
||||
height: 70upx;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 8upx;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
&:nth-child(1) {
|
||||
width: 170rpx;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
flex: 1;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
width: 226rpx;
|
||||
}
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
&.left {
|
||||
left: 14upx;
|
||||
}
|
||||
&.right {
|
||||
right: 14upx;
|
||||
}
|
||||
}
|
||||
.ipt {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 28rpx;
|
||||
&.left {
|
||||
padding-left: 68upx;
|
||||
}
|
||||
&.right {
|
||||
padding: 0 56upx 0 24upx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.status-wrap {
|
||||
display: flex;
|
||||
padding-top: 28upx;
|
||||
position: relative;
|
||||
.icon-wrap {
|
||||
height: 12upx;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
.active-icon {
|
||||
width: 24upx;
|
||||
height: 12upx;
|
||||
z-index: 9;
|
||||
}
|
||||
}
|
||||
.item {
|
||||
flex: 1;
|
||||
height: 80upx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.t {
|
||||
font-size: 32upx;
|
||||
color: #666666;
|
||||
transition: all 0.3s ease-in-out 0.1s;
|
||||
}
|
||||
&.active {
|
||||
.t {
|
||||
color: #e3ad7f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.list-wrap {
|
||||
padding-top: 28upx;
|
||||
.item {
|
||||
border-radius: 18upx;
|
||||
background-color: #fff;
|
||||
padding: 28upx;
|
||||
&:not(:first-child) {
|
||||
margin-top: 28upx;
|
||||
}
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 28upx;
|
||||
.icon {
|
||||
$size: 140upx;
|
||||
width: $size;
|
||||
height: $size;
|
||||
margin-right: 28upx;
|
||||
}
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 8upx;
|
||||
padding-left: 28upx;
|
||||
position: relative;
|
||||
&::after {
|
||||
$height: 100upx;
|
||||
content: "";
|
||||
height: $height;
|
||||
border-left: 1upx solid #f7f7f7;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: $height * 0.5 * -1;
|
||||
left: 0;
|
||||
}
|
||||
.view {
|
||||
flex: 1;
|
||||
&.name {
|
||||
font-size: 32upx;
|
||||
color: #333;
|
||||
}
|
||||
&.time {
|
||||
.t {
|
||||
font-size: 24upx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
width: 120upx;
|
||||
height: 48upx;
|
||||
border-radius: 48upx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #333;
|
||||
.t {
|
||||
font-size: 24upx;
|
||||
color: #fff;
|
||||
}
|
||||
&.disabled {
|
||||
background-color: #f8f8f8;
|
||||
.t {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.btm {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28upx 0 14upx;
|
||||
border-top: 1upx solid #f7f7f7;
|
||||
.left {
|
||||
flex: 1;
|
||||
height: 40upx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 24upx;
|
||||
color: #999;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 28upx;
|
||||
justify-content: flex-end;
|
||||
.t {
|
||||
font-size: 24upx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.shoplist-popup {
|
||||
padding: 0 28upx 28upx;
|
||||
.title {
|
||||
padding: 28upx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.t {
|
||||
font-size: 32upx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.popup-list {
|
||||
max-height: 50vh;
|
||||
.item {
|
||||
padding: 28upx;
|
||||
border-radius: 12upx;
|
||||
background-color: #f7f7f7;
|
||||
margin-bottom: 28upx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 28upx;
|
||||
.t {
|
||||
font-size: 28upx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
/* 必须设置宽度 */
|
||||
width: 600upx; /* 或具体像素值 */
|
||||
|
||||
/* 关键属性 */
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1; /* 显示2行 */
|
||||
overflow: hidden;
|
||||
|
||||
/* 文本溢出省略号 */
|
||||
text-overflow: ellipsis;
|
||||
|
||||
/* 可选:防止行高度,确保计算准确 */
|
||||
line-height: 1.5;
|
||||
word-break: break-all; /* 允许在单词内换行 */
|
||||
word-wrap: break-word; /* 允许长单词或URL换行 */
|
||||
}
|
||||
.intro {
|
||||
font-size: 28upx;
|
||||
color: #999;
|
||||
/* 必须设置宽度 */
|
||||
width: 600upx; /* 或具体像素值 */
|
||||
|
||||
/* 关键属性 */
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2; /* 显示2行 */
|
||||
overflow: hidden;
|
||||
|
||||
/* 文本溢出省略号 */
|
||||
text-overflow: ellipsis;
|
||||
|
||||
/* 可选:防止行高度,确保计算准确 */
|
||||
line-height: 1.5;
|
||||
word-break: break-all; /* 允许在单词内换行 */
|
||||
word-wrap: break-word; /* 允许长单词或URL换行 */
|
||||
}
|
||||
}
|
||||
.ul {
|
||||
.li {
|
||||
color: #999;
|
||||
font-size: 28upx;
|
||||
padding: 8upx 0;
|
||||
.t {
|
||||
color: #999;
|
||||
font-size: 28upx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
338
distribution/index.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<view>
|
||||
<up-navbar
|
||||
bg-color="transparent"
|
||||
title="分销中心"
|
||||
@leftClick="back"
|
||||
:fixed="true"
|
||||
></up-navbar>
|
||||
<view class="top">
|
||||
<image class="top_bg" src="/distribution/static/top_bg.png"></image>
|
||||
<view class="top_content">
|
||||
<view class="u-flex justify-between">
|
||||
<view>
|
||||
<view class="u-flex">
|
||||
<text class="font-12 color-666 u-m-r-6" @click="toShouyiDetail('')"
|
||||
>总收益</text
|
||||
>
|
||||
<up-icon
|
||||
name="question-circle"
|
||||
size="12"
|
||||
color="#666"
|
||||
@click="questionClick('总收益')"
|
||||
></up-icon>
|
||||
</view>
|
||||
<view class="price" @click="toShouyiDetail('')">{{
|
||||
state.totalIncome
|
||||
}}</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="u-flex">
|
||||
<text class="font-12 color-666 u-m-r-6" @click="toShouyiDetail('待入账')"
|
||||
>待入账</text
|
||||
>
|
||||
<up-icon
|
||||
name="question-circle"
|
||||
size="12"
|
||||
color="#666"
|
||||
@click="questionClick('待入账')"
|
||||
></up-icon>
|
||||
</view>
|
||||
<view class="price" @click="toShouyiDetail('待入账')">{{
|
||||
state.pendingIncome
|
||||
}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex justify-between u-m-t-16">
|
||||
<view>
|
||||
<view class="u-flex">
|
||||
<text class="font-12 color-666 u-m-r-6" @click="toTixian"
|
||||
>可提现金额</text
|
||||
>
|
||||
</view>
|
||||
<view class="u-flex" style="align-items: baseline">
|
||||
<text class="price">{{ state.cashOutAmount }}</text>
|
||||
<view class="u-flex" @click="toTixian">
|
||||
<text class="font-12 color-666 u-m-r-6 u-m-l-6">去提现</text>
|
||||
<up-icon
|
||||
name="arrow-right"
|
||||
size="12"
|
||||
color="#666"
|
||||
@click="toTixian"
|
||||
></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<view class="u-flex">
|
||||
<view class="title">我的分销</view>
|
||||
</view>
|
||||
<view class="u-m-t-36 small-title"
|
||||
>已成为{{ state.activates.totalRow }}家店铺的分销员</view
|
||||
>
|
||||
<view class="list">
|
||||
<view
|
||||
v-for="(item, index) in state.activates.records"
|
||||
@click="toShopDetail(item, 'activates')"
|
||||
:key="index"
|
||||
class="shop-item"
|
||||
>
|
||||
<up-image
|
||||
width="104rpx"
|
||||
height="104rpx"
|
||||
radius="8rpx"
|
||||
:src="item.coverImg"
|
||||
></up-image>
|
||||
<view class="u-flex-1 u-m-l-14">
|
||||
<view class="u-flex justify-between">
|
||||
<view>
|
||||
<view class="shop-name">{{ item.shopName }}</view>
|
||||
<view class="address u-line-1 u-m-t-16">{{
|
||||
item.shopAddress
|
||||
}}</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="shouyi">收益</view>
|
||||
<view class="price">¥{{ item.income || "0.00" }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="state.activates.totalRow > 0"
|
||||
class="u-flex justify-center font-12 color-666"
|
||||
style="align-items: baseline"
|
||||
@click="toShopList('activates')"
|
||||
>
|
||||
<view>查看全部店铺</view>
|
||||
<up-icon name="arrow-right" size="12" color="#666"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="u-flex u-m-t-32">
|
||||
<view class="title">更多店铺解锁</view>
|
||||
</view>
|
||||
<view class="list">
|
||||
<view
|
||||
v-for="(item, index) in state.unActivates.records"
|
||||
:key="index"
|
||||
class="shop-item"
|
||||
@click="toShopDetail(item, 'unActivates')"
|
||||
>
|
||||
<up-image
|
||||
width="104rpx"
|
||||
height="104rpx"
|
||||
radius="8rpx"
|
||||
:src="item.coverImg"
|
||||
></up-image>
|
||||
<view class="u-flex-1 u-m-l-14">
|
||||
<view class="u-flex justify-between align-center">
|
||||
<view>
|
||||
<view class="shop-name">{{ item.shopName }}</view>
|
||||
<view class="u-flex">
|
||||
<view class="tag" v-if="item.labelContent">{{
|
||||
item.labelContent
|
||||
}}</view>
|
||||
</view>
|
||||
<view class="address u-line-1">{{ item.shopAddress }}</view>
|
||||
</view>
|
||||
<view class="u-flex u-flex-col justify-center">
|
||||
<view class="fufei" v-if="item.openType == 'pay'"
|
||||
>付费开通</view
|
||||
>
|
||||
<view class="fufei" v-else-if="item.openType == 'manual'"
|
||||
>手动开通</view
|
||||
>
|
||||
<template v-else-if="item.openType == 'auto'">
|
||||
<view class="font-12 color-333 font-700">自动开通</view>
|
||||
<view class="u-m-t-8 color-666 font-12"
|
||||
>还差{{
|
||||
item.shopInviteCount - item.userInviteCount
|
||||
}}人开通</view
|
||||
>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="state.activates.totalRow > 0"
|
||||
class="u-flex justify-center font-12 color-666"
|
||||
style="align-items: baseline"
|
||||
@click="toShopList('unActivates')"
|
||||
>
|
||||
<view>查看全部店铺</view>
|
||||
<up-icon name="arrow-right" size="12" color="#666"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<TipsPopup v-model="showPopup" :tips-type="tipsType"></TipsPopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from "vue";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import TipsPopup from "./components/tips-popup.vue";
|
||||
import * as distributionApi from "@/common/api/market/distribution.js";
|
||||
const showPopup = ref(false);
|
||||
const popupText = ref("");
|
||||
const tipsType = ref("");
|
||||
function questionClick(title) {
|
||||
if (title == "总收益") {
|
||||
tipsType.value = "总收益";
|
||||
}
|
||||
if (title == "待入账") {
|
||||
tipsType.value = "待入账";
|
||||
}
|
||||
showPopup.value = true;
|
||||
}
|
||||
function back() {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
}
|
||||
|
||||
function toShouyiDetail(name) {
|
||||
uni.navigateTo({
|
||||
url: "/distribution/income-details/index?name="+name,
|
||||
});
|
||||
}
|
||||
|
||||
function toShopDetail(item, type) {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
"/distribution/shop-detail/index?shopId=" + item.shopId + "&type=" + type,
|
||||
});
|
||||
}
|
||||
|
||||
function toTixian() {
|
||||
uni.navigateTo({
|
||||
url: "/distribution/withdraw/index",
|
||||
});
|
||||
}
|
||||
function toShopList(type) {
|
||||
uni.navigateTo({
|
||||
url: "/distribution/shop-list/index?type=" + type,
|
||||
});
|
||||
}
|
||||
const state = reactive({
|
||||
totalIncome: 0,
|
||||
pendingIncome: 0,
|
||||
cashOutAmount: 0,
|
||||
totalIncome: 0,
|
||||
activates: {
|
||||
totalRow: 0,
|
||||
records: [],
|
||||
},
|
||||
unActivates: {
|
||||
totalRow: 0,
|
||||
records: [],
|
||||
},
|
||||
});
|
||||
async function init() {
|
||||
const res = await distributionApi.centerUser();
|
||||
if (res) {
|
||||
Object.assign(state, res);
|
||||
}
|
||||
}
|
||||
onShow(init);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.list {
|
||||
.shop-item {
|
||||
padding: 16rpx 0;
|
||||
border-top: 2rpx solid #ededed;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
.fufei {
|
||||
color: #e8ad7b;
|
||||
}
|
||||
.tag {
|
||||
font-size: 24rpx;
|
||||
color: #ff1c1c;
|
||||
background-color: #ffe4e4;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.shop-name {
|
||||
}
|
||||
.address {
|
||||
max-width: 390rpx;
|
||||
}
|
||||
.shouyi {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
.price {
|
||||
font-size: 20rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price {
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
font-size: 40rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.top {
|
||||
position: relative;
|
||||
.top_content {
|
||||
background-color: #faf7f4;
|
||||
position: absolute;
|
||||
left: 28rpx;
|
||||
right: 28rpx;
|
||||
bottom: 0;
|
||||
padding: 32rpx 106rpx 32rpx 56rpx;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
}
|
||||
}
|
||||
.top_bg {
|
||||
width: 100%;
|
||||
height: 530rpx;
|
||||
}
|
||||
.bottom {
|
||||
padding: 34rpx 28rpx;
|
||||
}
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 4rpx;
|
||||
background-color: #9ee708;
|
||||
border-radius: 10rpx;
|
||||
z-index: -1;
|
||||
width: 94.2rpx;
|
||||
height: 13.98rpx;
|
||||
flex-shrink: 0;
|
||||
stroke-width: 4rpx;
|
||||
stroke: #9ee708d6;
|
||||
}
|
||||
}
|
||||
.small-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
279
distribution/poster.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<view class="poster-page">
|
||||
<view class="preview-container" v-if="posterUrl">
|
||||
<image :src="posterUrl" mode="widthFix" class="poster-img"></image>
|
||||
<button class="save-btn" @click="saveToAlbum">保存到相册</button>
|
||||
</view>
|
||||
<button class="generate-btn" @click="generatePoster" v-if="!posterUrl">生成海报</button>
|
||||
<canvas
|
||||
id="posterCanvas"
|
||||
type="2d"
|
||||
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
||||
class="canvas-hidden"
|
||||
></canvas>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, getCurrentInstance } from 'vue'
|
||||
import { onReady } from '@dcloudio/uni-app'
|
||||
|
||||
// 基础变量(不变)
|
||||
const canvasWidth = ref(375)
|
||||
const canvasHeight = ref(667)
|
||||
const posterUrl = ref('')
|
||||
let canvasNode = null
|
||||
let ctx = null
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// 海报配置(不变)
|
||||
const posterConfig = {
|
||||
bgImage: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/677c4a5ae43a45eb98c0ae1a6d242021.png',
|
||||
title: { text: '限时优惠活动', x: 20, y: 40, fontSize: 24, color: '#fff', fontWeight: 'bold' },
|
||||
desc: { text: '全场商品满200减50,限时3天', x: 20, y: 80, fontSize: 16, color: '#fff' },
|
||||
mainImage: { url: 'https://img.yzcdn.cn/vant/apple-1.jpg', x: 20, y: 120, width: 335, height: 335, radius: 10 },
|
||||
qrcode: { url: 'https://czg-oss.oss-cn-hangzhou.aliyuncs.com/catering/store/HQ200龙虾仔.JPG', x: 265, y: 500, width: 90, height: 90 },
|
||||
footerText: { text: '扫码立即参与活动', x: 20, y: 550, fontSize: 14, color: '#333' }
|
||||
}
|
||||
|
||||
// 初始化 Canvas(不变)
|
||||
onReady(() => {
|
||||
const query = uni.createSelectorQuery().in(instance)
|
||||
query
|
||||
.select('#posterCanvas')
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
if (!res[0] || !res[0].node) {
|
||||
console.error('未找到 Canvas 节点')
|
||||
uni.showToast({ title: 'Canvas节点初始化失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
canvasNode = res[0].node
|
||||
canvasNode.width = canvasWidth.value
|
||||
canvasNode.height = canvasHeight.value
|
||||
ctx = canvasNode.getContext('2d')
|
||||
if (!ctx) {
|
||||
console.error('获取 2D 上下文失败')
|
||||
uni.showToast({ title: 'Canvas上下文初始化失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 绘制背景(依赖修复后的 drawImage)
|
||||
const drawBackground = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!posterConfig.bgImage) return reject('无背景图')
|
||||
drawImage({
|
||||
url: posterConfig.bgImage,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: canvasWidth.value,
|
||||
height: canvasHeight.value
|
||||
}).then(resolve).catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
// 绘制文字(不变)
|
||||
const drawText = (options) => {
|
||||
const { text, x, y, fontSize, color, fontWeight = 'normal', fontFamily = 'sans-serif' } = options
|
||||
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`
|
||||
ctx.fillStyle = color
|
||||
ctx.fillText(text, x, y)
|
||||
}
|
||||
|
||||
// 绘制圆角矩形(不变)
|
||||
const drawRoundRect = (x, y, width, height, radius) => {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x + radius, y)
|
||||
ctx.arcTo(x + width, y, x + width, y + height, radius)
|
||||
ctx.arcTo(x + width, y + height, x, y + height, radius)
|
||||
ctx.arcTo(x, y + height, x, y, radius)
|
||||
ctx.arcTo(x, y, x + width, y, radius)
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
// ---------------------- 核心修复:drawImage 方法(无需 Image 实例) ----------------------
|
||||
const drawImage = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { url, x, y, width, height, radius = 0 } = options
|
||||
if (!url) return reject('图片路径为空')
|
||||
if (width <= 0 || height <= 0) return reject(`图片尺寸无效:${width}x${height}`)
|
||||
// 关键:确保 canvasNode 已初始化(否则无法创建图片对象)
|
||||
if (!canvasNode) return reject('Canvas 节点未初始化,无法创建图片对象')
|
||||
|
||||
// 1. 获取图片临时路径(不变)
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: (imgInfo) => {
|
||||
if (imgInfo.width <= 0 || imgInfo.height <= 0) {
|
||||
return reject(`无效图片:${url}`)
|
||||
}
|
||||
|
||||
// 2. 关键修复:用 Canvas 节点的 createImage() 方法创建图片对象
|
||||
// 这是微信小程序 Canvas 2D 节点原生支持的方法,类型完全匹配
|
||||
const image = canvasNode.createImage()
|
||||
|
||||
// 3. 监听图片加载完成(接口与标准 Image 一致)
|
||||
image.onload = () => {
|
||||
try {
|
||||
if (radius > 0) {
|
||||
ctx.save()
|
||||
drawRoundRect(x, y, width, height, radius)
|
||||
ctx.clip()
|
||||
}
|
||||
|
||||
// 4. 绘制图片:传入 Canvas 节点创建的图片实例(类型匹配)
|
||||
ctx.drawImage(image, x, y, width, height)
|
||||
|
||||
if (radius > 0) ctx.restore()
|
||||
resolve()
|
||||
} catch (drawErr) {
|
||||
reject(`绘制图片失败:${drawErr.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听加载失败
|
||||
image.onerror = (err) => {
|
||||
reject(`图片加载失败:${err.message || '未知错误'}`)
|
||||
}
|
||||
|
||||
// 5. 赋值临时路径,触发加载
|
||||
image.src = imgInfo.path
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(`获取图片信息失败:${err.errMsg}(路径:${url})`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 生成海报(不变)
|
||||
const generatePoster = async () => {
|
||||
try {
|
||||
if (!canvasNode || !ctx) throw new Error('Canvas 未初始化完成')
|
||||
ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
|
||||
|
||||
// 绘制背景(失败则用纯色兜底)
|
||||
await drawBackground().catch(() => {
|
||||
ctx.fillStyle = '#f5f5f5'
|
||||
ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value)
|
||||
})
|
||||
|
||||
// 绘制文字
|
||||
drawText(posterConfig.title)
|
||||
drawText(posterConfig.desc)
|
||||
drawText(posterConfig.footerText)
|
||||
|
||||
// 绘制图片(主图 + 二维码)
|
||||
await drawImage(posterConfig.mainImage)
|
||||
await drawImage(posterConfig.qrcode)
|
||||
|
||||
// 延迟确保绘制生效
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
await convertToImage()
|
||||
uni.showToast({ title: '海报生成成功', icon: 'success' })
|
||||
} catch (err) {
|
||||
console.error('生成失败:', err)
|
||||
uni.showToast({ title: `生成失败:${err.message}`, icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
// 转图片(不变)
|
||||
const convertToImage = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!canvasNode) return reject('Canvas 节点不存在')
|
||||
uni.canvasToTempFilePath({
|
||||
canvas: canvasNode,
|
||||
width: canvasWidth.value,
|
||||
height: canvasHeight.value,
|
||||
destWidth: canvasWidth.value * 2,
|
||||
destHeight: canvasHeight.value * 2,
|
||||
success: (res) => {
|
||||
posterUrl.value = res.tempFilePath
|
||||
resolve()
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(`转图片失败:${err.errMsg}`)
|
||||
}
|
||||
}, instance)
|
||||
})
|
||||
}
|
||||
|
||||
// 保存相册相关方法(不变)
|
||||
const saveToAlbum = () => {
|
||||
if (!posterUrl.value) return
|
||||
uni.getSetting({
|
||||
success: (res) => {
|
||||
if (!res.authSetting['scope.writePhotosAlbum']) {
|
||||
uni.authorize({
|
||||
scope: 'scope.writePhotosAlbum',
|
||||
success: saveImage,
|
||||
fail: () => {
|
||||
uni.showModal({
|
||||
title: '权限申请',
|
||||
content: '需要相册权限才能保存海报',
|
||||
success: (modalRes) => modalRes.confirm && uni.openSetting()
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
saveImage()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const saveImage = () => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: posterUrl.value,
|
||||
success: () => uni.showToast({ title: '保存成功', icon: 'success' }),
|
||||
fail: (err) => {
|
||||
console.error('保存失败:', err)
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式不变 */
|
||||
.poster-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
}
|
||||
.canvas-hidden {
|
||||
position: absolute;
|
||||
left: -9999rpx;
|
||||
top: -9999rpx;
|
||||
z-index: -1;
|
||||
width: 375px;
|
||||
height: 667px;
|
||||
}
|
||||
.preview-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.poster-img {
|
||||
width: 100%;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.generate-btn, .save-btn {
|
||||
width: 600rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
margin-top: 40rpx;
|
||||
background-color: #07c160;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
.save-btn {
|
||||
background-color: #1677ff;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
132
distribution/shiming/index.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<view class="min-h-100vh bg-gray">
|
||||
<view class="box">
|
||||
<view class="u-flex">
|
||||
<view class="title">姓名</view>
|
||||
<view class="u-flex-1 input-box">
|
||||
<input
|
||||
type="text"
|
||||
v-model="form.realName"
|
||||
class="input"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex u-m-t-32">
|
||||
<view class="title">身份证号</view>
|
||||
<view class="u-flex-1 input-box">
|
||||
<input
|
||||
type="text"
|
||||
v-model="form.idCard"
|
||||
class="input"
|
||||
placeholder="请输入身份证号"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips">请输入和当前微信账号为同一实名,否则将会提现失败</view>
|
||||
</view>
|
||||
<view class="u-m-t-60 u-p-l-28 u-p-r-28">
|
||||
<view class="submit" @click="submit">提交</view>
|
||||
<view class="cancel" @click="cancel">取消</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import { realNameAuth } from "@/common/api/market/distribution";
|
||||
import { ref, reactive } from "vue";
|
||||
import {validateName,validateIdCard} from "@/utils/util.js"
|
||||
const form = reactive({
|
||||
realName: "",
|
||||
idCard: "",
|
||||
});
|
||||
const userInfo = ref(uni.cache.get("userInfo") || {});
|
||||
form.realName=userInfo.value.realName
|
||||
form.idCard=userInfo.value.idCard
|
||||
|
||||
async function submit() {
|
||||
const validNameResult = validateName(form.realName)
|
||||
if(!validNameResult.valid){
|
||||
return uni.showToast({
|
||||
title: validNameResult.msg,
|
||||
icon: "none",
|
||||
duration: 1500,
|
||||
})
|
||||
}
|
||||
const validIdCardResult = validateIdCard(form.idCard)
|
||||
if(!validIdCardResult.valid){
|
||||
return uni.showToast({
|
||||
title: validIdCardResult.msg,
|
||||
icon: "none",
|
||||
duration: 1500,
|
||||
})
|
||||
}
|
||||
|
||||
const res = await realNameAuth({ ...form, id: userInfo.value.id });
|
||||
if (res) {
|
||||
uni.showToast({
|
||||
title: "修改成功",
|
||||
icon: "none",
|
||||
duration: 1500,
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
function cancel() {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.min-h-100vh {
|
||||
padding: 32rpx 28rpx;
|
||||
}
|
||||
.tips {
|
||||
font-size: 24rpx;
|
||||
line-height: 40rpx;
|
||||
color: #ff1c1c;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
.box {
|
||||
background-color: #fff;
|
||||
padding: 32rpx 28rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
color: #333;
|
||||
.title {
|
||||
min-width: 160rpx;
|
||||
}
|
||||
.input-box {
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
.input {
|
||||
}
|
||||
}
|
||||
}
|
||||
.submit {
|
||||
background: #ffd158;
|
||||
border-radius: 100rpx;
|
||||
padding: 22rpx 288rpx;
|
||||
font-size: 32rpx;
|
||||
line-height: 46rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cancel {
|
||||
color: #999999;
|
||||
white-space: nowrap;
|
||||
|
||||
border-radius: 100rpx;
|
||||
padding: 22rpx 288rpx;
|
||||
font-size: 32rpx;
|
||||
line-height: 46rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
119
distribution/shop-detail/components/bind-shangji.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<up-popup
|
||||
:show="show"
|
||||
bgColor="transparent"
|
||||
:safeAreaInsetBottom="false"
|
||||
:closeOnClickOverlay="false"
|
||||
@close="close"
|
||||
mode="center"
|
||||
>
|
||||
<view class="box">
|
||||
<view class="u-flex top justify-between u-p-32">
|
||||
<text class="title">绑定上级</text>
|
||||
<up-icon name="close" @click="close"></up-icon>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="u-flex u-col-center">
|
||||
<view class="color-333 small-title">上级邀请码</view>
|
||||
<view class="u-m-l-32 u-flex-1 border">
|
||||
<input placeholder="请输入上级邀请码" v-model="code" />
|
||||
</view>
|
||||
<image src="/distribution/static/scan.svg" class="u-m-l-10" style="width: 60rpx; height: 60rpx;" @click="saoma"></image>
|
||||
</view>
|
||||
|
||||
<view class="u-m-t-32 u-flex u-col-center" style="gap: 54rpx">
|
||||
<view class="cancel" @click="close">取消</view>
|
||||
<view class="confirm" @click="confirm">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
|
||||
const show = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const code = ref("");
|
||||
const emits = defineEmits(["cancel", "confirm"]);
|
||||
function close() {
|
||||
show.value = false;
|
||||
emits("cancel");
|
||||
}
|
||||
function confirm() {
|
||||
if (code.value == "") {
|
||||
uni.showToast({
|
||||
title: "请输入上级邀请码",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
show.value = false;
|
||||
emits("confirm", code.value);
|
||||
}
|
||||
function saoma(){
|
||||
uni.scanCode({
|
||||
success: (res) => {
|
||||
code.value = res.result;
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.border {
|
||||
border: 2rpx solid #d9d9d9;
|
||||
padding: 18rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
.box {
|
||||
width: 586rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
.title {
|
||||
color: #000000;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.top {
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
}
|
||||
.info {
|
||||
padding: 32rpx 40rpx 40rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.small-title {
|
||||
min-width: 84rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel {
|
||||
padding: 14rpx 76rpx;
|
||||
border-radius: 36rpx;
|
||||
border: 2rpx solid #e8ad7b;
|
||||
color: #e8ad7b;
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
.confirm {
|
||||
padding: 14rpx 76rpx;
|
||||
border-radius: 36rpx;
|
||||
background-color: #e8ad7b;
|
||||
border: 2rpx solid #e8ad7b;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
line-height: 48rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
184
distribution/shop-detail/components/share-popup.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<view class="w-qrcode">
|
||||
<w-qrcode
|
||||
:options="codeOptions"
|
||||
:opacity="0"
|
||||
ref="wQrcode"
|
||||
@generate="(e) => qrcodeResult(e)"
|
||||
></w-qrcode>
|
||||
</view>
|
||||
|
||||
<up-popup
|
||||
:show="show"
|
||||
bgColor="transparent"
|
||||
:safeAreaInsetBottom="false"
|
||||
:closeOnClickOverlay="true"
|
||||
@close="close"
|
||||
mode="center"
|
||||
>
|
||||
<view class="box">
|
||||
<view class="info">
|
||||
<view class="u-flex justify-center">
|
||||
<up-avatar size="214rpx" :src="shopUserInfo.headImg"></up-avatar>
|
||||
</view>
|
||||
<view
|
||||
class="u-m-t-48 font-14 font-700 color-333 text-center line-height-54"
|
||||
>
|
||||
<view>{{ shopUserInfo.nickName }} </view>
|
||||
<view>{{ desensitizePhone(shopUserInfo.phone) }}</view>
|
||||
</view>
|
||||
<view class="u-m-t-16 font-14 line-height-54 text-center">
|
||||
<text class="color-666">邀请码</text>
|
||||
<text class="u-m-l-16 u-m-r-16 color-333 font-16 font-700">{{
|
||||
inviteCode
|
||||
}}</text>
|
||||
<text class="" style="color: #fe6d11" @click="copyCode">复制</text>
|
||||
</view>
|
||||
<view class="u-flex justify-center" style="margin-top: 90rpx">
|
||||
<!-- <w-qrcode
|
||||
:options="codeOptions"
|
||||
:opacity="1"
|
||||
ref="wQrcode"
|
||||
@generate="(e) => qrcodeResult(e)"
|
||||
></w-qrcode> -->
|
||||
<up-image width="322rpx" height="322rpx" :src="code"></up-image>
|
||||
</view>
|
||||
|
||||
<view class="u-m-t-60 u-flex u-col-center justify-center">
|
||||
<view class="confirm" @click="save">保存图片</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import wQrcode from "@/uni_modules/wmf-code/components/w-qrcode/w-qrcode.vue";
|
||||
import {desensitizePhone} from "@/utils/util.js";
|
||||
import { ref } from "vue";
|
||||
const props = defineProps({
|
||||
inviteCode: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
shopUserInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const codeOptions = ref({
|
||||
size: 200,
|
||||
code: props.inviteCode,
|
||||
});
|
||||
function copyCode() {
|
||||
uni.setClipboardData({
|
||||
data: props.inviteCode,
|
||||
success: function () {
|
||||
console.log("success");
|
||||
},
|
||||
});
|
||||
}
|
||||
const code = ref("");
|
||||
|
||||
function qrcodeResult(e) {
|
||||
console.log("qrcodeResult", e);
|
||||
code.value = e.img.tempFilePath;
|
||||
console.log("code", code.value);
|
||||
}
|
||||
|
||||
const show = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const emits = defineEmits(["cancel", "confirm"]);
|
||||
function close() {
|
||||
show.value = false;
|
||||
emits("cancel");
|
||||
}
|
||||
function save() {
|
||||
show.value = false;
|
||||
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: code.value,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: "保存成功",
|
||||
});
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: "保存失败",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
emits("confirm", code.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.info .canvas) {
|
||||
opacity: 1;
|
||||
}
|
||||
.border {
|
||||
border: 2rpx solid #d9d9d9;
|
||||
padding: 18rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
.box {
|
||||
width: 638rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
.title {
|
||||
color: #000000;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.top {
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
}
|
||||
.info {
|
||||
padding: 96rpx 40rpx 96rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.small-title {
|
||||
min-width: 84rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel {
|
||||
padding: 14rpx 76rpx;
|
||||
border-radius: 36rpx;
|
||||
border: 2rpx solid #e8ad7b;
|
||||
color: #e8ad7b;
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
.confirm {
|
||||
padding: 14rpx 76rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: #e8ad7b;
|
||||
border: 2rpx solid #e8ad7b;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
line-height: 48rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.line-height-54 {
|
||||
line-height: 54rpx;
|
||||
}
|
||||
.w-qrcode {
|
||||
position: fixed;
|
||||
left: -9999px;
|
||||
top: -9999px;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
||||
905
distribution/shop-detail/index.vue
Normal file
@@ -0,0 +1,905 @@
|
||||
<template>
|
||||
<view class="min-h-100vh bg-gray">
|
||||
<up-navbar
|
||||
bg-color="transparent"
|
||||
title="分销中心"
|
||||
@leftClick="back"
|
||||
:fixed="true"
|
||||
></up-navbar>
|
||||
<view class="top">
|
||||
<image
|
||||
class="top_bg"
|
||||
src="/distribution/static/top_bg.png"
|
||||
:style="imageStyle"
|
||||
></image>
|
||||
<view class="box" :class="{ type1: isActivated }">
|
||||
<view class="u-flex align-center justify-between">
|
||||
<view class="u-flex align-center">
|
||||
<!-- <up-avatar size="62rpx" /> -->
|
||||
<text class="u-m-l-14 font-14 color-333 font-700">{{
|
||||
state.shopName
|
||||
}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<template v-if="state.parentPhone">
|
||||
<view class="font-12 color-666">
|
||||
上级:{{ state.parentName }}{{ state.parentPhone }}
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="!state.parentPhone">
|
||||
<view class="bind" @click="showBindShangji = true">
|
||||
绑定上级
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="top_content type1 u-m-t-32"
|
||||
v-if="state.distributionUser && state.distributionUser.level"
|
||||
>
|
||||
<view class="font-14">
|
||||
<view class="u-flex align-center">
|
||||
<text class="color-666">我的分销等级:</text>
|
||||
<text
|
||||
class="color-333 font-700 u-m-r-6"
|
||||
v-if="state.distributionUser"
|
||||
>{{ state.distributionUser.level }}级
|
||||
{{ state.distributionUser.levelName }}</text
|
||||
>
|
||||
<up-icon
|
||||
name="question-circle"
|
||||
size="24rpx"
|
||||
color="#666"
|
||||
@click="questionClick('等级升级条件')"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="u-m-t-28 u-flex align-center"
|
||||
v-if="
|
||||
juNextLvMoney &&
|
||||
state.distributionUser &&
|
||||
state.distributionUser.isAssignLevel == 0
|
||||
"
|
||||
>
|
||||
<text class="color-666">距离下一级还差:</text>
|
||||
<text class="color-333 font-700 u-m-r-18"
|
||||
>{{ juNextLvMoney }}
|
||||
</text>
|
||||
<text class="color-333 font-700 u-m-r-18"
|
||||
>{{ config.upgradeType == "cost" ? "元" : "人" }}
|
||||
</text>
|
||||
<!-- <up-icon
|
||||
name="question-circle"
|
||||
size="24rpx"
|
||||
color="#666"
|
||||
@click="showRule = true"
|
||||
/> -->
|
||||
</view>
|
||||
<view class="u-flex u-m-t-28">
|
||||
<view class="u-flex-1">
|
||||
<view class="u-flex align-center">
|
||||
<text
|
||||
class="u-m-r-10 font-12 color-666"
|
||||
@click="toShouyiDetail('')"
|
||||
>总收益</text
|
||||
>
|
||||
<up-icon
|
||||
name="question-circle"
|
||||
size="24rpx"
|
||||
color="#666"
|
||||
@click="questionClick('总收益')"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="u-m-t-16 price"
|
||||
@click="toShouyiDetail('')"
|
||||
v-if="state.distributionUser"
|
||||
>{{ state.distributionUser.totalIncome }}</view
|
||||
>
|
||||
</view>
|
||||
<view class="u-flex-1">
|
||||
<view class="u-flex align-center">
|
||||
<text
|
||||
class="u-m-r-10 font-12 color-666"
|
||||
@click="toShouyiDetail('待入账')"
|
||||
>待入账</text
|
||||
>
|
||||
<up-icon
|
||||
name="question-circle"
|
||||
size="24rpx"
|
||||
color="#666"
|
||||
@click="questionClick('待入账')"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="u-m-t-16 price"
|
||||
@click="toShouyiDetail('待入账')"
|
||||
v-if="state.distributionUser"
|
||||
>{{ state.distributionUser.pendingIncome }}</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view class="top_content u-m-t-32" v-if="config">
|
||||
<template v-if="config.openType == 'auto'">
|
||||
<view class="color-333 font-16 font-700"> 如何成为分销员 </view>
|
||||
<view class="u-m-t-16 color-666 font-14">
|
||||
<view> 需要邀请人数:{{ config.inviteCount }}人</view>
|
||||
<view
|
||||
>是否需要邀请人数下单:{{
|
||||
config.inviteConsume ? "是" : "否"
|
||||
}}
|
||||
</view>
|
||||
<view
|
||||
>每人可获得的分销奖励次数:{{
|
||||
config.rewardCount == -1
|
||||
? "永久"
|
||||
: config.rewardCount + "次"
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="config.openType == 'pay'">
|
||||
<view class="color-333 font-16 font-700 text-center">
|
||||
如何成为分销员
|
||||
</view>
|
||||
<view class="u-m-t-16 color-666 font-14 text-center">
|
||||
<view
|
||||
>只需付费{{ config.payAmount || "" }}元,即可成为分销员</view
|
||||
>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="config.openType == 'manual'">
|
||||
<view class="color-333 font-16 font-700 text-center">
|
||||
如何成为分销员
|
||||
</view>
|
||||
<view class="u-m-t-16 color-666 font-14 text-center">
|
||||
<view>请联系商家咨询详情</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bottom type1" v-if="isActivated">
|
||||
<view class="u-flex justify-between align-center">
|
||||
<view class="u-flex align-center">
|
||||
<view class="color-333 font-16 u-m-r-6"
|
||||
>我的邀请({{ inviteUserRes.totalRow }})</view
|
||||
>
|
||||
<up-icon
|
||||
name="question-circle"
|
||||
size="24rpx"
|
||||
@click="showRule = true"
|
||||
color="#666"
|
||||
/>
|
||||
</view>
|
||||
<view class="font-10 color-999 u-flex align-center">
|
||||
<text
|
||||
>分成比例{{ state.distributionUser.levelOneCommission || 0 }}%</text
|
||||
>
|
||||
<up-icon
|
||||
name="question-circle"
|
||||
size="24rpx"
|
||||
color="#666"
|
||||
@click="questionClick('等级分成比例')"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tabs" v-if="false">
|
||||
<view
|
||||
class="tabs-item"
|
||||
:class="{ active: activeTab == 'distributor' }"
|
||||
@click="activeTab = 'distributor'"
|
||||
>分销员</view
|
||||
>
|
||||
<view
|
||||
class="tabs-item"
|
||||
:class="{ active: activeTab == 'inviter' }"
|
||||
@click="activeTab = 'inviter'"
|
||||
>邀请人</view
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="u-m-t-48 font-14">
|
||||
<view class="u-flex justify-between color-333">
|
||||
<view>用户</view>
|
||||
<view>获得利益(元)</view>
|
||||
<view>邀请时间</view>
|
||||
</view>
|
||||
<view class="u-m-t-16">
|
||||
<view
|
||||
v-for="(item, index) in userList"
|
||||
:key="index"
|
||||
class="u-flex justify-between align-center recoder-item color-666 font-12"
|
||||
>
|
||||
<view class="">
|
||||
<view class="u-line-1" style="max-width: 160rpx">
|
||||
<text>
|
||||
{{ item.shopUserName }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="u-line-1" style="max-width: 160rpx">
|
||||
<text v-if="item.levelName"> ( {{ item.levelName }}) </text>
|
||||
</view>
|
||||
|
||||
<view>{{ desensitizePhone(item.shopUserPhone) }}</view>
|
||||
</view>
|
||||
<view>
|
||||
<text>{{ item.oneIncome }}</text>
|
||||
<text v-if="item.distributionLevelName"
|
||||
>({{ item.distributionLevelName }})</text
|
||||
>
|
||||
</view>
|
||||
<view>
|
||||
<text v-if="item.inviteTime">{{
|
||||
item.inviteTime.split(" ")[0]
|
||||
}}</text>
|
||||
<text style="color: #fff" v-else>{{ "yyyy-MM-dd" }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view class="bottom" v-if="config">
|
||||
<view class="u-flex">
|
||||
<view class="title">规则说明</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="font-12 color-666 u-m-t-16">
|
||||
<view>
|
||||
<view> 我的收益什么时候可以到账?</view>
|
||||
<view> 分销的结算时长为{{ config.settlementDay || 0 }}天</view>
|
||||
</view>
|
||||
<template
|
||||
v-if="
|
||||
config.upgradeType != 'not_upgrade' &&
|
||||
config.levelConfigList &&
|
||||
config.levelConfigList.length >= 2
|
||||
"
|
||||
>
|
||||
<view class="u-m-t-40">
|
||||
<view>怎么样才能升级分销员等级?</view>
|
||||
|
||||
<template v-if="config.upgradeType == 'invite' && nextLvMoney">
|
||||
<view>邀请的有效人数达到{{ nextLvMoney }}人即可升级</view>
|
||||
<view class="u-m-t-40"> 什么是有效邀请人数?</view>
|
||||
<view> 被邀请人在店铺消费过,即有一笔订单完成才算有效</view>
|
||||
</template>
|
||||
<template v-if="config.upgradeType == 'cost' && nextLvMoney">
|
||||
<view> 消费金额总计达到{{ nextLvMoney }}元即可升级</view>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<view>请联系商家</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="u-m-t-40" v-if="config.upgradeType == 'cost'">
|
||||
<view>消费金额如何计算?</view>
|
||||
<view
|
||||
>消费金额是计算您和您邀请的人在店铺消费的总金额,但退款订单不计入</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="parse-html">
|
||||
<up-parse :content="content"></up-parse>
|
||||
</view>
|
||||
<view style="height: 240rpx"></view>
|
||||
</template>
|
||||
|
||||
<view
|
||||
class="tips u-m-t-32"
|
||||
v-if="state.distributionUser && state.distributionUser.status"
|
||||
>您的分销员身份已取消,不再获得分成有疑问可联系商家</view
|
||||
>
|
||||
|
||||
<view class="u-flex justify-center bottom-btn" v-if="showInviteCode">
|
||||
<view class="copy" @click="copyCode">
|
||||
<view>复制邀请码</view>
|
||||
<view v-if="inviteCode">{{ inviteCode }}</view>
|
||||
</view>
|
||||
<view class="u-flex u-flex-col justify-center">
|
||||
<view class="share" @click="showSharePopup = true">分享邀请</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="buy"
|
||||
v-if="config.openType == 'pay' && config.payAmount && !isActivated"
|
||||
@click="buy"
|
||||
>
|
||||
付费{{ config.payAmount }}元开通
|
||||
</view>
|
||||
|
||||
<bindShangji
|
||||
v-model="showBindShangji"
|
||||
@confirm="confirmBindShangji"
|
||||
></bindShangji>
|
||||
<sharePopup
|
||||
v-model="showSharePopup"
|
||||
v-if="
|
||||
(state.distributionUser && state.distributionUser.inviteCode) ||
|
||||
inviteCode
|
||||
"
|
||||
:inviteCode="
|
||||
state.distributionUser ? state.distributionUser.inviteCode : inviteCode
|
||||
"
|
||||
:shopUserInfo="shopUserInfo"
|
||||
></sharePopup>
|
||||
|
||||
<TipsPopup v-model="showPopup" :tips-type="tipsType"></TipsPopup>
|
||||
<commissionPopup
|
||||
:tipsType="commissionTipsType"
|
||||
v-model="showCommission"
|
||||
:config="config"
|
||||
:levelConfigList="config.levelConfigList || []"
|
||||
></commissionPopup>
|
||||
<rulePopup
|
||||
v-model="showRule"
|
||||
:config="config"
|
||||
:distributionUser="state.distributionUser"
|
||||
></rulePopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import bindShangji from "./components/bind-shangji.vue";
|
||||
import sharePopup from "./components/share-popup.vue";
|
||||
import TipsPopup from "../components/tips-popup.vue";
|
||||
import commissionPopup from "../components/commission.vue";
|
||||
import rulePopup from "../components/rule.vue";
|
||||
import { desensitizePhone } from "@/utils/util.js";
|
||||
import BigNumber from "bignumber.js";
|
||||
import * as distributionApi from "@/common/api/market/distribution.js";
|
||||
import { distributionLtPayOrder } from "@/common/api/order/index.js";
|
||||
import { APIshopUserInfo } from "@/common/api/member.js";
|
||||
import { pay } from "@/utils/pay.js";
|
||||
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
const showBindShangji = ref(false);
|
||||
const showSharePopup = ref(false);
|
||||
import { ref, reactive, computed, watch } from "vue";
|
||||
const content = ref("");
|
||||
const showPopup = ref(false);
|
||||
const showRule = ref(false);
|
||||
const showCommission = ref(false);
|
||||
const tipsType = ref("");
|
||||
const commissionTipsType = ref("");
|
||||
|
||||
function toShouyiDetail(name) {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
"/distribution/income-details/index?name=" +
|
||||
name +
|
||||
"&shopId=" +
|
||||
options.shopId,
|
||||
});
|
||||
}
|
||||
function questionClick(title) {
|
||||
if (title == "总收益") {
|
||||
tipsType.value = "总收益";
|
||||
showPopup.value = true;
|
||||
}
|
||||
if (title == "待入账") {
|
||||
tipsType.value = "待入账";
|
||||
showPopup.value = true;
|
||||
}
|
||||
if (title == "等级分成比例") {
|
||||
commissionTipsType.value = "等级分成比例";
|
||||
showCommission.value = true;
|
||||
}
|
||||
if (title == "等级升级条件") {
|
||||
commissionTipsType.value = "等级升级条件";
|
||||
showCommission.value = true;
|
||||
}
|
||||
}
|
||||
function back() {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmBindShangji(code) {
|
||||
const res = await distributionApi.bindInviteUser({
|
||||
shopId: options.shopId,
|
||||
inviteCode: code,
|
||||
id: shopUserInfo.value.id,
|
||||
});
|
||||
if (res) {
|
||||
uni.showToast({
|
||||
title: "绑定成功",
|
||||
icon: "none",
|
||||
});
|
||||
setTimeout(() => {
|
||||
init();
|
||||
}, 1500);
|
||||
}
|
||||
// else {
|
||||
// uni.showToast({
|
||||
// title: "绑定失败",
|
||||
// icon: "none",
|
||||
// });
|
||||
// }
|
||||
}
|
||||
function copyCode() {
|
||||
uni.setClipboardData({
|
||||
data: inviteCode.value,
|
||||
success: function () {
|
||||
console.log("success");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const shopUserInfo = ref();
|
||||
const config = reactive({});
|
||||
//邀请码
|
||||
const inviteCode = ref("");
|
||||
async function init() {
|
||||
const shopUserRes = await APIshopUserInfo({
|
||||
shopId: options.shopId,
|
||||
});
|
||||
|
||||
const configRes = await distributionApi.getConfig({
|
||||
shopId: options.shopId,
|
||||
});
|
||||
Object.assign(config, configRes);
|
||||
|
||||
if (shopUserRes) {
|
||||
shopUserInfo.value = shopUserRes;
|
||||
}
|
||||
if (configRes && configRes.openType == "auto") {
|
||||
const codeRes = await distributionApi.getInviteCode({
|
||||
shopId: options.shopId,
|
||||
shopUserId: shopUserRes.id,
|
||||
});
|
||||
if (codeRes) {
|
||||
inviteCode.value = codeRes;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await distributionApi.centerConfig({
|
||||
shopId: options.shopId,
|
||||
});
|
||||
if (res) {
|
||||
if (res.distributionId) {
|
||||
options.type = "activates";
|
||||
}
|
||||
Object.assign(state, res);
|
||||
if (res.distributionUser) {
|
||||
inviteCode.value = res.distributionUser.inviteCode;
|
||||
}
|
||||
content.value = res.config ? res.config.notActivatedPage : "";
|
||||
}
|
||||
}
|
||||
const options = reactive({ type: "" });
|
||||
const imageStyle = computed(() => {
|
||||
return {
|
||||
height: isActivated.value ? "580rpx" : "580rpx",
|
||||
};
|
||||
});
|
||||
|
||||
async function buy() {
|
||||
const res = await distributionLtPayOrder({
|
||||
shopId: options.shopId,
|
||||
userId: uni.cache.get("userInfo") ? uni.cache.get("userInfo").id : "",
|
||||
returnUrl: "",
|
||||
buyerRemark: "",
|
||||
amount: "",
|
||||
remark: "",
|
||||
code: "",
|
||||
});
|
||||
const payRes = await pay(res);
|
||||
if (payRes) {
|
||||
uni.showToast({
|
||||
title: "购买成功",
|
||||
icon: "none",
|
||||
});
|
||||
setTimeout(() => {
|
||||
init();
|
||||
getRecoders();
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
parentPhone: "",
|
||||
parentName: "",
|
||||
shopName: "",
|
||||
});
|
||||
|
||||
const query = reactive({
|
||||
page: 1,
|
||||
size: 10,
|
||||
});
|
||||
const isEnd = ref(false);
|
||||
const activeTab = ref("inviter");
|
||||
const userList = ref([]);
|
||||
|
||||
const inviteUserRes = reactive({
|
||||
records: [],
|
||||
totalRow: 0,
|
||||
totalPage: 0,
|
||||
});
|
||||
async function getRecoders() {
|
||||
if (state.config) return;
|
||||
const ajaxQuery = {
|
||||
...query,
|
||||
shopId: options.shopId,
|
||||
};
|
||||
if (activeTab.value == "distributor") {
|
||||
ajaxQuery.parentId = state.distributionUser.distributionId;
|
||||
} else {
|
||||
ajaxQuery.id = state.distributionUser.distributionId;
|
||||
}
|
||||
const res =
|
||||
activeTab.value == "distributor"
|
||||
? await distributionApi.childUser(ajaxQuery)
|
||||
: await distributionApi.inviteUser(ajaxQuery);
|
||||
if (res) {
|
||||
Object.assign(inviteUserRes, res);
|
||||
if (query.page == 1) {
|
||||
userList.value = res.records || [];
|
||||
} else {
|
||||
userList.value.push(...(res.records || []));
|
||||
}
|
||||
isEnd.value = query.page >= res.totalPage * 1;
|
||||
}
|
||||
}
|
||||
const nextLvMoney = computed(() => {
|
||||
let nextLv = undefined;
|
||||
|
||||
if (!config.levelConfigList || !config.levelConfigList.length) {
|
||||
return "";
|
||||
}
|
||||
const nowLevel = state.distributionUser
|
||||
? state.distributionUser.level || 1
|
||||
: 1;
|
||||
if (!nowLevel) {
|
||||
nextLv = config.levelConfigList[0];
|
||||
} else {
|
||||
nextLv = config.levelConfigList.find((v) => v.level == nowLevel + 1);
|
||||
}
|
||||
|
||||
if (nextLv) {
|
||||
if (config.upgradeType == "cost") {
|
||||
return nextLv.costAmount;
|
||||
}
|
||||
if (config.upgradeType == "invite") {
|
||||
return nextLv.inviteCount;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const juNextLvMoney = computed(() => {
|
||||
if (!nextLvMoney.value) {
|
||||
return "";
|
||||
}
|
||||
let total = 0;
|
||||
if (config.upgradeType == "cost" && state.distributionUser) {
|
||||
total = new BigNumber(nextLvMoney.value).minus(state.distributionUser.consumeAmount || 0).toNumber();
|
||||
}
|
||||
if (config.upgradeType == "invite" && state.distributionUser) {
|
||||
total = new BigNumber(nextLvMoney.value).minus(config.inviteCount || 0).toNumber();
|
||||
}
|
||||
return Math.max(total, 0);
|
||||
});
|
||||
//是否显示邀请码
|
||||
|
||||
const showInviteCode = computed(() => {
|
||||
if (config.upgradeType == "invite") {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
config.openType == "manual" &&
|
||||
(!state.distributionUser || !state.distributionUser.level)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
state.distributionUser &&
|
||||
state.distributionUser.level &&
|
||||
inviteCode.value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (!state.distributionUser && config.openType == "manual") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
//是否已成为分销员
|
||||
const isActivated = computed(() => {
|
||||
return state.distributionUser && state.distributionUser.level;
|
||||
});
|
||||
watch(
|
||||
() => activeTab.value,
|
||||
(newVal, oldVal) => {
|
||||
query.page = 1;
|
||||
isEnd.value = false;
|
||||
if (newVal != oldVal) {
|
||||
getRecoders();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function parseQueryString(queryString) {
|
||||
const queryParams = queryString.split("&").map((param) => param.split("="));
|
||||
const params = {};
|
||||
for (const [key, value] of queryParams) {
|
||||
params[key] = value;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
onLoad(async (opt) => {
|
||||
if (opt.q) {
|
||||
const q = decodeURIComponent(opt.q);
|
||||
const params = parseQueryString(q.split("?")[1]);
|
||||
Object.assign(options, params);
|
||||
} else {
|
||||
Object.assign(options, opt);
|
||||
}
|
||||
console.log(options);
|
||||
await init();
|
||||
getRecoders();
|
||||
});
|
||||
|
||||
onReachBottom(async () => {
|
||||
if (!isEnd.value) {
|
||||
query.page++;
|
||||
await getRecoders();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input-number-box {
|
||||
width: 428rpx;
|
||||
padding-bottom: 10rpx;
|
||||
border-bottom: 1px solid #999;
|
||||
font-size: 28rpx;
|
||||
align-items: baseline;
|
||||
.fuhao {
|
||||
font-size: 64rpx;
|
||||
color: #333;
|
||||
}
|
||||
.input-number {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
padding-left: 24rpx;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
.all-in {
|
||||
font-size: 28rpx;
|
||||
color: #fe7e00;
|
||||
}
|
||||
}
|
||||
.list {
|
||||
.shop-item {
|
||||
padding: 32rpx 28rpx;
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.fufei {
|
||||
color: #e8ad7b;
|
||||
}
|
||||
.tag {
|
||||
font-size: 24rpx;
|
||||
color: #ff1c1c;
|
||||
background-color: #ffe4e4;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.name {
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
}
|
||||
.shouxufei {
|
||||
}
|
||||
.shouyi {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.status {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
text-align: right;
|
||||
color: #333333;
|
||||
&.fail {
|
||||
color: #ff1c1c;
|
||||
}
|
||||
}
|
||||
.lingqu {
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #fe6d11;
|
||||
padding: 8rpx 16rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
.price {
|
||||
font-weight: 700;
|
||||
font-size: 40rpx;
|
||||
color: #333;
|
||||
}
|
||||
.top {
|
||||
position: relative;
|
||||
.box {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 28rpx 28rpx 52rpx 28rpx;
|
||||
&.type1 {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
.top_content {
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
border-radius: 16rpx;
|
||||
flex-shrink: 0;
|
||||
fill: #ffffff3b;
|
||||
stroke-width: 2rpx;
|
||||
padding: 32rpx 28rpx;
|
||||
stroke: #fff;
|
||||
filter: drop-shadow(2rpx -4rpx 13.4rpx #ff6f0124);
|
||||
backdrop-filter: blur(5.1rpx);
|
||||
&.type1 {
|
||||
filter: none;
|
||||
border: none;
|
||||
background-color: #fcf5ed;
|
||||
border-radius: 36rpx 36rpx 0 0;
|
||||
padding: 32rpx 36rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-group {
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
.btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
border: 2rpx solid #fe6d11;
|
||||
&.shiming {
|
||||
color: #fe6d11;
|
||||
}
|
||||
&.tixian {
|
||||
color: #fff;
|
||||
background-color: #fe6d11;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
padding: 16rpx 18rpx;
|
||||
background: #ffe2e2;
|
||||
padding: 16rpx 18rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 48rpx;
|
||||
color: #ff1c1c;
|
||||
}
|
||||
.bind {
|
||||
padding: 8rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
border: 2rpx solid #fe6d11;
|
||||
color: #fff;
|
||||
background-color: #fe6d11;
|
||||
}
|
||||
.top_bg {
|
||||
width: 100%;
|
||||
}
|
||||
.bottom {
|
||||
margin: 0 28rpx;
|
||||
border-radius: 36rpx;
|
||||
background-color: #fff;
|
||||
transform: translateY(-20rpx);
|
||||
padding: 32rpx 28rpx;
|
||||
&.type1 {
|
||||
transform: translateY(0);
|
||||
margin: 0;
|
||||
padding-bottom: 42rpx;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
.small-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
.parse-html {
|
||||
margin: 32rpx 28rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.recoder-item {
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
}
|
||||
.share {
|
||||
border-radius: 16rpx;
|
||||
background: #e8ad7b;
|
||||
padding: 14rpx 76rpx;
|
||||
font-size: 32rpx;
|
||||
line-height: 48rpx;
|
||||
color: #fff;
|
||||
}
|
||||
.copy {
|
||||
padding: 4rpx 30rpx;
|
||||
border-radius: 18rpx;
|
||||
border: 2rpx solid #e8ad7b;
|
||||
background: #fff;
|
||||
font-size: 28rpx;
|
||||
color: #e8ad7b;
|
||||
line-height: 48rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.bottom-btn {
|
||||
position: fixed;
|
||||
left: 84rpx;
|
||||
right: 84rpx;
|
||||
bottom: 100rpx;
|
||||
white-space: nowrap;
|
||||
gap: 54rpx;
|
||||
}
|
||||
.buy {
|
||||
padding: 32rpx 224rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(98deg, #fe6d1100 40.64%, #ffd1b4 105.2%),
|
||||
linear-gradient(259deg, #fe6d11 50.14%, #ffd1b4 114.93%);
|
||||
box-shadow: 0 14rpx 30.4rpx 0 #fe8b435e;
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
position: fixed;
|
||||
left: 28rpx;
|
||||
right: 28rpx;
|
||||
bottom: 62rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin: 20rpx 0;
|
||||
gap: 30rpx;
|
||||
.tabs-item {
|
||||
flex: 1;
|
||||
padding: 4rpx 30rpx;
|
||||
border-radius: 18rpx;
|
||||
border: 2rpx solid #e8ad7b;
|
||||
background: #fff;
|
||||
font-size: 28rpx;
|
||||
color: #e8ad7b;
|
||||
line-height: 48rpx;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
&.active {
|
||||
background-color: #e8ad7b;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
201
distribution/shop-list/index.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<view class="box">
|
||||
<!-- <up-search v-model="query.shopName" @search="getData" @custom="getData"></up-search> -->
|
||||
<view class="list">
|
||||
<view class="u-flex shop-item " v-for="(item, index) in list" :key="index" @click="toShopDetail(item)">
|
||||
<up-image
|
||||
width="104rpx"
|
||||
height="104rpx"
|
||||
radius="8rpx"
|
||||
:src="item.coverImg"
|
||||
></up-image>
|
||||
<view class="u-flex-1 u-m-l-14">
|
||||
<view class="u-flex justify-between align-center">
|
||||
<view>
|
||||
<view class="shop-name">{{ item.shopName }}</view>
|
||||
<view class="u-flex">
|
||||
<view class="tag" v-if="item.labelContent">{{
|
||||
item.labelContent
|
||||
}}</view>
|
||||
</view>
|
||||
<view class="address u-line-1">{{ item.shopAddress }}</view>
|
||||
</view>
|
||||
<view v-if="options.type == 'activates'">
|
||||
<view class="shouyi">收益</view>
|
||||
<view class="price">¥{{ item.income||'0.00' }}</view>
|
||||
</view>
|
||||
<view class="u-flex u-flex-col justify-center" v-else>
|
||||
<view class="fufei" v-if="item.openType == 'pay'">付费开通</view>
|
||||
<view class="fufei" v-else-if="item.openType == 'manual'"
|
||||
>手动开通</view
|
||||
>
|
||||
<template v-else-if="item.openType == 'auto'">
|
||||
<view class="font-12 color-333 font-700">自动开通</view>
|
||||
<view class="u-m-t-8 color-666 font-12"
|
||||
>还差{{
|
||||
item.shopInviteCount - item.userInviteCount
|
||||
}}人开通</view
|
||||
>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<up-loadmore :status="query.isEnd?'noMore':'loadmore'"></up-loadmore>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as distributionApi from "@/common/api/market/distribution.js";
|
||||
|
||||
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
|
||||
const list = ref([]);
|
||||
const query = reactive({
|
||||
shopName: "",
|
||||
page: 1,
|
||||
size: 10,
|
||||
isEnd: false,
|
||||
});
|
||||
async function getData() {
|
||||
const res =
|
||||
options.type == "activates"
|
||||
? await distributionApi.activates(query)
|
||||
: await distributionApi.unActivates(query);
|
||||
if (res) {
|
||||
if (query.page == 1) {
|
||||
list.value = res.records || [];
|
||||
} else {
|
||||
list.value.push(...(res.records || []));
|
||||
}
|
||||
query.isEnd = query.page >= res.totalPage * 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function toShopDetail(item,type) {
|
||||
uni.navigateTo({
|
||||
url: "/distribution/shop-detail/index?shopId=" + item.shopId
|
||||
});
|
||||
}
|
||||
const options = reactive({});
|
||||
onLoad((opt) => {
|
||||
console.log(opt);
|
||||
Object.assign(options, opt);
|
||||
getData();
|
||||
});
|
||||
onReachBottom(() => {
|
||||
query.page++;
|
||||
if (query.isEnd) {
|
||||
return;
|
||||
}
|
||||
getData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
padding: 32rpx 28rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.color-1 {
|
||||
color: #ff6300;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.list {
|
||||
.shop-item {
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.fufei {
|
||||
color: #e8ad7b;
|
||||
}
|
||||
.tag {
|
||||
font-size: 24rpx;
|
||||
color: #ff1c1c;
|
||||
background-color: #ffe4e4;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.shop-name {
|
||||
}
|
||||
.address {
|
||||
max-width: 390rpx;
|
||||
}
|
||||
.shouyi {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
.price {
|
||||
font-size: 20rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price {
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
font-size: 40rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.top {
|
||||
position: relative;
|
||||
.top_content {
|
||||
background-color: #faf7f4;
|
||||
position: absolute;
|
||||
left: 28rpx;
|
||||
right: 28rpx;
|
||||
bottom: 0;
|
||||
padding: 32rpx 106rpx 32rpx 56rpx;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
}
|
||||
}
|
||||
.top_bg {
|
||||
width: 100%;
|
||||
height: 530rpx;
|
||||
}
|
||||
.bottom {
|
||||
padding: 34rpx 28rpx;
|
||||
}
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 4rpx;
|
||||
background-color: #9ee708;
|
||||
border-radius: 10rpx;
|
||||
z-index: -1;
|
||||
width: 94.2rpx;
|
||||
height: 13.98rpx;
|
||||
flex-shrink: 0;
|
||||
stroke-width: 4rpx;
|
||||
stroke: #9ee708d6;
|
||||
}
|
||||
}
|
||||
.small-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
1
distribution/static/scan.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761787146008" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4672" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M146.432 336.896h-81.92V106.496l40.96-40.96h231.424v81.92H146.432zM336.896 958.464H105.472l-40.96-40.96V687.104h81.92v189.44h190.464zM956.416 336.896h-81.92V147.456H684.032v-81.92h231.424l40.96 40.96zM915.456 958.464H613.376v-81.92h261.12V659.456h81.92v258.048z" fill="#437DFF" p-id="4673"></path><path d="M326.656 334.848h61.44v98.304h-61.44zM415.744 575.488h61.44v133.12h-61.44zM265.216 575.488h61.44v114.688h-61.44zM566.272 575.488h61.44v98.304h-61.44zM706.56 575.488h61.44v154.624h-61.44zM477.184 297.984h61.44v135.168h-61.44zM627.712 329.728h61.44v103.424h-61.44z" fill="#63F7DE" p-id="4674"></path><path d="M10.24 473.088h1003.52v61.44H10.24z" fill="#437DFF" p-id="4675"></path></svg>
|
||||
|
After Width: | Height: | Size: 1023 B |
BIN
distribution/static/top_bg.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
374
distribution/withdraw/index.vue
Normal file
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<view class="min-h-100vh bg-gray">
|
||||
<up-navbar
|
||||
bg-color="transparent"
|
||||
title="提现"
|
||||
@leftClick="back"
|
||||
:fixed="true"
|
||||
></up-navbar>
|
||||
<view class="top">
|
||||
<image class="top_bg" src="/distribution/static/top_bg.png"></image>
|
||||
<view class="top_content">
|
||||
<view class="color-333 font-16"> 提现金额 </view>
|
||||
<view class="u-m-t-32 u-flex input-number-box">
|
||||
<text class="fuhao">¥</text>
|
||||
<input
|
||||
v-model="money"
|
||||
type="digit"
|
||||
class="input-number"
|
||||
placeholder="最小提现金额为30"
|
||||
/>
|
||||
<text class="all-in" @click="allin">全部提现</text>
|
||||
</view>
|
||||
<view class="color-666 font-12 u-m-t-16">
|
||||
<text>可提现金额:¥{{ state.cashOutAmount || 0 }}</text>
|
||||
<text class="u-m-l-20">手续费为8%</text>
|
||||
</view>
|
||||
|
||||
<view class="btn-group">
|
||||
<view class="btn shiming" @click="toShiming">实名认证</view>
|
||||
<view class="btn tixian u-m-t-32" @click="tixian">立即提现</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<view class="u-flex">
|
||||
<view class="title">提现记录</view>
|
||||
</view>
|
||||
<view class="list">
|
||||
<view v-for="(item, index) in list" :key="index" class="shop-item">
|
||||
<view class="u-flex-1">
|
||||
<view class="u-flex justify-between">
|
||||
<view>
|
||||
<view class="name">提现</view>
|
||||
<view class="shouxufei u-m-t-16"
|
||||
>手续费{{ item.serviceFee }}元</view
|
||||
>
|
||||
<view class="font-12 color-999 u-m-t-10">
|
||||
时间:{{ item.createTime }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex u-flex-col justify-center">
|
||||
<template v-if="item.status == 'pending'">
|
||||
<view class="lingqu" @click="lingqu(item)">点击领取</view>
|
||||
<view class="price reduce"
|
||||
>-{{
|
||||
BigNumber(item.serviceFee).plus(item.amount).toFixed(2)
|
||||
}}</view
|
||||
>
|
||||
</template>
|
||||
<template v-if="item.status == 'finish'">
|
||||
<view class="status">提现成功</view>
|
||||
<view class="price reduce"
|
||||
>-{{
|
||||
BigNumber(item.serviceFee).plus(item.amount).toFixed(2)
|
||||
}}</view
|
||||
>
|
||||
</template>
|
||||
<template v-if="item.status == 'fail'">
|
||||
<view class="status fail">提现失败</view>
|
||||
<view class="price"
|
||||
>+{{
|
||||
BigNumber(item.serviceFee).plus(item.amount).toFixed(2)
|
||||
}}</view
|
||||
>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="" style="padding-bottom: 60rpx;">
|
||||
<up-loadmore
|
||||
:status="isEnd?'no-more':'loadmore'"
|
||||
></up-loadmore>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as distributionApi from "@/common/api/market/distribution.js";
|
||||
import { productStore } from "@/stores/user.js";
|
||||
const storeuser = productStore();
|
||||
import { ref, onMounted, reactive } from "vue";
|
||||
import { onLoad, onReachBottom, onShow } from "@dcloudio/uni-app";
|
||||
import BigNumber from "bignumber.js";
|
||||
function back() {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
}
|
||||
async function lingqu(item) {
|
||||
const res = await distributionApi.withdrawDetail({ id: item.id });
|
||||
if (res) {
|
||||
uni.requestMerchantTransfer({
|
||||
...res,
|
||||
success: (res) => {
|
||||
uni.showToast({
|
||||
title: "领取成功",
|
||||
icon: "none",
|
||||
});
|
||||
refesh();
|
||||
},
|
||||
fail: (res) => {
|
||||
uni.showToast({
|
||||
title: "领取失败",
|
||||
icon: "none",
|
||||
});
|
||||
refesh();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
function toShiming() {
|
||||
uni.navigateTo({
|
||||
url: "/distribution/shiming/index",
|
||||
});
|
||||
}
|
||||
const userinfo = ref(uni.cache.get("userinfo") || {});
|
||||
const query = reactive({
|
||||
page: 1,
|
||||
size: 10,
|
||||
});
|
||||
const list = ref([]);
|
||||
const isEnd = ref(false);
|
||||
async function withdrawFlow() {
|
||||
const res = await distributionApi.withdrawFlow(query);
|
||||
if (res) {
|
||||
if (query.page == 1) {
|
||||
list.value = res.records;
|
||||
} else {
|
||||
list.value.push(...res.records);
|
||||
}
|
||||
isEnd.value = query.page >= res.totalPage * 1;
|
||||
}
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
cashOutAmount: 0,
|
||||
pendingIncome: 0,
|
||||
totalIncome: 0,
|
||||
});
|
||||
async function centerUser() {
|
||||
const res = await distributionApi.centerUser();
|
||||
if (res) {
|
||||
Object.assign(state, res);
|
||||
}
|
||||
}
|
||||
|
||||
const money = ref(0);
|
||||
|
||||
function allin() {
|
||||
money.value = state.cashOutAmount;
|
||||
}
|
||||
async function tixian() {
|
||||
if (!userinfo.value.idCard) {
|
||||
uni.showToast({
|
||||
title: "请先实名认证",
|
||||
icon: "none",
|
||||
});
|
||||
setTimeout(() => {
|
||||
toShiming();
|
||||
}, 1500);
|
||||
return;
|
||||
}
|
||||
if (money.value <= 0) {
|
||||
uni.showToast({
|
||||
title: "请输入提现金额",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (money.value > state.cashOutAmount) {
|
||||
uni.showToast({
|
||||
title: "提现金额不能大于可提现金额",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const res = await distributionApi.withdraw({
|
||||
amount: money.value,
|
||||
});
|
||||
if (res) {
|
||||
uni.showToast({
|
||||
title: "提现成功",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
|
||||
refesh();
|
||||
}
|
||||
function refesh() {
|
||||
setTimeout(() => {
|
||||
query.page = 1;
|
||||
init();
|
||||
}, 3000);
|
||||
}
|
||||
async function init() {
|
||||
await centerUser();
|
||||
await withdrawFlow();
|
||||
}
|
||||
onReachBottom(async () => {
|
||||
console.log('onReachBottom', isEnd.value);
|
||||
if (!isEnd.value) {
|
||||
query.page++;
|
||||
await withdrawFlow();
|
||||
}
|
||||
});
|
||||
onLoad(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
storeuser.actionsAPIuser().then((res) => {
|
||||
userinfo.value = res;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input-number-box {
|
||||
width: 428rpx;
|
||||
padding-bottom: 10rpx;
|
||||
border-bottom: 1px solid #999;
|
||||
font-size: 28rpx;
|
||||
align-items: baseline;
|
||||
.fuhao {
|
||||
font-size: 64rpx;
|
||||
color: #333;
|
||||
}
|
||||
.input-number {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
padding-left: 24rpx;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
.all-in {
|
||||
font-size: 28rpx;
|
||||
color: #fe7e00;
|
||||
}
|
||||
}
|
||||
.list {
|
||||
.shop-item {
|
||||
padding: 32rpx 28rpx;
|
||||
border-bottom: 2rpx solid #ededed;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.fufei {
|
||||
color: #e8ad7b;
|
||||
}
|
||||
.tag {
|
||||
font-size: 24rpx;
|
||||
color: #ff1c1c;
|
||||
background-color: #ffe4e4;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.name {
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
}
|
||||
.shouxufei {
|
||||
}
|
||||
.shouyi {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.status {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
text-align: right;
|
||||
color: #333333;
|
||||
&.fail {
|
||||
color: #ff1c1c;
|
||||
}
|
||||
}
|
||||
.lingqu {
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #fe6d11;
|
||||
padding: 8rpx 16rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
.price {
|
||||
font-weight: 700;
|
||||
font-size: 48rpx;
|
||||
margin-top: 16rpx;
|
||||
color: #fe7e00;
|
||||
text-align: right;
|
||||
&.reduce {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
.top {
|
||||
position: relative;
|
||||
.top_content {
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
position: absolute;
|
||||
left: 28rpx;
|
||||
right: 28rpx;
|
||||
bottom: 52rpx;
|
||||
padding: 28rpx 28rpx 52rpx 28rpx;
|
||||
border-radius: 16rpx;
|
||||
|
||||
flex-shrink: 0;
|
||||
fill: #ffffff3b;
|
||||
stroke-width: 2rpx;
|
||||
stroke: #fff;
|
||||
filter: drop-shadow(2rpx -4rpx 13.4rpx #ff6f0124);
|
||||
backdrop-filter: blur(5.1rpx);
|
||||
.btn-group {
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
.btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
border: 2rpx solid #fe6d11;
|
||||
&.shiming {
|
||||
color: #fe6d11;
|
||||
}
|
||||
&.tixian {
|
||||
color: #fff;
|
||||
background-color: #fe6d11;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.top_bg {
|
||||
width: 100%;
|
||||
height: 580rpx;
|
||||
}
|
||||
.bottom {
|
||||
margin: 0 28rpx;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
background-color: #fff;
|
||||
transform: translateY(-20rpx);
|
||||
}
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
padding: 28rpx;
|
||||
}
|
||||
.small-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
@@ -7,9 +7,6 @@ const proxyApi = "/api"
|
||||
// #ifdef MP-WEIXIN || APP || MP-ALIPAY
|
||||
const proxyApi = 'http://192.168.1.42' // 调试地址
|
||||
const proxyApiwws = 'ws://192.168.1.42:2348' // 调试地址
|
||||
// 测试
|
||||
// const proxyApi = "https://fv901fw8033.vicp.fun"
|
||||
// const proxyApiwws = 'wss://sockets.sxczgkj.com/wss'
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
@@ -23,10 +20,6 @@ const baseUrlwws = debug ? proxyApiwws : 'wss://czgeatws.sxczgkj.com/wss' // 线
|
||||
// #endif
|
||||
|
||||
|
||||
// import VConsole from "./vConsole.js"
|
||||
// if (debug) {
|
||||
// new VConsole()
|
||||
// }
|
||||
const version = '100'
|
||||
const autoRemoveCache = {
|
||||
count: 100000,
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.3.1",
|
||||
"pinia-plugin-unistorage": "^0.1.2",
|
||||
"ysk-utils": "^1.0.40"
|
||||
"ysk-utils": "^1.0.78"
|
||||
}
|
||||
}
|
||||
|
||||
108
pages.json
@@ -203,38 +203,94 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/user/member/czzx-shop-list",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "适用门店"
|
||||
"path": "pages/user/member/czzx-shop-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "适用门店"
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [{
|
||||
"root": "user",
|
||||
"pages": [{
|
||||
"path": "vip/vip",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom"
|
||||
"root": "user",
|
||||
"pages": [{
|
||||
"path": "vip/vip",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "vip/buy-vip",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "score/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "积分门店"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "exchange/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "vip/buy-vip",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "score/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "积分门店"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}],
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "distribution",
|
||||
"pages": [{
|
||||
"path": "index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "分销中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "withdraw/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "提现",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "shiming/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "实名认证"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "shop-detail/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "分销中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "shop-list/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "店铺列表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "income-details/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收益明细"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "poster",
|
||||
"style": {
|
||||
"navigationBarTitleText": "poster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"uniIdRouter": {},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
|
||||
@@ -194,7 +194,9 @@ const hometop = async () => {
|
||||
const query = uni.createSelectorQuery().select('#fourcontent');
|
||||
query
|
||||
.boundingClientRect((rect) => {
|
||||
elementTop.value = rect.top - store.height;
|
||||
if(rect){
|
||||
elementTop.value = rect.top - store.height;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
}, 500);
|
||||
@@ -324,33 +326,8 @@ const indexsRef = ref(null);
|
||||
|
||||
onShow(async () => {
|
||||
try {
|
||||
uni.getLocation({
|
||||
type: 'wgs84',
|
||||
success: async (res) => {
|
||||
console.log('getLocation',res);
|
||||
let successres = await APIgeocodelocation({
|
||||
lng: res.longitude,
|
||||
lat: res.latitude
|
||||
});
|
||||
if (successres) {
|
||||
let datastorage = {
|
||||
country: successres.addressComponent.country, // "中国"
|
||||
province: successres.addressComponent.province, //province: "陕西省"
|
||||
address: successres.addressComponent.city, //district: "西安市"
|
||||
district: successres.addressComponent.district, //district: "未央区"
|
||||
lng: res.longitude,
|
||||
lat: res.latitude
|
||||
};
|
||||
uni.cache.set('getLocationstorage', datastorage);
|
||||
// 登录
|
||||
proxy.$isResolve();
|
||||
}
|
||||
},
|
||||
fail(error) {
|
||||
console.error('获取经纬度失败',error);
|
||||
}
|
||||
});
|
||||
|
||||
await storeuser.getLocation()
|
||||
proxy.$isResolve();
|
||||
const shopId=uni.cache.get('shopId')
|
||||
if(shopId){
|
||||
indexsRef.value.getVipConfig();
|
||||
|
||||
@@ -1,189 +1,213 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="box" v-if="isShow">
|
||||
<view class="u-flex u-col-center" style="align-items: center;">
|
||||
<image src="/static/icon/charge.png" class="charge" mode=""></image>
|
||||
<view class="u-m-l-28 color-333 font-700"> 充值享优惠</view>
|
||||
</view>
|
||||
<scroll-view scroll-x="true" class="u-m-t-20">
|
||||
<view class="list">
|
||||
<view class="item color1" @click="itemClick(index)" v-for="(item,index) in list" :key="index"
|
||||
:class="{active:sel==index}">
|
||||
<view class="">
|
||||
<text>¥</text>
|
||||
<text class="font-700" style="font-size: 48rpx;"
|
||||
:class="{color2:sel==index}">{{item.amount}}</text>
|
||||
</view>
|
||||
<view class="font-12" v-if="item.rewardAmount" :class="{color2:sel==index}">
|
||||
<text>赠</text>
|
||||
<text>¥</text>
|
||||
<text class="font-14">{{item.rewardAmount}}</text>
|
||||
</view>
|
||||
<view class="font-12" v-if="item.rewardPoints">
|
||||
<text>送</text>
|
||||
<text class="font-14">{{item.rewardPoints}}</text>
|
||||
<text>积分</text>
|
||||
</view>
|
||||
<view class="font-12 color-666" v-if="item.couponInfoList.length">
|
||||
<text>送</text>
|
||||
<text>{{couponNum(item.couponInfoList)}}</text>
|
||||
<text>张券</text>
|
||||
<text class="color2 u-m-l-8" v-if="sel==index" @click="lookCoupon(item)">查看</text>
|
||||
</view>
|
||||
|
||||
<view class="sel u-flex" v-if="sel==index">
|
||||
<image class="image" src="/static/vip/sel.png" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="" v-else></view>
|
||||
<CouponList v-model="couponModel.show" :list="couponModel.couponInfoList"></CouponList>
|
||||
</view>
|
||||
<view>
|
||||
<view class="box" v-if="isShow">
|
||||
<view class="u-flex u-col-center" style="align-items: center">
|
||||
<image src="/static/icon/charge.png" class="charge" mode=""></image>
|
||||
<view class="u-m-l-28 color-333 font-700"> 充值享优惠</view>
|
||||
</view>
|
||||
<scroll-view scroll-x="true" class="u-m-t-20">
|
||||
<view class="list">
|
||||
<view
|
||||
class="item color1"
|
||||
@click="itemClick(index)"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:class="{ active: sel == index }"
|
||||
>
|
||||
<view class="">
|
||||
<text>¥</text>
|
||||
<text
|
||||
class="font-700"
|
||||
style="font-size: 48rpx"
|
||||
:class="{ color2: sel == index }"
|
||||
>{{ item.amount }}</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="font-12"
|
||||
v-if="item.rewardAmount"
|
||||
:class="{ color2: sel == index }"
|
||||
>
|
||||
<text>赠</text>
|
||||
<text>¥</text>
|
||||
<text class="font-14">{{ item.rewardAmount }}</text>
|
||||
</view>
|
||||
<view class="font-12" v-if="item.rewardPoints">
|
||||
<text>送</text>
|
||||
<text class="font-14">{{ item.rewardPoints }}</text>
|
||||
<text>积分</text>
|
||||
</view>
|
||||
<view class="font-12 color-666" v-if="item.couponInfoList.length">
|
||||
<text>送</text>
|
||||
<text>{{ couponNum(item.couponInfoList) }}</text>
|
||||
<text>张券</text>
|
||||
<text
|
||||
class="color2 u-m-l-8"
|
||||
v-if="sel == index"
|
||||
@click="lookCoupon(item)"
|
||||
>查看</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="sel u-flex" v-if="sel == index">
|
||||
<image class="image" src="/static/vip/sel.png" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="" v-else></view>
|
||||
<CouponList
|
||||
v-model="couponModel.show"
|
||||
:list="couponModel.couponInfoList"
|
||||
></CouponList>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CouponList from '@/components/coupon/list.vue'
|
||||
import * as rechargeApi from '@/common/api/market/recharge.js'
|
||||
import {
|
||||
useCartsStore
|
||||
} from '@/stores/carts.js';
|
||||
import {
|
||||
onMounted,reactive,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
const couponModel = reactive({
|
||||
show: false,
|
||||
couponInfoList: []
|
||||
})
|
||||
|
||||
function lookCoupon(item) {
|
||||
couponModel.couponInfoList = item.couponInfoList
|
||||
couponModel.show = true
|
||||
}
|
||||
|
||||
function couponNum(list) {
|
||||
return list.reduce((prve, cur) => {
|
||||
return prve + cur.num
|
||||
}, 0)
|
||||
}
|
||||
const cartStore = useCartsStore()
|
||||
import CouponList from "@/components/coupon/list.vue";
|
||||
import * as rechargeApi from "@/common/api/market/recharge.js";
|
||||
import { useCartsStore } from "@/stores/carts.js";
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
const couponModel = reactive({
|
||||
show: false,
|
||||
couponInfoList: [],
|
||||
});
|
||||
|
||||
const list = ref([])
|
||||
const sel = ref(-1)
|
||||
const isShow = ref(false)
|
||||
let data={}
|
||||
let $riginList=[]
|
||||
async function init() {
|
||||
console.log('recharge', )
|
||||
const shopId = uni.cache.get('shopId')
|
||||
const res = await rechargeApi.config({
|
||||
shopId
|
||||
})
|
||||
if (res) {
|
||||
data=res;
|
||||
$riginList=res.rechargeDetailList
|
||||
isShow.value = res.isOrder
|
||||
list.value = res.rechargeDetailList.filter(v=>v.amount>cartStore.orderCostSummary.finalPayAmount)
|
||||
if(list.value.length){
|
||||
updateSel()
|
||||
}
|
||||
}
|
||||
}
|
||||
watch(()=>cartStore.orderCostSummary.finalPayAmount,(newval)=>{
|
||||
list.value=$riginList.filter(v=>v.amount>newval)
|
||||
if(list.value.length){
|
||||
updateSel()
|
||||
}
|
||||
})
|
||||
const emits=defineEmits(['updateChargeSel','updateRechargeId'])
|
||||
function updateSel(){
|
||||
const selItem=list.value[sel.value]
|
||||
emits('updateChargeSel',selItem?selItem:{})
|
||||
emits('updateRechargeId',data.id)
|
||||
}
|
||||
function itemClick(index){
|
||||
|
||||
console.log('itemClick',sel.value,index);
|
||||
if(sel.value==-1){
|
||||
sel.value=index
|
||||
return
|
||||
}
|
||||
if(sel.value==index){
|
||||
sel.value=-1
|
||||
return
|
||||
}
|
||||
sel.value=index
|
||||
}
|
||||
watch(()=>sel.value,(newval)=>{
|
||||
updateSel()
|
||||
})
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
function lookCoupon(item) {
|
||||
couponModel.couponInfoList = item.couponInfoList;
|
||||
couponModel.show = true;
|
||||
}
|
||||
|
||||
function couponNum(list) {
|
||||
return list.reduce((prve, cur) => {
|
||||
return prve + cur.num;
|
||||
}, 0);
|
||||
}
|
||||
const cartStore = useCartsStore();
|
||||
|
||||
const list = ref([]);
|
||||
const sel = ref(-1);
|
||||
const isShow = ref(false);
|
||||
let data = {};
|
||||
let $riginList = [];
|
||||
const emits = defineEmits(["updateChargeSel", "updateRechargeId","updateIsShow"]);
|
||||
|
||||
async function init() {
|
||||
console.log("recharge");
|
||||
const shopId = uni.cache.get("shopId");
|
||||
const res = await rechargeApi.config({
|
||||
shopId,
|
||||
});
|
||||
if (res) {
|
||||
data = res;
|
||||
$riginList = res.rechargeDetailList;
|
||||
isShow.value = res.isOrder&&res.isEnable?true:false;
|
||||
list.value = res.rechargeDetailList.filter(
|
||||
(v) => v.amount > cartStore.orderCostSummary.finalPayAmount
|
||||
);
|
||||
if (list.value.length) {
|
||||
updateSel();
|
||||
}
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => cartStore.orderCostSummary.finalPayAmount,
|
||||
(newval) => {
|
||||
list.value = $riginList.filter((v) => v.amount > newval);
|
||||
if (list.value.length) {
|
||||
updateSel();
|
||||
}
|
||||
}
|
||||
);
|
||||
function updateSel() {
|
||||
const selItem = list.value[sel.value];
|
||||
emits("updateChargeSel", selItem ? selItem : {});
|
||||
emits("updateRechargeId", data.id);
|
||||
}
|
||||
function itemClick(index) {
|
||||
console.log("itemClick", sel.value, index);
|
||||
if (sel.value == -1) {
|
||||
sel.value = index;
|
||||
return;
|
||||
}
|
||||
if (sel.value == index) {
|
||||
sel.value = -1;
|
||||
return;
|
||||
}
|
||||
sel.value = index;
|
||||
}
|
||||
watch(
|
||||
() => sel.value,
|
||||
(newval) => {
|
||||
updateSel();
|
||||
}
|
||||
);
|
||||
watch(()=>isShow.value,(newval)=>{
|
||||
emits("updateIsShow", newval);
|
||||
})
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.color1{
|
||||
color: #5F2E0F;
|
||||
}
|
||||
.color2{
|
||||
color: #FF6300;
|
||||
}
|
||||
.box {
|
||||
background-color: #fdf9f6;
|
||||
padding: 30rpx;
|
||||
margin-top: 32rpx;
|
||||
border-radius: 22rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.color1 {
|
||||
color: #5f2e0f;
|
||||
}
|
||||
.color2 {
|
||||
color: #ff6300;
|
||||
}
|
||||
.box {
|
||||
background-color: #fdf9f6;
|
||||
padding: 30rpx;
|
||||
margin-top: 32rpx;
|
||||
border-radius: 22rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.charge {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
.charge {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx 0;
|
||||
.list {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx 0;
|
||||
|
||||
.item {
|
||||
padding: 36rpx 22rpx;
|
||||
border-radius: 42rpx;
|
||||
background: linear-gradient(180deg, #F5F5F5 58.54%, #FFF 104.47%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
border: 6rpx solid transparent;
|
||||
transition: all .3s ease-in-out;
|
||||
min-width: 202rpx;
|
||||
.item {
|
||||
padding: 36rpx 22rpx;
|
||||
border-radius: 42rpx;
|
||||
background: linear-gradient(180deg, #f5f5f5 58.54%, #fff 104.47%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
border: 6rpx solid transparent;
|
||||
transition: all 0.3s ease-in-out;
|
||||
min-width: 202rpx;
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(180deg, #FFC29A -26.17%, #FFF 64.06%);
|
||||
border: 6rpx solid #FE6C0E;
|
||||
box-shadow: 0 0 31rpx 2rpx #fe8b435e;
|
||||
}
|
||||
&.active {
|
||||
background: linear-gradient(180deg, #ffc29a -26.17%, #fff 64.06%);
|
||||
border: 6rpx solid #fe6c0e;
|
||||
box-shadow: 0 0 31rpx 2rpx #fe8b435e;
|
||||
}
|
||||
|
||||
.sel {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
.sel {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
|
||||
transform: translateX(-50%) translateY(21rpx);
|
||||
transform: translateX(-50%) translateY(21rpx);
|
||||
|
||||
.image {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.image {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,92 +1,107 @@
|
||||
<template>
|
||||
<!-- 充值免单 -->
|
||||
<view class="rechargeFree">
|
||||
<view class="rechargeFree_bg" @click="changeFree">
|
||||
<view class="left">
|
||||
<view class="icon">优惠</view>
|
||||
<view class="text">
|
||||
充值消费{{freeDineConfig.rechargeTimes}}倍(订单满¥{{freeDineConfig.rechargeThreshold}}元可用),本单立享免单!</view>
|
||||
</view>
|
||||
<up-checkbox :disabled="!freeDineConfig.enable" @change="change" shape="circle" usedAlone v-model:checked="changeFreeenable" icon-size="20" size="20">
|
||||
</up-checkbox>
|
||||
<!-- <up-checkbox-group iconPlacement="right">
|
||||
<!-- 充值免单 -->
|
||||
<view class="rechargeFree">
|
||||
<view class="rechargeFree_bg" @click="itemClick">
|
||||
<view class="left">
|
||||
<view class="icon">优惠</view>
|
||||
<view class="text">
|
||||
充值消费{{ freeDineConfig.rechargeTimes }}倍(订单满¥{{
|
||||
freeDineConfig.rechargeThreshold
|
||||
}}元可用),本单立享免单!</view
|
||||
>
|
||||
</view>
|
||||
<view @click.stop="()=>{}">
|
||||
<up-checkbox
|
||||
:disabled="!freeDineConfig.enable"
|
||||
@change="changeFree"
|
||||
shape="circle"
|
||||
activeColor="#E8AD7B"
|
||||
usedAlone
|
||||
v-model:checked="changeFreeenable"
|
||||
icon-size="20"
|
||||
size="20"
|
||||
>
|
||||
</up-checkbox>
|
||||
</view>
|
||||
|
||||
<!-- <up-checkbox-group iconPlacement="right">
|
||||
<up-checkbox : v-model="changeFreeenable"
|
||||
:checked="freeDineConfig.enable" @change="change" activeColor="#E8AD7B" shape="circle"
|
||||
icon-size="16" size="16">
|
||||
</up-checkbox>
|
||||
</up-checkbox-group> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
defineProps,
|
||||
defineEmits
|
||||
} from 'vue';
|
||||
import { ref, defineProps, defineEmits } from "vue";
|
||||
|
||||
// 定义接收的属性
|
||||
const props = defineProps({
|
||||
freeDineConfig: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
payAmount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
});
|
||||
// 定义接收的属性
|
||||
const props = defineProps({
|
||||
freeDineConfig: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
payAmount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
function itemClick(){
|
||||
changeFreeenable.value = !changeFreeenable.value;
|
||||
emits("changeFree", changeFreeenable.value);
|
||||
|
||||
const emits = defineEmits(['changeFree']);
|
||||
}
|
||||
const emits = defineEmits(["changeFree"]);
|
||||
|
||||
const changeFreeenable = ref(false)
|
||||
const changeFreeenable = ref(false);
|
||||
|
||||
/**
|
||||
* 监听是否免单
|
||||
*/
|
||||
const change = (e) => {
|
||||
emits('changeFree', e);
|
||||
}
|
||||
/**
|
||||
* 监听是否免单
|
||||
*/
|
||||
const changeFree = (e) => {
|
||||
emits("changeFree", e);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rechargeFree {
|
||||
// padding: 0 20rpx;
|
||||
margin-top: 32rpx;
|
||||
.rechargeFree {
|
||||
// padding: 0 20rpx;
|
||||
margin-top: 32rpx;
|
||||
|
||||
.rechargeFree_bg {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #fff;
|
||||
.rechargeFree_bg {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
width: 68rpx;
|
||||
height: 36rpx;
|
||||
text-align: center;
|
||||
line-height: 36rpx;
|
||||
background: linear-gradient(180deg, #FEDE81 0%, #FEB263 100%);
|
||||
border-radius: 12rpx 0rpx 12rpx 0rpx;
|
||||
font-weight: 500;
|
||||
font-size: 20rpx;
|
||||
color: #FFFFFF;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.icon {
|
||||
width: 68rpx;
|
||||
height: 36rpx;
|
||||
text-align: center;
|
||||
line-height: 36rpx;
|
||||
background: linear-gradient(180deg, #fede81 0%, #feb263 100%);
|
||||
border-radius: 12rpx 0rpx 12rpx 0rpx;
|
||||
font-weight: 500;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
width: 80%;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.text {
|
||||
width: 80%;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -219,6 +219,7 @@
|
||||
|
||||
const orderinfo = (e) => {
|
||||
if(e.status=='unpaid'){
|
||||
uni.cache.set('shopId',e.shopId)
|
||||
uni.pro.navigateTo('order/confirm-order', {
|
||||
orderId: e.id,
|
||||
shopId: e.shopId,
|
||||
|
||||
251
pages/product/components/goods-modal.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<up-popup :show="visible" :round="20" mode="bottom" @close="close">
|
||||
<view class="shop_sku">
|
||||
<scroll-view scroll-y style="max-height: 60vh; width: 100%">
|
||||
<view class="positionabsolute">
|
||||
<up-icon
|
||||
name="close-circle"
|
||||
@click="close"
|
||||
color="#000"
|
||||
size="25"
|
||||
></up-icon>
|
||||
</view>
|
||||
<up-swiper :list="specifications.item.images" height="250"></up-swiper>
|
||||
<view class="shop_sku_name">{{ specifications.item.name }}</view>
|
||||
<view class="shop_sku_description">
|
||||
{{
|
||||
specifications.item.shortTitle ? specifications.item.shortTitle : ""
|
||||
}}
|
||||
</view>
|
||||
<view v-if="specifications.item.type != 'package'">
|
||||
<view
|
||||
class="shop_sku_box"
|
||||
v-for="(specOptions, specType) in specifications.item
|
||||
.selectSpecInfo"
|
||||
:key="specType"
|
||||
>
|
||||
<view class="shop_sku_box_name">
|
||||
{{ specType }}
|
||||
</view>
|
||||
<view class="flex-start">
|
||||
<view
|
||||
class="shop_sku_box_item"
|
||||
v-for="option in specOptions"
|
||||
:key="option"
|
||||
@click="selectSpec(specType, option)"
|
||||
:class="{
|
||||
shop_sku_box_item_selected: isSelected(option),
|
||||
}"
|
||||
>
|
||||
{{ option }}
|
||||
<view
|
||||
class="shop_sku_box_item_tip"
|
||||
v-if="
|
||||
specifications.item.result &&
|
||||
specifications.item.result.isSoldStock == 1 &&
|
||||
selectedSpecs[specType] === option
|
||||
"
|
||||
>
|
||||
<view>售罄</view>
|
||||
</view>
|
||||
<view
|
||||
class="shop_sku_box_item_tip"
|
||||
v-if="
|
||||
specifications.item.result == 'kong' &&
|
||||
canSubmit == false &&
|
||||
selectedSpecs[specType] === option
|
||||
"
|
||||
>
|
||||
<view>已下架</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 套餐 -->
|
||||
<view v-else>
|
||||
<view class="shop_sku_box">
|
||||
<view
|
||||
v-for="(setmenu, setmenuindex) in specifications.item.groupSnap"
|
||||
:key="setmenuindex"
|
||||
>
|
||||
<view class="shop_sku_box_name"
|
||||
>{{ setmenu.title }} {{ setmenu.count }} 选{{
|
||||
setmenu.number
|
||||
}}</view
|
||||
>
|
||||
<view class="flex-start">
|
||||
<view
|
||||
class="shop_sku_box_item"
|
||||
v-for="(option, goodsid) in setmenu.goods"
|
||||
:key="goodsid"
|
||||
@click="goodsidClick(setmenuindex, option, goodsid)"
|
||||
:class="{
|
||||
shop_sku_box_item_selected: isOptionSelected(
|
||||
setmenuindex,
|
||||
option
|
||||
),
|
||||
}"
|
||||
:disabled="
|
||||
isMaxSelected(setmenuindex) &&
|
||||
!isOptionSelected(setmenuindex, option)
|
||||
"
|
||||
>
|
||||
{{ option.proName }}
|
||||
<text v-if="option.unitName">/{{ option.unitName }}</text>
|
||||
<view
|
||||
class="shop_sku_box_item_tip"
|
||||
v-if="specifications.item.isSoldStock == 1"
|
||||
>
|
||||
<view>售罄</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="shop_bottom">
|
||||
<view class="flex-between">
|
||||
<view
|
||||
class="price"
|
||||
v-if="
|
||||
specifications.item.type != 'package' &&
|
||||
specifications.item.result
|
||||
"
|
||||
>
|
||||
<text class="i">¥</text>
|
||||
<view class="num">
|
||||
<GoodsPrice
|
||||
:limitDiscount="limitTimeDiscountRes"
|
||||
:cart="specifications.item.result"
|
||||
:shopUserInfo="shopUserInfo"
|
||||
:shopInfo="shopInfo"
|
||||
></GoodsPrice>
|
||||
</view>
|
||||
|
||||
<text class="num" v-if="false">
|
||||
{{
|
||||
shopInfo.isVip == 1 && shopInfo.isMemberPrice == 1
|
||||
? specifications.item.result.memberPrice ||
|
||||
specifications.item.result.salePrice
|
||||
: specifications.item.result.salePrice
|
||||
}}
|
||||
</text>
|
||||
<text class="i" v-if="specifications.item.unitName"
|
||||
>/{{ specifications.item.unitName }}</text
|
||||
>
|
||||
<text v-if="specifications.item.result.suitNum > 1"
|
||||
>「{{ specifications.item.result.suitNum
|
||||
}}{{ specifications.item.result.unitName }}起点」</text
|
||||
>
|
||||
</view>
|
||||
<view class="price" v-else>
|
||||
<text class="i">¥</text>
|
||||
<view class="num">
|
||||
<GoodsPrice
|
||||
:limitDiscount="limitTimeDiscountRes"
|
||||
:cart="specifications.item"
|
||||
:shopUserInfo="shopUserInfo"
|
||||
:shopInfo="shopInfo"
|
||||
></GoodsPrice>
|
||||
</view>
|
||||
<text class="num" v-if="false">
|
||||
{{
|
||||
shopInfo.isVip == 1 && shopInfo.isMemberPrice == 1
|
||||
? specifications.item.memberPrice ||
|
||||
specifications.item.salePrice
|
||||
: specifications.item.salePrice
|
||||
}}
|
||||
</text>
|
||||
<text class="i" v-if="specifications.item.unitName"
|
||||
>/{{ specifications.item.unitName }}</text
|
||||
>
|
||||
<text v-if="specifications.item.suitNum > 1"
|
||||
>「{{ specifications.item.suitNum
|
||||
}}{{ specifications.item.unitName }}起点」</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="operation-wrap"
|
||||
v-if="specifications.item.type != 'package'"
|
||||
>
|
||||
<view class="btn">
|
||||
<up-icon
|
||||
name="minus-circle-fill"
|
||||
color="#E9AB7A"
|
||||
size="25"
|
||||
v-if="shopCartNumber > 0"
|
||||
></up-icon>
|
||||
<view class="btnClick" @click="shopCart('-')"></view>
|
||||
</view>
|
||||
<text class="num">{{ shopCartNumber }}</text>
|
||||
<view class="btn">
|
||||
<up-icon
|
||||
name="plus-circle-fill"
|
||||
color="#E9AB7A"
|
||||
size="25"
|
||||
></up-icon>
|
||||
<view class="btnClick" @click="shopCart('+')"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="shop_skuselect flex-start" v-if="selectedSpecsStr">
|
||||
<view class="shop_skuselectname">{{ selectedSpecsStr }}</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="specifications.item.type == 'package'"
|
||||
class="addShopping"
|
||||
:class="shopCartNumber > 0 && allConditionsSatisfied ? 'active' : ''"
|
||||
@click="submitSelection()"
|
||||
>
|
||||
{{ skuBtnText }}
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="addShopping"
|
||||
:class="shopCartNumber > 0 && canSubmit ? 'active' : ''"
|
||||
@click="submitSelection()"
|
||||
>
|
||||
{{ skuBtnText }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
specifications: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
item: {},
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
const visible = defineModel({
|
||||
default: false,
|
||||
});
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
const selectedSpecs = ref({});
|
||||
function isSelected(option) {
|
||||
return selectedSpecs.value[specType] === option;
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.box {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
196
pages/product/components/recommend-goods-modal.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<up-overlay :show="show">
|
||||
<view class="box" v-if="SelData">
|
||||
<view class="bg-fff u-p-30 item">
|
||||
<view class="u-flex coverImg">
|
||||
<up-image
|
||||
width="204rpx"
|
||||
height="228rpx"
|
||||
radius="6"
|
||||
:src="SelData.goods.coverImg"
|
||||
></up-image>
|
||||
<view class="limitDiscount" v-if="showLimitDiscount(SelData.goods)"
|
||||
>限时折扣</view
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="u-flex-1 u-p-l-36">
|
||||
<view class="u-flex u-col-center justify-between u-m-t-10">
|
||||
<view class="u-line-1 font-16 color-333 font-bold u-p-r-16">{{
|
||||
SelData.title
|
||||
}}</view>
|
||||
<up-icon
|
||||
size="20"
|
||||
name="close-circle"
|
||||
color="#666"
|
||||
@click="close"
|
||||
></up-icon>
|
||||
</view>
|
||||
<view class="color-333 font-14 u-m-t-32 u-line-1">{{
|
||||
SelData.guideDetail
|
||||
}}</view>
|
||||
<view class="u-flex justify-between u-m-t-30">
|
||||
<view class="price"
|
||||
>¥
|
||||
<GoodsPrice
|
||||
:limitDiscount="cartStore.limitTimeDiscount"
|
||||
:cart="SelData.goods"
|
||||
:shopUserInfo="shopUserInfo"
|
||||
:shopInfo="shopInfo"
|
||||
></GoodsPrice
|
||||
></view>
|
||||
<view class="buy" @click="buy">立即下单</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-overlay>
|
||||
</template>
|
||||
<script setup>
|
||||
import * as orderUtils from "@/utils/order-utils.js";
|
||||
import GoodsPrice from "@/components/goods-price.vue";
|
||||
import * as suggestApi from "@/common/api/market/suggest.js";
|
||||
import { ref, onMounted, inject, watch } from "vue";
|
||||
const cartStore = inject("cartStore");
|
||||
const shopUserInfo = inject("shopUserInfo");
|
||||
const shopInfo = inject("shopInfo");
|
||||
|
||||
function showLimitDiscount(item) {
|
||||
if (!cartStore.limitTimeDiscount) {
|
||||
return false;
|
||||
}
|
||||
return orderUtils.canUseLimitTimeDiscount(
|
||||
item,
|
||||
cartStore.limitTimeDiscount,
|
||||
shopInfo,
|
||||
shopUserInfo.value,
|
||||
"id"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
let timer = null;
|
||||
|
||||
function taskEnd() {
|
||||
clearInterval(timer);
|
||||
show.value = false;
|
||||
}
|
||||
function statTime() {
|
||||
clearInterval(timer);
|
||||
timer = setTimeout(() => {
|
||||
const item = list.value[nowIndex.value];
|
||||
if (item) {
|
||||
SelData.value = item;
|
||||
show.value = true;
|
||||
} else {
|
||||
taskEnd();
|
||||
}
|
||||
}, suggestTime.value * 1000);
|
||||
}
|
||||
|
||||
const list = ref([]);
|
||||
const SelData = ref(null);
|
||||
const nowIndex = ref(0);
|
||||
function close() {
|
||||
suggestTime.value = 30;
|
||||
taskEnd();
|
||||
nowIndex.value += 1;
|
||||
if (nowIndex.value == list.value.length) {
|
||||
return;
|
||||
}
|
||||
statTime();
|
||||
}
|
||||
const suggestTime = ref(100);
|
||||
|
||||
const emits = defineEmits(["onBuyClick"]);
|
||||
function buy() {
|
||||
emits("onBuyClick", SelData.value.goods);
|
||||
}
|
||||
onMounted(() => {
|
||||
suggestApi
|
||||
.getGoods({
|
||||
shopId: uni.cache.get("shopId"),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
suggestTime.value = res.suggestTime;
|
||||
list.value = (res.list || [])
|
||||
.map((v) => {
|
||||
return {
|
||||
...v,
|
||||
goods: cartStore.returnGoods(v.foods),
|
||||
};
|
||||
})
|
||||
.filter((v) => v.goods);
|
||||
console.log('list.value ', list.value )
|
||||
startWatch();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function startWatch() {
|
||||
watch(
|
||||
() => cartStore.isEmpty,
|
||||
(newval) => {
|
||||
if (newval && list.value.length) {
|
||||
statTime();
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
nowIndex.value = 0;
|
||||
show.value = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.box {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
left: 30rpx;
|
||||
right: 30rpx;
|
||||
bottom: calc(var(--safe-area-inset-bottom) + 0px);
|
||||
.item {
|
||||
padding: 32rpx 28rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
}
|
||||
.price {
|
||||
font-size: 40rpx;
|
||||
color: #333;
|
||||
}
|
||||
.buy {
|
||||
padding: 4rpx 28rpx;
|
||||
border-radius: 36rpx;
|
||||
background: #e8ad7b;
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
}
|
||||
.coverImg{
|
||||
position: relative;
|
||||
}
|
||||
.limitDiscount {
|
||||
background-color: #cc5617;
|
||||
padding: 2rpx 10rpx;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
border-radius: 20rpx 0rpx 20rpx 0rpx;
|
||||
z-index: 9;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,65 +1,13 @@
|
||||
<template>
|
||||
<view class="u-p-30">
|
||||
<view>
|
||||
<up-button type="warning" @click="popupShow">初始化</up-button>
|
||||
</view>
|
||||
<view class="u-m-t-30">
|
||||
<up-button type="primary" @click="toCreate">去下单</up-button>
|
||||
</view>
|
||||
|
||||
<up-popup :show="show" mode="bottom" close-on-click-overlay @close="resetForm">
|
||||
<view class="u-p-30">
|
||||
<up-form label-width="80" ref="refForm">
|
||||
<up-form-item label="台桌码">
|
||||
<up-input v-model="form.tableCode" placeholder="请输入台桌码"></up-input>
|
||||
</up-form-item>
|
||||
</up-form>
|
||||
<view class="u-flex gap-20 u-m-t-30">
|
||||
<up-button @click="show=false">取消</up-button>
|
||||
<up-button type="primary" @click="scanCodehandle">确定</up-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</up-popup>
|
||||
</view>
|
||||
<view class="u-p-30">
|
||||
<xb-swiper-preview></xb-swiper-preview>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onLoad} from '@dcloudio/uni-app'
|
||||
import { reactive,ref} from 'vue'
|
||||
import {
|
||||
productStore
|
||||
} from '@/stores/user.js';
|
||||
const store = productStore();
|
||||
const scanCodehandle = async (i) => {
|
||||
await store.scanCodeactions(form)
|
||||
}
|
||||
const show=ref(false);
|
||||
const options=ref({})
|
||||
function popupShow(){
|
||||
show.value=true;
|
||||
}
|
||||
const refForm=ref(null);
|
||||
const form=reactive({
|
||||
tableCode:"40963902920"
|
||||
})
|
||||
function resetForm(){
|
||||
form.tableCode=""
|
||||
}
|
||||
onLoad((opt)=>{
|
||||
console.log(opt);
|
||||
options.value=opt
|
||||
})
|
||||
function toCreate(){
|
||||
uni.navigateTo({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
import xbSwiperPreview from '@/components/xb-swiper-preview/index.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.gap-20{
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header-wrap">
|
||||
<view class="u-flex" style="justify-content: flex-end;">
|
||||
<view @click="toExchangeCode" class="color-333 font-12 u-m-b-26 font-700">优惠券兑换码</view>
|
||||
</view>
|
||||
<view class="search-wrap">
|
||||
<view class="input-wrap">
|
||||
<view class="icon left">
|
||||
@@ -137,7 +140,11 @@ onReachBottom(() => {
|
||||
getCouponList();
|
||||
}
|
||||
});
|
||||
|
||||
function toExchangeCode(){
|
||||
uni.navigateTo({
|
||||
url: '/user/exchange/index'
|
||||
})
|
||||
}
|
||||
const showDetail = ref(false);
|
||||
const selectListItem = ref('');
|
||||
const selectListItemDetails = ref([]);
|
||||
|
||||
@@ -57,6 +57,15 @@
|
||||
<text v-if="item.bizCode == 'adminOut'">
|
||||
管理员消费
|
||||
</text>
|
||||
<text v-if="item.bizCode == 'cashback'">
|
||||
消费返现
|
||||
</text>
|
||||
<text v-if="item.bizCode == 'freeIn'">
|
||||
霸王餐充值
|
||||
</text>
|
||||
<text v-if="item.bizCode == 'rechargeRedemption'">
|
||||
兑换券充值
|
||||
</text>
|
||||
</view>
|
||||
<view v-else>
|
||||
{{item.content}}
|
||||
|
||||
@@ -1,411 +1,461 @@
|
||||
<!-- 充值中心 -->
|
||||
<template>
|
||||
<view class="container">
|
||||
<up-navbar bgColor="transparent" title="充值中心" @leftClick="back"></up-navbar>
|
||||
<view class="header-wrap">
|
||||
<image class="bg" src="/static/czzx_header_bg.png" mode="aspectFill"></image>
|
||||
<view class="select-shop">
|
||||
<view class="select-btn">
|
||||
<up-icon name="map" color="#333"></up-icon>
|
||||
<text class="t">{{shopInfo.shopName}}</text>
|
||||
<up-icon name="arrow-right" color="#333"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="balance-wrap">
|
||||
<view class="left">
|
||||
<text class="i t">余额</text>
|
||||
<text class="n t">{{shopUserInfo.amount||0}}</text>
|
||||
</view>
|
||||
<view class="right">
|
||||
<text class="t" @click="toDetail">明细</text>
|
||||
<text class="t" @click="toPwd">密码设置</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btm-wrap">
|
||||
<view class="">
|
||||
<view class="container">
|
||||
<up-navbar
|
||||
bgColor="transparent"
|
||||
title="充值中心"
|
||||
@leftClick="back"
|
||||
></up-navbar>
|
||||
<view class="header-wrap">
|
||||
<image
|
||||
class="bg"
|
||||
src="/static/czzx_header_bg.png"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<view class="select-shop">
|
||||
<view class="select-btn">
|
||||
<up-icon name="map" color="#333"></up-icon>
|
||||
<text class="t">{{ shopInfo.shopName }}</text>
|
||||
<up-icon name="arrow-right" color="#333"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="balance-wrap">
|
||||
<view class="left">
|
||||
<text class="i t">余额</text>
|
||||
<text class="n t">{{ shopUserInfo.amount || 0 }}</text>
|
||||
</view>
|
||||
<view class="right">
|
||||
<text class="t" @click="toduihuan">兑换码</text>
|
||||
<text class="t" @click="toDetail">明细</text>
|
||||
<text class="t" @click="toPwd">密码设置</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btm-wrap">
|
||||
<view class=""> </view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<view class="u-flex u-flex-between">
|
||||
<view class="u-flex">
|
||||
<image
|
||||
src="/static/vip/money.png"
|
||||
style="width: 44rpx; height: 44rpx"
|
||||
mode=""
|
||||
></image>
|
||||
<text class="u-m-l-24 color-333 font-16 font-700">立即充值</text>
|
||||
</view>
|
||||
<view class="font-12 color-999">
|
||||
<text>充值代表接受</text>
|
||||
<text style="color: #ecb592">《用户隐私协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list u-m-t-40">
|
||||
<view
|
||||
class="item color1"
|
||||
@click="sel = index"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:class="{ active: sel == index }"
|
||||
>
|
||||
<view class="">
|
||||
<text>¥</text>
|
||||
<text
|
||||
class="font-700"
|
||||
style="font-size: 48 u-flex-1rpx"
|
||||
:class="{ color2: sel == index }"
|
||||
>{{ item.amount }}</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="font-12"
|
||||
v-if="item.rewardAmount"
|
||||
:class="{ color2: sel == index }"
|
||||
>
|
||||
<text>赠</text>
|
||||
<text>¥</text>
|
||||
<text class="font-14">{{ item.rewardAmount }}</text>
|
||||
</view>
|
||||
<view class="font-12" v-if="item.rewardPoints" style="color: #5f2e0f">
|
||||
<text>送</text>
|
||||
<text class="font-14">{{ item.rewardPoints }}</text>
|
||||
<text class="">积分</text>
|
||||
</view>
|
||||
<view class="font-12 color-666" v-if="item.couponInfoList.length">
|
||||
<text>送</text>
|
||||
<text>{{ couponNum(item.couponInfoList) }}</text>
|
||||
<text>张券</text>
|
||||
<text
|
||||
class="color2 u-m-l-8"
|
||||
v-if="sel == index"
|
||||
@click="lookCoupon(item)"
|
||||
>查看</text
|
||||
>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<view class="u-flex u-flex-between">
|
||||
<view class="u-flex ">
|
||||
<image src="/static/vip/money.png" style="width: 44rpx;height: 44rpx;" mode=""></image>
|
||||
<text class="u-m-l-24 color-333 font-16 font-700">立即充值</text>
|
||||
</view>
|
||||
<view class="font-12 color-999">
|
||||
<text>充值代表接受</text>
|
||||
<text style="color: #ECB592;">《用户隐私协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list u-m-t-40">
|
||||
<view class="item color1" @click="sel=index" v-for="(item,index) in list" :key="index"
|
||||
:class="{active:sel==index}">
|
||||
<view class="">
|
||||
<text>¥</text>
|
||||
<text class="font-700" style="font-size: 48 u-flex-1rpx;"
|
||||
:class="{color2:sel==index}">{{item.amount}}</text>
|
||||
</view>
|
||||
<view class="font-12" v-if="item.rewardAmount" :class="{color2:sel==index}">
|
||||
<text>赠</text>
|
||||
<text>¥</text>
|
||||
<text class="font-14">{{item.rewardAmount}}</text>
|
||||
</view>
|
||||
<view class="font-12" v-if="item.rewardPoints" style="color: #5F2E0F;">
|
||||
<text>送</text>
|
||||
<text class="font-14">{{item.rewardPoints}}</text>
|
||||
<text class="">积分</text>
|
||||
</view>
|
||||
<view class="font-12 color-666" v-if="item.couponInfoList.length">
|
||||
<text>送</text>
|
||||
<text>{{couponNum(item.couponInfoList) }}</text>
|
||||
<text>张券</text>
|
||||
<text class="color2 u-m-l-8" v-if="sel==index" @click="lookCoupon(item)">查看</text>
|
||||
</view>
|
||||
<view class="sel u-flex" v-if="sel == index">
|
||||
<image class="image" src="/static/vip/sel.png" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="state.isCustom">
|
||||
<view class="u-flex other flex-center">
|
||||
<text class="font-14 color-333 font-700 u-m-r-28">其他金额</text>
|
||||
<up-input
|
||||
v-model="money"
|
||||
type="number"
|
||||
placeholder="请输入充值金额"
|
||||
border="none"
|
||||
placeholder-style="font-size:14px;"
|
||||
></up-input>
|
||||
</view>
|
||||
<view class="color-999 font-12 u-m-t-4"
|
||||
>自定义金额充值时,不享受任何优惠赠送</view
|
||||
>
|
||||
</template>
|
||||
|
||||
<view class="sel u-flex" v-if="sel==index">
|
||||
<image class="image" src="/static/vip/sel.png" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="state.isCustom">
|
||||
<view class="u-flex other flex-center">
|
||||
<text class="font-14 color-333 font-700 u-m-r-28">其他金额</text>
|
||||
<up-input v-model="money" type="number" placeholder="请输入充值金额" border="none"
|
||||
placeholder-style="font-size:14px;"></up-input>
|
||||
</view>
|
||||
<view class="color-999 font-12 u-m-t-4">自定义金额充值时,不享受任何优惠赠送</view>
|
||||
</template>
|
||||
|
||||
<button class="buy-btn" @click="buy">
|
||||
<text class="font-16 ">¥{{charge_money}}</text>
|
||||
<text class="font-14 u-m-l-24">立即充值</text>
|
||||
</button>
|
||||
<view class="u-m-t-36 color-999 font-12">
|
||||
<view>充值说明</view>
|
||||
<view class="u-m-t-16">
|
||||
<text>适用门店</text>
|
||||
<text class="color2 u-m-l-28" @click="toShopList">全国门店通用 {{'>'}} </text>
|
||||
</view>
|
||||
<view class="u-m-t-16">
|
||||
<text>有效期限</text>
|
||||
<text class=" u-m-l-28">永久有效 </text>
|
||||
</view>
|
||||
<view class="u-m-t-16 u-flex u-flex-y-center">
|
||||
<text class="no-wrap">注意事项</text>
|
||||
<view class="u-m-l-28">
|
||||
<view>1.储值完成后不支持自助退款,可联系商家处理</view>
|
||||
<view> 2.余额不支持转赠,不可提现,长期有效</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="u-m-t-16 u-flex u-flex-y-center">
|
||||
<text class="no-wrap">充值说明</text>
|
||||
<text class="u-m-l-28" style="word-break: break-all;">
|
||||
{{state.remark||''}}
|
||||
</text>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<CouponList v-model="couponModel.show" :list="couponModel.couponInfoList"></CouponList>
|
||||
</view>
|
||||
<button
|
||||
class="buy-btn"
|
||||
@click="buy"
|
||||
:class="{ disabled: !state.isEnable }"
|
||||
>
|
||||
<text class="font-16">¥{{ charge_money }}</text>
|
||||
<text class="font-14 u-m-l-24">立即充值</text>
|
||||
</button>
|
||||
<view class="u-m-t-36 color-999 font-12">
|
||||
<view>充值说明</view>
|
||||
<view class="u-m-t-16">
|
||||
<text>适用门店</text>
|
||||
<text class="color2 u-m-l-28" @click="toShopList"
|
||||
>全国门店通用 {{ ">" }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="u-m-t-16">
|
||||
<text>有效期限</text>
|
||||
<text class="u-m-l-28">永久有效 </text>
|
||||
</view>
|
||||
<view class="u-m-t-16 u-flex u-flex-y-center">
|
||||
<text class="no-wrap">注意事项</text>
|
||||
<view class="u-m-l-28">
|
||||
<view>1.储值完成后不支持自助退款,可联系商家处理</view>
|
||||
<view> 2.余额不支持转赠,不可提现,长期有效</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-m-t-16 u-flex u-flex-y-center">
|
||||
<text class="no-wrap">充值说明</text>
|
||||
<text class="u-m-l-28" style="word-break: break-all">
|
||||
{{ state.remark || "" }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<CouponList
|
||||
v-model="couponModel.show"
|
||||
:list="couponModel.couponInfoList"
|
||||
></CouponList>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
APIusershopInfodetail,
|
||||
APIshopUserInfo
|
||||
} from '@/common/api/member.js'
|
||||
import CouponList from '@/components/coupon/list.vue'
|
||||
import * as rechargeApi from '@/common/api/market/recharge.js'
|
||||
import {
|
||||
recharge
|
||||
} from '@/common/api/order/index.js'
|
||||
import {
|
||||
joinMember
|
||||
} from '@/common/api/order/index.js'
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
computed,
|
||||
reactive,
|
||||
watch
|
||||
} from 'vue'
|
||||
import {
|
||||
onLoad
|
||||
} from '@dcloudio/uni-app'
|
||||
import {
|
||||
pay
|
||||
} from '@/utils/pay.js'
|
||||
|
||||
function toShopList(){
|
||||
uni.navigateTo({
|
||||
url:'/pages/user/member/czzx-shop-list?shopId='+option.shopId
|
||||
})
|
||||
}
|
||||
import { APIusershopInfodetail, APIshopUserInfo } from "@/common/api/member.js";
|
||||
import CouponList from "@/components/coupon/list.vue";
|
||||
import * as rechargeApi from "@/common/api/market/recharge.js";
|
||||
import { recharge } from "@/common/api/order/index.js";
|
||||
import { joinMember } from "@/common/api/order/index.js";
|
||||
import { ref, onMounted, computed, reactive, watch } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { pay } from "@/utils/pay.js";
|
||||
|
||||
function couponNum(list) {
|
||||
return list.reduce((prve, cur) => {
|
||||
return prve + cur.num
|
||||
}, 0)
|
||||
}
|
||||
const couponModel = reactive({
|
||||
show: false,
|
||||
couponInfoList: []
|
||||
})
|
||||
function toShopList() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/user/member/czzx-shop-list?shopId=" + option.shopId,
|
||||
});
|
||||
}
|
||||
|
||||
function lookCoupon(item) {
|
||||
couponModel.show = true
|
||||
couponModel.couponInfoList = item.couponInfoList
|
||||
}
|
||||
async function buy() {
|
||||
if (!charge_money.value) {
|
||||
return uni.showToast({
|
||||
title: '请选择或者输入充值金额',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
const json = {
|
||||
shopId: option.shopId,
|
||||
shopUserId: shopUserInfo.id,
|
||||
}
|
||||
if (sel.value < 0) {
|
||||
json.amount = `${money.value}`.trim() * 1
|
||||
} else {
|
||||
json.rechargeDetailId = list.value[sel.value].id
|
||||
json.amount = list.value[sel.value].amount
|
||||
}
|
||||
const res = await recharge(json)
|
||||
if (!res) {
|
||||
return uni.showToast({
|
||||
title: '充值失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
const payRes = await pay(res)
|
||||
console.log(payRes);
|
||||
if (payRes) {
|
||||
uni.showToast({
|
||||
title: '充值成功',
|
||||
icon: 'none'
|
||||
})
|
||||
init()
|
||||
}
|
||||
function toduihuan() {
|
||||
uni.navigateTo({
|
||||
url: "/user/exchange/index",
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
function couponNum(list) {
|
||||
return list.reduce((prve, cur) => {
|
||||
return prve + cur.num;
|
||||
}, 0);
|
||||
}
|
||||
const couponModel = reactive({
|
||||
show: false,
|
||||
couponInfoList: [],
|
||||
});
|
||||
|
||||
function toDetail() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/member/billDetails?type=1&shopId=' + option.shopId
|
||||
})
|
||||
}
|
||||
function lookCoupon(item) {
|
||||
couponModel.show = true;
|
||||
couponModel.couponInfoList = item.couponInfoList;
|
||||
}
|
||||
async function buy() {
|
||||
if (!state.isEnable) {
|
||||
return uni.showToast({
|
||||
title: "充值未开启,暂不能充值",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
if (!charge_money.value) {
|
||||
return uni.showToast({
|
||||
title: "请选择或者输入充值金额",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
const json = {
|
||||
shopId: option.shopId,
|
||||
shopUserId: shopUserInfo.id,
|
||||
};
|
||||
if (sel.value < 0) {
|
||||
json.amount = `${money.value}`.trim() * 1;
|
||||
} else {
|
||||
json.rechargeDetailId = list.value[sel.value].id;
|
||||
json.amount = list.value[sel.value].amount;
|
||||
}
|
||||
const res = await recharge(json);
|
||||
if (!res) {
|
||||
return uni.showToast({
|
||||
title: "充值失败",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
const payRes = await pay(res);
|
||||
console.log(payRes);
|
||||
if (payRes) {
|
||||
uni.showToast({
|
||||
title: "充值成功",
|
||||
icon: "none",
|
||||
});
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
function toPwd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/member/setPassword?type=1&shopId=' + option.shopId
|
||||
})
|
||||
}
|
||||
function toDetail() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/user/member/billDetails?type=1&shopId=" + option.shopId,
|
||||
});
|
||||
}
|
||||
|
||||
function back() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
const list = ref([])
|
||||
const sel = ref(0)
|
||||
const money = ref(null);
|
||||
const option = reactive({
|
||||
shopId: ''
|
||||
})
|
||||
const shopInfo = reactive({})
|
||||
const shopUserInfo = reactive({})
|
||||
const state = reactive({})
|
||||
async function init() {
|
||||
const shopInfoRes = await APIusershopInfodetail({
|
||||
shopId: option.shopId
|
||||
})
|
||||
if (shopInfoRes) {
|
||||
Object.assign(shopInfo, shopInfoRes.shopInfo)
|
||||
}
|
||||
const shopUserInfoRes = await APIshopUserInfo({
|
||||
shopId: option.shopId
|
||||
})
|
||||
if (shopUserInfoRes) {
|
||||
Object.assign(shopUserInfo, shopUserInfoRes)
|
||||
}
|
||||
const res = await rechargeApi.config({
|
||||
shopId: option.shopId
|
||||
})
|
||||
if (res) {
|
||||
Object.assign(state, res)
|
||||
list.value = res.rechargeDetailList
|
||||
}
|
||||
}
|
||||
const charge_money = computed(() => {
|
||||
if (sel.value < 0) {
|
||||
if (money.value > 0) {
|
||||
return money.value
|
||||
}
|
||||
return ''
|
||||
}
|
||||
const item = list.value[sel.value]
|
||||
if (item) {
|
||||
return item.amount
|
||||
}
|
||||
return ''
|
||||
})
|
||||
onLoad((opt) => {
|
||||
Object.assign(option, opt)
|
||||
init()
|
||||
})
|
||||
function toPwd() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/user/member/setPassword?type=1&shopId=" + option.shopId,
|
||||
});
|
||||
}
|
||||
|
||||
watch(() => money.value, (newval) => {
|
||||
if (newval && newval > 0) {
|
||||
sel.value = -1
|
||||
}
|
||||
})
|
||||
function back() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
const list = ref([]);
|
||||
const sel = ref(0);
|
||||
const money = ref(null);
|
||||
const option = reactive({
|
||||
shopId: "",
|
||||
});
|
||||
const shopInfo = reactive({});
|
||||
const shopUserInfo = reactive({});
|
||||
const state = reactive({});
|
||||
async function init() {
|
||||
const shopInfoRes = await APIusershopInfodetail({
|
||||
shopId: option.shopId,
|
||||
});
|
||||
if (shopInfoRes) {
|
||||
Object.assign(shopInfo, shopInfoRes.shopInfo);
|
||||
}
|
||||
const shopUserInfoRes = await APIshopUserInfo({
|
||||
shopId: option.shopId,
|
||||
});
|
||||
if (shopUserInfoRes) {
|
||||
Object.assign(shopUserInfo, shopUserInfoRes);
|
||||
}
|
||||
const res = await rechargeApi.config({
|
||||
shopId: option.shopId,
|
||||
});
|
||||
if (res) {
|
||||
Object.assign(state, res);
|
||||
list.value = res.rechargeDetailList;
|
||||
}
|
||||
}
|
||||
const charge_money = computed(() => {
|
||||
if (sel.value < 0) {
|
||||
if (money.value > 0) {
|
||||
return money.value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
const item = list.value[sel.value];
|
||||
if (item) {
|
||||
return item.amount;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
onLoad((opt) => {
|
||||
Object.assign(option, opt);
|
||||
// init();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => money.value,
|
||||
(newval) => {
|
||||
if (newval && newval > 0) {
|
||||
sel.value = -1;
|
||||
}
|
||||
}
|
||||
);
|
||||
onShow(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.color1 {
|
||||
color: #5F2E0F;
|
||||
}
|
||||
.color1 {
|
||||
color: #5f2e0f;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
color: #FF6300;
|
||||
}
|
||||
.color2 {
|
||||
color: #ff6300;
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
margin-top: 28rpx;
|
||||
padding: 32rpx 32rpx;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
border-radius: 80rpx;
|
||||
line-height: 1;
|
||||
background: linear-gradient(98deg, #fe6d1100 40.64%, #FFD1B4 105.2%), linear-gradient(259deg, #FE6D11 50.14%, #FFD1B4 114.93%);
|
||||
box-shadow: 0 14rpx 30.4rpx 0 #fe8b435e;
|
||||
}
|
||||
.buy-btn {
|
||||
margin-top: 28rpx;
|
||||
padding: 32rpx 32rpx;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
border-radius: 80rpx;
|
||||
line-height: 1;
|
||||
background: linear-gradient(98deg, #fe6d1100 40.64%, #ffd1b4 105.2%),
|
||||
linear-gradient(259deg, #fe6d11 50.14%, #ffd1b4 114.93%);
|
||||
box-shadow: 0 14rpx 30.4rpx 0 #fe8b435e;
|
||||
&.disabled {
|
||||
background: #eee;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.other {
|
||||
background: #F6F6F6;
|
||||
padding: 24rpx 16rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
.other {
|
||||
background: #f6f6f6;
|
||||
padding: 24rpx 16rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
height: 530rpx;
|
||||
box-sizing: border-box;
|
||||
padding: calc(var(--status-bar-height) + 140rpx) 28rpx 28rpx;
|
||||
position: relative;
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
height: 530rpx;
|
||||
box-sizing: border-box;
|
||||
padding: calc(var(--status-bar-height) + 140rpx) 28rpx 28rpx;
|
||||
position: relative;
|
||||
|
||||
.bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.select-shop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
.select-shop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.select-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12upx;
|
||||
.select-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12upx;
|
||||
|
||||
.t {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
.t {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.balance-wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding-top: 40upx;
|
||||
.balance-wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding-top: 20rpx;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.t {
|
||||
color: #5e3110;
|
||||
.t {
|
||||
color: #5e3110;
|
||||
|
||||
&.i {
|
||||
position: relative;
|
||||
top: 10upx;
|
||||
font-size: 28upx;
|
||||
}
|
||||
&.i {
|
||||
position: relative;
|
||||
top: 10upx;
|
||||
font-size: 28upx;
|
||||
}
|
||||
|
||||
&.n {
|
||||
font-size: 64upx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.n {
|
||||
font-size: 64upx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12upx;
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12upx;
|
||||
|
||||
.t {
|
||||
color: #86491d;
|
||||
font-size: 28upx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.t {
|
||||
color: #86491d;
|
||||
font-size: 28upx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
background-color: rgba(255, 255, 255, .3);
|
||||
padding: 40rpx 28rpx 0 28rpx;
|
||||
transform: translateY(-140rpx);
|
||||
border-radius: 74rpx 74rpx 0 0;
|
||||
}
|
||||
.bottom {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
padding: 40rpx 28rpx 0 28rpx;
|
||||
transform: translateY(-140rpx);
|
||||
border-radius: 74rpx 74rpx 0 0;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
column-gap: 20rpx;
|
||||
row-gap: 22rpx;
|
||||
.list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
column-gap: 20rpx;
|
||||
row-gap: 22rpx;
|
||||
|
||||
.item {
|
||||
padding: 36rpx 22rpx;
|
||||
border-radius: 42rpx;
|
||||
background: linear-gradient(180deg, #F5F5F5 58.54%, #FFF 140.47%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
border: 6rpx solid transparent;
|
||||
transition: all .3s ease-in-out;
|
||||
.item {
|
||||
padding: 36rpx 22rpx;
|
||||
border-radius: 42rpx;
|
||||
background: linear-gradient(180deg, #f5f5f5 58.54%, #fff 140.47%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
border: 6rpx solid transparent;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(180deg, #FFC29A -26.17%, #FFF 64.06%);
|
||||
border: 6rpx solid #FE6C0E;
|
||||
box-shadow: 0 0 31rpx 2rpx #fe8b435e;
|
||||
}
|
||||
&.active {
|
||||
background: linear-gradient(180deg, #ffc29a -26.17%, #fff 64.06%);
|
||||
border: 6rpx solid #fe6c0e;
|
||||
box-shadow: 0 0 31rpx 2rpx #fe8b435e;
|
||||
}
|
||||
|
||||
.sel {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
.sel {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
|
||||
transform: translateX(-50%) translateY(21rpx);
|
||||
transform: translateX(-50%) translateY(21rpx);
|
||||
|
||||
.image {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.image {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,338 +1,373 @@
|
||||
<template>
|
||||
<view class="Box">
|
||||
<view class="box_box">
|
||||
<!-- <view class="box_item flex-between">
|
||||
<view class="Box">
|
||||
<view class="box_box">
|
||||
<!-- <view class="box_item flex-between">
|
||||
<text class="top_box_one_text">当前账号</text>
|
||||
<input type="number" v-model="form.mobile" placeholder="请输入手机号" maxlength="11" disabled="disabled" />
|
||||
<button v-if="!form.mobile" class="getPhone" open-type="getPhoneNumber" @getphonenumber="getPhone">
|
||||
<view class="text">获取手机号</view>
|
||||
</button>
|
||||
</view> -->
|
||||
<view class="u-flex">
|
||||
<view class="u-p-r-40">
|
||||
<view class="color-333 font-16 font-700">密码支付</view>
|
||||
<view class="font-12 color-999 u-m-t-4">关闭时,使用余额支付将会直接扣除,不再进行确认</view>
|
||||
</view>
|
||||
<up-switch v-model="form.usePayPwd" active-color="#FFD158" :inactive-value="0" @change="usePayPwdChange"
|
||||
:active-value="1"></up-switch>
|
||||
</view>
|
||||
<view class="color-333 font-16 font-700" style="margin-top: 60rpx;">密码支付</view>
|
||||
<view class="box_item flex-between">
|
||||
<text class="top_box_one_text">当前账号</text>
|
||||
<input type="number" v-model="form.mobile" placeholder="请输入手机号" maxlength="11" :disabled="true" />
|
||||
<view class="top_box_one_texts">
|
||||
<view v-if="datalist.showText == true" class="yzm" @click="CodeRegister">{{ datalist.Recapture }}
|
||||
</view>
|
||||
<view v-else style="color: #ccc; background-color: #f9f9f9; border-radius:16px; padding: 10rpx;">
|
||||
{{ datalist.second }}s重新发送
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="box_item flex-between" style="position:relative;">
|
||||
<text class="top_box_one_text"></text>
|
||||
<input type="number" v-model="form.checkCode" placeholder="请输入验证码"
|
||||
style="padding-right: 140rpx;color: #333" />
|
||||
|
||||
</view>
|
||||
<view class="box_item flex-between">
|
||||
<text class="top_box_one_text">新密码</text>
|
||||
<input type="number" v-model="form.password" placeholder="请输入6位数字交易密码" maxlength="6"
|
||||
:password="!passwords" />
|
||||
</view>
|
||||
<view class="box_item flex-between">
|
||||
<text class="top_box_one_text">确认密码</text>
|
||||
<input type="number" v-model="form.payPassword" placeholder="请再次输入密码" maxlength="6"
|
||||
:password="!payPasswords" />
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="flex-colum">
|
||||
<view class="Box_bottom active"
|
||||
@click="userInfosavePayPassword">重置密码</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex">
|
||||
<view class="u-p-r-40">
|
||||
<view class="color-333 font-16 font-700">密码支付</view>
|
||||
<view class="font-12 color-999 u-m-t-4"
|
||||
>关闭时,使用余额支付将会直接扣除,不再进行确认</view
|
||||
>
|
||||
</view>
|
||||
<up-switch
|
||||
v-model="form.usePayPwd"
|
||||
active-color="#FFD158"
|
||||
:inactive-value="0"
|
||||
@change="usePayPwdChange"
|
||||
:active-value="1"
|
||||
></up-switch>
|
||||
</view>
|
||||
<view class="color-333 font-16 font-700" style="margin-top: 60rpx"
|
||||
>密码支付</view
|
||||
>
|
||||
<view class="box_item flex-between">
|
||||
<text class="top_box_one_text">当前账号</text>
|
||||
<input
|
||||
type="number"
|
||||
v-model="form.mobile"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
:disabled="false"
|
||||
/>
|
||||
<view class="top_box_one_texts">
|
||||
<view
|
||||
v-if="datalist.showText == true"
|
||||
class="yzm"
|
||||
@click="CodeRegister"
|
||||
>{{ datalist.Recapture }}
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
style="
|
||||
color: #ccc;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 16px;
|
||||
padding: 10rpx;
|
||||
"
|
||||
>
|
||||
{{ datalist.second }}s重新发送
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="box_item flex-between" style="position: relative">
|
||||
<text class="top_box_one_text"></text>
|
||||
<input
|
||||
type="number"
|
||||
v-model="form.checkCode"
|
||||
placeholder="请输入验证码"
|
||||
style="padding-right: 140rpx; color: #333"
|
||||
/>
|
||||
</view>
|
||||
<view class="box_item flex-between">
|
||||
<text class="top_box_one_text">新密码</text>
|
||||
<input
|
||||
type="number"
|
||||
v-model="form.password"
|
||||
placeholder="请输入6位数字交易密码"
|
||||
maxlength="6"
|
||||
:password="!passwords"
|
||||
/>
|
||||
</view>
|
||||
<view class="box_item flex-between">
|
||||
<text class="top_box_one_text">确认密码</text>
|
||||
<input
|
||||
type="number"
|
||||
v-model="form.payPassword"
|
||||
placeholder="请再次输入密码"
|
||||
maxlength="6"
|
||||
:password="!payPasswords"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-colum">
|
||||
<view class="Box_bottom active" @click="userInfosavePayPassword"
|
||||
>重置密码</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
reactive,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import { reactive, onMounted } from "vue";
|
||||
|
||||
import {
|
||||
APIuserpwd,
|
||||
APIusercode
|
||||
} from '@/common/api/api.js'
|
||||
import {
|
||||
APIshopUserInfo
|
||||
} from '@/common/api/member.js'
|
||||
import { APIuserpwd, APIusercode } from "@/common/api/api.js";
|
||||
import { APIshopUserInfo } from "@/common/api/member.js";
|
||||
|
||||
import {
|
||||
productStore
|
||||
} from '@/stores/user.js';
|
||||
import {APIuser} from '@/common/api/api.js'
|
||||
const storeuser = productStore();
|
||||
const shopUserInfo = reactive({
|
||||
shopInfo: uni.cache.get('orderVIP'),
|
||||
shopId: ''
|
||||
})
|
||||
|
||||
const userInfo=uni.cache.get('userInfo')
|
||||
import { productStore } from "@/stores/user.js";
|
||||
import { APIuser } from "@/common/api/api.js";
|
||||
const storeuser = productStore();
|
||||
const shopUserInfo = reactive({
|
||||
shopInfo: uni.cache.get("orderVIP"),
|
||||
shopId: "",
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
mobile: uni.cache.get('orderVIP').phone,
|
||||
password: '',
|
||||
payPassword: '',
|
||||
checkCode: '',
|
||||
usePayPwd: userInfo.usePayPwd
|
||||
})
|
||||
const userInfo = uni.cache.get("userInfo");
|
||||
|
||||
const datalist = reactive({
|
||||
isPwd: "",
|
||||
passwords: false,
|
||||
payPasswords: false,
|
||||
// 注册定时器 初始值
|
||||
second: 60,
|
||||
showText: true,
|
||||
Recapture: '获取验证码',
|
||||
})
|
||||
const form = reactive({
|
||||
mobile: uni.cache.get("orderVIP").phone,
|
||||
password: "",
|
||||
payPassword: "",
|
||||
checkCode: "",
|
||||
usePayPwd: userInfo.usePayPwd,
|
||||
});
|
||||
|
||||
const CodeRegister = async () => {
|
||||
console.log('CodeRegister');
|
||||
const res = await APIusercode({
|
||||
// post 手机验证码
|
||||
phone: form.mobile
|
||||
});
|
||||
if (res) {
|
||||
uni.showToast({
|
||||
title: '验证码获取成功',
|
||||
icon: 'none'
|
||||
});
|
||||
// 定时器
|
||||
datalist.showText = false;
|
||||
datalist.Recapture = '重新获取';
|
||||
var interval = setInterval(() => {
|
||||
let times = --datalist.second;
|
||||
datalist.second = times < 10 ? '0' + times : times; //小于10秒补 0
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
datalist.second = 60;
|
||||
datalist.showText = true;
|
||||
}, 60000);
|
||||
}
|
||||
}
|
||||
const datalist = reactive({
|
||||
isPwd: "",
|
||||
passwords: false,
|
||||
payPasswords: false,
|
||||
// 注册定时器 初始值
|
||||
second: 60,
|
||||
showText: true,
|
||||
Recapture: "获取验证码",
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取手机号
|
||||
* @param {Object} d
|
||||
*/
|
||||
const getPhone = async (d) => {
|
||||
if (d.detail.iv) {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: async (data) => {
|
||||
console.log(data)
|
||||
let res = await this.api.userwxlogins({
|
||||
code: data.code,
|
||||
encryptedData: d.detail.encryptedData,
|
||||
iv: d.detail.iv,
|
||||
})
|
||||
// form.mobile = res ? (res.slice(0, 3) + '****' + res.slice(7)) : '';
|
||||
form.mobile = res
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const loginwxuserInfo = async () => {
|
||||
let res = await this.api.loginwxuserInfo({
|
||||
userId: uni.cache.get('userInfo').id
|
||||
})
|
||||
if (res.code == 0) {
|
||||
uni.cache.set('userInfo', res.data);
|
||||
}
|
||||
}
|
||||
const CodeRegister = async () => {
|
||||
console.log("CodeRegister");
|
||||
const res = await APIusercode({
|
||||
// post 手机验证码
|
||||
phone: form.mobile,
|
||||
});
|
||||
if (res) {
|
||||
uni.showToast({
|
||||
title: "验证码获取成功",
|
||||
icon: "none",
|
||||
});
|
||||
// 定时器
|
||||
datalist.showText = false;
|
||||
datalist.Recapture = "重新获取";
|
||||
var interval = setInterval(() => {
|
||||
let times = --datalist.second;
|
||||
datalist.second = times < 10 ? "0" + times : times; //小于10秒补 0
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
datalist.second = 60;
|
||||
datalist.showText = true;
|
||||
}, 60000);
|
||||
}
|
||||
};
|
||||
|
||||
const userInfosavePayPassword = async () => {
|
||||
if (form.mobile.length != 11) {
|
||||
uni.showToast({
|
||||
title: '手机号必须是11位',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.password == null || form.password == '') {
|
||||
uni.showToast({
|
||||
title: '请输入密码',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.payPassword == null || form.payPassword == '') {
|
||||
uni.showToast({
|
||||
title: '请输入确认密码',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.password.length != 6 || form.payPassword.length != 6) {
|
||||
uni.showToast({
|
||||
title: '密码必须是6位',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.payPassword != form.password) {
|
||||
uni.showToast({
|
||||
title: '密码和确认密码不一致',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.checkCode == null || form.checkCode == '') {
|
||||
uni.showToast({
|
||||
title: '请输入验证码',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 获取手机号
|
||||
* @param {Object} d
|
||||
*/
|
||||
const getPhone = async (d) => {
|
||||
if (d.detail.iv) {
|
||||
uni.login({
|
||||
provider: "weixin",
|
||||
success: async (data) => {
|
||||
console.log(data);
|
||||
let res = await this.api.userwxlogins({
|
||||
code: data.code,
|
||||
encryptedData: d.detail.encryptedData,
|
||||
iv: d.detail.iv,
|
||||
});
|
||||
// form.mobile = res ? (res.slice(0, 3) + '****' + res.slice(7)) : '';
|
||||
form.mobile = res;
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
const loginwxuserInfo = async () => {
|
||||
let res = await this.api.loginwxuserInfo({
|
||||
userId: uni.cache.get("userInfo").id,
|
||||
});
|
||||
if (res.code == 0) {
|
||||
uni.cache.set("userInfo", res.data);
|
||||
}
|
||||
};
|
||||
|
||||
let res = await APIuserpwd({
|
||||
checkPayPwd: form.payPassword,
|
||||
payPwd: form.password,
|
||||
code: form.checkCode,
|
||||
usePayPwd:form.usePayPwd
|
||||
})
|
||||
// * 获取会员信息
|
||||
await storeuser.actionsproductqueryProduct()
|
||||
if (res) {
|
||||
uni.navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
async function usePayPwdChange(){
|
||||
uni.showLoading()
|
||||
await APIuserpwd({
|
||||
usePayPwd:form.usePayPwd
|
||||
})
|
||||
uni.showToast({
|
||||
title:'修改成功'
|
||||
})
|
||||
}
|
||||
onMounted(()=>{
|
||||
APIuser().then(res=>{
|
||||
uni.cache.set('userInfo', res);
|
||||
form.usePayPwd=res.usePayPwd
|
||||
})
|
||||
})
|
||||
const userInfosavePayPassword = async () => {
|
||||
if (form.mobile.length != 11) {
|
||||
uni.showToast({
|
||||
title: "手机号必须是11位",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.password == null || form.password == "") {
|
||||
uni.showToast({
|
||||
title: "请输入密码",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.payPassword == null || form.payPassword == "") {
|
||||
uni.showToast({
|
||||
title: "请输入确认密码",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.password.length != 6 || form.payPassword.length != 6) {
|
||||
uni.showToast({
|
||||
title: "密码必须是6位",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.payPassword != form.password) {
|
||||
uni.showToast({
|
||||
title: "密码和确认密码不一致",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (form.checkCode == null || form.checkCode == "") {
|
||||
uni.showToast({
|
||||
title: "请输入验证码",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let res = await APIuserpwd({
|
||||
checkPayPwd: form.payPassword,
|
||||
payPwd: form.password,
|
||||
code: form.checkCode,
|
||||
usePayPwd: form.usePayPwd,
|
||||
});
|
||||
// * 获取会员信息
|
||||
await storeuser.actionsproductqueryProduct();
|
||||
if (res) {
|
||||
const userInfo = uni.cache.get("userInfo");
|
||||
const orderVIP = uni.cache.get("orderVIP");
|
||||
uni.cache.set("userInfo", { ...userInfo, usePayPwd: form.usePayPwd });
|
||||
uni.cache.set("orderVIP", { ...orderVIP, payPwd: 'abcd' });
|
||||
uni.navigateBack();
|
||||
}
|
||||
};
|
||||
|
||||
async function usePayPwdChange() {
|
||||
uni.showLoading();
|
||||
const res = await APIuserpwd({
|
||||
usePayPwd: form.usePayPwd,
|
||||
});
|
||||
if (res) {
|
||||
const userInfo = uni.cache.get("userInfo");
|
||||
uni.cache.set("userInfo", { ...userInfo, usePayPwd: form.usePayPwd });
|
||||
|
||||
uni.showToast({
|
||||
title: "修改成功",
|
||||
});
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
APIuser().then((res) => {
|
||||
uni.cache.set("userInfo", res);
|
||||
form.usePayPwd = res.usePayPwd;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.Box {
|
||||
padding: 80rpx 28rpx 28rpx;
|
||||
.Box {
|
||||
padding: 80rpx 28rpx 28rpx;
|
||||
|
||||
.box_one {
|
||||
margin-top: 26rpx;
|
||||
font-size: 24rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
}
|
||||
.box_one {
|
||||
margin-top: 26rpx;
|
||||
font-size: 24rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.box_box {
|
||||
background: #fff;
|
||||
.box_box {
|
||||
background: #fff;
|
||||
|
||||
.box_item {
|
||||
// border-bottom: 1rpx solid #E5E5E5;
|
||||
position: relative;
|
||||
.box_item {
|
||||
// border-bottom: 1rpx solid #E5E5E5;
|
||||
position: relative;
|
||||
|
||||
.top_box_one_text {
|
||||
font-family: Source Han Sans CN, Source Han Sans CN;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
.top_box_one_text {
|
||||
font-family: Source Han Sans CN, Source Han Sans CN;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 26rpx 0;
|
||||
width: 75%;
|
||||
font-size: 28rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
}
|
||||
input {
|
||||
padding: 26rpx 0;
|
||||
width: 75%;
|
||||
font-size: 28rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.top_box_one_texts {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
font-size: 24rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 400;
|
||||
color: #5082fd;
|
||||
z-index: 100;
|
||||
.yzm {
|
||||
padding: 8rpx 20rpx;
|
||||
background: #ffd158;
|
||||
border-radius: 24rpx;
|
||||
font-weight: 500;
|
||||
font-size: 24rpx;
|
||||
color: #5f2e0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top_box_one_texts {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
font-size: 24rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 400;
|
||||
color: #5082fd;
|
||||
z-index: 100;
|
||||
.yzm {
|
||||
padding: 8rpx 20rpx;
|
||||
background: #FFD158;
|
||||
border-radius: 24rpx;
|
||||
font-weight: 500;
|
||||
font-size: 24rpx;
|
||||
color: #5F2E0F;
|
||||
}
|
||||
}
|
||||
}
|
||||
.box_item:nth-child(1) {
|
||||
border-bottom: none;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
|
||||
.box_item:nth-child(1) {
|
||||
border-bottom: none;
|
||||
border-top: 1rpx solid #E5E5E5;
|
||||
input {
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
border-bottom: 1rpx solid #E5E5E5;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
.flex-colum {
|
||||
width: 100%;
|
||||
margin-top: 56rpx;
|
||||
|
||||
.Box_bottom {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background-color: #ffd158;
|
||||
border-radius: 48rpx;
|
||||
font-size: 36rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-colum {
|
||||
width: 100%;
|
||||
margin-top: 56rpx;
|
||||
|
||||
.Box_bottom {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background-color: #FFD158;
|
||||
border-radius: 48rpx;
|
||||
font-size: 36rpx;
|
||||
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.getPhone {
|
||||
line-height: initial;
|
||||
background-color: none;
|
||||
border: 2rpx solid #E3AD7F;
|
||||
color: #E3AD7F;
|
||||
font-size: 28rpx;
|
||||
padding: 5rpx 10rpx;
|
||||
position: absolute;
|
||||
right: 10rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
.Box .box_box .box_item uni-input{
|
||||
border-bottom: 1rpx solid #E5E5E5;
|
||||
}
|
||||
.Box .box_box .box_item input{
|
||||
border-bottom: 1rpx solid #E5E5E5;
|
||||
}
|
||||
:deep(.Box .box_box .box_item uni-input){
|
||||
border-bottom: 1rpx solid #E5E5E5;
|
||||
}
|
||||
.getPhone {
|
||||
line-height: initial;
|
||||
background-color: none;
|
||||
border: 2rpx solid #e3ad7f;
|
||||
color: #e3ad7f;
|
||||
font-size: 28rpx;
|
||||
padding: 5rpx 10rpx;
|
||||
position: absolute;
|
||||
right: 10rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
.Box .box_box .box_item uni-input {
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
.Box .box_box .box_item input {
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
:deep(.Box .box_box .box_item uni-input) {
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
</style>
|
||||
59
pnpm-lock.yaml
generated
@@ -30,8 +30,8 @@ importers:
|
||||
specifier: ^0.1.2
|
||||
version: 0.1.2
|
||||
ysk-utils:
|
||||
specifier: ^1.0.40
|
||||
version: 1.0.40
|
||||
specifier: ^1.0.78
|
||||
version: 1.0.78
|
||||
|
||||
packages:
|
||||
|
||||
@@ -39,17 +39,17 @@ packages:
|
||||
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.27.1':
|
||||
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
||||
'@babel/helper-validator-identifier@7.28.5':
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.28.4':
|
||||
resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
|
||||
'@babel/parser@7.28.5':
|
||||
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/types@7.28.4':
|
||||
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
|
||||
'@babel/types@7.28.5':
|
||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@dcloudio/types@3.4.21':
|
||||
@@ -104,8 +104,8 @@ packages:
|
||||
bignumber.js@9.3.1:
|
||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
dayjs@1.11.18:
|
||||
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
|
||||
@@ -120,11 +120,15 @@ packages:
|
||||
jsbarcode@3.12.1:
|
||||
resolution: {integrity: sha512-QZQSqIknC2Rr/YOUyOkCBqsoiBAOTYK+7yNN3JsqfoUtJtkazxNw1dmPpxuv7VVvqW13kA3/mKiLq+s/e3o9hQ==}
|
||||
|
||||
loadsh@0.0.4:
|
||||
resolution: {integrity: sha512-U+wLL8InpfRalWrr+0SuhWgGt10M4OyAk6G8xCYo2rwpiHtxZkWiFpjei0vO463ghW8LPCdhqQxXlMy2qicAEw==}
|
||||
deprecated: This is a typosquat on the popular Lodash package. This is not maintained nor is the original Lodash package.
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
magic-string@0.30.19:
|
||||
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
@@ -173,23 +177,23 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
ysk-utils@1.0.40:
|
||||
resolution: {integrity: sha512-Hi+XI7sykGJizExMY5kVWIBhPvO3wudp2q92Vqv6zI6eDtquLz4CwxIfOLEXFfn5crYXVu/yfJQVjK3WAyNYDQ==}
|
||||
ysk-utils@1.0.78:
|
||||
resolution: {integrity: sha512-Bgr5B3WWiy0nbgL91QVKoVPYm4wt13Rlav757zEjMVRHbmTjwFEhi3wJlYus0JGd52mbknSxXHMazAPHXwA7uQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/helper-string-parser@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.27.1': {}
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@babel/parser@7.28.4':
|
||||
'@babel/parser@7.28.5':
|
||||
dependencies:
|
||||
'@babel/types': 7.28.4
|
||||
'@babel/types': 7.28.5
|
||||
|
||||
'@babel/types@7.28.4':
|
||||
'@babel/types@7.28.5':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@dcloudio/types@3.4.21': {}
|
||||
|
||||
@@ -202,7 +206,7 @@ snapshots:
|
||||
|
||||
'@vue/compiler-core@3.5.22':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.4
|
||||
'@babel/parser': 7.28.5
|
||||
'@vue/shared': 3.5.22
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
@@ -215,13 +219,13 @@ snapshots:
|
||||
|
||||
'@vue/compiler-sfc@3.5.22':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.4
|
||||
'@babel/parser': 7.28.5
|
||||
'@vue/compiler-core': 3.5.22
|
||||
'@vue/compiler-dom': 3.5.22
|
||||
'@vue/compiler-ssr': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.19
|
||||
magic-string: 0.30.21
|
||||
postcss: 8.5.6
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -250,7 +254,7 @@ snapshots:
|
||||
'@vue/reactivity': 3.5.22
|
||||
'@vue/runtime-core': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
csstype: 3.1.3
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vue/server-renderer@3.5.22(vue@3.5.22)':
|
||||
dependencies:
|
||||
@@ -262,7 +266,7 @@ snapshots:
|
||||
|
||||
bignumber.js@9.3.1: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
csstype@3.2.3: {}
|
||||
|
||||
dayjs@1.11.18: {}
|
||||
|
||||
@@ -272,9 +276,11 @@ snapshots:
|
||||
|
||||
jsbarcode@3.12.1: {}
|
||||
|
||||
loadsh@0.0.4: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
magic-string@0.30.19:
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
@@ -314,7 +320,8 @@ snapshots:
|
||||
'@vue/server-renderer': 3.5.22(vue@3.5.22)
|
||||
'@vue/shared': 3.5.22
|
||||
|
||||
ysk-utils@1.0.40:
|
||||
ysk-utils@1.0.78:
|
||||
dependencies:
|
||||
bignumber.js: 9.3.1
|
||||
loadsh: 0.0.4
|
||||
lodash: 4.17.21
|
||||
|
||||
|
Before Width: | Height: | Size: 619 B After Width: | Height: | Size: 596 B |
1
static/icon/fenxiao.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761788431710" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5024" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M381.952 539.136h-31.744v-16.384h31.744c14.848 0 26.112-8.704 26.112-19.968s-11.776-19.968-26.112-19.968h-17.408l38.4-66.048c4.096-6.656 5.12-14.848 3.584-21.504-1.024-5.12-4.608-9.728-9.216-12.288-10.24-5.632-25.088 0-31.744 12.288L338.432 445.44c-9.728 17.92-10.24 19.456-10.24 19.456l-0.512 1.024c-12.8-25.088-36.352-68.608-37.376-71.168-6.656-12.288-22.016-17.92-32.256-11.776-4.608 3.072-8.192 7.68-9.728 12.8-2.048 6.656-0.512 14.336 3.072 20.992l35.84 61.952 2.048 3.584h-18.944c-14.848 0-26.112 8.704-26.112 19.968s11.776 19.968 26.112 19.968h33.792v16.384h-33.792c-14.848 0-26.112 8.704-26.112 20.48 0 10.752 10.24 18.944 24.576 19.968h35.84v21.504c0 14.336 10.24 26.112 23.04 26.112s23.04-11.264 23.04-25.6V578.56h36.864c12.8-2.048 20.992-9.728 20.992-19.456-0.512-11.264-11.776-19.968-26.624-19.968z" fill="#333333" p-id="5025"></path><path d="M817.152 354.304c82.944 0 150.528-67.584 150.528-150.528s-67.584-150.528-150.528-150.528-150.528 67.584-150.528 150.528c0 13.312 1.536 25.6 4.608 37.888l-138.752 92.16c-5.632-6.656-11.264-13.312-17.92-19.456-50.688-50.688-117.76-78.336-189.44-78.336s-138.752 27.648-189.44 78.336c-50.688 50.688-78.336 117.76-78.336 189.44s27.648 138.752 78.336 189.44c50.688 50.688 117.76 78.336 189.44 78.336 71.168 0 138.24-27.648 188.928-77.824l146.944 102.4c-6.144 14.336-9.216 30.72-9.216 47.104 0 23.04 6.144 44.544 17.408 62.976h-139.776c-16.384 0-30.208 13.312-30.208 30.208 0 16.384 13.312 30.208 30.208 30.208h247.296c67.584-1.024 121.856-55.808 121.856-123.392 0-68.096-55.296-123.392-123.392-123.392-29.696 0-56.832 10.24-77.824 27.648l-145.408-101.376c26.624-42.496 40.96-91.648 40.96-142.848 0-41.984-9.728-82.944-27.648-119.296l133.12-88.576c28.16 35.84 70.656 58.88 118.784 58.88z m0-248.832c54.272 0 98.304 44.032 98.304 98.304S871.424 302.08 817.152 302.08s-98.304-44.032-98.304-98.304 44.032-98.304 98.304-98.304z m-41.472 674.816c34.816 0 62.976 28.16 62.976 62.976s-28.16 62.976-62.976 62.976-62.976-28.16-62.976-62.976 28.16-62.976 62.976-62.976zM326.144 711.68c-114.688 0-207.872-93.184-207.872-207.872 0-114.688 93.184-207.872 207.872-207.872s207.872 93.184 207.872 207.872C533.504 618.496 440.32 711.68 326.144 711.68z" fill="#333333" p-id="5026"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
BIN
static/icon/jifen.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
static/icon/newUserDiscount.png
Normal file
|
After Width: | Height: | Size: 502 B |
1174
stores/carts.js
@@ -23,10 +23,7 @@ export const Memberpay = defineStore('memberpay', {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let res = await APIpayltPayVip({
|
||||
shopId: data.shopId,
|
||||
shopUserId: data.shopUserId,
|
||||
amount: data.amount,
|
||||
activateId: data.activateId,
|
||||
...data,
|
||||
// #ifdef MP-WEIXIN
|
||||
payType: 'wechatPay',
|
||||
openId: uni.cache.get('userInfo').wechatOpenId,
|
||||
@@ -35,11 +32,6 @@ export const Memberpay = defineStore('memberpay', {
|
||||
payType: 'aliPay',
|
||||
openId: uni.cache.get('userInfo').wechatOpenId,
|
||||
// #endif
|
||||
returnUrl: data.returnUrl ? data.returnUrl : '',
|
||||
buyerRemark: data.buyerRemark ? data.buyerRemark : '',
|
||||
orderId: data.orderId,
|
||||
userAllPack: data.userAllPack,
|
||||
seatNum:data.seatNum
|
||||
})
|
||||
if (res) {
|
||||
uni.showLoading({
|
||||
@@ -67,20 +59,20 @@ export const Memberpay = defineStore('memberpay', {
|
||||
title: "支付成功"
|
||||
})
|
||||
console.log('支付成功')
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1000)
|
||||
resolve(res)
|
||||
// setTimeout(() => {
|
||||
// uni.navigateBack()
|
||||
// }, 1000)
|
||||
resolve(true)
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
if (res.resultCode == '9000') {
|
||||
uni.showToast({
|
||||
title: "支付成功"
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1000)
|
||||
resolve(res)
|
||||
// setTimeout(() => {
|
||||
// uni.navigateBack()
|
||||
// }, 1000)
|
||||
resolve(true)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "支付失败"
|
||||
@@ -133,6 +125,10 @@ export const Memberpay = defineStore('memberpay', {
|
||||
userId: uni.cache.get('userInfo').id || ''
|
||||
})
|
||||
console.log('actionsltPayOrder:res',res);
|
||||
if(typeof res ==='string'){
|
||||
resolve(res)
|
||||
return
|
||||
}
|
||||
if(!res){
|
||||
console.log('支付失败');
|
||||
reject(false)
|
||||
@@ -163,14 +159,14 @@ export const Memberpay = defineStore('memberpay', {
|
||||
title: "支付成功"
|
||||
})
|
||||
console.log('支付成功')
|
||||
resolve(res)
|
||||
resolve(true)
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
if (res.resultCode == '9000') {
|
||||
uni.showToast({
|
||||
title: "支付成功"
|
||||
})
|
||||
resolve(res)
|
||||
resolve(true)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "支付失败"
|
||||
@@ -232,8 +228,10 @@ export const Memberpay = defineStore('memberpay', {
|
||||
|
||||
// 生成订单
|
||||
actionscreateOrder(data) {
|
||||
console.log('actionscreateOrder:生成订单',data);
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let res = await APIcreateOrder({
|
||||
...data,
|
||||
orderId: data.orderId, //多次下单时使用
|
||||
shopId: uni.cache.get('shopId'), //店铺Id
|
||||
userId: uni.cache.get('userInfo').id || '', //
|
||||
@@ -246,7 +244,6 @@ export const Memberpay = defineStore('memberpay', {
|
||||
placeNum: data.placeNum, //当前订单下单次数
|
||||
waitCall: data.waitCall //是否等叫 0 否 1 等叫
|
||||
})
|
||||
console.log('actionscreateOrder res:');
|
||||
console.log(res);
|
||||
if (res) {
|
||||
resolve(res)
|
||||
|
||||
672
stores/user.js
@@ -1,270 +1,432 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { APIuserlogin, APIuser } from "@/common/api/api.js";
|
||||
import {
|
||||
defineStore
|
||||
} from 'pinia';
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
import {
|
||||
APIuserlogin,
|
||||
APIuser
|
||||
} from '@/common/api/api.js'
|
||||
import {
|
||||
APIproductqueryShop,
|
||||
APIusershopInfodetail,
|
||||
APIshopUserInfo
|
||||
} from '@/common/api/member.js'
|
||||
export const Storelogin = defineStore('login', {
|
||||
state: () => ({
|
||||
token: '',
|
||||
miniAppOpenId: '',
|
||||
userInfo: '',
|
||||
shopInfo: {}
|
||||
}),
|
||||
actions: {
|
||||
async getShopInfo(shopId) {
|
||||
const shopRes = await APIusershopInfodetail({
|
||||
shopId
|
||||
})
|
||||
console.log(shopRes);
|
||||
},
|
||||
actionslogin() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: (data) => {
|
||||
// 微信小程序环境
|
||||
uni.getUserInfo({
|
||||
provider: 'weixin',
|
||||
success: async (infoRes) => {
|
||||
let res = await APIuserlogin({
|
||||
code: data.code, //临时登录凭证
|
||||
rawData: infoRes.rawData,
|
||||
source: 'wechat'
|
||||
})
|
||||
if (res) {
|
||||
this.token = res.token
|
||||
this.miniAppOpenId = res.userInfo
|
||||
.miniAppOpenId
|
||||
this.userInfo = res.userInfo
|
||||
uni.cache.set('token', res.token);
|
||||
uni.cache.set('userInfo', res.userInfo);
|
||||
}
|
||||
resolve(true);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
my.getAuthCode({
|
||||
scopes: 'auth_base',
|
||||
success: async (data) => {
|
||||
// 支付宝小程序环境
|
||||
// my.getAuthUserInfo({
|
||||
// success: async (infoRes) => {
|
||||
let res = await APIuserlogin({
|
||||
code: data.authCode, //临时登录凭证
|
||||
// rawData: JSON.stringify(infoRes),
|
||||
source: 'alipay'
|
||||
})
|
||||
if (res) {
|
||||
this.token = res.token
|
||||
this.miniAppOpenId = res.userInfo.miniAppOpenId
|
||||
this.userInfo = res.userInfo
|
||||
uni.cache.set('token', res.token);
|
||||
uni.cache.set('openId', res.userInfo
|
||||
.alipayOpenId)
|
||||
uni.cache.set('userInfo', res.userInfo);
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
reject(false);
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
}
|
||||
APIproductqueryShop,
|
||||
APIusershopInfodetail,
|
||||
APIshopUserInfo,
|
||||
} from "@/common/api/member.js";
|
||||
import { getDistance } from "@/utils/address.js";
|
||||
import { APIgeocodelocation } from "@/common/api/api.js";
|
||||
|
||||
export const Storelogin = defineStore("login", {
|
||||
state: () => ({
|
||||
token: "",
|
||||
miniAppOpenId: "",
|
||||
userInfo: "",
|
||||
shopInfo: {},
|
||||
}),
|
||||
actions: {
|
||||
async getShopInfo(shopId) {
|
||||
const shopRes = await APIusershopInfodetail({
|
||||
shopId,
|
||||
});
|
||||
console.log(shopRes);
|
||||
},
|
||||
actionslogin() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.login({
|
||||
provider: "weixin",
|
||||
success: (data) => {
|
||||
// 微信小程序环境
|
||||
uni.getUserInfo({
|
||||
provider: "weixin",
|
||||
success: async (infoRes) => {
|
||||
let res = await APIuserlogin({
|
||||
code: data.code, //临时登录凭证
|
||||
rawData: infoRes.rawData,
|
||||
source: "wechat",
|
||||
});
|
||||
if (res) {
|
||||
this.token = res.token;
|
||||
this.miniAppOpenId = res.userInfo.miniAppOpenId;
|
||||
this.userInfo = res.userInfo;
|
||||
uni.cache.set("token", res.token);
|
||||
uni.cache.set("userInfo", res.userInfo);
|
||||
uni.cache.set("followIndex", res.followIndex || "");
|
||||
}
|
||||
resolve(true);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(false);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
my.getAuthCode({
|
||||
scopes: "auth_base",
|
||||
success: async (data) => {
|
||||
// 支付宝小程序环境
|
||||
// my.getAuthUserInfo({
|
||||
// success: async (infoRes) => {
|
||||
let res = await APIuserlogin({
|
||||
code: data.authCode, //临时登录凭证
|
||||
// rawData: JSON.stringify(infoRes),
|
||||
source: "alipay",
|
||||
});
|
||||
if (res) {
|
||||
this.token = res.token;
|
||||
this.miniAppOpenId = res.userInfo.miniAppOpenId;
|
||||
this.userInfo = res.userInfo;
|
||||
uni.cache.set("token", res.token);
|
||||
uni.cache.set("openId", res.userInfo.alipayOpenId);
|
||||
uni.cache.set("userInfo", res.userInfo);
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
reject(false);
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const productStore = defineStore('product', {
|
||||
actions: {
|
||||
getQueryString(url, name) { //解码
|
||||
var reg = new RegExp('(^|&|/?)' + name + '=([^&|/?]*)(&|/?|$)', 'i')
|
||||
var r = url.substr(1).match(reg)
|
||||
if (r != null) {
|
||||
return r[2]
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// 扫码请求
|
||||
scanCodeactions(q) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (q) {
|
||||
console.log(q)
|
||||
let tableCode = ""
|
||||
// #ifdef MP-WEIXIN
|
||||
tableCode = this.getQueryString(decodeURIComponent(q), 'code')
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
tableCode = q
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
tableCode = q.tableCode
|
||||
// #endif
|
||||
console.log(tableCode);
|
||||
// 储存卓玛
|
||||
uni.cache.set('tableCode', tableCode)
|
||||
if (tableCode) {
|
||||
console.log(uni.cache.get('tableCode'));
|
||||
let data = await this.actionsproductqueryShop(tableCode)
|
||||
|
||||
console.log('data', data)
|
||||
// -4请求登录
|
||||
if (data.code == '500') {
|
||||
if (await this.actionslogin()) {
|
||||
// 成功 接着在调用
|
||||
await this.actionsproductqueryShop()
|
||||
}
|
||||
}
|
||||
// 是否免除桌位费 0否1是
|
||||
if (uni.cache.get('shopInfo').isTableFee == 0) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/product/choosetable'
|
||||
});
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/product/index'
|
||||
});
|
||||
}
|
||||
export const productStore = defineStore("product", {
|
||||
state: () => ({
|
||||
location: {
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
},
|
||||
shopInfo: {
|
||||
shopId: "",
|
||||
isOrderFence: 0,
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
getLocation() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log("获取经纬度");
|
||||
uni.getLocation({
|
||||
type: "wgs84",
|
||||
altitude: true,
|
||||
isHighAccuracy: true,
|
||||
success: (res) => {
|
||||
console.log("获取经纬度成功", res);
|
||||
|
||||
}
|
||||
} else {
|
||||
// #ifdef APP || MP-WEIXIN || MP-ALIPAY
|
||||
uni.scanCode({
|
||||
success: async (res) => {
|
||||
let tableCode = this.getQueryString(
|
||||
decodeURIComponent(res.result), 'code')
|
||||
// 储存卓玛
|
||||
uni.cache.set('tableCode', tableCode)
|
||||
if (tableCode) {
|
||||
let data = await this.actionsproductqueryShop()
|
||||
if (!data) {
|
||||
uni.showToast({
|
||||
title: '店铺已过期或其他问题,请联系商家',
|
||||
icon:'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
// -4请求登录
|
||||
const store = Storelogin()
|
||||
if (data.code == '-4') {
|
||||
if (await store.actionslogin()) {
|
||||
// 成功 接着在调用
|
||||
await this.actionsproductqueryShop()
|
||||
}
|
||||
}
|
||||
// 是否免除桌位费 0否1是
|
||||
if (uni.cache.get('shopInfo').isTableFee == 0) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/product/choosetable'
|
||||
});
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/product/index'
|
||||
});
|
||||
}
|
||||
this.location = res;
|
||||
this.APIgeocodelocation();
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("获取经纬度失败", err);
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
async APIgeocodelocation() {
|
||||
let successres = await APIgeocodelocation({
|
||||
lng: this.location.longitude,
|
||||
lat: this.location.latitude,
|
||||
});
|
||||
if (successres) {
|
||||
let datastorage = {
|
||||
country: successres.addressComponent.country, // "中国"
|
||||
province: successres.addressComponent.province, //province: "陕西省"
|
||||
address: successres.addressComponent.city, //district: "西安市"
|
||||
district: successres.addressComponent.district, //district: "未央区"
|
||||
lng: this.location.longitude,
|
||||
lat: this.location.latitude,
|
||||
};
|
||||
uni.cache.set("getLocationstorage", datastorage);
|
||||
}
|
||||
},
|
||||
getQueryString(url, name) {
|
||||
//解码
|
||||
var reg = new RegExp("(^|&|/?)" + name + "=([^&|/?]*)(&|/?|$)", "i");
|
||||
var r = url.substr(1).match(reg);
|
||||
if (r != null) {
|
||||
return r[2];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log(res)
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 扫码请求
|
||||
* @param {*} q
|
||||
* @returns
|
||||
*/
|
||||
async scanCodeactions(q) {
|
||||
console.log("扫码内容", q);
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (q) {
|
||||
console.log(q);
|
||||
let tableCode = "";
|
||||
// #ifdef MP-WEIXIN
|
||||
tableCode = this.getQueryString(decodeURIComponent(q), "code");
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
tableCode = q;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
tableCode = q.tableCode;
|
||||
// #endif
|
||||
console.log(tableCode);
|
||||
// 储存卓玛
|
||||
uni.cache.set("tableCode", tableCode);
|
||||
if (tableCode) {
|
||||
console.log("台桌码", uni.cache.get("tableCode"));
|
||||
let data = await this.actionsproductqueryShop(tableCode);
|
||||
|
||||
})
|
||||
},
|
||||
console.log("data", data);
|
||||
// -4请求登录
|
||||
if (data.code == "500") {
|
||||
if (await this.actionslogin()) {
|
||||
// 成功 接着在调用
|
||||
await this.actionsproductqueryShop();
|
||||
}
|
||||
}
|
||||
|
||||
// /通过桌码获取当前店铺信息
|
||||
actionsproductqueryShop(tableCode) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// try {
|
||||
try {
|
||||
let res = await APIproductqueryShop({
|
||||
tableCode: tableCode ? tableCode : uni.cache.get('tableCode'),
|
||||
})
|
||||
if (res) {
|
||||
res.shopInfo.isVip = res.vip ? '1' : '0'
|
||||
res.shopTable.shopExtendMap = res.shopExtendMap
|
||||
// 店铺信息
|
||||
uni.cache.set('shopTable', res.shopTable)
|
||||
// 台桌信息
|
||||
uni.cache.set('shopInfo', res.shopInfo)
|
||||
uni.cache.set('shopId', res.shopTable.shopId, 30)
|
||||
// 当前用户距离店铺的米数
|
||||
uni.cache.set('distance', res.distance)
|
||||
if (this.shopInfo.isOrderFence == 0) {
|
||||
this.jumpToOrderPage();
|
||||
return;
|
||||
}
|
||||
const canGetLocation = await this.openLocationAuth();
|
||||
if (canGetLocation) {
|
||||
const canOrder = await this.computedDistance();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// #ifdef APP || MP-WEIXIN || MP-ALIPAY
|
||||
uni.scanCode({
|
||||
success: async (res) => {
|
||||
let tableCode = this.getQueryString(
|
||||
decodeURIComponent(res.result),
|
||||
"code"
|
||||
);
|
||||
// 储存卓玛
|
||||
uni.cache.set("tableCode", tableCode);
|
||||
if (tableCode) {
|
||||
let data = await this.actionsproductqueryShop();
|
||||
if (!data) {
|
||||
uni.showToast({
|
||||
title: "店铺已过期或其他问题,请联系商家",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// -4请求登录
|
||||
const store = Storelogin();
|
||||
if (data.code == "-4") {
|
||||
if (await store.actionslogin()) {
|
||||
// 成功 接着在调用
|
||||
await this.actionsproductqueryShop();
|
||||
}
|
||||
}
|
||||
if (this.shopInfo.isOrderFence == 0) {
|
||||
this.jumpToOrderPage();
|
||||
return;
|
||||
}
|
||||
const canGetLocation = await this.openLocationAuth();
|
||||
if (canGetLocation) {
|
||||
const canOrder = await this.computedDistance();
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log(res);
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
});
|
||||
},
|
||||
async openLocationAuth() {
|
||||
try {
|
||||
// 1. 检查当前位置授权状态
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getSetting({
|
||||
success: (settingRes) => {
|
||||
if (settingRes.authSetting["scope.userLocation"]) {
|
||||
// 2. 已授权:直接获取位置
|
||||
resolve(true);
|
||||
} else if (
|
||||
settingRes.authSetting["scope.userLocation"] === undefined
|
||||
) {
|
||||
// 3. 未请求过授权:发起授权请求
|
||||
uni
|
||||
.authorize({ scope: "scope.userLocation" })
|
||||
.then((authRes) => {
|
||||
if (authRes.errMsg === "authorize:ok") {
|
||||
// 授权成功后获取位置
|
||||
resolve(true);
|
||||
} else {
|
||||
reject(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 4. 已拒绝授权:提示用户去设置页开启
|
||||
uni.showModal({
|
||||
title: "开启定位",
|
||||
content: "请允许“零点八零”使用您的位置,方便您进入店铺点餐",
|
||||
confirmText: "开启定位",
|
||||
cancelText: "取消",
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
// 跳转微信小程序授权设置页
|
||||
uni.openSetting({
|
||||
success: async (openRes) => {
|
||||
// 用户在设置页开启授权后,再次获取位置
|
||||
if (openRes.authSetting["scope.userLocation"]) {
|
||||
resolve(true);
|
||||
} else {
|
||||
reject(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("位置授权失败:", err);
|
||||
uni.showToast({ title: "授权失败,请稍后重试", icon: "none" });
|
||||
}
|
||||
},
|
||||
//计算距离判断是否可以点餐
|
||||
async computedDistance() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log("店铺经纬度", this.shopInfo.lat, this.shopInfo.lng);
|
||||
console.log(
|
||||
"用户经纬度",
|
||||
this.location.latitude,
|
||||
this.location.longitude
|
||||
);
|
||||
|
||||
resolve(res)
|
||||
} else {
|
||||
console.log(res)
|
||||
reject()
|
||||
}
|
||||
const juli = getDistance(
|
||||
this.location.latitude,
|
||||
this.location.longitude,
|
||||
this.shopInfo.lat,
|
||||
this.shopInfo.lng
|
||||
);
|
||||
const orderFenceDistance = (
|
||||
(this.shopInfo.orderFenceDistance || 2000) / 2000
|
||||
).toFixed(2);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
reject()
|
||||
//TODO handle the exception
|
||||
}
|
||||
console.log("距离", juli);
|
||||
|
||||
// } catch (e) {
|
||||
// reject(false)
|
||||
// }
|
||||
if (this.shopInfo.isOrderFence && juli > orderFenceDistance) {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
confirmText: "重新定位",
|
||||
content:
|
||||
"抱歉,您当前距离店铺过远,为保障您的用餐体验,请您到店后或在门店附近再下单。若您已在店铺附近,可尝试重新定位",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
console.log("用户点击了确认");
|
||||
await this.getLocation();
|
||||
this.computedDistance();
|
||||
} else if (res.cancel) {
|
||||
console.log("用户点击了取消");
|
||||
}
|
||||
},
|
||||
});
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
this.jumpToOrderPage();
|
||||
resolve(juli);
|
||||
});
|
||||
},
|
||||
// 跳转点餐页面
|
||||
jumpToOrderPage() {
|
||||
// 是否免除桌位费 0否1是
|
||||
if (this.shopInfo.isTableFee == 0) {
|
||||
uni.reLaunch({
|
||||
url: "/pages/product/choosetable",
|
||||
});
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: "/pages/product/index",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
},
|
||||
// /通过桌码获取当前店铺信息
|
||||
actionsproductqueryShop(tableCode) {
|
||||
console.log("台桌码", tableCode);
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// try {
|
||||
try {
|
||||
let res = await APIproductqueryShop({
|
||||
tableCode: tableCode ? tableCode : uni.cache.get("tableCode"),
|
||||
});
|
||||
if (res) {
|
||||
res.shopInfo.isVip = res.vip ? "1" : "0";
|
||||
res.shopTable.shopExtendMap = res.shopExtendMap;
|
||||
this.shopInfo = res.shopInfo;
|
||||
// 店铺信息
|
||||
uni.cache.set("shopTable", res.shopTable);
|
||||
// 台桌信息
|
||||
uni.cache.set("shopInfo", res.shopInfo);
|
||||
uni.cache.set("shopId", res.shopTable.shopId, 30);
|
||||
|
||||
// 通过shopId 获取店铺会员信息
|
||||
actionsproductqueryProduct() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let res = await APIshopUserInfo()
|
||||
uni.cache.set('shopUserInfo', res);
|
||||
uni.cache.set('orderVIP', res)
|
||||
uni.cache.set('ordershopUserInfo', res.shopInfo)
|
||||
resolve(res)
|
||||
} catch (e) {
|
||||
reject(false)
|
||||
}
|
||||
resolve(res);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "通过桌码获取当前店铺信息失败",
|
||||
icon: "none",
|
||||
});
|
||||
console.error("通过桌码获取当前店铺信息失败", res);
|
||||
reject();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
//TODO handle the exception
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
// } catch (e) {
|
||||
// reject(false)
|
||||
// }
|
||||
});
|
||||
},
|
||||
|
||||
// 用户信息获取
|
||||
actionsAPIuser() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let res = null
|
||||
// 获取店铺用户会员信息
|
||||
if (uni.cache.get('shopId')) {
|
||||
res = await this.actionsproductqueryProduct()
|
||||
} else {
|
||||
res = await APIuser()
|
||||
uni.cache.set('userInfo', res);
|
||||
}
|
||||
console.log('actionsAPIuser res', res);
|
||||
resolve(res)
|
||||
} catch (e) {
|
||||
reject(false)
|
||||
}
|
||||
// 通过shopId 获取店铺会员信息
|
||||
actionsproductqueryProduct() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let res = await APIshopUserInfo();
|
||||
uni.cache.set("shopUserInfo", res);
|
||||
uni.cache.set("orderVIP", res);
|
||||
uni.cache.set("ordershopUserInfo", res.shopInfo);
|
||||
resolve(res);
|
||||
} catch (e) {
|
||||
reject(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
// 用户信息获取
|
||||
actionsAPIuser() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let res = null;
|
||||
// 获取店铺用户会员信息
|
||||
if (uni.cache.get("shopId")) {
|
||||
res = await this.actionsproductqueryProduct();
|
||||
} else {
|
||||
res = await APIuser();
|
||||
uni.cache.set("userInfo", res);
|
||||
}
|
||||
console.log("actionsAPIuser res", res);
|
||||
resolve(res);
|
||||
} catch (e) {
|
||||
reject(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
225
uni_modules/lime-painter/changelog.md
Normal file
@@ -0,0 +1,225 @@
|
||||
## 1.9.6.6(2024-09-25)
|
||||
- fix: 修复background-position无效的问题
|
||||
## 1.9.6.5(2024-04-14)
|
||||
- fix: 修复`nvue`无法生图的问题
|
||||
## 1.9.6.4(2024-03-10)
|
||||
- fix: 修复代理ctx导致H5不能使用ctx.save
|
||||
## 1.9.6.3(2024-03-08)
|
||||
- fix: 修复支付宝真机无法使用的问题
|
||||
## 1.9.6.2(2024-02-22)
|
||||
- fix: 修复使用render函数报错的问题
|
||||
## 1.9.6.1(2023-12-22)
|
||||
- fix: 修复字节小程序非2d字体偏移
|
||||
- fix: 修复`canvasToTempFilePathSync`会触发两次的问题
|
||||
- fix: 修复`parser`图片没有宽度的问题
|
||||
## 1.9.6(2023-12-06)
|
||||
- fix: 修复背景图受padding影响
|
||||
- fix: 修复因字节报错改了代理实现导致微信报错
|
||||
- 1.9.5.8(2023-11-16)
|
||||
- fix: 修复margin问题
|
||||
- fix: 修复borderWidth问题
|
||||
- fix: 修复textBox问题
|
||||
- fix: 修复字节开发工具报`could not be cloned.`问题
|
||||
## 1.9.5.7(2023-07-27)
|
||||
- fix: 去掉多余的方法
|
||||
- chore: 更新文档,增加自定义字体说明
|
||||
## 1.9.5.6(2023-07-21)
|
||||
- feat: 有限的支持富文本
|
||||
- feat: H5和APP 增加 `hidpi` prop,主要用于大尺寸无法生成图片时用
|
||||
- fix: 修复 钉钉小程序 缺少 `measureText` 方法
|
||||
- chore: 由于微信小程序 pc 端的 canvas 2d 时不时抽风,故不使用canvas 2d
|
||||
## 1.9.5.5(2023-06-27)
|
||||
- fix: 修复把`emoji`表情字符拆分成多个字符的情况
|
||||
## 1.9.5.4(2023-06-05)
|
||||
- fix: 修复因`canvasToTempFilePathSync`监听导致重复调用
|
||||
## 1.9.5.3(2023-05-23)
|
||||
- fix: 因isPc错写成了isPC导致小程序PC不能生成图片
|
||||
## 1.9.5.2(2023-05-22)
|
||||
- feat: 删除多余文件
|
||||
## 1.9.5.1(2023-05-22)
|
||||
- fix: 修复 文字行数与`line-clamp`相同但不满一行时也加了省略号的问题
|
||||
## 1.9.5(2023-05-14)
|
||||
- feat: 增加 `text-indent` 和 `calc` 方法
|
||||
- feat: 优化 布局时间
|
||||
## 1.9.4.4(2023-04-15)
|
||||
- fix: 修复无法匹配负值
|
||||
- fix: 修复 Nvue IOS getImageInfo `useCORS` 为 undefined
|
||||
## 1.9.4.3(2023-04-01)
|
||||
- feat: 增加支持文字描边 `text-stroke: '5rpx #fff'`
|
||||
## 1.9.4.2(2023-03-30)
|
||||
- fix: 修复 支付宝小程序 isPC 在手机也为true的问题
|
||||
- feat: 由 微信开发工具 3060 版 无法获取图片尺寸,现 微信开发工具 3220 版 修复该问题,故还原上一版的获取图片方式。
|
||||
## 1.9.4.1(2023-03-28)
|
||||
- fix: 修复固定高度不正确问题
|
||||
## 1.9.4(2023-03-17)
|
||||
- fix: nvue ios getImageInfo缺少this报错
|
||||
- fix: pathType 非2d无效问题
|
||||
- fix: 修复 小米9se 可能会存在多次init 导致画面多次放大
|
||||
- fix: 修复 border 分开写 width style无效问题
|
||||
- fix: 修复 支付宝小程序IOS 再次进入不渲染的问题
|
||||
- fix: 修复 支付宝小程序安卓Zindex排序错乱问题
|
||||
- fix: 修复 微信开发工具 3060 版 无法获取图片的问题
|
||||
- feat: 把 for in 改为 forEach
|
||||
- feat: 增加 hidden
|
||||
- feat: 根节点 box-sizing 默认 `border-box`
|
||||
- feat: 增加支持 `vw` `wh`
|
||||
- chore: pathType 取消 默认值,因为字节开发工具不能显示
|
||||
- chore: 支付宝小程序开发工具不支持 生成图片 请以真机调试为准
|
||||
- bug: 企业微信 2.20.3无法使用
|
||||
## 1.9.3.5(2022-06-29)
|
||||
- feat: justifyContent 增加 `space-around`、`space-between`
|
||||
- feat: canvas 2d 也使用`getImageInfo`
|
||||
- fix: 修复 `text`的 `text-decoration`错位
|
||||
## 1.9.3.4(2022-06-20)
|
||||
- fix: 修复 因创建节点速度问题导致顺序出错。
|
||||
- fix: 修复 微信小程序 PC 无法显示本地图片
|
||||
- fix: 修复 flex-box 对齐问题
|
||||
- feat: 增加 `text-shadow`
|
||||
- feat: 重写 `text` 对齐方式
|
||||
- chore: 更新文档
|
||||
## 1.9.3.3(2022-06-17)
|
||||
- fix: 修复 支付宝小程序 canvas 2d 存在ctx.draw问题导致报错
|
||||
- fix: 修复 支付宝小程序 toDataURL 存在权限问题改用 `toTempFilePath`
|
||||
- fix: 修复 支付宝小程序 image size 问题导致 `objectFit` 无效
|
||||
## 1.9.3.2(2022-06-14)
|
||||
- fix: 修复 image 设置背景色不生效问题
|
||||
- fix: 修复 nvue 环境判断缺少参数问题
|
||||
## 1.9.3.1(2022-06-14)
|
||||
- fix: 修复 bottom 定位不对问题
|
||||
- fix: 修复 因小数导致计算出错换行问题
|
||||
- feat: 增加 `useCORS` h5端图片跨域 在设置请求头无效果后试一下设置这个值
|
||||
- chore: 更新文档
|
||||
## 1.9.3(2022-06-13)
|
||||
- feat: 增加 `zIndex`
|
||||
- feat: 增加 `flex-box` 该功能处于原始阶段,非常简陋。
|
||||
- tips: QQ小程序 vue3 不支持, 为 uni 官方BUG
|
||||
## 1.9.2.9(2022-06-10)
|
||||
- fix: 修复`text-align`及`margin`居中问题
|
||||
## 1.9.2.8(2022-06-10)
|
||||
- fix: 修复 Nvue `canvasToTempFilePathSync` 不生效问题
|
||||
## 1.9.2.7(2022-06-10)
|
||||
- fix: 修复 margin及padding的bug
|
||||
- fix: 修复 Nvue `isCanvasToTempFilePath` 不生效问题
|
||||
## 1.9.2.6(2022-06-09)
|
||||
- fix: 修复 Nvue 不显示
|
||||
- feat: 增加支持字体渐变
|
||||
```html
|
||||
<l-painter-text
|
||||
text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
|
||||
css="background: linear-gradient(,#ff971b 0%, #1989fa 100%); background-clip: text" />
|
||||
```
|
||||
## 1.9.2.5(2022-06-09)
|
||||
- chore: 更变获取父级宽度的设定
|
||||
- chore: `pathType` 在canvas 2d 默认为 `url`
|
||||
## 1.9.2.4(2022-06-08)
|
||||
- fix: 修复 `pathType` 不生效问题
|
||||
## 1.9.2.3(2022-06-08)
|
||||
- fix: 修复 `canvasToTempFilePath` 漏写 `success` 参数
|
||||
## 1.9.2.2(2022-06-07)
|
||||
- chore: 更新文档
|
||||
## 1.9.2.1(2022-06-07)
|
||||
- fix: 修复 vue3 赋值给this再传入导致image无法绘制
|
||||
- fix: 修复 `canvasToTempFilePathSync` 时机问题
|
||||
- feat: canvas 2d 更改图片生成方式 `toDataURL`
|
||||
## 1.9.2(2022-05-30)
|
||||
- fix: 修复 `canvasToTempFilePathSync` 在 vue3 下只生成一次
|
||||
## 1.9.1.7(2022-05-28)
|
||||
- fix: 修复 `qrcode`显示不全问题
|
||||
## 1.9.1.6(2022-05-28)
|
||||
- fix: 修复 `canvasToTempFilePathSync` 会重复多次问题
|
||||
- fix: 修复 `view` css `backgroundImage` 图片下载失败导致 子节点不渲染
|
||||
## 1.9.1.5(2022-05-27)
|
||||
- fix: 修正支付宝小程序 canvas 2d版本号 2.7.15
|
||||
## 1.9.1.4(2022-05-22)
|
||||
- fix: 修复字节小程序无法使用xml方式
|
||||
- fix: 修复字节小程序无法使用base64(非2D情况下工具上无法显示)
|
||||
- fix: 修复支付宝小程序 `canvasToTempFilePath` 报错
|
||||
## 1.9.1.3(2022-04-29)
|
||||
- fix: 修复vue3打包后uni对象为空后的报错
|
||||
## 1.9.1.2(2022-04-25)
|
||||
- fix: 删除多余文件
|
||||
## 1.9.1.1(2022-04-25)
|
||||
- fix: 修复图片不显示问题
|
||||
## 1.9.1(2022-04-12)
|
||||
- fix: 因四舍五入导致有些机型错位
|
||||
- fix: 修复无views报错
|
||||
- chore: nvue下因ios无法读取插件内static文件,改由下载方式
|
||||
## 1.9.0(2022-03-20)
|
||||
- fix: 因无法固定尺寸导致生成图片不全
|
||||
- fix: 特定情况下text判断无效
|
||||
- chore: 本地化APP Nvue webview
|
||||
## 1.8.9(2022-02-20)
|
||||
- fix: 修复 小程序下载最多10次并发的问题
|
||||
- fix: 修复 APP端无法获取本地图片
|
||||
- fix: 修复 APP Nvue端不执行问题
|
||||
- chore: 增加图片缓存机制
|
||||
## 1.8.8.8(2022-01-27)
|
||||
- fix: 修复 主动调用尺寸问题
|
||||
## 1.8.8.6(2022-01-26)
|
||||
- fix: 修复 nvue 下无宽度时获取父级宽度
|
||||
- fix: 修复 ios app 无法渲染问题
|
||||
## 1.8.8(2022-01-23)
|
||||
- fix: 修复 主动调用时无节点问题
|
||||
- fix: 修复 `box-shadow` 颜色问题
|
||||
- fix: 修复 `transform:rotate` 角度位置问题
|
||||
- feat: 增加 `overflow:hidden`
|
||||
## 1.8.7(2022-01-07)
|
||||
- fix: 修复 image 方向为 `right` 时原始宽高问题
|
||||
- feat: 支持 view 设置背景图 `background-image: url(xxx)`
|
||||
- chore: 去掉可选链
|
||||
## 1.8.6(2021-11-28)
|
||||
- feat: 支持`view`对`inline-block`的子集使用`text-align`
|
||||
## 1.8.5.5(2021-08-17)
|
||||
- chore: 更新文档,删除 replace
|
||||
- fix: 修复 text 值为 number时报错
|
||||
## 1.8.5.4(2021-08-16)
|
||||
- fix: 字节小程序兼容
|
||||
## 1.8.5.3(2021-08-15)
|
||||
- fix: 修复线性渐变与css现实效果不一致的问题
|
||||
- chore: 更新文档
|
||||
## 1.8.5.2(2021-08-13)
|
||||
- chore: 增加`background-image`、`background-repeat` 能力,主要用于背景纹理的绘制,并不是代替`image`。例如:大面积的重复平铺的水印
|
||||
- 注意:这个功能H5暂时无法使用,因为[官方的API有BUG](https://ask.dcloud.net.cn/question/128793),待官方修复!!!
|
||||
## 1.8.5.1(2021-08-10)
|
||||
- fix: 修复因`margin`报错问题
|
||||
## 1.8.5(2021-08-09)
|
||||
- chore: 增加margin支持`auto`,以达到居中效果
|
||||
## 1.8.4(2021-08-06)
|
||||
- chore: 增加判断缓存文件条件
|
||||
- fix: 修复css 多余空格报错问题
|
||||
## 1.8.3(2021-08-04)
|
||||
- tips: 1.6.x 以下的版本升级到1.8.x后要为每个元素都加上定位:position: 'absolute'
|
||||
- fix: 修复只有一个view子元素时不计算高度的问题
|
||||
## 1.8.2(2021-08-03)
|
||||
- fix: 修复 path-type 为 `url` 无效问题
|
||||
- fix: 修复 qrcode `text` 为空时报错问题
|
||||
- fix: 修复 image `src` 动态设置时不生效问题
|
||||
- feat: 增加 css 属性 `min-width` `max-width`
|
||||
## 1.8.1(2021-08-02)
|
||||
- fix: 修复无法加载本地图片
|
||||
## 1.8.0(2021-08-02)
|
||||
- chore 文档更新
|
||||
- 使用旧版的同学不要升级!
|
||||
## 1.8.0-beta(2021-07-30)
|
||||
- ## 全新布局方式 不兼容旧版!
|
||||
- chore: 布局方式变更
|
||||
- tips: 微信canvas 2d 不支持真机调试
|
||||
## 1.6.6(2021-07-09)
|
||||
- chore: 统一命名规范,无须主动引入组件
|
||||
## 1.6.5(2021-06-08)
|
||||
- chore: 去掉console
|
||||
## 1.6.4(2021-06-07)
|
||||
- fix: 修复 数字 为纯字符串时不转换的BUG
|
||||
## 1.6.3(2021-06-06)
|
||||
- fix: 修复 PC 端放大的BUG
|
||||
## 1.6.2(2021-05-31)
|
||||
- fix: 修复 报`adaptor is not a function`错误
|
||||
- fix: 修复 text 多行高度
|
||||
- fix: 优化 默认文字的基准线
|
||||
- feat: `@progress`事件,监听绘制进度
|
||||
## 1.6.1(2021-02-28)
|
||||
- 删除多余节点
|
||||
## 1.6.0(2021-02-26)
|
||||
- 调整为uni_modules目录规范
|
||||
- 修复:transform的rotate不能为负数问题
|
||||
- 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`
|
||||
150
uni_modules/lime-painter/components/common/relation.js
Normal file
@@ -0,0 +1,150 @@
|
||||
const styles = (v ='') => v.split(';').filter(v => v && !/^[\n\s]+$/.test(v)).map(v => {
|
||||
const key = v.slice(0, v.indexOf(':'))
|
||||
const value = v.slice(v.indexOf(':')+1)
|
||||
return {
|
||||
[key
|
||||
.replace(/-([a-z])/g, function() { return arguments[1].toUpperCase()})
|
||||
.replace(/\s+/g, '')
|
||||
]: value.replace(/^\s+/, '').replace(/\s+$/, '') || ''
|
||||
}
|
||||
})
|
||||
export function parent(parent) {
|
||||
return {
|
||||
provide() {
|
||||
return {
|
||||
[parent]: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
el: {
|
||||
id: null,
|
||||
css: {},
|
||||
views: []
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
css: {
|
||||
handler(v) {
|
||||
if(this.canvasId) {
|
||||
this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
|
||||
this.canvasWidth = this.el.css && this.el.css.width || this.canvasWidth
|
||||
this.canvasHeight = this.el.css && this.el.css.height || this.canvasHeight
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function children(parent, options = {}) {
|
||||
const indexKey = options.indexKey || 'index'
|
||||
return {
|
||||
inject: {
|
||||
[parent]: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
el: {
|
||||
handler(v, o) {
|
||||
if(JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
src: {
|
||||
handler(v, o) {
|
||||
if(v != o)
|
||||
this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
text: {
|
||||
handler(v, o) {
|
||||
if(v != o) this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
css: {
|
||||
handler(v, o) {
|
||||
if(v != o)
|
||||
this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
replace: {
|
||||
handler(v, o) {
|
||||
if(JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(!this._uid) {
|
||||
this._uid = this._.uid
|
||||
}
|
||||
Object.defineProperty(this, 'parent', {
|
||||
get: () => this[parent] || [],
|
||||
})
|
||||
Object.defineProperty(this, 'index', {
|
||||
get: () => {
|
||||
this.bindRelation();
|
||||
const {parent: {el: {views=[]}={}}={}} = this
|
||||
return views.indexOf(this.el)
|
||||
},
|
||||
});
|
||||
this.el.type = this.type
|
||||
if(this.uid) {
|
||||
this.el.uid = this.uid
|
||||
}
|
||||
this.bindRelation()
|
||||
},
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.removeEl()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
beforeDestroy() {
|
||||
this.removeEl()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
removeEl() {
|
||||
if (this.parent) {
|
||||
this.parent.el.views = this.parent.el.views.filter(
|
||||
(item) => item._uid !== this._uid
|
||||
);
|
||||
}
|
||||
},
|
||||
bindRelation() {
|
||||
if(!this.el._uid) {
|
||||
this.el._uid = this._uid
|
||||
}
|
||||
if(['text','qrcode'].includes(this.type)) {
|
||||
this.el.text = this.$slots && this.$slots.default && this.$slots.default[0].text || `${this.text || ''}`.replace(/\\n/g, '\n')
|
||||
}
|
||||
if(this.type == 'image') {
|
||||
this.el.src = this.src
|
||||
}
|
||||
if (!this.parent) {
|
||||
return;
|
||||
}
|
||||
let views = this.parent.el.views || [];
|
||||
if(views.indexOf(this.el) !== -1) {
|
||||
this.parent.el.views = views.map(v => v._uid == this._uid ? this.el : v)
|
||||
} else {
|
||||
this.parent.el.views = [...views, this.el];
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// this.bindRelation()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-image',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
id: String,
|
||||
css: [String, Object],
|
||||
src: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'image',
|
||||
el: {
|
||||
css: {},
|
||||
src: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-qrcode',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
id: String,
|
||||
css: [String, Object],
|
||||
text: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'qrcode',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<text style="opacity: 0;height: 0;"><slot/></text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-text',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
uid: String,
|
||||
css: [String, Object],
|
||||
text: [String, Number],
|
||||
replace: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// type: 'text',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<view><slot/></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-view',
|
||||
mixins:[children('painter'), parent('painter')],
|
||||
props: {
|
||||
id: String,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'view'
|
||||
},
|
||||
css: [String, Object],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// type: 'view',
|
||||
el: {
|
||||
css: {},
|
||||
views:[]
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
461
uni_modules/lime-painter/components/l-painter/l-painter.vue
Normal file
@@ -0,0 +1,461 @@
|
||||
<template>
|
||||
<view class="lime-painter" ref="limepainter">
|
||||
<view v-if="canvasId && size" :style="styles">
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
|
||||
<canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size"
|
||||
:width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>
|
||||
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<web-view :style="size" ref="webview"
|
||||
src="/uni_modules/lime-painter/hybrid/html/index.html"
|
||||
class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage">
|
||||
</web-view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { parent } from '../common/relation'
|
||||
import props from './props'
|
||||
import {toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo }from './utils';
|
||||
// #ifndef APP-NVUE
|
||||
import { canIUseCanvas2d, isPC} from './utils';
|
||||
import Painter from './painter';
|
||||
// import Painter from '@painter'
|
||||
const nvue = {}
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
import nvue from './nvue'
|
||||
// #endif
|
||||
export default {
|
||||
name: 'lime-painter',
|
||||
mixins: [props, parent('painter'), nvue],
|
||||
data() {
|
||||
return {
|
||||
use2dCanvas: false,
|
||||
canvasHeight: 150,
|
||||
canvasWidth: null,
|
||||
parentWidth: 0,
|
||||
inited: false,
|
||||
progress: 0,
|
||||
firstRender: 0,
|
||||
done: false,
|
||||
tasks: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
return `${this.size}${this.customStyle||''};` + (this.hidden && 'position: fixed; left: 1500rpx;')
|
||||
},
|
||||
canvasId() {
|
||||
return `l-painter${this._ && this._.uid || this._uid}`
|
||||
},
|
||||
size() {
|
||||
if (this.boardWidth && this.boardHeight) {
|
||||
return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
|
||||
}
|
||||
},
|
||||
dpr() {
|
||||
return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
|
||||
},
|
||||
boardWidth() {
|
||||
const {width = 0} = (this.elements && this.elements.css) || this.elements || this
|
||||
const w = toPx(width||this.width)
|
||||
return w || Math.max(w, toPx(this.canvasWidth));
|
||||
},
|
||||
boardHeight() {
|
||||
const {height = 0} = (this.elements && this.elements.css) || this.elements || this
|
||||
const h = toPx(height||this.height)
|
||||
return h || Math.max(h, toPx(this.canvasHeight));
|
||||
},
|
||||
hasBoard() {
|
||||
return this.board && Object.keys(this.board).length
|
||||
},
|
||||
elements() {
|
||||
return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC
|
||||
},
|
||||
async mounted() {
|
||||
await sleep(30)
|
||||
await this.getParentWeith()
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.$watch('elements', this.watchRender, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
});
|
||||
}, 30)
|
||||
})
|
||||
},
|
||||
// #ifdef VUE3
|
||||
unmounted() {
|
||||
this.done = false
|
||||
this.inited = false
|
||||
this.firstRender = 0
|
||||
this.progress = 0
|
||||
this.painter = null
|
||||
clearTimeout(this.rendertimer)
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
destroyed() {
|
||||
this.done = false
|
||||
this.inited = false
|
||||
this.firstRender = 0
|
||||
this.progress = 0
|
||||
this.painter = null
|
||||
clearTimeout(this.rendertimer)
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
async watchRender(val, old) {
|
||||
if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return;
|
||||
this.firstRender = 1
|
||||
this.progress = 0
|
||||
this.done = false
|
||||
clearTimeout(this.rendertimer)
|
||||
this.rendertimer = setTimeout(() => {
|
||||
this.render(val);
|
||||
}, this.beforeDelay)
|
||||
},
|
||||
async setFilePath(path, param) {
|
||||
let filePath = path
|
||||
const {pathType = this.pathType} = param || this
|
||||
if (pathType == 'base64' && !isBase64(path)) {
|
||||
filePath = await pathToBase64(path)
|
||||
} else if (pathType == 'url' && isBase64(path)) {
|
||||
filePath = await base64ToPath(path)
|
||||
}
|
||||
if (param && param.isEmit) {
|
||||
this.$emit('success', filePath);
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
async getSize(args) {
|
||||
const {width} = args.css || args
|
||||
const {height} = args.css || args
|
||||
if (!this.size) {
|
||||
if (width || height) {
|
||||
this.canvasWidth = width || this.canvasWidth
|
||||
this.canvasHeight = height || this.canvasHeight
|
||||
await sleep(30);
|
||||
} else {
|
||||
await this.getParentWeith()
|
||||
}
|
||||
}
|
||||
},
|
||||
canvasToTempFilePathSync(args) {
|
||||
// this.stopWatch && this.stopWatch()
|
||||
// this.stopWatch = this.$watch('done', (v) => {
|
||||
// if (v) {
|
||||
// this.canvasToTempFilePath(args)
|
||||
// this.stopWatch && this.stopWatch()
|
||||
// }
|
||||
// }, {
|
||||
// immediate: true
|
||||
// })
|
||||
this.tasks.push(args)
|
||||
if(this.done){
|
||||
this.runTask()
|
||||
}
|
||||
},
|
||||
runTask(){
|
||||
while(this.tasks.length){
|
||||
const task = this.tasks.shift()
|
||||
this.canvasToTempFilePath(task)
|
||||
}
|
||||
},
|
||||
// #ifndef APP-NVUE
|
||||
getParentWeith() {
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`.lime-painter`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
const {width, height} = res[0]||{}
|
||||
this.parentWidth = Math.ceil(width||0)
|
||||
this.canvasWidth = this.parentWidth || 300
|
||||
this.canvasHeight = height || this.canvasHeight||150
|
||||
resolve(res[0])
|
||||
})
|
||||
})
|
||||
},
|
||||
async render(args = {}) {
|
||||
if(!Object.keys(args).length) {
|
||||
return console.error('空对象')
|
||||
}
|
||||
this.progress = 0
|
||||
this.done = false
|
||||
// #ifdef APP-NVUE
|
||||
this.tempFilePath.length = 0
|
||||
// #endif
|
||||
await this.getSize(args)
|
||||
const ctx = await this.getContext();
|
||||
|
||||
let {
|
||||
use2dCanvas,
|
||||
boardWidth,
|
||||
boardHeight,
|
||||
canvas,
|
||||
afterDelay
|
||||
} = this;
|
||||
if (use2dCanvas && !canvas) {
|
||||
return Promise.reject(new Error('canvas 没创建'));
|
||||
}
|
||||
this.boundary = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: boardWidth,
|
||||
height: boardHeight
|
||||
};
|
||||
this.painter = null
|
||||
if (!this.painter) {
|
||||
const {width} = args.css || args
|
||||
const {height} = args.css || args
|
||||
if(!width && this.parentWidth) {
|
||||
Object.assign(args, {width: this.parentWidth})
|
||||
}
|
||||
const param = {
|
||||
context: ctx,
|
||||
canvas,
|
||||
width: boardWidth,
|
||||
height: boardHeight,
|
||||
pixelRatio: this.dpr,
|
||||
useCORS: this.useCORS,
|
||||
createImage: getImageInfo.bind(this),
|
||||
performance: this.performance,
|
||||
listen: {
|
||||
onProgress: (v) => {
|
||||
this.progress = v
|
||||
this.$emit('progress', v)
|
||||
},
|
||||
onEffectFail: (err) => {
|
||||
this.$emit('faill', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.painter = new Painter(param)
|
||||
}
|
||||
try{
|
||||
// vue3 赋值给data会引起图片无法绘制
|
||||
const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args)))
|
||||
this.boundary.height = this.canvasHeight = height
|
||||
this.boundary.width = this.canvasWidth = width
|
||||
await sleep(this.sleep);
|
||||
await this.painter.render()
|
||||
await new Promise(resolve => this.$nextTick(resolve));
|
||||
if (!use2dCanvas) {
|
||||
await this.canvasDraw();
|
||||
}
|
||||
if (afterDelay && use2dCanvas) {
|
||||
await sleep(afterDelay);
|
||||
}
|
||||
this.$emit('done');
|
||||
this.done = true
|
||||
if (this.isCanvasToTempFilePath) {
|
||||
this.canvasToTempFilePath()
|
||||
.then(res => {
|
||||
this.$emit('success', res.tempFilePath)
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('fail', new Error(JSON.stringify(err)));
|
||||
});
|
||||
}
|
||||
this.runTask()
|
||||
return Promise.resolve({
|
||||
ctx,
|
||||
draw: this.painter,
|
||||
node: this.node
|
||||
});
|
||||
}catch(e){
|
||||
//TODO handle the exception
|
||||
}
|
||||
|
||||
},
|
||||
canvasDraw(flag = false) {
|
||||
return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this
|
||||
.afterDelay)));
|
||||
},
|
||||
async getContext() {
|
||||
if (!this.canvasWidth) {
|
||||
this.$emit('fail', 'painter no size')
|
||||
console.error('[lime-painter]: 给画板或父级设置尺寸')
|
||||
return Promise.reject();
|
||||
}
|
||||
if (this.ctx && this.inited) {
|
||||
return Promise.resolve(this.ctx);
|
||||
}
|
||||
const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
|
||||
const _getContext = () => {
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
if (res) {
|
||||
const ctx = uni.createCanvasContext(this.canvasId, this);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = false;
|
||||
this.canvas = res;
|
||||
}
|
||||
|
||||
// 钉钉小程序框架不支持 measureText 方法,用此方法 mock
|
||||
if (!ctx.measureText) {
|
||||
function strLen(str) {
|
||||
let len = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
|
||||
len++;
|
||||
} else {
|
||||
len += 2;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
ctx.measureText = text => {
|
||||
let fontSize = ctx.state && ctx.state.fontSize || 12;
|
||||
const font = ctx.__font
|
||||
if (font && fontSize == 12) {
|
||||
fontSize = parseInt(font.split(' ')[3], 10);
|
||||
}
|
||||
fontSize /= 2;
|
||||
return {
|
||||
width: strLen(text) * fontSize
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
ctx.scale(dpr, dpr);
|
||||
// #endif
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
} else {
|
||||
console.error('[lime-painter] no node')
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
if (!use2dCanvas) {
|
||||
return _getContext();
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.node()
|
||||
.exec(res => {
|
||||
let {node: canvas} = res && res[0]||{};
|
||||
if(canvas) {
|
||||
const ctx = canvas.getContext(type);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = true;
|
||||
this.canvas = canvas;
|
||||
}
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
} else {
|
||||
console.error('[lime-painter]: no size')
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
canvasToTempFilePath(args = {}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
|
||||
const success = async (res) => {
|
||||
try {
|
||||
const tempFilePath = await this.setFilePath(res.tempFilePath || res, args)
|
||||
const result = Object.assign(res, {tempFilePath})
|
||||
args.success && args.success(result)
|
||||
resolve(result)
|
||||
} catch (e) {
|
||||
this.$emit('fail', e)
|
||||
}
|
||||
}
|
||||
|
||||
let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
|
||||
// let destWidth = width * dpr;
|
||||
// let destHeight = height * dpr;
|
||||
// #ifdef MP-ALIPAY
|
||||
// width = destWidth;
|
||||
// height = destHeight;
|
||||
// #endif
|
||||
|
||||
const copyArgs = Object.assign({
|
||||
// x,
|
||||
// y,
|
||||
// width,
|
||||
// height,
|
||||
// destWidth,
|
||||
// destHeight,
|
||||
canvasId,
|
||||
id: canvasId,
|
||||
fileType,
|
||||
quality,
|
||||
}, args, {success});
|
||||
// if(this.isPC || use2dCanvas) {
|
||||
// copyArgs.canvas = this.canvas
|
||||
// }
|
||||
if (use2dCanvas) {
|
||||
copyArgs.canvas = this.canvas
|
||||
try{
|
||||
// #ifndef MP-ALIPAY
|
||||
const oFilePath = this.canvas.toDataURL(`image/${args.fileType||fileType}`.replace(/pg/, 'peg'), args.quality||quality)
|
||||
if(/data:,/.test(oFilePath)) {
|
||||
uni.canvasToTempFilePath(copyArgs, this);
|
||||
} else {
|
||||
const tempFilePath = await this.setFilePath(oFilePath, args)
|
||||
args.success && args.success({tempFilePath})
|
||||
resolve({tempFilePath})
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
this.canvas.toTempFilePath(copyArgs)
|
||||
// #endif
|
||||
}catch(e){
|
||||
args.fail && args.fail(e)
|
||||
reject(e)
|
||||
}
|
||||
} else {
|
||||
// #ifdef MP-ALIPAY
|
||||
if(this.ctx.toTempFilePath) {
|
||||
// 钉钉
|
||||
const ctx = uni.createCanvasContext(canvasId);
|
||||
ctx.toTempFilePath(copyArgs);
|
||||
} else {
|
||||
my.canvasToTempFilePath(copyArgs);
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
uni.canvasToTempFilePath(copyArgs, this);
|
||||
// #endif
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.lime-painter,
|
||||
.lime-painter__canvas {
|
||||
// #ifndef APP-NVUE
|
||||
width: 100%;
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
flex: 1;
|
||||
// #endif
|
||||
}
|
||||
</style>
|
||||
214
uni_modules/lime-painter/components/l-painter/nvue.js
Normal file
@@ -0,0 +1,214 @@
|
||||
// #ifdef APP-NVUE
|
||||
import {
|
||||
sleep,
|
||||
getImageInfo,
|
||||
isBase64,
|
||||
networkReg
|
||||
} from './utils';
|
||||
const dom = weex.requireModule('dom')
|
||||
import {
|
||||
version
|
||||
} from '../../package.json'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tempFilePath: [],
|
||||
isInitFile: false,
|
||||
osName: uni.getSystemInfoSync().osName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getParentWeith() {
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(this.$refs.limepainter, (res) => {
|
||||
this.parentWidth = Math.ceil(res.size.width)
|
||||
this.canvasWidth = this.canvasWidth || this.parentWidth || 300
|
||||
this.canvasHeight = res.size.height || this.canvasHeight || 150
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
},
|
||||
onPageFinish() {
|
||||
this.webview = this.$refs.webview
|
||||
this.webview.evalJS(`init(${this.dpr})`)
|
||||
},
|
||||
onMessage(e) {
|
||||
const res = e.detail.data[0] || null;
|
||||
if (res.event) {
|
||||
if (res.event == 'inited') {
|
||||
this.inited = true
|
||||
}
|
||||
if (res.event == 'fail') {
|
||||
this.$emit('fail', res)
|
||||
}
|
||||
if (res.event == 'layoutChange') {
|
||||
const data = typeof res.data == 'string' ? JSON.parse(res.data) : res.data
|
||||
this.canvasWidth = Math.ceil(data.width);
|
||||
this.canvasHeight = Math.ceil(data.height);
|
||||
}
|
||||
if (res.event == 'progressChange') {
|
||||
this.progress = res.data * 1
|
||||
}
|
||||
if (res.event == 'file') {
|
||||
this.tempFilePath.push(res.data)
|
||||
if (this.tempFilePath.length > 7) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (res.event == 'success') {
|
||||
if (res.data) {
|
||||
this.tempFilePath.push(res.data)
|
||||
if (this.tempFilePath.length > 8) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
if (this.isCanvasToTempFilePath) {
|
||||
this.setFilePath(this.tempFilePath.join(''), {
|
||||
isEmit: true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.$emit('fail', 'canvas no data')
|
||||
}
|
||||
return
|
||||
}
|
||||
this.$emit(res.event, JSON.parse(res.data));
|
||||
} else if (res.file) {
|
||||
this.file = res.data;
|
||||
} else {
|
||||
console.info(res[0])
|
||||
}
|
||||
},
|
||||
getWebViewInited() {
|
||||
if (this.inited) return Promise.resolve(this.inited);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'inited',
|
||||
async val => {
|
||||
if (val) {
|
||||
resolve(val)
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
})
|
||||
},
|
||||
getTempFilePath() {
|
||||
if (this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'tempFilePath',
|
||||
async val => {
|
||||
if (val.length == 8) {
|
||||
resolve(val.join(''))
|
||||
}
|
||||
}, {
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
})
|
||||
},
|
||||
getWebViewDone() {
|
||||
if (this.progress == 1) return Promise.resolve(this.progress);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'progress',
|
||||
async val => {
|
||||
if (val == 1) {
|
||||
this.$emit('done')
|
||||
this.done = true
|
||||
this.runTask()
|
||||
resolve(val)
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
})
|
||||
},
|
||||
async render(args) {
|
||||
try {
|
||||
await this.getSize(args)
|
||||
const {
|
||||
width
|
||||
} = args.css || args
|
||||
if (!width && this.parentWidth) {
|
||||
Object.assign(args, {
|
||||
width: this.parentWidth
|
||||
})
|
||||
}
|
||||
const newNode = await this.calcImage(args);
|
||||
await this.getWebViewInited()
|
||||
this.webview.evalJS(`source(${JSON.stringify(newNode)})`)
|
||||
await this.getWebViewDone()
|
||||
await sleep(this.afterDelay)
|
||||
if (this.isCanvasToTempFilePath) {
|
||||
const params = {
|
||||
fileType: this.fileType,
|
||||
quality: this.quality
|
||||
}
|
||||
this.webview.evalJS(`save(${JSON.stringify(params)})`)
|
||||
}
|
||||
return Promise.resolve()
|
||||
} catch (e) {
|
||||
this.$emit('fail', e)
|
||||
}
|
||||
},
|
||||
async calcImage(args) {
|
||||
let node = JSON.parse(JSON.stringify(args))
|
||||
const urlReg = /url\((.+)\)/
|
||||
const {
|
||||
backgroundImage
|
||||
} = node.css || {}
|
||||
const isBG = backgroundImage && urlReg.exec(backgroundImage)[1]
|
||||
const url = node.url || node.src || isBG
|
||||
if (['text', 'qrcode'].includes(node.type)) {
|
||||
return node
|
||||
}
|
||||
if ((node.type === "image" || isBG) && url && !isBase64(url) && (this.osName == 'ios' || !networkReg
|
||||
.test(url))) {
|
||||
let {
|
||||
path
|
||||
} = await getImageInfo(url, true)
|
||||
if (isBG) {
|
||||
node.css.backgroundImage = `url(${path})`
|
||||
} else {
|
||||
node.src = path
|
||||
}
|
||||
} else if (node.views && node.views.length) {
|
||||
for (let i = 0; i < node.views.length; i++) {
|
||||
node.views[i] = await this.calcImage(node.views[i])
|
||||
}
|
||||
}
|
||||
return node
|
||||
},
|
||||
async canvasToTempFilePath(args = {}) {
|
||||
if (!this.inited) {
|
||||
return this.$emit('fail', 'no init')
|
||||
}
|
||||
this.tempFilePath = []
|
||||
if (args.fileType == 'jpg') {
|
||||
args.fileType = 'jpeg'
|
||||
}
|
||||
|
||||
this.webview.evalJS(`save(${JSON.stringify(args)})`)
|
||||
try {
|
||||
let tempFilePath = await this.getTempFilePath()
|
||||
|
||||
tempFilePath = await this.setFilePath(tempFilePath, args)
|
||||
args.success({
|
||||
errMsg: "canvasToTempFilePath:ok",
|
||||
tempFilePath
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('e', e)
|
||||
args.fail({
|
||||
error: e
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
1
uni_modules/lime-painter/components/l-painter/painter.js
Normal file
56
uni_modules/lime-painter/components/l-painter/props.js
Normal file
@@ -0,0 +1,56 @@
|
||||
export default {
|
||||
props: {
|
||||
board: Object,
|
||||
pathType: String, // 'base64'、'url'
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'png'
|
||||
},
|
||||
hidden: Boolean,
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
css: [String, Object],
|
||||
// styles: [String, Object],
|
||||
width: [Number, String],
|
||||
height: [Number, String],
|
||||
pixelRatio: Number,
|
||||
customStyle: String,
|
||||
isCanvasToTempFilePath: Boolean,
|
||||
// useCanvasToTempFilePath: Boolean,
|
||||
sleep: {
|
||||
type: Number,
|
||||
default: 1000 / 30
|
||||
},
|
||||
beforeDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
afterDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
performance: Boolean,
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
type: {
|
||||
type: String,
|
||||
default: '2d'
|
||||
},
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
hybrid: Boolean,
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
// #endif
|
||||
// #ifdef H5 || APP-PLUS
|
||||
useCORS: Boolean,
|
||||
hidpi: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
1
uni_modules/lime-painter/components/l-painter/single.js
Normal file
368
uni_modules/lime-painter/components/l-painter/utils.js
Normal file
@@ -0,0 +1,368 @@
|
||||
export const networkReg = /^(http|\/\/)/;
|
||||
export const isBase64 = (path) => /^data:image\/(\w+);base64/.test(path);
|
||||
export function sleep(delay) {
|
||||
return new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
let {platform, SDKVersion} = uni.getSystemInfoSync()
|
||||
export const isPC = /windows|mac/.test(platform)
|
||||
// 缓存图片
|
||||
let cache = {}
|
||||
export function isNumber(value) {
|
||||
return /^-?\d+(\.\d+)?$/.test(value);
|
||||
}
|
||||
export function toPx(value, baseSize, isDecimal = false) {
|
||||
// 如果是数字
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
// 如果是字符串数字
|
||||
if (isNumber(value)) {
|
||||
return value * 1
|
||||
}
|
||||
// 如果有单位
|
||||
if (typeof value === 'string') {
|
||||
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g
|
||||
const results = reg.exec(value);
|
||||
if (!value || !results) {
|
||||
return 0;
|
||||
}
|
||||
const unit = results[3];
|
||||
value = parseFloat(value);
|
||||
let res = 0;
|
||||
if (unit === 'rpx') {
|
||||
res = uni.upx2px(value);
|
||||
} else if (unit === 'px') {
|
||||
res = value * 1;
|
||||
} else if (unit === '%') {
|
||||
res = value * toPx(baseSize) / 100;
|
||||
} else if (unit === 'em') {
|
||||
res = value * toPx(baseSize || 14);
|
||||
}
|
||||
return isDecimal ? res.toFixed(2) * 1 : Math.round(res);
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 计算版本
|
||||
export function 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
|
||||
}
|
||||
|
||||
function gte(version) {
|
||||
// #ifdef MP-ALIPAY
|
||||
SDKVersion = my.SDKVersion
|
||||
// #endif
|
||||
return compareVersion(SDKVersion, version) >= 0;
|
||||
}
|
||||
export function canIUseCanvas2d() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return gte('2.9.2');
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return gte('2.7.15');
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
return gte('1.78.0');
|
||||
// #endif
|
||||
return false
|
||||
}
|
||||
|
||||
// #ifdef MP
|
||||
export const prefix = () => {
|
||||
// #ifdef MP-TOUTIAO
|
||||
return tt
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
return wx
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
return swan
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return my
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
return qq
|
||||
// #endif
|
||||
// #ifdef MP-360
|
||||
return qh
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* base64转路径
|
||||
* @param {Object} base64
|
||||
*/
|
||||
export function base64ToPath(base64) {
|
||||
const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP
|
||||
const fs = uni.getFileSystemManager()
|
||||
//自定义文件名
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
let pre = prefix()
|
||||
// #ifdef MP-TOUTIAO
|
||||
const filePath = `${pre.getEnvInfoSync().common.USER_DATA_PATH}/${time}.${format}`
|
||||
// #endif
|
||||
// #ifndef MP-TOUTIAO
|
||||
const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
|
||||
// #endif
|
||||
fs.writeFile({
|
||||
filePath,
|
||||
data: base64.split(',')[1],
|
||||
encoding: 'base64',
|
||||
success() {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail(err) {
|
||||
console.error(err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// mime类型
|
||||
let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
|
||||
//base64 解码
|
||||
let byteString = atob(base64.split(',')[1]);
|
||||
//创建缓冲数组
|
||||
let arrayBuffer = new ArrayBuffer(byteString.length);
|
||||
//创建视图
|
||||
let intArray = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
intArray[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
resolve(URL.createObjectURL(new Blob([intArray], {
|
||||
type: mimeString
|
||||
})))
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||
bitmap.loadBase64Data(base64, () => {
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const filePath = `_doc/uniapp_temp/${time}.${format}`
|
||||
bitmap.save(filePath, {},
|
||||
() => {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
},
|
||||
(error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径转base64
|
||||
* @param {Object} string
|
||||
*/
|
||||
export function pathToBase64(path) {
|
||||
if (/^data:/.test(path)) return path
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef H5
|
||||
let image = new Image();
|
||||
image.setAttribute("crossOrigin", 'Anonymous');
|
||||
image.onload = function() {
|
||||
let canvas = document.createElement('canvas');
|
||||
canvas.width = this.naturalWidth;
|
||||
canvas.height = this.naturalHeight;
|
||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
||||
let result = canvas.toDataURL('image/png')
|
||||
resolve(result);
|
||||
canvas.height = canvas.width = 0
|
||||
}
|
||||
image.src = path + '?v=' + Math.random()
|
||||
image.onerror = (error) => {
|
||||
reject(error);
|
||||
};
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
if (uni.canIUse('getFileSystemManager')) {
|
||||
uni.getFileSystemManager().readFile({
|
||||
filePath: path,
|
||||
encoding: 'base64',
|
||||
success: (res) => {
|
||||
resolve('data:image/png;base64,' + res.data)
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error({error, path})
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
|
||||
entry.file((file) => {
|
||||
const fileReader = new plus.io.FileReader()
|
||||
fileReader.onload = (data) => {
|
||||
resolve(data.target.result)
|
||||
}
|
||||
fileReader.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}, reject)
|
||||
}, reject)
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getImageInfo(path, useCORS) {
|
||||
const isCanvas2D = this && this.canvas && this.canvas.createImage
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// let time = +new Date()
|
||||
let src = path.replace(/^@\//,'/')
|
||||
if (cache[path] && cache[path].errMsg) {
|
||||
resolve(cache[path])
|
||||
} else {
|
||||
try {
|
||||
// #ifdef MP || APP-PLUS
|
||||
if (isBase64(path) && (isCanvas2D ? isPC : true)) {
|
||||
src = await base64ToPath(path)
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(useCORS) {
|
||||
src = await pathToBase64(path)
|
||||
}
|
||||
// #endif
|
||||
} catch (error) {
|
||||
reject({
|
||||
...error,
|
||||
src
|
||||
})
|
||||
}
|
||||
// #ifndef APP-NVUE
|
||||
if(isCanvas2D && !isPC) {
|
||||
const img = this.canvas.createImage()
|
||||
img.onload = function() {
|
||||
const image = {
|
||||
path: img,
|
||||
width: img.width,
|
||||
height: img.height
|
||||
}
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
}
|
||||
img.onerror = function(err) {
|
||||
reject({err,path})
|
||||
}
|
||||
img.src = src
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (image) => {
|
||||
const localReg = /^\.|^\/(?=[^\/])/;
|
||||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
|
||||
image.path = localReg.test(src) ? `/${image.path}` : image.path;
|
||||
// #endif
|
||||
if(isCanvas2D) {
|
||||
const img = this.canvas.createImage()
|
||||
img.onload = function() {
|
||||
image.path = img
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
}
|
||||
img.onerror = function(err) {
|
||||
reject({err,path})
|
||||
}
|
||||
img.src = src
|
||||
return
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
// console.log('getImageInfo', +new Date() - time)
|
||||
// ios 比较严格 可能需要设置跨域
|
||||
if(uni.getSystemInfoSync().osName == 'ios' && useCORS) {
|
||||
pathToBase64(image.path).then(base64 => {
|
||||
image.path = base64
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
}).catch(err => {
|
||||
console.error({err, path})
|
||||
reject({err,path})
|
||||
})
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
cache[path] = image
|
||||
resolve(cache[path])
|
||||
},
|
||||
fail(err) {
|
||||
console.error({err, path})
|
||||
reject({err,path})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const getLocalFilePath = (path) => {
|
||||
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path
|
||||
.indexOf('_downloads') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('file://') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/storage/emulated/0/') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/') === 0) {
|
||||
const localFilePath = plus.io.convertAbsoluteFileSystem(path)
|
||||
if (localFilePath !== path) {
|
||||
return localFilePath
|
||||
} else {
|
||||
path = path.substr(1)
|
||||
}
|
||||
}
|
||||
return '_www/' + path
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
119
uni_modules/lime-painter/hybrid/html/index.html
Normal file
@@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title></title>
|
||||
<style type="text/css">
|
||||
html,
|
||||
body,
|
||||
canvas {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="lime-painter"></canvas>
|
||||
<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
|
||||
<script type="text/javascript" src="./painter.js"></script>
|
||||
<script>
|
||||
var cache = [];
|
||||
var painter = null;
|
||||
var canvas = null;
|
||||
var context = null;
|
||||
var timer = null;
|
||||
var pixelRatio = 1;
|
||||
console.log = function (...args) {
|
||||
postMessage(args);
|
||||
};
|
||||
// function stringify(key, value) {
|
||||
// if (typeof value === 'object' && value !== null) {
|
||||
// if (cache.indexOf(value) !== -1) {
|
||||
// return;
|
||||
// }
|
||||
// cache.push(value);
|
||||
// }
|
||||
// return value;
|
||||
// };
|
||||
|
||||
function emit(event, data) {
|
||||
postMessage({
|
||||
event,
|
||||
data: (typeof data !== 'object' && data !== null ? data : JSON.stringify(data))
|
||||
});
|
||||
cache = [];
|
||||
};
|
||||
function postMessage(data) {
|
||||
uni.postMessage({
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
function init(dpr) {
|
||||
canvas = document.querySelector('#lime-painter');
|
||||
context = canvas.getContext('2d');
|
||||
pixelRatio = dpr || window.devicePixelRatio;
|
||||
painter = new Painter({
|
||||
id: 'lime-painter',
|
||||
context,
|
||||
canvas,
|
||||
pixelRatio,
|
||||
width: canvas.offsetWidth,
|
||||
height: canvas.offsetHeight,
|
||||
listen: {
|
||||
onProgress(v) {
|
||||
emit('progressChange', v);
|
||||
},
|
||||
onEffectFail(err) {
|
||||
//console.error(err)
|
||||
emit('fail', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
emit('inited', true);
|
||||
};
|
||||
function save(args) {
|
||||
delete args.success;
|
||||
delete args.fail;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
const path = painter.save(args);
|
||||
if (typeof path == 'string') {
|
||||
const index = Math.ceil(path.length / 8);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (i == 7) {
|
||||
emit('success', path.substr(i * index, index));
|
||||
} else {
|
||||
emit('file', path.substr(i * index, index));
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// console.log('canvas no data')
|
||||
emit('fail', 'canvas no data');
|
||||
};
|
||||
}, 30);
|
||||
};
|
||||
async function source(args) {
|
||||
let size = await painter.source(args);
|
||||
emit('layoutChange', size);
|
||||
if(!canvas.height) {
|
||||
console.log('canvas no size')
|
||||
emit('fail', 'canvas no size');
|
||||
}
|
||||
painter.render().catch(err => {
|
||||
// console.error(err)
|
||||
emit('fail', err);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
uni_modules/lime-painter/hybrid/html/painter.js
Normal file
93
uni_modules/lime-painter/package.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"id": "lime-painter",
|
||||
"displayName": "海报画板",
|
||||
"version": "1.9.6.6",
|
||||
"description": "一款canvas海报组件,更优雅的海报生成方案,有限的支持富文本",
|
||||
"keywords": [
|
||||
"海报",
|
||||
"富文本",
|
||||
"生成海报",
|
||||
"生成二维码",
|
||||
"JSON"
|
||||
],
|
||||
"repository": "https://gitee.com/liangei/lime-painter",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.4.14"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": "305716444"
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "lime-painter",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
388
uni_modules/lime-painter/parser.js
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* HTML5 Parser By Sam Blowes
|
||||
*
|
||||
* Designed for HTML5 documents
|
||||
*
|
||||
* Original code by John Resig (ejohn.org)
|
||||
* http://ejohn.org/blog/pure-javascript-html-parser/
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* License
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This code is triple licensed using Apache Software License 2.0,
|
||||
* Mozilla Public License or GNU Public License
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License
|
||||
* Version 1.1 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS"
|
||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing rights and limitations
|
||||
* under the License.
|
||||
*
|
||||
* The Original Code is Simple HTML Parser.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Erik Arvidsson.
|
||||
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
|
||||
* Reserved.
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* Usage
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* // Use like so:
|
||||
* HTMLParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* // or to get an XML string:
|
||||
* HTMLtoXML(htmlString);
|
||||
*
|
||||
* // or to get an XML DOM Document
|
||||
* HTMLtoDOM(htmlString);
|
||||
*
|
||||
* // or to inject into an existing document/DOM node
|
||||
* HTMLtoDOM(htmlString, document);
|
||||
* HTMLtoDOM(htmlString, document.body);
|
||||
*
|
||||
*/
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
|
||||
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
|
||||
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
|
||||
|
||||
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
|
||||
// fixed by xxx 将 ins 标签从块级名单中移除
|
||||
|
||||
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
|
||||
|
||||
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
|
||||
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
|
||||
|
||||
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
|
||||
|
||||
var special = makeMap('script,style');
|
||||
function HTMLParser(html, handler) {
|
||||
var index;
|
||||
var chars;
|
||||
var match;
|
||||
var stack = [];
|
||||
var last = html;
|
||||
|
||||
stack.last = function () {
|
||||
return this[this.length - 1];
|
||||
};
|
||||
|
||||
while (html) {
|
||||
chars = true; // Make sure we're not in a script or style element
|
||||
|
||||
if (!stack.last() || !special[stack.last()]) {
|
||||
// Comment
|
||||
if (html.indexOf('<!--') == 0) {
|
||||
index = html.indexOf('-->');
|
||||
|
||||
if (index >= 0) {
|
||||
if (handler.comment) {
|
||||
handler.comment(html.substring(4, index));
|
||||
}
|
||||
|
||||
html = html.substring(index + 3);
|
||||
chars = false;
|
||||
} // end tag
|
||||
|
||||
} else if (html.indexOf('</') == 0) {
|
||||
match = html.match(endTag);
|
||||
|
||||
if (match) {
|
||||
html = html.substring(match[0].length);
|
||||
match[0].replace(endTag, parseEndTag);
|
||||
chars = false;
|
||||
} // start tag
|
||||
|
||||
} else if (html.indexOf('<') == 0) {
|
||||
match = html.match(startTag);
|
||||
|
||||
if (match) {
|
||||
html = html.substring(match[0].length);
|
||||
match[0].replace(startTag, parseStartTag);
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (chars) {
|
||||
index = html.indexOf('<');
|
||||
var text = index < 0 ? html : html.substring(0, index);
|
||||
html = index < 0 ? '' : html.substring(index);
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
|
||||
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text);
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
parseEndTag('', stack.last());
|
||||
}
|
||||
|
||||
if (html == last) {
|
||||
throw 'Parse Error: ' + html;
|
||||
}
|
||||
|
||||
last = html;
|
||||
} // Clean up any remaining tags
|
||||
|
||||
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag(tag, tagName, rest, unary) {
|
||||
tagName = tagName.toLowerCase();
|
||||
if (block[tagName]) {
|
||||
while (stack.last() && inline[stack.last()]) {
|
||||
parseEndTag('', stack.last());
|
||||
}
|
||||
}
|
||||
|
||||
if (closeSelf[tagName] && stack.last() == tagName) {
|
||||
parseEndTag('', tagName);
|
||||
}
|
||||
|
||||
unary = empty[tagName] || !!unary;
|
||||
|
||||
if (!unary) {
|
||||
stack.push(tagName);
|
||||
}
|
||||
|
||||
if (handler.start) {
|
||||
var attrs = [];
|
||||
rest.replace(attr, function (match, name) {
|
||||
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
|
||||
attrs.push({
|
||||
name: name,
|
||||
value: value,
|
||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
if (handler.start) {
|
||||
handler.start(tagName, attrs, unary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag(tag, tagName) {
|
||||
// If no tag name is provided, clean shop
|
||||
if (!tagName) {
|
||||
var pos = 0;
|
||||
} // Find the closest opened tag of the same type
|
||||
else {
|
||||
for (var pos = stack.length - 1; pos >= 0; pos--) {
|
||||
if (stack[pos] == tagName) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
for (var i = stack.length - 1; i >= pos; i--) {
|
||||
if (handler.end) {
|
||||
handler.end(stack[i]);
|
||||
}
|
||||
} // Remove the open elements from the stack
|
||||
|
||||
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeMap(str) {
|
||||
var obj = {};
|
||||
var items = str.split(',');
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
obj[items[i]] = true;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function removeDOCTYPE(html) {
|
||||
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
|
||||
}
|
||||
|
||||
function parseAttrs(attrs) {
|
||||
return attrs.reduce(function (pre, attr) {
|
||||
var value = attr.value;
|
||||
var name = attr.name;
|
||||
if (pre[name]) {
|
||||
pre[name] = pre[name] + " " + value;
|
||||
} else {
|
||||
pre[name] = value;
|
||||
}
|
||||
|
||||
return pre;
|
||||
}, {});
|
||||
}
|
||||
function convertStyleStringToJSON(styleString) {
|
||||
var styles = styleString.split(";"); // 通过分号将样式字符串分割为多个样式声明
|
||||
var result = {};
|
||||
|
||||
styles.forEach(function(style) {
|
||||
var styleParts = style.split(":"); // 通过冒号将样式声明分割为属性和值
|
||||
var property = styleParts[0].trim();
|
||||
var value = styleParts[1] && styleParts[1].trim();
|
||||
|
||||
if (property && value) {
|
||||
result[property] = value; // 将属性和值添加到结果对象中
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
function parseHtml(html) {
|
||||
html = removeDOCTYPE(html);
|
||||
var stacks = [];
|
||||
var results = {
|
||||
node: 'root',
|
||||
children: []
|
||||
};
|
||||
HTMLParser(html, {
|
||||
start: function start(tag, attrs, unary) {
|
||||
var node = {
|
||||
name: tag
|
||||
};
|
||||
|
||||
if (attrs.length !== 0) {
|
||||
node.attrs = parseAttrs(attrs);
|
||||
node.styles = node.attrs.style ? convertStyleStringToJSON(node.attrs.style) : {}
|
||||
}
|
||||
|
||||
if(!node.type) {
|
||||
if(inline[node.name] && node.name !== 'img' ) {
|
||||
node.type = 'text';
|
||||
if(node.name == 'br') {
|
||||
node.text = '\n'
|
||||
} else if(node.name == 'strong'){
|
||||
node.styles.fontWeight = 'bold'
|
||||
}
|
||||
} else if(node.name == 'img'){
|
||||
node.type = 'image'
|
||||
node.src = node.attrs.src
|
||||
} else {
|
||||
node.type = 'view'
|
||||
if(['h1','h2','h3','h4','h5','h6'].includes(node.name)) {
|
||||
node.styles.fontWeight = 'bold'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unary) {
|
||||
var parent = stacks[0] || results;
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
} else {
|
||||
stacks.unshift(node);
|
||||
}
|
||||
},
|
||||
end: function end(tag) {
|
||||
var node = stacks.shift();
|
||||
if (node.name !== tag) console.error('invalid state: mismatch end tag');
|
||||
if (stacks.length === 0) {
|
||||
results.children.push(node);
|
||||
} else {
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
parent.children.push(node);
|
||||
}
|
||||
const isTextBox = node.children && node.children.length > 1 && node.children.every(child => {
|
||||
return ['text','image'].includes(child.type)
|
||||
})
|
||||
if(isTextBox) {
|
||||
node.type = 'textBox'
|
||||
}
|
||||
},
|
||||
chars: function chars(text) {
|
||||
var node = {
|
||||
type: 'text',
|
||||
text: text
|
||||
};
|
||||
|
||||
if (stacks.length === 0) {
|
||||
results.children.push(node);
|
||||
} else {
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
}
|
||||
},
|
||||
comment: function comment(text) {
|
||||
var node = {
|
||||
node: 'comment',
|
||||
text: text
|
||||
};
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
}
|
||||
});
|
||||
return results.children;
|
||||
}
|
||||
|
||||
export default parseHtml;
|
||||
961
uni_modules/lime-painter/readme.md
Normal file
@@ -0,0 +1,961 @@
|
||||
# Painter 画板 测试版
|
||||
|
||||
> uniapp 海报画板,更优雅的海报生成方案
|
||||
> [查看更多](https://limeui.qcoon.cn/#/painter)
|
||||
|
||||
## 平台兼容
|
||||
|
||||
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
|
||||
| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
|
||||
| √ | √ | √ | 未测 | √ | √ | √ |
|
||||
|
||||
## 安装
|
||||
在市场导入**[海报画板](https://ext.dcloud.net.cn/plugin?id=2389)uni_modules**版本的即可,无需`import`
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 插件demo
|
||||
- lime-painter 为 demo
|
||||
- 位于 uni_modules/lime-painter/components/lime-painter
|
||||
- 导入插件后直接使用可查看demo
|
||||
```vue
|
||||
<lime-painter />
|
||||
```
|
||||
|
||||
|
||||
### 基本用法
|
||||
|
||||
- 插件提供 JSON 及 Template 的方式绘制海报
|
||||
- 参考 css 块状流布局模拟 css schema。
|
||||
- 另外flex布局还不是成完善,请谨慎使用,普通的流布局我觉得已经够用了。
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
- 提供`l-painter-view`、`l-painter-text`、`l-painter-image`、`l-painter-qrcode`四种类型组件
|
||||
- 通过 `css` 属性绘制样式,与 style 使用方式保持一致。
|
||||
```html
|
||||
<l-painter>
|
||||
//如果使用Template出现顺序错乱,可使用`template` 等所有变量完成再显示
|
||||
<template v-if="show">
|
||||
<l-painter-view
|
||||
css="background: #07c160; height: 120rpx; width: 120rpx; display: inline-block"
|
||||
></l-painter-view>
|
||||
<l-painter-view
|
||||
css="background: #1989fa; height: 120rpx; width: 120rpx; border-top-right-radius: 60rpx; border-bottom-left-radius: 60rpx; display: inline-block; margin: 0 30rpx;"
|
||||
></l-painter-view>
|
||||
<l-painter-view
|
||||
css="background: #ff9d00; height: 120rpx; width: 120rpx; border-radius: 50%; display: inline-block"
|
||||
></l-painter-view>
|
||||
<template>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
- 在 json 里四种类型组件的`type`为`view`、`text`、`image`、`qrcode`
|
||||
- 通过 `board` 设置海报所需的 JSON 数据进行绘制或`ref`获取组件实例调用组件内的`render(json)`
|
||||
- 所有类型的 schema 都具有`css`字段,css 的 key 值使用**驼峰**如:`lineHeight`
|
||||
|
||||
```html
|
||||
<l-painter :board="poster"/>
|
||||
```
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
poster: {
|
||||
css: {
|
||||
// 根节点若无尺寸,自动获取父级节点
|
||||
width: '750rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
css: {
|
||||
background: "#07c160",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
display: "inline-block"
|
||||
},
|
||||
type: "view"
|
||||
},
|
||||
{
|
||||
css: {
|
||||
background: "#1989fa",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
borderTopRightRadius: "60rpx",
|
||||
borderBottomLeftRadius: "60rpx",
|
||||
display: "inline-block",
|
||||
margin: "0 30rpx"
|
||||
},
|
||||
views: [],
|
||||
type: "view"
|
||||
},
|
||||
{
|
||||
css: {
|
||||
background: "#ff9d00",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
borderRadius: "50%",
|
||||
display: "inline-block"
|
||||
},
|
||||
views: [],
|
||||
type: "view"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### View 容器
|
||||
|
||||
- 类似于 `div` 可以嵌套承载更多的 view、text、image,qrcode 共同构建一颗完整的节点树
|
||||
- 在 JSON 里具有 `views` 的数组字段,用于嵌套承载节点。
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<l-painter-view css="background: #f0f0f0; padding-top: 100rpx;">
|
||||
<l-painter-view
|
||||
css="background: #d9d9d9; width: 33.33%; height: 100rpx; display: inline-block"
|
||||
></l-painter-view>
|
||||
<l-painter-view
|
||||
css="background: #bfbfbf; width: 66.66%; height: 100rpx; display: inline-block"
|
||||
></l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
{
|
||||
css: {},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#f0f0f0',
|
||||
paddingTop: '100rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#d9d9d9',
|
||||
width: '33.33%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#bfbfbf',
|
||||
width: '66.66%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Text 文本
|
||||
|
||||
- 通过 `text` 属性填写文本内容。
|
||||
- 支持`\n`换行符
|
||||
- 支持省略号,使用 css 的`line-clamp`设置行数,当文字内容超过会显示省略号。
|
||||
- 支持`text-decoration`
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<l-painter-view css="background: #e0e2db; padding: 30rpx; color: #222a29">
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
css="text-align:center; padding-top: 20rpx; text-decoration: line-through "
|
||||
/>
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
css="text-align:right; padding-top: 20rpx"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
|
||||
css="line-clamp: 3; padding-top: 20rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%); background-clip: text"
|
||||
/>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
// 基础用法
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置居中对齐
|
||||
textAlign: 'center',
|
||||
// 设置中划线
|
||||
textDecoration: 'line-through'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置右对齐
|
||||
textAlign: 'right',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置行数,超出显示省略号
|
||||
lineClamp: 3,
|
||||
// 渐变文字
|
||||
background: 'linear-gradient(,#ff971b 0%, #1989fa 100%)',
|
||||
backgroundClip: 'text'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Image 图片
|
||||
|
||||
- 通过 `src` 属性填写图片路径。
|
||||
- 图片路径支持:网络图片,本地 static 里的图片路径,缓存路径,**字节的static目录是写相对路径**
|
||||
- 通过 `css` 的 `object-fit`属性可以设置图片的填充方式,可选值见下方 CSS 表格。
|
||||
- 通过 `css` 的 `object-position`配合 `object-fit` 可以设置图片的对齐方式,类似于`background-position`,详情见下方 CSS 表格。
|
||||
- 使用网络图片时:小程序需要去公众平台配置 [downloadFile](https://mp.weixin.qq.com/) 域名
|
||||
- 使用网络图片时:**H5 和 Nvue 需要决跨域问题**
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<!-- 基础用法 -->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx"
|
||||
/>
|
||||
<!-- 填充方式 -->
|
||||
<!-- css object-fit 设置 填充方式 见下方表格-->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx; object-fit: contain; background: #eee"
|
||||
/>
|
||||
<!-- css object-position 设置 图片的对齐方式-->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx; object-fit: contain; object-position: 50% 50%; background: #eee"
|
||||
/>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
// 基础用法
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx'
|
||||
}
|
||||
},
|
||||
// 填充方式
|
||||
// css objectFit 设置 填充方式 见下方表格
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
objectFit: 'contain'
|
||||
}
|
||||
},
|
||||
// css objectPosition 设置 图片的对齐方式
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
objectFit: 'contain',
|
||||
objectPosition: '50% 50%'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Qrcode 二维码
|
||||
|
||||
- 通过`text`属性填写需要生成二维码的文本。
|
||||
- 通过 `css` 里的 `color` 可设置生成码点的颜色。
|
||||
- 通过 `css` 里的 `background`可设置背景色。
|
||||
- 通过 `css `里的 `width`、`height`设置尺寸。
|
||||
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<l-painter>
|
||||
<l-painter-qrcode
|
||||
text="limeui.qcoon.cn"
|
||||
css="width: 200rpx; height: 200rpx"
|
||||
/>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```js
|
||||
{
|
||||
type: 'qrcode',
|
||||
text: 'limeui.qcoon.cn',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 富文本
|
||||
- 这是一个有限支持的测试能力,只能通过JSON方式,不要抱太大希望!
|
||||
- 首先需要把富文本转成JSON,这需要引入`parser`这个包,如果你不使用是不会进入主包
|
||||
|
||||
```html
|
||||
<l-painter ref="painter"/>
|
||||
```
|
||||
```js
|
||||
import parseHtml from '@/uni_modules/lime-painter/parser'
|
||||
const json = parseHtml(`<p><span>测试测试</span><img src="/static/logo.png"/></p>`)
|
||||
this.$refs.painter.render(json)
|
||||
```
|
||||
|
||||
### 生成图片
|
||||
|
||||
- 方式1、通过设置`isCanvasToTempFilePath`自动生成图片并在 `@success` 事件里接收海报临时路径
|
||||
- 方式2、通过调用内部方法生成图片:
|
||||
|
||||
```html
|
||||
<l-painter ref="painter">...code</l-painter>
|
||||
```
|
||||
|
||||
```js
|
||||
this.$refs.painter.canvasToTempFilePathSync({
|
||||
fileType: "jpg",
|
||||
// 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
|
||||
pathType: 'url',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
console.log(res.tempFilePath);
|
||||
// 非H5 保存到相册
|
||||
// H5 提示用户长按图另存
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: function () {
|
||||
console.log('save success');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 主动调用方式
|
||||
|
||||
- 通过获取组件实例内部的`render`函数 传递`JSON`即可
|
||||
|
||||
```html
|
||||
<l-painter ref="painter" />
|
||||
```
|
||||
|
||||
```js
|
||||
// 渲染
|
||||
this.$refs.painter.render(jsonSchema);
|
||||
// 生成图片
|
||||
this.$refs.painter.canvasToTempFilePathSync({
|
||||
fileType: "jpg",
|
||||
// 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
|
||||
pathType: 'url',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
console.log(res.tempFilePath);
|
||||
// 非H5 保存到相册
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: function () {
|
||||
console.log('save success');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### H5跨域
|
||||
- 一般是需要后端或管理OSS资源的大佬处理
|
||||
- 一般OSS的处理方式:
|
||||
|
||||
1、设置来源
|
||||
```cmd
|
||||
*
|
||||
```
|
||||
|
||||
2、允许Methods
|
||||
```html
|
||||
GET
|
||||
```
|
||||
|
||||
3、允许Headers
|
||||
```html
|
||||
access-control-allow-origin:*
|
||||
```
|
||||
|
||||
4、最后如果还是不行,可试下给插件设置`useCORS`
|
||||
```html
|
||||
<l-painter useCORS>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 海报示例
|
||||
|
||||
- 提供一份示例,只把插件当成生成图片的工具,非必要不要在弹窗里使用。
|
||||
- 通过设置`isCanvasToTempFilePath`主动生成图片,再由 `@success` 事件接收海报临时路径
|
||||
- 设置`hidden`隐藏画板。
|
||||
请注意,示例用到了图片,海报的渲染是包括下载图片的时间,也许在某天图片会失效或访问超级慢,请更换为你的图片再查看,另外如果你是小程序请在使用示例时把**不校验合法域名**勾上!!!!!不然不显示还以为是插件的锅,求求了大佬们!
|
||||
#### 方式一 Template
|
||||
|
||||
```html
|
||||
<image :src="path" mode="widthFix"></image>
|
||||
<l-painter
|
||||
isCanvasToTempFilePath
|
||||
@success="path = $event"
|
||||
hidden
|
||||
css="width: 750rpx; padding-bottom: 40rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%)"
|
||||
>
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="margin-left: 40rpx; margin-top: 40rpx; width: 84rpx; height: 84rpx; border-radius: 50%;"
|
||||
/>
|
||||
<l-painter-view
|
||||
css="margin-top: 40rpx; padding-left: 20rpx; display: inline-block"
|
||||
>
|
||||
<l-painter-text
|
||||
text="隔壁老王"
|
||||
css="display: block; padding-bottom: 10rpx; color: #fff; font-size: 32rpx; fontWeight: bold"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="为您挑选了一个好物"
|
||||
css="color: rgba(255,255,255,.7); font-size: 24rpx"
|
||||
/>
|
||||
</l-painter-view>
|
||||
<l-painter-view
|
||||
css="margin-left: 40rpx; margin-top: 30rpx; padding: 32rpx; box-sizing: border-box; background: #fff; border-radius: 16rpx; width: 670rpx; box-shadow: 0 20rpx 58rpx rgba(0,0,0,.15)"
|
||||
>
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="object-fit: cover; object-position: 50% 50%; width: 606rpx; height: 606rpx; border-radius: 12rpx;"
|
||||
/>
|
||||
<l-painter-view
|
||||
css="margin-top: 32rpx; color: #FF0000; font-weight: bold; font-size: 28rpx; line-height: 1em;"
|
||||
>
|
||||
<l-painter-text text="¥" css="vertical-align: bottom" />
|
||||
<l-painter-text
|
||||
text="39"
|
||||
css="vertical-align: bottom; font-size: 58rpx"
|
||||
/>
|
||||
<l-painter-text text=".39" css="vertical-align: bottom" />
|
||||
<l-painter-text
|
||||
text="¥59.99"
|
||||
css="vertical-align: bottom; padding-left: 10rpx; font-weight: normal; text-decoration: line-through; color: #999999"
|
||||
/>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 32rpx; font-size: 26rpx; color: #8c5400">
|
||||
<l-painter-text text="自营" css="color: #212121; background: #ffb400;" />
|
||||
<l-painter-text
|
||||
text="30天最低价"
|
||||
css="margin-left: 16rpx; background: #fff4d9; text-decoration: line-through;"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="满减优惠"
|
||||
css="margin-left: 16rpx; background: #fff4d9"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="超高好评"
|
||||
css="margin-left: 16rpx; background: #fff4d9"
|
||||
/>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 30rpx">
|
||||
<l-painter-text
|
||||
css="line-clamp: 2; color: #333333; line-height: 1.8em; font-size: 36rpx; width: 478rpx; padding-right:32rpx; box-sizing: border-box"
|
||||
text="360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝"
|
||||
></l-painter-text>
|
||||
<l-painter-qrcode
|
||||
css="width: 128rpx; height: 128rpx;"
|
||||
text="limeui.qcoon.cn"
|
||||
></l-painter-qrcode>
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
path: ''
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 方式二 JSON
|
||||
|
||||
```html
|
||||
<image :src="path" mode="widthFix"></image>
|
||||
<l-painter
|
||||
:board="poster"
|
||||
isCanvasToTempFilePath
|
||||
@success="path = $event"
|
||||
hidden
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
path: '',
|
||||
poster: {
|
||||
css: {
|
||||
width: "750rpx",
|
||||
paddingBottom: "40rpx",
|
||||
background: "linear-gradient(,#000 0%, #ff5000 100%)"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
|
||||
type: "image",
|
||||
css: {
|
||||
background: "#fff",
|
||||
objectFit: "cover",
|
||||
marginLeft: "40rpx",
|
||||
marginTop: "40rpx",
|
||||
width: "84rpx",
|
||||
border: "2rpx solid #fff",
|
||||
boxSizing: "border-box",
|
||||
height: "84rpx",
|
||||
borderRadius: "50%"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
marginTop: "40rpx",
|
||||
paddingLeft: "20rpx",
|
||||
display: "inline-block"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
text: "隔壁老王",
|
||||
type: "text",
|
||||
css: {
|
||||
display: "block",
|
||||
paddingBottom: "10rpx",
|
||||
color: "#fff",
|
||||
fontSize: "32rpx",
|
||||
fontWeight: "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "为您挑选了一个好物",
|
||||
type: "text",
|
||||
css: {
|
||||
color: "rgba(255,255,255,.7)",
|
||||
fontSize: "24rpx"
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
css: {
|
||||
marginLeft: "40rpx",
|
||||
marginTop: "30rpx",
|
||||
padding: "32rpx",
|
||||
boxSizing: "border-box",
|
||||
background: "#fff",
|
||||
borderRadius: "16rpx",
|
||||
width: "670rpx",
|
||||
boxShadow: "0 20rpx 58rpx rgba(0,0,0,.15)"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
|
||||
type: "image",
|
||||
css: {
|
||||
objectFit: "cover",
|
||||
objectPosition: "50% 50%",
|
||||
width: "606rpx",
|
||||
height: "606rpx"
|
||||
},
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "32rpx",
|
||||
color: "#FF0000",
|
||||
fontWeight: "bold",
|
||||
fontSize: "28rpx",
|
||||
lineHeight: "1em"
|
||||
},
|
||||
views: [{
|
||||
text: "¥",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom"
|
||||
},
|
||||
}, {
|
||||
text: "39",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom",
|
||||
fontSize: "58rpx"
|
||||
},
|
||||
}, {
|
||||
text: ".39",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom"
|
||||
},
|
||||
}, {
|
||||
text: "¥59.99",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom",
|
||||
paddingLeft: "10rpx",
|
||||
fontWeight: "normal",
|
||||
textDecoration: "line-through",
|
||||
color: "#999999"
|
||||
}
|
||||
}],
|
||||
|
||||
type: "view"
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "32rpx",
|
||||
fontSize: "26rpx",
|
||||
color: "#8c5400"
|
||||
},
|
||||
views: [{
|
||||
text: "自营",
|
||||
type: "text",
|
||||
css: {
|
||||
color: "#212121",
|
||||
background: "#ffb400"
|
||||
},
|
||||
}, {
|
||||
text: "30天最低价",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9",
|
||||
textDecoration: "line-through"
|
||||
},
|
||||
}, {
|
||||
text: "满减优惠",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9"
|
||||
},
|
||||
}, {
|
||||
text: "超高好评",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9"
|
||||
},
|
||||
|
||||
}],
|
||||
|
||||
type: "view"
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "30rpx"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
text: "360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝",
|
||||
type: "text",
|
||||
css: {
|
||||
paddingRight: "32rpx",
|
||||
boxSizing: "border-box",
|
||||
lineClamp: 2,
|
||||
color: "#333333",
|
||||
lineHeight: "1.8em",
|
||||
fontSize: "36rpx",
|
||||
width: "478rpx"
|
||||
},
|
||||
}, {
|
||||
text: "limeui.qcoon.cn",
|
||||
type: "qrcode",
|
||||
css: {
|
||||
width: "128rpx",
|
||||
height: "128rpx",
|
||||
},
|
||||
|
||||
}],
|
||||
type: "view"
|
||||
}],
|
||||
type: "view"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 自定义字体
|
||||
- 需要平台的支持,已知微信小程序支持,其它的没试过,如果可行请告之
|
||||
|
||||
```
|
||||
// 需要在app.vue中下载字体
|
||||
uni.loadFontFace({
|
||||
global:true,
|
||||
scopes: ['native'],
|
||||
family: '自定义字体名称',
|
||||
source: 'url("https://sungd.github.io/Pacifico.ttf")',
|
||||
|
||||
success() {
|
||||
console.log('success')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 然后就可以在插件的css中写font-family: '自定义字体名称'
|
||||
```
|
||||
|
||||
|
||||
### Nvue
|
||||
- 必须为HBX 3.4.11及以上
|
||||
|
||||
|
||||
### 原生小程序
|
||||
|
||||
- 插件里的`painter.js`支持在原生小程序中使用
|
||||
- new Painter 之后在`source`里传入 JSON
|
||||
- 再调用`render`绘制海报
|
||||
- 如需生成图片,请查看微信小程序 cavnas 的[canvasToTempFilePath](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html)
|
||||
|
||||
```html
|
||||
<canvas type="2d" id="painter" style="width: 100%"></canvas>
|
||||
```
|
||||
|
||||
```js
|
||||
import { Painter } from "./painter";
|
||||
page({
|
||||
data: {
|
||||
poster: {
|
||||
css: {
|
||||
width: "750rpx",
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#d2d4c8",
|
||||
paddingTop: "100rpx",
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#5f7470",
|
||||
width: "33.33%",
|
||||
height: "100rpx",
|
||||
display: "inline-block",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#889696",
|
||||
width: "33.33%",
|
||||
height: "100rpx",
|
||||
display: "inline-block",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
background: "#b8bdb5",
|
||||
width: "33.33%",
|
||||
height: "100rpx",
|
||||
display: "inline-block",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
async onLoad() {
|
||||
const res = await this.getCentext();
|
||||
const painter = new Painter(res);
|
||||
// 返回计算布局后的整个内容尺寸
|
||||
const { width, height } = await painter.source(this.data.poster);
|
||||
// 得到计算后的尺寸后 可给canvas尺寸赋值,达到动态响应效果
|
||||
// 渲染
|
||||
await painter.render();
|
||||
},
|
||||
// 获取canvas 2d
|
||||
// 非2d 需要传一个 createImage 方法用于获取图片信息 即把 getImageInfo 的 success 通过 promise resolve 返回
|
||||
getCentext() {
|
||||
return new Promise((resolve) => {
|
||||
wx.createSelectorQuery()
|
||||
.select(`#painter`)
|
||||
.node()
|
||||
.exec((res) => {
|
||||
let { node: canvas } = res[0];
|
||||
resolve({
|
||||
canvas,
|
||||
context: canvas.getContext("2d"),
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
// createImage: getImageInfo()
|
||||
pixelRatio: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 旧版(1.6.x)更新
|
||||
|
||||
- 由于 1.8.x 版放弃了以定位的方式,所以 1.6.x 版更新之后要每个样式都加上`position: absolute`
|
||||
- 旧版的 `image` mode 模式被放弃,使用`object-fit`
|
||||
- 旧版的 `isRenderImage` 改成 `is-canvas-to-temp-file-path`
|
||||
- 旧版的 `maxLines` 改成 `line-clamp`
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| -------------------------- | ------------------------------------------------------------ | ---------------- | ------------ |
|
||||
| board | JSON 方式的海报元素对象集 | <em>object</em> | - |
|
||||
| css | 海报内容最外层的样式,可以理解为`body` | <em>object</em> | 参数请向下看 |
|
||||
| custom-style | canvas 元素的样式 | <em>string</em> | |
|
||||
| hidden | 隐藏画板 | <em>boolean</em> | `false` |
|
||||
| is-canvas-to-temp-file-path | 是否生成图片,在`@success`事件接收图片地址 | <em>boolean</em> | `false` |
|
||||
| after-delay | 生成图片错乱,可延时生成图片 | <em>number</em> | `100` |
|
||||
| type | canvas 类型,对微信头条支付宝小程序可有效,可选值:`2d`,`''` | <em>string</em> | `2d` |
|
||||
| file-type | 生成图片的后缀类型, 可选值:`png`、`jpg` | <em>string</em> | `png` |
|
||||
| path-type | 生成图片路径类型,可选值`url`、`base64` | <em>string</em> | `-` |
|
||||
| pixel-ratio | 生成图片的像素密度,默认为对应手机的像素密度,`nvue`无效 | <em>number</em> | `-` |
|
||||
| hidpi | H5和APP是否使用高清处理 | <em>boolean</em> | `true` |
|
||||
| width | **废弃** 画板的宽度,一般只用于通过内部方法时加上 | <em>number</em> | `` |
|
||||
| height | **废弃** 画板的高度 ,同上 | <em>number</em> | `` |
|
||||
|
||||
### css
|
||||
| 属性名 | 支持的值或类型 | 默认值 |
|
||||
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
|
||||
| (min\max)width | 支持`%`、`rpx`、`px` | - |
|
||||
| height | 同上 | - |
|
||||
| color | `string` | - |
|
||||
| position | 定位,可选值:`absolute`、`fixed` | - |
|
||||
| ↳ left、top、right、bottom | 配合`position`才生效,支持`%`、`rpx`、`px` | - |
|
||||
| margin | 可简写或各方向分别写,如:`margin-top`,支持`auto`、`rpx`、`px` | - |
|
||||
| padding | 可简写或各方向分别写,支持`rpx`、`px` | - |
|
||||
| border | 可简写或各个值分开写:`border-width`、`border-style` 、`border-color`,简写请按顺序写 | - |
|
||||
| line-clamp | `number`,超过行数显示省略号 | - |
|
||||
| vertical-align | 文字垂直对齐,可选值:`bottom`、`top`、`middle` | `middle` |
|
||||
| line-height | 文字行高,支持`rpx`、`px`、`em` | `1.4em` |
|
||||
| font-weight | 文字粗细,可选值:`normal`、`bold` | `normal` |
|
||||
| font-size | 文字大小,`string`,支持`rpx`、`px` | `14px` |
|
||||
| text-decoration | 文本修饰,可选值:`underline` 、`line-through`、`overline` | - |
|
||||
| text-stroke | 文字描边,可简写或各个值分开写,如:`text-stroke-color`, `text-stroke-width` | - |
|
||||
| text-align | 文本水平对齐,可选值:`right` 、`center` | `left` |
|
||||
| display | 框类型,可选值:`block`、`inline-block`、`flex`、`none`,当为`none`时是不渲染该段, `flex`功能简陋。 | - |
|
||||
| flex | 配合 display: flex; 属性定义了在分配多余空间,目前只用为数值如: flex: 1 | - |
|
||||
| align-self | 配合 display: flex; 单个项目垂直轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
||||
| justify-content | 配合 display: flex; 水平轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
||||
| align-items | 配合 display: flex; 垂直轴对齐方式: `flex-start` `flex-end` `center` | `flex-start` |
|
||||
| border-radius | 圆角边框,支持`%`、`rpx`、`px` | - |
|
||||
| box-sizing | 可选值:`border-box` | - |
|
||||
| box-shadow | 投影 | - |
|
||||
| background(color) | 支持渐变,但必须写百分比!如:`linear-gradient(,#ff971b 0%, #ff5000 100%)`、`radial-gradient(#0ff 15%, #f0f 60%)`,目前 radial-gradient 渐变的圆心为元素中点,半径为最长边,不支持设置 | - |
|
||||
| background-clip | 文字渐变,配合`background`背景渐变,设置`background-clip: text` 达到文字渐变效果 | - |
|
||||
| background-image | view 元素背景:`url(src)`,若只是设置背景图,请不要设置`background-repeat` | - |
|
||||
| background-repeat | 设置是否及如何重复背景纹理,可选值:`repeat`、`repeat-x`、`repeat-y`、`no-repeat` | `repeat` |
|
||||
| [object-fit](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit/) | 图片元素适应容器方式,类似于`mode`,可选值:`cover`、 `contain`、 `fill`、 `none` | - |
|
||||
| [object-position](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position) | 图片的对齐方式,配合`object-fit`使用 | - |
|
||||
|
||||
### 图片填充模式 object-fit
|
||||
|
||||
| 名称 | 含义 |
|
||||
| ------- | ------------------------------------------------------ |
|
||||
| contain | 保持宽高缩放图片,使图片的长边能完全显示出来 |
|
||||
| cover | 保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边 |
|
||||
| fill | 拉伸图片,使图片填满元素 |
|
||||
| none | 保持图片原有尺寸 |
|
||||
|
||||
### 事件 Events
|
||||
|
||||
| 事件名 | 说明 | 返回值 |
|
||||
| -------- | ---------------------------------------------------------------- | ------ |
|
||||
| success | 生成图片成功,若使用`is-canvas-to-temp-filePath` 可以接收图片地址 | path |
|
||||
| fail | 生成图片失败 | error |
|
||||
| done | 绘制成功 | |
|
||||
| progress | 绘制进度 | number |
|
||||
|
||||
### 暴露函数 Expose
|
||||
| 事件名 | 说明 | 返回值 |
|
||||
| -------- | ---------------------------------------------------------------- | ------ |
|
||||
| render(object) | 渲染器,传入JSON 绘制海报 | promise |
|
||||
| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(object) | 把当前画布指定区域的内容导出生成指定大小的图片,并返回文件临时路径。 | |
|
||||
| canvasToTempFilePathSync(object) | 同步接口,同上 | |
|
||||
|
||||
|
||||
## 常见问题
|
||||
|
||||
- 1、H5 端使用网络图片需要解决跨域问题。
|
||||
- 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!
|
||||
- 3、H5 端生成图片是 base64,有时显示只有一半可以使用原生标签`<IMG/>`
|
||||
- 4、发生保存图片倾斜变形或提示 native buffer exceed size limit 时,使用 pixel-ratio="2"参数,降分辨率。
|
||||
- 5、h5 保存图片不需要调接口,提示用户长按图片保存。
|
||||
- 6、画板不能隐藏,包括`v-if`,`v-show`、`display:none`、`opacity:0`,另外也不要把画板放在弹窗里。如果需要隐藏画板请设置 `custom-style="position: fixed; left: 200%"`
|
||||
- 7、微信小程序真机调试请使用 **真机调试2.0**,不支持1.0。
|
||||
- 8、微信小程序打开调试时可以生但并闭无法生成时,这种情况一般是没有在公众号配置download域名
|
||||
- 9、HBX 3.4.5之前的版本不支持vue3
|
||||
- 10、在微信开发工具上 canvas 层级最高无法zindex,并不影响真机
|
||||
- 11、请不要导入非uni_modules插件
|
||||
- 12、关于QQ小程序 报 Propertyor method"toJSON"is not defined 请把基础库调到 1.50.3
|
||||
- 13、支付宝小程序 IDE 不支持 生成图片 请以真机调试结果为准
|
||||
- 14、返回值为字符串 `data:,` 大概是尺寸超过限制,设置 pixel-ratio="2"
|
||||
- 华为手机 APP 上无法生成图片,请使用 HBX2.9.11++(已过时,忽略这条)
|
||||
- IOS APP 请勿使用 HBX2.9.3.20201014 的版本!这个版本无法生成图片。(已过时,忽略这条)
|
||||
- 苹果微信 7.0.20 存在闪退和图片无法 onload 为微信 bug(已过时,忽略这条)
|
||||
- 微信小程序 IOS 旧接口 如父级设置圆角,子级也设会导致子级的失效,为旧接口BUG。
|
||||
- 微信小程序 安卓 旧接口 如使用图片必须加背景色,为旧接口BUG。
|
||||
- 微信小程序 安卓端 [图片可能在首次可以加载成功,再次加载会不触发任何事件](https://developers.weixin.qq.com/community/develop/doc/000ee2b8dacf4009337f51f4556800?highLine=canvas%25202d%2520createImage),临时解决方法是给图片加个时间戳
|
||||
## 打赏
|
||||
|
||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
|
||||
|
||||

|
||||

|
||||
49
uni_modules/lime-qrcode/changelog.md
Normal file
@@ -0,0 +1,49 @@
|
||||
## 0.2.4(2025-09-22)
|
||||
- fix: uniappx ios报错
|
||||
## 0.2.3(2025-06-13)
|
||||
- fix: 修复uniapp x 安卓问题
|
||||
## 0.2.2(2025-06-13)
|
||||
- fix: 修复uniapp x ios问题
|
||||
## 0.2.1(2025-05-24)
|
||||
- feat: 支持uniapp x 鸿蒙next
|
||||
## 0.2.0(2025-02-09)
|
||||
- chore: 更新文档
|
||||
## 0.1.9(2024-11-24)
|
||||
- feat: 支持uniapp x 微信小程序
|
||||
## 0.1.8(2024-08-05)
|
||||
- fix: 修复因shared升级导致微信小程序可能不生效的问题
|
||||
## 0.1.7(2024-05-15)
|
||||
- fix: 修复uvue因缺少依赖无法使用
|
||||
## 0.1.6(2024-05-09)
|
||||
- fix: 修复vue2因type问题导致无法使用
|
||||
## 0.1.5(2024-04-14)
|
||||
- fix: 修复缺少依赖
|
||||
## 0.1.4(2024-04-10)
|
||||
- chore: 更新文档
|
||||
## 0.1.3(2024-04-01)
|
||||
- chore: 兼容uniapp x ios(app-js)
|
||||
## 0.1.2(2023-12-14)
|
||||
- fix: uvue 引入 API 自定义包出错
|
||||
## 0.1.1(2023-12-11)
|
||||
- chore: uvue的二维码API独立,需要单独下载
|
||||
## 0.1.0(2023-12-07)
|
||||
- fix: 修复因utssdk目录导致无法运行
|
||||
## 0.0.9(2023-12-06)
|
||||
- feat: 支持uvue
|
||||
## 0.0.8(2023-12-06)
|
||||
- feat: 支持uvue
|
||||
## 0.0.7(2023-12-06)
|
||||
- feat: 支持uvue
|
||||
## 0.0.6(2023-12-06)
|
||||
- feat: 支持uvue
|
||||
## 0.0.5(2023-07-30)
|
||||
- fix: 修复再次生成前没有清空,导致图形叠加
|
||||
## 0.0.4(2023-07-27)
|
||||
- fix: 修复相同尺寸无法再次生成
|
||||
## 0.0.3(2023-06-09)
|
||||
- feat: 支持通过`@vue/composition-api`在`vue2`上使用
|
||||
- chore: 更新文档
|
||||
## 0.0.2(2023-06-08)
|
||||
- chore: 更新文档
|
||||
## 0.0.1(2023-06-08)
|
||||
- 首次
|
||||
180
uni_modules/lime-qrcode/components/l-qrcode/ios/index.uts
Normal file
@@ -0,0 +1,180 @@
|
||||
// export * from '../qrcode'
|
||||
import { qrcodegen } from './qrcodegen'
|
||||
|
||||
// export * from '@/uni_modules/lime-qrcodegen/utssdk/interface';
|
||||
// import { type QRCodePropsTypes , type ImageSettings, type QRCodeCallback } from '@/uni_modules/lime-qrcodegen/utssdk/interface'
|
||||
export type QRCodePropsTypes = {
|
||||
value?: string
|
||||
size?: number
|
||||
fgColor?: string
|
||||
level?: string
|
||||
marginSize: number
|
||||
includeMargin: boolean
|
||||
imageSettings?: ImageSettings
|
||||
}
|
||||
export type ImageSettings = {
|
||||
width: number
|
||||
height: number
|
||||
x?: number
|
||||
y?: number
|
||||
src?: string
|
||||
excavate: boolean
|
||||
}
|
||||
export type QRCodeCallback = (cells : boolean[][]) => void
|
||||
|
||||
|
||||
const Ecc = qrcodegen.QrCode.Ecc
|
||||
const QrCode = qrcodegen.QrCode
|
||||
type Modules = boolean[][];
|
||||
type Excavation = { x : number; y : number; w : number; h : number };
|
||||
type ImageSettingExcavation = {
|
||||
x: number
|
||||
y: number
|
||||
h: number
|
||||
w: number
|
||||
excavation: Excavation
|
||||
}
|
||||
const ERROR_LEVEL_MAP = {
|
||||
L: Ecc.LOW,
|
||||
M: Ecc.MEDIUM,
|
||||
Q: Ecc.QUARTILE,
|
||||
H: Ecc.HIGH,
|
||||
};
|
||||
|
||||
const DEFAULT_SIZE:number = 128;
|
||||
const DEFAULT_LEVEL:string = 'L';
|
||||
// const DEFAULT_BGCOLOR:string = '#FFFFFF';
|
||||
const DEFAULT_FGCOLOR:string = '#000000';
|
||||
const DEFAULT_INCLUDEMARGIN:boolean = false;
|
||||
|
||||
const SPEC_MARGIN_SIZE : number = 4;
|
||||
const DEFAULT_MARGIN_SIZE : number = 0;
|
||||
|
||||
// This is *very* rough estimate of max amount of QRCode allowed to be covered.
|
||||
// It is "wrong" in a lot of ways (area is a terrible way to estimate, it
|
||||
// really should be number of modules covered), but if for some reason we don't
|
||||
// get an explicit height or width, I'd rather default to something than throw.
|
||||
const DEFAULT_IMG_SCALE = 0.1;
|
||||
|
||||
// We could just do this in generatePath, except that we want to support
|
||||
// non-Path2D canvas, so we need to keep it an explicit step.
|
||||
function excavateModules(modules : Modules, excavation : Excavation) : Modules {
|
||||
const ox = excavation.x
|
||||
const oy = excavation.y
|
||||
const oh = excavation.h
|
||||
const ow = excavation.w
|
||||
return modules.slice().map((row, y):boolean[] => {
|
||||
if (y < oy || y >= oy + oh) {
|
||||
return row;
|
||||
}
|
||||
return row.map((cell, x):boolean => {
|
||||
if (x < ox || x >= ox + ow) {
|
||||
return cell;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getImageSettings(
|
||||
cells : Modules,
|
||||
size : number,
|
||||
margin : number,
|
||||
imageSettings: ImageSettings | null
|
||||
) : ImageSettingExcavation | null {
|
||||
if (imageSettings == null) {
|
||||
return null;
|
||||
}
|
||||
// const result : UTSJSONObject = {}
|
||||
const numCells = cells.length + margin * 2;
|
||||
const defaultSize = Math.floor(size * DEFAULT_IMG_SCALE);
|
||||
const scale = numCells / size;
|
||||
|
||||
const width = imageSettings.width
|
||||
const height = imageSettings.height
|
||||
const ox = imageSettings.x
|
||||
const oy = imageSettings.y
|
||||
const excavate = imageSettings.excavate
|
||||
|
||||
const w = (width > 0 ? width : defaultSize) * scale;
|
||||
const h = (height > 0 ? height: defaultSize) * scale;
|
||||
const x = ox == null ? cells.length / 2 - w / 2 : ox * scale;
|
||||
const y = oy == null ? cells.length / 2 - h / 2 : oy * scale;
|
||||
|
||||
let excavation: Excavation = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
if (excavate) {
|
||||
let floorX = Math.floor(x);
|
||||
let floorY = Math.floor(y);
|
||||
let ceilW = Math.ceil(w + x - floorX);
|
||||
let ceilH = Math.ceil(h + y - floorY);
|
||||
// excavation = { x: floorX, y: floorY, w: ceilW, h: ceilH };
|
||||
excavation.x = floorX
|
||||
excavation.y = floorY
|
||||
excavation.w = ceilW
|
||||
excavation.h = ceilH
|
||||
}
|
||||
|
||||
return { x, y, h, w, excavation } as ImageSettingExcavation;
|
||||
}
|
||||
function getMarginSize(includeMargin : boolean, marginSize: number|null) : number {
|
||||
if (marginSize != null) {
|
||||
return Math.floor(marginSize);
|
||||
}
|
||||
return includeMargin ? SPEC_MARGIN_SIZE : DEFAULT_MARGIN_SIZE;
|
||||
}
|
||||
export class QRCodeCanvas {
|
||||
ctx : DrawableContext
|
||||
constructor(ctx : DrawableContext) {
|
||||
this.ctx = ctx
|
||||
}
|
||||
render(props : QRCodePropsTypes, cb : QRCodeCallback | null=null) {
|
||||
const ctx = this.ctx
|
||||
const value = props.value
|
||||
const size = props.size ?? DEFAULT_SIZE
|
||||
const level = props.level ?? DEFAULT_LEVEL
|
||||
const fgColor = props.fgColor ?? DEFAULT_FGCOLOR
|
||||
const includeMargin = props.includeMargin || DEFAULT_INCLUDEMARGIN
|
||||
const marginSize = props.marginSize
|
||||
const imageSettings = props.imageSettings
|
||||
if (value == null || value == '') {
|
||||
return;
|
||||
}
|
||||
let cells = QrCode.encodeText(value, ERROR_LEVEL_MAP[level] as Ecc).getModules();
|
||||
const margin = getMarginSize(includeMargin, marginSize);
|
||||
const numCells = cells.length + margin * 2;
|
||||
const scale = (size / numCells);
|
||||
const calculatedImageSettings = getImageSettings(
|
||||
cells,
|
||||
size,
|
||||
margin,
|
||||
imageSettings
|
||||
)
|
||||
const haveImageToRender = calculatedImageSettings != null
|
||||
const excavation: Excavation | null = haveImageToRender ? calculatedImageSettings?.excavation : null
|
||||
|
||||
if(haveImageToRender && excavation != null) {
|
||||
cells = excavateModules(cells, excavation);
|
||||
}
|
||||
ctx.reset()
|
||||
// ctx.clearRect(0, 0, size, size)
|
||||
// ctx.fillStyle = bgColor;
|
||||
// ctx.fillRect(0, 0, numCells, numCells);
|
||||
ctx.fillStyle = fgColor;
|
||||
cells.forEach(function (row, rdx) {
|
||||
row.forEach(function (cell, cdx) {
|
||||
if (cell) {
|
||||
ctx.fillRect((cdx + margin) * scale, (rdx + margin) * scale, scale, scale);
|
||||
}
|
||||
});
|
||||
});
|
||||
ctx.update()
|
||||
if(cb != null){
|
||||
cb(cells)
|
||||
}
|
||||
}
|
||||
}
|
||||
300
uni_modules/lime-qrcode/components/l-qrcode/l-qrcode.uvue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<!-- #ifndef APP || WEB -->
|
||||
<canvas :style="styles" v-if="use2d" type="2d" :canvas-id="canvasId" :id="canvasId"></canvas>
|
||||
<canvas :style="styles" v-else :canvas-id="canvasId" :id="canvasId"></canvas>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP -->
|
||||
<view class="l-qrcode" ref="drawableRef" :style="[styles]">
|
||||
<image class="l-qrcode__icon" v-if="icon" :src="icon" :style="[iconStyle]"></image>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef WEB -->
|
||||
<view class="l-qrcode" ref="drawableRef" :style="[styles]"></view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
<script lang="uts" setup>
|
||||
/**
|
||||
* QRCode 二维码组件
|
||||
* @description 用于生成二维码图形,支持自定义图标和样式配置
|
||||
* <br>插件类型:LQrcodeComponentPublicInstance
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-qrcode
|
||||
*
|
||||
* @property {string} value 二维码内容(支持文本/URL等)
|
||||
* @property {string} icon 中心图标路径(支持本地/网络路径)
|
||||
* @property {number | string} size 二维码尺寸
|
||||
* @property {number | string} iconSize 中心图标尺寸
|
||||
* @property {number} marginSize 二维码外边距(默认:0)
|
||||
* @property {string} color 二维码颜色(默认:#000000)
|
||||
* @property {string} bgColor 背景颜色(默认:#ffffff)
|
||||
* @property {boolean} bordered 显示边框(默认:false)
|
||||
* @property {'L' | 'M' | 'Q' | 'H'} errorLevel 容错等级(默认:'H')
|
||||
* @value L 可恢复7%的数据
|
||||
* @value M 可恢复15%的数据
|
||||
* @value Q 可恢复25%的数据
|
||||
* @value H 可恢复30%的数据
|
||||
* @property {boolean} useCanvasToTempFilePath 使用canvas生成临时路径(H5端可能需要)
|
||||
* @property {boolean} use2d 启用2D上下文渲染(性能优化,默认:false)
|
||||
*/
|
||||
import { type PropType, nextTick } from 'vue'
|
||||
// #ifndef APP
|
||||
import { createImage } from '@/uni_modules/lime-shared/createImage'
|
||||
import { getCanvas, isCanvas2d } from './useCanvas'
|
||||
import { QRCodeCanvas } from './qrcode.js';
|
||||
import { QRCodePropsTypes , ImageSettings } from './type'
|
||||
// #endif
|
||||
// #ifdef APP-ANDROID || APP-HARMONY
|
||||
import { QRCodeCanvas, type QRCodePropsTypes , type ImageSettings } from '@/uni_modules/lime-qrcodegen'
|
||||
// #endif
|
||||
// #ifdef APP-IOS
|
||||
import { QRCodeCanvas, type QRCodePropsTypes , type ImageSettings } from './ios'
|
||||
// #endif
|
||||
// import { addUnit } from '@/uni_modules/lime-shared/addUnit'
|
||||
// import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
|
||||
// import { toBoolean } from '@/uni_modules/lime-shared/toBoolean'
|
||||
import { addUnit, unitConvert } from './utils'
|
||||
import { LQrcodeFailCallback, LQrcodeCompleteCallback, LQrcodeSuccessCallback} from './type'
|
||||
|
||||
const name = 'l-qrcode'
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String
|
||||
},
|
||||
icon: {
|
||||
type: String
|
||||
},
|
||||
// #ifdef APP-ANDROID
|
||||
size: {
|
||||
type: Object,
|
||||
default: 160
|
||||
},
|
||||
iconSize: {
|
||||
type: Object,
|
||||
default: 40
|
||||
},
|
||||
// #endif
|
||||
// #ifndef APP-ANDROID
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 160
|
||||
},
|
||||
iconSize: {
|
||||
type: [Number, String],
|
||||
default: 40
|
||||
},
|
||||
// #endif
|
||||
marginSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#000'
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
errorLevel: {
|
||||
type: String as PropType<'L' | 'M' | 'Q' | 'H'>,
|
||||
default: 'M' // 'L' | 'M' | 'Q' | 'H'
|
||||
},
|
||||
useCanvasToTempFilePath: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
use2d: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
// status: {
|
||||
// type: String as PropType<'active'|'expired'|'loading'>,
|
||||
// default: 'active' // active | expired | loading
|
||||
// }
|
||||
})
|
||||
const emits = defineEmits(['success'])
|
||||
const context = getCurrentInstance();
|
||||
const canvasId = `l-qrcode${context!.uid}`
|
||||
const styles = computed<Map<string, any>>(():Map<string, any>=>{
|
||||
const style = new Map<string, any>()
|
||||
const size = addUnit(props.size);
|
||||
if(size!=null){
|
||||
style.set('width', size)
|
||||
style.set('height', size)
|
||||
}
|
||||
style.set('background', props.bgColor)
|
||||
return style
|
||||
})
|
||||
// #ifdef APP
|
||||
const iconStyle = computed<Map<string, any>>(():Map<string, any>=>{
|
||||
const style = new Map<string, any>()
|
||||
const size = addUnit(props.iconSize);
|
||||
// if(size!=null){
|
||||
style.set('width', size)
|
||||
style.set('height', size)
|
||||
// }
|
||||
return style
|
||||
})
|
||||
// #endif
|
||||
const drawableRef = ref<UniElement|null>(null);
|
||||
// #ifndef APP
|
||||
let canvas:HTMLCanvasElement|null = null
|
||||
// #endif
|
||||
let qrcode:QRCodeCanvas|null = null
|
||||
|
||||
const canvasToTempFilePath = (options: UTSJSONObject)=>{
|
||||
const format = options.getString('format') ?? 'png';
|
||||
const fail = options.get('fail') as LQrcodeFailCallback | null;
|
||||
const complete = options.get('complete') as LQrcodeCompleteCallback | null;
|
||||
const success = options.get('success') as LQrcodeSuccessCallback | null;
|
||||
// #ifdef APP
|
||||
const newOptions = {
|
||||
format,
|
||||
fail,
|
||||
complete,
|
||||
success,
|
||||
} as TakeSnapshotOptions
|
||||
drawableRef.value!.takeSnapshot(newOptions)
|
||||
// #endif
|
||||
// #ifdef WEB
|
||||
success?.({
|
||||
tempFilePath: canvas?.toDataURL('image/'+format)
|
||||
})
|
||||
// #endif
|
||||
|
||||
}
|
||||
const render = ()=>{
|
||||
const param:QRCodePropsTypes = {
|
||||
value: props.value,
|
||||
size: unitConvert(props.size),
|
||||
fgColor: props.color,
|
||||
level: ['L', 'M', 'Q', 'H'].includes(props.errorLevel) ? props.errorLevel : 'M',
|
||||
marginSize: props.marginSize,
|
||||
includeMargin: props.bordered,
|
||||
imageSettings: null,
|
||||
} as QRCodePropsTypes
|
||||
// #ifdef APP-HARMONY
|
||||
param.value = props.value
|
||||
param.size = unitConvert(props.size)
|
||||
param.fgColor = props.color
|
||||
param.level = ['L', 'M', 'Q', 'H'].includes(props.errorLevel) ? props.errorLevel : 'M'
|
||||
param.marginSize = props.marginSize
|
||||
param.includeMargin = props.bordered
|
||||
param.imageSettings = null
|
||||
// #endif
|
||||
if(props.icon != null){
|
||||
// if(toBoolean(props.iconSize) && toBoolean(props.icon)){
|
||||
const size = unitConvert(props.iconSize)
|
||||
param.imageSettings = {
|
||||
src: props.icon,
|
||||
width: size,
|
||||
height: size,
|
||||
excavate: true
|
||||
} as ImageSettings
|
||||
|
||||
// #ifdef APP-HARMONY
|
||||
param.imageSettings.src = props.icon
|
||||
param.imageSettings.width = size
|
||||
param.imageSettings.height = size
|
||||
param.imageSettings.excavate = true
|
||||
// #endif
|
||||
}
|
||||
qrcode?.render(param)
|
||||
if(props.useCanvasToTempFilePath){
|
||||
setTimeout(()=>{
|
||||
canvasToTempFilePath({
|
||||
success: (res: TakeSnapshotSuccess)=>{
|
||||
emits('success', res.tempFilePath)
|
||||
}
|
||||
})
|
||||
},100)
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
canvasToTempFilePath
|
||||
})
|
||||
onMounted(()=>{
|
||||
nextTick(()=>{
|
||||
// #ifdef APP
|
||||
requestAnimationFrame(()=> {
|
||||
drawableRef.value?.getBoundingClientRectAsync()?.then(res => {
|
||||
const ctx = drawableRef.value!.getDrawableContext();
|
||||
qrcode = new QRCodeCanvas(ctx!)
|
||||
watchEffect(()=>{
|
||||
render()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// #endif
|
||||
// #ifdef WEB
|
||||
canvas = document.createElement('canvas')
|
||||
canvas!.style.width = '100%'
|
||||
canvas!.style.height = '100%'
|
||||
drawableRef.value!.appendChild(canvas!)
|
||||
qrcode = new QRCodeCanvas(canvas, {
|
||||
pixelRatio: uni.getSystemInfoSync().pixelRatio,
|
||||
createImage: () => {
|
||||
const image = new Image();
|
||||
// @ts-ignore
|
||||
image.crossOrigin = 'anonymous';
|
||||
return image;
|
||||
}
|
||||
})
|
||||
watchEffect(()=>{
|
||||
render()
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP || WEB
|
||||
getCanvas(canvasId, { context }).then(res => {
|
||||
canvas = res;
|
||||
qrcode = new QRCodeCanvas(res, {
|
||||
path2D: false,
|
||||
pixelRatio: isCanvas2d && props.use2d ? uni.getSystemInfoSync().pixelRatio : 1,
|
||||
createImage
|
||||
})
|
||||
watchEffect(()=>{
|
||||
render()
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
// #ifdef WEB
|
||||
canvas?.remove();
|
||||
// #endif
|
||||
qrcode = null;
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.l-qrcode {
|
||||
position: relative;
|
||||
background-color: aqua;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&-mask {
|
||||
position: absolute;
|
||||
// inset: 0;
|
||||
// inset-block-start: 0;
|
||||
// inset-inline-start: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
line-height: 1.5714285714285714;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
249
uni_modules/lime-qrcode/components/l-qrcode/l-qrcode.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<view class="l-qrcode" :style="[styles]">
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<canvas :style="styles" v-if="use2d" type="2d" :canvas-id="canvasId" :id="canvasId"></canvas>
|
||||
<canvas :style="styles" v-else :canvas-id="canvasId" :id="canvasId"></canvas>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<web-view
|
||||
ref="qrcodeRef"
|
||||
@pagefinish="onFinished"
|
||||
@error="onError"
|
||||
@onPostMessage="onMessage"
|
||||
:style="styles" src="/uni_modules/lime-qrcode/hybrid/html/index.html?v=1"></web-view>
|
||||
<!-- #endif -->
|
||||
<!-- <view class="l-qrcode-mask" v-if="['loading', 'expired'].includes(props.status)">
|
||||
<l-loading v-if="props.status == 'loading'"></l-loading>
|
||||
<view class="l-qrcode-expired" v-if="props.status == 'expired'">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* QRCode 二维码组件
|
||||
* @description 用于生成二维码图形,支持自定义图标和样式配置
|
||||
* <br>插件类型:LQrcodeComponentPublicInstance
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-qrcode
|
||||
*
|
||||
* @property {string} value 二维码内容(支持文本/URL等)
|
||||
* @property {string} icon 中心图标路径(支持本地/网络路径)
|
||||
* @property {number | string} size 二维码尺寸
|
||||
* @property {number | string} iconSize 中心图标尺寸
|
||||
* @property {number} marginSize 二维码外边距(默认:0)
|
||||
* @property {string} color 二维码颜色(默认:#000000)
|
||||
* @property {string} bgColor 背景颜色(默认:#ffffff)
|
||||
* @property {boolean} bordered 显示边框(默认:false)
|
||||
* @property {'L' | 'M' | 'Q' | 'H'} errorLevel 容错等级(默认:'H')
|
||||
* @value L 可恢复7%的数据
|
||||
* @value M 可恢复15%的数据
|
||||
* @value Q 可恢复25%的数据
|
||||
* @value H 可恢复30%的数据
|
||||
* @property {boolean} useCanvasToTempFilePath 使用canvas生成临时路径(H5端可能需要)
|
||||
* @property {boolean} use2d 启用2D上下文渲染(性能优化,默认:false)
|
||||
*/
|
||||
import { computed, defineComponent, getCurrentInstance, watch, onUnmounted, onMounted } from '@/uni_modules/lime-shared/vue';
|
||||
import QRCodeProps from './props'
|
||||
// #ifndef APP-NVUE
|
||||
import { getCanvas, isCanvas2d } from './useCanvas'
|
||||
import { QRCodeCanvas } from './qrcode.js';
|
||||
// #endif
|
||||
import { addUnit } from '@/uni_modules/lime-shared/addUnit'
|
||||
import { createImage } from '@/uni_modules/lime-shared/createImage'
|
||||
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
|
||||
import { isBase64 } from '@/uni_modules/lime-shared/isBase64'
|
||||
import { pathToBase64 } from '@/uni_modules/lime-shared/pathToBase64'
|
||||
import { debounce } from '@/uni_modules/lime-shared/debounce'
|
||||
const name = 'l-qrcode'
|
||||
export default defineComponent({
|
||||
name,
|
||||
props: QRCodeProps,
|
||||
emits: ['success'],
|
||||
setup(props, {emit}) {
|
||||
const context = getCurrentInstance();
|
||||
const canvasId = `l-qrcode${context.uid}`
|
||||
const styles = computed(() => `width: ${addUnit(props.size)}; height: ${addUnit(props.size)};`)
|
||||
let qrcode = null
|
||||
let canvas = null
|
||||
const qrCodeProps = computed(() => {
|
||||
const { value, icon, size, color, bgColor, bordered, iconSize, errorLevel, marginSize } = props
|
||||
const imageSettings = {
|
||||
src: icon,
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
height: unitConvert(iconSize),
|
||||
width: unitConvert(iconSize),
|
||||
excavate: true,
|
||||
}
|
||||
return {
|
||||
value,
|
||||
size: unitConvert(size),
|
||||
level: errorLevel,
|
||||
bgColor,
|
||||
fgColor: color,
|
||||
imageSettings: icon ? imageSettings : undefined,
|
||||
includeMargin: bordered,
|
||||
marginSize: marginSize ?? 0
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
const stacks = new Map()
|
||||
// #endif
|
||||
const canvasToTempFilePath = debounce((args: UniNamespace.CanvasToTempFilePathRes) => {
|
||||
if(!canvas) return
|
||||
// #ifndef APP-NVUE
|
||||
const copyArgs = Object.assign({
|
||||
canvasId,
|
||||
canvas: null
|
||||
}, args)
|
||||
|
||||
if (isCanvas2d && props.use2d) {
|
||||
// copyArgs.canvas = canvas
|
||||
const tempFilePath = canvas.toDataURL();
|
||||
copyArgs.success({
|
||||
tempFilePath
|
||||
})
|
||||
return
|
||||
}
|
||||
if ('toTempFilePath' in canvas) {
|
||||
canvas.toTempFilePath(copyArgs)
|
||||
} else {
|
||||
uni.canvasToTempFilePath(copyArgs, context);
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
if(!stacks.size) {
|
||||
const flie = 'file-' + Math.random();
|
||||
const stack = {args, time: +new Date()}
|
||||
stacks.set(`${flie}`, stack)
|
||||
canvas.toDataURL(flie)
|
||||
setTimeout(() => {
|
||||
const stack = stacks.get(flie)
|
||||
if(stack && 'fail' in stack.args) {
|
||||
stack.args.fail({
|
||||
error: '超时'
|
||||
})
|
||||
stacks.delete(flie)
|
||||
}
|
||||
},5000)
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
const useCanvasToTempFilePath = () => {
|
||||
if(props.useCanvasToTempFilePath) {
|
||||
canvasToTempFilePath({
|
||||
success(res: UniNamespace.CanvasToTempFilePathRes) {
|
||||
emit('success', res.tempFilePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// #ifdef APP-NVUE
|
||||
const onFinished = () => {
|
||||
const { pixelRatio } = uni.getSystemInfoSync()
|
||||
canvas = {
|
||||
toDataURL(flie: string) {
|
||||
const ref: any = context.refs['qrcodeRef'];
|
||||
if(ref) {
|
||||
ref?.evalJS(`toDataURL('${flie}')`)
|
||||
}
|
||||
}
|
||||
};
|
||||
qrcode = {
|
||||
async render(props: any) {
|
||||
const ref: any = context.refs['qrcodeRef'];
|
||||
const { src } = props.imageSettings || { };
|
||||
if(!ref) return
|
||||
if(src && !isBase64(src) && !/^http/.test(src) && /^\/static/.test(src)) {
|
||||
props.imageSettings.src = await pathToBase64(src)
|
||||
}
|
||||
const _props = JSON.stringify(Object.assign({}, props, {pixelRatio}));
|
||||
ref?.evalJS(`render(${_props})`);
|
||||
}
|
||||
}
|
||||
qrcode.render(qrCodeProps.value)
|
||||
useCanvasToTempFilePath()
|
||||
}
|
||||
const onError = () => {
|
||||
console.warn('lime-qrcode 加载失败')
|
||||
}
|
||||
const onMessage = (e: any) => {
|
||||
const {detail:{data: [res]}} = e
|
||||
if(res.event == 'toDataURL') {
|
||||
const {file, image, msg} = res.data;
|
||||
const stack = stacks.get(file)
|
||||
if(stack && image && 'success' in stack.args) {
|
||||
stack.args.success({tempFilePath: image})
|
||||
stacks.delete(file)
|
||||
} else if(stack && 'fails' in stack.args) {
|
||||
stack.args.fail({error: msg})
|
||||
stacks.delete(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
const propsWatch = watch(props, () => {
|
||||
if (qrcode) {
|
||||
qrcode.render(qrCodeProps.value)
|
||||
useCanvasToTempFilePath()
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
// #ifndef APP-NVUE
|
||||
getCanvas(canvasId, { context }).then(res => {
|
||||
canvas = res;
|
||||
qrcode = new QRCodeCanvas(res, {
|
||||
path2D: false,
|
||||
pixelRatio: isCanvas2d && props.use2d ? uni.getSystemInfoSync().pixelRatio : 1,
|
||||
createImage
|
||||
})
|
||||
qrcode.render(qrCodeProps.value)
|
||||
useCanvasToTempFilePath()
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
onUnmounted(() => {
|
||||
propsWatch && propsWatch()
|
||||
})
|
||||
return {
|
||||
canvasId,
|
||||
styles,
|
||||
props,
|
||||
canvasToTempFilePath,
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
onFinished,
|
||||
onError,
|
||||
onMessage
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.l-qrcode {
|
||||
position: relative;
|
||||
|
||||
&-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
// inset-block-start: 0;
|
||||
// inset-inline-start: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
line-height: 1.5714285714285714;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
40
uni_modules/lime-qrcode/components/l-qrcode/props.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// @ts-nocheck
|
||||
// import type { PropType } from './vue'
|
||||
export default {
|
||||
value: String,
|
||||
icon: String,
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 160
|
||||
},
|
||||
iconSize: {
|
||||
type: [Number, String],
|
||||
default: 40
|
||||
},
|
||||
marginSize: Number,
|
||||
color: {
|
||||
type: String,
|
||||
default: '#000'
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
errorLevel: {
|
||||
type: String as PropType<'L'|'M'|'Q'|'H'>,
|
||||
default: 'M' // 'L' | 'M' | 'Q' | 'H'
|
||||
},
|
||||
useCanvasToTempFilePath: Boolean,
|
||||
use2d: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
// status: {
|
||||
// type: String as PropType<'active'|'expired'|'loading'>,
|
||||
// default: 'active' // active | expired | loading
|
||||
// }
|
||||
}
|
||||
6
uni_modules/lime-qrcode/components/l-qrcode/qrcode.js
Normal file
48
uni_modules/lime-qrcode/components/l-qrcode/type.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
// @ts-nocheck
|
||||
export type ImageSettings = {
|
||||
width: number
|
||||
height: number
|
||||
x?: number
|
||||
y?: number
|
||||
excavate: boolean
|
||||
}
|
||||
export type QRCodePropsTypes = {
|
||||
value?: string
|
||||
size?: number
|
||||
fgColor?: string
|
||||
level?: string
|
||||
marginSize: number
|
||||
includeMargin: boolean
|
||||
imageSettings?: ImageSettings
|
||||
}
|
||||
|
||||
export type QRCodeCallback = (cells : boolean[][]) => void
|
||||
|
||||
export type Excavation = {
|
||||
x: number
|
||||
y: number
|
||||
h: number
|
||||
w: number
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 成功回调函数定义
|
||||
*/
|
||||
export type TakeSnapshotSuccessCallback = (res: TakeSnapshotSuccess) => void
|
||||
/**
|
||||
* 失败回调函数定义
|
||||
*/
|
||||
export type TakeSnapshotFailCallback = (res: TakeSnapshotFail) => void
|
||||
/**
|
||||
* 完成回调函数定义
|
||||
*/
|
||||
export type TakeSnapshotCompleteCallback = (res: any) => void
|
||||
|
||||
|
||||
export type LQrcodeFailCallback = TakeSnapshotFailCallback
|
||||
export type LQrcodeCompleteCallback = TakeSnapshotCompleteCallback
|
||||
export type LQrcodeSuccessCallback = TakeSnapshotSuccessCallback
|
||||
78
uni_modules/lime-qrcode/components/l-qrcode/useCanvas.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
// @ts-nocheck
|
||||
import type { ComponentInternalInstance } from '@/uni_modules/lime-shared/vue'
|
||||
import { getRect } from '@/uni_modules/lime-shared/getRect'
|
||||
import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d'
|
||||
export const isCanvas2d = canIUseCanvas2d()
|
||||
|
||||
export async function getCanvas(canvasId: string, options: {context: ComponentInternalInstance}) {
|
||||
let { context } = options
|
||||
// #ifdef MP || VUE2
|
||||
if (context.proxy) context = context.proxy
|
||||
// #endif
|
||||
return getRect('#' + canvasId, context, isCanvas2d).then(res => {
|
||||
if(res.node){
|
||||
return res.node
|
||||
} else {
|
||||
const ctx = uni.createCanvasContext(canvasId, context)
|
||||
return {
|
||||
getContext(type: string) {
|
||||
if(type == '2d') {
|
||||
return ctx
|
||||
}
|
||||
},
|
||||
width: res.width,
|
||||
height: res.height,
|
||||
}
|
||||
// #ifdef H5
|
||||
// canvas.value = context.proxy.$el.querySelector('#'+ canvasId)
|
||||
// #endif
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// #ifndef H5 || APP-NVUE
|
||||
class Image {
|
||||
currentSrc: string | null = null
|
||||
naturalHeight: number = 0
|
||||
naturalWidth: number = 0
|
||||
width: number = 0
|
||||
height: number = 0
|
||||
tagName: string = 'IMG'
|
||||
path: any = ''
|
||||
crossOrigin: any = ''
|
||||
referrerPolicy: any = ''
|
||||
onload: () => void
|
||||
onerror: () => void
|
||||
constructor() {}
|
||||
set src(src) {
|
||||
this.currentSrc = src
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
this.path = res.path
|
||||
this.naturalWidth = this.width = res.width
|
||||
this.naturalHeight = this.height = res.height
|
||||
this.onload()
|
||||
},
|
||||
fail: () => {
|
||||
this.onerror()
|
||||
}
|
||||
})
|
||||
}
|
||||
get src() {
|
||||
return this.currentSrc
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
export function createImage(canvas: WechatMiniprogram.Canvas) {
|
||||
if(canvas && canvas.createImage) {
|
||||
return canvas.createImage()
|
||||
} else if(typeof window != 'undefined' && window.Image) {
|
||||
return new window.Image()
|
||||
}
|
||||
// #ifndef H5 || APP-NVUE
|
||||
return new Image()
|
||||
// #endif
|
||||
}
|
||||