first commit

main
gukai 1 year ago
commit d929afa146

8
.gitignore vendored

@ -0,0 +1,8 @@
.project
.hbuilderx
.mini-ide
.idea
unpackage
.DS_Store
node_modules

@ -0,0 +1,183 @@
<script>
import {
getUpdateManager,
setHeaderHeight
} from "./utils/app";
import utils from "./utils/utils.js";
// #ifdef MP-ALIPAY
//
const pluginGoodsEdit = requirePlugin('theGoodsEdit')
// #endif
// #ifdef MP-ALIPAY
// sop
if (!my.canIUse('groupPurchaseoucherPlugin') && !my.isIDE) {
// my.ap && my.ap.updateAlipayClient && my.ap.updateAlipayClient(); //
}
// #endif
export default {
onLaunch(options) {
// #ifdef MP-ALIPAY
//
if (pluginGoodsEdit) {
pluginGoodsEdit.setPlaceOrderCallback((arg) => {
// console.log(arg, 'arg')
uni.redirectTo({
url: arg.outItemId ? '/subPackages/goods/goods/index?id=' + arg.outItemId :
'/pages/index/index', //
});
})
}
// #endif
this.initLog({
mch_id: 0
}) // 访
getUpdateManager();
setHeaderHeight();
},
onShow(options) {
},
onHide() {
// console.log("App Hide");
},
methods: {}
};
</script>
<style lang="scss">
/*每个页面公共css */
@import "@/uni_modules/uview-ui/index.scss";
@import "@/static/css/common.scss";
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
page {
display: flex;
flex-direction: column;
background: #f2f2f2;
min-height: 100%;
font-family: PingFang SC, Arial, 'Helvetica Neue', Helvetica, sans-serif;
}
.g-price {
display: flex;
align-items: center;
.fwb {
position: relative;
top: -2rpx;
font-weight: bold;
font-size: 32rpx;
line-height: 1;
}
}
.tabbar-page {
padding-bottom: 120rpx;
padding-bottom: calc(constant(safe-area-inset-bottom) + 120rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 120rpx);
box-sizing: border-box;
background: #f2f2f2;
min-height: 100%;
}
.m-form {
background: #fff;
}
.m-form-item {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
&.top {
padding-top: 24rpx;
align-items: start;
}
}
.m-form-label {
min-width: 150rpx;
font-size: 28rpx;
}
.m-form-rig {
display: flex;
align-items: center;
flex: 1;
overflow: hidden;
margin-left: 10rpx;
}
.m-form-inp {
flex: 1;
overflow: hidden;
::v-deep {
.u-input {
height: 80rpx;
}
.u-textarea {
padding: 0 !important;
}
}
}
.m-form-switch {
display: flex;
justify-content: center;
text-align: right;
}
.jcsb {
justify-content: flex-end;
}
// .m-form-textarea {
// margin: 10rpx 0;
// padding: 0;
// }
.clearfix::after {
clear: both;
content: " ";
display: block;
height: 0;
line-height: 0;
visibility: hidden;
font-size: 0;
}
//
.goodsPop {
.info {
.stock {
display: none;
}
}
}
.empty_no {
display: flex;
flex-direction: column;
align-items: center;
padding: 26rpx;
}
.line-1 {
display: -webkit-box !important;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical !important;
}
</style>

@ -0,0 +1,290 @@
## 支付宝交易组件
```
第一步 订购插件和关联小程序
在接入插件之前,确保本账号或者本账号所属的主账号已经订购了本插件,并且已经关联到需要使用本插件的小程序。点击这里查看详细步骤。
第二步 配置主体小程序项目
在小程序开发工具里面打开需要使用插件的项目然后配置app.json
{
"plugins": {
"tradePay": {
"version": "*",// 目前只支持设置 * 拉取当前上架最新版本
"provider": "2021003178648009"
}
}
}
注意请修改上述文件的“tradePay”名称以和使用插件的代码匹配。
uniappmanifest.json插件配置
"mp-alipay" : {
"usingComponents" : true,
"appid" : "2021003197674866",
"plugins": {
"tradePay": {
"version": "*", // 目前只支持设置 * ,自动选择版本
"provider": "2021003178648009" // 组件Id固定不变
},
}
},
uniapppages.jsonsubPackages/order/preview/indexstyle内增加
"usingComponents": {
"discount-card": "plugin://tradePay/discountCard"
}
```
## 商品详情组件插件
```
第一步 订购插件和关联小程序
在接入插件之前,确保本账号或者本账号所属的主账号已经订购了本插件,并且已经关联到需要使用本插件的小程序。点击这里查看详细步骤。
第二步 配置主体小程序项目
在小程序开发工具里面打开需要使用插件的项目然后配置app.json
{
"plugins": {
"theGoodsEdit": {
"version": "*",// 目前只支持设置 * 拉取当前上架最新版本
"provider": "2021003177653028"
}
}
}
注意请修改上述文件的“tradePay”名称以和使用插件的代码匹配。
uniappmanifest.json插件配置
"mp-alipay" : {
"usingComponents" : true,
"appid" : "2021003197674866",
"plugins": {
"theGoodsEdit": {
"lazy",
"genericsImplementation": {
"goodsDetail": {
"detail-images": "/components/detail-images/detail-images" // 商家可以通过修改 /components/detail-images/detail-images 路径,传入自定义组件
}
},
"version": "*", // 目前只支持设置 * ,自动选择版本
"provider": "2021003177653028" // 组件Id固定不变
},
}
},
```
## 本地生活商品插件集成sop
## [文档](https://www.yuque.com/wenheng-68qmd/dthzsu/cnfuc25naz42q61v?singleDoc#OaKw6)
```
第一步 订购插件和关联小程序
在接入插件之前,确保本账号或者本账号所属的主账号已经订购了本插件,并且已经关联到需要使用本插件的小程序。点击这里查看详细步骤。
第二步 配置主体小程序项目
在小程序开发工具里面打开需要使用插件的项目然后配置app.json
{
"plugins": {
"groupPurchaseoucherPlugin": {
"version": "*", // 目前只支持设置 * ,自动选择版本
"provider": "2021003190661263"
},
}
}
<!--
uniappmanifest.json插件配置
"mp-alipay" : {
"usingComponents" : true,
"appid" : "2021003197674866",
"plugins": {
"groupPurchaseoucherPlugin": {
"version": "*", // 目前只支持设置 * ,自动选择版本
"provider": "2021003190661263"
}
}
}, -->
```
## 商家会员卡
## [文档](https://opendocs.alipay.com/open/03sx7u?pathHash=1abe9241&ref=api)
```
第一步 订购插件和关联小程序
在接入插件之前,确保本账号或者本账号所属的主账号已经订购了本插件,并且已经关联到需要使用本插件的小程序。点击这里查看详细步骤。
第二步 配置主体小程序项目
在小程序开发工具里面打开需要使用插件的项目然后配置app.json
{
"plugins": {
"alipassToolKit": { // 请自定义插件名称
"version": "*", // 目前只支持设置 * 拉取当前上架最新版本
"provider": "2021001107697072" // 固定值,插件 ID
}
}
}
装修组件 pages\index\components\TemplateAliMembershipCard.vue
```
## 支付宝支付券(领券组件)插件
```
文档https://opendocs.alipay.com/open/08b369?pathHash=6f84a8c0
微信公众平台,小程序设置,增加支付券插件
{
"plugins": {
"couponPlugin": {
"version": "*", // 拉取当前上架最新版本
"provider": "2021002172680015" // 领券组件插件 ID固定值
}
}
}
注意请修改上述文件的“couponPlugin”名称以和使用插件的代码匹配。
uniappmanifest.json插件配置
"plugins": {
"couponPlugin": {
"version": "*", // 拉取当前上架最新版本
"provider": "2021002172680015" // 领券组件插件 ID固定值
}
}
uniapppages.jsonsubPackages/pay_coupon/infostyle内增加
"usingComponents": {
"get-coupon": "plugin://couponPlugin/get-coupon"
}
```
## 微信支付券插件 - 暂时废弃
```
文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_3_1.shtml
微信公众平台,小程序设置,增加支付券插件
{
"plugins": {
"sendCoupon": {
"version": "1.4.13",
"provider": "wxf3f436ba9bd4be7b"
}
}
}
注意请修改上述文件的“sendCoupon”名称以和使用插件的代码匹配。
uniappmanifest.json插件配置
"mp-weixin" : {
"plugins": {
"sendCoupon": {
"version": "1.5.0",
"provider": "wxf3f436ba9bd4be7b"
}
}
},
uniapppages.jsonsubPackages/pay_coupon/indexstyle内增加
"usingComponents": {
"send-coupon": "plugin://sendCoupon/send-coupon"
}
```
## 微信 H5 支付
## [参考文档 前端的微信支付JSSDK支付](https://juejin.cn/post/6844903954355060749#heading-11)
## [参考文档 JSAPI调起支付API](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml)
## [参考文档 JS-SDK说明文档](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#59)
// utils/wxConfig.js
// 异步获取配置数据
```JavaScript
async function getConfigData(url) {
// 接口获取签名
return {
app_id: "", // 必填,公众号的唯一标识
timestamp: "", // 必填,生成签名的时间戳
noncestr: "", // 必填,生成签名的随机串
signature: "", // 必填,签名
};
}
```
- subPackages/pay/index.vue
- 发起一个微信支付请求 `subPackages\pay\index.vue`
```JavaScript
wechatPayNow() // h5 调用
```
```JavaScript
//发送给朋友
onShareAppMessage(res) {
if (res.from === 'button') { // 来自页面内分享按钮
console.log(res.target)
}
return {
title: this.control.titleStyle.name,
path: `/pages/index/index?distributor_id=${this.getUser("id")}`,
summary: ""
}
},
```
· 首页 (分销ID) `/pages/index/index`
· 商品详情 (商品ID、分销ID) `/subPackages/goods/goods/index`
· 限时折扣 (折扣ID) `/subPackages/goods/goods/index`
· 满减优惠 (折扣ID) `/subPackages/goods/goods/index`
· 套餐列表 `/subPackages/goods/goods/packageList`
· 套餐详情 (套餐ID、mch_id) `/subPackages/goods/goods/package`
· 分类页面 `/pages/cat/cat`
· 附近门店 `/subPackages/stores/index/index`
· 门店详情 (门店ID) `/subPackages/stores/storesDetails/index`
· 门店列表 (点餐) `/subPackages/stores/restaurantList/index`
· 点餐页面 (门店ID) `/subPackages/takeaway/index`
· 文章列表 `/subPackages/article/index`
· 文章详情 `/subPackages/article/detail`
· 领券中心 `/subPackages/coupons/index/index`
· DIY页面 (页面ID) `/subPackages/diy/index`
· 预约中心 `/subPackages/reservation/index`
· 预约服务 `/subPackages/reservation/serve`
· 预约技师列表 (ID) `/subPackages/reservation/technician`
· 预约技师 (ID) `/subPackages/reservation/technicianDetali`

@ -0,0 +1,13 @@
const files = require.context("./modules", false, /\.js$/),
api = {};
files.keys().forEach((key) => {
const last = key.indexOf(".js"),
name = key.slice(2, last);
api[name] = files(key).default;
});
export default {
...api,
};

@ -0,0 +1,181 @@
import {
request,
uploadFile
} from "@/utils/request";
export default {
/**
* 获取图片库
*/
getImages(data) {
return request({
url: "/common/getImages",
method: "GET",
data,
needLogin: true,
});
},
/**
* 获取商城信息
*/
getStoreDetail(data) {
return request({
url: "/store_setting/store_detail",
method: "GET",
data,
needLogin: true,
});
},
/**
* 上传图片视频
*/
upload(data) {
return uploadFile(data);
},
orderCount(data) {
return request({
url: "/common/order-count",
method: "GET",
data,
needLogin: true,
});
},
/**
* 获取商品标签信息
*/
getLable(data) {
return request({
url: "/common/lable",
method: "GET",
data,
needLogin: true,
});
},
/**
* isPhone
*/
isPhone(data) {
return request({
url: "/user/is-phone",
method: "GET",
data,
needLogin: true,
});
},
/**
* 优惠券列表
*/
list(data) {
return request({
url: "/common/List",
method: "GET",
data,
needLogin: true,
});
},
/**
* 获取门店列表
* @param {String} longitude 经度
* @param {String} latitude 纬度
*/
getShopList(data) {
return request({
url: "/common/getShopList",
method: "POST",
data,
needLogin: true,
});
},
/**
* 折扣信息
*/
discountsItem(data) {
return request({
url: "/common/discountsItem",
method: "GET",
data,
needLogin: true,
});
},
/**
* 折扣信息
*/
packageItem(data) {
return request({
url: "/common/packageItem",
method: "GET",
data,
needLogin: true,
});
},
/**
* 折扣信息
*/
redutionItem(data) {
return request({
url: "/common/redutionItem",
method: "GET",
data,
needLogin: true,
});
},
/**
* wechatH5
*/
wechatH5(data) {
return request({
url: "/common/wechatH5",
method: "GET",
data,
needLogin: true,
});
},
aliH5(data) {
return request({
url: "/common/aliH5",
method: "GET",
data,
needLogin: true,
});
},
sendSms(data) {
return request({
url: "/common/sendSms",
method: "GET",
data,
needLogin: true,
});
},
getFlashMch(data) {
return request({
url: "/common/FlashListMch",
method: "GET",
data,
needLogin: true,
});
},
getFlashGoods(data) {
return request({
url: "/common/FlashListGoods",
method: "GET",
data,
needLogin: true,
});
},
};

@ -0,0 +1,39 @@
import { request } from "@/utils/request";
export default {
/**
* 优惠券列表
*/
list(data) {
return request({
url: "/Coupon/List",
method: "GET",
data,
needLogin: true,
});
},
/**
* 领取优惠券
*/
getCoupon(data) {
return request({
url: "/Coupon/GetCoupon",
method: "GET",
data,
needLogin: true,
});
},
/**
* 已领取优惠券
*/
getUserCoupon(data) {
return request({
url: "/Coupon/GetUserCoupon",
method: "GET",
data,
needLogin: true,
});
},
};

@ -0,0 +1,16 @@
import { request } from "@/utils/request";
export default {
/**
* 获取diy模板
*/
showTemplate(data) {
return request({
url: "/common/diy-show",
method: "GET",
data,
needLogin: true,
});
},
};

@ -0,0 +1,63 @@
import { request } from "@/utils/request";
export default {
/**
* 获取轮播图设置
*/
getStoreSlideShowSetting(data) {
return request({
url: "/storeSetting/getStoreSlideShowSetting",
method: "GET",
data,
needLogin: true,
});
},
/**
* 获取分类导航设置
*/
getStoreNavSetting(data) {
return request({
url: "/storeSetting/GetStoreNavSetting",
method: "GET",
data,
needLogin: true,
});
},
/**
* 获取分类导航设置
*/
getCateGoods(data) {
return request({
url: "/storeSetting/GetCateGoods",
method: "GET",
data,
needLogin: true,
});
},
/**
* 获取门店分类&商品
*/
GetShopGoodsList(data) {
return request({
url: "/storeSetting/GetShopGoodsList",
method: "GET",
data,
needLogin: true,
});
},
/**
* 获取首页导航设置
*/
goodsClassify(data) {
return request({
url: "/goods/classify",
method: "GET",
data,
needLogin: true,
});
},
};

@ -0,0 +1,34 @@
import { request } from "@/utils/request";
export default {
/**
* 当面付页面
*/
storeInfo(data) {
return request({
url: "/order/PaymentInfo",
method: "POST",
data,
});
},
/**
* 当面付页面
*/
inpersonPaymentOrder(data) {
return request({
url: "/order/InpersonPaymentOrder",
method: "POST",
data,
needLogin: true,
});
},
storeInfos(data) {
return request({
url: "/common/PaymentInfo",
method: "POST",
data,
});
},
};

@ -0,0 +1,33 @@
import {
request
} from "@/utils/request";
export default {
/**
* 收银台页面
* @param {String} order_no
*/
CheckStand(data) {
return request({
url: "/PayOrder/CheckStand",
method: "POST",
data,
needLogin: true,
});
},
/**
* 发起支付
* @param {String} order_no
* @param {String} pay_type // 1商城订单 2秒杀订单 3拼团订单
* @param {String} order_type 1支付宝 2 微信 3余额 4货到付款
*/
OrderPayment(data) {
return request({
url: "/PayOrder/OrderPayment",
method: "POST",
data,
needLogin: true,
});
},
};

@ -0,0 +1,56 @@
import { request } from "@/utils/request";
export default {
/**
* 获取列表
* @param {String} shop_id
*/
getData(data) {
return request({
url: "/user/getPayCoupon",
method: "POST",
data,
needLogin: true,
});
},
sendCoupon(data) {
return request({
url: "/user/sendCoupon",
method: "POST",
data,
needLogin: true,
});
},
getCat(data) {
return request({
url: "/common/payCouponCatList",
method: "POST",
data,
needLogin: true,
});
},
payCouponList(data) {
return request({
url: "/common/payCouponList",
method: "POST",
data,
needLogin: true,
});
},
getCoupon(data) {
return request({
url: "/common/getPayCoupon",
method: "POST",
data,
needLogin: true,
});
},
getBanner(data) {
return request({
url: "/common/getPayCouponBanner",
method: "POST",
data,
needLogin: true,
});
},
};

@ -0,0 +1,240 @@
import {
request
} from "@/utils/request";
export default {
classify(data) {
return request({
url: "/book/client/classify/index",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
goodsList(data) {
return request({
url: "/book/client/goods/index",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
goodsShow(data) {
return request({
url: "/book/client/goods/show",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
staffList(data) {
return request({
url: "/book/client/common/index",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
staffShow(data) {
return request({
url: "/book/client/common/show",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
staffEvaluateList(data) {
return request({
url: "/book/client/common/evaluateList",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
getBookOrder(data) {
return request({
url: "/book/client/orders/getBookOrder",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
appointment_time(data) {
return request({
url: "/book/client/common/appointment_time",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
PlaceOrder(data) {
return request({
url: "/book/client/orders/PlaceOrder",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
appointmenTime(data) {
return request({
url: "/book/client/common/appointment_time",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
userOrderList(data) {
return request({
url: "/book/client/orders/orderList",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
OrderEvaluate(data) {
return request({
url: "/book/client/orders/OrderEvaluate",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
orderCancel(data) {
return request({
url: "/book/client/orders/orderCancel",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
orderDetail(data) {
return request({
url: "/book/client/orders/orderDetail",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
orderDetails(data) {
return request({
url: "/book/client/orders/orderDetails",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
isStaff(data) {
return request({
url: "/book/client/staff/isStaff",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
StaffOrderList(data) {
return request({
url: "/book/client/orders/StaffOrderList",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
staffCenter(data) {
return request({
url: "/book/client/staff/staffCenter",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
takeOrder(data) {
return request({
url: "/book/client/orders/takeOrder",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
startOrder(data) {
return request({
url: "/book/client/orders/startOrder",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
completeOrder(data) {
return request({
url: "/book/client/orders/completeOrder",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
orderEvaluateList(data) {
return request({
url: "/book/client/orders/orderEvaluateList",
method: "POST",
data,
needLogin: true,
noclient: true
});
},
goodsEvaluate(data) {
return request({
url: "/book/client/goods/evaluate",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
showBanner(data) {
return request({
url: "/book/client/index/show",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
broker(data) {
return request({
url: "/book/client/staff/broker",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
GetSkuList(data) {
return request({
url: "/book/client/orders/GetSkuList",
method: "GET",
data,
needLogin: true,
noclient: true
});
},
};

@ -0,0 +1,25 @@
import { request } from "@/utils/request";
export default {
/**
* 获取当前积分
*/
getIntegral() {
return request({
url: "/user/integral",
method: "GET",
needLogin: true,
});
},
/**
* 获取当前余额
*/
getBalance() {
return request({
url: "/user/balance",
method: "GET",
needLogin: true,
});
},
};

@ -0,0 +1,178 @@
import {
request
} from "@/utils/request";
export default {
// 登录
login(data) {
return request({
url: "/auth/login",
method: "POST",
data,
});
},
// 手机号注册
regPhone(data) {
return request({
url: "/auth/regPhone",
method: "POST",
data,
});
},
loginPhone(data) {
return request({
url: "/auth/loginPhone",
method: "POST",
data,
});
},
// 测试登录
testLogin(data) {
return request({
url: "/auth/testLogin",
method: "POST",
data,
});
},
// 置换token
resetToken(data) {
return request({
url: "/auth/resetToken",
method: "POST",
data,
});
},
userInfo(data = {}) {
return request({
url: "/auth/user",
method: "POST",
data,
needLogin: true,
});
},
userMobile(data = {}) {
return request({
url: "/user/userMobile",
method: "POST",
data,
needLogin: true,
});
},
setMobile(data = {}) {
return request({
url: "/user/setMobile",
method: "POST",
data,
needLogin: true,
});
},
setUserInfo(data = {}) {
return request({
url: "/user/SetUserInfo",
method: "POST",
data,
needLogin: true,
});
},
UpdateUser(data = {}) {
return request({
url: "/user/UpdateUser",
method: "POST",
data,
needLogin: true,
});
},
send(data = {}) {
return request({
url: "/user/send",
method: "POST",
data,
needLogin: true,
});
},
bind_merge(data = {}) {
return request({
url: "/user/bind_merge",
method: "POST",
data,
needLogin: true,
});
},
LevelInfo(data = {}) {
return request({
url: "/User/LevelInfo",
method: "GET",
data,
needLogin: true,
});
},
GetLevelList(data = {}) {
return request({
url: "/User/GetLevelList",
method: "GET",
data,
needLogin: true,
});
},
fix(data = {}) {
return request({
url: "/User/fix",
method: "GET",
data,
needLogin: true,
});
},
/**
* 访问记录
*/
log(data) {
return request({
url: "/user/log",
method: "GET",
data,
needLogin: true,
});
},
isLogin(data = {}) {
return request({
url: "/auth/isLogin",
method: "GET",
data,
needLogin: true,
});
},
taimiInfo(data = {}) {
return request({
url: "/user/taimiInfo",
method: "GET",
data,
needLogin: true,
});
},
taimiSave(data = {}) {
return request({
url: "/user/taimiSave",
method: "POST",
data,
needLogin: true,
});
},
taimiTongbu(data = {}) {
return request({
url: "/user/taimiTongbu",
method: "POST",
data,
needLogin: true,
});
},
createVip(data = {}) {
return request({
url: "/user/createVip",
method: "POST",
data,
needLogin: true,
});
}
};

@ -0,0 +1,263 @@
<template>
<view class="content page-box">
<view class="login">
<image class="logo" :src="storeDetail.logo_url"></image>
<view class="name"> {{ storeDetail.name }} </view>
<view style="padding: 30rpx; width: 90%">
<!-- #ifdef MP-WEIXIN -->
<u-button v-if="storeDetail.display_setting && storeDetail.display_setting.wx_login_phone == 1"
open-type="getPhoneNumber" shape="circle" type="success" size="large" text="登录/注册"
@getphonenumber="getPhoneNumber"></u-button>
<u-button v-else shape="circle" type="success" size="large" text="登录/注册" @click="login"></u-button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<u-button shape="circle" color="#2979ff" size="large" type="success" icon="zhifubao" text="登录/注册"
@click="login()"></u-button>
<!-- #endif -->
<!-- H5微信/支付宝快捷登陆 -->
<!-- #ifdef H5 -->
<view></view>
<u-button v-if="$utils.isInWeChatBrowser()" @click="login" shape="circle" type="success"
text="微信快捷登录"></u-button>
<u-button v-if="$utils.isInAliBrowser()" color="#2979ff" @click="login" shape="circle" type="success"
text="支付宝快捷登录"></u-button>
<!-- #endif -->
<!-- 账号密码登陆 -->
<!-- #ifndef MP -->
<!-- 非小程序环境需要排除微信和支付宝环境 -->
<!-- 仅有浏览器和app才会展示账号密码登陆 -->
<template v-if="!$utils.isInWeChatBrowser() && !$utils.isInAliBrowser()">
<view style="background: #FFF;width: 100%;padding: 10rpx;border-radius: 10px;">
<u--form :model="form" ref="uForm" :rules="rules" labelWidth="120rpx">
<u-form-item label="账号:" prop="name">
<u--input v-model="form.name" border="bottom" placeholder="请输入账号"></u--input>
</u-form-item>
<u-form-item label="密码:" prop="pwd">
<u--input v-model="form.pwd" :password="!is_eye" border="bottom" placeholder="请输入密码">
<!-- <template slot="suffix">
<u-icon size="36rpx" name="eye" v-if="!is_eye" @click="is_eye = 1"></u-icon>
<u-icon size="36rpx" name="eye-off" @click="is_eye = 0" v-else></u-icon>
</template> -->
</u--input>
<u-icon size="36rpx" name="eye" v-if="!is_eye" @click="is_eye = 1"></u-icon>
<u-icon size="36rpx" name="eye-off" @click="is_eye = 0" v-else></u-icon>
</u-form-item>
</u--form>
</view>
<view style="display: flex;align-items: center;justify-content: space-between;margin-top: 10rpx;">
<view>
<u--text @click="$utils.toUrl('/subPackages/login/reg/reset', 'redirectTo')" type="warning"
text="忘记密码?"></u--text>
</view>
<view>
<u--text @click="$utils.toUrl('/subPackages/login/reg/index', 'redirectTo')" type="primary"
text="注册"></u--text>
</view>
</view>
<view style="margin-top: 20px;">
<u-button shape="circle" type="success" color="#fa3534" size="large" text="登录"
@click="pwdSubmit"></u-button>
</view>
</template>
<!-- #endif -->
</view>
</view>
</view>
</template>
<script>
export default {
components: {
},
data() {
return {
userToEditPop: true,
user_infoTemporary: {
avatarUrl: "",
nickname: "",
},
data: {
user_info: null,
},
showAliempower: false,
is_eye: 0,
form: {
name: '',
pwd: ''
},
rules: {
name: [{
required: true,
message: '请输入账号',
trigger: ['blur', 'change']
}],
pwd: [{
required: true,
message: '请输入密码',
trigger: ['blur', 'change']
}]
}
}
},
computed: {
storeDetail() {
return this.$store.state.$GstoreDetail || {}
}
},
methods: {
pwdSubmit() {
let that = this
this.$refs.uForm.validate().then(async (res) => {
uni.showLoading({
title: '提交中'
})
const res1 = await this.$api.user.loginPhone(this.form);
uni.hideLoading()
if (res1.code > 0) {
uni.showToast({
icon: 'none',
title: res1.msg
})
} else {
uni.showToast({
icon: 'none',
title: '登录成功!'
})
let {
token,
user_info,
store_id
} = res1.data;
uni.setStorageSync("token_" + store_id, token);
uni.setStorageSync("user_info", user_info);
setTimeout(() => {
const backPage = uni.getStorageSync("backPage")
// console.log(backPage, 'backPage')
if (backPage) {
if (backPage.includes('login/login')) {
// console.log(11)
that.$utils.toUrl("/pages/index/index", "redirectTo");
} else {
// console.log(222)
that.$utils.toUrl(backPage, 'redirectTo');
}
} else {
that.$utils.toUrl("/pages/index/index", "redirectTo");
}
}, 2000)
}
}).catch(errors => {})
},
async getPhoneNumber(e) {
let detail = e.detail
// console.log(detail, 'detail')
if (detail.errMsg == 'getPhoneNumber:ok') {
let code = detail.code
this.login({
phone_code: code
})
} else {
uni.showToast({
icon: 'none',
title: detail.encryptedData
})
}
},
login({
phone_code
} = {}) {
// console.log(phone_code, 'phone_code')
this.$emit('goLogin', true, {
phone_code: phone_code
})
},
// // H5
// async weChatLogin() {
// // APIH5
// const res = await this.$api.common.wechatH5();
// // appid
// const appid = res.data.appid;
// //
// let redirect_uri = window.location.href;
// redirect_uri = encodeURIComponent(redirect_uri);
// // appid
// const wxAuthUrl =
// `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`;
// //
// window.location.href = wxAuthUrl;
// }
}
}
</script>
<style scoped lang="scss">
.content {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
background: #FFF;
.login {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
}
.logo {
height: 200rpx;
width: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
border-radius: 40rpx;
box-shadow: 0 0.3125rem 1.25rem 0px rgba(166, 199, 251, 0.12);
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
.name {
color: #000;
font-weight: bold;
}
</style>

@ -0,0 +1,185 @@
<template>
<view>
<u-popup :show="show" mode="bottom" @close="close" :round="10" zIndex="999999">
<view class="popup-box" style="background: #FFF;">
<view class="title">
授权登录
<view class="iconClear">
<u-icon name="close" size="14" @click="close"></u-icon>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<view style="width: 100%;display: flex;justify-content: center;">
<u-icon color="#19be6b" name="fingerprint" size="40px"></u-icon>
</view>
<view class="logo-box">
<u-button v-if="storeDetail.display_setting && storeDetail.display_setting.wx_login_phone == 1"
type="success" text="登录/注册" open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"></u-button>
<u-button v-else type="success" text="登录" @click="click"></u-button>
</view>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<view style="width: 100%;display: flex;justify-content: center;">
<u-icon color="#2979ff" name="zhifubao" size="40px"></u-icon>
</view>
<view class="logo-box">
<u-button type="primary" text="支付宝授权登录" @click="click"></u-button>
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<template v-if="$utils.isInWeChatBrowser()">
<view style="width: 100%;display: flex;justify-content: center;">
<u-icon color="#19be6b" name="fingerprint" size="40px"></u-icon>
</view>
<view class="logo-box">
<!-- <u-button @click="click" shape="circle" type="success" text="微信快捷登录"></u-button> -->
<u-button @click="$utils.toUrl('/subPackages/login/login/index')" shape="circle" type="success" text="微信快捷登录"></u-button>
</view>
</template>
<template v-else-if="$utils.isInAliBrowser()">
<view style="width: 100%;display: flex;justify-content: center;">
<u-icon color="#2979ff" name="zhifubao" size="40px"></u-icon>
</view>
<view class="logo-box">
<u-button @click="click" type="primary" text="支付宝授权登录"></u-button>
</view>
</template>
<template v-else>
<view class="logo-box">
<u-button @click="$utils.toUrl('/subPackages/login/login/index')" shape="circle" type="success"
text="去登陆"></u-button>
</view>
</template>
<!-- #endif -->
<!-- #ifdef APP -->
<template>
<view class="logo-box">
<u-button @click="$utils.toUrl('/subPackages/login/login/index')" shape="circle" type="success"
text="去登陆"></u-button>
</view>
</template>
<!-- #endif -->
</view>
</u-popup>
</view>
</template>
<script>
// #ifdef H5
// import ap from "@/uni_modules/alipayjsapi/alipayjsapi.js"
// #endif
export default {
data() {
return {
}
},
props: {
show: Boolean
},
computed: {
storeDetail() {
return this.$store.state.$GstoreDetail || {}
}
},
methods: {
close() {
this.$emit('loginShow', false)
},
click() {
this.$emit('empower', true)
},
// // H5
// async weChatLogin() {
// // APIH5
// const res = await this.$api.common.wechatH5();
// // appid
// const appid = res.data.appid;
// //
// let redirect_uri = window.location.href;
// redirect_uri = encodeURIComponent(redirect_uri);
// // appid
// const wxAuthUrl =
// `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`;
// //
// window.location.href = wxAuthUrl;
// },
// // h5
// async aliH5Login() {
// //
// ap.getAuthCode({
// appId: '2021004101685250',
// scopes: ['auth_user'],
// }, function(res) {
// ap.alert(JSON.stringify(res));
// if (res.authCode) {
// } else {
// uni.showToast({
// icon: 'none',
// title: res.errorDesc
// })
// }
// });
// },
async getPhoneNumber(e) {
let detail = e.detail
console.log(detail, 'detail')
if (detail.errMsg == 'getPhoneNumber:ok') {
let code = detail.code
this.login({
phone_code: code
})
} else {
uni.showToast({
icon: 'none',
title: detail.encryptedData
})
}
},
login({
phone_code
} = {}) {
// console.log(phone_code, 'phone_code')
this.$emit('goLogin', true, {
phone_code: phone_code
})
},
}
}
</script>
<style lang="scss" scoped>
.popup-box {
padding-bottom: 20px;
.title {
width: 100%;
height: 100rpx;
text-align: center;
position: relative;
line-height: 100rpx;
border-radius: 20rpx 20rpx 0 0;
font-weight: bold;
.iconClear {
position: absolute;
right: 20rpx;
top: 40rpx;
}
}
.logo-box {
padding: 30rpx 0;
text-align: center;
margin: 0 100rpx;
}
}
</style>

@ -0,0 +1,213 @@
<template>
<view>
<loginPage v-if="type == 1" @goLogin="goLogin" @backPage="backPage"></loginPage>
<loginPopup v-if="type == 2" :show='show' @loginShow="loginShow" @empower="empower" @goLogin="goLogin">
</loginPopup>
</view>
</template>
<script>
import loginPopup from './loginPopup.vue'
import loginPage from './loginPage.vue'
// #ifdef H5
import ap from "@/uni_modules/alipayjsapi/alipayjsapi.js"
// #endif
export default {
name: "login",
components: {
loginPopup,
loginPage
},
data() {
return {
title: "Hello",
disabled: false,
show: false
};
},
props: {
type: Number,
// show: Boolean,
wcCode: String,
},
created() {
if (this.wcCode) {
this.actionLogin(this.wcCode, '', 1);
}
//
if (this.type == 2) {
this.isLogin()
}
// #ifdef H5
// h5
if (this.type == 0) {
if (this.$utils.isInAliBrowser()) {
this.login()
}
}
// #endif
},
methods: {
async login({
phone_code
} = {}) {
this.disabled = true;
let that = this
// #ifdef MP-WEIXIN
uni.login({
provider: "weixin", //使
// onlyAuthorize: '',
success: (loginRes) => {
this.actionLogin(loginRes.code, phone_code);
},
fail: (res) => {
uni.$u.toast(res.errMsg);
this.disabled = false;
},
});
// #endif
// #ifdef MP-ALIPAY
my.getAuthCode({
scopes: 'auth_user',
success: (loginRes) => {
if (loginRes.authCode) {
this.actionLogin(loginRes.authCode);
} else {
// uni.$u.toast(loginRes.errMsg ? loginRes.errMsg : '');
}
},
fail: (res) => {
uni.$u.toast(res.errMsg);
this.disabled = false;
},
});
// #endif
//#ifdef H5
// h5
if (this.$utils.isInAliBrowser()) {
const res = await this.$api.common.aliH5();
// appid
const appid = res.data.appid;
ap.getAuthCode({
appId: appid,
scopes: ['auth_base'],
}, function(res) {
console.log(res, 'resresres')
if (res.authCode) {
that.actionLogin(res.authCode, '', 2);
} else {
uni.showToast({
icon: 'none',
title: res.errorDesc || res.errorMessage
})
}
});
return;
} else if (this.$utils.isInWeChatBrowser()) {
// h5
// APIH5
const res = await this.$api.common.wechatH5();
// appid
const appid = res.data.appid;
//
let redirect_uri = window.location.href;
redirect_uri = encodeURIComponent(redirect_uri);
// appid
const wxAuthUrl =
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`;
//
window.location.href = wxAuthUrl;
return;
} else {
return;
}
//#endif
},
//
async isLogin() {
const res = await this.$api.user.isLogin();
if (res.data.is_login > 0) {
this.$emit('isLogin')
} else {
this.show = true;
}
},
async actionLogin(code, phone_code = "", origin_h5 = 0) {
const foke_parent_user = uni.getStorageSync('distributor_id') || 0
try {
const res = await this.$api.user.login({
code,
foke_parent_user,
phone_code,
origin_h5
});
uni.hideLoading();
if (!res.code) {
let {
token,
user_info,
store_id
} = res.data;
uni.setStorageSync("token_" + store_id, token);
uni.setStorageSync("user_info", user_info);
if (this.type != 0) {
uni.showToast({
icon: "none",
title: "登录成功",
});
}
foke_parent_user && this.initDistributor(foke_parent_user)
this.backPage()
return;
}
uni.$u.toast(res.message);
} catch (e) {
uni.hideLoading();
throw new Error(e);
}
this.disabled = false;
},
loginShow(e) {
// this.$emit('loginShow', e);
this.show = e
},
empower(e) {
this.login()
},
goLogin(e, params) {
this.login(params)
},
backPage() {
if (this.type == 0) {
} else if (this.type != 2) {
setTimeout(() => {
const backPage = uni.getStorageSync("backPage")
if (backPage) {
if (backPage.includes('login/login')) {
this.$utils.toUrl("/pages/index/index", "redirectTo");
} else {
this.$utils.toUrl(backPage);
}
} else {
this.$utils.toUrl("/pages/index/index", "redirectTo");
}
// this.$emit('loginShow', false);
}, 200);
} else {
this.$emit('isLogin')
this.loginShow(false)
}
}
},
};
</script>

@ -0,0 +1,189 @@
<template>
<view>
<view class="tabbar">
<view :class="['item', index === select_index ? 'checked' : '']" v-for="(item, index) of tabbar" :key="index"
@click="goUrl(item)">
<image v-if="index === select_index" :src="item.pic_url_on" mode="aspectFill"></image>
<image v-else :src="item.pic_url" mode="aspectFill"></image>
<view :style="{ '--font_size': '12px' }" class="name">{{ item.name }}
</view>
</view>
</view>
</view>
</template>
<script>
import {
getTabbarIndex
} from "@/utils/app";
export default {
name: 'the-tabbar',
components: {},
data() {
return {
select_index: 0,
status: false,
templateList: null,
loading: true,
vibrateShort: false
};
},
computed: {
cartNum() {
return this.$store.getters.cartNum;
},
tabbar() {
const tabImage = this.$store.state.$Gimage.tab || {}
return [
{
name: "首页",
link: "pages/index/index",
pic_url: tabImage.index,
pic_url_on: tabImage.index_active,
},
{
name: "我的",
link: "pages/my/my",
pic_url: tabImage.user,
pic_url_on: tabImage.user_active,
},
]
}
},
created() {
this.getTemplate();
// console.log('this.$store.getters.cartNum11111111111111111', this.$store.getters.cartNum)
},
methods: {
async getTemplate() {
uni.showLoading({
title: "请稍等",
mask: true,
});
try {
const res = await this.$api.diy.showTemplate({
type: 2
});
uni.hideLoading();
if (res.code) return uni.$u.toast(res.message);
this.status = res.data ? res.data.data[0].status : 0;
this.templateList = res.data.data[0];
if (res.data.data[0].vibrateShort) {
this.vibrateShort = true
}
this.getTabbar();
this.loading = false
} catch (e) {
uni.hideLoading();
throw new Error(e);
}
},
goUrl(item) {
// uni.setStorageSync("sharePage", item);
// #ifdef MP
if (this.vibrateShort) {
uni.vibrateShort({
success: function () {
console.log('success');
}
});
}
// #endif
uni.switchTab({
url: `/${item.link}`,
});
},
goDiyUrl(item) {
uni.setStorageSync("sharePage", item);
this.toLinkUrl(item.link)
},
getTabbar() {
// this.select_index =
this.getTabbarIndex();
},
getTabbarIndex() {
const routes = getCurrentPages(),
last = routes[routes.length - 1]
// var static_index = 0
var that = this
// console.log(last.$page.fullPath, 'lastlastlast')
// console.log(this.status, 'this.status')
if (this.status) {
this.templateList.list.forEach((item, index) => {
if (last.$page.fullPath == item.link.link) {
that.select_index = index
}
})
} else {
this.tabbar.forEach((item, index) => {
if (last.$page.fullPath == '/' + item.link) {
that.select_index = index
}
})
}
// return static_index;
},
},
onReachBottom() { },
onPullDownRefresh() { },
}
</script>
<style scoped lang="scss">
.tabbar {
position: fixed;
left: 0;
bottom: 0;
z-index: 9;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
width: 100%;
background: #fff;
padding-bottom: 0;
padding-bottom: calc(constant(safe-area-inset-bottom));
padding-bottom: calc(env(safe-area-inset-bottom));
box-shadow: 0 0.3125rem 1.25rem 0px rgba(166, 199, 251, 0.12);
.item {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 25%;
height: 100rpx;
position: relative;
.box {
position: absolute;
top: 0;
right: 36rpx;
}
&.checked {
.name {
color: #f0250e;
}
}
}
image {
display: block;
width: 40rpx;
height: 40rpx;
}
.name {
height: 40rpx;
line-height: 40rpx;
font-size: var(--font_size);
color: #666;
}
}
</style>

@ -0,0 +1,138 @@
<template>
<view>
<slot v-if="phone"></slot>
<u-popup zIndex="9999999999" style="z-index: 9999999999;" v-else show mode="center" closeable @close="back">
<view class="tipPopup" v-if="!phone">
<h3>提示</h3>
<u--text margin="12px 0 12px 0" text="当前账号未绑定手机号" type="info"></u--text>
<!-- <view class="btns">
<view class="btn" @click="back">
<view>
返回
</view>
</view>
<view class="btn1" @click="toUrl('/subPackages/user/user-mobile')">
<view>
前往绑定
</view>
</view>
</view> -->
<view>
<view class="btn1" @click="toUrl('/subPackages/user/user-mobile')">
<view>
前往绑定
</view>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
export default {
data() {
return {
phone: true
}
},
created() {
uni.$on('changePhone', res => {
console.log(res, '接收到的参数')
this.phone = res
})
this.getInfo()
},
methods: {
async getInfo() {
const res = await this.$api.common.isPhone();
this.phone = !!res.data
},
back() {
uni.navigateBack();
}
},
};
</script>
<style scoped lang="scss">
.tipPopup {
width: 90vw;
margin: auto;
// border-radius: 20rpx;
overflow: hidden;
box-sizing: border-box;
padding: 36rpx;
background-color: #fff;
border-radius: 20rpx;
}
.btn {
margin: 0 20rpx;
margin-top: 40rpx;
font-weight: 600;
view {
display: flex;
align-items: center;
justify-content: center;
padding: 22rpx;
color: white;
border-radius: 100px;
background: linear-gradient(to left, #ff2c2c, #ff4e4e);
transition: .24s;
background-size: 1000% 1000%;
transition: all .2s ease;
animation: Gradient 40s linear infinite;
&:hover {
opacity: 0.8;
}
}
}
.btn1 {
margin: 0 20rpx;
margin-top: 40rpx;
font-weight: 600;
view {
display: flex;
align-items: center;
justify-content: center;
padding: 22rpx;
color: white;
border-radius: 100px;
transition: .24s;
background: linear-gradient(-45deg, #448bff, #44e9ff, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background: linear-gradient(to left, #ff2c2c, #ff4e4e);
background-size: 1000% 1000%;
transition: all .2s ease;
animation: Gradient 40s linear infinite;
&:hover {
opacity: 0.8;
}
}
}
.btns {
display: grid;
grid-template-columns: 1fr 2fr;
}
/deep/ {
.u-fade-enter-active {
background-color: rgba(0, 0, 0, 0.1) !important;
filter: blur(100px);
}
}
</style>

@ -0,0 +1,95 @@
<template>
<view class="number-step">
<view :class="['action', value <= min ? 'disabled' : '']" @click="reduceAction">
<text>-</text>
</view>
<view class="number-block">
<input class="number-input" type="number" :value="value" @blur="numberInputChange" :disabled="disabled" />
</view>
<view :class="['action', disabled ? 'disabled' : '']" @click="addAction">
<text>+</text>
</view>
</view>
</template>
<script>
export default {
props: {
value: {
type: [Number, String],
default: 0,
},
min: {
type: Number,
default: 0,
},
disabled: {
type: Boolean,
default: false,
},
},
methods: {
numberInputChange(e) {
this.$emit("input", e.target.value);
},
addAction() {
if (this.disabled) return;
this.$emit("input", this.value + 1);
},
reduceAction() {
if (this.disabled) return;
this.$emit("input", this.value - 1);
},
},
};
</script>
<style scoped lang="scss">
.number-step {
border: 1px solid #f2f2f2;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
.action {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
width: 60rpx;
position: relative;
font-weight: bold;
text {
position: relative;
top: -3rpx;
}
.disabled {
color: #cccccc;
pointer-events: none;
}
}
.number-block {
width: 70rpx;
height: 100%;
display: flex;
align-items: center;
.number-input {
border-left: 1px solid #f2f2f2;
border-right: 1px solid #f2f2f2;
width: 100%;
height: 60rpx;
font-size: 24rpx;
font-weight: 600;
color: #262626;
display: inline-block;
text-align: center;
}
}
}
</style>

@ -0,0 +1,15 @@
const env = {
host: "https://saasdemo.byin.vip",
NETWORK_TIME_OUT: 15000,
version: "0.1",
store_id: 1,
mini_id: 0
};
// #ifdef H5
// 生产环境
if (process.env.NODE_ENV === 'production') {
// TODO
env.host = `${window.location.origin}`
}
// #endif
export default env;

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

@ -0,0 +1,52 @@
import App from "./App";
import Vue from "vue";
import mixin from "@/mixin/index.js";
import utils from "@/utils/utils.js";
import api from "@/api/index.js";
import store from "@/store/index.js";
import uView from "@/uni_modules/uview-ui";
/* #ifdef H5 */
import {
h5init
} from "@/utils/h5init";
h5init()
/* #endif */
const globalInformation = new Promise(async (resolve) => {
resolve({
image: await api.common.getImages(),
storeDetail: await api.common.getStoreDetail(),
})
}).catch(e => {
console.log(e);
})
globalInformation.then(({
image,
storeDetail
}) => {
store.dispatch("setData", {
key: "$Gimage",
val: image.data
})
store.dispatch("setData", {
key: "$GstoreDetail",
val: storeDetail.data
})
})
Vue.use(uView);
Vue.mixin(mixin);
Vue.config.productionTip = false;
App.mpType = "app";
Vue.prototype.$utils = utils;
Vue.prototype.$api = api;
const app = new Vue({
store,
...App,
});
app.$mount();

@ -0,0 +1,114 @@
{
"name" : "佰因商城",
"appid" : "__UNI__730E59B",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"h5" : {
"router" : {
"base" : "/pageh5/",
"mode" : "hash"
}
},
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "佰因商城",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx2efc62ddeef55fb9",
"setting" : {
"urlCheck" : false,
"ignoreDevUnusedFiles" : false,
"postcss" : true
},
"usingComponents" : true,
"optimization" : {
"subPackages" : true
},
"permission" : {
"scope.userLocation" : {
"desc" : "同城配送地址查询"
},
"scope.writePhotosAlbum" : {
"desc" : "保存分享图片"
}
},
"plugins" : {},
"requiredPrivateInfos" : [ "getLocation", "chooseAddress" ]
},
"mp-alipay" : {
"usingComponents" : true,
"appid" : "2021003197674866",
"plugins" : {
// "groupPurchaseoucherPlugin": {
// "version": "*", // *
// "provider": "2021003190661263"
// },
// "theGoodsEdit": {
// "version": "*", // *
// "provider": "2021003177653028" // Id
// },
"alipassToolKit" : {
//
"version" : "*", // *
"provider" : "2021001107697072" // ID
},
"couponPlugin" : {
"version" : "*", //
"provider" : "2021002172680015" // ID
}
}
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2"
}

@ -0,0 +1,162 @@
import utils from "@/utils/utils.js";
import wxConfig from '@/utils/wxConfig'
export default {
data() {
return {}
},
computed: {
publicImage() {
return this.$store.state.$Gimage || {}
},
storeDetail() {
return this.$store.state.$GstoreDetail || {}
}
},
onLoad() {
if (utils.isInWeChatBrowser()) {
if (uni.getStorageSync("sharePage")) {
this.diySharePage(uni.getStorageSync("sharePage"))
}
}
},
methods: {
// 获取当前登录信息
getUser(key) {
const user_info = uni.getStorageSync('user_info')
return user_info[key] || user_info || {}
},
/**
* 复制文本到剪贴板
* @param {string} e - 要复制的文本
* @returns {void}
* @vue/component
*/
copy(e) {
uni.setClipboardData({
data: e,
showToast: false,
success: () => {
this.$u.toast('复制成功');
},
fail: () => {
this.$u.toast('复制失败,请检查权限!');
}
});
},
// 领取优惠券
async getACoupon({
id
}) {
try {
const res = await this.$api.coupon.getCoupon({
id
});
if (res.code != 0) {
throw new Error(res)
}
} catch (error) {
console.log({
getACoupon: error
});
}
},
// 成为下线的条件 = 首次点击链接
// 存在推荐人时调用 => 后台去判断成为下线条件
async initDistributor(id = 0) {
if (!id) {
return
}
try {
setTimeout(async () => {
const res = await this.$api.distSales.ApplyForNext({
foke_parent_user: id || uni.getStorageSync('distributor_id') || 0
})
if (res.code === 0) {
console.log("initDistributor调用成功");
}
}, 1000)
} catch (e) {
console.log({
initDistributorError: e
});
}
},
// 访问记录
async initLog({
mch_id = 0
} = {}) {
try {
if (uni.getStorageSync("token")) {
setTimeout(async () => {
const res = await this.$api.user.log({
mch_id
})
if (res.code === 0) {
console.log("iinitLog调用成功");
}
}, 1000)
}
} catch (e) {
console.log({
initLogError: e
});
}
},
diySharePage(data = {}) {
uni.removeStorageSync('sharePage');
let base_url = window.location.href;
const that = this
// 获取用户ID
const userId = this.getUser("id");
// 构建基本的查询字符串
let queryString = `distributor_id=${userId}`;
// 检查是否已经存在参数
if (base_url.includes('?')) {
// 如果已经存在参数,在原有参数的基础上添加新参数
queryString = `${base_url}&${queryString}`;
} else {
// 如果不存在参数,直接添加新参数
queryString = `${base_url}?${queryString}`;
}
wxConfig.init(['updateAppMessageShareData', 'updateTimelineShareData']).then(wx => {
wx.updateAppMessageShareData({
title: data.name || data.title || that.storeDetail.name, // 分享标题
desc: data.desc || '', // 分享描述
link: queryString, // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: data.imageUrl ? this.httpToHttps(data.imageUrl) : this.httpToHttps(that
.storeDetail.logo_url), // 分享图标
success: function() {
// 设置成功
}
})
wx.updateTimelineShareData({
title: data.name || data.title || that.storeDetail.name, // 分享标题
desc: data.desc || '', // 分享描述
link: queryString, // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: data.imageUrl ? this.httpToHttps(data.imageUrl) : this.httpToHttps(that
.storeDetail.logo_url), // 分享图标
success: function() {
// 设置成功
}
})
})
},
httpToHttps: utils.httpToHttps,
toUrl: utils.toUrl,
kmUnit: utils.kmUnit,
}
};

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

@ -0,0 +1,3 @@
{
}

@ -0,0 +1,110 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES"
}
}
},
{
"path": "pages/my/my",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "我的",
"enablePullDownRefresh": true,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES"
}
}
}
],
"subPackages": [
{
"root": "subPackages/login",
"pages": [
// #ifndef MP
{
"path": "reg/index",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "reg/reset",
"style": {
"navigationBarTitleText": "找回密码"
}
},
// #endif
{
"path": "login/index",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "forgotPassword/index",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "forgotPassword/nuxt",
"style": {
"navigationBarTitleText": "忘记密码"
}
}
]
},
{
"root": "subPackages/inPersonToPay",
"pages": [
{
"path": "index/index",
"style": {
"navigationBarTitleText": "当面付"
}
},
{
"path": "result/index",
"style": {
"navigationBarTitleText": "支付成功"
}
}
]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#ffffff",
"h5": {
"navigationStyle": "custom"
}
},
"uniIdRouter": {},
"tabBar": {
"color": "#2E2D2D",
"selectedColor": "#E2231A",
"borderStyle": "black",
"backgroundColor": "#fff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/my/my",
"text": "我的"
}
]
}
}

@ -0,0 +1,79 @@
<template>
<view class="tabbar-page page-box" style="position: relative;display: flex;align-items: center;">
<view style="display: flex;flex-direction: column;align-items: center;width: 100%;">
<view>
<image style="width: 200rpx;height: 200rpx;border-radius: 20rpx;" :src="storeDetail.logo_url"></image>
</view>
<view style="margin-top: 20rpx;">
{{ storeDetail.name }}
</view>
</view>
<theTabBar></theTabBar>
</view>
</template>
<script>
import theTabBar from "@/components/the/the-tabbar";
export default {
mixins: [],
components: {
theTabBar
},
data() {
return {
}
},
onLoad(opt) {
uni.hideTabBar({
animation: true,
success: function (res) {
console.log(res); // { "success": true}
},
fail: function (err) {
console.log(err);
}
});
},
computed: {
shareData() {
return {
path: "/pages/index/index",
desc: "多彩时尚,一站式购物体验"
}
}
},
async onPullDownRefresh() {
},
//
onShareAppMessage(res) {
},
onReachBottom() {
this.on_reach_bottom = true;
},
onPullDownRefresh() {
this.getTemplate();
setTimeout(function() {
uni.stopPullDownRefresh();
}, 200);
},
methods: {
}
}
</script>
<style lang="scss">
// #ifndef H5
.content {
padding-top: calc(44px + var(--status-bar-height));
}
// #endif
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -0,0 +1,772 @@
<template>
<view class="tabbar-page page-box" style="background-color: white;">
<view v-if="templateInfo">
<!-- #ifdef MP -->
<template v-if="control.titleStyle">
<template v-if="control.titleStyle.title_type == 0">
<view :style="{
paddingTop: statusBarHeight + 'px',
height: headHeight + 'px',
backgroundColor: control.titleStyle.title_background_color,
backgroundImage: control.titleStyle.backgroundImage,
fontSize: control.titleStyle.font_size / 2 + 'px',
color: control.titleStyle.font_color,
textAlign: 'center',
lineHeight: headHeight + 'px',
position: 'fixed',
width: '100%',
left: 0,
top: 0,
zIndex: 10
}">
{{ control.titleStyle.show_title ? control.titleStyle.name : '' }}
</view>
<!-- 占位高度 -->
<view :style="{ height: statusBarHeight + headHeight + 'px' }"></view>
</template>
</template>
<!-- #endif -->
<!-- #ifndef MP-->
<!-- <view class="status" v-if="control.titleStyle && control.titleStyle.title_type == 0"
:style="{ backgroundColor: control.titleStyle.title_background_color }"></view>
<view class="navTitle" v-if="control.titleStyle && control.titleStyle.title_type == 0"
:style="{ backgroundColor: control.titleStyle.title_background_color, fontSize: control.titleStyle.font_size / 2 + 'px', color: control.titleStyle.font_color }">
{{ control.titleStyle.name }}
</view> -->
<!-- #endif -->
<!-- 用户头部 -->
<view :class="ifMP ? '' : 'content'"></view>
<view style="border-radius: 0 0 40rpx 40rpx;overflow: hidden;"
:style="{ background: control.user.userBgType == 2 ? `url(${control.user.userBgImg})` : control.user.userBgColor, }">
<view class="user-box" style="background: transparent;">
<view class="user-info" style="justify-content: space-between;width: 100%;"
v-if="data.user_info && data.user_info.id">
<view class="user-info" @click="triggerUsertoeditpop">
<view class="user-logo">
<u-image width="100rpx" height="100rpx" :src="data.user_info.avatar_pic"></u-image>
</view>
<view style="text-align: left;">
<view style="display: flex;"
:style="{ color: control.user.color, fontSize: control.user.font_size / 2 + 'px' }">
{{
data.user_info.nickname }}
<u--text type="warning" size="12" margin="0px 0px 0px 10rpx" suffixIcon="edit-pen"
iconStyle="color: #ff9900"></u--text>
</view>
<!-- <u--text type="warning" size="12" text="编辑用户信息" suffixIcon="edit-pen"
iconStyle="color: #ff9900"></u--text> -->
</view>
</view>
<!-- #ifndef MP -->
<view style="font-size: 26rpx;height: 140rpx;">
<view style="display: flex;align-items: center;flex-direction: column;" @click="logout">
<u-icon name="info-circle" size="32rpx" color="#FFF"></u-icon>
<view>
注销
</view>
</view>
</view>
<!-- #endif -->
</view>
<view class="user-info" v-else>
<view class="user-logo">
<u-icon size="40" name="account-fill"></u-icon>
</view>
<view @click="toUrl('/subPackages/login/login/index')">
<view :style="{ color: control.user.color, fontSize: control.user.font_size / 2 + 'px' }">
登录/注册
</view>
</view>
</view>
</view>
<view style="border-radius: 20rpx 20rpx 20rpx 20rpx;overflow: hidden;" class="wallet-box"
v-if="templateInfo.userInfo && templateInfo.userInfo.walletShow && data.user_info">
<view class="wallet-title">
<u-image v-show="templateInfo.userInfo.walletImage" style="width: 16px;height: 16px;"
class="img" width="16px" height="16px" :src="templateInfo.userInfo.walletImage"></u-image>
{{ templateInfo.userInfo.walletName }}
</view>
<view class="wallet-list" v-if="templateInfo.userInfo.walletList"
:style="{ 'grid-template-columns': `repeat(${(templateInfo.userInfo.walletList.length)} , 1fr)` }">
<view v-for="(item, index) in templateInfo.userInfo.walletList" :key="index" class="item"
@click="toUrl(item.link)">
<view class="price" style="text-align: center;">
{{ data.user_info[item.key] || 0 }}
</view>
<view class="text">
<view v-show="item.pic_url" style="width: 14px;height: 14px;margin-right: 8rpx;">
<u-image v-show="item.pic_url" class="img" width="14px" height="14px"
:src="item.pic_url"></u-image>
</view>
{{ item.name }}
</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="!templateInfo && isTemplate">
<view :style="{
paddingTop: statusBarHeight + 'px',
height: headHeight + 'px',
backgroundColor: '#f0250e',
fontSize: '16px',
color: '#fff',
textAlign: 'center',
lineHeight: headHeight + 'px',
position: 'fixed',
width: '100%',
left: 0,
top: 0,
zIndex: 10
}">
个人中心
</view>
<!-- 占位高度 -->
<view :style="{ height: statusBarHeight + headHeight + 'px' }"></view>
<!-- 用户头部 -->
<view class="user-box">
<view class="user-info" v-if="data.user_info && data.user_info.id" @click="userToEditPop = true">
<view class="user-logo">
<u-image width="100rpx" height="100rpx" :src="data.user_info.avatar_pic"></u-image>
</view>
<view>
<view>{{ data.user_info.nickname }}</view>
</view>
</view>
<view class="user-info" v-else>
<view class="user-logo">
<u-icon size="40" name="account-fill"></u-icon>
</view>
<view @click="toUrl('/subPackages/login/login/index')">
<view>登录/注册</view>
</view>
</view>
</view>
</view>
<theTabBar></theTabBar>
</view>
</template>
<script>
import theTabBar from "@/components/the/the-tabbar";
export default {
mixins: [],
components: {
theTabBar
},
data() {
return {
data: {
user_info: null,
},
templateInfo: null,
control: {},
statusBarHeight: uni.getStorageSync('statusBarHeight'),
headHeight: uni.getStorageSync('headHeight'),
user_infoTemporary: {
avatarUrl: "",
nickname: "",
},
fileList: [],
userToEditPop: false,
userToEditPopA: false,
ifMP: false,
isTemplate: false,
order_count: null,
pictureUpload: false
};
},
onLoad(params) {
uni.hideTabBar({
animation: true,
success: function (res) {
console.log(res); // { "success": true}
},
fail: function (err) {
console.log(err);
}
});
uni.hideTabBar();
// #ifdef MP
this.ifMP = true;
// #endif
},
onShow() {
this.getData();
this.getTemplate();
this.getOrderCount()
},
computed: {
storeDetail() {
return this.$store.state.$GstoreDetail || {}
},
shareData() {
return {
path: "/pages/my/my",
title: "我的",
desc: "用户中心"
}
}
},
onPullDownRefresh() {
this.getData();
this.getTemplate();
this.getOrderCount()
setTimeout(function() {
uni.stopPullDownRefresh();
}, 200);
},
methods: {
async getTemplate() {
uni.showLoading({
title: "请稍等",
mask: true,
});
try {
const res = await this.$api.diy.showTemplate({
type: 3
});
uni.hideLoading();
if (res.code) return uni.$u.toast(res.message);
this.templateInfo = res.data.data[0];
if (this.templateInfo?.userInfo?.walletList) {
this.templateInfo.userInfo.walletList = this.templateInfo.userInfo.walletList.filter(item =>
item.show)
}
if (this.templateInfo?.aliMembershipCardInfo) {
this.templateInfo.aliMembershipCardInfo = this.templateInfo.aliMembershipCardInfo || {}
}
this.control = res.data.data[1];
if (this.control.showTools === undefined) {
this.control.showTools = true
}
} catch (e) {
uni.hideLoading();
// throw new Error(e);
}
this.isTemplate = true
},
async getData() {
try {
const res = await this.$api.user.userInfo();
if (!res.data.user_info) {
return
}
this.data.user_info = res.data.user_info;
if (!this.user_infoTemporary.avatarUrl) {
this.user_infoTemporary.avatarUrl = res.data.user_info.avatar_pic
this.user_infoTemporary.nickname = res.data.user_info.nickname
}
//
if (res.data.user_info.is_update_user === 0 && this.storeDetail.display_setting && this.storeDetail
.display_setting.wx_user_info == 1) {
this.userToEditPopA = true
this.user_infoTemporary.avatarUrl = ""
this.user_infoTemporary.nickname = ""
}
} catch (e) {
// throw new Error(e);
}
},
async getOrderCount() {
try {
const res = await this.$api.common.orderCount();
if (res.code == 0) {
this.order_count = res.data
}
} catch (e) {
console.log(e, 'eeeee')
// throw new Error(e);
}
},
//
triggerUsertoeditpop() {
this.user_infoTemporary.avatarUrl = this.data.user_info.avatar_pic
this.user_infoTemporary.nickname = this.data.user_info.nickname
this.userToEditPop = true
},
//
async onChooseAvatar(e) {
this.user_infoTemporary.avatarUrl = await this.uploadFilePromise(e.detail.avatarUrl)
},
//
async afterRead(event) {
this.user_infoTemporary.avatarUrl = await this.uploadFilePromise(event.file.url)
},
async uploadFilePromise(filePath) {
this.pictureUpload = true
const result = await this.$api.common.upload({
filePath: filePath,
name: "image",
})
setTimeout(() => {
this.pictureUpload = false
}, 1000);
return result.data.url
},
getNickname(e) {
this.user_infoTemporary.nickname = e
},
//
async userToEdit() {
if (this.user_infoTemporary.nickname.trim() == "") {
this.$u.toast("请输入昵称")
return
}
const res = this.$api.user.UpdateUser({
avatar_pic: this.user_infoTemporary.avatarUrl,
nickname: this.user_infoTemporary.nickname,
})
if (!res.code) {
this.userToEditPop = false
this.userToEditPopA = false
this.data.user_info.avatar_pic = this.user_infoTemporary.avatarUrl
this.data.user_info.nickname = this.user_infoTemporary.nickname
uni.$u.toast('保存成功');
}
},
logout() {
let that = this
uni.showModal({
title: '提示',
content: '是否确认注销?',
success(res) {
if (res.confirm) {
uni.removeStorageSync("token");
uni.removeStorageSync("user_info");
that.getData();
}
}
})
}
},
}
</script>
<style lang="scss" scoped>
.taimi-box {
background: #1F222C;
margin: 10rpx;
padding: 20rpx;
border-radius: 16rpx;
color: #8E827A;
display: flex;
align-items: center;
justify-content: space-between;
.taimi-icon {
height: 70rpx;
width: 70rpx;
background: #595B65;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.taimi-but {
background: #E7BD9C;
padding: 10rpx 20rpx;
color: #1F222C;
font-size: 24rpx;
border-radius: 30rpx;
}
}
// #ifndef H5
.content {
padding-top: calc(44px + var(--status-bar-height));
}
// #endif
.status {
position: fixed;
width: 100%;
left: 0;
top: 0;
z-index: 10;
height: var(--status-bar-height);
}
.navTitle {
height: 44px;
line-height: 44px;
margin-top: var(--status-bar-height);
text-align: center;
position: fixed;
width: 100%;
left: 0;
top: 0;
z-index: 10;
}
* {
box-sizing: border-box;
}
.tabbar-page {
background: #f2f2f2;
}
.menu-item-box {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 24rpx;
gap: 24rpx;
flex-wrap: wrap;
align-items: center;
margin-top: 26rpx;
.menu-item {
// width: 20%;
font-size: 24rpx;
// margin-bottom: 22rpx;
.menu-item-image {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 14rpx;
width: 100%;
}
view {
text-align: center;
}
}
}
.user-box {
height: 200rpx;
background: #f0250e;
text-align: center;
display: flex;
// align-items: center;
padding: 10rpx 20rpx;
background-position: top left;
background-size: cover;
box-shadow: 0 0.3125rem 1.25rem 20px rgba(166, 199, 251, 0.12);
.user-info {
display: flex;
align-items: center;
color: #fff;
font-size: 40rpx;
.user-logo {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #fff;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
overflow: hidden;
}
}
}
.order-box {
margin: 30rpx 20rpx;
padding: 20rpx;
border-radius: 10rpx;
border-radius: 20rpx;
background: #fff;
box-shadow: 0 0.3125rem 1.25rem 0px rgba(188, 188, 188, 0.16);
.order-title {
// font-size: 28rpx;
// font-weight: bold;
// border-bottom: 1px solid #eee;
// padding-bottom: 20rpx;
// display: none;
font-size: 28rpx;
font-weight: bold;
// border-bottom: 1px solid #eee;
box-shadow: 0 0.3125rem 1.25rem 0px rgba(166, 199, 251, 0.12);
padding-bottom: 20rpx;
margin: -30rpx -20rpx;
padding: 30rpx 20rpx;
margin-bottom: 26rpx;
display: flex;
justify-content: space-between;
.more {
font-size: 24rpx;
color: #999;
}
}
.order-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 24rpx;
gap: 24rpx;
.item {
display: inline-flex;
align-items: center;
flex-direction: column;
position: relative;
.box {
position: absolute;
right: 0;
top: 0;
}
}
.text {
display: inline-flex;
margin-top: 10rpx;
font-size: 28rpx;
}
}
}
.menu-box {
padding: 20rpx;
border-radius: 10rpx;
background: #fff;
margin: 30rpx 20rpx;
box-shadow: 0 0.3125rem 1.25rem 0px rgba(188, 188, 188, 0.16);
border-radius: 20rpx;
overflow: hidden;
.menu-title {
font-size: 28rpx;
font-weight: bold;
// border-bottom: 1px solid #eee;
box-shadow: 0 0.3125rem 1.25rem 0px rgba(166, 199, 251, 0.12);
padding-bottom: 20rpx;
margin: -30rpx -20rpx;
padding: 30rpx 20rpx;
margin-bottom: 26rpx;
}
}
/deep/ {
.u-badge--error {
background: linear-gradient(90deg, #ff1e00 0%, #fc754c 100%);
}
}
.userToEditPop {
padding: 20rpx 60rpx 0rpx;
/deep/ {
.u-input {
border: 1px solid rgb(236, 236, 236) !important;
border-radius: 100px !important;
}
}
.avatar-wrapper {
padding: 0;
width: 56px !important;
height: 56px !important;
border-radius: 100rpx;
margin-top: 40rpx !important;
margin-bottom: 40rpx !important;
margin-left: auto;
margin-right: auto;
position: relative;
.pictureUpload {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
background: #ffffffb8;
display: flex;
align-items: center;
justify-content: center;
}
}
.weui-input {
border-radius: 100px !important;
border: 1px solid #ececec !important;
}
.avatar {
display: block;
width: 56px;
height: 56px;
box-shadow: 0 0.3125rem 1.25rem 20px rgba(166, 199, 251, 0.12);
border-radius: 100px !important;
overflow: hidden;
}
.container {
display: flex;
}
}
.userToEditPopA {
padding: 40rpx 40rpx 30rpx;
width: 80vw;
box-sizing: border-box;
/deep/ {
.u-input {
border: 1px solid rgb(236, 236, 236) !important;
border-radius: 100px !important;
}
}
.avatar-wrapper {
padding: 0;
width: 56px !important;
height: 56px !important;
border-radius: 100rpx;
margin-top: 40rpx !important;
margin-bottom: 30rpx !important;
margin-left: auto;
margin-right: auto;
position: relative;
.pictureUpload {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
background: #ffffffb8;
display: flex;
align-items: center;
justify-content: center;
}
}
.weui-input {
border-radius: 100px !important;
border: 1px solid #ececec !important;
}
.avatar {
display: block;
width: 56px;
height: 56px;
box-shadow: 0 0.3125rem 1.25rem 20px rgba(166, 199, 251, 0.12);
border-radius: 100px !important;
overflow: hidden;
}
.container {
display: flex;
}
}
.btn {
width: 100%;
padding: 42rpx 0 24rpx;
background: #fff;
box-sizing: border-box;
transition: all 1s cubic-bezier(0.075, 0.82, 0.165, 1);
&:hover {
opacity: 0.8;
}
view {
display: flex;
align-items: center;
justify-content: center;
height: 76rpx;
background: linear-gradient(90deg, #f22407 0%, #f84d17 100%);
border-radius: 32px;
font-size: 26rpx;
font-weight: bold;
color: #fff;
}
}
.wallet-box {
display: grid;
grid-template-columns: 1fr 3fr;
background-color: white;
height: 84px;
box-sizing: border-box;
padding: 18px 0;
font-size: 24rpx;
background: hsla(0, 0%, 100%, .4);
.wallet-title {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
border-right: 1px #eee dotted;
margin-right: 24rpx;
}
.wallet-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
.item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
color: rgb(241, 193, 105);
.text {
display: flex;
align-items: center;
color: black;
padding-right: 14rpx;
}
.price {
font-weight: 600;
}
}
}
}
</style>

@ -0,0 +1,48 @@
.page-box {
min-height: 100vh !important;
}
.flex {
display: flex;
}
.flex-1 {
flex: 1;
}
.flex-row {
flex-direction: row;
}
.flex-col {
flex-direction: column;
}
.justify-around {
justify-content: space-around;
}
.items-center {
align-items: center;
}
.flex-x-between {
justify-content: space-between;
}
.flex-x-end {
justify-content: flex-end;
}
.flex-x-center {
justify-content: center;
}
.flex-y-center {
align-items: center;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}

@ -0,0 +1,78 @@
/**
* uni-app
*
* uni-app https://ext.dcloud.net.cn使
* 使scss使 import 便App
*
*/
/**
* App使
*
* 使scss scss 使 import
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//
$uni-text-color-inverse:#fff;//
$uni-text-color-grey:#999;//
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; //
/* 文章场景相关 */
$uni-color-title: #2C405A; //
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; //
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; //
$uni-font-size-paragraph:15px;
@import '@/uni_modules/uview-ui/theme.scss';

File diff suppressed because one or more lines are too long

@ -0,0 +1,31 @@
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
$Gimage: {},
$GstoreDetail: {},
// 购物车数量
cartNum: 0,
},
getters: {
cartNum: (state) => {
return state.cartNum || 0;
},
},
mutations: {
SET: (state, { key, val }) => {
state[key] = val
},
},
actions: {
setData({ commit }, { key, val }) {
commit('SET', { key, val })
}
},
modules: {},
});
export default store;

@ -0,0 +1,455 @@
<template>
<view class="coupon">
<view class="coupon_text" @click="show = true">
<view class="flex items-center">
<u--text bold margin="0 6px 0 0" text="优惠券" align="right"></u--text>
<!-- <u-tag v-if="currentcoupon.id" text="已选择" plain size="mini" type="error"></u-tag> -->
</view>
<u--text v-if="currentcoupon.id" type="error" :text="`- ¥${currentcoupon.coupon.price}`"
align="right"></u--text>
<u--text v-else type="info" text="请选择" align="right" suffixIcon="arrow-right"></u--text>
</view>
<u-popup bgColor="#fff" :round="22" :show="show" @close="show = false" @open="available = 1">
<view class="pop">
<view class="tabs">
<view class="tab" @click="available = 1" :class="{ active: available === 1 }">可用优惠券 ({{ on.length }})
</view>
<view class="tab" @click="available = 0" :class="{ active: available === 0 }">不可用优惠券 ({{ off.length }})
</view>
</view>
<view v-if="[off, on][available] && [off, on][available].length" class="chooseACoupon"
:class="{ sideEffects: available === 0 }">
<view class="item" v-for="item of [off, on][available]" @click="temporaryCurrentcoupon = item">
<view class="info" :class="{ true: available === 1 }">
<view class="left">
<view class="num">
{{ item.coupon.price || 0 }}
</view>
<view class="num_info" v-if="item.coupon.full_price || item.coupon.price">
{{ item.coupon.full_price }}{{ item.coupon.price }}
</view>
</view>
<view class="rigth">
<view class="more">
{{ item.coupon.name }}
</view>
<view class="type">
类型: <text>{{ ["优惠券", "商品", "分类", "当面付"][item.coupon.coupon_type] }}可用</text>
</view>
</view>
<view v-if="available === 1" class="operation"
:class="{ active: item.id === temporaryCurrentcoupon.id }">
<view class="checkbox" style="pointer-events: none;">
<u-checkbox-group>
<u-checkbox class="radio" shape="circle" activeColor="#F0250E"
:checked="item.id === temporaryCurrentcoupon.id"></u-checkbox>
</u-checkbox-group>
</view>
<view class="c"></view>
</view>
</view>
<view class="more_info">
<view class="top">
详细信息 <u-icon name="arrow-down" color="#808080"></u-icon>
</view>
<view class="cont">
<text>过期时间{{ $u.timeFormat(item.end_time, 'yyyy年mm月dd日 hh:MM:ss') }}</text>
</view>
</view>
</view>
</view>
<view v-if="[off, on][available] && [off, on][available].length === 0" class="chooseACoupon"
style="padding: 60rpx 0;">
<u-empty text="优惠券为空 ~">
</u-empty>
</view>
<view class="btn" @click="confirmationVoucher" v-show="available == 1">
<view>确认</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
export default {
props: {
coupon: {
type: Array,
default: () => []
},
currentPrice: {
type: Number | String,
default: 0
},
},
data() {
return {
currentcoupon: {},
temporaryCurrentcoupon: {},
available: 1,
show: false
}
},
watch: {
currentPrice(newVal) {
try {
const coupon = this.coupon.filter(item => {
return newVal >= Number(item.coupon.full_price)
}).sort((a, b) => Number(b.coupon.price) - Number(a.coupon.price))[0]
this.currentcoupon = coupon || {}
this.temporaryCurrentcoupon = coupon || {}
this.$emit("update:activeCoupon", this.currentcoupon);
} catch (e) {
this.currentcoupon = {}
this.temporaryCurrentcoupon = {}
this.$emit("update:activeCoupon", this.currentcoupon);
}
if (Number(newVal) < Number(this.currentcoupon?.coupon?.full_price)) {
this.$u.toast("付款金额发生变化,请重新选择优惠券")
this.currentcoupon = {}
this.temporaryCurrentcoupon = {}
this.$emit("update:activeCoupon", this.currentcoupon);
}
}
},
computed: {
on() {
return this.coupon.filter(item => Number(this.currentPrice) >= Number(item.coupon.full_price))
},
off() {
return this.coupon.filter(item => {
return Number(this.currentPrice) < Number(item.coupon.full_price)
})
},
},
methods: {
confirmationVoucher() {
this.currentcoupon = JSON.parse(JSON.stringify(this.temporaryCurrentcoupon))
this.$emit("update:activeCoupon", this.currentcoupon);
this.show = false
},
}
}
</script>
<style lang="scss" scoped>
.coupon {
padding: 32rpx 0rpx 20rpx 0;
.coupon_text {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.pop {
.tabs {
width: 100%;
display: flex;
padding: 40rpx 0;
box-shadow: 0 0.3125rem 1.25rem rgba(166, 199, 251, 0.12);
position: relative;
z-index: 99999;
.tab {
flex: 1;
text-align: center;
color: black;
}
.active {
position: relative;
&::before {
content: "";
position: absolute;
width: 44rpx;
height: 10rpx;
border-radius: 100px;
background: linear-gradient(86deg, #fc5928 0%, #ff7f58 100%);
bottom: -16rpx;
left: 0;
right: 0;
margin: 0 auto;
}
}
}
.sideEffects {
margin-bottom: 0rpx !important;
height: 680rpx !important;
}
.chooseACoupon {
display: flex;
flex-direction: column;
margin-bottom: 80rpx;
height: 600rpx;
overflow: auto;
padding: 42rpx 34rpx;
.item {
margin-bottom: 26rpx;
background-color: white;
border-radius: 22rpx;
box-shadow: 0 0.4125rem 2rem 2px rgba(215, 216, 217, 0.49);
.info {
display: flex;
flex: 1;
padding: 20rpx;
border-radius: 22rpx 22rpx 0rpx 22rpx;
position: relative;
overflow: hidden;
.left {
padding: 32rpx 42rpx 32rpx 14rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.num {
background: linear-gradient(90deg, #ff1e00 0%, #fc5420 100%);
background-clip: text;
color: transparent;
font-size: 54rpx;
font-weight: 600;
letter-spacing: 2rpx
}
.num_info {
color: #808080;
font-size: 24rpx;
margin-top: 10rpx;
}
}
.rigth {
z-index: 999;
font-size: 24rpx;
display: flex;
flex-direction: column;
justify-content: space-evenly;
.time {
color: black;
font-weight: 600;
}
.type {
font-size: 24rpx;
color: #808080;
}
}
.operation {
position: absolute;
right: 0;
width: 17%;
top: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 32rpx;
&.active {
.c {
&::after {
background-color: #fc511d !important;
}
}
}
.c {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
transform: translateY(-8%) rotate(8deg);
transform-origin: bottom left;
// new
transform: translateY(-4%) rotate(0deg);
transform-origin: top left;
&::after {
content: "";
top: 44rpx;
left: 0;
position: absolute;
width: 32rpx;
height: 32rpx;
background-color: white;
border-radius: 100px;
transform: translateX(-64%);
}
// new
&::after {
top: 33rpx;
transform: translateX(-36%);
}
// &::before {
// content: "";
// bottom: 0px;
// left: 0;
// position: absolute;
// width: 32rpx;
// height: 32rpx;
// background-color: white;
// border-radius: 100px;
// transform: translateX(-56%) translateY(50%);
// }
}
}
}
.true {
background: linear-gradient(86deg, #FFF 82.7%, rgb(252, 183, 162) 83%, #f8cb00 100%);
.operation {
transform: scale(1.1) translateY(0%);
&.active {
.c {
&::after {
background: white !important;
}
}
}
.c {
transform: translateY(-4%) rotate(0deg);
transform-origin: top left;
&::after {
top: 33rpx;
transform: translateX(-36%);
}
// &::before {
// content: "";
// bottom: 0px;
// left: 0;
// position: absolute;
// width: 32rpx;
// height: 32rpx;
// background-color: white;
// border-radius: 100px;
// transform: translateX(-56%) translateY(50%);
// }
}
}
}
.more_info {
padding: 18rpx;
border-top: 2px dotted #f3f3f3;
color: #808080;
font-size: 24rpx;
.top {
display: flex;
align-items: center;
grid-gap: 12rpx;
gap: 12rpx;
display: none;
}
.cont {
// padding: 20rpx 0 0 0;
}
}
}
}
.btn {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: 22rpx 36rpx 12rpx;
padding: 22rpx 36rpx calc(constant(safe-area-inset-bottom) + 22rpx);
padding: 22rpx 36rpx calc(env(safe-area-inset-bottom) + 22rpx);
background: #fff;
box-sizing: border-box;
transition: all 1s cubic-bezier(0.075, 0.82, 0.165, 1);
z-index: 9999;
&:hover {
opacity: 0.8;
}
view {
display: flex;
align-items: center;
justify-content: center;
height: 76rpx;
background: linear-gradient(90deg, rgb(250, 144, 112) 0%, #f8cb00 100%);
border-radius: 32px;
font-size: 26rpx;
font-weight: bold;
color: #fff;
}
}
}
::v-deep {
.u-checkbox__icon-wrap {
border-width: 0px !important;
}
}
</style>

@ -0,0 +1,212 @@
<template>
<view class="list" v-if="list.length">
<view @click="toUrl('/subPackages/goods/goods/index?id=' + e.id)" class="list-items" v-for="(e, i) of sortedItems"
:key="i">
<view class="list-con">
<view class="list-img">
<view class="tag" v-if="e.lable" :style="{ background: e.lable.bg_color }">{{ e.lable.name }}</view>
<view class="img">
<u-image loadingIcon="bag" showLoading bgColor="#fff" :fade="true" lazyLoad width="100%"
height="auto" :src="e.pic_url" mode="widthFix">
<template #loading>
<view
style="min-height: 200rpx;;display: flex;align-items: center;justify-content: center;">
<u-icon name="bag"></u-icon>
</view>
</template>
</u-image>
</view>
</view>
<view class="list-txt">
<view class="list-name mb">{{ e.name }}</view>
<view v-if="e.server_project && e.server_project.length" class="list-attr">
<u-text color="#f0250e" size="12" :lines="1" :text="e.server_project"></u-text>
</view>
<view class="list-txt-fd">
<view class="list-price g-price">
<template v-if="e.autoCouponPrice">
<text style="font-size: 24rpx;margin-right: 6rpx;">卷后</text>
<view>¥</view>
<view class="fwb">{{ getPrice(e.autoCouponPrice)[0] }}</view>
<view>.{{ getPrice(e.autoCouponPrice)[1] }}</view>
</template>
<template v-else>
<view>¥</view>
<view class="fwb">{{ getPrice(e.price_min)[0] }}</view>
<view>.{{ getPrice(e.price_min)[1] }}</view>
</template>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// new_list: [],
}
},
props: {
list: {
type: Array,
default: () => {
return []
}
},
classify: {
type: [String, Number]
}
},
computed: {
getPrice() {
return (price) => {
return price ? price.split(".") : ["0.00"];
};
},
sortedItems: function () {
let sortedArray = [];
let columnCount = 2; //
let columnIndex = 0;
//
let groups = this.list.reduce(function (result, item) {
if (!result[columnIndex]) {
result[columnIndex] = [];
}
result[columnIndex].push(item);
columnIndex = (columnIndex + 1) % columnCount;
return result;
}, []);
//
for (let i = 0; i < groups.length; i++) {
for (let j = 0; j < groups[i].length; j++) {
sortedArray.push(groups[i][j]);
}
}
return sortedArray;
}
},
}
</script>
<style lang="scss" scoped>
.list {
padding: 22rpx 12rpx;
column-count: 2;
gap: 20rpx;
grid-gap: 20rpx;
column-gap: 20rpx;
* {
box-sizing: border-box;
}
.list-items {
margin-bottom: 20rpx;
box-sizing: border-box;
break-inside: avoid;
box-shadow: 0 0.3125rem 1.25rem 0px rgba(166, 199, 251, 0.12);
&:first-child {
margin-top: 0;
}
}
.list-item {
padding: 12rpx;
width: 100%;
box-sizing: border-box;
}
.list-con {
background: #fff;
border-radius: 8px;
overflow: hidden;
.list-img {
width: 100%;
padding: 20rpx 20rpx 0;
overflow: hidden;
box-sizing: border-box;
min-height: 200rpx;
display: flex;
border-radius: 8rpx;
position: relative;
display: flex;
.tag {
position: absolute;
top: 20rpx;
left: 20rpx;
padding: 6rpx 15rpx 8rpx 10rpx;
font-size: 20rpx;
color: #fff;
z-index: 2;
border-radius: 8rpx 0 10px 0;
}
.img {
box-sizing: border-box;
border-radius: 8rpx;
overflow: hidden;
width: 100%;
}
}
.list-txt {
padding: 20rpx;
flex: 1;
overflow: hidden;
.list-name {
display: -webkit-box;
margin-bottom: 10rpx;
max-height: 80rpx;
white-space: normal;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
overflow: hidden;
font-size: 24rpx;
color: #262626;
line-height: 40rpx;
}
.list-txt-fd {
display: flex;
align-items: flex-end;
justify-content: space-between;
.list-price {
padding-right: 5rpx;
flex: 1;
overflow: hidden;
color: #f0250e;
}
}
}
.list-attr {
font-size: 24rpx;
color: #8c8c8c;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-bottom: 10rpx;
color: #f0250e;
text {
margin-right: 10rpx;
}
}
}
}
</style>

@ -0,0 +1,662 @@
<template>
<!-- <isPhone>
</isPhone> -->
<view class="page-box">
<template v-if="info.id">
<view class="info">
<view class="logo">
<u--image :showLoading="true" :src="info.logo_url" width="60px" height="60px"></u--image>
</view>
<text>
{{ info.name }}
</text>
</view>
<view class="con">
<u--text text="付款金额" margin="0 0 8px 8px" bold color="#000000" size="14"></u--text>
<view class="price">
<view>
<u--text text="¥" margin="0 3px 0 0" size="32" bold color="#000000"></u--text>
</view>
<input ref="input" v-model.lazy="total_price" @blur="formatInput" :focus="focusState" border="none"
type="digit" class="input" />
</view>
<coupons v-if="coupon && coupon.length" :activeCoupon.sync="activeCoupon" :coupon="coupon"
:currentPrice="total_price">
</coupons>
</view>
<view class="payList">
<view class="item" @click="pay(3)" v-if="info.pay_type.includes(3)">
<view class="let">
<text>余额支付</text>
<!-- <text v-show="total_price">{{ price || 0 }} </text> -->
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<view style="background: #19be6b;" class="item" @click="pay(2)" v-if="info.pay_type.includes(2)">
<view class="let">
<text>微信</text>
<!-- <text v-show="total_price">{{ price || 0 }} </text> -->
</view>
</view>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<view style="background: #2979ff;" class="item">
<view class="let" @click="pay(1)" v-if="info.pay_type.includes(1)">
<text>支付宝</text>
<!-- <text v-show="total_price">{{ price || 0 }} </text> -->
</view>
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view v-if="$utils.isInWeChatBrowser() && info.pay_type.includes(2)" style="background: #19be6b;"
class="item" @click="pay(2)">
<view class="let">
<text>微信</text>
<!-- <text v-show="total_price">{{ price || 0 }} </text> -->
</view>
</view>
<view v-if="$utils.isInAliBrowser() && info.pay_type.includes(1)" style="background: #2979ff;"
class="item" @click="pay(1)">
<view class="let">
<text>支付宝</text>
<!-- <text v-show="total_price">{{ price || 0 }} </text> -->
</view>
</view>
<!-- #endif -->
</view>
</template>
<logins @isLogin="isLogin" :type="$utils.isInAliBrowser() ? 0 : 2"></logins>
</view>
</template>
<script>
import isPhone from "@/components/tools/isPhone/index";
import coupons from '../compontents/coupons.vue';
import logins from '@/components/login/logins.vue'
// #ifndef MP
import wxConfig from '@/utils/wxConfig'
// #endif
export default {
components: {
coupons,
isPhone,
logins
},
data() {
return {
order_type: 1,
info: {
pay_type: []
},
total_price: "",
pop: false,
focusState: true,
payCurrent: false,
coupon: [],
activeCoupon: {},
mch_id: 0
}
},
computed: {
price() {
console.log(this.activeCoupon);
if (this.activeCoupon?.coupon?.price) {
return (this.total_price - this.activeCoupon.coupon.price)?.toFixed?.(2)
} else {
return (this.total_price)
}
}
},
async onLoad(query) {
let qrCode = '';
// #ifdef MP-ALIPAY
let qrQuery = ''
if (query.query && query.query.qrCode) {
qrQuery = query.query.qrCode; //
} else {
qrQuery = uni.getStorageSync('qrcode')
}
qrCode = decodeURIComponent(qrQuery || ''); //
// #endif
// #ifdef MP-WEIXIN
qrCode = decodeURIComponent(query.q || '');
// #endif
// #ifdef MP
const params = this.$utils.oneValues(qrCode)
// #endif
// #ifdef H5
const params = query
// #endif
const {
mch_id
} = params;
this.mch_id = mch_id || 0
this.$nextTick(() => {
this.init()
})
},
onShow() {
this.watchToMonitor = this.$watch(
//
() => {
return this.total_price
},
() => {
// do some thing
uni.setStorageSync("支付" + this.mch_id, JSON.stringify(this.total_price))
}, {
deep: true
})
},
onHide() {
this.watchToMonitor()
if (!this.payCurrent) {
this.payCurrent = false
this.focusState = false
}
},
methods: {
isLogin() {
// this.init()
},
async init() {
const res = await this.$api.inPersonToPay.storeInfos({
mch_id: this.mch_id,
})
this.info = {
...res.data.store,
...res.data.shop
}
if (!this.info.pay_type) {
this.info.pay_type = []
}
this.coupon = res.data.coupon
},
formatInput() {
if (this.total_price !== null) {
let value = this.total_price.toString();
value = value.replace(/[^\d.]/g, ""); //
value = value.replace(/^\./g, ""); //
value = value.replace(/\.{2,}/g, "."); //
value = value.replace(".", "$#$").replace(/\./g, "").replace("$#$", ".");
//
value = parseFloat(value);
if (isNaN(value)) {
value = null;
} else {
value = value.toFixed(2); //
}
this.total_price = value;
}
},
async pay(type) {
if (this.submit()) return
this.payCurrent = true
let {
order_type
} = this;
const payType = type //
let order_no
const res = await this.$api.inPersonToPay.inpersonPaymentOrder({
total_price: this.total_price,
pay_type: type,
mch_id: this.mch_id,
user_coupon_id: this.activeCoupon.id || 0,
})
if (res.code !== 0) {
uni.$u.toast(res.message || "出错了");
return
}
order_no = res.data.order_no //
uni.showLoading({
title: "请稍等",
mask: true,
});
switch (payType) {
case 1:
await this.payTreasureToPay({
payType,
order_no,
order_type
})
break;
case 2:
// #ifndef MP
await this.wechatPayNow({
payType,
order_no,
order_type
})
// #endif
// #ifdef MP-WEIXIN
await this.wechatPay({
payType,
order_no,
order_type
})
// #endif
break;
case 3:
console.log("余额");
await this.balancePayment({
payType,
order_no,
order_type
})
break;
default:
break;
}
uni.hideLoading();
},
//
async balancePayment({
payType,
order_no,
order_type
}) {
try {
const res = await this.$api.pay.OrderPayment({
pay_type: payType,
order_no: order_no,
order_type,
});
if (res.code) {
// this.$u.toast(res.message)
throw new Error(res.message)
};
this.successSubmit(res)
} catch (error) {
this.$u.toast(error.message)
}
},
// H5
async wechatPayNow({
payType,
order_no,
order_type
}) {
wxConfig.init(["chooseWXPay"]).then((wx) => {
console.log(wx.chooseWXPay);
})
try {
const res = await this.$api.pay.OrderPayment({
pay_type: payType,
order_no: order_no,
order_type
});
if (res.code) return this.$u.toast(res.message)
if (res.data == -1) {
return this.successSubmit();
}
wxConfig.init(["chooseWXPay"]).then(wx => {
wx.chooseWXPay({
// jssdk使timestamp
// 使timeStampS
timestamp: res.data.timeStamp, //
nonceStr: res.data.nonceStr, // 32
package: res.data.package, // prepay_idprepay_id=\*\*\*
signType: res.data.signType, //
paySign: res.data.paySign, //
success: function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok" || res.err_Info ==
"success" || res.errMsg == "chooseWXPay:ok") {
this.successSubmit();
} else {
this.failSubmit();
}
},
complete: function(err) {
if (res.err_msg == "get_brand_wcpay_request:ok" || res.err_Info ==
"success" || res.errMsg == "chooseWXPay:ok" || res.errMsg ==
"getBrandWCPayRequest:ok") {
this.successSubmit();
} else {
this.failSubmit();
}
},
})
})
} catch (error) {
this.$u.toast(error.message)
}
},
//
async wechatPay({
payType,
order_no,
order_type
}) {
try {
const res = await this.$api.pay.OrderPayment({
pay_type: payType,
order_no: order_no,
order_type,
});
if (res.code) return this.$u.toast(res.message)
// res
//
await uni.requestPayment({
provider: 'wxpay', //
timeStamp: res.data.timeStamp, //
nonceStr: res.data.nonceStr, //
package: res.data.package,
signType: res.data.signType, //
paySign: res.data.paySign, //
success: (res) => {
if (res.errMsg === 'requestPayment:ok') {
this.successSubmit();
}
console.log(res, 'success');
},
fail: (res) => {
console.log(res, 'fail')
if (res.errMsg == 'requestPayment:fail cancel') {
this.failSubmit();
}
},
complete(res) {
console.log(res, 'complete')
}
});
} catch (error) {
this.$u.toast(error.message)
console.log(error)
}
},
//
async payTreasureToPay({
payType,
order_no,
order_type
}) {
try {
let that = this
const res = await this.$api.pay.OrderPayment({
pay_type: payType,
order_no: order_no,
order_type,
});
if (res.code) return this.$u.toast(res.message)
// #ifdef H5
if (this.$utils.isInAliBrowser()) {
AlipayJSBridge.call("tradePay", {
tradeNO: res.data.trade_no
}, function(data) {
if ("9000" == data.resultCode) {
that.successSubmit(res)
} else {
throw new Error('支付失败,状态码:' + data.resultCode)
}
});
} else {
return uni.showToast({
icon: 'none',
title: '请到支付宝或微信环境中支付'
})
}
// #endif
// #ifdef MP-ALIPAY
// res
//
uni.requestPayment({
provider: 'alipay', //
orderInfo: res.data.trade_no,
success: (res) => {
console.log(res);
if (res.errMsg === 'requestPayment:ok' && res.resultCode ===
'9000') { //
this.successSubmit(res)
}
},
fail: (err) => {
if (err.errMsg !== 'requestPayment:fail cancel') { //
throw new Error(err.errMsg)
}
},
complete: ({
errMsg
}) => {
// if (errMsg) {
// this.$u.toast(errMsg);
// }
}
});
// #endif
} catch (error) {
this.$u.toast(error.message)
}
},
failSubmit() {
this.$u.toast("支付失败")
},
successSubmit() {
this.pop = false
this.$u.toast("支付成功")
setTimeout(() => {
this.toUrl('/subPackages/inPersonToPay/result/index?price=' + this.total_price + '&mch_id=' +
this.mch_id)
uni.removeStorageSync("支付" + this.mch_id)
this.total_price = ""
this.payCurrent = false
}, 300);
},
submit() {
if (!this.total_price) {
this.$u.toast("请输入付款金额")
return true
}
if (this.total_price <= 0) {
this.$u.toast("付款金额低于0元")
return true
}
return false
}
}
}
</script>
<style lang="scss" scoped>
.info {
display: flex;
align-items: center;
margin: 20px;
grid-gap: 32rpx;
grid: 32rpx;
.logo {
border-radius: 26rpx;
border-right-color: rgb(212, 212, 212);
overflow: hidden;
}
}
.con {
padding: 19px;
box-shadow: 0 0.4125rem 2rem 2px rgba(215, 216, 217, .49);
margin: 20px;
border-radius: 20rpx;
}
::v-deep {
uni-input {
font-weight: 600 !important;
color: black;
transition: all 0.6s;
font-weight: 600;
min-height: 80px !important;
display: flex;
}
.u-border-bottom {
border-bottom-width: 0.5px !important;
border-color: rgb(224, 223, 223) !important;
border-bottom-style: solid;
}
.u-popup__content {
background-color: transparent;
}
}
.input {
font-size: 32px;
padding: 10rpx 2%;
height: 42px;
line-height: 42px;
}
.payList {
// position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 12rpx 36rpx 12rpx;
// padding: 12rpx 36rpx calc(constant(safe-area-inset-bottom) + 22rpx);
// padding: 12rpx 36rpx calc(env(safe-area-inset-bottom) + 22rpx);
background: #fff;
padding: 19px;
box-shadow: 0 0.4125rem 2rem 2px rgba(215, 216, 217, .49);
margin: 20px;
border-radius: 40rpx;
.item {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(90deg, #f22407 0%, #f84d17 100%);
padding: 14rpx;
border-radius: 32px;
font-size: 26rpx;
font-weight: bold;
color: #fff;
box-sizing: border-box;
transition: all 1s cubic-bezier(0.075, 0.82, 0.165, 1);
&:hover {
opacity: 0.8;
}
}
.title {
padding-top: 34rpx;
font-weight: 600;
text-align: center;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
.let {
display: flex;
align-items: center;
// justify-content: space-between;
justify-content: center;
}
padding: 0 28rpx;
&:not(:last-child) {
margin-bottom: 20rpx;
border-bottom: 1px solid #f8f8f8;
}
}
.m-tit {
margin-bottom: 20rpx;
}
.let {
flex: 1;
overflow: hidden;
display: flex;
align-items: center;
}
.img {
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
width: 40rpx;
height: 40rpx;
}
image {
// width: 100%;
height: 40rpx;
}
}
.price {
display: flex;
align-items: center;
border-bottom: 1px solid #f8f8f8;
}
</style>
<style>
page {
background-color: white;
}
</style>

@ -0,0 +1,174 @@
<template>
<view class="page page-box">
<view class="banner_inner"></view>
<view class="head">
<u-icon name="checkmark-circle-fill" size="56" color="#fff"></u-icon>
<view class="text">支付成功</view>
<view class="price"><text class="iconPrice"></text>{{ `${price}` }}</view>
</view>
<view class="icon">
<image class="logo" :src="info.logo_url"></image>
</view>
<!-- <view class="goodsList">
<view class="goodsTitle">推荐商品</view>
<twoGoods :list="goodsList"></twoGoods>
</view> -->
</view>
</template>
<script>
import twoGoods from '../compontents/twoGoods.vue';
export default {
components: {
twoGoods
},
data() {
return {
price: "",
info: {},
goodsList: []
}
},
onLoad({
price,
mch_id
}) {
this.price = price
this.mch_id = mch_id
this.init()
this.fetchData()
},
methods: {
async init() {
const res = await this.$api.inPersonToPay.storeInfo({
mch_id: this.mch_id,
})
this.info = {
...res.data.store,
...res.data.shop
}
},
async fetchData(keyword = "1") {
const res = await this.$api.goods.searchGoodsList({
keywords: keyword
})
this.goodsList = res.data.rows
console.log(res);
},
}
}
</script>
<style lang="scss" scoped>
.page {
padding: 20rpx;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
color: hsla(0, 0%, 100%, .9);
overflow: auto;
.icon {
margin-bottom: 24rpx;
}
}
.head {
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
top: 60rpx;
right: 0;
left: 0;
.text {
margin: 24rpx 0;
}
.price {
font-weight: 600;
font-size: 60rpx;
font-family: PingFang SC, Arial, 'Helvetica Neue', Helvetica, sans-serif;
letter-spacing: 4rpx;
.iconPrice {
font-size: 32rpx;
}
}
}
.banner_inner {
content: "";
display: block;
position: absolute;
height: 40vh;
top: 0;
width: 150%;
min-width: 780px;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(60deg, #18b566 0%, #71d5a1 100%);
border-radius: 0 0 65% 65%;
z-index: 0;
overflow: hidden;
}
.logo {
position: absolute;
top: 40vh;
left: 0;
right: 0;
transform: translateY(-50%);
height: 180rpx;
width: 180rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
border-radius: 60rpx;
box-shadow: 0 0.3125rem 1.25rem 0px rgba(166, 199, 251, 0.12);
}
.goodsTitle {
position: relative;
color: #000;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
&::after {
content: "";
height: 6px;
left: 0;
right: 0;
bottom: 6px;
position: absolute;
background: linear-gradient(60deg, #ff5740 0%, rgb(252, 143, 76) 100%);
width: 100rpx;
margin: 0 auto;
opacity: 0.4;
}
}
.goodsList {
margin-top: calc(40vh + 100rpx);
left: 0;
right: 0;
}
</style>
<style>
page {
background-color: #f8f8f8;
}
</style>

@ -0,0 +1,38 @@
<template>
<view class="content page-box">
<view>
<view style="display: flex;align-items: center;">
<view style="margin-right: 20rpx;">
账号
</view>
<view style="flex:1;margin-bottom: 20rpx;background-color: ghostwhite;padding: 20rpx 24rpx;border-radius: 10px;">
<u--input focus :customStyle="{}" placeholder="账号名/手机号" border="none" clearable></u--input>
</view>
</view>
<u-button @click="toUrl('/subPackages/login/forgotPassword/nuxt')" shape="circle" type="error"
text="下一步"></u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
};
},
onLoad() {
},
methods: {},
};
</script>
<style lang="scss" scoped>
.content {
padding: 20rpx;
background-color: white;
}
</style>

@ -0,0 +1,321 @@
<template>
<view class="page page-box">
<view class="saveThePhoneNumber">
<!-- <view class="mobilePhone">
<u-input border="none" placeholder="请输入手机号" v-model="form.phone">
<template #prefix>
<span style="margin-right: 10rpx; font-size: 24rpx;color: #a1a1a1;font-weight: 600;">
+86
</span>
</template>
</u-input>
</view> -->
<view class="tip">
向您绑定的手机号 199xxxx1234 发送验证码
</view>
<view class="input">
<view class="verificationCode">
<u-input v-model="form.password" border="none" placeholder="请输入验证码"></u-input>
</view>
<view class="toObtain">
<u-code keepRunning ref="uCode" @change="codeChange" seconds="60" changeText="X秒重新获取"></u-code>
<u-button @click="getCode" :text="tips" :disabled="!['获取验证码', '重新获取'].includes(tips)"
:color="['获取验证码', '重新获取'].includes(tips) ? 'rgb(247, 56, 38)' : '#cccccc'"></u-button>
</view>
</view>
<view class="mobilePhone">
<u-input border="none" placeholder="请输入新密码" v-model="form.newPassword">
<template #prefix>
<span style="margin-right: 10rpx; font-size: 24rpx;color: #a1a1a1;font-weight: 600;">
新密码
</span>
</template>
</u-input>
</view>
<view class="mobilePhone">
<u-input border="none" placeholder="请重新输入密码" v-model="form.phone">
<template #prefix>
<span style="margin-right: 10rpx; font-size: 24rpx;color: #a1a1a1;font-weight: 600;">
确认密码
</span>
</template>
</u-input>
</view>
<view style="width: 100%;margin: 20rpx auto;border-radius: 18rpx;font-weight: 600;overflow: hidden;">
<u-button color="rgb(247, 56, 38)" text="修改密码" @click="saveThePhoneNumber"></u-button>
</view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
tips: "",
form: {
password: "",
newPassword: "",
code: ""
}
};
},
onLoad() { },
onShow() {
},
methods: {
codeChange(text) {
this.tips = text;
},
/**
* 获取验证码并发送给指定手机号
* @returns {void}
*/
async getCode() {
if (!/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(this.form.phone)) {
return uni.$u.toast('请检查手机号格式');
}
if (this.form.newPassword != this.form.password) {
return uni.$u.toast('两次输入密码不一致');
}
if (this.$refs.uCode.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码'
});
// setTimeout(() => {
// uni.hideLoading();
// // this.start()
// uni.$u.toast('');
// //
// this.$refs.uCode.start();
// }, 2000);
const res = await this.$api.user.send({
mobile: this.form.phone
})
if (res.code === 0) {
this.$refs.uCode.start();
} else {
uni.$u.toast(res.msg);
}
uni.hideLoading();
} else {
uni.$u.toast('倒计时结束后再发送');
}
},
/**
* 保存手机号码并进行验证
* @returns {void}
*/
async saveThePhoneNumber() {
// if (!/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(this.form.phone)) {
// return uni.$u.toast('');
// }
if (this.form.code.trim() === "") {
return uni.$u.toast('请输入验证码');
}
//
const res = await this.$api.user.bind_merge({
mobile: this.form.phone,
sms_code: this.form.code
})
if (res.code === 0) {
uni.$u.toast('绑定成功');
} else {
uni.$u.toast(res.msg);
}
this.getData();
}
},
computed: {},
watch: {},
onReachBottom() { },
onPullDownRefresh() {
console.log("加载");
setTimeout(() => {
uni.stopPullDownRefresh();
}, 200);
},
}
</script>
<style lang="scss" scoped>
.page {
position: relative;
min-height: 100%;
background: #f2f2f2;
padding: 24rpx 0 100rpx;
padding: 24rpx 0 calc(constant(safe-area-inset-bottom) + 100rpx);
padding: 24rpx 0 calc(env(safe-area-inset-bottom) + 100rpx);
box-sizing: border-box;
}
.m-box {
margin-bottom: 24rpx;
padding: 0 24rpx;
background: #fff;
width: 100%;
}
.payList {
.item {
display: flex;
align-items: center;
height: 120rpx;
border-top: 1px solid #f8f8f8;
}
.let {
flex: 1;
overflow: hidden;
display: flex;
align-items: center;
}
.img {
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
width: 60rpx;
height: 60rpx;
}
image {
// width: 100%;
height: 50rpx;
}
}
.tip {
font-size: 22rpx;
color: #a0a0a0;
font-weight: bold;
margin-bottom: 14rpx;
margin-left: 10rpx;
}
.m-tit {
display: flex;
align-items: center;
height: 100rpx;
font-size: 32rpx;
color: #262626;
font-weight: bold;
}
.payTime {
display: flex;
align-items: center;
justify-content: center;
padding-top: 50rpx;
font-size: 24rpx;
color: #8c8c8c;
}
.price {
display: flex;
justify-content: center;
align-items: flex-end;
padding: 10rpx 0 30rpx;
color: #262626;
font-size: 80rpx;
line-height: 1;
font-weight: bold;
&:before {
content: "¥";
font-size: 48rpx;
}
}
::v-deep {
.u-count-down__text {
padding-left: 8rpx;
color: #8c8c8c;
font-size: 24rpx;
}
}
.btn {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: 12rpx 36rpx 12rpx;
padding: 12rpx 36rpx calc(constant(safe-area-inset-bottom) + 12rpx);
padding: 12rpx 36rpx calc(env(safe-area-inset-bottom) + 12rpx);
background: #fff;
view {
display: flex;
align-items: center;
justify-content: center;
height: 76rpx;
background: linear-gradient(90deg, #f22407 0%, #f84d17 100%);
border-radius: 18rpx;
font-size: 26rpx;
font-weight: bold;
color: #fff;
}
}
.saveThePhoneNumber {
padding: 0 20rpx;
.mobilePhone {
background-color: white;
margin-bottom: 22rpx;
border-radius: 18rpx;
padding: 18rpx;
}
.input {
display: flex;
margin-bottom: 20rpx;
align-items: center;
.verificationCode {
border-radius: 18rpx;
overflow: hidden;
background-color: white;
flex: 1;
display: flex;
align-items: center;
padding: 18rpx;
}
.toObtain {
width: 7em;
margin-left: 22rpx;
border-radius: 18rpx;
overflow: hidden;
height: 100%;
}
}
}
</style>

@ -0,0 +1,27 @@
<template>
<view>
<logins :type="1" :wcCode="wcCode"></logins>
</view>
</template>
<script>
import logins from '@/components/login/logins.vue'
export default {
components: {
logins
},
data() {
return {
wcCode: undefined
};
},
onLoad({ wxcode } = {}) {
if (wxcode) {
this.wcCode = wxcode
}
},
methods: {},
};
</script>
<style></style>

@ -0,0 +1,174 @@
<template>
<view class="page-box">
<u--form :model="form" ref="uForm" :rules="rules" labelWidth="180rpx">
<u-form-item label="手机号:" prop="mobile">
<u--input v-model="form.mobile" border="bottom" placeholder="请输入手机号"></u--input>
</u-form-item>
<u-form-item label="验证码:" prop="code">
<u--input v-model="form.code" border="bottom" placeholder="请填写验证码">
<!-- <template slot="suffix">
<u-code :seconds="60" @end="codeEnd" @start="codeStart" @change="codeChange" ref="uCode"></u-code>
<template v-if="is_send_sms">
<u-button type="primary" text="获取验证码" size="mini" @click="sendSms"></u-button>
</template>
<template v-else>
<u-button type="primary" :text="sms_info" size="mini"></u-button>
</template>
</template> -->
</u--input>
<view>
<u-code :seconds="60" @end="codeEnd" @start="codeStart" @change="codeChange" ref="uCode"></u-code>
<template v-if="is_send_sms">
<u-button type="primary" text="获取验证码" size="mini" @click="sendSms"></u-button>
</template>
<template v-else>
<u-button type="primary" :text="sms_info" size="mini"></u-button>
</template>
</view>
</u-form-item>
<u-form-item label="邀请码:" prop="parent_id">
<u--input v-model="form.parent_id" :disabled="distributor_id > 0" border="bottom" placeholder="请输入邀请码"></u--input>
</u-form-item>
<u-form-item label="密码:" prop="pwd">
<u--input v-model="form.pwd" :password="true" border="bottom" placeholder="请输入密码"></u--input>
</u-form-item>
<u-form-item label="确认密码:" prop="pwd_1">
<u--input v-model="form.pwd_1" :password="true" border="bottom" placeholder="请输入密码"></u--input>
</u-form-item>
</u--form>
<view style="margin-top: 40rpx;">
<u-button shape="circle" type="success" color="#fa3534" size="large" text="登录" @click="pwdSubmit"></u-button>
</view>
</view>
</template>
<script>
export default {
components: { },
data() {
return {
is_eye: 0,
sms_info: '60秒重新获取',
is_send_sms: 1,
distributor_id: '',
form: {
code: '',
mobile: '',
pwd: '',
pwd_1: '',
parent_id: ''
},
rules: {
mobile: [
{
required: true,
message: '请输入账号',
trigger: ['blur', 'change']
}
],
code: [
{
required: true,
message: '请填写6位验证码',
len: 6,
trigger: ['blur', 'change']
}
],
pwd: [
{
required: true,
message: '请输入至少6位密码',
min: 6,
trigger: ['blur', 'change']
}
],
pwd_1: [
{
required: true,
message: '请输入至少6位密码',
min: 6,
trigger: ['blur', 'change']
}
]
}
}
},
computed: {
},
onShow() {
let distributor_id = uni.getStorageSync('distributor_id')
if (distributor_id) {
this.distributor_id = distributor_id
this.form.parent_id = distributor_id.padStart(7, '0');
}
},
methods: {
codeEnd() {
console.log('codeEnd')
this.is_send_sms = 1
},
codeStart() {
console.log('codeStart')
this.is_send_sms = 0
},
codeChange(e) {
this.sms_info = e
},
async sendSms() {
uni.showLoading({
title: '发送中'
})
const res = await this.$api.common.sendSms({
mobile: this.form.mobile
});
uni.hideLoading()
if (res.code) {
uni.showToast({
icon: 'none',
title: res.msg
})
} else {
this.is_send_sms = 0
this.$nextTick(() => {
this.$refs.uCode.start();
})
}
},
pwdSubmit() {
this.$refs.uForm.validate().then(async (res) => {
uni.showLoading({
title: '注册中'
})
const res1 = await this.$api.user.regPhone(this.form);
uni.hideLoading()
if (res1.code) {
uni.showToast({
icon: 'none',
title: res1.msg
})
} else {
uni.showToast({
icon: 'none',
title: '注册成功'
})
setTimeout(() => {
this.$utils.toUrl("/subPackages/login/login/index", "redirectTo")
}, 2000)
}
}).catch(errors => {})
}
}
}
</script>
<style lang="less" scoped>
.page-box {
background: #FFF;
padding: 30rpx;
min-height: 100vh;
}
</style>

@ -0,0 +1,161 @@
<template>
<view class="page-box">
<u--form :model="form" ref="uForm" :rules="rules" labelWidth="180rpx">
<u-form-item label="手机号:" prop="mobile">
<u--input v-model="form.mobile" border="bottom" placeholder="请输入手机号"></u--input>
</u-form-item>
<u-form-item label="验证码:" prop="code">
<u--input v-model="form.code" border="bottom" placeholder="请填写验证码">
<!-- <template slot="suffix">
<u-code :seconds="60" @end="codeEnd" @start="codeStart" @change="codeChange" ref="uCode"></u-code>
<template v-if="is_send_sms">
<u-button type="primary" text="获取验证码" size="mini" @click="sendSms"></u-button>
</template>
<template v-else>
<u-button type="primary" :text="sms_info" size="mini"></u-button>
</template>
</template> -->
</u--input>
<view>
<u-code :seconds="60" @end="codeEnd" @start="codeStart" @change="codeChange" ref="uCode"></u-code>
<template v-if="is_send_sms">
<u-button type="primary" text="获取验证码" size="mini" @click="sendSms"></u-button>
</template>
<template v-else>
<u-button type="primary" :text="sms_info" size="mini"></u-button>
</template>
</view>
</u-form-item>
<u-form-item label="密码:" prop="pwd">
<u--input v-model="form.pwd" :password="true" border="bottom" placeholder="请输入密码"></u--input>
</u-form-item>
<u-form-item label="确认密码:" prop="pwd_1">
<u--input v-model="form.pwd_1" :password="true" border="bottom" placeholder="请输入密码"></u--input>
</u-form-item>
</u--form>
<view style="margin-top: 40rpx;">
<u-button shape="circle" type="success" color="#fa3534" size="large" text="登录" @click="pwdSubmit"></u-button>
</view>
</view>
</template>
<script>
export default {
components: { },
data() {
return {
is_eye: 0,
sms_info: '60秒重新获取',
is_send_sms: 1,
form: {
code: '',
mobile: '',
pwd: '',
pwd_1: '',
},
rules: {
mobile: [
{
required: true,
message: '请输入账号',
trigger: ['blur', 'change']
}
],
code: [
{
required: true,
message: '请填写6位验证码',
len: 6,
trigger: ['blur', 'change']
}
],
pwd: [
{
required: true,
message: '请输入至少6位密码',
min: 6,
trigger: ['blur', 'change']
}
],
pwd_1: [
{
required: true,
message: '请输入至少6位密码',
min: 6,
trigger: ['blur', 'change']
}
]
}
}
},
computed: {
},
methods: {
codeEnd() {
console.log('codeEnd')
this.is_send_sms = 1
},
codeStart() {
console.log('codeStart')
this.is_send_sms = 0
},
codeChange(e) {
this.sms_info = e
},
async sendSms() {
uni.showLoading({
title: '发送中'
})
const res = await this.$api.common.sendSms({
mobile: this.form.mobile
});
uni.hideLoading()
if (res.code) {
uni.showToast({
icon: 'none',
title: res.msg
})
} else {
this.is_send_sms = 0
this.$nextTick(() => {
this.$refs.uCode.start();
})
}
},
pwdSubmit() {
this.$refs.uForm.validate().then(async (res) => {
uni.showLoading({
title: '重置中'
})
const res1 = await this.$api.user.regPhone(this.form);
uni.hideLoading()
if (res1.code) {
uni.showToast({
icon: 'none',
title: res1.msg
})
} else {
uni.showToast({
icon: 'none',
title: '重置成功'
})
setTimeout(() => {
this.$utils.toUrl("/subPackages/login/login/index", "redirectTo")
}, 2000)
}
}).catch(errors => {})
}
}
}
</script>
<style lang="less" scoped>
.page-box {
background: #FFF;
padding: 30rpx;
min-height: 100vh;
}
</style>

@ -0,0 +1,78 @@
/**
* uni-app
*
* uni-app https://ext.dcloud.net.cn使
* 使scss使 import 便App
*
*/
/**
* App使
*
* 使scss scss 使 import
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//
$uni-text-color-inverse:#fff;//
$uni-text-color-grey:#999;//
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; //
/* 文章场景相关 */
$uni-color-title: #2C405A; //
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; //
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; //
$uni-font-size-paragraph:15px;
@import '@/uni_modules/uview-ui/theme.scss';

File diff suppressed because it is too large Load Diff

@ -0,0 +1,203 @@
## 1.9.5.72023-07-27
- fix: 去掉多余的方法
- chore: 更新文档,增加自定义字体说明
## 1.9.5.62023-07-21
- feat: 有限的支持富文本
- feat: H5和APP 增加 `hidpi` prop主要用于大尺寸无法生成图片时用
- fix: 修复 钉钉小程序 缺少 `measureText` 方法
- chore: 由于微信小程序 pc 端的 canvas 2d 时不时抽风故不使用canvas 2d
## 1.9.5.52023-06-27
- fix: 修复把`emoji`表情字符拆分成多个字符的情况
## 1.9.5.42023-06-05
- fix: 修复因`canvasToTempFilePathSync`监听导致重复调用
## 1.9.5.32023-05-23
- fix: 因isPc错写成了isPC导致小程序PC不能生成图片
## 1.9.5.22023-05-22
- feat: 删除多余文件
## 1.9.5.12023-05-22
- fix: 修复 文字行数与`line-clamp`相同但不满一行时也加了省略号的问题
## 1.9.52023-05-14
- feat: 增加 `text-indent``calc` 方法
- feat: 优化 布局时间
## 1.9.4.42023-04-15
- fix: 修复无法匹配负值
- fix: 修复 Nvue IOS getImageInfo `useCORS` 为 undefined
## 1.9.4.32023-04-01
- feat: 增加支持文字描边 `text-stroke: '5rpx #fff'`
## 1.9.4.22023-03-30
- fix: 修复 支付宝小程序 isPC 在手机也为true的问题
- feat: 由 微信开发工具 3060 版 无法获取图片尺寸,现 微信开发工具 3220 版 修复该问题,故还原上一版的获取图片方式。
## 1.9.4.12023-03-28
- fix: 修复固定高度不正确问题
## 1.9.42023-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.52022-06-29
- feat: justifyContent 增加 `space-around`、`space-between`
- feat: canvas 2d 也使用`getImageInfo`
- fix: 修复 `text``text-decoration`错位
## 1.9.3.42022-06-20
- fix: 修复 因创建节点速度问题导致顺序出错。
- fix: 修复 微信小程序 PC 无法显示本地图片
- fix: 修复 flex-box 对齐问题
- feat: 增加 `text-shadow`
- feat: 重写 `text` 对齐方式
- chore: 更新文档
## 1.9.3.32022-06-17
- fix: 修复 支付宝小程序 canvas 2d 存在ctx.draw问题导致报错
- fix: 修复 支付宝小程序 toDataURL 存在权限问题改用 `toTempFilePath`
- fix: 修复 支付宝小程序 image size 问题导致 `objectFit` 无效
## 1.9.3.22022-06-14
- fix: 修复 image 设置背景色不生效问题
- fix: 修复 nvue 环境判断缺少参数问题
## 1.9.3.12022-06-14
- fix: 修复 bottom 定位不对问题
- fix: 修复 因小数导致计算出错换行问题
- feat: 增加 `useCORS` h5端图片跨域 在设置请求头无效果后试一下设置这个值
- chore: 更新文档
## 1.9.32022-06-13
- feat: 增加 `zIndex`
- feat: 增加 `flex-box` 该功能处于原始阶段,非常简陋。
- tips: QQ小程序 vue3 不支持, 为 uni 官方BUG
## 1.9.2.92022-06-10
- fix: 修复`text-align`及`margin`居中问题
## 1.9.2.82022-06-10
- fix: 修复 Nvue `canvasToTempFilePathSync` 不生效问题
## 1.9.2.72022-06-10
- fix: 修复 margin及padding的bug
- fix: 修复 Nvue `isCanvasToTempFilePath` 不生效问题
## 1.9.2.62022-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.52022-06-09
- chore: 更变获取父级宽度的设定
- chore: `pathType` 在canvas 2d 默认为 `url`
## 1.9.2.42022-06-08
- fix: 修复 `pathType` 不生效问题
## 1.9.2.32022-06-08
- fix: 修复 `canvasToTempFilePath` 漏写 `success` 参数
## 1.9.2.22022-06-07
- chore: 更新文档
## 1.9.2.12022-06-07
- fix: 修复 vue3 赋值给this再传入导致image无法绘制
- fix: 修复 `canvasToTempFilePathSync` 时机问题
- feat: canvas 2d 更改图片生成方式 `toDataURL`
## 1.9.22022-05-30
- fix: 修复 `canvasToTempFilePathSync` 在 vue3 下只生成一次
## 1.9.1.72022-05-28
- fix: 修复 `qrcode`显示不全问题
## 1.9.1.62022-05-28
- fix: 修复 `canvasToTempFilePathSync` 会重复多次问题
- fix: 修复 `view` css `backgroundImage` 图片下载失败导致 子节点不渲染
## 1.9.1.52022-05-27
- fix: 修正支付宝小程序 canvas 2d版本号 2.7.15
## 1.9.1.42022-05-22
- fix: 修复字节小程序无法使用xml方式
- fix: 修复字节小程序无法使用base64(非2D情况下工具上无法显示)
- fix: 修复支付宝小程序 `canvasToTempFilePath` 报错
## 1.9.1.32022-04-29
- fix: 修复vue3打包后uni对象为空后的报错
## 1.9.1.22022-04-25
- fix: 删除多余文件
## 1.9.1.12022-04-25
- fix: 修复图片不显示问题
## 1.9.12022-04-12
- fix: 因四舍五入导致有些机型错位
- fix: 修复无views报错
- chore: nvue下因ios无法读取插件内static文件改由下载方式
## 1.9.02022-03-20
- fix: 因无法固定尺寸导致生成图片不全
- fix: 特定情况下text判断无效
- chore: 本地化APP Nvue webview
## 1.8.92022-02-20
- fix: 修复 小程序下载最多10次并发的问题
- fix: 修复 APP端无法获取本地图片
- fix: 修复 APP Nvue端不执行问题
- chore: 增加图片缓存机制
## 1.8.8.82022-01-27
- fix: 修复 主动调用尺寸问题
## 1.8.8.62022-01-26
- fix: 修复 nvue 下无宽度时获取父级宽度
- fix: 修复 ios app 无法渲染问题
## 1.8.82022-01-23
- fix: 修复 主动调用时无节点问题
- fix: 修复 `box-shadow` 颜色问题
- fix: 修复 `transform:rotate` 角度位置问题
- feat: 增加 `overflow:hidden`
## 1.8.72022-01-07
- fix: 修复 image 方向为 `right` 时原始宽高问题
- feat: 支持 view 设置背景图 `background-image: url(xxx)`
- chore: 去掉可选链
## 1.8.62021-11-28
- feat: 支持`view`对`inline-block`的子集使用`text-align`
## 1.8.5.52021-08-17
- chore: 更新文档,删除 replace
- fix: 修复 text 值为 number时报错
## 1.8.5.42021-08-16
- fix: 字节小程序兼容
## 1.8.5.32021-08-15
- fix: 修复线性渐变与css现实效果不一致的问题
- chore: 更新文档
## 1.8.5.22021-08-13
- chore: 增加`background-image`、`background-repeat` 能力,主要用于背景纹理的绘制,并不是代替`image`。例如:大面积的重复平铺的水印
- 注意这个功能H5暂时无法使用因为[官方的API有BUG](https://ask.dcloud.net.cn/question/128793),待官方修复!!!
## 1.8.5.12021-08-10
- fix: 修复因`margin`报错问题
## 1.8.52021-08-09
- chore: 增加margin支持`auto`,以达到居中效果
## 1.8.42021-08-06
- chore: 增加判断缓存文件条件
- fix: 修复css 多余空格报错问题
## 1.8.32021-08-04
- tips: 1.6.x 以下的版本升级到1.8.x后要为每个元素都加上定位position: 'absolute'
- fix: 修复只有一个view子元素时不计算高度的问题
## 1.8.22021-08-03
- fix: 修复 path-type 为 `url` 无效问题
- fix: 修复 qrcode `text` 为空时报错问题
- fix: 修复 image `src` 动态设置时不生效问题
- feat: 增加 css 属性 `min-width` `max-width`
## 1.8.12021-08-02
- fix: 修复无法加载本地图片
## 1.8.02021-08-02
- chore 文档更新
- 使用旧版的同学不要升级!
## 1.8.0-beta2021-07-30
- ## 全新布局方式 不兼容旧版!
- chore: 布局方式变更
- tips: 微信canvas 2d 不支持真机调试
## 1.6.62021-07-09
- chore: 统一命名规范,无须主动引入组件
## 1.6.52021-06-08
- chore: 去掉console
## 1.6.42021-06-07
- fix: 修复 数字 为纯字符串时不转换的BUG
## 1.6.32021-06-06
- fix: 修复 PC 端放大的BUG
## 1.6.22021-05-31
- fix: 修复 报`adaptor is not a function`错误
- fix: 修复 text 多行高度
- fix: 优化 默认文字的基准线
- feat: `@progress`事件,监听绘制进度
## 1.6.12021-02-28
- 删除多余节点
## 1.6.02021-02-26
- 调整为uni_modules目录规范
- 修复transform的rotate不能为负数问题
- 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`

@ -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,29 @@
<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: {
uid: String,
css: [String, Object],
text: [String, Number],
replace: Object,
},
data() {
return {
type: 'text',
el: {
css: {},
text: null
},
}
}
}
</script>
<style>
</style>

@ -0,0 +1,30 @@
<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,
css: [String, Object],
},
data() {
return {
type: 'view',
el: {
css: {},
views:[]
},
}
},
mounted() {
}
}
</script>
<style>
</style>

@ -0,0 +1,445 @@
<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 :canvas-id="canvasId" :style="size" :id="canvasId"
: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,
};
},
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
})
},
// #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)
}
// 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)));
});
}
return Promise.resolve({
ctx,
draw: this.painter,
node: this.node
});
},
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>

@ -0,0 +1,190 @@
// #ifdef APP-NVUE
import { sleep, getImageInfo, isBase64, useNvue, 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(''))
}
}
);
})
},
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
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) {
args.fail({
error: e
})
}
}
}
}
// #endif

File diff suppressed because one or more lines are too long

@ -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
}
}

@ -0,0 +1,363 @@
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()
const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
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

File diff suppressed because one or more lines are too long

@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,92 @@
{
"id": "lime-painter",
"displayName": "海报画板",
"version": "1.9.5.7",
"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"
},
"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"
}

@ -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;

@ -0,0 +1,963 @@
# Painter 画板 测试版
> uniapp 海报画板,更优雅的海报生成方案
> [查看更多 站点 1](https://limeui.qcoon.cn/#/painter)
> [查看更多 站点 2](http://liangei.gitee.io/limeui/#/painter)
> Q 群1169785031
## 平台兼容
| 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、imageqrcode 共同构建一颗完整的节点树
- 在 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://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

@ -0,0 +1,70 @@
## 1.8.42023-11-15
- 新增 uni-popup 支持uni-app-x 注意暂时仅支持 `maskClick` `@open` `@close`
## 1.8.32023-04-17
- 修复 uni-popup 重复打开时的 bug
## 1.8.22023-02-02
- uni-popup-dialog 组件新增 inputType 属性
## 1.8.12022-12-01
- 修复 nvue 下 v-show 报错
## 1.8.02022-11-29
- 优化 主题样式
## 1.7.92022-04-02
- 修复 弹出层内部无法滚动的bug
## 1.7.82022-03-28
- 修复 小程序中高度错误的bug
## 1.7.72022-03-17
- 修复 快速调用open出现问题的Bug
## 1.7.62022-02-14
- 修复 safeArea 属性不能设置为false的bug
## 1.7.52022-01-19
- 修复 isMaskClick 失效的bug
## 1.7.42022-01-19
- 新增 cancelText \ confirmText 属性 ,可自定义文本
- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
## 1.7.32022-01-13
- 修复 设置 safeArea 属性不生效的bug
## 1.7.22021-11-26
- 优化 组件示例
## 1.7.12021-11-26
- 修复 vuedoc 文字错误
## 1.7.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
## 1.6.22021-08-24
- 新增 支持国际化
## 1.6.12021-07-30
- 优化 vue3下事件警告的问题
## 1.6.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.5.02021-06-23
- 新增 mask-click 遮罩层点击事件
## 1.4.52021-06-22
- 修复 nvue 平台中间弹出后点击内容再点击遮罩无法关闭的Bug
## 1.4.42021-06-18
- 修复 H5平台中间弹出后点击内容再点击遮罩无法关闭的Bug
## 1.4.32021-06-08
- 修复 错误的 watch 字段
- 修复 safeArea 属性不生效的问题
- 修复 点击内容再点击遮罩无法关闭的Bug
## 1.4.22021-05-12
- 新增 组件示例地址
## 1.4.12021-04-29
- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
## 1.4.0 2021-04-29
- 新增 type 属性的 left\right 值,支持左右弹出
- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
- 新增 safeArea 属性,是否适配底部安全区
- 修复 App\h5\微信小程序底部安全区占位不对的Bug
- 修复 App 端弹出等待的Bug
- 优化 提升低配设备性能,优化动画卡顿问题
- 优化 更简单的组件自定义方式
## 1.2.92021-02-05
- 优化 组件引用关系通过uni_modules引用组件
## 1.2.82021-02-05
- 调整为uni_modules目录规范
## 1.2.72021-02-05
- 调整为uni_modules目录规范
- 新增 支持 PC 端
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

@ -0,0 +1,275 @@
<template>
<view class="uni-popup-dialog">
<view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
</view>
<view v-if="mode === 'base'" class="uni-dialog-content">
<slot>
<text class="uni-dialog-content-text">{{content}}</text>
</slot>
</view>
<view v-else class="uni-dialog-content">
<slot>
<input class="uni-dialog-input" v-model="val" :type="inputType" :placeholder="placeholderText" :focus="focus" >
</slot>
</view>
<view class="uni-dialog-button-group">
<view class="uni-dialog-button" @click="closeDialog">
<text class="uni-dialog-button-text">{{closeText}}</text>
</view>
<view class="uni-dialog-button uni-border-left" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
</view>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
/**
* PopUp 弹出层-对话框样式
* @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} value input 模式下的默认值
* @property {String} placeholder input 模式下输入提示
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} mode = [base|input] 模式
* @value base 基础对话框
* @value input 可输入对话框
* @property {String} content 对话框内容
* @property {Boolean} beforeClose 是否拦截取消事件
* @event {Function} confirm 点击确认按钮触发
* @event {Function} close 点击取消按钮触发
*/
export default {
name: "uniPopupDialog",
mixins: [popup],
emits:['confirm','close'],
props: {
inputType:{
type: String,
default: 'text'
},
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'error'
},
mode: {
type: String,
default: 'base'
},
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
},
cancelText:{
type: String,
default: ''
},
confirmText:{
type: String,
default: ''
}
},
data() {
return {
dialogType: 'error',
focus: false,
val: ""
}
},
computed: {
okText() {
return this.confirmText || t("uni-popup.ok")
},
closeText() {
return this.cancelText || t("uni-popup.cancel")
},
placeholderText() {
return this.placeholder || t("uni-popup.placeholder")
},
titleText() {
return this.title || t("uni-popup.title")
}
},
watch: {
type(val) {
this.dialogType = val
},
mode(val) {
if (val === 'input') {
this.dialogType = 'info'
}
},
value(val) {
this.val = val
}
},
created() {
//
this.popup.disableMask()
// this.popup.closeMask()
if (this.mode === 'input') {
this.dialogType = 'info'
this.val = this.value
} else {
this.dialogType = this.type
}
},
mounted() {
this.focus = true
},
methods: {
/**
* 点击确认按钮
*/
onOk() {
if (this.mode === 'input'){
this.$emit('confirm', this.val)
}else{
this.$emit('confirm')
}
if(this.beforeClose) return
this.popup.close()
},
/**
* 点击取消按钮
*/
closeDialog() {
this.$emit('close')
if(this.beforeClose) return
this.popup.close()
},
close(){
this.popup.close()
}
}
}
</script>
<style lang="scss" >
.uni-popup-dialog {
width: 300px;
border-radius: 11px;
background-color: #fff;
}
.uni-dialog-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 25px;
}
.uni-dialog-title-text {
font-size: 16px;
font-weight: 500;
}
.uni-dialog-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
padding: 20px;
}
.uni-dialog-content-text {
font-size: 14px;
color: #6C6C6C;
}
.uni-dialog-button-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
border-top-color: #f5f5f5;
border-top-style: solid;
border-top-width: 1px;
}
.uni-dialog-button {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
}
.uni-border-left {
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}
.uni-dialog-button-text {
font-size: 16px;
color: #333;
}
.uni-button-color {
color: #007aff;
}
.uni-dialog-input {
flex: 1;
font-size: 14px;
border: 1px #eee solid;
height: 40px;
padding: 0 10px;
border-radius: 5px;
color: #555;
}
.uni-popup__success {
color: #4cd964;
}
.uni-popup__warn {
color: #f0ad4e;
}
.uni-popup__error {
color: #dd524d;
}
.uni-popup__info {
color: #909399;
}
</style>

@ -0,0 +1,143 @@
<template>
<view class="uni-popup-message">
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
<slot>
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
</slot>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
/**
* PopUp 弹出层-消息提示
* @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} message 消息提示文字
* @property {String} duration 显示时间设置为 0 则不会自动关闭
*/
export default {
name: 'uniPopupMessage',
mixins:[popup],
props: {
/**
* 主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'success'
},
/**
* 消息文字
*/
message: {
type: String,
default: ''
},
/**
* 显示时间设置为 0 则不会自动关闭
*/
duration: {
type: Number,
default: 3000
},
maskShow:{
type:Boolean,
default:false
}
},
data() {
return {}
},
created() {
this.popup.maskShow = this.maskShow
this.popup.messageChild = this
},
methods: {
timerClose(){
if(this.duration === 0) return
clearTimeout(this.timer)
this.timer = setTimeout(()=>{
this.popup.close()
},this.duration)
}
}
}
</script>
<style lang="scss" >
.uni-popup-message {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
}
.uni-popup-message__box {
background-color: #e1f3d8;
padding: 10px 15px;
border-color: #eee;
border-style: solid;
border-width: 1px;
flex: 1;
}
@media screen and (min-width: 500px) {
.fixforpc-width {
margin-top: 20px;
border-radius: 4px;
flex: none;
min-width: 380px;
/* #ifndef APP-NVUE */
max-width: 50%;
/* #endif */
/* #ifdef APP-NVUE */
max-width: 500px;
/* #endif */
}
}
.uni-popup-message-text {
font-size: 14px;
padding: 0;
}
.uni-popup__success {
background-color: #e1f3d8;
}
.uni-popup__success-text {
color: #67C23A;
}
.uni-popup__warn {
background-color: #faecd8;
}
.uni-popup__warn-text {
color: #E6A23C;
}
.uni-popup__error {
background-color: #fde2e2;
}
.uni-popup__error-text {
color: #F56C6C;
}
.uni-popup__info {
background-color: #F2F6FC;
}
.uni-popup__info-text {
color: #909399;
}
</style>

@ -0,0 +1,187 @@
<template>
<view class="uni-popup-share">
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{item.text}}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">{{cancelText}}</button>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
export default {
name: 'UniPopupShare',
mixins:[popup],
emits:['select'],
props: {
title: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
bottomData: [{
text: '微信',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
name: 'wx'
},
{
text: '支付宝',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
name: 'wx'
},
{
text: 'QQ',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
name: 'qq'
},
{
text: '新浪',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
name: 'sina'
},
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
// name: 'copy'
// },
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
// name: 'more'
// }
]
}
},
created() {},
computed: {
cancelText() {
return t("uni-popup.cancel")
},
shareTitleText() {
return this.title || t("uni-popup.shareTitle")
}
},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit('select', {
item,
index
})
this.close()
},
/**
* 关闭窗口
*/
close() {
if(this.beforeClose) return
this.popup.close()
}
}
}
</script>
<style lang="scss" >
.uni-popup-share {
background-color: #fff;
border-top-left-radius: 11px;
border-top-right-radius: 11px;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 90px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 30px;
height: 30px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3B4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "cancel",
"uni-popup.ok": "ok",
"uni-popup.placeholder": "pleace enter",
"uni-popup.title": "Hint",
"uni-popup.shareTitle": "Share to"
}

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "确定",
"uni-popup.placeholder": "请输入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "確定",
"uni-popup.placeholder": "請輸入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif

@ -0,0 +1,26 @@
export default {
data() {
return {
}
},
created(){
this.popup = this.getParent()
},
methods:{
/**
* 获取父元素实例
*/
getParent(name = 'uniPopup') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
}
}

@ -0,0 +1,90 @@
<template>
<view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask">
<view @click.stop>
<slot></slot>
</view>
</view>
</template>
<script>
type CloseCallBack = ()=> void;
let closeCallBack:CloseCallBack = () :void => {};
export default {
emits:["close","clickMask"],
data() {
return {
isShow:false,
isOpen:false
}
},
props: {
maskClick: {
type: Boolean,
default: true
},
},
watch: {
// 设置show = true 时,如果没有 open 需要设置为 open
isShow:{
handler(isShow) {
// console.log("isShow",isShow)
if(isShow && this.isOpen == false){
this.isOpen = true
}
},
immediate:true
},
// 设置isOpen = true 时,如果没有 isShow 需要设置为 isShow
isOpen:{
handler(isOpen) {
// console.log("isOpen",isOpen)
if(isOpen && this.isShow == false){
this.isShow = true
}
},
immediate:true
}
},
methods:{
open(){
// ...funs : CloseCallBack[]
// if(funs.length > 0){
// closeCallBack = funs[0]
// }
this.isOpen = true;
},
clickMask(){
if(this.maskClick == true){
this.$emit('clickMask')
this.close()
}
},
close(): void{
this.isOpen = false;
this.$emit('close')
closeCallBack()
},
hiden(){
this.isShow = false
},
show(){
this.isShow = true
}
}
}
</script>
<style>
.popup-root {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
height: 100%;
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
justify-content: center;
align-items: center;
z-index: 99;
}
</style>

@ -0,0 +1,473 @@
<template>
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
<view @touchstart="touchstart">
<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
:duration="duration" :show="showTrans" @click="onTap" />
<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
:show="showTrans" @click="onTap">
<view class="uni-popup__wrapper" :style="{ backgroundColor: bg }" :class="[popupstyle]" @click="clear">
<slot />
</view>
</uni-transition>
</view>
<!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" />
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress.js'
// #endif
/**
* PopUp 弹出层
* @description 弹出层组件为了解决遮罩弹层的问题
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value left 左侧弹出
* @value right 右侧弹出
* @value message 消息提示
* @value dialog 对话框
* @value share 底部分享示例
* @property {Boolean} animation = [true|false] 是否开启动画
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
* @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
* @property {String} backgroundColor 主窗口背景色
* @property {String} maskBackgroundColor 蒙版颜色
* @property {Boolean} safeArea 是否适配底部安全区
* @event {Function} change 打开关闭弹窗触发e={show: false}
* @event {Function} maskClick 点击遮罩触发
*/
export default {
name: 'uniPopup',
components: {
// #ifdef H5
keypress
// #endif
},
emits: ['change', 'maskClick'],
props: {
//
animation: {
type: Boolean,
default: true
},
// top: bottomcenter
// message: ; dialog :
type: {
type: String,
default: 'center'
},
// maskClick
isMaskClick: {
type: Boolean,
default: null
},
// TODO 2 使 isMaskClick
maskClick: {
type: Boolean,
default: null
},
backgroundColor: {
type: String,
default: 'none'
},
safeArea: {
type: Boolean,
default: true
},
maskBackgroundColor: {
type: String,
default: 'rgba(0, 0, 0, 0.4)'
},
},
watch: {
/**
* 监听type类型
*/
type: {
handler: function(type) {
if (!this.config[type]) return
this[this.config[type]](true)
},
immediate: true
},
isDesktop: {
handler: function(newVal) {
if (!this.config[newVal]) return
this[this.config[this.type]](true)
},
immediate: true
},
/**
* 监听遮罩是否可点击
* @param {Object} val
*/
maskClick: {
handler: function(val) {
this.mkclick = val
},
immediate: true
},
isMaskClick: {
handler: function(val) {
this.mkclick = val
},
immediate: true
},
// H5
showPopup(show) {
// #ifdef H5
// fix by mehaotian h5 穿
document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
// #endif
}
},
data() {
return {
duration: 300,
ani: [],
showPopup: false,
showTrans: false,
popupWidth: 0,
popupHeight: 0,
config: {
top: 'top',
bottom: 'bottom',
center: 'center',
left: 'left',
right: 'right',
message: 'top',
dialog: 'center',
share: 'bottom'
},
maskClass: {
position: 'fixed',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)'
},
transClass: {
position: 'fixed',
left: 0,
right: 0
},
maskShow: true,
mkclick: true,
popupstyle: 'top'
}
},
computed: {
isDesktop() {
return this.popupWidth >= 500 && this.popupHeight >= 500
},
bg() {
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
return 'transparent'
}
return this.backgroundColor
}
},
mounted() {
const fixSize = () => {
const {
windowWidth,
windowHeight,
windowTop,
safeArea,
screenHeight,
safeAreaInsets
} = uni.getSystemInfoSync()
this.popupWidth = windowWidth
this.popupHeight = windowHeight + (windowTop || 0)
// TODO fix by mehaotian ,ios app ios
if (safeArea && this.safeArea) {
// #ifdef MP-WEIXIN
this.safeAreaInsets = screenHeight - safeArea.bottom
// #endif
// #ifndef MP-WEIXIN
this.safeAreaInsets = safeAreaInsets.bottom
// #endif
} else {
this.safeAreaInsets = 0
}
}
fixSize()
// #ifdef H5
// window.addEventListener('resize', fixSize)
// this.$once('hook:beforeDestroy', () => {
// window.removeEventListener('resize', fixSize)
// })
// #endif
},
// #ifndef VUE3
// TODO vue2
destroyed() {
this.setH5Visible()
},
// #endif
// #ifdef VUE3
// TODO vue3
unmounted() {
this.setH5Visible()
},
// #endif
created() {
// this.mkclick = this.isMaskClick || this.maskClick
if (this.isMaskClick === null && this.maskClick === null) {
this.mkclick = true
} else {
this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
}
if (this.animation) {
this.duration = 300
} else {
this.duration = 0
}
// TODO message
this.messageChild = null
// TODO
this.clearPropagation = false
this.maskClass.backgroundColor = this.maskBackgroundColor
},
methods: {
setH5Visible() {
// #ifdef H5
// fix by mehaotian h5 穿
document.getElementsByTagName('body')[0].style.overflow = 'visible'
// #endif
},
/**
* 公用方法不显示遮罩层
*/
closeMask() {
this.maskShow = false
},
/**
* 公用方法遮罩层禁止点击
*/
disableMask() {
this.mkclick = false
},
// TODO nvue
clear(e) {
// #ifndef APP-NVUE
e.stopPropagation()
// #endif
this.clearPropagation = true
},
open(direction) {
// fix by mehaotian
if (this.showPopup) {
return
}
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
if (!(direction && innerType.indexOf(direction) !== -1)) {
direction = this.type
}
if (!this.config[direction]) {
console.error('缺少类型:', direction)
return
}
this[this.config[direction]]()
this.$emit('change', {
show: true,
type: direction
})
},
close(type) {
this.showTrans = false
this.$emit('change', {
show: false,
type: this.type
})
clearTimeout(this.timer)
// //
// this.customOpen && this.customClose()
this.timer = setTimeout(() => {
this.showPopup = false
}, 300)
},
// TODO
touchstart() {
this.clearPropagation = false
},
onTap() {
if (this.clearPropagation) {
// fix by mehaotian nvue
this.clearPropagation = false
return
}
this.$emit('maskClick')
if (!this.mkclick) return
this.close()
},
/**
* 顶部弹出样式处理
*/
top(type) {
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
this.ani = ['slide-top']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
backgroundColor: this.bg
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
this.$nextTick(() => {
if (this.messageChild && this.type === 'message') {
this.messageChild.timerClose()
}
})
},
/**
* 底部弹出样式处理
*/
bottom(type) {
this.popupstyle = 'bottom'
this.ani = ['slide-bottom']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
paddingBottom: this.safeAreaInsets + 'px',
backgroundColor: this.bg
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
/**
* 中间弹出样式处理
*/
center(type) {
this.popupstyle = 'center'
this.ani = ['zoom-out', 'fade']
this.transClass = {
position: 'fixed',
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center'
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
left(type) {
this.popupstyle = 'left'
this.ani = ['slide-left']
this.transClass = {
position: 'fixed',
left: 0,
bottom: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
right(type) {
this.popupstyle = 'right'
this.ani = ['slide-right']
this.transClass = {
position: 'fixed',
bottom: 0,
right: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
}
}
}
</script>
<style lang="scss">
.uni-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
&.top,
&.left,
&.right {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
.uni-popup__wrapper {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
/* iphonex 等安全区设置,底部安全区适配 */
/* #ifndef APP-NVUE */
// padding-bottom: constant(safe-area-inset-bottom);
// padding-bottom: env(safe-area-inset-bottom);
/* #endif */
&.left,
&.right {
/* #ifdef H5 */
padding-top: var(--window-top);
/* #endif */
/* #ifndef H5 */
padding-top: 0;
/* #endif */
flex: 1;
}
}
}
.fixforpc-z-index {
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.fixforpc-top {
top: 0;
}
</style>

@ -0,0 +1,87 @@
{
"id": "uni-popup",
"displayName": "uni-popup 弹出层",
"version": "1.8.4",
"description": " Popup 组件,提供常用的弹层",
"keywords": [
"uni-ui",
"弹出层",
"弹窗",
"popup",
"弹框"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-transition"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"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": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

@ -0,0 +1,17 @@
## Popup 弹出层
> **组件名uni-popup**
> 代码块: `uPopup`
> 关联组件:`uni-transition`
弹出层组件,在应用中弹出一个消息提示窗口、提示框等
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

@ -0,0 +1,8 @@
## 1.0.32022-01-21
- 优化 组件示例
## 1.0.22021-11-22
- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
## 1.0.12021-11-22
- 修复 vue3中scss语法兼容问题
## 1.0.02021-11-18
- init

@ -0,0 +1 @@
@import './styles/index.scss';

@ -0,0 +1,82 @@
{
"id": "uni-scss",
"displayName": "uni-scss 辅助样式",
"version": "1.0.3",
"description": "uni-sass是uni-ui提供的一套全局样式 通过一些简单的类名和sass变量实现简单的页面布局操作比如颜色、边距、圆角等。",
"keywords": [
"uni-scss",
"uni-ui",
"辅助样式"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"JS SDK",
"通用 SDK"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "n",
"联盟": "n"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

@ -0,0 +1,4 @@
`uni-sass``uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

@ -0,0 +1,7 @@
@import './setting/_variables.scss';
@import './setting/_border.scss';
@import './setting/_color.scss';
@import './setting/_space.scss';
@import './setting/_radius.scss';
@import './setting/_text.scss';
@import './setting/_styles.scss';

@ -0,0 +1,3 @@
.uni-border {
border: 1px $uni-border-1 solid;
}

@ -0,0 +1,66 @@
// TODO class 使 使
// @mixin get-styles($k,$c) {
// @if $k == size or $k == weight{
// font-#{$k}:#{$c}
// }@else{
// #{$k}:#{$c}
// }
// }
$uni-ui-color:(
//
primary: $uni-primary,
primary-disable: $uni-primary-disable,
primary-light: $uni-primary-light,
//
success: $uni-success,
success-disable: $uni-success-disable,
success-light: $uni-success-light,
warning: $uni-warning,
warning-disable: $uni-warning-disable,
warning-light: $uni-warning-light,
error: $uni-error,
error-disable: $uni-error-disable,
error-light: $uni-error-light,
info: $uni-info,
info-disable: $uni-info-disable,
info-light: $uni-info-light,
//
main-color: $uni-main-color,
base-color: $uni-base-color,
secondary-color: $uni-secondary-color,
extra-color: $uni-extra-color,
//
bg-color: $uni-bg-color,
//
border-1: $uni-border-1,
border-2: $uni-border-2,
border-3: $uni-border-3,
border-4: $uni-border-4,
//
black:$uni-black,
//
white:$uni-white,
//
transparent:$uni-transparent
) !default;
@each $key, $child in $uni-ui-color {
.uni-#{"" + $key} {
color: $child;
}
.uni-#{"" + $key}-bg {
background-color: $child;
}
}
.uni-shadow-sm {
box-shadow: $uni-shadow-sm;
}
.uni-shadow-base {
box-shadow: $uni-shadow-base;
}
.uni-shadow-lg {
box-shadow: $uni-shadow-lg;
}
.uni-mask {
background-color:$uni-mask;
}

@ -0,0 +1,55 @@
@mixin radius($r,$d:null ,$important: false){
$radius-value:map-get($uni-radius, $r) if($important, !important, null);
// Key exists within the $uni-radius variable
@if (map-has-key($uni-radius, $r) and $d){
@if $d == t {
border-top-left-radius:$radius-value;
border-top-right-radius:$radius-value;
}@else if $d == r {
border-top-right-radius:$radius-value;
border-bottom-right-radius:$radius-value;
}@else if $d == b {
border-bottom-left-radius:$radius-value;
border-bottom-right-radius:$radius-value;
}@else if $d == l {
border-top-left-radius:$radius-value;
border-bottom-left-radius:$radius-value;
}@else if $d == tl {
border-top-left-radius:$radius-value;
}@else if $d == tr {
border-top-right-radius:$radius-value;
}@else if $d == br {
border-bottom-right-radius:$radius-value;
}@else if $d == bl {
border-bottom-left-radius:$radius-value;
}
}@else{
border-radius:$radius-value;
}
}
@each $key, $child in $uni-radius {
@if($key){
.uni-radius-#{"" + $key} {
@include radius($key)
}
}@else{
.uni-radius {
@include radius($key)
}
}
}
@each $direction in t, r, b, l,tl, tr, br, bl {
@each $key, $child in $uni-radius {
@if($key){
.uni-radius-#{"" + $direction}-#{"" + $key} {
@include radius($key,$direction,false)
}
}@else{
.uni-radius-#{$direction} {
@include radius($key,$direction,false)
}
}
}
}

@ -0,0 +1,56 @@
@mixin fn($space,$direction,$size,$n) {
@if $n {
#{$space}-#{$direction}: #{$size*$uni-space-root}px
} @else {
#{$space}-#{$direction}: #{-$size*$uni-space-root}px
}
}
@mixin get-styles($direction,$i,$space,$n){
@if $direction == t {
@include fn($space, top,$i,$n);
}
@if $direction == r {
@include fn($space, right,$i,$n);
}
@if $direction == b {
@include fn($space, bottom,$i,$n);
}
@if $direction == l {
@include fn($space, left,$i,$n);
}
@if $direction == x {
@include fn($space, left,$i,$n);
@include fn($space, right,$i,$n);
}
@if $direction == y {
@include fn($space, top,$i,$n);
@include fn($space, bottom,$i,$n);
}
@if $direction == a {
@if $n {
#{$space}:#{$i*$uni-space-root}px;
} @else {
#{$space}:#{-$i*$uni-space-root}px;
}
}
}
@each $orientation in m,p {
$space: margin;
@if $orientation == m {
$space: margin;
} @else {
$space: padding;
}
@for $i from 0 through 16 {
@each $direction in t, r, b, l, x, y, a {
.uni-#{$orientation}#{$direction}-#{$i} {
@include get-styles($direction,$i,$space,true);
}
.uni-#{$orientation}#{$direction}-n#{$i} {
@include get-styles($direction,$i,$space,false);
}
}
}
}

@ -0,0 +1,167 @@
/* #ifndef APP-NVUE */
$-color-white:#fff;
$-color-black:#000;
@mixin base-style($color) {
color: #fff;
background-color: $color;
border-color: mix($-color-black, $color, 8%);
&:not([hover-class]):active {
background: mix($-color-black, $color, 10%);
border-color: mix($-color-black, $color, 20%);
color: $-color-white;
outline: none;
}
}
@mixin is-color($color) {
@include base-style($color);
&[loading] {
@include base-style($color);
&::before {
margin-right:5px;
}
}
&[disabled] {
&,
&[loading],
&:not([hover-class]):active {
color: $-color-white;
border-color: mix(darken($color,10%), $-color-white);
background-color: mix($color, $-color-white);
}
}
}
@mixin base-plain-style($color) {
color:$color;
background-color: mix($-color-white, $color, 90%);
border-color: mix($-color-white, $color, 70%);
&:not([hover-class]):active {
background: mix($-color-white, $color, 80%);
color: $color;
outline: none;
border-color: mix($-color-white, $color, 50%);
}
}
@mixin is-plain($color){
&[plain] {
@include base-plain-style($color);
&[loading] {
@include base-plain-style($color);
&::before {
margin-right:5px;
}
}
&[disabled] {
&,
&:active {
color: mix($-color-white, $color, 40%);
background-color: mix($-color-white, $color, 90%);
border-color: mix($-color-white, $color, 80%);
}
}
}
}
.uni-btn {
margin: 5px;
color: #393939;
border:1px solid #ccc;
font-size: 16px;
font-weight: 200;
background-color: #F9F9F9;
// TODO
overflow: visible;
&::after{
border: none;
}
&:not([type]),&[type=default] {
color: #999;
&[loading] {
background: none;
&::before {
margin-right:5px;
}
}
&[disabled]{
color: mix($-color-white, #999, 60%);
&,
&[loading],
&:active {
color: mix($-color-white, #999, 60%);
background-color: mix($-color-white,$-color-black , 98%);
border-color: mix($-color-white, #999, 85%);
}
}
&[plain] {
color: #999;
background: none;
border-color: $uni-border-1;
&:not([hover-class]):active {
background: none;
color: mix($-color-white, $-color-black, 80%);
border-color: mix($-color-white, $-color-black, 90%);
outline: none;
}
&[disabled]{
&,
&[loading],
&:active {
background: none;
color: mix($-color-white, #999, 60%);
border-color: mix($-color-white, #999, 85%);
}
}
}
}
&:not([hover-class]):active {
color: mix($-color-white, $-color-black, 50%);
}
&[size=mini] {
font-size: 16px;
font-weight: 200;
border-radius: 8px;
}
&.uni-btn-small {
font-size: 14px;
}
&.uni-btn-mini {
font-size: 12px;
}
&.uni-btn-radius {
border-radius: 999px;
}
&[type=primary] {
@include is-color($uni-primary);
@include is-plain($uni-primary)
}
&[type=success] {
@include is-color($uni-success);
@include is-plain($uni-success)
}
&[type=error] {
@include is-color($uni-error);
@include is-plain($uni-error)
}
&[type=warning] {
@include is-color($uni-warning);
@include is-plain($uni-warning)
}
&[type=info] {
@include is-color($uni-info);
@include is-plain($uni-info)
}
}
/* #endif */

@ -0,0 +1,24 @@
@mixin get-styles($k,$c) {
@if $k == size or $k == weight{
font-#{$k}:#{$c}
}@else{
#{$k}:#{$c}
}
}
@each $key, $child in $uni-headings {
/* #ifndef APP-NVUE */
.uni-#{$key} {
@each $k, $c in $child {
@include get-styles($k,$c)
}
}
/* #endif */
/* #ifdef APP-NVUE */
.container .uni-#{$key} {
@each $k, $c in $child {
@include get-styles($k,$c)
}
}
/* #endif */
}

@ -0,0 +1,146 @@
// @use "sass:math";
@import '../tools/functions.scss';
//
$uni-space-root: 2 !default;
//
$uni-radius-root:5px !default;
$uni-radius: () !default;
//
$uni-radius: map-deep-merge(
(
0: 0,
// TODO sm
// 'sm': math.div($uni-radius-root, 2),
null: $uni-radius-root,
'lg': $uni-radius-root * 2,
'xl': $uni-radius-root * 6,
'pill': 9999px,
'circle': 50%
),
$uni-radius
);
//
$body-font-family: 'Roboto', sans-serif !default;
//
$heading-font-family: $body-font-family !default;
$uni-headings: () !default;
$letterSpacing: -0.01562em;
$uni-headings: map-deep-merge(
(
'h1': (
size: 32px,
weight: 300,
line-height: 50px,
// letter-spacing:-0.01562em
),
'h2': (
size: 28px,
weight: 300,
line-height: 40px,
// letter-spacing: -0.00833em
),
'h3': (
size: 24px,
weight: 400,
line-height: 32px,
// letter-spacing: normal
),
'h4': (
size: 20px,
weight: 400,
line-height: 30px,
// letter-spacing: 0.00735em
),
'h5': (
size: 16px,
weight: 400,
line-height: 24px,
// letter-spacing: normal
),
'h6': (
size: 14px,
weight: 500,
line-height: 18px,
// letter-spacing: 0.0125em
),
'subtitle': (
size: 12px,
weight: 400,
line-height: 20px,
// letter-spacing: 0.00937em
),
'body': (
font-size: 14px,
font-weight: 400,
line-height: 22px,
// letter-spacing: 0.03125em
),
'caption': (
'size': 12px,
'weight': 400,
'line-height': 20px,
// 'letter-spacing': 0.03333em,
// 'text-transform': false
)
),
$uni-headings
);
//
$uni-primary: #2979ff !default;
$uni-primary-disable:lighten($uni-primary,20%) !default;
$uni-primary-light: lighten($uni-primary,25%) !default;
//
// 使
$uni-success: #18bc37 !default;
$uni-success-disable:lighten($uni-success,20%) !default;
$uni-success-light: lighten($uni-success,25%) !default;
$uni-warning: #f3a73f !default;
$uni-warning-disable:lighten($uni-warning,20%) !default;
$uni-warning-light: lighten($uni-warning,25%) !default;
$uni-error: #e43d33 !default;
$uni-error-disable:lighten($uni-error,20%) !default;
$uni-error-light: lighten($uni-error,25%) !default;
$uni-info: #8f939c !default;
$uni-info-disable:lighten($uni-info,20%) !default;
$uni-info-light: lighten($uni-info,25%) !default;
//
//
$uni-main-color: #3a3a3a !default; //
$uni-base-color: #6a6a6a !default; //
$uni-secondary-color: #909399 !default; //
$uni-extra-color: #c7c7c7 !default; //
//
$uni-border-1: #F0F0F0 !default;
$uni-border-2: #EDEDED !default;
$uni-border-3: #DCDCDC !default;
$uni-border-4: #B9B9B9 !default;
//
$uni-black: #000000 !default;
$uni-white: #ffffff !default;
$uni-transparent: rgba($color: #000000, $alpha: 0) !default;
//
$uni-bg-color: #f7f7f7 !default;
/* 水平间距 */
$uni-spacing-sm: 8px !default;
$uni-spacing-base: 15px !default;
$uni-spacing-lg: 30px !default;
//
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;
//
$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;

@ -0,0 +1,19 @@
// map
@function map-deep-merge($parent-map, $child-map){
$result: $parent-map;
@each $key, $child in $child-map {
$parent-has-key: map-has-key($result, $key);
$parent-value: map-get($result, $key);
$parent-type: type-of($parent-value);
$child-type: type-of($child);
$parent-is-map: $parent-type == map;
$child-is-map: $child-type == map;
@if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){
$result: map-merge($result, ( $key: $child ));
}@else {
$result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));
}
}
@return $result;
};

@ -0,0 +1,31 @@
//
$uni-space-root: 2;
//
$uni-radius-root:5px;
//
$uni-primary: #2979ff;
//
$uni-success: #4cd964;
//
$uni-warning: #f0ad4e;
//
$uni-error: #dd524d;
//
$uni-info: #909399;
//
$uni-main-color: #303133;
$uni-base-color: #606266;
$uni-secondary-color: #909399;
$uni-extra-color: #C0C4CC;
//
$uni-bg-color: #f5f5f5;
//
$uni-border-1: #DCDFE6;
$uni-border-2: #E4E7ED;
$uni-border-3: #EBEEF5;
$uni-border-4: #F2F6FC;
//
$uni-black: #000000;
$uni-white: #ffffff;
$uni-transparent: rgba($color: #000000, $alpha: 0);

@ -0,0 +1,62 @@
@import './styles/setting/_variables.scss';
//
$uni-space-root: 2;
//
$uni-radius-root:5px;
//
$uni-primary: #2979ff;
$uni-primary-disable:mix(#fff,$uni-primary,50%);
$uni-primary-light: mix(#fff,$uni-primary,80%);
//
// 使
$uni-success: #18bc37;
$uni-success-disable:mix(#fff,$uni-success,50%);
$uni-success-light: mix(#fff,$uni-success,80%);
$uni-warning: #f3a73f;
$uni-warning-disable:mix(#fff,$uni-warning,50%);
$uni-warning-light: mix(#fff,$uni-warning,80%);
$uni-error: #e43d33;
$uni-error-disable:mix(#fff,$uni-error,50%);
$uni-error-light: mix(#fff,$uni-error,80%);
$uni-info: #8f939c;
$uni-info-disable:mix(#fff,$uni-info,50%);
$uni-info-light: mix(#fff,$uni-info,80%);
//
//
$uni-main-color: #3a3a3a; //
$uni-base-color: #6a6a6a; //
$uni-secondary-color: #909399; //
$uni-extra-color: #c7c7c7; //
//
$uni-border-1: #F0F0F0;
$uni-border-2: #EDEDED;
$uni-border-3: #DCDCDC;
$uni-border-4: #B9B9B9;
//
$uni-black: #000000;
$uni-white: #ffffff;
$uni-transparent: rgba($color: #000000, $alpha: 0);
//
$uni-bg-color: #f7f7f7;
/* 水平间距 */
$uni-spacing-sm: 8px;
$uni-spacing-base: 15px;
$uni-spacing-lg: 30px;
//
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
//
$uni-mask: rgba($color: #000000, $alpha: 0.4);

@ -0,0 +1,22 @@
## 1.3.22023-05-04
- 修复 NVUE 平台报错的问题
## 1.3.12021-11-23
- 修复 init 方法初始化问题
## 1.3.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition)
## 1.2.12021-09-27
- 修复 init 方法不生效的 Bug
## 1.2.02021-07-30
- 组件兼容 vue3如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.12021-05-12
- 新增 示例地址
- 修复 示例项目缺少组件的 Bug
## 1.1.02021-04-22
- 新增 通过方法自定义动画
- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
- 优化 动画触发逻辑,使动画更流畅
- 优化 支持单独的动画类型
- 优化 文档示例
## 1.0.22021-02-05
- 调整为 uni_modules 目录规范

@ -0,0 +1,131 @@
// const defaultOption = {
// duration: 300,
// timingFunction: 'linear',
// delay: 0,
// transformOrigin: '50% 50% 0'
// }
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
// #endif
class MPAnimation {
constructor(options, _this) {
this.options = options
// 在iOS10+QQ小程序平台下传给原生的对象一定是个普通对象而不是Proxy对象否则会报parameter should be Object instead of ProxyObject的错误
this.animation = uni.createAnimation({
...options
})
this.currentStepAnimates = {}
this.next = 0
this.$ = _this
}
_nvuePushAnimates(type, args) {
let aniObj = this.currentStepAnimates[this.next]
let styles = {}
if (!aniObj) {
styles = {
styles: {},
config: {}
}
} else {
styles = aniObj
}
if (animateTypes1.includes(type)) {
if (!styles.styles.transform) {
styles.styles.transform = ''
}
let unit = ''
if(type === 'rotate'){
unit = 'deg'
}
styles.styles.transform += `${type}(${args+unit}) `
} else {
styles.styles[type] = `${args}`
}
this.currentStepAnimates[this.next] = styles
}
_animateRun(styles = {}, config = {}) {
let ref = this.$.$refs['ani'].ref
if (!ref) return
return new Promise((resolve, reject) => {
nvueAnimation.transition(ref, {
styles,
...config
}, res => {
resolve()
})
})
}
_nvueNextAnimate(animates, step = 0, fn) {
let obj = animates[step]
if (obj) {
let {
styles,
config
} = obj
this._animateRun(styles, config).then(() => {
step += 1
this._nvueNextAnimate(animates, step, fn)
})
} else {
this.currentStepAnimates = {}
typeof fn === 'function' && fn()
this.isEnd = true
}
}
step(config = {}) {
// #ifndef APP-NVUE
this.animation.step(config)
// #endif
// #ifdef APP-NVUE
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
// #endif
return this
}
run(fn) {
// #ifndef APP-NVUE
this.$.animationData = this.animation.export()
this.$.timer = setTimeout(() => {
typeof fn === 'function' && fn()
}, this.$.durationTime)
// #endif
// #ifdef APP-NVUE
this.isEnd = false
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
if(!ref) return
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
this.next = 0
// #endif
}
}
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 = ['opacity', 'backgroundColor']
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
MPAnimation.prototype[type] = function(...args) {
// #ifndef APP-NVUE
this.animation[type](...args)
// #endif
// #ifdef APP-NVUE
this._nvuePushAnimates(type, args)
// #endif
return this
}
})
export function createAnimation(option, _this) {
if(!_this) return
clearTimeout(_this.timer)
return new MPAnimation(option, _this)
}

@ -0,0 +1,286 @@
<template>
<!-- #ifndef APP-NVUE -->
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
<!-- #endif -->
</template>
<script>
import { createAnimation } from './createAnimation'
/**
* Transition 过渡动画
* @description 简单过渡动画组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
* @value fade 渐隐渐出过渡
* @value slide-top 由上至下过渡
* @value slide-right 由右至左过渡
* @value slide-bottom 由下至上过渡
* @value slide-left 由左至右过渡
* @value zoom-in 由小到大过渡
* @value zoom-out 由大到小过渡
* @property {Number} duration 过渡动画持续时间
* @property {Object} styles 组件样式 css 样式注意带-连接符的属性需要使用小驼峰写法如`backgroundColor:red`
*/
export default {
name: 'uniTransition',
emits:['click','change'],
props: {
show: {
type: Boolean,
default: false
},
modeClass: {
type: [Array, String],
default() {
return 'fade'
}
},
duration: {
type: Number,
default: 300
},
styles: {
type: Object,
default() {
return {}
}
},
customClass:{
type: String,
default: ''
},
onceRender:{
type:Boolean,
default:false
},
},
data() {
return {
isShow: false,
transform: '',
opacity: 1,
animationData: {},
durationTime: 300,
config: {}
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.open()
} else {
// close,
if (this.isShow) {
this.close()
}
}
},
immediate: true
}
},
computed: {
//
stylesObject() {
let styles = {
...this.styles,
'transition-duration': this.duration / 1000 + 's'
}
let transform = ''
for (let i in styles) {
let line = this.toLine(i)
transform += line + ':' + styles[i] + ';'
}
return transform
},
//
transformStyles() {
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
}
},
created() {
//
this.config = {
duration: this.duration,
timingFunction: 'ease',
transformOrigin: '50% 50%',
delay: 0
}
this.durationTime = this.duration
},
methods: {
/**
* ref 触发 初始化动画
*/
init(obj = {}) {
if (obj.duration) {
this.durationTime = obj.duration
}
this.animation = createAnimation(Object.assign(this.config, obj),this)
},
/**
* 点击组件触发回调
*/
onClick() {
this.$emit('click', {
detail: this.isShow
})
},
/**
* ref 触发 动画分组
* @param {Object} obj
*/
step(obj, config = {}) {
if (!this.animation) return
for (let i in obj) {
try {
if(typeof obj[i] === 'object'){
this.animation[i](...obj[i])
}else{
this.animation[i](obj[i])
}
} catch (e) {
console.error(`方法 ${i} 不存在`)
}
}
this.animation.step(config)
return this
},
/**
* ref 触发 执行动画
*/
run(fn) {
if (!this.animation) return
this.animation.run(fn)
},
//
open() {
clearTimeout(this.timer)
this.transform = ''
this.isShow = true
let { opacity, transform } = this.styleInit(false)
if (typeof opacity !== 'undefined') {
this.opacity = opacity
}
this.transform = transform
// nextTick wx
this.$nextTick(() => {
// TODO
this.timer = setTimeout(() => {
this.animation = createAnimation(this.config, this)
this.tranfromInit(false).step()
this.animation.run()
this.$emit('change', {
detail: this.isShow
})
}, 20)
})
},
//
close(type) {
if (!this.animation) return
this.tranfromInit(true)
.step()
.run(() => {
this.isShow = false
this.animationData = null
this.animation = null
let { opacity, transform } = this.styleInit(false)
this.opacity = opacity || 1
this.transform = transform
this.$emit('change', {
detail: this.isShow
})
})
},
//
styleInit(type) {
let styles = {
transform: ''
}
let buildStyle = (type, mode) => {
if (mode === 'fade') {
styles.opacity = this.animationType(type)[mode]
} else {
styles.transform += this.animationType(type)[mode] + ' '
}
}
if (typeof this.modeClass === 'string') {
buildStyle(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildStyle(type, mode)
})
}
return styles
},
//
tranfromInit(type) {
let buildTranfrom = (type, mode) => {
let aniNum = null
if (mode === 'fade') {
aniNum = type ? 0 : 1
} else {
aniNum = type ? '-100%' : '0'
if (mode === 'zoom-in') {
aniNum = type ? 0.8 : 1
}
if (mode === 'zoom-out') {
aniNum = type ? 1.2 : 1
}
if (mode === 'slide-right') {
aniNum = type ? '100%' : '0'
}
if (mode === 'slide-bottom') {
aniNum = type ? '100%' : '0'
}
}
this.animation[this.animationMode()[mode]](aniNum)
}
if (typeof this.modeClass === 'string') {
buildTranfrom(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildTranfrom(type, mode)
})
}
return this.animation
},
animationType(type) {
return {
fade: type ? 1 : 0,
'slide-top': `translateY(${type ? '0' : '-100%'})`,
'slide-right': `translateX(${type ? '0' : '100%'})`,
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
'slide-left': `translateX(${type ? '0' : '-100%'})`,
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
}
},
//
animationMode() {
return {
fade: 'opacity',
'slide-top': 'translateY',
'slide-right': 'translateX',
'slide-bottom': 'translateY',
'slide-left': 'translateX',
'zoom-in': 'scale',
'zoom-out': 'scale'
}
},
// 线
toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
}
}
}
</script>
<style></style>

@ -0,0 +1,84 @@
{
"id": "uni-transition",
"displayName": "uni-transition 过渡动画",
"version": "1.3.2",
"description": "元素的简单过渡动画",
"keywords": [
"uni-ui",
"uniui",
"动画",
"过渡",
"过渡动画"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"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": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

@ -0,0 +1,11 @@
## Transition 过渡动画
> **组件名uni-transition**
> 代码块: `uTransition`
元素过渡动画
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save