init
This commit is contained in:
commit
8da1c3403e
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/unpackage
|
||||
/utils/request-1.js
|
||||
.DS_Store
|
20
.hbuilderx/launch.json
Normal file
20
.hbuilderx/launch.json
Normal file
@ -0,0 +1,20 @@
|
||||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version": "0.0",
|
||||
"configurations": [{
|
||||
"default" :
|
||||
{
|
||||
"launchtype" : "remote"
|
||||
},
|
||||
"h5" :
|
||||
{
|
||||
"launchtype" : "remote"
|
||||
},
|
||||
"mp-weixin" :
|
||||
{
|
||||
"launchtype" : "remote"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
66
App.vue
Normal file
66
App.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<script>
|
||||
export default {
|
||||
|
||||
/**
|
||||
* 全局变量
|
||||
*/
|
||||
globalData: {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化完成时触发
|
||||
*/
|
||||
onLaunch() {
|
||||
// 小程序主动更新
|
||||
this.updateManager()
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* 小程序主动更新
|
||||
*/
|
||||
updateManager() {
|
||||
const updateManager = uni.getUpdateManager();
|
||||
updateManager.onCheckForUpdate(res => {
|
||||
// 请求完新版本信息的回调
|
||||
// console.log(res.hasUpdate)
|
||||
})
|
||||
updateManager.onUpdateReady(() => {
|
||||
uni.showModal({
|
||||
title: '更新提示',
|
||||
content: '新版本已经准备好,即将重启应用',
|
||||
showCancel: false,
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
||||
updateManager.applyUpdate()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
updateManager.onUpdateFailed(() => {
|
||||
// 新的版本下载失败
|
||||
uni.showModal({
|
||||
title: '更新提示',
|
||||
content: '新版本下载失败',
|
||||
showCancel: false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 引入uView库样式 */
|
||||
@import "uview-ui/index.scss";
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 项目基础样式 */
|
||||
@import "./app.scss";
|
||||
</style>
|
33
api/address.js
Normal file
33
api/address.js
Normal file
@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/address/list',
|
||||
detail: 'clientApi/address/detail',
|
||||
save: 'clientApi/address/save'
|
||||
}
|
||||
|
||||
// 个人收货地址列表
|
||||
export const list = (param) => {
|
||||
return request.get(api.list, param)
|
||||
}
|
||||
|
||||
// 收货地址详情
|
||||
export const detail = (addressId) => {
|
||||
return request.post(api.detail, { addressId })
|
||||
}
|
||||
|
||||
// 保存收货地址
|
||||
export const save = (name, mobile, provinceId, cityId, regionId, detail, status, addressId) => {
|
||||
return request.post(api.save, { name, mobile, provinceId, cityId, regionId, detail, status, addressId })
|
||||
}
|
||||
|
||||
// 设置默认收货地址
|
||||
export const setDefault = (addressId, isDefault) => {
|
||||
return request.post(api.save, { addressId, isDefault })
|
||||
}
|
||||
|
||||
// 删除收货地址
|
||||
export const remove = (addressId, status) => {
|
||||
return request.post(api.save, { addressId, status })
|
||||
}
|
23
api/article.js
Normal file
23
api/article.js
Normal file
@ -0,0 +1,23 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/article/list',
|
||||
detail: 'clientApi/article/detail',
|
||||
cate: 'clientApi/article/cateList',
|
||||
}
|
||||
|
||||
// 文章列表
|
||||
export const list = (param) => {
|
||||
return request.post(api.list, param)
|
||||
}
|
||||
|
||||
// 文章详情
|
||||
export const detail = (articleId) => {
|
||||
return request.post(api.detail, { articleId })
|
||||
}
|
||||
|
||||
// 文章分类列表
|
||||
export const cateList = (param) => {
|
||||
return request.post(api.cate, param)
|
||||
}
|
23
api/balance.js
Normal file
23
api/balance.js
Normal file
@ -0,0 +1,23 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/balance/list',
|
||||
setting: 'clientApi/balance/setting',
|
||||
doRecharge: 'clientApi/balance/doRecharge',
|
||||
}
|
||||
|
||||
// 余额设置
|
||||
export const setting = (param) => {
|
||||
return request.get(api.setting, param)
|
||||
}
|
||||
|
||||
// 余额明细
|
||||
export const list = (param) => {
|
||||
return request.post(api.list, param)
|
||||
}
|
||||
|
||||
// 提交充值
|
||||
export const doRecharge = (rechargeAmount, customAmount) => {
|
||||
return request.post(api.doRecharge, { rechargeAmount, customAmount })
|
||||
}
|
23
api/cart.js
Normal file
23
api/cart.js
Normal file
@ -0,0 +1,23 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/cart/list',
|
||||
save: 'clientApi/cart/save',
|
||||
clear: 'clientApi/cart/clear',
|
||||
}
|
||||
|
||||
// 购物车列表
|
||||
export const list = (cartIds, goodsId, skuId, buyNum, couponId, point) => {
|
||||
return request.post(api.list, { cartIds, goodsId, skuId, buyNum, couponId, point })
|
||||
}
|
||||
|
||||
// 更新购物车
|
||||
export const save = (goodsId, action, skuId, buyNum) => {
|
||||
return request.post(api.save, { goodsId, action, skuId, buyNum })
|
||||
}
|
||||
|
||||
// 删除购物车商品
|
||||
export const clear = (cartId) => {
|
||||
return request.post(api.clear, { cartId })
|
||||
}
|
11
api/confirm.js
Normal file
11
api/confirm.js
Normal file
@ -0,0 +1,11 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
doConfirm: 'clientApi/confirm/doConfirm',
|
||||
}
|
||||
|
||||
// 确定核销
|
||||
export function doConfirm(code, amount, remark) {
|
||||
return request.post(api.doConfirm, { code, amount, remark })
|
||||
}
|
28
api/coupon.js
Normal file
28
api/coupon.js
Normal file
@ -0,0 +1,28 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/coupon/list',
|
||||
receive: 'clientApi/coupon/receive',
|
||||
detail: 'clientApi/coupon/detail'
|
||||
}
|
||||
|
||||
// 卡券列表
|
||||
export const list = (param, option) => {
|
||||
const options = {
|
||||
isPrompt: true, //(默认 true 说明:本接口抛出的错误是否提示)
|
||||
load: true, //(默认 true 说明:本接口是否提示加载动画)
|
||||
...option
|
||||
}
|
||||
return request.post(api.list, param, options)
|
||||
}
|
||||
|
||||
// 领券接口
|
||||
export const receive = (param) => {
|
||||
return request.post(api.receive, param)
|
||||
}
|
||||
|
||||
// 会员卡券详情
|
||||
export function detail(couponId) {
|
||||
return request.post(api.detail, { couponId })
|
||||
}
|
17
api/give.js
Normal file
17
api/give.js
Normal file
@ -0,0 +1,17 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
doGive: 'clientApi/give/doGive',
|
||||
giveLog: 'clientApi/give/giveLog'
|
||||
}
|
||||
|
||||
// 转赠
|
||||
export const doGive = (data) => {
|
||||
return request.post(api.doGive, data)
|
||||
}
|
||||
|
||||
// 转赠记录
|
||||
export const giveLog = (param, option) => {
|
||||
return request.post(api.giveLog, param)
|
||||
}
|
29
api/goods.js
Normal file
29
api/goods.js
Normal file
@ -0,0 +1,29 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
cateList: 'clientApi/goodsApi/cateList',
|
||||
list: 'clientApi/goodsApi/list',
|
||||
search: 'clientApi/goodsApi/search',
|
||||
detail: 'clientApi/goodsApi/detail'
|
||||
}
|
||||
|
||||
// 商品分类列表
|
||||
export const cateList = param => {
|
||||
return request.get(api.cateList, param)
|
||||
}
|
||||
|
||||
// 商品列表
|
||||
export const list = param => {
|
||||
return request.get(api.list, param)
|
||||
}
|
||||
|
||||
// 商品搜索
|
||||
export const search = param => {
|
||||
return request.post(api.search, param)
|
||||
}
|
||||
|
||||
// 商品详情
|
||||
export const detail = goodsId => {
|
||||
return request.post(api.detail, { goodsId })
|
||||
}
|
11
api/goods/service.js
Normal file
11
api/goods/service.js
Normal file
@ -0,0 +1,11 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/goods/service/list'
|
||||
}
|
||||
|
||||
// 商品评价列表
|
||||
export function list(goodsId) {
|
||||
return;
|
||||
}
|
11
api/help.js
Normal file
11
api/help.js
Normal file
@ -0,0 +1,11 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/article/list'
|
||||
}
|
||||
|
||||
// 帮助中心列表
|
||||
export const list = (param) => {
|
||||
return request.post(api.list, param)
|
||||
}
|
41
api/login/index.js
Normal file
41
api/login/index.js
Normal file
@ -0,0 +1,41 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
register: 'clientApi/sign/register',
|
||||
login: 'clientApi/sign/signIn',
|
||||
mpWxLogin: 'clientApi/sign/mpWxLogin',
|
||||
mpWxAuth: 'clientApi/sign/mpWxAuth',
|
||||
captcha: 'clientApi/captcha/getCode',
|
||||
sendSmsCaptcha: 'clientApi/sms/sendVerifyCode'
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
export function register(data) {
|
||||
return request.post(api.register, data)
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
export function login(data) {
|
||||
return request.post(api.login, data)
|
||||
}
|
||||
|
||||
// 微信小程序快捷登录
|
||||
export function mpWxLogin(data, option) {
|
||||
return request.post(api.mpWxLogin, data, option)
|
||||
}
|
||||
|
||||
// 微信公众号授权
|
||||
export function mpWxAuth(data, option) {
|
||||
return request.post(api.mpWxAuth, data, option)
|
||||
}
|
||||
|
||||
// 图形验证码
|
||||
export function captcha() {
|
||||
return request.get(api.captcha)
|
||||
}
|
||||
|
||||
// 发送短信验证码
|
||||
export function sendSmsCaptcha(data) {
|
||||
return request.post(api.sendSmsCaptcha, data)
|
||||
}
|
16
api/merchant.js
Normal file
16
api/merchant.js
Normal file
@ -0,0 +1,16 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
merchantInfo: 'merchantApi/merchant/info',
|
||||
}
|
||||
|
||||
// 当前商户信息
|
||||
export const info = (param, option) => {
|
||||
const options = {
|
||||
isPrompt: true, //(默认 true 说明:本接口抛出的错误是否提示)
|
||||
load: true, //(默认 true 说明:本接口是否提示加载动画)
|
||||
...option
|
||||
}
|
||||
return request.get(api.merchantInfo, param, options)
|
||||
}
|
19
api/merchant/member.js
Normal file
19
api/merchant/member.js
Normal file
@ -0,0 +1,19 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
info: 'merchantApi/user/info',
|
||||
list: 'merchantApi/member/list'
|
||||
}
|
||||
|
||||
// 会员详情
|
||||
export function detail(userId, param) {
|
||||
return request.post(api.info, { userId, ...param })
|
||||
}
|
||||
|
||||
|
||||
// 会员列表
|
||||
export function list(param, option) {
|
||||
return request.post(api.list, param, option)
|
||||
}
|
||||
|
18
api/merchant/order.js
Normal file
18
api/merchant/order.js
Normal file
@ -0,0 +1,18 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'merchantApi/order/list',
|
||||
detail: 'merchantApi/order/detail'
|
||||
}
|
||||
|
||||
|
||||
// 订单列表
|
||||
export function list(param, option) {
|
||||
return request.post(api.list, param, option)
|
||||
}
|
||||
|
||||
// 订单详情
|
||||
export function detail(orderId, param) {
|
||||
return request.get(api.detail, { orderId, ...param })
|
||||
}
|
24
api/message.js
Normal file
24
api/message.js
Normal file
@ -0,0 +1,24 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
getOne: 'clientApi/message/getOne',
|
||||
readed: 'clientApi/message/readed',
|
||||
getSubTemplate: 'clientApi/message/getSubTemplate'
|
||||
}
|
||||
|
||||
// 读取最新的一条消息
|
||||
export const getOne = (param, option) => {
|
||||
return request.get(api.getOne, param)
|
||||
}
|
||||
|
||||
// 将消息置为已读
|
||||
export const readed = (param) => {
|
||||
return request.get(api.readed, param)
|
||||
}
|
||||
|
||||
// 获取订阅消息模板
|
||||
export const getSubTemplate = (param, option) => {
|
||||
return request.get(api.getSubTemplate, param)
|
||||
}
|
||||
|
33
api/myCoupon.js
Normal file
33
api/myCoupon.js
Normal file
@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/myCoupon/list',
|
||||
mydetail: 'clientApi/userCouponApi/detail',
|
||||
detail: 'clientApi/coupon/detail'
|
||||
}
|
||||
|
||||
// 会员卡券列表
|
||||
export const list = (param, option) => {
|
||||
const options = {
|
||||
isPrompt: true, //(默认 true 说明:本接口抛出的错误是否提示)
|
||||
load: true, //(默认 true 说明:本接口是否提示加载动画)
|
||||
...option
|
||||
}
|
||||
return request.get(api.list, param, options)
|
||||
}
|
||||
|
||||
// 卡券详情
|
||||
export function detail(couponId, userCouponId, userCouponCode) {
|
||||
if (parseInt(userCouponId) > 0 || userCouponCode.length > 0) {
|
||||
if (userCouponId == undefined) {
|
||||
userCouponId = 0
|
||||
}
|
||||
if (userCouponCode == undefined) {
|
||||
userCouponCode = ""
|
||||
}
|
||||
return request.get(api.mydetail, { userCouponId, userCouponCode })
|
||||
} else {
|
||||
return request.post(api.detail, { couponId })
|
||||
}
|
||||
}
|
41
api/order.js
Normal file
41
api/order.js
Normal file
@ -0,0 +1,41 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
todoCounts: 'clientApi/order/todoCounts',
|
||||
list: 'clientApi/order/list',
|
||||
detail: 'clientApi/order/detail',
|
||||
cancel: 'clientApi/order/cancel',
|
||||
pay: 'clientApi/pay/doPay',
|
||||
receipt: 'clientApi/order/receipt',
|
||||
}
|
||||
|
||||
// 当前用户待处理的订单数量
|
||||
export function todoCounts(param) {
|
||||
return request.get(api.todoCounts, param)
|
||||
}
|
||||
|
||||
// 我的订单列表
|
||||
export function list(param, option) {
|
||||
return request.post(api.list, param, option)
|
||||
}
|
||||
|
||||
// 订单详情
|
||||
export function detail(orderId, param) {
|
||||
return request.get(api.detail, { orderId, ...param })
|
||||
}
|
||||
|
||||
// 取消订单
|
||||
export function cancel(orderId, data) {
|
||||
return request.get(api.cancel, { orderId, ...data })
|
||||
}
|
||||
|
||||
// 立即支付
|
||||
export function pay(orderId, payType, param) {
|
||||
return request.get(api.pay, { orderId, payType, ...param })
|
||||
}
|
||||
|
||||
// 确认收货
|
||||
export function receipt(orderId, data) {
|
||||
return request.get(api.receipt, { orderId, ...data })
|
||||
}
|
11
api/page.js
Normal file
11
api/page.js
Normal file
@ -0,0 +1,11 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const apiUri = {
|
||||
home: 'clientApi/page/home'
|
||||
}
|
||||
|
||||
// 页面数据
|
||||
export function home() {
|
||||
return request.get(apiUri.home)
|
||||
}
|
17
api/points/log.js
Normal file
17
api/points/log.js
Normal file
@ -0,0 +1,17 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/points/list',
|
||||
gift: 'clientApi/points/doGive'
|
||||
}
|
||||
|
||||
// 积分明细列表
|
||||
export const list = (param) => {
|
||||
return request.get(api.list, param)
|
||||
}
|
||||
|
||||
// 积分转赠
|
||||
export const gift = (param) => {
|
||||
return request.post(api.gift, param)
|
||||
}
|
30
api/refund.js
Normal file
30
api/refund.js
Normal file
@ -0,0 +1,30 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
list: 'clientApi/refund/list',
|
||||
goods: 'clientApi/refund/goods',
|
||||
apply: 'clientApi/refund/submit',
|
||||
detail: 'clientApi/refund/detail',
|
||||
delivery: 'clientApi/refund/delivery'
|
||||
}
|
||||
|
||||
// 售后单列表
|
||||
export const list = (param, option) => {
|
||||
return request.get(api.list, param, option)
|
||||
}
|
||||
|
||||
// 申请售后
|
||||
export const apply = (orderId, data) => {
|
||||
return request.post(api.apply, { orderId, type: data.type, remark: data.content, images: data.images })
|
||||
}
|
||||
|
||||
// 售后单详情
|
||||
export const detail = (refundId, param) => {
|
||||
return request.get(api.detail, { refundId, ...param })
|
||||
}
|
||||
|
||||
// 用户发货
|
||||
export const delivery = (refundId, data) => {
|
||||
return request.post(api.delivery, { refundId, form: data })
|
||||
}
|
17
api/region.js
Normal file
17
api/region.js
Normal file
@ -0,0 +1,17 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
all: 'clientApi/region/all',
|
||||
tree: 'clientApi/region/tree'
|
||||
}
|
||||
|
||||
// 获取所有地区
|
||||
export const all = (param) => {
|
||||
return request.get(api.all, param)
|
||||
}
|
||||
|
||||
// 获取所有地区(树状)
|
||||
export const tree = (param) => {
|
||||
return request.get(api.tree, param)
|
||||
}
|
29
api/setting.js
Normal file
29
api/setting.js
Normal file
@ -0,0 +1,29 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
recharge: 'clientApi/system/recharge',
|
||||
system: 'clientApi/system/config',
|
||||
storeList: 'clientApi/store/list',
|
||||
storeDetail: 'clientApi/store/detail',
|
||||
}
|
||||
|
||||
// 充值配置
|
||||
export function recharge() {
|
||||
return request.get(api.recharge)
|
||||
}
|
||||
|
||||
// 系统配置
|
||||
export function systemConfig() {
|
||||
return request.get(api.system)
|
||||
}
|
||||
|
||||
// 店铺列表
|
||||
export const storeList = (keyword) => {
|
||||
return request.post(api.storeList, { keyword })
|
||||
}
|
||||
|
||||
// 店铺详情
|
||||
export function storeDetail() {
|
||||
return request.get(api.storeDetail)
|
||||
}
|
22
api/settlement.js
Normal file
22
api/settlement.js
Normal file
@ -0,0 +1,22 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
submit: 'clientApi/settlement/submit',
|
||||
prePay: 'clientApi/pay/prePay',
|
||||
}
|
||||
|
||||
// 结算台订单提交
|
||||
export const submit = (targetId, selectNum, type, remark, payAmount, usePoint, couponId, cartIds, goodsId, skuId, buyNum, orderMode, payType) => {
|
||||
return request.post(api.submit, { targetId, selectNum, type, remark, payAmount, usePoint, couponId, cartIds, goodsId, skuId, buyNum, orderMode, payType})
|
||||
}
|
||||
|
||||
// 支付前查询
|
||||
export const prePay = (param) => {
|
||||
return request.get(api.prePay, param)
|
||||
}
|
||||
|
||||
// 发起收款
|
||||
export const doCashier = (param) => {
|
||||
return request.post(api.submit, param)
|
||||
}
|
23
api/upload.js
Normal file
23
api/upload.js
Normal file
@ -0,0 +1,23 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
uploadUrl: 'clientApi/file/upload'
|
||||
}
|
||||
|
||||
// 图片上传
|
||||
export const image = (files) => {
|
||||
// 文件上传大小, 2M
|
||||
const maxSize = 1024 * 1024 * 2;
|
||||
// 执行上传
|
||||
return new Promise((resolve, reject) => {
|
||||
request.urlFileUpload({ files, maxSize })
|
||||
.then(result => {
|
||||
const fileIds = result.map(item => {
|
||||
return item.data;
|
||||
})
|
||||
resolve(fileIds, result)
|
||||
})
|
||||
.catch(err => reject(err))
|
||||
})
|
||||
}
|
51
api/user.js
Normal file
51
api/user.js
Normal file
@ -0,0 +1,51 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
userInfo: 'clientApi/user/info',
|
||||
qrCode: 'clientApi/user/qrCode',
|
||||
assets: 'clientApi/user/asset/',
|
||||
setting: 'clientApi/user/setting',
|
||||
defaultStore: 'clientApi/user/defaultStore',
|
||||
save: 'clientApi/user/saveInfo'
|
||||
}
|
||||
|
||||
// 当前登录的用户信息
|
||||
export const info = (param, option) => {
|
||||
const options = {
|
||||
isPrompt: true, //(默认 true 说明:本接口抛出的错误是否提示)
|
||||
load: true, //(默认 true 说明:本接口是否提示加载动画)
|
||||
...option
|
||||
}
|
||||
return request.get(api.userInfo, param, options)
|
||||
}
|
||||
|
||||
// 当前登录的会员码信息
|
||||
export const qrCode = (param, option) => {
|
||||
const options = {
|
||||
isPrompt: true,
|
||||
load: true,
|
||||
...option
|
||||
}
|
||||
return request.get(api.qrCode, param, options)
|
||||
}
|
||||
|
||||
// 账户资产
|
||||
export const assets = (param, option) => {
|
||||
return request.get(api.assets+param)
|
||||
}
|
||||
|
||||
// 获取会员设置
|
||||
export const setting = (param, option) => {
|
||||
return request.get(api.setting, param)
|
||||
}
|
||||
|
||||
// 设置会员的默认店铺
|
||||
export const defaultStore = (storeId) => {
|
||||
return request.get(api.defaultStore, { storeId })
|
||||
}
|
||||
|
||||
// 保存会员信息
|
||||
export const save = (param, option) => {
|
||||
return request.post(api.save, param)
|
||||
}
|
11
api/user/coupon.js
Normal file
11
api/user/coupon.js
Normal file
@ -0,0 +1,11 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// api地址
|
||||
const api = {
|
||||
receive: 'clientApi/user/coupon/receive'
|
||||
}
|
||||
|
||||
// 优惠券列表
|
||||
export const receive = (data) => {
|
||||
return request.post(api.receive, data)
|
||||
}
|
26
app.scss
Normal file
26
app.scss
Normal file
@ -0,0 +1,26 @@
|
||||
/* utils.scss */
|
||||
@import "/utils/utils.scss";
|
||||
|
||||
page {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg) scale(1);
|
||||
}
|
||||
}
|
3
common/constant/index.js
Normal file
3
common/constant/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import paginate from './paginate'
|
||||
|
||||
export { paginate }
|
7
common/constant/paginate.js
Normal file
7
common/constant/paginate.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
content: [], // 列表数据
|
||||
currentPage: 1, // 当前页码
|
||||
totalPages: 1, // 最大页码
|
||||
pageSize: 15, // 每页记录数
|
||||
totalElements: 0, // 总记录数
|
||||
}
|
10
common/enum/coupon/ApplyRange.js
Normal file
10
common/enum/coupon/ApplyRange.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:优惠券适用范围
|
||||
* ApplyRangeEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'ALL', name: '全部商品', value: 10 },
|
||||
{ key: 'SOME_GOODS', name: '指定商品', value: 20 }
|
||||
])
|
11
common/enum/coupon/CouponType.js
Normal file
11
common/enum/coupon/CouponType.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:卡券类型
|
||||
* CouponTypeEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'C', name: '优惠券', value: 1000 },
|
||||
{ key: 'P', name: '储值卡', value: 2000 },
|
||||
{ key: 'T', name: '计次卡', value: 3000 }
|
||||
])
|
10
common/enum/coupon/ExpireType.js
Normal file
10
common/enum/coupon/ExpireType.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:优惠券到期类型
|
||||
* ExpireTypeEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'RECEIVE', name: '领取后', value: 10 },
|
||||
{ key: 'FIXED_TIME', name: '固定时间', value: 20 }
|
||||
])
|
5
common/enum/coupon/index.js
Normal file
5
common/enum/coupon/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import ApplyRangeEnum from './ApplyRange'
|
||||
import ExpireTypeEnum from './ExpireType'
|
||||
import CouponTypeEnum from './CouponType'
|
||||
|
||||
export { ApplyRangeEnum, CouponTypeEnum, ExpireTypeEnum }
|
85
common/enum/enum.js
Normal file
85
common/enum/enum.js
Normal file
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 枚举类
|
||||
* Enum.IMAGE.name => "图片"
|
||||
* Enum.getNameByKey('IMAGE') => "图片"
|
||||
* Enum.getValueByKey('IMAGE') => 10
|
||||
* Enum.getNameByValue(10) => "图片"
|
||||
* Enum.getData() => [{key: "IMAGE", name: "图片", value: 10}]
|
||||
*/
|
||||
class Enum {
|
||||
constructor (param) {
|
||||
const keyArr = []
|
||||
const valueArr = []
|
||||
|
||||
if (!Array.isArray(param)) {
|
||||
throw new Error('param is not an array!')
|
||||
}
|
||||
|
||||
param.map(element => {
|
||||
if (!element.key || !element.name) {
|
||||
return
|
||||
}
|
||||
// 保存key值组成的数组,方便A.getName(name)类型的调用
|
||||
keyArr.push(element.key)
|
||||
valueArr.push(element.value)
|
||||
// 根据key生成不同属性值,以便A.B.name类型的调用
|
||||
this[element.key] = element
|
||||
if (element.key !== element.value) {
|
||||
this[element.value] = element
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 保存源数组
|
||||
this.data = param
|
||||
this.keyArr = keyArr
|
||||
this.valueArr = valueArr
|
||||
|
||||
// 防止被修改
|
||||
// Object.freeze(this)
|
||||
}
|
||||
|
||||
// 根据key得到对象
|
||||
keyOf (key) {
|
||||
return this.data[this.keyArr.indexOf(key)]
|
||||
}
|
||||
|
||||
// 根据key得到对象
|
||||
valueOf (key) {
|
||||
return this.data[this.valueArr.indexOf(key)]
|
||||
}
|
||||
|
||||
// 根据key获取name值
|
||||
getNameByKey (key) {
|
||||
const prop = this.keyOf(key)
|
||||
if (!prop) {
|
||||
throw new Error('No enum constant' + key)
|
||||
}
|
||||
return prop.name
|
||||
}
|
||||
|
||||
// 根据value获取name值
|
||||
getNameByValue (value) {
|
||||
const prop = this.valueOf(value)
|
||||
if (!prop) {
|
||||
throw new Error('No enum constant' + value)
|
||||
}
|
||||
return prop.name
|
||||
}
|
||||
|
||||
// 根据key获取value值
|
||||
getValueByKey (key) {
|
||||
const prop = this.keyOf(key)
|
||||
if (!prop) {
|
||||
throw new Error('No enum constant' + key)
|
||||
}
|
||||
return prop.key
|
||||
}
|
||||
|
||||
// 返回源数组
|
||||
getData () {
|
||||
return this.data
|
||||
}
|
||||
}
|
||||
|
||||
export default Enum
|
10
common/enum/order/DeliveryStatus.js
Normal file
10
common/enum/order/DeliveryStatus.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:订单发货状态
|
||||
* DeliveryStatusEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'NOT_DELIVERED', name: '未发货', value: 10 },
|
||||
{ key: 'DELIVERED', name: '已发货', value: 20 }
|
||||
])
|
9
common/enum/order/DeliveryType.js
Normal file
9
common/enum/order/DeliveryType.js
Normal file
@ -0,0 +1,9 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:配送方式
|
||||
* DeliveryTypeEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'EXPRESS', name: '快递配送', value: 10 }
|
||||
])
|
11
common/enum/order/OrderSource.js
Normal file
11
common/enum/order/OrderSource.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:订单来源
|
||||
* OrderSourceEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'MASTER', name: '普通订单', value: 10 },
|
||||
{ key: 'BARGAIN', name: '砍价订单', value: 20 },
|
||||
{ key: 'SHARP', name: '秒杀订单', value: 30 }
|
||||
])
|
16
common/enum/order/OrderStatus.js
Normal file
16
common/enum/order/OrderStatus.js
Normal file
@ -0,0 +1,16 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:订单状态
|
||||
* OrderStatusEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'CREATED', name: '待支付', value: 'A' },
|
||||
{ key: 'PAID', name: '已支付', value: 'B' },
|
||||
{ key: 'CANCEL', name: '已取消', value: 'C' },
|
||||
{ key: 'DELIVERY', name: '待发货', value: 'D' },
|
||||
{ key: 'DELIVERED', name: '已发货', value: 'E' },
|
||||
{ key: 'RECEIVED', name: '已收货', value: 'F' },
|
||||
{ key: 'DELETED', name: '已删除', value: 'G' },
|
||||
{ key: 'REFUND', name: '已退款', value: 'H' },
|
||||
])
|
10
common/enum/order/PayStatus.js
Normal file
10
common/enum/order/PayStatus.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:订单支付状态
|
||||
* PayStatusEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'PENDING', name: '待支付', value: 10 },
|
||||
{ key: 'SUCCESS', name: '已支付', value: 20 }
|
||||
])
|
13
common/enum/order/PayType.js
Normal file
13
common/enum/order/PayType.js
Normal file
@ -0,0 +1,13 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:订单支付方式
|
||||
* PayTypeEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'BALANCE', name: '余额支付', value: 'BALANCE' },
|
||||
{ key: 'WECHAT', name: '微信支付', value: 'JSAPI' },
|
||||
{ key: 'CASH', name: '余额支付', value: 'CASH' },
|
||||
{ key: 'MICROPAY', name: '微信扫码支付', value: 'MICROPAY' },
|
||||
{ key: 'ALISCAN', name: '支付宝支付', value: 'ALISCAN' },
|
||||
])
|
10
common/enum/order/ReceiptStatus.js
Normal file
10
common/enum/order/ReceiptStatus.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:订单收货状态
|
||||
* ReceiptStatusEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'NOT_RECEIVED', name: '未收货', value: 10 },
|
||||
{ key: 'RECEIVED', name: '已收货', value: 20 }
|
||||
])
|
17
common/enum/order/index.js
Normal file
17
common/enum/order/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import DeliveryStatusEnum from './DeliveryStatus'
|
||||
import DeliveryTypeEnum from './DeliveryType'
|
||||
import OrderSourceEnum from './OrderSource'
|
||||
import OrderStatusEnum from './OrderStatus'
|
||||
import PayStatusEnum from './PayStatus'
|
||||
import PayTypeEnum from './PayType'
|
||||
import ReceiptStatusEnum from './ReceiptStatus'
|
||||
|
||||
export {
|
||||
DeliveryStatusEnum,
|
||||
DeliveryTypeEnum,
|
||||
OrderSourceEnum,
|
||||
OrderStatusEnum,
|
||||
PayStatusEnum,
|
||||
PayTypeEnum,
|
||||
ReceiptStatusEnum
|
||||
}
|
11
common/enum/order/refund/AuditStatus.js
Normal file
11
common/enum/order/refund/AuditStatus.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Enum from '../../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:商家审核状态
|
||||
* AuditStatusEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'WAIT', name: '待审核', value: 0 },
|
||||
{ key: 'REVIEWED', name: '已同意', value: 10 },
|
||||
{ key: 'REJECTED', name: '已拒绝', value: 20 }
|
||||
])
|
12
common/enum/order/refund/RefundStatus.js
Normal file
12
common/enum/order/refund/RefundStatus.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Enum from '../../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:售后单状态
|
||||
* RefundStatusEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'A', name: '待审核' },
|
||||
{ key: 'B', name: '已同意' },
|
||||
{ key: 'C', name: '已拒绝' },
|
||||
{ key: 'D', name: '已取消' }
|
||||
])
|
10
common/enum/order/refund/RefundType.js
Normal file
10
common/enum/order/refund/RefundType.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Enum from '../../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:售后类型
|
||||
* RefundTypeEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'RETURN', name: '退货退款', value: 'return' },
|
||||
{ key: 'EXCHANGE', name: '换货', value: 'exchange' }
|
||||
])
|
9
common/enum/order/refund/index.js
Normal file
9
common/enum/order/refund/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
import AuditStatusEnum from './AuditStatus'
|
||||
import RefundStatusEnum from './RefundStatus'
|
||||
import RefundTypeEnum from './RefundType'
|
||||
|
||||
export {
|
||||
AuditStatusEnum,
|
||||
RefundStatusEnum,
|
||||
RefundTypeEnum
|
||||
}
|
19
common/enum/setting/Key.js
Normal file
19
common/enum/setting/Key.js
Normal file
@ -0,0 +1,19 @@
|
||||
import Enum from '../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:设置项索引
|
||||
* SettingKeyEnum
|
||||
*/
|
||||
export default new Enum([{
|
||||
key: 'PAGE_CATEGORY_TEMPLATE',
|
||||
name: '分类页模板',
|
||||
value: 'page_category_template'
|
||||
}, {
|
||||
key: 'POINTS',
|
||||
name: '积分设置',
|
||||
value: 'points'
|
||||
}, {
|
||||
key: 'RECHARGE',
|
||||
name: '充值设置',
|
||||
value: 'recharge'
|
||||
}])
|
11
common/enum/store/page/category/Style.js
Normal file
11
common/enum/store/page/category/Style.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Enum from '../../../enum'
|
||||
|
||||
/**
|
||||
* 枚举类:地址类型
|
||||
* PageCategoryStyleEnum
|
||||
*/
|
||||
export default new Enum([
|
||||
{ key: 'ONE_LEVEL_BIG', name: '一级分类[大图]', value: 10 },
|
||||
{ key: 'ONE_LEVEL_SMALL', name: '一级分类[小图]', value: 11 },
|
||||
{ key: 'TWO_LEVEL', name: '二级分类', value: 20 }
|
||||
])
|
3
common/enum/store/page/category/index.js
Normal file
3
common/enum/store/page/category/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import PageCategoryStyleEnum from './Style'
|
||||
|
||||
export { PageCategoryStyleEnum }
|
57
common/model/Region.js
Normal file
57
common/model/Region.js
Normal file
@ -0,0 +1,57 @@
|
||||
import * as Api from '@/api/region'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const REGION_TREE = 'region_tree'
|
||||
|
||||
/**
|
||||
* 商品分类 model类
|
||||
* RegionModel
|
||||
*/
|
||||
export default {
|
||||
|
||||
// 从服务端获取全部地区数据(树状)
|
||||
getTreeDataFromApi () {
|
||||
return new Promise((resolve, reject) => {
|
||||
Api.tree().then(result => resolve(result.data.data))
|
||||
})
|
||||
},
|
||||
|
||||
// 获取所有地区(树状)
|
||||
getTreeData () {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 判断缓存中是否存在
|
||||
const data = storage.get(REGION_TREE)
|
||||
// 从服务端获取全部地区数据
|
||||
if (data) {
|
||||
resolve(data)
|
||||
} else {
|
||||
this.getTreeDataFromApi().then(list => {
|
||||
// 缓存24小时
|
||||
storage.set(REGION_TREE, list, 1 * 24 * 60 * 60 * 1000)
|
||||
resolve(list)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取所有地区的总数
|
||||
getCitysCount () {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 获取所有地区(树状)
|
||||
this.getTreeData().then(data => {
|
||||
const cityIds = []
|
||||
// 遍历省份
|
||||
for (const pidx in data) {
|
||||
const province = data[pidx]
|
||||
// 遍历城市
|
||||
for (const cidx in province.city) {
|
||||
const cityItem = province.city[cidx]
|
||||
cityIds.push(cityItem.id)
|
||||
}
|
||||
}
|
||||
resolve(cityIds.length)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
58
common/model/Setting.js
Normal file
58
common/model/Setting.js
Normal file
@ -0,0 +1,58 @@
|
||||
import * as SettingApi from '@/api/setting'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const CACHE_KEY = 'Setting'
|
||||
|
||||
// 写入缓存, 到期时间30分钟
|
||||
const setStorage = (data) => {
|
||||
const expireTime = 30 * 60;
|
||||
storage.set(CACHE_KEY, data, expireTime);
|
||||
}
|
||||
|
||||
// 获取缓存中的数据
|
||||
const getStorage = () => {
|
||||
return storage.get(CACHE_KEY);
|
||||
}
|
||||
|
||||
// 获取系统设置
|
||||
const getApiData = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
SettingApi.data()
|
||||
.then(result => {
|
||||
resolve(result.data.setting);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商城设置
|
||||
* 有缓存的情况下返回缓存, 没有缓存从后端api获取
|
||||
* @param {bool} isCache 是否从缓存中获取
|
||||
*/
|
||||
const data = (isCache = true) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cacheData = getStorage()
|
||||
if (isCache && cacheData) {
|
||||
resolve(cacheData)
|
||||
} else {
|
||||
getApiData().then(data => {
|
||||
setStorage(data)
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取指定的系统设置
|
||||
const item = (key, isCache = true) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
data(isCache).then(setting => {
|
||||
resolve(setting[key])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
data,
|
||||
item
|
||||
}
|
42
components/actionsheet/index.js
Normal file
42
components/actionsheet/index.js
Normal file
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
Component({
|
||||
externalClasses: ['mask-class', 'container-class'],
|
||||
properties: {
|
||||
actions: {
|
||||
type: Array,
|
||||
value: []
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
cancelWithMask: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMaskClick: function onMaskClick() {
|
||||
if (this.data.cancelWithMask) {
|
||||
this.cancelClick();
|
||||
}
|
||||
},
|
||||
cancelClick: function cancelClick() {
|
||||
this.triggerEvent('cancel');
|
||||
},
|
||||
handleBtnClick: function handleBtnClick(_ref) {
|
||||
var _ref$currentTarget = _ref.currentTarget,
|
||||
currentTarget = _ref$currentTarget === undefined ? {} : _ref$currentTarget;
|
||||
|
||||
var dataset = currentTarget.dataset || {};
|
||||
var index = dataset.index;
|
||||
|
||||
this.triggerEvent('actionclick', { index: index });
|
||||
}
|
||||
}
|
||||
});
|
6
components/actionsheet/index.json
Normal file
6
components/actionsheet/index.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"zan-btn": "../btn/index"
|
||||
}
|
||||
}
|
39
components/actionsheet/index.wxml
Normal file
39
components/actionsheet/index.wxml
Normal file
@ -0,0 +1,39 @@
|
||||
<view class="zan-actionsheet {{ show ? 'zan-actionsheet--show' : '' }}">
|
||||
<view
|
||||
class="mask-class zan-actionsheet__mask"
|
||||
bindtap="onMaskClick"
|
||||
></view>
|
||||
<view class="container-class zan-actionsheet__container">
|
||||
<!-- 选项按钮 -->
|
||||
<zan-btn
|
||||
wx:for="{{ actions }}"
|
||||
wx:key="this"
|
||||
bind:btnclick="handleBtnClick"
|
||||
data-index="{{ index }}"
|
||||
open-type="{{ item.openType }}"
|
||||
custom-class="zan-actionsheet__btn"
|
||||
loading="{{ item.loading }}"
|
||||
>
|
||||
<!-- 自定义组件控制 slot 样式有问题,故在 slot 容器上传入 loading 信息 -->
|
||||
<view class="zan-actionsheet__btn-content {{ item.loading ? 'zan-actionsheet__btn--loading' : '' }}">
|
||||
<view class="zan-actionsheet__name">{{ item.name }}</view>
|
||||
<view
|
||||
wx:if="{{ item.subname }}"
|
||||
class="zan-actionsheet__subname">
|
||||
{{ item.subname }}
|
||||
</view>
|
||||
</view>
|
||||
</zan-btn>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
wx:if="{{ cancelText }}"
|
||||
class="zan-actionsheet__footer"
|
||||
>
|
||||
<zan-btn
|
||||
custom-class="zan-actionsheet__btn"
|
||||
catchtap="cancelClick"
|
||||
>{{ cancelText }}</zan-btn>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
86
components/actionsheet/index.wxss
Normal file
86
components/actionsheet/index.wxss
Normal file
@ -0,0 +1,86 @@
|
||||
.zan-actionsheet {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.zan-actionsheet__mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.zan-actionsheet__container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #f8f8f8;
|
||||
-webkit-transform: translate3d(0, 50%, 0);
|
||||
transform: translate3d(0, 50%, 0);
|
||||
-webkit-transform-origin: center;
|
||||
transform-origin: center;
|
||||
-webkit-transition: all 0.2s ease;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 11;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.zan-actionsheet__btn {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.zan-actionsheet__footer .zan-actionsheet__btn {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.zan-actionsheet__btn-content {
|
||||
display: -webkit-box;
|
||||
display: flex;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.zan-actionsheet__subname {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.zan-actionsheet__name, .zan-actionsheet__subname {
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
}
|
||||
|
||||
.zan-actionsheet__btn.zan-btn:last-child::after {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.zan-actionsheet__subname {
|
||||
margin-left: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.zan-actionsheet__footer {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.zan-actionsheet__btn--loading .zan-actionsheet__subname {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.zan-actionsheet--show .zan-actionsheet__container {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.zan-actionsheet--show .zan-actionsheet__mask {
|
||||
display: block;
|
||||
}
|
25
components/bian-keywords/iconfont/iconfont.css
Normal file
25
components/bian-keywords/iconfont/iconfont.css
Normal file
@ -0,0 +1,25 @@
|
||||
@font-face {font-family: "iconfont";
|
||||
src: url('~@/static/iconfont/iconfont.eot?t=1590026368431'); /* IE9 */
|
||||
src: url('~@/static/iconfont/iconfont.eot?t=1590026368431#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAANcAAsAAAAAB2gAAAMOAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgqCTIIbATYCJAMMCwgABCAFhG0HPBtnBsgekiSCCAEFAIljCIAi+Bp73Xd3QXRIKhMdHh+ViULQwAVUZSMrKzs1HjQb8f93LcvsbAE4b23LkkEhDPxkATMpOUB4vqzaA9Qt2zvWwHpT5GDE57mc3iiQKJy/bTmttW3jUS/AOKDA9sQo4EAKJCBvGLugJd4nUK9XCmGlvLYBeSvMZYE4M1EJeWeyisIatUJ1xd4i3iIPtem17hrxJvh+/Ecz4U1SlZlrVw/KVFT4q1/B4pB7SIdHIN10YbBDZExDCrFRGV0nRtVponrVrNd6kWpFSFMV3G4CrWav3ugfLxHVzOZGMA5riV9ejpLgV/A+CWRQ7THqO5BzxO68JJRhf//OUI1uDkdGbsyEq8SVCV8bigBVJmJ9NoqCOaruDDqzE9W8Obs+FN6w5jcT2bQxnOqIaFzfavSfuaChneG4monP/QK+3CTo9q7Z2ho7LyJg80gQHx9G+q+F5/dWVl27Fn465iozfe29kHZXVlUQc34YrtJX3gnwEvQrMuTu+f5BX8TPcHpwQFb9CONG/ogkb3B0t3D2Niz91cn91+Tzs8dbHPY2I0j/Qh/0OVHwHjq52/319t4QOBFouH/760gPhMrn/En0g3yj2/4Nv/H1rHypOyj3f21DAd+/CY+CbOVz0d1ee5Kx4KXrOvYVzWD6clG0jtaIZXOptARU84QKMpPtPg63d3VuJtTqSpDU6EFWa5gs7GlUaTCLarXmUW9KxeEGHRhRlDpMGgcIrc6QNPuGrNUNWdgfUKXbd1RrDRH11iLszAajYfItYSBxULE8iE2Kw07sAPcNW+uA9lolljYEvCZgmqmAM9My8o0KsAObYoPWR7M4J5gwhw2Xg8fAanVgJ3OYQeFpRs6d2enppOpFaYrDhnwtY4CEAypMNggzUTjYEW8451v4fB1A9bKSsAZGVbUJYDSm8bFMaTI6kBVWeyfGrTyj6UNl4TgCIxgHG6wchIHVqjpgzupZZkDBpTEOKDtlS0eDSFdr2vJ22/sdgXrmmTlS5CiqD3slu2zykSXFojndCCAEAAA=') format('woff2'),
|
||||
url('~@/static/iconfont/iconfont.woff?t=1590026368431') format('woff'),
|
||||
url('~@/static/iconfont/iconfont.ttf?t=1590026368431') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
|
||||
url('~@/static/iconfont/iconfont.svg?t=1590026368431#iconfont') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-guanbi:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.icon-backspace:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
BIN
components/bian-keywords/iconfont/iconfont.eot
Normal file
BIN
components/bian-keywords/iconfont/iconfont.eot
Normal file
Binary file not shown.
1
components/bian-keywords/iconfont/iconfont.js
Normal file
1
components/bian-keywords/iconfont/iconfont.js
Normal file
@ -0,0 +1 @@
|
||||
!function(e){var t,n,o,c,i,a,l,d='<svg><symbol id="icon-guanbi" viewBox="0 0 1024 1024"><path d="M574.55 522.35L904.4 192.5c16.65-16.65 16.65-44.1 0-60.75l-1.8-1.8c-16.65-16.65-44.1-16.65-60.75 0L512 460.25l-329.85-330.3c-16.65-16.65-44.1-16.65-60.75 0l-1.8 1.8c-17.1 16.65-17.1 44.1 0 60.75l329.85 329.85L119.6 852.2c-16.65 16.65-16.65 44.1 0 60.75l1.8 1.8c16.65 16.65 44.1 16.65 60.75 0L512 584.9l329.85 329.85c16.65 16.65 44.1 16.65 60.75 0l1.8-1.8c16.65-16.65 16.65-44.1 0-60.75L574.55 522.35z" ></path></symbol><symbol id="icon-backspace" viewBox="0 0 1024 1024"><path d="M486.989 617.262a26.127 26.127 0 0 1-18.473-44.6l158.27-158.275a26.127 26.127 0 0 1 36.95 36.946l-158.27 158.28a26.06 26.06 0 0 1-18.477 7.65z" fill="#2C2C2C" ></path><path d="M645.258 617.262a26.05 26.05 0 0 1-18.473-7.65l-158.27-158.279a26.122 26.122 0 0 1 0-36.946 26.112 26.112 0 0 1 36.947 0l158.27 158.275a26.138 26.138 0 0 1-18.474 44.6z" fill="#2C2C2C" ></path><path d="M804.93 792.806H356.307c-25.856 0-50.181-11.386-66.744-31.237L115.804 552.852c-9.58-11.863-14.715-26.265-14.71-40.857 0.006-13.978 4.245-27.11 12.263-37.975 0.307-0.415 0.62-0.82 0.952-1.208L289.224 262.82a86.574 86.574 0 0 1 67.082-31.632H804.93c47.211 0 85.621 38.406 85.621 85.617v390.38c0 47.211-38.41 85.621-85.621 85.621zM155.09 505.492c-0.824 1.234-1.74 3.2-1.74 6.518 0 2.652 1.044 5.463 2.866 7.721l173.486 208.39a35.02 35.02 0 0 0 26.604 12.436H804.93a33.408 33.408 0 0 0 33.372-33.372v-390.38a33.408 33.408 0 0 0-33.372-33.367H356.306a35.062 35.062 0 0 0-26.824 12.687L155.09 505.492z" fill="#2C2C2C" ></path></symbol></svg>',s=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(s&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(e){console&&console.log(e)}}function r(){a||(a=!0,c())}n=function(){var e,t,n,o,c,i=document.createElement("div");i.innerHTML=d,d=null,(e=i.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",t=e,(n=document.body).firstChild?(o=t,(c=n.firstChild).parentNode.insertBefore(o,c)):n.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(n,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),n()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(c=n,i=e.document,a=!1,(l=function(){try{i.documentElement.doScroll("left")}catch(e){return void setTimeout(l,50)}r()})(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,r())})}(window);
|
23
components/bian-keywords/iconfont/iconfont.json
Normal file
23
components/bian-keywords/iconfont/iconfont.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": "1831825",
|
||||
"name": "keywords",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "9673157",
|
||||
"name": "关 闭",
|
||||
"font_class": "guanbi",
|
||||
"unicode": "e607",
|
||||
"unicode_decimal": 58887
|
||||
},
|
||||
{
|
||||
"icon_id": "9512492",
|
||||
"name": "退格",
|
||||
"font_class": "backspace",
|
||||
"unicode": "e610",
|
||||
"unicode_decimal": 58896
|
||||
}
|
||||
]
|
||||
}
|
32
components/bian-keywords/iconfont/iconfont.svg
Normal file
32
components/bian-keywords/iconfont/iconfont.svg
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<!--
|
||||
2013-9-30: Created.
|
||||
-->
|
||||
<svg>
|
||||
<metadata>
|
||||
Created by iconfont
|
||||
</metadata>
|
||||
<defs>
|
||||
|
||||
<font id="iconfont" horiz-adv-x="1024" >
|
||||
<font-face
|
||||
font-family="iconfont"
|
||||
font-weight="500"
|
||||
font-stretch="normal"
|
||||
units-per-em="1024"
|
||||
ascent="896"
|
||||
descent="-128"
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="guanbi" unicode="" d="M574.55 373.65L904.4 703.5c16.65 16.65 16.65 44.1 0 60.75l-1.8 1.8c-16.65 16.65-44.1 16.65-60.75 0L512 435.75l-329.85 330.3c-16.65 16.65-44.1 16.65-60.75 0l-1.8-1.8c-17.1-16.65-17.1-44.1 0-60.75l329.85-329.85L119.6 43.8c-16.65-16.65-16.65-44.1 0-60.75l1.8-1.8c16.65-16.65 44.1-16.65 60.75 0L512 311.1l329.85-329.85c16.65-16.65 44.1-16.65 60.75 0l1.8 1.8c16.65 16.65 16.65 44.1 0 60.75L574.55 373.65z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="backspace" unicode="" d="M486.989 278.73800000000006a26.127 26.127 0 0 0-18.473 44.6l158.27 158.275a26.127 26.127 0 0 0 36.95-36.946l-158.27-158.28a26.06 26.06 0 0 0-18.477-7.65zM645.258 278.73800000000006a26.05 26.05 0 0 0-18.473 7.65l-158.27 158.279a26.122 26.122 0 0 0 0 36.946 26.112 26.112 0 0 0 36.947 0l158.27-158.275a26.138 26.138 0 0 0-18.474-44.6zM804.93 103.19399999999996H356.307c-25.856 0-50.181 11.386-66.744 31.237L115.804 343.148c-9.58 11.863-14.715 26.265-14.71 40.857 0.006 13.978 4.245 27.11 12.263 37.975 0.307 0.415 0.62 0.82 0.952 1.208L289.224 633.1800000000001a86.574 86.574 0 0 0 67.082 31.632H804.93c47.211 0 85.621-38.406 85.621-85.617v-390.38c0-47.211-38.41-85.621-85.621-85.621zM155.09 390.508c-0.824-1.234-1.74-3.2-1.74-6.518 0-2.652 1.044-5.463 2.866-7.721l173.486-208.39a35.02 35.02 0 0 1 26.604-12.436H804.93a33.408 33.408 0 0 1 33.372 33.372v390.38a33.408 33.408 0 0 1-33.372 33.367H356.306a35.062 35.062 0 0 1-26.824-12.687L155.09 390.508z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
|
||||
|
||||
</font>
|
||||
</defs></svg>
|
After Width: | Height: | Size: 1.9 KiB |
BIN
components/bian-keywords/iconfont/iconfont.ttf
Normal file
BIN
components/bian-keywords/iconfont/iconfont.ttf
Normal file
Binary file not shown.
BIN
components/bian-keywords/iconfont/iconfont.woff
Normal file
BIN
components/bian-keywords/iconfont/iconfont.woff
Normal file
Binary file not shown.
BIN
components/bian-keywords/iconfont/iconfont.woff2
Normal file
BIN
components/bian-keywords/iconfont/iconfont.woff2
Normal file
Binary file not shown.
270
components/bian-keywords/index.vue
Normal file
270
components/bian-keywords/index.vue
Normal file
@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<view class="mark" :class="[show_key ? '' : 'hidden']">
|
||||
<view class="kong"></view>
|
||||
<!-- 信息框 -->
|
||||
<view class="msg">
|
||||
<!-- 关闭按钮 -->
|
||||
<view class="img iconfont icon-guanbi" @tap="closeFuc">
|
||||
</view>
|
||||
<view class="title"> {{ title ? title : "请输入您的密码" }}</view>
|
||||
<view class="subTitle" v-show="show_subTitle && price > 0">
|
||||
付款金额:{{price}}
|
||||
</view>
|
||||
<view class="pswBox">
|
||||
<view v-for="item in 6" :key="item" class="content_item">{{password[item] ? '●' : ''}}</view>
|
||||
</view>
|
||||
<view class="forget" v-if="false" @tap="forgetFuc">忘记密码?</view>
|
||||
</view>
|
||||
<!-- 数字键盘 -->
|
||||
<view class="numeric">
|
||||
<!-- 正常模式 -->
|
||||
<view class="num" v-if="mix" v-for="(item,index) in num1" :key="index" :class="item == 10 ? 'amend1' : item == 12 ? 'amend3 iconfont icon-backspace' : ''" @tap="press({num:item})">
|
||||
{{item == 10 ? '' : item == 11 ? '0' : item == 12 ? '': item}}
|
||||
</view>
|
||||
<!-- 混淆模式 -->
|
||||
<view class="num" v-else v-for="(item,index) in num" :key="index" :class="item == 10 ? 'amend1' : item == 12 ? 'amend3 iconfont icon-backspace' : ''" @tap="press({num:item})">
|
||||
{{item == 10 ? '' : item == 11 ? '0' : item == 12 ? '': item}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props:{
|
||||
show_key:Boolean,
|
||||
price:String,
|
||||
title:String,
|
||||
show_subTitle:{
|
||||
default:true
|
||||
},
|
||||
mix:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
num:[], // 乱序
|
||||
num1:[1,2,3,4,5,6,7,8,9,10,11,12],// 顺序
|
||||
password:"",
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 打乱数组
|
||||
this.num = this.randomArray([1,2,3,4,5,6,7,8,9,11]);
|
||||
this.num.splice(9, 0, 10);
|
||||
this.num.splice(11, 0, 12);
|
||||
console.log(this.num);
|
||||
console.log(this.key_words);
|
||||
},
|
||||
methods:{
|
||||
// 数组混淆
|
||||
randomArray(arr){
|
||||
return arr.sort(() => Math.random() -0.5);
|
||||
},
|
||||
press (obj) {
|
||||
let num = obj.num
|
||||
if (obj.num == 10) {
|
||||
console.log('我是10我什么都不干')
|
||||
} else if (obj.num == 12) {
|
||||
this.password = this.password.slice(0,this.password.length-1);
|
||||
} else if (obj.num == 11) {
|
||||
num = '0'
|
||||
this.password += num;
|
||||
} else {
|
||||
this.password += num;
|
||||
}
|
||||
if (this.password.length == 6) {
|
||||
this.$emit('getPassword',{password:this.password})
|
||||
this.password = "";
|
||||
}
|
||||
},
|
||||
// 关闭支付页面
|
||||
closeFuc () {
|
||||
console.log('关闭支付页面');
|
||||
this.$emit("closeFuc",false)
|
||||
},
|
||||
// 找回密码
|
||||
forgetFuc () {
|
||||
uni.navigateTo({
|
||||
url:'/pages/user/findPwd'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.mark{
|
||||
width: 750upx;
|
||||
background: rgba(0,0,0,0.7);
|
||||
padding: 0 0 700upx 0;
|
||||
position: absolute;
|
||||
top: 0upx;
|
||||
left: 0upx;
|
||||
z-index: 99;
|
||||
}
|
||||
.hidden{
|
||||
display: none;
|
||||
}
|
||||
.kong{
|
||||
width: 750upx;
|
||||
height: 250upx;
|
||||
}
|
||||
.msg{
|
||||
width: 550upx;
|
||||
height: 450upx;
|
||||
background: rgba(255,255,255,1);
|
||||
border-radius: 20upx;
|
||||
margin: 0 auto;
|
||||
animation: msgBox .2s linear;
|
||||
}
|
||||
@keyframes msgBox{
|
||||
0%{
|
||||
transform:translateY(50%);
|
||||
opacity: 0;
|
||||
}
|
||||
50%{
|
||||
transform:translateY(25%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100%{
|
||||
transform:translateY(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes numBox{
|
||||
0%{
|
||||
transform:translateY(50%);
|
||||
opacity: 0;
|
||||
}
|
||||
50%{
|
||||
transform:translateY(25%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100%{
|
||||
transform:translateY(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.msg>.img{
|
||||
padding: 20upx 0 0 20upx;
|
||||
font-size: 40upx;
|
||||
}
|
||||
.msg>.title{
|
||||
width: 100%;
|
||||
height: 100upx;
|
||||
line-height: 100upx;
|
||||
font-weight: 500;
|
||||
font-size: 36upx;
|
||||
text-align: center;
|
||||
}
|
||||
.msg>.subTitle{
|
||||
width: 100%;
|
||||
height: 50upx;
|
||||
line-height: 50upx;
|
||||
font-weight: 400;
|
||||
font-size: 32upx;
|
||||
text-align: center;
|
||||
}
|
||||
.pswBox{
|
||||
width: 80%;
|
||||
height: 80upx;
|
||||
margin: 50upx auto 0;
|
||||
display: flex;
|
||||
}
|
||||
.content_item{
|
||||
flex: 2;
|
||||
text-align: center;
|
||||
line-height: 80upx;
|
||||
border: 1upx solid #D6D6D6;
|
||||
border-right: 0upx solid;
|
||||
}
|
||||
.content_item:nth-child(1){
|
||||
border-radius: 10upx 0 0 10upx;
|
||||
}
|
||||
.content_item:nth-child(6){
|
||||
border-right: 1upx solid #D6D6D6;
|
||||
border-radius: 0 10upx 10upx 0;
|
||||
}
|
||||
.numeric{
|
||||
height: 480upx;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: #EBEBEB;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
flex-wrap: wrap;
|
||||
animation: msgBox .2s linear;
|
||||
}
|
||||
.num{
|
||||
box-sizing: border-box;
|
||||
width: 250upx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
font-size: 40upx;
|
||||
color: #333;
|
||||
height: 120upx;
|
||||
border: 1upx solid #F2F2F2;
|
||||
border-top:none;
|
||||
border-left:none;
|
||||
}
|
||||
.numColor{
|
||||
background: #FF0000;
|
||||
}
|
||||
.forget{
|
||||
font-size: 28upx;
|
||||
font-weight: 500;
|
||||
color: #3D84EA;
|
||||
text-align: center;
|
||||
line-height: 80upx;
|
||||
}
|
||||
.amend1{
|
||||
border: 1upx solid #CCCFD6;
|
||||
background-color: #CCCFD6;
|
||||
}
|
||||
.amend3{
|
||||
font-size: 60upx;
|
||||
border: 1upx solid #CCCFD6;
|
||||
background-color: #CCCFD6;
|
||||
}
|
||||
/* .amend11{
|
||||
position: absolute;
|
||||
top: 313upx;
|
||||
left: 0upx;
|
||||
background-color: #CCCFD6;
|
||||
border: 1upx solid #FF0000;
|
||||
}
|
||||
.amend1{
|
||||
height: 100upx !important;
|
||||
position: absolute;
|
||||
top: 306upx;
|
||||
left: 0upx;
|
||||
z-index: 99;
|
||||
background-color: #CCCFD6;
|
||||
border: 2upx solid #CCCFD6;
|
||||
}
|
||||
.amend2{
|
||||
position: absolute;
|
||||
top: 306upx;
|
||||
left: 250upx;
|
||||
z-index: 99;
|
||||
}
|
||||
.amend3{
|
||||
position: absolute;
|
||||
top: 306upx;
|
||||
left: 500upx;
|
||||
z-index: 99;
|
||||
font-size: 60upx;
|
||||
border: 0upx;
|
||||
background-color: #CCCFD6;
|
||||
} */
|
||||
|
||||
</style>
|
61
components/btn/index.js
Normal file
61
components/btn/index.js
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
var nativeButtonBehavior = require('./native-button-behaviors');
|
||||
|
||||
Component({
|
||||
externalClasses: ['custom-class', 'theme-class'],
|
||||
behaviors: [nativeButtonBehavior],
|
||||
relations: {
|
||||
'../btn-group/index': {
|
||||
type: 'parent',
|
||||
linked: function linked() {
|
||||
this.setData({ inGroup: true });
|
||||
},
|
||||
unlinked: function unlinked() {
|
||||
this.setData({ inGroup: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
type: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
}
|
||||
},
|
||||
|
||||
data: {
|
||||
inGroup: false,
|
||||
isLast: false
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleTap: function handleTap() {
|
||||
if (this.data.disabled) {
|
||||
this.triggerEvent('disabledclick');
|
||||
return;
|
||||
}
|
||||
this.triggerEvent('btnclick');
|
||||
},
|
||||
switchLastButtonStatus: function switchLastButtonStatus() {
|
||||
var isLast = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||
|
||||
this.setData({ isLast: isLast });
|
||||
}
|
||||
}
|
||||
});
|
3
components/btn/index.json
Normal file
3
components/btn/index.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
24
components/btn/index.wxml
Normal file
24
components/btn/index.wxml
Normal file
@ -0,0 +1,24 @@
|
||||
<button
|
||||
class="custom-class theme-class zan-btn {{ inGroup ? 'zan-btn--group' : '' }} {{ isLast ? 'zan-btn--last' : '' }} {{size ? 'zan-btn--'+size : ''}} {{size === 'mini' ? 'zan-btn--plain' : ''}} {{plain ? 'zan-btn--plain' : ''}} {{type ? 'zan-btn--'+type : ''}} {{loading ? 'zan-btn--loading' : ''}} {{disabled ? 'zan-btn--disabled' : ''}}"
|
||||
disabled="{{ disabled }}"
|
||||
hover-class="button-hover"
|
||||
open-type="{{ openType }}"
|
||||
app-parameter="{{ appParameter }}"
|
||||
hover-stop-propagation="{{ hoverStopPropagation }}"
|
||||
hover-start-time="{{ hoverStartTime }}"
|
||||
hover-stay-time="{{ hoverStayTime }}"
|
||||
lang="{{ lang }}"
|
||||
session-from="{{ sessionFrom }}"
|
||||
send-message-title="{{ sendMessageTitle }}"
|
||||
send-message-path="{{ sendMessagePath }}"
|
||||
send-message-img="{{ sendMessageImg }}"
|
||||
show-message-card="{{ showMessageCard }}"
|
||||
bindtap="handleTap"
|
||||
bindcontact="bindcontact"
|
||||
bindgetuserinfo="bindgetuserinfo"
|
||||
bindgetphonenumber="bindgetphonenumber"
|
||||
binderror="binderror"
|
||||
bindopensetting="bindopensetting"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
1
components/btn/index.wxss
Normal file
1
components/btn/index.wxss
Normal file
@ -0,0 +1 @@
|
||||
.zan-btn{position:relative;color:#333;background-color:#fff;padding-left:15px;padding-right:15px;border-radius:2px;font-size:16px;line-height:45px;height:45px;box-sizing:border-box;text-decoration:none;text-align:center;vertical-align:middle;overflow:visible}.zan-btn--group{margin-bottom:10px}.zan-btn::after{content:'';position:absolute;top:0;left:0;width:200%;height:200%;-webkit-transform:scale(.5);transform:scale(.5);-webkit-transform-origin:0 0;transform-origin:0 0;pointer-events:none;box-sizing:border-box;border:0 solid #e5e5e5;border-width:1px;border-radius:4px}.zan-btn--primary{color:#fff;background-color:#4b0}.zan-btn--primary::after{border-color:#0a0}.zan-btn--warn{color:#fff;background-color:#f85}.zan-btn--warn::after{border-color:#f85}.zan-btn--danger{color:#fff;background-color:#f44}.zan-btn--danger::after{border-color:#e33}.zan-btn--small{display:inline-block;height:30px;line-height:30px;font-size:12px}.zan-btn--small.zan-btn--group{margin-bottom:0;margin-right:5px}.zan-btn--mini{display:inline-block;line-height:21px;height:22px;font-size:10px;padding-left:5px;padding-right:5px}.zan-btn--mini.zan-btn--group{margin-bottom:0;margin-right:5px}.zan-btn--large{border-radius:0;border:none;line-height:50px;height:50px}.zan-btn--large.zan-btn--group{margin-bottom:0}.zan-btn--plain.zan-btn{background-color:transparent}.zan-btn--plain.zan-btn--primary{color:#06bf04}.zan-btn--plain.zan-btn--warn{color:#f60}.zan-btn--plain.zan-btn--danger{color:#f44}.button-hover{opacity:.9}.zan-btn--loading{color:transparent;opacity:1}.zan-btn--loading::before{position:absolute;left:50%;top:50%;content:' ';width:16px;height:16px;margin-left:-8px;margin-top:-8px;border:3px solid #e5e5e5;border-color:#666 #e5e5e5 #e5e5e5 #e5e5e5;border-radius:8px;box-sizing:border-box;-webkit-animation:btn-spin .6s linear;animation:btn-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.zan-btn--danger.zan-btn--loading::before,.zan-btn--primary.zan-btn--loading::before,.zan-btn--warn.zan-btn--loading::before{border-color:#fff rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.1)}@-webkit-keyframes btn-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes btn-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.zan-btn.zan-btn--disabled{color:#999!important;background:#f8f8f8!important;border-color:#e5e5e5!important;cursor:not-allowed!important;opacity:1!important}.zan-btn.zan-btn--disabled::after{border-color:#e5e5e5!important}.zan-btn--group.zan-btn--last{margin-bottom:0;margin-right:0}
|
74
components/btn/native-button-behaviors.js
Normal file
74
components/btn/native-button-behaviors.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = Behavior({
|
||||
properties: {
|
||||
loading: Boolean,
|
||||
// 在自定义组件中,无法与外界的 form 组件联动,暂时不开放
|
||||
// formType: String,
|
||||
openType: String,
|
||||
appParameter: String,
|
||||
// 暂时不开放,直接传入无法设置样式
|
||||
// hoverClass: {
|
||||
// type: String,
|
||||
// value: 'button-hover'
|
||||
// },
|
||||
hoverStopPropagation: Boolean,
|
||||
hoverStartTime: {
|
||||
type: Number,
|
||||
value: 20
|
||||
},
|
||||
hoverStayTime: {
|
||||
type: Number,
|
||||
value: 70
|
||||
},
|
||||
lang: {
|
||||
type: String,
|
||||
value: 'en'
|
||||
},
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
sendMessageTitle: String,
|
||||
sendMessagePath: String,
|
||||
sendMessageImg: String,
|
||||
showMessageCard: String
|
||||
},
|
||||
methods: {
|
||||
bindgetuserinfo: function bindgetuserinfo() {
|
||||
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
_ref$detail = _ref.detail,
|
||||
detail = _ref$detail === undefined ? {} : _ref$detail;
|
||||
|
||||
this.triggerEvent('getuserinfo', detail);
|
||||
},
|
||||
bindcontact: function bindcontact() {
|
||||
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
_ref2$detail = _ref2.detail,
|
||||
detail = _ref2$detail === undefined ? {} : _ref2$detail;
|
||||
|
||||
this.triggerEvent('contact', detail);
|
||||
},
|
||||
bindgetphonenumber: function bindgetphonenumber() {
|
||||
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
_ref3$detail = _ref3.detail,
|
||||
detail = _ref3$detail === undefined ? {} : _ref3$detail;
|
||||
|
||||
this.triggerEvent('getphonenumber', detail);
|
||||
},
|
||||
bindopensetting: function bindopensetting() {
|
||||
var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
_ref4$detail = _ref4.detail,
|
||||
detail = _ref4$detail === undefined ? {} : _ref4$detail;
|
||||
|
||||
this.triggerEvent('opensetting', detail);
|
||||
},
|
||||
binderror: function binderror() {
|
||||
var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
_ref5$detail = _ref5.detail,
|
||||
detail = _ref5$detail === undefined ? {} : _ref5$detail;
|
||||
|
||||
this.triggerEvent('error', detail);
|
||||
}
|
||||
}
|
||||
});
|
3
components/common/color.js
Normal file
3
components/common/color.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const RED = '#f44';
|
||||
export const BLUE = '#1989fa';
|
||||
export const GREEN = '#07c160';
|
48
components/common/component.js
Normal file
48
components/common/component.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { basic } from '../mixins/basic';
|
||||
import { observe } from '../mixins/observer/index';
|
||||
function mapKeys(source, target, map) {
|
||||
Object.keys(map).forEach(key => {
|
||||
if (source[key]) {
|
||||
target[map[key]] = source[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
function VantComponent(vantOptions = {}) {
|
||||
const options = {};
|
||||
mapKeys(vantOptions, options, {
|
||||
data: 'data',
|
||||
props: 'properties',
|
||||
mixins: 'behaviors',
|
||||
methods: 'methods',
|
||||
beforeCreate: 'created',
|
||||
created: 'attached',
|
||||
mounted: 'ready',
|
||||
relations: 'relations',
|
||||
destroyed: 'detached',
|
||||
classes: 'externalClasses'
|
||||
});
|
||||
const { relation } = vantOptions;
|
||||
if (relation) {
|
||||
options.relations = Object.assign(options.relations || {}, {
|
||||
[`../${relation.name}/index`]: relation
|
||||
});
|
||||
}
|
||||
// add default externalClasses
|
||||
options.externalClasses = options.externalClasses || [];
|
||||
options.externalClasses.push('custom-class');
|
||||
// add default behaviors
|
||||
options.behaviors = options.behaviors || [];
|
||||
options.behaviors.push(basic);
|
||||
// map field to form-field behavior
|
||||
if (vantOptions.field) {
|
||||
options.behaviors.push('wx://form-field');
|
||||
}
|
||||
// add default options
|
||||
options.options = {
|
||||
multipleSlots: true,
|
||||
addGlobalClass: true
|
||||
};
|
||||
observe(vantOptions, options);
|
||||
Component(options);
|
||||
}
|
||||
export { VantComponent };
|
1
components/common/index.wxss
Normal file
1
components/common/index.wxss
Normal file
@ -0,0 +1 @@
|
||||
.van-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.van-multi-ellipsis--l2{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.van-multi-ellipsis--l3{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}.van-clearfix::after{content:'';display:table;clear:both}.van-hairline,.van-hairline--bottom,.van-hairline--left,.van-hairline--right,.van-hairline--surround,.van-hairline--top,.van-hairline--top-bottom{position:relative}.van-hairline--bottom::after,.van-hairline--left::after,.van-hairline--right::after,.van-hairline--surround::after,.van-hairline--top-bottom::after,.van-hairline--top::after,.van-hairline::after{content:' ';position:absolute;pointer-events:none;box-sizing:border-box;-webkit-transform-origin:center;transform-origin:center;top:-50%;left:-50%;right:-50%;bottom:-50%;-webkit-transform:scale(.5);transform:scale(.5);border:0 solid #eee}.van-hairline--top::after{border-top-width:1px}.van-hairline--left::after{border-left-width:1px}.van-hairline--right::after{border-right-width:1px}.van-hairline--bottom::after{border-bottom-width:1px}.van-hairline--top-bottom::after{border-width:1px 0}.van-hairline--surround::after{border-width:1px}
|
1
components/common/style/clearfix.wxss
Normal file
1
components/common/style/clearfix.wxss
Normal file
@ -0,0 +1 @@
|
||||
.van-clearfix::after{content:'';display:table;clear:both}
|
1
components/common/style/ellipsis.wxss
Normal file
1
components/common/style/ellipsis.wxss
Normal file
@ -0,0 +1 @@
|
||||
.van-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.van-multi-ellipsis--l2{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.van-multi-ellipsis--l3{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}
|
1
components/common/style/hairline.wxss
Normal file
1
components/common/style/hairline.wxss
Normal file
@ -0,0 +1 @@
|
||||
.van-hairline,.van-hairline--bottom,.van-hairline--left,.van-hairline--right,.van-hairline--surround,.van-hairline--top,.van-hairline--top-bottom{position:relative}.van-hairline--bottom::after,.van-hairline--left::after,.van-hairline--right::after,.van-hairline--surround::after,.van-hairline--top-bottom::after,.van-hairline--top::after,.van-hairline::after{content:' ';position:absolute;pointer-events:none;box-sizing:border-box;-webkit-transform-origin:center;transform-origin:center;top:-50%;left:-50%;right:-50%;bottom:-50%;-webkit-transform:scale(.5);transform:scale(.5);border:0 solid #eee}.van-hairline--top::after{border-top-width:1px}.van-hairline--left::after{border-left-width:1px}.van-hairline--right::after{border-right-width:1px}.van-hairline--bottom::after{border-bottom-width:1px}.van-hairline--top-bottom::after{border-width:1px 0}.van-hairline--surround::after{border-width:1px}
|
0
components/common/style/mixins/clearfix.wxss
Normal file
0
components/common/style/mixins/clearfix.wxss
Normal file
0
components/common/style/mixins/ellipsis.wxss
Normal file
0
components/common/style/mixins/ellipsis.wxss
Normal file
0
components/common/style/mixins/hairline.wxss
Normal file
0
components/common/style/mixins/hairline.wxss
Normal file
0
components/common/style/var.wxss
Normal file
0
components/common/style/var.wxss
Normal file
14
components/common/utils.js
Normal file
14
components/common/utils.js
Normal file
@ -0,0 +1,14 @@
|
||||
function isDef(value) {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
function isObj(x) {
|
||||
const type = typeof x;
|
||||
return x !== null && (type === 'object' || type === 'function');
|
||||
}
|
||||
function isNumber(value) {
|
||||
return /^\d+$/.test(value);
|
||||
}
|
||||
function range(num, min, max) {
|
||||
return Math.min(Math.max(num, min), max);
|
||||
}
|
||||
export { isObj, isDef, isNumber, range };
|
26
components/dialog/data.js
Normal file
26
components/dialog/data.js
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
// 标题
|
||||
title: '',
|
||||
// 内容
|
||||
message: ' ',
|
||||
// 选择节点
|
||||
selector: '#zan-dialog',
|
||||
// 按钮是否展示为纵向
|
||||
buttonsShowVertical: false,
|
||||
// 是否展示确定
|
||||
showConfirmButton: true,
|
||||
// 确认按钮文案
|
||||
confirmButtonText: '确定',
|
||||
// 确认按钮颜色
|
||||
confirmButtonColor: '#3CC51F',
|
||||
// 是否展示取消
|
||||
showCancelButton: false,
|
||||
// 取消按钮文案
|
||||
cancelButtonText: '取消',
|
||||
// 取消按钮颜色
|
||||
cancelButtonColor: '#333',
|
||||
// 点击按钮自动关闭 dialog
|
||||
autoClose: true
|
||||
};
|
104
components/dialog/dialog.js
Normal file
104
components/dialog/dialog.js
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
var defaultData = require('./data');
|
||||
|
||||
function getDialogCtx(_ref) {
|
||||
var selector = _ref.selector,
|
||||
pageCtx = _ref.pageCtx;
|
||||
|
||||
var ctx = pageCtx;
|
||||
if (!ctx) {
|
||||
var pages = getCurrentPages();
|
||||
ctx = pages[pages.length - 1];
|
||||
}
|
||||
return ctx.selectComponent(selector);
|
||||
}
|
||||
|
||||
function getParsedOptions() {
|
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
return Object.assign({
|
||||
// 自定义 btn 列表
|
||||
// { type: 按钮类型,回调时以此作为区分依据,text: 按钮文案, color: 按钮文字颜色 }
|
||||
buttons: []
|
||||
}, defaultData, options);
|
||||
}
|
||||
|
||||
// options 使用参数
|
||||
// pageCtx 页面 page 上下文
|
||||
function Dialog(options, pageCtx) {
|
||||
var parsedOptions = getParsedOptions(options);
|
||||
|
||||
var dialogCtx = getDialogCtx({
|
||||
selector: parsedOptions.selector,
|
||||
pageCtx: pageCtx
|
||||
});
|
||||
|
||||
if (!dialogCtx) {
|
||||
console.error('无法找到对应的dialog组件,请于页面中注册并在 wxml 中声明 dialog 自定义组件');
|
||||
return Promise.reject({ type: 'component error' });
|
||||
}
|
||||
|
||||
// 处理默认按钮的展示
|
||||
// 纵向排布确认按钮在上方
|
||||
var _parsedOptions$button = parsedOptions.buttons,
|
||||
buttons = _parsedOptions$button === undefined ? [] : _parsedOptions$button;
|
||||
|
||||
var showCustomBtns = false;
|
||||
if (buttons.length === 0) {
|
||||
if (parsedOptions.showConfirmButton) {
|
||||
buttons.push({
|
||||
type: 'confirm',
|
||||
text: parsedOptions.confirmButtonText,
|
||||
color: parsedOptions.confirmButtonColor
|
||||
});
|
||||
}
|
||||
|
||||
if (parsedOptions.showCancelButton) {
|
||||
var cancelButton = {
|
||||
type: 'cancel',
|
||||
text: parsedOptions.cancelButtonText,
|
||||
color: parsedOptions.cancelButtonColor
|
||||
};
|
||||
if (parsedOptions.buttonsShowVertical) {
|
||||
buttons.push(cancelButton);
|
||||
} else {
|
||||
buttons.unshift(cancelButton);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showCustomBtns = true;
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
dialogCtx.setData(Object.assign({}, parsedOptions, {
|
||||
buttons: buttons,
|
||||
showCustomBtns: showCustomBtns,
|
||||
key: '' + new Date().getTime(),
|
||||
show: true,
|
||||
promiseFunc: { resolve: resolve, reject: reject },
|
||||
openTypePromiseFunc: null
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
Dialog.close = function (options, pageCtx) {
|
||||
var parsedOptions = getParsedOptions(options);
|
||||
|
||||
var dialogCtx = getDialogCtx({
|
||||
selector: parsedOptions.selector,
|
||||
pageCtx: pageCtx
|
||||
});
|
||||
|
||||
if (!dialogCtx) {
|
||||
return;
|
||||
}
|
||||
|
||||
dialogCtx.setData({
|
||||
show: false,
|
||||
promiseFunc: null,
|
||||
openTypePromiseFunc: null
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Dialog;
|
148
components/dialog/index.js
Normal file
148
components/dialog/index.js
Normal file
@ -0,0 +1,148 @@
|
||||
'use strict';
|
||||
|
||||
var _f = function _f() {};
|
||||
var needResponseOpenTypes = ['getUserInfo', 'getPhoneNumber', 'openSetting'];
|
||||
|
||||
Component({
|
||||
properties: {},
|
||||
|
||||
data: {
|
||||
// 标题
|
||||
title: '',
|
||||
// 自定义 btn 列表
|
||||
// { type: 按钮类型,回调时以此作为区分依据,text: 按钮文案, color: 按钮文字颜色, openType: 微信开放能力 }
|
||||
buttons: [],
|
||||
// 内容
|
||||
message: ' ',
|
||||
// 选择节点
|
||||
selector: '#zan-dialog',
|
||||
// 是否允许滚动
|
||||
isScroll: false,
|
||||
// 按钮是否展示为纵向
|
||||
buttonsShowVertical: false,
|
||||
// 是否展示确定
|
||||
showConfirmButton: true,
|
||||
// 确认按钮文案
|
||||
confirmButtonText: '确定',
|
||||
// 确认按钮颜色
|
||||
confirmButtonColor: '#3CC51F',
|
||||
// 是否展示取消
|
||||
showCancelButton: false,
|
||||
// 取消按钮文案
|
||||
cancelButtonText: '取消',
|
||||
// 取消按钮颜色
|
||||
cancelButtonColor: '#333',
|
||||
key: '',
|
||||
autoClose: true,
|
||||
show: false,
|
||||
showCustomBtns: false,
|
||||
promiseFunc: {},
|
||||
openTypePromiseFunc: {}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleButtonClick: function handleButtonClick(e) {
|
||||
var _this = this;
|
||||
|
||||
var _e$currentTarget = e.currentTarget,
|
||||
currentTarget = _e$currentTarget === undefined ? {} : _e$currentTarget;
|
||||
var _currentTarget$datase = currentTarget.dataset,
|
||||
dataset = _currentTarget$datase === undefined ? {} : _currentTarget$datase;
|
||||
|
||||
// 获取当次弹出框的信息
|
||||
|
||||
var _ref = this.data.promiseFunc || {},
|
||||
_ref$resolve = _ref.resolve,
|
||||
resolve = _ref$resolve === undefined ? _f : _ref$resolve,
|
||||
_ref$reject = _ref.reject,
|
||||
reject = _ref$reject === undefined ? _f : _ref$reject;
|
||||
|
||||
// 重置展示
|
||||
|
||||
|
||||
if (this.data.autoClose) {
|
||||
this.setData({
|
||||
show: false
|
||||
});
|
||||
}
|
||||
|
||||
// 自定义按钮,全部 resolve 形式返回,根据 type 区分点击按钮
|
||||
if (this.data.showCustomBtns) {
|
||||
var isNeedOpenDataButton = needResponseOpenTypes.indexOf(dataset.openType) > -1;
|
||||
var resolveData = {
|
||||
type: dataset.type
|
||||
};
|
||||
// 如果需要 openData,就额外返回一个 promise,用于后续 open 数据返回
|
||||
if (isNeedOpenDataButton) {
|
||||
resolveData.openDataPromise = new Promise(function(resolve, reject) {
|
||||
_this.setData({
|
||||
openTypePromiseFunc: {
|
||||
resolve: resolve,
|
||||
reject: reject
|
||||
}
|
||||
});
|
||||
});
|
||||
resolveData.hasOpenDataPromise = true;
|
||||
}
|
||||
resolve(resolveData);
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认按钮,确认为 resolve,取消为 reject
|
||||
if (dataset.type === 'confirm') {
|
||||
resolve({
|
||||
type: 'confirm'
|
||||
});
|
||||
} else {
|
||||
reject({
|
||||
type: 'cancel'
|
||||
});
|
||||
}
|
||||
|
||||
this.setData({
|
||||
promiseFunc: {}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 以下为处理微信按钮开放能力的逻辑
|
||||
handleUserInfoResponse: function handleUserInfoResponse(_ref2) {
|
||||
var detail = _ref2.detail;
|
||||
|
||||
this.__handleOpenDataResponse({
|
||||
type: detail.errMsg === 'getUserInfo:ok' ? 'resolve' : 'reject',
|
||||
data: detail
|
||||
});
|
||||
},
|
||||
handlePhoneResponse: function handlePhoneResponse(_ref3) {
|
||||
var detail = _ref3.detail;
|
||||
|
||||
this.__handleOpenDataResponse({
|
||||
type: detail.errMsg === 'getPhoneNumber:ok' ? 'resolve' : 'reject',
|
||||
data: detail
|
||||
});
|
||||
},
|
||||
handleOpenSettingResponse: function handleOpenSettingResponse(_ref4) {
|
||||
var detail = _ref4.detail;
|
||||
|
||||
this.__handleOpenDataResponse({
|
||||
type: detail.errMsg === 'openSetting:ok' ? 'resolve' : 'reject',
|
||||
data: detail
|
||||
});
|
||||
},
|
||||
__handleOpenDataResponse: function __handleOpenDataResponse(_ref5) {
|
||||
var _ref5$type = _ref5.type,
|
||||
type = _ref5$type === undefined ? 'resolve' : _ref5$type,
|
||||
_ref5$data = _ref5.data,
|
||||
data = _ref5$data === undefined ? {} : _ref5$data;
|
||||
|
||||
var promiseFuncs = this.data.openTypePromiseFunc || {};
|
||||
var responseFunc = promiseFuncs[type] || _f;
|
||||
|
||||
responseFunc(data);
|
||||
this.setData({
|
||||
openTypePromiseFunc: null
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
7
components/dialog/index.json
Normal file
7
components/dialog/index.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"pop-manager": "../pop-manager/index",
|
||||
"zan-button": "../btn/index"
|
||||
}
|
||||
}
|
18
components/dialog/index.wxml
Normal file
18
components/dialog/index.wxml
Normal file
@ -0,0 +1,18 @@
|
||||
<pop-manager show="{{ show }}" type="center">
|
||||
<view class="zan-dialog--container">
|
||||
<view wx:if="{{ title }}" class="zan-dialog__header">{{ title }}</view>
|
||||
<view class="zan-dialog__content {{ title ? 'zan-dialog__content--title' : '' }}">
|
||||
<scroll-view class="zan-dialog__content--scroll" scroll-y="{{ isScroll }}">
|
||||
<text>{{ message }}</text>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="zan-dialog__footer {{ buttonsShowVertical ? 'zan-dialog__footer--vertical' : 'zan-dialog__footer--horizon' }}">
|
||||
<block wx:for="{{ buttons }}" wx:key="this">
|
||||
<zan-button class="zan-dialog__button" custom-class="{{ index === 0 ? 'zan-dialog__button-inside--first' : 'zan-dialog__button-inside' }}" data-type="{{ item.type }}" data-open-type="{{ item.openType }}" open-type="{{ item.openType }}" bind:btnclick="handleButtonClick"
|
||||
bind:getuserinfo="handleUserInfoResponse" bind:getphonenumber="handlePhoneResponse" bind:opensetting="handleOpenSettingResponse">
|
||||
<view style="color: {{ item.color || '#333' }}">{{ item.text }}</view>
|
||||
</zan-button>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</pop-manager>
|
79
components/dialog/index.wxss
Normal file
79
components/dialog/index.wxss
Normal file
@ -0,0 +1,79 @@
|
||||
.zan-dialog--container {
|
||||
width: 80vw;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.zan-dialog__header {
|
||||
padding: 15px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.zan-dialog__content {
|
||||
position: relative;
|
||||
padding: 15px 20px;
|
||||
line-height: 1.5;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.zan-dialog__content::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
-webkit-transform: scale(0.5);
|
||||
transform: scale(0.5);
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
border: 0 solid #e5e5e5;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.zan-dialog__content--title {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.zan-dialog__content--scroll {
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.zan-dialog__footer {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.zan-dialog__button {
|
||||
-webkit-box-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.zan-dialog__button-inside, .zan-dialog__button-inside--first {
|
||||
margin-bottom: 0;
|
||||
line-height: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.zan-dialog__button-inside--first::after, .zan-dialog__button-inside::after {
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.zan-dialog__footer--horizon {
|
||||
display: -webkit-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.zan-dialog__footer--horizon .zan-dialog__button-inside::after {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.zan-dialog__footer--vertical .zan-dialog__button-inside::after {
|
||||
border-top-width: 1px;
|
||||
}
|
66
components/empty/index.vue
Normal file
66
components/empty/index.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<view v-if="!isLoading" class="empty-content" :style="customStyle">
|
||||
<view class="empty-icon">
|
||||
<image class="image" src="/static/empty.png" mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="tips">{{ tips }}</view>
|
||||
<slot name="slot"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
/**
|
||||
* 组件的属性列表
|
||||
* 用于组件自定义设置
|
||||
*/
|
||||
props: {
|
||||
// 正在加载
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 提示的问题
|
||||
tips: {
|
||||
type: String,
|
||||
default: '亲,暂无相关数据'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty-content {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 140rpx 50rpx;
|
||||
text-align: center;
|
||||
|
||||
.tips {
|
||||
font-size: 26rpx;
|
||||
color: gray;
|
||||
margin: 10rpx 0rpx 40rpx 0rpx;
|
||||
}
|
||||
|
||||
.empty-icon .image {
|
||||
width: 280rpx;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
869
components/goods-sku-popup/index.vue
Normal file
869
components/goods-sku-popup/index.vue
Normal file
@ -0,0 +1,869 @@
|
||||
<template>
|
||||
<view class="goods-sku-popup popup" catchtouchmove="true" :class="(value && complete) ? 'show' : 'none'"
|
||||
@touchmove.stop.prevent="moveHandle">
|
||||
<!-- 页面内容开始 -->
|
||||
<view class="mask" @click="close('mask')"></view>
|
||||
<!-- 页面开始 -->
|
||||
<view class="layer attr-content" :style="'border-radius: '+borderRadius+'rpx '+borderRadius+'rpx 0 0;'">
|
||||
<view class="specification-wrapper">
|
||||
<scroll-view class="specification-wrapper-content" scroll-y="true">
|
||||
<view class="specification-header">
|
||||
<view class="specification-left">
|
||||
<image class="product-img" :src="selectShop.image ? selectShop.image : goodsInfo[goodsThumbName]"
|
||||
mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="specification-right">
|
||||
<view class="price-content" :style="'color: '+priceColor+' ;'">
|
||||
<text class="sign">¥</text>
|
||||
<text class="price">{{ (selectShop.price || defaultPrice) | priceFilter }}</text>
|
||||
</view>
|
||||
<view class="inventory">{{ stockText }}:{{ selectShop[stockName] || defaultStock }}</view>
|
||||
<view class="choose" v-show="goodsInfo[specListName] && goodsInfo[specListName][0].name !== defaultSingleSkuName">
|
||||
<text v-if="!selectArr.every(val => val == '')">已选:{{ selectArr.join(' ') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="specification-content">
|
||||
<view v-show="goodsInfo[specListName][0].name !== defaultSingleSkuName" class="specification-item" v-for="(item, index1) in goodsInfo[specListName]" :key="index1">
|
||||
<view class="item-title">{{ item.name }}</view>
|
||||
<view class="item-wrapper">
|
||||
<view class="item-content" @tap="skuClick(item_value, index1, $event, index2)" v-for="(item_value, index2) in item.list"
|
||||
:key="index2" :class="[item_value.ishow ? '' : 'noactived', subIndex[index1] == index2 ? 'actived' : '']"
|
||||
:style="[item_value.ishow ? '' : disableStyle,
|
||||
item_value.ishow ? btnStyle :'',
|
||||
subIndex[index1] == index2 ? activedStyle : ''
|
||||
]">
|
||||
{{ item_value.name }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view style="display: flex;">
|
||||
<view style="flex: 1;">
|
||||
<text style="font-size: 26rpx; color: #333; line-height: 50rpx;">数量</text>
|
||||
</view>
|
||||
<view style="flex: 4;text-align: right;">
|
||||
<number-box :min="minBuyNum" :max="maxBuyNum" :step="stepBuyNum" v-model="selectNum"
|
||||
:positive-integer="true">
|
||||
</number-box>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="close" @click="close('close')" v-if="showClose">
|
||||
<image class="close-item" :src="closeImage"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-option">
|
||||
<view class="btn-wrapper" v-if="outFoStock || mode == 4">
|
||||
<view class="sure" style="color:#ffffff;background-color:#cccccc">{{ noStockText }}</view>
|
||||
</view>
|
||||
<view class="btn-wrapper" v-else-if="mode == 1">
|
||||
<view class="sure add-cart" style="border-radius:38rpx 0rpx 0rpx 38rpx;" @click="addCart" :style="'color:'+addCartColor+';background:'+addCartBackgroundColor">{{ addCartText }}</view>
|
||||
<view class="sure" style="border-radius:0rpx 38rpx 38rpx 0rpx;" @click="buyNow" :style="'color:'+buyNowColor+';background-color:'+buyNowBackgroundColor">{{ buyNowText }}</view>
|
||||
</view>
|
||||
<view class="btn-wrapper" v-else-if="mode == 2">
|
||||
<view class="sure add-cart" @click="addCart" :style="'color:'+addCartColor+';background:'+addCartBackgroundColor">{{ addCartText }}</view>
|
||||
</view>
|
||||
<view class="btn-wrapper" v-else-if="mode == 3">
|
||||
<view class="sure" @click="buyNow" :style="'color:'+buyNowColor+';background:'+buyNowBackgroundColor">{{ buyNowText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 页面结束 -->
|
||||
</view>
|
||||
<!-- 页面内容结束 -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NumberBox from './number-box'
|
||||
|
||||
var that; // 当前页面对象
|
||||
var vk; // 自定义函数集
|
||||
export default {
|
||||
name: 'GoodsSkuPopup',
|
||||
components: {
|
||||
NumberBox
|
||||
},
|
||||
props: {
|
||||
// true 组件显示 false 组件隐藏
|
||||
value: {
|
||||
Type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// vk云函数路由模式参数开始-----------------------------------------------------------
|
||||
// 商品id
|
||||
goodsId: {
|
||||
Type: String,
|
||||
default: ""
|
||||
},
|
||||
// vk路由模式框架下的云函数地址
|
||||
action: {
|
||||
Type: String,
|
||||
default: ""
|
||||
},
|
||||
// vk云函数路由模式参数结束-----------------------------------------------------------
|
||||
// 该商品已抢完时的按钮文字
|
||||
noStockText: {
|
||||
Type: String,
|
||||
default: "该商品已抢完"
|
||||
},
|
||||
// 库存文字
|
||||
stockText: {
|
||||
Type: String,
|
||||
default: "库存"
|
||||
},
|
||||
// 商品表id的字段名
|
||||
goodsIdName: {
|
||||
Type: String,
|
||||
default: "_id"
|
||||
},
|
||||
// sku表id的字段名
|
||||
skuIdName: {
|
||||
Type: String,
|
||||
default: "_id"
|
||||
},
|
||||
// sku_list的字段名
|
||||
skuListName: {
|
||||
Type: String,
|
||||
default: "sku_list"
|
||||
},
|
||||
// spec_list的字段名
|
||||
specListName: {
|
||||
Type: String,
|
||||
default: "spec_list"
|
||||
},
|
||||
// stock的字段名
|
||||
stockName: {
|
||||
Type: String,
|
||||
default: "stock"
|
||||
},
|
||||
// sku_name的字段名
|
||||
skuName: {
|
||||
Type: String,
|
||||
default: "sku_name"
|
||||
},
|
||||
// sku组合路径的字段名
|
||||
skuArrName: {
|
||||
Type: String,
|
||||
default: "sku_name_arr"
|
||||
},
|
||||
// 默认单规格时的规格组名称
|
||||
defaultSingleSkuName: {
|
||||
Type: String,
|
||||
default: "默认"
|
||||
},
|
||||
// 模式 1:都显示 2:只显示购物车 3:只显示立即购买 4:显示缺货按钮 默认 1
|
||||
mode: {
|
||||
Type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 点击遮罩是否关闭组件 true 关闭 false 不关闭 默认true
|
||||
maskCloseAble: {
|
||||
Type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 顶部圆角值
|
||||
borderRadius: {
|
||||
Type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 商品缩略图字段名(未选择sku时)
|
||||
goodsThumbName: {
|
||||
Type: [String],
|
||||
default: "goods_thumb"
|
||||
},
|
||||
// 最小购买数量
|
||||
minBuyNum: {
|
||||
Type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 最大购买数量
|
||||
maxBuyNum: {
|
||||
Type: Number,
|
||||
default: 100000
|
||||
},
|
||||
// 每次点击后的数量
|
||||
stepBuyNum: {
|
||||
Type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 自定义获取商品信息的函数
|
||||
customAction: {
|
||||
Type: [Function],
|
||||
default: null
|
||||
},
|
||||
// 价格的字体颜色
|
||||
priceColor: {
|
||||
Type: String,
|
||||
default: "#fe560a"
|
||||
},
|
||||
// 立即购买按钮的文字
|
||||
buyNowText: {
|
||||
Type: String,
|
||||
default: "立即购买"
|
||||
},
|
||||
// 立即购买按钮的字体颜色
|
||||
buyNowColor: {
|
||||
Type: String,
|
||||
default: "#ffffff"
|
||||
},
|
||||
// 立即购买按钮的背景颜色
|
||||
buyNowBackgroundColor: {
|
||||
Type: String,
|
||||
default: "linear-gradient(to right, #00acac, #00acac)"
|
||||
},
|
||||
// 加入购物车按钮的文字
|
||||
addCartText: {
|
||||
Type: String,
|
||||
default: "加入购物车"
|
||||
},
|
||||
// 加入购物车按钮的字体颜色
|
||||
addCartColor: {
|
||||
Type: String,
|
||||
default: "#ffffff"
|
||||
},
|
||||
// 加入购物车按钮的背景颜色
|
||||
addCartBackgroundColor: {
|
||||
Type: String,
|
||||
default: "linear-gradient(to right, #00acac, #00acac)"
|
||||
},
|
||||
// 不可点击时,按钮的样式
|
||||
disableStyle: {
|
||||
Type: Object,
|
||||
default: null
|
||||
},
|
||||
// 按钮点击时的样式
|
||||
activedStyle: {
|
||||
Type: Object,
|
||||
default: null
|
||||
},
|
||||
// 按钮常态的样式
|
||||
btnStyle: {
|
||||
Type: Object,
|
||||
default: null
|
||||
},
|
||||
// 是否显示右上角关闭按钮
|
||||
showClose: {
|
||||
Type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 关闭按钮的图片地址
|
||||
closeImage: {
|
||||
Type: String,
|
||||
default: "https://img.alicdn.com/imgextra/i1/121022687/O1CN01ImN0O11VigqwzpLiK_!!121022687.png"
|
||||
},
|
||||
// 默认库存数量 (未选择sku时)
|
||||
defaultStock: {
|
||||
Type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 默认显示的价格 (未选择sku时)
|
||||
defaultPrice: {
|
||||
Type: Number,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
complete: false, // 组件是否加载完成
|
||||
goodsInfo: {}, // 商品信息
|
||||
isShow: false, // true 显示 false 隐藏
|
||||
initKey: true, // 是否已初始化
|
||||
shopItemInfo: {}, // 存放要和选中的值进行匹配的数据
|
||||
selectArr: [], // 存放被选中的值
|
||||
subIndex: [], // 是否选中 因为不确定是多规格还是单规格,所以这里定义数组来判断
|
||||
selectShop: {}, // 存放最后选中的商品
|
||||
selectNum: this.minBuyNum, // 选中数量
|
||||
outFoStock: false, // 是否全部sku都缺货
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
that = this;
|
||||
vk = that.vk;
|
||||
if (that.value) {
|
||||
that.open();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
// 清空之前的数据
|
||||
that.selectArr = [];
|
||||
that.subIndex = [];
|
||||
that.selectShop = {};
|
||||
that.selectNum = that.minBuyNum;
|
||||
that.outFoStock = false;
|
||||
that.shopItemInfo = {};
|
||||
|
||||
let specListName = that.specListName;
|
||||
that.goodsInfo[specListName].map(item => {
|
||||
that.selectArr.push('');
|
||||
that.subIndex.push(-1);
|
||||
});
|
||||
|
||||
that.checkItem(); // 计算sku里面规格形成路径
|
||||
that.checkInpath(-1); // 传-1是为了不跳过循环
|
||||
that.autoClickSku(); // 自动选择sku策略
|
||||
},
|
||||
// 使用vk路由模式框架获取商品信息
|
||||
findGoodsInfo() {
|
||||
if (typeof vk == "undefined") {
|
||||
that.toast("custom-action必须是function");
|
||||
return false;
|
||||
}
|
||||
vk.callFunction({
|
||||
url: that.action,
|
||||
title: '请求中...',
|
||||
data: {
|
||||
goods_id: that.goodsId
|
||||
},
|
||||
success(data) {
|
||||
that.updateGoodsInfo(data.goodsInfo);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 更新商品信息(库存、名称、图片)
|
||||
updateGoodsInfo(goodsInfo) {
|
||||
let skuListName = that.skuListName;
|
||||
if (JSON.stringify(that.goodsInfo) === "{}" || that.goodsInfo[that.goodsIdName] !== goodsInfo[that.goodsIdName]) {
|
||||
that.goodsInfo = goodsInfo;
|
||||
that.initKey = true;
|
||||
} else {
|
||||
that.goodsInfo[skuListName] = goodsInfo[skuListName];
|
||||
}
|
||||
if (that.initKey) {
|
||||
that.initKey = false;
|
||||
that.init();
|
||||
}
|
||||
// 更新选中sku的库存信息
|
||||
let select_sku_info = that.getListItem(that.goodsInfo[skuListName], that.skuIdName, that.selectShop[that.skuIdName]);
|
||||
Object.assign(that.selectShop, select_sku_info);
|
||||
that.complete = true;
|
||||
that.$emit("open", true);
|
||||
that.$emit("input", true);
|
||||
},
|
||||
async open() {
|
||||
let findGoodsInfoRun = true;
|
||||
let skuListName = that.skuListName;
|
||||
if (that.customAction && typeof(that.customAction) === 'function') {
|
||||
let goodsInfo = await that.customAction();
|
||||
if (goodsInfo && typeof goodsInfo == "object" && JSON.stringify(goodsInfo) != "{}") {
|
||||
findGoodsInfoRun = false;
|
||||
that.updateGoodsInfo(goodsInfo);
|
||||
} else {
|
||||
that.toast("无法获取到商品信息");
|
||||
that.$emit("input", false);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (findGoodsInfoRun) {
|
||||
that.findGoodsInfo();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 监听 - 弹出层收起
|
||||
close(s) {
|
||||
if (s == "close") {
|
||||
that.$emit("input", false);
|
||||
that.$emit("close", "close");
|
||||
} else if (s == "mask") {
|
||||
if (that.maskCloseAble) {
|
||||
that.$emit("input", false);
|
||||
that.$emit("close", "mask");
|
||||
}
|
||||
}
|
||||
},
|
||||
moveHandle() {
|
||||
//禁止父元素滑动
|
||||
},
|
||||
// sku按钮的点击事件
|
||||
skuClick(value, index1, event, index2) {
|
||||
if (value.ishow) {
|
||||
if (that.selectArr[index1] != value.name) {
|
||||
that.$set(that.selectArr, index1, value.name);
|
||||
that.$set(that.subIndex, index1, index2);
|
||||
} else {
|
||||
that.$set(that.selectArr, index1, '');
|
||||
that.$set(that.subIndex, index1, -1);
|
||||
}
|
||||
that.checkInpath(index1);
|
||||
// 如果全部选完
|
||||
that.checkSelectShop();
|
||||
}
|
||||
},
|
||||
// 检测是否已经选完sku
|
||||
checkSelectShop() {
|
||||
// 如果全部选完
|
||||
if (that.selectArr.every(item => item != '')) {
|
||||
that.selectShop = that.shopItemInfo[that.selectArr];
|
||||
that.selectNum = that.minBuyNum;
|
||||
} else {
|
||||
that.selectShop = {};
|
||||
}
|
||||
},
|
||||
// 检查路径
|
||||
checkInpath(clickIndex) {
|
||||
let specListName = that.specListName;
|
||||
//循环所有属性判断哪些属性可选
|
||||
//当前选中的兄弟节点和已选中属性不需要循环
|
||||
let specList = that.goodsInfo[specListName];
|
||||
for (let i = 0, len = specList.length; i < len; i++) {
|
||||
if (i == clickIndex) {
|
||||
continue;
|
||||
}
|
||||
let len2 = specList[i].list.length;
|
||||
for (let j = 0; j < len2; j++) {
|
||||
if (that.subIndex[i] != -1 && j == that.subIndex[i]) {
|
||||
continue;
|
||||
}
|
||||
let choosed_copy = [...that.selectArr];
|
||||
that.$set(choosed_copy, i, specList[i].list[j].name);
|
||||
let choosed_copy2 = choosed_copy.filter(item => item !== '' && typeof item !== 'undefined');
|
||||
if (that.shopItemInfo.hasOwnProperty(choosed_copy2)) {
|
||||
specList[i].list[j].ishow = true;
|
||||
} else {
|
||||
specList[i].list[j].ishow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
that.$set(that.goodsInfo, specListName, specList);
|
||||
},
|
||||
// 计算sku里面规格形成路径
|
||||
checkItem() {
|
||||
let skuListName = that.skuListName;
|
||||
// console.time('计算有多小种可选路径需要的时间是');
|
||||
// 去除库存小于等于0的商品sku
|
||||
let skuList = that.goodsInfo[skuListName];
|
||||
let stockNum = 0;
|
||||
for (let i = 0; i < skuList.length; i++) {
|
||||
if (skuList[i][that.stockName] <= 0) {
|
||||
skuList.splice(i, 1);
|
||||
i--;
|
||||
} else {
|
||||
stockNum += skuList[i][that.stockName];
|
||||
}
|
||||
}
|
||||
if (stockNum <= 0) {
|
||||
that.outFoStock = true;
|
||||
}
|
||||
// 计算有多小种可选路径
|
||||
let result = skuList.reduce(
|
||||
(arrs, items) => {
|
||||
return arrs.concat(
|
||||
items[that.skuArrName].reduce(
|
||||
(arr, item) => {
|
||||
return arr.concat(
|
||||
arr.map(item2 => {
|
||||
// 利用对象属性的唯一性实现二维数组去重
|
||||
if (!that.shopItemInfo.hasOwnProperty([...item2, item])) {
|
||||
that.shopItemInfo[[...item2, item]] = items;
|
||||
}
|
||||
return [...item2, item];
|
||||
})
|
||||
);
|
||||
},
|
||||
[
|
||||
[]
|
||||
]
|
||||
)
|
||||
);
|
||||
},
|
||||
[
|
||||
[]
|
||||
]
|
||||
);
|
||||
},
|
||||
// 检测sku选项是否已全部选完,且有库存
|
||||
checkSelectComplete(obj = {}) {
|
||||
let selectShop = that.selectShop;
|
||||
if (selectShop && selectShop[that.skuIdName] !== undefined) {
|
||||
// 判断库存
|
||||
if (that.selectNum <= selectShop[that.stockName]) {
|
||||
if (typeof obj.success == "function") obj.success(selectShop);
|
||||
} else {
|
||||
that.toast(that.stockText + "不足", "none")
|
||||
}
|
||||
} else {
|
||||
that.toast("请先选择对应规格", "none");
|
||||
}
|
||||
},
|
||||
// 加入购物车
|
||||
addCart() {
|
||||
that.checkSelectComplete({
|
||||
success: function(selectShop) {
|
||||
selectShop.buy_num = that.selectNum;
|
||||
that.$emit("add-cart", selectShop);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 立即购买
|
||||
buyNow() {
|
||||
that.checkSelectComplete({
|
||||
success: function(selectShop) {
|
||||
selectShop.buy_num = that.selectNum;
|
||||
that.$emit("buy-now", selectShop);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 弹窗
|
||||
toast(title, icon) {
|
||||
uni.showToast({
|
||||
title: title,
|
||||
icon: icon
|
||||
});
|
||||
},
|
||||
// 获取对象数组中的某一个item,根据指定的键值
|
||||
getListItem(list, key, value) {
|
||||
let item;
|
||||
for (let i in list) {
|
||||
if (typeof value == "object") {
|
||||
if (JSON.stringify(list[i][key]) === JSON.stringify(value)) {
|
||||
item = list[i];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (list[i][key] === value) {
|
||||
item = list[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return item;
|
||||
},
|
||||
// 自动选择sku前提是只有一组sku,默认自动选择最前面的有库存的sku
|
||||
autoClickSku() {
|
||||
let skuList = that.goodsInfo[that.skuListName];
|
||||
let specListArr = that.goodsInfo[that.specListName];
|
||||
|
||||
if (specListArr.length == 1) {
|
||||
let specList = specListArr[0].list;
|
||||
|
||||
for (let i = 0; i < specList.length; i++) {
|
||||
let sku = that.getListItem(skuList, that.skuArrName, [specList[i].name]);
|
||||
if (sku) {
|
||||
that.skuClick(specList[i], 0, {}, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 过滤器
|
||||
filters: {
|
||||
// 金额显示过滤器
|
||||
priceFilter(n = 0) {
|
||||
if (typeof n == "string") {
|
||||
n = parseFloat(n)
|
||||
}
|
||||
return n ? n.toFixed(2) : n
|
||||
}
|
||||
},
|
||||
// 计算属性
|
||||
computed: {
|
||||
|
||||
},
|
||||
watch: {
|
||||
value: function(val) {
|
||||
if (val) {
|
||||
that.open();
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* sku弹出层 */
|
||||
.goods-sku-popup {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
&.show {
|
||||
display: block;
|
||||
|
||||
.mask {
|
||||
animation: showPopup 0.2s linear both;
|
||||
}
|
||||
|
||||
.layer {
|
||||
animation: showLayer 0.2s linear both;
|
||||
}
|
||||
}
|
||||
|
||||
&.hide {
|
||||
.mask {
|
||||
animation: hidePopup 0.2s linear both;
|
||||
}
|
||||
|
||||
.layer {
|
||||
animation: hideLayer 0.2s linear both;
|
||||
}
|
||||
}
|
||||
|
||||
&.none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.layer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-height: 960rpx;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
bottom: 0;
|
||||
border-radius: 10rpx 10rpx 0 0;
|
||||
background-color: #ffffff;
|
||||
margin-top: 10rpx;
|
||||
overflow-y: scroll;
|
||||
.btn-option {
|
||||
padding: 1rpx;
|
||||
display: block;
|
||||
clear: both;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
.specification-wrapper {
|
||||
width: 100%;
|
||||
margin-top: 20rpx;
|
||||
padding: 30rpx 25rpx;
|
||||
box-sizing: border-box;
|
||||
.specification-wrapper-content {
|
||||
width: 100%;
|
||||
min-height: 300rpx;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
/*隐藏滚轮*/
|
||||
display: none;
|
||||
}
|
||||
|
||||
.specification-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.specification-left {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
flex: 0 0 180rpx;
|
||||
|
||||
.product-img {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
background-color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.specification-right {
|
||||
flex: 1;
|
||||
padding: 0 35rpx 10rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
font-weight: 500;
|
||||
|
||||
.price-content {
|
||||
color: #fe560a;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.sign {
|
||||
font-size: 28rpx;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.inventory {
|
||||
font-size: 24rpx;
|
||||
color: #525252;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.choose {
|
||||
font-size: 24rpx;
|
||||
color: #525252;
|
||||
min-height: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.specification-content {
|
||||
font-weight: 500;
|
||||
.specification-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
margin-bottom: 15rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-flow: wrap;
|
||||
margin-bottom: -20rpx;
|
||||
|
||||
.item-content {
|
||||
display: block;
|
||||
padding: 10rpx 20rpx;
|
||||
min-width: 110rpx;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
border-radius: 30rpx;
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
margin-right: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border: 2rpx solid #cccccc;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.actived {
|
||||
border-color: #fe560a;
|
||||
color: #fe560a;
|
||||
}
|
||||
|
||||
&.noactived {
|
||||
// background-color: #e4e4e4;
|
||||
// border-color: #e4e4e4;
|
||||
// color: #9e9e9e;
|
||||
// text-decoration: line-through;
|
||||
color: #c8c9cc;
|
||||
background: #f2f3f5;
|
||||
border-color: #f2f3f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 25rpx;
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
text-align: center;
|
||||
line-height: 50rpx;
|
||||
|
||||
.close-item {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
flex: 0 0 120rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 26rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.layer-btn {
|
||||
width: 335rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
color: #fff;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
|
||||
&.add-cart {
|
||||
background: #ffbe46;
|
||||
}
|
||||
|
||||
&.buy {
|
||||
background: #fe560a;
|
||||
}
|
||||
}
|
||||
|
||||
.sure {
|
||||
width: 698rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 38rpx;
|
||||
color: #fff;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
background: #fe560a;
|
||||
}
|
||||
|
||||
.sure.add-cart {
|
||||
background: #ff9402;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes showPopup {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hidePopup {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes showLayer {
|
||||
0% {
|
||||
transform: translateY(120%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hideLayer {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(120%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
394
components/goods-sku-popup/number-box/index.vue
Normal file
394
components/goods-sku-popup/number-box/index.vue
Normal file
@ -0,0 +1,394 @@
|
||||
<template>
|
||||
<view class="number-box">
|
||||
<view class="u-icon-minus" @touchstart.prevent="btnTouchStart('minus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal <= min }"
|
||||
:style="{
|
||||
background: bgColor,
|
||||
height: inputHeight + 'rpx',
|
||||
color: color,
|
||||
fontSize: size + 'rpx',
|
||||
minHeight: '1.4em'
|
||||
}">
|
||||
<view :style="'font-size:'+(Number(size)+10)+'rpx'" class="num-btn">-</view>
|
||||
</view>
|
||||
<input :disabled="disabledInput || disabled" :cursor-spacing="getCursorSpacing" :class="{ 'u-input-disabled': disabled }"
|
||||
v-model="inputVal" class="u-number-input" @blur="onBlur"
|
||||
type="number" :style="{
|
||||
color: color,
|
||||
fontSize: size + 'rpx',
|
||||
background: bgColor,
|
||||
height: inputHeight + 'rpx',
|
||||
width: inputWidth + 'rpx',
|
||||
}" />
|
||||
<view class="u-icon-plus" @touchstart.prevent="btnTouchStart('plus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal >= max }"
|
||||
:style="{
|
||||
background: bgColor,
|
||||
height: inputHeight + 'rpx',
|
||||
color: color,
|
||||
fontSize: size + 'rpx',
|
||||
minHeight: '1.4em',
|
||||
}">
|
||||
<view :style="'font-size:'+(Number(size)+10)+'rpx'" class="num-btn">+</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* numberBox 步进器
|
||||
* @description 该组件一般用于商城购物选择物品数量的场景。注意:该输入框只能输入大于或等于0的整数,不支持小数输入
|
||||
* @tutorial https://www.uviewui.com/components/numberBox.html
|
||||
* @property {Number} value 输入框初始值(默认1)
|
||||
* @property {String} bg-color 输入框和按钮的背景颜色(默认#F2F3F5)
|
||||
* @property {Number} min 用户可输入的最小值(默认0)
|
||||
* @property {Number} max 用户可输入的最大值(默认99999)
|
||||
* @property {Number} step 步长,每次加或减的值(默认1)
|
||||
* @property {Number} stepFirst 步进值,首次增加或最后减的值(默认step值和一致)
|
||||
* @property {Boolean} disabled 是否禁用操作,禁用后无法加减或手动修改输入框的值(默认false)
|
||||
* @property {Boolean} disabled-input 是否禁止输入框手动输入值(默认false)
|
||||
* @property {Boolean} positive-integer 是否只能输入正整数(默认true)
|
||||
* @property {String | Number} size 输入框文字和按钮字体大小,单位rpx(默认26)
|
||||
* @property {String} color 输入框文字和加减按钮图标的颜色(默认#323233)
|
||||
* @property {String | Number} input-width 输入框宽度,单位rpx(默认80)
|
||||
* @property {String | Number} input-height 输入框和按钮的高度,单位rpx(默认50)
|
||||
* @property {String | Number} index 事件回调时用以区分当前发生变化的是哪个输入框
|
||||
* @property {Boolean} long-press 是否开启长按连续递增或递减(默认true)
|
||||
* @property {String | Number} press-time 开启长按触发后,每触发一次需要多久,单位ms(默认250)
|
||||
* @property {String | Number} cursor-spacing 指定光标于键盘的距离,避免键盘遮挡输入框,单位rpx(默认200)
|
||||
* @event {Function} change 输入框内容发生变化时触发,对象形式
|
||||
* @event {Function} blur 输入框失去焦点时触发,对象形式
|
||||
* @event {Function} minus 点击减少按钮时触发(按钮可点击情况下),对象形式
|
||||
* @event {Function} plus 点击增加按钮时触发(按钮可点击情况下),对象形式
|
||||
* @example <number-box :min="1" :max="100"></number-box>
|
||||
*/
|
||||
export default {
|
||||
name: "NumberBox",
|
||||
props: {
|
||||
// 预显示的数字
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#F2F3F5'
|
||||
},
|
||||
// 最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 99999
|
||||
},
|
||||
// 步进值,每次加或减的值
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 步进值,首次增加或最后减的值
|
||||
stepFirst: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否禁用加减操作
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// input的字体大小,单位rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 加减图标的颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#323233'
|
||||
},
|
||||
// input宽度,单位rpx
|
||||
inputWidth: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
// input高度,单位rpx
|
||||
inputHeight: {
|
||||
type: [Number, String],
|
||||
default: 50
|
||||
},
|
||||
// index索引,用于列表中使用,让用户知道是哪个numberbox发生了变化,一般使用for循环出来的index值即可
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用输入框,与disabled作用于输入框时,为OR的关系,即想要禁用输入框,又可以加减的话
|
||||
// 设置disabled为false,disabledInput为true即可
|
||||
disabledInput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入框于键盘之间的距离
|
||||
cursorSpacing: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
// 是否开启长按连续递增或递减
|
||||
longPress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 开启长按触发后,每触发一次需要多久
|
||||
pressTime: {
|
||||
type: [Number, String],
|
||||
default: 250
|
||||
},
|
||||
// 是否只能输入大于或等于0的整数(正整数)
|
||||
positiveInteger: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(v1, v2) {
|
||||
// 只有value的改变是来自外部的时候,才去同步inputVal的值,否则会造成循环错误
|
||||
if(!this.changeFromInner) {
|
||||
this.inputVal = v1;
|
||||
// 因为inputVal变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true,
|
||||
// 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
|
||||
// 将changeFromInner设置为false
|
||||
this.$nextTick(function(){
|
||||
this.changeFromInner = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
inputVal(v1, v2) {
|
||||
// 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
|
||||
if (v1 == '') return;
|
||||
let value = 0;
|
||||
// 首先判断是否数值,并且在min和max之间,如果不是,使用原来值
|
||||
let tmp = this.isNumber(v1);
|
||||
if (tmp && v1 >= this.min && v1 <= this.max) value = v1;
|
||||
else value = v2;
|
||||
// 判断是否只能输入大于等于0的整数
|
||||
if(this.positiveInteger) {
|
||||
// 小于0,或者带有小数点,
|
||||
if(v1 < 0 || String(v1).indexOf('.') !== -1) {
|
||||
value = v2;
|
||||
// 双向绑定input的值,必须要使用$nextTick修改显示的值
|
||||
this.$nextTick(() => {
|
||||
this.inputVal = v2;
|
||||
})
|
||||
}
|
||||
}
|
||||
// 发出change事件
|
||||
this.handleChange(value, 'change');
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputVal: 1, // 输入框中的值,不能直接使用props中的value,因为应该改变props的状态
|
||||
timer: null, // 用作长按的定时器
|
||||
changeFromInner: false, // 值发生变化,是来自内部还是外部
|
||||
innerChangeTimer: null, // 内部定时器
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.inputVal = Number(this.value);
|
||||
},
|
||||
computed: {
|
||||
getCursorSpacing() {
|
||||
// 先将值转为px单位,再转为数值
|
||||
return Number(uni.upx2px(this.cursorSpacing));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击退格键
|
||||
btnTouchStart(callback) {
|
||||
// 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能
|
||||
this[callback]();
|
||||
// 如果没开启长按功能,直接返回
|
||||
if (!this.longPress) return;
|
||||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
|
||||
this.timer = null;
|
||||
this.timer = setInterval(() => {
|
||||
// 执行加或减函数
|
||||
this[callback]();
|
||||
}, this.pressTime);
|
||||
},
|
||||
clearTimer() {
|
||||
this.$nextTick(() => {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
})
|
||||
},
|
||||
minus() {
|
||||
this.computeVal('minus');
|
||||
},
|
||||
plus() {
|
||||
this.computeVal('plus');
|
||||
},
|
||||
// 为了保证小数相加减出现精度溢出的问题
|
||||
calcPlus(num1, num2) {
|
||||
let baseNum, baseNum1, baseNum2;
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum1 = 0;
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum2 = 0;
|
||||
}
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; //精度
|
||||
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision);
|
||||
},
|
||||
// 为了保证小数相加减出现精度溢出的问题
|
||||
calcMinus(num1, num2) {
|
||||
let baseNum, baseNum1, baseNum2;
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum1 = 0;
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum2 = 0;
|
||||
}
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2;
|
||||
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision);
|
||||
},
|
||||
computeVal(type) {
|
||||
uni.hideKeyboard();
|
||||
if (this.disabled) return;
|
||||
let value = 0;
|
||||
// 新增stepFirst开始
|
||||
// 减
|
||||
if (type === 'minus') {
|
||||
if(this.stepFirst > 0 && this.inputVal == this.stepFirst){
|
||||
value = this.min;
|
||||
}else{
|
||||
value = this.calcMinus(this.inputVal, this.step);
|
||||
}
|
||||
} else if (type === 'plus') {
|
||||
if(this.stepFirst > 0 && this.inputVal < this.stepFirst){
|
||||
value = this.stepFirst;
|
||||
}else{
|
||||
value = this.calcPlus(this.inputVal, this.step);
|
||||
}
|
||||
}
|
||||
if (value > this.max ) {
|
||||
value = this.max;
|
||||
}else if (value < this.min) {
|
||||
value = this.min;
|
||||
}
|
||||
// 新增stepFirst结束
|
||||
this.inputVal = value;
|
||||
this.handleChange(value, type);
|
||||
},
|
||||
// 处理用户手动输入的情况
|
||||
onBlur(event) {
|
||||
let val = 0;
|
||||
let value = event.detail.value;
|
||||
// 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
|
||||
// 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
|
||||
if (!/(^\d+$)/.test(value) || value[0] == 0) val = this.min;
|
||||
val = +value;
|
||||
if (val > this.max) {
|
||||
val = this.max;
|
||||
} else if (val < this.min) {
|
||||
val = this.min;
|
||||
}
|
||||
// 新增stepFirst开始
|
||||
if(this.stepFirst > 0 && this.inputVal < this.stepFirst && this.inputVal>0){
|
||||
val = this.stepFirst;
|
||||
}
|
||||
// 新增stepFirst结束
|
||||
this.$nextTick(() => {
|
||||
this.inputVal = val;
|
||||
})
|
||||
this.handleChange(val, 'blur');
|
||||
},
|
||||
handleChange(value, type) {
|
||||
if (this.disabled) return;
|
||||
// 清除定时器,避免造成混乱
|
||||
if(this.innerChangeTimer) {
|
||||
clearTimeout(this.innerChangeTimer);
|
||||
this.innerChangeTimer = null;
|
||||
}
|
||||
// 发出input事件,修改通过v-model绑定的值,达到双向绑定的效果
|
||||
this.changeFromInner = true;
|
||||
// 一定时间内,清除changeFromInner标记,否则内部值改变后
|
||||
// 外部通过程序修改value值,将会无效
|
||||
this.innerChangeTimer = setTimeout(() => {
|
||||
this.changeFromInner = false;
|
||||
}, 150);
|
||||
this.$emit('input', Number(value));
|
||||
this.$emit(type, {
|
||||
// 转为Number类型
|
||||
value: Number(value),
|
||||
index: this.index
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 验证十进制数字
|
||||
*/
|
||||
isNumber(value) {
|
||||
return /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.number-box {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-number-input {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin: 0 6rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-icon-plus,
|
||||
.u-icon-minus {
|
||||
width: 60rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-icon-plus {
|
||||
border-radius: 0 8rpx 8rpx 0;
|
||||
}
|
||||
|
||||
.u-icon-minus {
|
||||
border-radius: 8rpx 0 0 8rpx;
|
||||
}
|
||||
|
||||
.u-icon-disabled {
|
||||
color: #c8c9cc !important;
|
||||
background: #f7f8fa !important;
|
||||
}
|
||||
|
||||
.u-input-disabled {
|
||||
color: #c8c9cc !important;
|
||||
background-color: #f2f3f5 !important;
|
||||
}
|
||||
.num-btn{
|
||||
font-weight:550;
|
||||
position: relative;
|
||||
top:-4rpx;
|
||||
}
|
||||
|
||||
</style>
|
524
components/grade-popup/index.vue
Normal file
524
components/grade-popup/index.vue
Normal file
@ -0,0 +1,524 @@
|
||||
<template>
|
||||
<view class="grade-popup popup" catchtouchmove="true" :class="(value && complete) ? 'show' : 'none'"
|
||||
@touchmove.stop.prevent="moveHandle">
|
||||
|
||||
<view class="mask" @click="close('mask')"></view>
|
||||
<!-- 要购买的等级信息确认 start -->
|
||||
<view v-if="!isShowPay" class="layer attr-content" :style="'border-radius: 10rpx 10rpx 0 0;'">
|
||||
<view class="specification-wrapper">
|
||||
<scroll-view class="specification-wrapper-content" scroll-y="true">
|
||||
<view class="specification-header">
|
||||
<view class="specification-name"><text class="price" v-if="memberGrade.catchValue > 0">¥{{ memberGrade.catchValue }}</text>购买{{ memberGrade.name }}</view>
|
||||
</view>
|
||||
<view class="specification-content">
|
||||
<view class="grade-item" v-if="memberGrade.discount > 0">
|
||||
<view class="item-rule"><view class="title">买单折扣:</view>{{ memberGrade.discount }}折</view>
|
||||
</view>
|
||||
<view class="grade-item" v-if="memberGrade.speedPoint > 0">
|
||||
<view class="item-rule"><view class="title">积分加速:</view>{{ memberGrade.speedPoint }}倍</view>
|
||||
</view>
|
||||
<view class="grade-item">
|
||||
<view class="item-rule">
|
||||
<view class="title">有效期限:</view>
|
||||
<text v-if="memberGrade.validDay > 0">{{ memberGrade.validDay }}天</text>
|
||||
<text v-else>永久</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="grade-description">
|
||||
<view class="item-rule">
|
||||
<view class="title">权益说明:</view>
|
||||
<view>{{ memberGrade.userPrivilege ? memberGrade.userPrivilege : '暂无'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="close" @click="close('close')" v-if="showClose">
|
||||
<image class="close-item" :src="closeImage"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-wrapper">
|
||||
<view class="sure" @click="buyNow">立即购买</view>
|
||||
</view>
|
||||
<!-- 页面结束 -->
|
||||
</view>
|
||||
<!-- 要购买的等级信息确认 end -->
|
||||
|
||||
<!-- 支付信息确认 start -->
|
||||
<view class="confirm" v-if="isShowPay">
|
||||
<view class="layer attr-content" :style="'border-radius: 10rpx 10rpx 0 0;'">
|
||||
<view class="specification-wrapper">
|
||||
<scroll-view class="specification-wrapper-content" scroll-y="true">
|
||||
<view class="specification-header">
|
||||
<view class="specification-name">支付确认</view>
|
||||
</view>
|
||||
<view class="specification-content">
|
||||
<view v-if="couponInfo && couponInfo.amount" class="pay-item">
|
||||
<view class="item-point">
|
||||
<view class="title" @click="doUseCoupon">
|
||||
<text v-if="useCoupon" class="iconfont is-use icon-success"></text>
|
||||
<text v-if="!useCoupon" class="iconfont icon-success"></text>
|
||||
<text class="point-amount">使用卡券抵扣</text>
|
||||
<text class="amount">¥{{ couponInfo.amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pay-item">
|
||||
<view class="item-amount">
|
||||
<view class="title">
|
||||
实付金额:<text class="amount">¥{{ ((parseFloat(memberGrade.catchValue) - parseFloat(useCouponInfo.amount)) >= 0 ? (parseFloat(memberGrade.catchValue) - parseFloat(useCouponInfo.amount)) : 0.0.toFixed(2)) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="close" @click="close('close')" v-if="showClose">
|
||||
<image class="close-item" :src="closeImage"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-wrapper">
|
||||
<view class="sure" @click="doBuy">确认支付</view>
|
||||
</view>
|
||||
<!-- 页面结束 -->
|
||||
</view>
|
||||
</view>
|
||||
<!-- 支付信息确认 end -->
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as SettlementApi from '@/api/settlement'
|
||||
import PayTypeEnum from '@/common/enum/order/PayType'
|
||||
import { wxPayment } from '@/utils/app'
|
||||
|
||||
var that; // 当前页面对象
|
||||
var vk; // 自定义函数集
|
||||
export default {
|
||||
name: 'GradePopup',
|
||||
props: {
|
||||
// true 组件显示 false 组件隐藏
|
||||
value: {
|
||||
Type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// vk云函数路由模式参数开始-----------------------------------------------------------
|
||||
// 等级信息
|
||||
memberGrade: {
|
||||
Type: Object,
|
||||
default: {}
|
||||
},
|
||||
// vk云函数路由模式参数结束-----------------------------------------------------------
|
||||
// 点击遮罩是否关闭组件 true 关闭 false 不关闭 默认true
|
||||
maskCloseAble: {
|
||||
Type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示右上角关闭按钮
|
||||
showClose: {
|
||||
Type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 关闭按钮的图片地址
|
||||
closeImage: {
|
||||
Type: String,
|
||||
default: "https://img.alicdn.com/imgextra/i1/121022687/O1CN01ImN0O11VigqwzpLiK_!!121022687.png"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
complete: false, // 组件是否加载完成
|
||||
isShowPay: false, // true 显示 false 隐藏
|
||||
useCoupon: true, // 是否使用卡券
|
||||
useCouponInfo: { amount: 0, id: '' }, // 使用的卡券
|
||||
couponInfo: null // 可用卡券
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
that = this;
|
||||
},
|
||||
methods: {
|
||||
async open() {
|
||||
that.complete = true;
|
||||
that.$emit("open", true);
|
||||
that.$emit("input", true);
|
||||
},
|
||||
// 监听 - 弹出层收起
|
||||
close(s) {
|
||||
if (s == "close") {
|
||||
that.$emit("input", false);
|
||||
that.$emit("close", "close");
|
||||
that.isShowPay = false;
|
||||
that.useCoupon = true;
|
||||
} else if (s == "mask") {
|
||||
if (that.maskCloseAble) {
|
||||
that.$emit("input", false);
|
||||
that.$emit("close", "mask");
|
||||
}
|
||||
}
|
||||
},
|
||||
moveHandle() {
|
||||
//禁止父元素滑动
|
||||
},
|
||||
// 立即购买
|
||||
buyNow() {
|
||||
this.prePay();
|
||||
},
|
||||
// 是否使用卡券
|
||||
doUseCoupon() {
|
||||
if (this.useCoupon) {
|
||||
this.useCoupon = false;
|
||||
this.useCouponInfo = { amount: 0, id: '' };
|
||||
} else {
|
||||
this.useCoupon = true;
|
||||
this.useCouponInfo = this.couponInfo;
|
||||
}
|
||||
},
|
||||
// 确认购买
|
||||
doBuy() {
|
||||
const app = this
|
||||
let couponId = "";
|
||||
if (app.useCoupon) {
|
||||
couponId = app.couponInfo ? app.couponInfo.userCouponId : 0;
|
||||
}
|
||||
// 请求api
|
||||
SettlementApi.submit(app.memberGrade.id, "", "member", "", "", 0, couponId, "", 0, 0, 0, "", "JSAPI")
|
||||
.then(result => app.onSubmitCallback(result))
|
||||
.catch(err => {
|
||||
if (err.result) {
|
||||
const errData = err.result.data
|
||||
if (errData) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 支付前查询
|
||||
prePay() {
|
||||
const app = this
|
||||
// 请求api
|
||||
SettlementApi.prePay({ type: 'memberGrade' })
|
||||
.then(result => {
|
||||
if (result.data) {
|
||||
if (result.data.canUseCouponInfo) {
|
||||
app.couponInfo = result.data.canUseCouponInfo;
|
||||
if (app.useCoupon) {
|
||||
app.useCouponInfo = app.couponInfo;
|
||||
}
|
||||
}
|
||||
app.isShowPay = true;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.result) {
|
||||
const errData = err.result.data;
|
||||
if (errData) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 订单提交成功后回调
|
||||
onSubmitCallback(result) {
|
||||
const app = this
|
||||
// 微信支付
|
||||
if (result.data.payType == PayTypeEnum.WECHAT.value) {
|
||||
wxPayment(result.data.payment)
|
||||
.then(() => {
|
||||
uni.showModal({
|
||||
title: '支付结果',
|
||||
content: '支付成功',
|
||||
showCancel: false,
|
||||
success(o) {
|
||||
if (o.confirm) {
|
||||
app.$router.go(0);
|
||||
app.$emit('onPaySuccess');
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(err => app.$error('支付失败'))
|
||||
.finally(() => {
|
||||
//empty
|
||||
})
|
||||
}
|
||||
|
||||
// 余额支付
|
||||
if (result.data.payType == PayTypeEnum.BALANCE.value) {
|
||||
app.$error('支付成功');
|
||||
app.$emit('onPaySuccess');
|
||||
}
|
||||
},
|
||||
// 弹窗
|
||||
toast(title, icon) {
|
||||
uni.showToast({
|
||||
title: title,
|
||||
icon: icon
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function(val) {
|
||||
if (val) {
|
||||
that.open();
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grade-popup {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 21;
|
||||
overflow: hidden;
|
||||
|
||||
&.show {
|
||||
display: block;
|
||||
|
||||
.mask {
|
||||
animation: showPopup 0.2s linear both;
|
||||
}
|
||||
|
||||
.layer {
|
||||
animation: showLayer 0.2s linear both;
|
||||
}
|
||||
}
|
||||
|
||||
&.hide {
|
||||
.mask {
|
||||
animation: hidePopup 0.2s linear both;
|
||||
}
|
||||
|
||||
.layer {
|
||||
animation: hideLayer 0.2s linear both;
|
||||
}
|
||||
}
|
||||
|
||||
&.none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.layer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
bottom: 0;
|
||||
border-radius: 10rpx 10rpx 0 0;
|
||||
background-color: #fff;
|
||||
|
||||
.specification-wrapper {
|
||||
width: 100%;
|
||||
padding: 30rpx 25rpx 10rpx 25rpx;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
|
||||
.specification-wrapper-content {
|
||||
width: 100%;
|
||||
max-height: 900rpx;
|
||||
min-height: 300rpx;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
/*隐藏滚轮*/
|
||||
display: none;
|
||||
}
|
||||
|
||||
.specification-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
margin-bottom: 40rpx;
|
||||
text-align: center;
|
||||
.specification-name {
|
||||
.price {
|
||||
color: #f03c3c;
|
||||
}
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
font-size: 30rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.specification-content {
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
.grade-item {
|
||||
.title {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
float: left;
|
||||
}
|
||||
display: flex;
|
||||
height: 100rpx;
|
||||
padding-top:30rpx;
|
||||
cursor:pointer;
|
||||
.item-rule {
|
||||
padding: 20rpx;
|
||||
border-bottom: solid 1px #cccccc;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.grade-description {
|
||||
margin-top: 20rpx;
|
||||
padding: 20rpx;
|
||||
min-height: 100rpx;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.pay-item {
|
||||
padding: 30rpx;
|
||||
font-size: 30rpx;
|
||||
background: #fff;
|
||||
border: 1rpx solid #00acac;
|
||||
border-radius: 8rpx;
|
||||
color: #888;
|
||||
margin-bottom: 12rpx;
|
||||
text-align: center;
|
||||
.amount {
|
||||
color: #f03c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.iconfont {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
.is-use {
|
||||
color: #00acac
|
||||
}
|
||||
.item-left_icon {
|
||||
margin-right: 20rpx;
|
||||
font-size: 48rpx;
|
||||
|
||||
&.wechat {
|
||||
color: #00c800;
|
||||
}
|
||||
|
||||
&.balance {
|
||||
color: #00acac;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 25rpx;
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
text-align: center;
|
||||
line-height: 50rpx;
|
||||
|
||||
.close-item {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
flex: 0 0 120rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 26rpx;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 120rpx;
|
||||
.layer-btn {
|
||||
width: 335rpx;
|
||||
height: 76rpx;
|
||||
border-radius: 38rpx;
|
||||
color: #fff;
|
||||
line-height: 76rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
|
||||
&.add-cart {
|
||||
background: #ffbe46;
|
||||
}
|
||||
|
||||
&.buy {
|
||||
background: #fe560a;
|
||||
}
|
||||
}
|
||||
|
||||
.sure {
|
||||
width: 698rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
color: #fff;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
background:linear-gradient(to right, #f9211c, #ff6335)
|
||||
}
|
||||
|
||||
.sure.add-cart {
|
||||
background: #ff9402;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes showPopup {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hidePopup {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes showLayer {
|
||||
0% {
|
||||
transform: translateY(120%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hideLayer {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(120%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
645
components/jyf-parser/jyf-parser.vue
Normal file
645
components/jyf-parser/jyf-parser.vue
Normal file
@ -0,0 +1,645 @@
|
||||
<template>
|
||||
<view>
|
||||
<slot v-if="!nodes.length" />
|
||||
<!--#ifdef APP-PLUS-NVUE-->
|
||||
<web-view id="_top" ref="web" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
|
||||
<!--#endif-->
|
||||
<!--#ifndef APP-PLUS-NVUE-->
|
||||
<view id="_top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')">
|
||||
<!--#ifdef H5 || MP-360-->
|
||||
<div :id="'rtf'+uid"></div>
|
||||
<!--#endif-->
|
||||
<!--#ifndef H5 || MP-360-->
|
||||
<trees :nodes="nodes" :lazyLoad="lazyLoad" :loading="loadingImg" />
|
||||
<!--#endif-->
|
||||
</view>
|
||||
<!--#endif-->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var search;
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
import trees from './libs/trees';
|
||||
var cache = {},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
|
||||
// #endif
|
||||
Parser = require('./libs/MpHtmlParser.js');
|
||||
var dom;
|
||||
// 计算 cache 的 key
|
||||
function hash(str) {
|
||||
for (var i = str.length, val = 5381; i--;)
|
||||
val += (val << 5) + str.charCodeAt(i);
|
||||
return val;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5 || APP-PLUS-NVUE || MP-360
|
||||
var {
|
||||
windowWidth,
|
||||
platform
|
||||
} = uni.getSystemInfoSync(),
|
||||
cfg = require('./libs/config.js');
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
var weexDom = weex.requireModule('dom');
|
||||
// #endif
|
||||
/**
|
||||
* Parser 富文本组件
|
||||
* @tutorial https://github.com/jin-yufeng/Parser
|
||||
* @property {String} html 富文本数据
|
||||
* @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
|
||||
* @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
|
||||
* @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
|
||||
* @property {Number} compress 压缩等级
|
||||
* @property {String} domain 图片、视频等链接的主域名
|
||||
* @property {Boolean} lazyLoad 是否开启图片懒加载
|
||||
* @property {String} loadingImg 图片加载完成前的占位图
|
||||
* @property {Boolean} selectable 是否开启长按复制
|
||||
* @property {Object} tagStyle 标签的默认样式
|
||||
* @property {Boolean} showWithAnimation 是否使用渐显动画
|
||||
* @property {Boolean} useAnchor 是否使用锚点
|
||||
* @property {Boolean} useCache 是否缓存解析结果
|
||||
* @event {Function} parse 解析完成事件
|
||||
* @event {Function} load dom 加载完成事件
|
||||
* @event {Function} ready 所有图片加载完毕事件
|
||||
* @event {Function} error 错误事件
|
||||
* @event {Function} imgtap 图片点击事件
|
||||
* @event {Function} linkpress 链接点击事件
|
||||
* @author JinYufeng
|
||||
* @version 20201029
|
||||
* @listens MIT
|
||||
*/
|
||||
export default {
|
||||
name: 'parser',
|
||||
data() {
|
||||
return {
|
||||
// #ifdef H5 || MP-360
|
||||
uid: this._uid,
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
height: 1,
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
showAm: '',
|
||||
// #endif
|
||||
nodes: []
|
||||
}
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
html: String,
|
||||
autopause: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoscroll: Boolean,
|
||||
autosetTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
compress: Number,
|
||||
loadingImg: String,
|
||||
useCache: Boolean,
|
||||
// #endif
|
||||
domain: String,
|
||||
lazyLoad: Boolean,
|
||||
selectable: Boolean,
|
||||
tagStyle: Object,
|
||||
showWithAnimation: Boolean,
|
||||
useAnchor: Boolean
|
||||
},
|
||||
watch: {
|
||||
html(html) {
|
||||
this.setContent(html);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 图片数组
|
||||
this.imgList = [];
|
||||
this.imgList.each = function(f) {
|
||||
for (var i = 0, len = this.length; i < len; i++)
|
||||
this.setItem(i, f(this[i], i, this));
|
||||
}
|
||||
this.imgList.setItem = function(i, src) {
|
||||
if (i == void 0 || !src) return;
|
||||
// #ifndef MP-ALIPAY || APP-PLUS
|
||||
// 去重
|
||||
if (src.indexOf('http') == 0 && this.includes(src)) {
|
||||
var newSrc = src.split('://')[0];
|
||||
for (var j = newSrc.length, c; c = src[j]; j++) {
|
||||
if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
|
||||
newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
|
||||
}
|
||||
newSrc += src.substr(j);
|
||||
return this[i] = newSrc;
|
||||
}
|
||||
// #endif
|
||||
this[i] = src;
|
||||
// 暂存 data src
|
||||
if (src.includes('data:image')) {
|
||||
var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
|
||||
if (!info) return;
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
|
||||
fs && fs.writeFile({
|
||||
filePath,
|
||||
data: info[3],
|
||||
encoding: info[2],
|
||||
success: () => this[i] = filePath
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
|
||||
var bitmap = new plus.nativeObj.Bitmap();
|
||||
bitmap.loadBase64Data(src, () => {
|
||||
bitmap.save(filePath, {}, () => {
|
||||
bitmap.clear()
|
||||
this[i] = filePath;
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5 || MP-360
|
||||
this.document = document.getElementById('rtf' + this._uid);
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
if (dom) this.document = new dom(this);
|
||||
// #endif
|
||||
if (search) this.search = args => search(this, args);
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
this.document = this.$refs.web;
|
||||
setTimeout(() => {
|
||||
// #endif
|
||||
if (this.html) this.setContent(this.html);
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
}, 30)
|
||||
// #endif
|
||||
},
|
||||
beforeDestroy() {
|
||||
// #ifdef H5 || MP-360
|
||||
if (this._observer) this._observer.disconnect();
|
||||
// #endif
|
||||
this.imgList.each(src => {
|
||||
// #ifdef APP-PLUS
|
||||
if (src && src.includes('_doc')) {
|
||||
plus.io.resolveLocalFileSystemURL(src, entry => {
|
||||
entry.remove();
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
if (src && src.includes(uni.env.USER_DATA_PATH))
|
||||
fs && fs.unlink({
|
||||
filePath: src
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
clearInterval(this._timer);
|
||||
},
|
||||
methods: {
|
||||
// 设置富文本内容
|
||||
setContent(html, append) {
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!html)
|
||||
return this.height = 1;
|
||||
if (append)
|
||||
this.$refs.web.evalJs("var b=document.createElement('div');b.innerHTML='" + html.replace(/'/g, "\\'") +
|
||||
"';document.getElementById('parser').appendChild(b)");
|
||||
else {
|
||||
html =
|
||||
'<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>html,body{width:100%;height:100%;overflow:hidden}body{margin:0}</style><base href="' +
|
||||
this.domain + '"><div id="parser"' + (this.selectable ? '>' : ' style="user-select:none">') + this._handleHtml(html).replace(/\n/g, '\\n') +
|
||||
'</div><script>"use strict";function e(e){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[e]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}document.body.onclick=function(){e({action:"click"})},' +
|
||||
(this.showWithAnimation ? 'document.body.style.animation="_show .5s",' : '') +
|
||||
'setTimeout(function(){e({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight})},50);\x3c/script>';
|
||||
if (platform == 'android') html = html.replace(/%/g, '%25');
|
||||
this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
|
||||
}
|
||||
this.$refs.web.evalJs(
|
||||
'var t=document.getElementsByTagName("title");t.length&&e({action:"getTitle",title:t[0].innerText});for(var o,n=document.getElementsByTagName("style"),r=1;o=n[r++];)o.innerHTML=o.innerHTML.replace(/body/g,"#parser");for(var a,c=document.getElementsByTagName("img"),s=[],i=0==c.length,d=0,l=0,g=0;a=c[l];l++)parseInt(a.style.width||a.getAttribute("width"))>' +
|
||||
windowWidth + '&&(a.style.height="auto"),a.onload=function(){++d==c.length&&(i=!0)},a.onerror=function(){++d==c.length&&(i=!0),' + (cfg.errorImg ? 'this.src="' + cfg.errorImg + '",' : '') +
|
||||
'e({action:"error",source:"img",target:this})},a.hasAttribute("ignore")||"A"==a.parentElement.nodeName||(a.i=g++,s.push(a.getAttribute("original-src")||a.src||a.getAttribute("data-src")),a.onclick=function(t){t.stopPropagation(),e({action:"preview",img:{i:this.i,src:this.src}})});e({action:"getImgList",imgList:s});for(var u,m=document.getElementsByTagName("a"),f=0;u=m[f];f++)u.onclick=function(m){m.stopPropagation();var t,o=this.getAttribute("href");if("#"==o[0]){var n=document.getElementById(o.substr(1));n&&(t=n.offsetTop)}return e({action:"linkpress",href:o,offset:t}),!1};for(var h,y=document.getElementsByTagName("video"),v=0;h=y[v];v++)h.style.maxWidth="100%",h.onerror=function(){e({action:"error",source:"video",target:this})}' +
|
||||
(this.autopause ? ',h.onplay=function(){for(var e,t=0;e=y[t];t++)e!=this&&e.pause()}' : '') +
|
||||
';for(var _,p=document.getElementsByTagName("audio"),w=0;_=p[w];w++)_.onerror=function(){e({action:"error",source:"audio",target:this})};' +
|
||||
(this.autoscroll ? 'for(var T,E=document.getElementsByTagName("table"),B=0;T=E[B];B++){var N=document.createElement("div");N.style.overflow="scroll",T.parentNode.replaceChild(N,T),N.appendChild(T)}' : '') +
|
||||
'var x=document.getElementById("parser");clearInterval(window.timer),window.timer=setInterval(function(){i&&clearInterval(window.timer),e({action:"ready",ready:i,height:x.scrollHeight})},350)'
|
||||
)
|
||||
this.nodes = [1];
|
||||
// #endif
|
||||
// #ifdef H5 || MP-360
|
||||
if (!html) {
|
||||
if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
|
||||
return;
|
||||
}
|
||||
var div = document.createElement('div');
|
||||
if (!append) {
|
||||
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
|
||||
this.rtf = div;
|
||||
} else {
|
||||
if (!this.rtf) this.rtf = div;
|
||||
else this.rtf.appendChild(div);
|
||||
}
|
||||
div.innerHTML = this._handleHtml(html, append);
|
||||
for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
|
||||
style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
|
||||
style.setAttribute('scoped', 'true');
|
||||
}
|
||||
// 懒加载
|
||||
if (!this._observer && this.lazyLoad && IntersectionObserver) {
|
||||
this._observer = new IntersectionObserver(changes => {
|
||||
for (let item, i = 0; item = changes[i++];) {
|
||||
if (item.isIntersecting) {
|
||||
item.target.src = item.target.getAttribute('data-src');
|
||||
item.target.removeAttribute('data-src');
|
||||
this._observer.unobserve(item.target);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
rootMargin: '500px 0px 500px 0px'
|
||||
})
|
||||
}
|
||||
var _ts = this;
|
||||
// 获取标题
|
||||
var title = this.rtf.getElementsByTagName('title');
|
||||
if (title.length && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: title[0].innerText
|
||||
})
|
||||
// 填充 domain
|
||||
var fill = target => {
|
||||
var src = target.getAttribute('src');
|
||||
if (this.domain && src) {
|
||||
if (src[0] == '/') {
|
||||
if (src[1] == '/')
|
||||
target.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
|
||||
else target.src = this.domain + src;
|
||||
} else if (!src.includes('://') && src.indexOf('data:') != 0) target.src = this.domain + '/' + src;
|
||||
}
|
||||
}
|
||||
// 图片处理
|
||||
this.imgList.length = 0;
|
||||
var imgs = this.rtf.getElementsByTagName('img');
|
||||
for (let i = 0, j = 0, img; img = imgs[i]; i++) {
|
||||
if (parseInt(img.style.width || img.getAttribute('width')) > windowWidth)
|
||||
img.style.height = 'auto';
|
||||
fill(img);
|
||||
if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
|
||||
img.i = j++;
|
||||
_ts.imgList.push(img.getAttribute('original-src') || img.src || img.getAttribute('data-src'));
|
||||
img.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
var preview = true;
|
||||
this.ignore = () => preview = false;
|
||||
_ts.$emit('imgtap', this);
|
||||
if (preview) {
|
||||
// uni.previewImage({
|
||||
// current: this.i,
|
||||
// urls: _ts.imgList
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
img.onerror = function() {
|
||||
if (cfg.errorImg)
|
||||
_ts.imgList[this.i] = this.src = cfg.errorImg;
|
||||
_ts.$emit('error', {
|
||||
source: 'img',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
|
||||
img.setAttribute('data-src', img.src);
|
||||
img.removeAttribute('src');
|
||||
this._observer.observe(img);
|
||||
}
|
||||
}
|
||||
// 链接处理
|
||||
var links = this.rtf.getElementsByTagName('a');
|
||||
for (var link of links) {
|
||||
link.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
var jump = true,
|
||||
href = this.getAttribute('href');
|
||||
_ts.$emit('linkpress', {
|
||||
href,
|
||||
ignore: () => jump = false
|
||||
});
|
||||
if (jump && href) {
|
||||
if (href[0] == '#') {
|
||||
if (_ts.useAnchor) {
|
||||
_ts.navigateTo({
|
||||
id: href.substr(1)
|
||||
})
|
||||
}
|
||||
} else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
|
||||
return true;
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 视频处理
|
||||
var videos = this.rtf.getElementsByTagName('video');
|
||||
_ts.videoContexts = videos;
|
||||
for (let video, i = 0; video = videos[i++];) {
|
||||
fill(video);
|
||||
video.style.maxWidth = '100%';
|
||||
video.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'video',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
video.onplay = function() {
|
||||
if (_ts.autopause)
|
||||
for (let item, i = 0; item = _ts.videoContexts[i++];)
|
||||
if (item != this) item.pause();
|
||||
}
|
||||
}
|
||||
// 音频处理
|
||||
var audios = this.rtf.getElementsByTagName('audio');
|
||||
for (var audio of audios) {
|
||||
fill(audio);
|
||||
audio.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'audio',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
}
|
||||
// 表格处理
|
||||
if (this.autoscroll) {
|
||||
var tables = this.rtf.getElementsByTagName('table');
|
||||
for (var table of tables) {
|
||||
let div = document.createElement('div');
|
||||
div.style.overflow = 'scroll';
|
||||
table.parentNode.replaceChild(div, table);
|
||||
div.appendChild(table);
|
||||
}
|
||||
}
|
||||
if (!append) this.document.appendChild(this.rtf);
|
||||
this.$nextTick(() => {
|
||||
this.nodes = [1];
|
||||
this.$emit('load');
|
||||
});
|
||||
setTimeout(() => this.showAm = '', 500);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
// #ifndef H5 || MP-360
|
||||
var nodes;
|
||||
if (!html) return this.nodes = [];
|
||||
var parser = new Parser(html, this);
|
||||
// 缓存读取
|
||||
if (this.useCache) {
|
||||
var hashVal = hash(html);
|
||||
if (cache[hashVal])
|
||||
nodes = cache[hashVal];
|
||||
else {
|
||||
nodes = parser.parse();
|
||||
cache[hashVal] = nodes;
|
||||
}
|
||||
} else nodes = parser.parse();
|
||||
this.$emit('parse', nodes);
|
||||
if (append) this.nodes = this.nodes.concat(nodes);
|
||||
else this.nodes = nodes;
|
||||
if (nodes.length && nodes.title && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: nodes.title
|
||||
})
|
||||
if (this.imgList) this.imgList.length = 0;
|
||||
this.videoContexts = [];
|
||||
this.$nextTick(() => {
|
||||
(function f(cs) {
|
||||
for (var i = cs.length; i--;) {
|
||||
if (cs[i].top) {
|
||||
cs[i].controls = [];
|
||||
cs[i].init();
|
||||
f(cs[i].$children);
|
||||
}
|
||||
}
|
||||
})(this.$children)
|
||||
this.$emit('load');
|
||||
})
|
||||
// #endif
|
||||
var height;
|
||||
clearInterval(this._timer);
|
||||
this._timer = setInterval(() => {
|
||||
// #ifdef H5 || MP-360
|
||||
this.rect = this.rtf.getBoundingClientRect();
|
||||
// #endif
|
||||
// #ifndef H5 || MP-360
|
||||
uni.createSelectorQuery().in(this)
|
||||
.select('#_top').boundingClientRect().exec(res => {
|
||||
if (!res) return;
|
||||
this.rect = res[0];
|
||||
// #endif
|
||||
if (this.rect.height == height) {
|
||||
this.$emit('ready', this.rect)
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
height = this.rect.height;
|
||||
// #ifndef H5 || MP-360
|
||||
});
|
||||
// #endif
|
||||
}, 350);
|
||||
if (this.showWithAnimation && !append) this.showAm = 'animation:_show .5s';
|
||||
// #endif
|
||||
},
|
||||
// 获取文本内容
|
||||
getText(ns = this.nodes) {
|
||||
var txt = '';
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
txt = this._text;
|
||||
// #endif
|
||||
// #ifdef H5 || MP-360
|
||||
txt = this.rtf.innerText;
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
for (var i = 0, n; n = ns[i++];) {
|
||||
if (n.type == 'text') txt += n.text.replace(/ /g, '\u00A0').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/&/g, '&');
|
||||
else if (n.type == 'br') txt += '\n';
|
||||
else {
|
||||
// 块级标签前后加换行
|
||||
var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
|
||||
'0' && n.name[1] < '7');
|
||||
if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
if (n.children) txt += this.getText(n.children);
|
||||
if (block && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
else if (n.name == 'td' || n.name == 'th') txt += '\t';
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
return txt;
|
||||
},
|
||||
// 锚点跳转
|
||||
in (obj) {
|
||||
if (obj.page && obj.selector && obj.scrollTop) this._in = obj;
|
||||
},
|
||||
navigateTo(obj) {
|
||||
if (!this.useAnchor) return obj.fail && obj.fail('Anchor is disabled');
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!obj.id)
|
||||
weexDom.scrollToElement(this.$refs.web);
|
||||
else
|
||||
this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
|
||||
'");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop+' + (obj.offset || 0) + '})');
|
||||
obj.success && obj.success();
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
var d = ' ';
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
||||
d = '>>>';
|
||||
// #endif
|
||||
var selector = uni.createSelectorQuery().in(this._in ? this._in.page : this).select((this._in ? this._in.selector :
|
||||
'#_top') + (obj.id ? `${d}#${obj.id},${this._in?this._in.selector:'#_top'}${d}.${obj.id}` : '')).boundingClientRect();
|
||||
if (this._in) selector.select(this._in.selector).scrollOffset().select(this._in.selector).boundingClientRect();
|
||||
else selector.selectViewport().scrollOffset();
|
||||
selector.exec(res => {
|
||||
if (!res[0]) return obj.fail && obj.fail('Label not found')
|
||||
var scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + (obj.offset || 0);
|
||||
if (this._in) this._in.page[this._in.scrollTop] = scrollTop;
|
||||
else uni.pageScrollTo({
|
||||
scrollTop,
|
||||
duration: 300
|
||||
})
|
||||
obj.success && obj.success();
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 获取视频对象
|
||||
getVideoContext(id) {
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
if (!id) return this.videoContexts;
|
||||
else
|
||||
for (var i = this.videoContexts.length; i--;)
|
||||
if (this.videoContexts[i].id == id) return this.videoContexts[i];
|
||||
// #endif
|
||||
},
|
||||
// #ifdef H5 || APP-PLUS-NVUE || MP-360
|
||||
_handleHtml(html, append) {
|
||||
if (!append) {
|
||||
// 处理 tag-style 和 userAgentStyles
|
||||
var style = '<style>@keyframes _show{0%{opacity:0}100%{opacity:1}}img{max-width:100%;display:block}';
|
||||
for (var item in cfg.userAgentStyles)
|
||||
style += `${item}{${cfg.userAgentStyles[item]}}`;
|
||||
for (item in this.tagStyle)
|
||||
style += `${item}{${this.tagStyle[item]}}`;
|
||||
style += '</style>';
|
||||
html = style + html;
|
||||
}
|
||||
// 处理 rpx
|
||||
if (html.includes('rpx'))
|
||||
html = html.replace(/[0-9.]+\s*rpx/g, $ => (parseFloat($) * windowWidth / 750) + 'px');
|
||||
return html;
|
||||
},
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
_message(e) {
|
||||
// 接收 web-view 消息
|
||||
var d = e.detail.data[0];
|
||||
switch (d.action) {
|
||||
case 'load':
|
||||
this.$emit('load');
|
||||
this.height = d.height;
|
||||
this._text = d.text;
|
||||
break;
|
||||
case 'getTitle':
|
||||
if (this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: d.title
|
||||
})
|
||||
break;
|
||||
case 'getImgList':
|
||||
this.imgList.length = 0;
|
||||
for (var i = d.imgList.length; i--;)
|
||||
this.imgList.setItem(i, d.imgList[i]);
|
||||
break;
|
||||
case 'preview':
|
||||
var preview = true;
|
||||
d.img.ignore = () => preview = false;
|
||||
this.$emit('imgtap', d.img);
|
||||
if (preview)
|
||||
// uni.previewImage({
|
||||
// current: d.img.i,
|
||||
// urls: this.imgList
|
||||
// })
|
||||
break;
|
||||
case 'linkpress':
|
||||
var jump = true,
|
||||
href = d.href;
|
||||
this.$emit('linkpress', {
|
||||
href,
|
||||
ignore: () => jump = false
|
||||
})
|
||||
if (jump && href) {
|
||||
if (href[0] == '#') {
|
||||
if (this.useAnchor)
|
||||
weexDom.scrollToElement(this.$refs.web, {
|
||||
offset: d.offset
|
||||
})
|
||||
} else if (href.includes('://'))
|
||||
plus.runtime.openWeb(href);
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
break;
|
||||
case 'error':
|
||||
if (d.source == 'img' && cfg.errorImg)
|
||||
this.imgList.setItem(d.target.i, cfg.errorImg);
|
||||
this.$emit('error', {
|
||||
source: d.source,
|
||||
target: d.target
|
||||
})
|
||||
break;
|
||||
case 'ready':
|
||||
this.height = d.height;
|
||||
if (d.ready) uni.createSelectorQuery().in(this).select('#_top').boundingClientRect().exec(res => {
|
||||
this.rect = res[0];
|
||||
this.$emit('ready', res[0]);
|
||||
})
|
||||
break;
|
||||
case 'click':
|
||||
this.$emit('click');
|
||||
this.$emit('tap');
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes _show {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
:host {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
100
components/jyf-parser/libs/CssHandler.js
Normal file
100
components/jyf-parser/libs/CssHandler.js
Normal file
@ -0,0 +1,100 @@
|
||||
const cfg = require('./config.js'),
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
|
||||
function CssHandler(tagStyle) {
|
||||
var styles = Object.assign(Object.create(null), cfg.userAgentStyles);
|
||||
for (var item in tagStyle)
|
||||
styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
|
||||
this.styles = styles;
|
||||
}
|
||||
CssHandler.prototype.getStyle = function(data) {
|
||||
this.styles = new parser(data, this.styles).parse();
|
||||
}
|
||||
CssHandler.prototype.match = function(name, attrs) {
|
||||
var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';
|
||||
if (attrs.class) {
|
||||
var items = attrs.class.split(' ');
|
||||
for (var i = 0, item; item = items[i]; i++)
|
||||
if (tmp = this.styles['.' + item])
|
||||
matched += tmp + ';';
|
||||
}
|
||||
if (tmp = this.styles['#' + attrs.id])
|
||||
matched += tmp + ';';
|
||||
return matched;
|
||||
}
|
||||
module.exports = CssHandler;
|
||||
|
||||
function parser(data, init) {
|
||||
this.data = data;
|
||||
this.floor = 0;
|
||||
this.i = 0;
|
||||
this.list = [];
|
||||
this.res = init;
|
||||
this.state = this.Space;
|
||||
}
|
||||
parser.prototype.parse = function() {
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
return this.res;
|
||||
}
|
||||
parser.prototype.section = function() {
|
||||
return this.data.substring(this.start, this.i);
|
||||
}
|
||||
// 状态机
|
||||
parser.prototype.Space = function(c) {
|
||||
if (c == '.' || c == '#' || isLetter(c)) {
|
||||
this.start = this.i;
|
||||
this.state = this.Name;
|
||||
} else if (c == '/' && this.data[this.i + 1] == '*')
|
||||
this.Comment();
|
||||
else if (!cfg.blankChar[c] && c != ';')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.Comment = function() {
|
||||
this.i = this.data.indexOf('*/', this.i) + 1;
|
||||
if (!this.i) this.i = this.data.length;
|
||||
this.state = this.Space;
|
||||
}
|
||||
parser.prototype.Ignore = function(c) {
|
||||
if (c == '{') this.floor++;
|
||||
else if (c == '}' && !--this.floor) {
|
||||
this.list = [];
|
||||
this.state = this.Space;
|
||||
}
|
||||
}
|
||||
parser.prototype.Name = function(c) {
|
||||
if (cfg.blankChar[c]) {
|
||||
this.list.push(this.section());
|
||||
this.state = this.NameSpace;
|
||||
} else if (c == '{') {
|
||||
this.list.push(this.section());
|
||||
this.Content();
|
||||
} else if (c == ',') {
|
||||
this.list.push(this.section());
|
||||
this.Comma();
|
||||
} else if (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.NameSpace = function(c) {
|
||||
if (c == '{') this.Content();
|
||||
else if (c == ',') this.Comma();
|
||||
else if (!cfg.blankChar[c]) this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.Comma = function() {
|
||||
while (cfg.blankChar[this.data[++this.i]]);
|
||||
if (this.data[this.i] == '{') this.Content();
|
||||
else {
|
||||
this.start = this.i--;
|
||||
this.state = this.Name;
|
||||
}
|
||||
}
|
||||
parser.prototype.Content = function() {
|
||||
this.start = ++this.i;
|
||||
if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;
|
||||
var content = this.section();
|
||||
for (var i = 0, item; item = this.list[i++];)
|
||||
if (this.res[item]) this.res[item] += ';' + content;
|
||||
else this.res[item] = content;
|
||||
this.list = [];
|
||||
this.state = this.Space;
|
||||
}
|
580
components/jyf-parser/libs/MpHtmlParser.js
Normal file
580
components/jyf-parser/libs/MpHtmlParser.js
Normal file
@ -0,0 +1,580 @@
|
||||
/**
|
||||
* html 解析器
|
||||
* @tutorial https://github.com/jin-yufeng/Parser
|
||||
* @version 20201029
|
||||
* @author JinYufeng
|
||||
* @listens MIT
|
||||
*/
|
||||
const cfg = require('./config.js'),
|
||||
blankChar = cfg.blankChar,
|
||||
CssHandler = require('./CssHandler.js'),
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth;
|
||||
var emoji;
|
||||
|
||||
function MpHtmlParser(data, options = {}) {
|
||||
this.attrs = {};
|
||||
this.CssHandler = new CssHandler(options.tagStyle, windowWidth);
|
||||
this.data = data;
|
||||
this.domain = options.domain;
|
||||
this.DOM = [];
|
||||
this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
|
||||
options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http';
|
||||
this.options = options;
|
||||
this.state = this.Text;
|
||||
this.STACK = [];
|
||||
// 工具函数
|
||||
this.bubble = () => {
|
||||
for (var i = this.STACK.length, item; item = this.STACK[--i];) {
|
||||
if (cfg.richOnlyTags[item.name]) return false;
|
||||
item.c = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.decode = (val, amp) => {
|
||||
var i = -1,
|
||||
j, en;
|
||||
while (1) {
|
||||
if ((i = val.indexOf('&', i + 1)) == -1) break;
|
||||
if ((j = val.indexOf(';', i + 2)) == -1) break;
|
||||
if (val[i + 1] == '#') {
|
||||
en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j));
|
||||
if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1);
|
||||
} else {
|
||||
en = val.substring(i + 1, j);
|
||||
if (cfg.entities[en] || en == amp)
|
||||
val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
this.getUrl = url => {
|
||||
if (url[0] == '/') {
|
||||
if (url[1] == '/') url = this.options.prot + ':' + url;
|
||||
else if (this.domain) url = this.domain + url;
|
||||
} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
|
||||
url = this.domain + '/' + url;
|
||||
return url;
|
||||
}
|
||||
this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
|
||||
this.section = () => this.data.substring(this.start, this.i);
|
||||
this.parent = () => this.STACK[this.STACK.length - 1];
|
||||
this.siblings = () => this.STACK.length ? this.parent().children : this.DOM;
|
||||
}
|
||||
MpHtmlParser.prototype.parse = function() {
|
||||
if (emoji) this.data = emoji.parseEmoji(this.data);
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
if (this.state == this.Text) this.setText();
|
||||
while (this.STACK.length) this.popNode(this.STACK.pop());
|
||||
return this.DOM;
|
||||
}
|
||||
// 设置属性
|
||||
MpHtmlParser.prototype.setAttr = function() {
|
||||
var name = this.attrName.toLowerCase(),
|
||||
val = this.attrVal;
|
||||
if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
|
||||
else if (val) {
|
||||
if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp'));
|
||||
else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp');
|
||||
else if (name.substr(0, 5) != 'data-') this.attrs[name] = val;
|
||||
}
|
||||
this.attrVal = '';
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
}
|
||||
// 设置文本节点
|
||||
MpHtmlParser.prototype.setText = function() {
|
||||
var back, text = this.section();
|
||||
if (!text) return;
|
||||
text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
|
||||
if (back) {
|
||||
this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
|
||||
let j = this.start + text.length;
|
||||
for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
|
||||
return;
|
||||
}
|
||||
if (!this.pre) {
|
||||
// 合并空白符
|
||||
var flag, tmp = [];
|
||||
for (let i = text.length, c; c = text[--i];)
|
||||
if (!blankChar[c]) {
|
||||
tmp.unshift(c);
|
||||
if (!flag) flag = 1;
|
||||
} else {
|
||||
if (tmp[0] != ' ') tmp.unshift(' ');
|
||||
if (c == '\n' && flag == void 0) flag = 0;
|
||||
}
|
||||
if (flag == 0) return;
|
||||
text = tmp.join('');
|
||||
}
|
||||
this.siblings().push({
|
||||
type: 'text',
|
||||
text: this.decode(text)
|
||||
});
|
||||
}
|
||||
// 设置元素节点
|
||||
MpHtmlParser.prototype.setNode = function() {
|
||||
var node = {
|
||||
name: this.tagName.toLowerCase(),
|
||||
attrs: this.attrs
|
||||
},
|
||||
close = cfg.selfClosingTags[node.name];
|
||||
if (this.options.nodes.length) node.type = 'node';
|
||||
this.attrs = {};
|
||||
if (!cfg.ignoreTags[node.name]) {
|
||||
// 处理属性
|
||||
var attrs = node.attrs,
|
||||
style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
|
||||
styleObj = {};
|
||||
if (attrs.id) {
|
||||
if (this.options.compress & 1) attrs.id = void 0;
|
||||
else if (this.options.useAnchor) this.bubble();
|
||||
}
|
||||
if ((this.options.compress & 2) && attrs.class) attrs.class = void 0;
|
||||
switch (node.name) {
|
||||
case 'a':
|
||||
case 'ad': // #ifdef APP-PLUS
|
||||
case 'iframe':
|
||||
// #endif
|
||||
this.bubble();
|
||||
break;
|
||||
case 'font':
|
||||
if (attrs.color) {
|
||||
styleObj['color'] = attrs.color;
|
||||
attrs.color = void 0;
|
||||
}
|
||||
if (attrs.face) {
|
||||
styleObj['font-family'] = attrs.face;
|
||||
attrs.face = void 0;
|
||||
}
|
||||
if (attrs.size) {
|
||||
var size = parseInt(attrs.size);
|
||||
if (size < 1) size = 1;
|
||||
else if (size > 7) size = 7;
|
||||
var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
|
||||
styleObj['font-size'] = map[size - 1];
|
||||
attrs.size = void 0;
|
||||
}
|
||||
break;
|
||||
case 'embed':
|
||||
// #ifndef APP-PLUS
|
||||
var src = node.attrs.src || '',
|
||||
type = node.attrs.type || '';
|
||||
if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8'))
|
||||
node.name = 'video';
|
||||
else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes(
|
||||
'.aac'))
|
||||
node.name = 'audio';
|
||||
else break;
|
||||
if (node.attrs.autostart)
|
||||
node.attrs.autoplay = 'T';
|
||||
node.attrs.controls = 'T';
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
this.bubble();
|
||||
break;
|
||||
// #endif
|
||||
case 'video':
|
||||
case 'audio':
|
||||
if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
|
||||
else this[`${node.name}Num`]++;
|
||||
if (node.name == 'video') {
|
||||
if (this.videoNum > 3)
|
||||
node.lazyLoad = 1;
|
||||
if (attrs.width) {
|
||||
styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px');
|
||||
attrs.width = void 0;
|
||||
}
|
||||
if (attrs.height) {
|
||||
styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px');
|
||||
attrs.height = void 0;
|
||||
}
|
||||
}
|
||||
if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T';
|
||||
attrs.source = [];
|
||||
if (attrs.src) {
|
||||
attrs.source.push(attrs.src);
|
||||
attrs.src = void 0;
|
||||
}
|
||||
this.bubble();
|
||||
break;
|
||||
case 'td':
|
||||
case 'th':
|
||||
if (attrs.colspan || attrs.rowspan)
|
||||
for (var k = this.STACK.length, item; item = this.STACK[--k];)
|
||||
if (item.name == 'table') {
|
||||
item.flag = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attrs.align) {
|
||||
if (node.name == 'table') {
|
||||
if (attrs.align == 'center') styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto';
|
||||
else styleObj['float'] = attrs.align;
|
||||
} else styleObj['text-align'] = attrs.align;
|
||||
attrs.align = void 0;
|
||||
}
|
||||
// 压缩 style
|
||||
var styles = style.split(';');
|
||||
style = '';
|
||||
for (var i = 0, len = styles.length; i < len; i++) {
|
||||
var info = styles[i].split(':');
|
||||
if (info.length < 2) continue;
|
||||
let key = info[0].trim().toLowerCase(),
|
||||
value = info.slice(1).join(':').trim();
|
||||
if (value[0] == '-' || value.includes('safe'))
|
||||
style += `;${key}:${value}`;
|
||||
else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
|
||||
styleObj[key] = value;
|
||||
}
|
||||
if (node.name == 'img') {
|
||||
if (attrs.src && !attrs.ignore) {
|
||||
if (this.bubble())
|
||||
attrs.i = (this.imgNum++).toString();
|
||||
else attrs.ignore = 'T';
|
||||
}
|
||||
if (attrs.ignore) {
|
||||
style += ';-webkit-touch-callout:none';
|
||||
styleObj['max-width'] = '100%';
|
||||
}
|
||||
var width;
|
||||
if (styleObj.width) width = styleObj.width;
|
||||
else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : parseFloat(attrs.width) + 'px';
|
||||
if (width) {
|
||||
styleObj.width = width;
|
||||
attrs.width = '100%';
|
||||
if (parseInt(width) > windowWidth) {
|
||||
styleObj.height = '';
|
||||
if (attrs.height) attrs.height = void 0;
|
||||
}
|
||||
}
|
||||
if (styleObj.height) {
|
||||
attrs.height = styleObj.height;
|
||||
styleObj.height = '';
|
||||
} else if (attrs.height && !attrs.height.includes('%'))
|
||||
attrs.height = parseFloat(attrs.height) + 'px';
|
||||
}
|
||||
for (var key in styleObj) {
|
||||
var value = styleObj[key];
|
||||
if (!value) continue;
|
||||
if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
|
||||
// 填充链接
|
||||
if (value.includes('url')) {
|
||||
var j = value.indexOf('(');
|
||||
if (j++ != -1) {
|
||||
while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
|
||||
value = value.substr(0, j) + this.getUrl(value.substr(j));
|
||||
}
|
||||
}
|
||||
// 转换 rpx
|
||||
else if (value.includes('rpx'))
|
||||
value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px');
|
||||
else if (key == 'white-space' && value.includes('pre') && !close)
|
||||
this.pre = node.pre = true;
|
||||
style += `;${key}:${value}`;
|
||||
}
|
||||
style = style.substr(1);
|
||||
if (style) attrs.style = style;
|
||||
if (!close) {
|
||||
node.children = [];
|
||||
if (node.name == 'pre' && cfg.highlight) {
|
||||
this.remove(node);
|
||||
this.pre = node.pre = true;
|
||||
}
|
||||
this.siblings().push(node);
|
||||
this.STACK.push(node);
|
||||
} else if (!cfg.filter || cfg.filter(node, this) != false)
|
||||
this.siblings().push(node);
|
||||
} else {
|
||||
if (!close) this.remove(node);
|
||||
else if (node.name == 'source') {
|
||||
var parent = this.parent();
|
||||
if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src)
|
||||
parent.attrs.source.push(node.attrs.src);
|
||||
} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
|
||||
}
|
||||
if (this.data[this.i] == '/') this.i++;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
// 移除标签
|
||||
MpHtmlParser.prototype.remove = function(node) {
|
||||
var name = node.name,
|
||||
j = this.i;
|
||||
// 处理 svg
|
||||
var handleSvg = () => {
|
||||
var src = this.data.substring(j, this.i + 1);
|
||||
node.attrs.xmlns = 'http://www.w3.org/2000/svg';
|
||||
for (var key in node.attrs) {
|
||||
if (key == 'viewbox') src = ` viewBox="${node.attrs.viewbox}"` + src;
|
||||
else if (key != 'style') src = ` ${key}="${node.attrs[key]}"` + src;
|
||||
}
|
||||
src = '<svg' + src;
|
||||
var parent = this.parent();
|
||||
if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
|
||||
parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
|
||||
this.siblings().push({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
|
||||
style: node.attrs.style,
|
||||
ignore: 'T'
|
||||
}
|
||||
})
|
||||
}
|
||||
if (node.name == 'svg' && this.data[j] == '/') return handleSvg(this.i++);
|
||||
while (1) {
|
||||
if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
|
||||
if (name == 'pre' || name == 'svg') this.i = j;
|
||||
else this.i = this.data.length;
|
||||
return;
|
||||
}
|
||||
this.start = (this.i += 2);
|
||||
while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
|
||||
if (this.section().toLowerCase() == name) {
|
||||
// 代码块高亮
|
||||
if (name == 'pre') {
|
||||
this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data
|
||||
.substr(this.i - 5);
|
||||
return this.i = j;
|
||||
} else if (name == 'style')
|
||||
this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
|
||||
else if (name == 'title')
|
||||
this.DOM.title = this.data.substring(j + 1, this.i - 7);
|
||||
if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
|
||||
if (name == 'svg') handleSvg();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 节点出栈处理
|
||||
MpHtmlParser.prototype.popNode = function(node) {
|
||||
// 空白符处理
|
||||
if (node.pre) {
|
||||
node.pre = this.pre = void 0;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].pre)
|
||||
this.pre = true;
|
||||
}
|
||||
var siblings = this.siblings(),
|
||||
len = siblings.length,
|
||||
childs = node.children;
|
||||
if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
|
||||
return siblings.pop();
|
||||
var attrs = node.attrs;
|
||||
// 替换一些标签名
|
||||
if (cfg.blockTags[node.name]) node.name = 'div';
|
||||
else if (!cfg.trustTags[node.name]) node.name = 'span';
|
||||
// 处理列表
|
||||
if (node.c && (node.name == 'ul' || node.name == 'ol')) {
|
||||
if ((node.attrs.style || '').includes('list-style:none')) {
|
||||
for (let i = 0, child; child = childs[i++];)
|
||||
if (child.name == 'li')
|
||||
child.name = 'div';
|
||||
} else if (node.name == 'ul') {
|
||||
var floor = 1;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == 'ul') floor++;
|
||||
if (floor != 1)
|
||||
for (let i = childs.length; i--;)
|
||||
childs[i].floor = floor;
|
||||
} else {
|
||||
for (let i = 0, num = 1, child; child = childs[i++];)
|
||||
if (child.name == 'li') {
|
||||
child.type = 'ol';
|
||||
child.num = ((num, type) => {
|
||||
if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
|
||||
if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
|
||||
if (type == 'i' || type == 'I') {
|
||||
num = (num - 1) % 99 + 1;
|
||||
var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
|
||||
ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
|
||||
res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
|
||||
if (type == 'i') return res.toLowerCase();
|
||||
return res;
|
||||
}
|
||||
return num;
|
||||
})(num++, attrs.type) + '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理表格
|
||||
if (node.name == 'table') {
|
||||
var padding = parseFloat(attrs.cellpadding),
|
||||
spacing = parseFloat(attrs.cellspacing),
|
||||
border = parseFloat(attrs.border);
|
||||
if (node.c) {
|
||||
if (isNaN(padding)) padding = 2;
|
||||
if (isNaN(spacing)) spacing = 2;
|
||||
}
|
||||
if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
|
||||
if (node.flag && node.c) {
|
||||
// 有 colspan 或 rowspan 且含有链接的表格转为 grid 布局实现
|
||||
attrs.style = `${attrs.style || ''};${spacing ? `;grid-gap:${spacing}px` : ';border-left:0;border-top:0'}`;
|
||||
var row = 1,
|
||||
col = 1,
|
||||
colNum,
|
||||
trs = [],
|
||||
children = [],
|
||||
map = {};
|
||||
(function f(ns) {
|
||||
for (var i = 0; i < ns.length; i++) {
|
||||
if (ns[i].name == 'tr') trs.push(ns[i]);
|
||||
else f(ns[i].children || []);
|
||||
}
|
||||
})(node.children)
|
||||
for (let i = 0; i < trs.length; i++) {
|
||||
for (let j = 0, td; td = trs[i].children[j]; j++) {
|
||||
if (td.name == 'td' || td.name == 'th') {
|
||||
while (map[row + '.' + col]) col++;
|
||||
var cell = {
|
||||
name: 'div',
|
||||
c: 1,
|
||||
attrs: {
|
||||
style: (td.attrs.style || '') + (border ? `;border:${border}px solid gray` + (spacing ? '' :
|
||||
';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '')
|
||||
},
|
||||
children: td.children
|
||||
}
|
||||
if (td.attrs.colspan) {
|
||||
cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + parseInt(td.attrs.colspan));
|
||||
if (!td.attrs.rowspan) cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + 1);
|
||||
col += parseInt(td.attrs.colspan) - 1;
|
||||
}
|
||||
if (td.attrs.rowspan) {
|
||||
cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + parseInt(td.attrs.rowspan));
|
||||
if (!td.attrs.colspan) cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + 1);
|
||||
for (var k = 1; k < td.attrs.rowspan; k++) map[(row + k) + '.' + col] = 1;
|
||||
}
|
||||
children.push(cell);
|
||||
col++;
|
||||
}
|
||||
}
|
||||
if (!colNum) {
|
||||
colNum = col - 1;
|
||||
attrs.style += `;grid-template-columns:repeat(${colNum},auto)`
|
||||
}
|
||||
col = 1;
|
||||
row++;
|
||||
}
|
||||
node.children = children;
|
||||
} else {
|
||||
attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
|
||||
if (border || padding)
|
||||
(function f(ns) {
|
||||
for (var i = 0, n; n = ns[i]; i++) {
|
||||
if (n.name == 'th' || n.name == 'td') {
|
||||
if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style || ''}`;
|
||||
if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style || ''}`;
|
||||
} else f(n.children || []);
|
||||
}
|
||||
})(childs)
|
||||
}
|
||||
if (this.options.autoscroll) {
|
||||
var table = Object.assign({}, node);
|
||||
node.name = 'div';
|
||||
node.attrs = {
|
||||
style: 'overflow:scroll'
|
||||
}
|
||||
node.children = [table];
|
||||
}
|
||||
}
|
||||
this.CssHandler.pop && this.CssHandler.pop(node);
|
||||
// 自动压缩
|
||||
if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div')
|
||||
siblings[len - 1] = childs[0];
|
||||
}
|
||||
// 状态机
|
||||
MpHtmlParser.prototype.Text = function(c) {
|
||||
if (c == '<') {
|
||||
var next = this.data[this.i + 1],
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
if (isLetter(next)) {
|
||||
this.setText();
|
||||
this.start = this.i + 1;
|
||||
this.state = this.TagName;
|
||||
} else if (next == '/') {
|
||||
this.setText();
|
||||
if (isLetter(this.data[++this.i + 1])) {
|
||||
this.start = this.i + 1;
|
||||
this.state = this.EndTag;
|
||||
} else this.Comment();
|
||||
} else if (next == '!' || next == '?') {
|
||||
this.setText();
|
||||
this.Comment();
|
||||
}
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.Comment = function() {
|
||||
var key;
|
||||
if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
|
||||
else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
|
||||
else key = '>';
|
||||
if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
|
||||
else this.i += key.length - 1;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
MpHtmlParser.prototype.TagName = function(c) {
|
||||
if (blankChar[c]) {
|
||||
this.tagName = this.section();
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
} else if (this.isClose()) {
|
||||
this.tagName = this.section();
|
||||
this.setNode();
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.AttrName = function(c) {
|
||||
if (c == '=' || blankChar[c] || this.isClose()) {
|
||||
this.attrName = this.section();
|
||||
if (blankChar[c])
|
||||
while (blankChar[this.data[++this.i]]);
|
||||
if (this.data[this.i] == '=') {
|
||||
while (blankChar[this.data[++this.i]]);
|
||||
this.start = this.i--;
|
||||
this.state = this.AttrValue;
|
||||
} else this.setAttr();
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.AttrValue = function(c) {
|
||||
if (c == '"' || c == "'") {
|
||||
this.start++;
|
||||
if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
|
||||
this.attrVal = this.section();
|
||||
this.i++;
|
||||
} else {
|
||||
for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
|
||||
this.attrVal = this.section();
|
||||
}
|
||||
this.setAttr();
|
||||
}
|
||||
MpHtmlParser.prototype.EndTag = function(c) {
|
||||
if (blankChar[c] || c == '>' || c == '/') {
|
||||
var name = this.section().toLowerCase();
|
||||
for (var i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == name) break;
|
||||
if (i != -1) {
|
||||
var node;
|
||||
while ((node = this.STACK.pop()).name != name) this.popNode(node);
|
||||
this.popNode(node);
|
||||
} else if (name == 'p' || name == 'br')
|
||||
this.siblings().push({
|
||||
name,
|
||||
attrs: {}
|
||||
});
|
||||
this.i = this.data.indexOf('>', this.i);
|
||||
this.start = this.i + 1;
|
||||
if (this.i == -1) this.i = this.data.length;
|
||||
else this.state = this.Text;
|
||||
}
|
||||
}
|
||||
module.exports = MpHtmlParser;
|
80
components/jyf-parser/libs/config.js
Normal file
80
components/jyf-parser/libs/config.js
Normal file
@ -0,0 +1,80 @@
|
||||
/* 配置文件 */
|
||||
var cfg = {
|
||||
// 出错占位图
|
||||
errorImg: null,
|
||||
// 过滤器函数
|
||||
filter: null,
|
||||
// 代码高亮函数
|
||||
highlight: null,
|
||||
// 文本处理函数
|
||||
onText: null,
|
||||
// 实体编码列表
|
||||
entities: {
|
||||
quot: '"',
|
||||
apos: "'",
|
||||
semi: ';',
|
||||
nbsp: '\xA0',
|
||||
ensp: '\u2002',
|
||||
emsp: '\u2003',
|
||||
ndash: '–',
|
||||
mdash: '—',
|
||||
middot: '·',
|
||||
lsquo: '‘',
|
||||
rsquo: '’',
|
||||
ldquo: '“',
|
||||
rdquo: '”',
|
||||
bull: '•',
|
||||
hellip: '…'
|
||||
},
|
||||
blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
|
||||
boolAttrs: makeMap('allowfullscreen,autoplay,autostart,controls,ignore,loop,muted'),
|
||||
// 块级标签,将被转为 div
|
||||
blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
|
||||
// 将被移除的标签
|
||||
ignoreTags: makeMap('area,base,canvas,frame,iframe,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'),
|
||||
// 只能被 rich-text 显示的标签
|
||||
richOnlyTags: makeMap('a,colgroup,fieldset,legend'),
|
||||
// 自闭合的标签
|
||||
selfClosingTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
|
||||
// 信任的标签
|
||||
trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
|
||||
// 默认的标签样式
|
||||
userAgentStyles: {
|
||||
address: 'font-style:italic',
|
||||
big: 'display:inline;font-size:1.2em',
|
||||
blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',
|
||||
caption: 'display:table-caption;text-align:center',
|
||||
center: 'text-align:center',
|
||||
cite: 'font-style:italic',
|
||||
dd: 'margin-left:40px',
|
||||
mark: 'background-color:yellow',
|
||||
pre: 'font-family:monospace;white-space:pre;overflow:scroll',
|
||||
s: 'text-decoration:line-through',
|
||||
small: 'display:inline;font-size:0.8em',
|
||||
u: 'text-decoration:underline'
|
||||
}
|
||||
}
|
||||
|
||||
function makeMap(str) {
|
||||
var map = Object.create(null),
|
||||
list = str.split(',');
|
||||
for (var i = list.length; i--;)
|
||||
map[list[i]] = true;
|
||||
return map;
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
if (wx.canIUse('editor')) {
|
||||
cfg.blockTags.pre = void 0;
|
||||
cfg.ignoreTags.rp = true;
|
||||
Object.assign(cfg.richOnlyTags, makeMap('bdi,bdo,caption,rt,ruby'));
|
||||
Object.assign(cfg.trustTags, makeMap('bdi,bdo,caption,pre,rt,ruby'));
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
cfg.ignoreTags.iframe = void 0;
|
||||
Object.assign(cfg.trustTags, makeMap('embed,iframe'));
|
||||
// #endif
|
||||
|
||||
module.exports = cfg;
|
22
components/jyf-parser/libs/handler.wxs
Normal file
22
components/jyf-parser/libs/handler.wxs
Normal file
@ -0,0 +1,22 @@
|
||||
var inline = {
|
||||
abbr: 1,
|
||||
b: 1,
|
||||
big: 1,
|
||||
code: 1,
|
||||
del: 1,
|
||||
em: 1,
|
||||
i: 1,
|
||||
ins: 1,
|
||||
label: 1,
|
||||
q: 1,
|
||||
small: 1,
|
||||
span: 1,
|
||||
strong: 1,
|
||||
sub: 1,
|
||||
sup: 1
|
||||
}
|
||||
module.exports = {
|
||||
use: function(item) {
|
||||
return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1
|
||||
}
|
||||
}
|
506
components/jyf-parser/libs/trees.vue
Normal file
506
components/jyf-parser/libs/trees.vue
Normal file
@ -0,0 +1,506 @@
|
||||
<template>
|
||||
<view :class="'interlayer '+(c||'')" :style="s">
|
||||
<block v-for="(n, i) in nodes" v-bind:key="i">
|
||||
<!--图片-->
|
||||
<view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap.stop="imgtap">
|
||||
<rich-text v-if="ctrl[i]!=0" :nodes="[{attrs:{src:loading&&(ctrl[i]||0)<2?loading:(lazyLoad&&!ctrl[i]?placeholder:(ctrl[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
|
||||
<image class="_image" :src="lazyLoad&&!ctrl[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad"
|
||||
:show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg"
|
||||
@error="error" />
|
||||
</view>
|
||||
<!--文本-->
|
||||
<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
|
||||
<!--#ifndef MP-BAIDU-->
|
||||
<text v-else-if="n.name=='br'">\n</text>
|
||||
<!--#endif-->
|
||||
<!--视频-->
|
||||
<view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&ctrl[i]==undefined" :id="n.attrs.id"
|
||||
:class="'_video '+(n.attrs.class||'')" :style="n.attrs.style" :data-i="i" @tap.stop="_loadVideo" />
|
||||
<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||ctrl[i]==0"
|
||||
:controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[ctrl[i]||0]"
|
||||
:unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" />
|
||||
<!--音频-->
|
||||
<audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author"
|
||||
:autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
|
||||
:src="n.attrs.source[ctrl[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio" @error.native="error"
|
||||
@play.native="play" />
|
||||
<!--链接-->
|
||||
<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
|
||||
:data-attrs="n.attrs" @tap.stop="linkpress">
|
||||
<trees class="_span" c="_span" :nodes="n.children" />
|
||||
</view>
|
||||
<!--广告-->
|
||||
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
|
||||
<!--列表-->
|
||||
<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex;flex-direction:row'">
|
||||
<view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
|
||||
<view v-else class="_ul-bef">
|
||||
<view v-if="n.floor%3==0" class="_ul-p1">█</view>
|
||||
<view v-else-if="n.floor%3==2" class="_ul-p2" />
|
||||
<view v-else class="_ul-p1" style="border-radius:50%">█</view>
|
||||
</view>
|
||||
<trees class="_li" c="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
|
||||
</view>
|
||||
<!--表格-->
|
||||
<view v-else-if="n.name=='table'&&n.c&&n.flag" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:grid'">
|
||||
<trees v-for="(cell,n) in n.children" v-bind:key="n" :class="cell.attrs.class" :c="cell.attrs.class" :style="cell.attrs.style"
|
||||
:s="cell.attrs.style" :nodes="cell.children" />
|
||||
</view>
|
||||
<view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'">
|
||||
<view v-for="(tbody, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
|
||||
<view v-for="(tr, p) in tbody.children" v-bind:key="p" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
|
||||
<trees v-if="tr.name=='td'" :nodes="tr.children" />
|
||||
<trees v-else v-for="(td, q) in tr.children" v-bind:key="q" :class="td.attrs.class" :c="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
|
||||
:s="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" :nodes="td.children" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--#ifdef APP-PLUS-->
|
||||
<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder"
|
||||
:width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
|
||||
<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
|
||||
<!--#endif-->
|
||||
<!--富文本-->
|
||||
<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
|
||||
<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
|
||||
<!--#endif-->
|
||||
<!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
|
||||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" />
|
||||
<!--#endif-->
|
||||
<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :c="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')"
|
||||
:style="n.attrs.style" :s="n.attrs.style" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script module="handler" lang="wxs" src="./handler.wxs"></script>
|
||||
<script>
|
||||
global.Parser = {};
|
||||
import trees from './trees'
|
||||
const errorImg = require('../libs/config.js').errorImg;
|
||||
export default {
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
name: 'trees',
|
||||
data() {
|
||||
return {
|
||||
ctrl: [],
|
||||
placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
|
||||
errorImg,
|
||||
loadVideo: typeof plus == 'undefined',
|
||||
// #ifndef MP-ALIPAY
|
||||
c: '',
|
||||
s: ''
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
props: {
|
||||
nodes: Array,
|
||||
lazyLoad: Boolean,
|
||||
loading: String,
|
||||
// #ifdef MP-ALIPAY
|
||||
c: String,
|
||||
s: String
|
||||
// #endif
|
||||
},
|
||||
mounted() {
|
||||
for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent);
|
||||
this.init();
|
||||
},
|
||||
// #ifdef APP-PLUS
|
||||
beforeDestroy() {
|
||||
this.observer && this.observer.disconnect();
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
init() {
|
||||
for (var i = this.nodes.length, n; n = this.nodes[--i];) {
|
||||
if (n.name == 'img') {
|
||||
this.top.imgList.setItem(n.attrs.i, n.attrs['original-src'] || n.attrs.src);
|
||||
// #ifdef APP-PLUS
|
||||
if (this.lazyLoad && !this.observer) {
|
||||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
|
||||
top: 500,
|
||||
bottom: 500
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.observer.observe('._img', res => {
|
||||
if (res.intersectionRatio) {
|
||||
for (var j = this.nodes.length; j--;)
|
||||
if (this.nodes[j].name == 'img')
|
||||
this.$set(this.ctrl, j, 1);
|
||||
this.observer.disconnect();
|
||||
}
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
// #endif
|
||||
} else if (n.name == 'video' || n.name == 'audio') {
|
||||
var ctx;
|
||||
if (n.name == 'video') {
|
||||
ctx = uni.createVideoContext(n.attrs.id
|
||||
// #ifndef MP-BAIDU
|
||||
, this
|
||||
// #endif
|
||||
);
|
||||
} else if (this.$refs[n.attrs.id])
|
||||
ctx = this.$refs[n.attrs.id][0];
|
||||
if (ctx) {
|
||||
ctx.id = n.attrs.id;
|
||||
this.top.videoContexts.push(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
// APP 上避免 video 错位需要延时渲染
|
||||
setTimeout(() => {
|
||||
this.loadVideo = true;
|
||||
}, 1000)
|
||||
// #endif
|
||||
},
|
||||
play(e) {
|
||||
var contexts = this.top.videoContexts;
|
||||
if (contexts.length > 1 && this.top.autopause)
|
||||
for (var i = contexts.length; i--;)
|
||||
if (contexts[i].id != e.currentTarget.dataset.id)
|
||||
contexts[i].pause();
|
||||
},
|
||||
imgtap(e) {
|
||||
var attrs = e.currentTarget.dataset.attrs;
|
||||
if (!attrs.ignore) {
|
||||
var preview = true,
|
||||
data = {
|
||||
id: e.target.id,
|
||||
src: attrs.src,
|
||||
ignore: () => preview = false
|
||||
};
|
||||
global.Parser.onImgtap && global.Parser.onImgtap(data);
|
||||
this.top.$emit('imgtap', data);
|
||||
if (preview) {
|
||||
var urls = this.top.imgList,
|
||||
current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
|
||||
uni.previewImage({
|
||||
current,
|
||||
urls
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
loadImg(e) {
|
||||
var i = e.currentTarget.dataset.i;
|
||||
if (this.lazyLoad && !this.ctrl[i]) {
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
this.$set(this.ctrl, i, 0);
|
||||
this.$nextTick(function() {
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
this.$set(this.ctrl, i, 1);
|
||||
// #endif
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
})
|
||||
// #endif
|
||||
} else if (this.loading && this.ctrl[i] != 2) {
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
this.$set(this.ctrl, i, 0);
|
||||
this.$nextTick(function() {
|
||||
// #endif
|
||||
this.$set(this.ctrl, i, 2);
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
linkpress(e) {
|
||||
var jump = true,
|
||||
attrs = e.currentTarget.dataset.attrs;
|
||||
attrs.ignore = () => jump = false;
|
||||
global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
|
||||
this.top.$emit('linkpress', attrs);
|
||||
if (jump) {
|
||||
// #ifdef MP
|
||||
if (attrs['app-id']) {
|
||||
return uni.navigateToMiniProgram({
|
||||
appId: attrs['app-id'],
|
||||
path: attrs.path
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
if (attrs.href) {
|
||||
if (attrs.href[0] == '#') {
|
||||
if (this.top.useAnchor)
|
||||
this.top.navigateTo({
|
||||
id: attrs.href.substring(1)
|
||||
})
|
||||
} else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openWeb(attrs.href);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
uni.setClipboardData({
|
||||
data: attrs.href,
|
||||
success: () =>
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
} else
|
||||
uni.navigateTo({
|
||||
url: attrs.href,
|
||||
fail() {
|
||||
uni.switchTab({
|
||||
url: attrs.href,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
error(e) {
|
||||
var target = e.currentTarget,
|
||||
source = target.dataset.source,
|
||||
i = target.dataset.i;
|
||||
if (source == 'video' || source == 'audio') {
|
||||
// 加载其他 source
|
||||
var index = this.ctrl[i] ? this.ctrl[i].i + 1 : 1;
|
||||
if (index < this.nodes[i].attrs.source.length)
|
||||
this.$set(this.ctrl, i, index);
|
||||
if (e.detail.__args__)
|
||||
e.detail = e.detail.__args__[0];
|
||||
} else if (errorImg && source == 'img') {
|
||||
this.top.imgList.setItem(target.dataset.index, errorImg);
|
||||
this.$set(this.ctrl, i, 3);
|
||||
}
|
||||
this.top && this.top.$emit('error', {
|
||||
source,
|
||||
target,
|
||||
errMsg: e.detail.errMsg
|
||||
});
|
||||
},
|
||||
_loadVideo(e) {
|
||||
this.$set(this.ctrl, e.target.dataset.i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 在这里引入自定义样式 */
|
||||
|
||||
/* 链接和图片效果 */
|
||||
._a {
|
||||
display: inline;
|
||||
padding: 1.5px 0 1.5px 0;
|
||||
color: #366092;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
._hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
._img {
|
||||
/* display: inline-block; */
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
:host {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef MP-ALIPAY || APP-PLUS */
|
||||
.interlayer {
|
||||
display: inherit;
|
||||
flex-direction: inherit;
|
||||
flex-wrap: inherit;
|
||||
align-content: inherit;
|
||||
align-items: inherit;
|
||||
justify-content: inherit;
|
||||
width: 100%;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
._b,
|
||||
._strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* #ifndef MP-ALIPAY */
|
||||
._blockquote,
|
||||
._div,
|
||||
._p,
|
||||
._ol,
|
||||
._ul,
|
||||
._li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
._code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
._del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
._em,
|
||||
._i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
._h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
._h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
._h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
._h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
|
||||
._h6 {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
|
||||
._h1,
|
||||
._h2,
|
||||
._h3,
|
||||
._h4,
|
||||
._h5,
|
||||
._h6 {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 360px;
|
||||
margin-top: -360px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
._ins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._li {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
._ol-bef {
|
||||
width: 36px;
|
||||
margin-right: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
._ul-bef {
|
||||
display: block;
|
||||
margin: 0 12px 0 23px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
._ol-bef,
|
||||
._ul-bef {
|
||||
flex: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
._ul-p1 {
|
||||
display: inline-block;
|
||||
width: 0.3em;
|
||||
height: 0.3em;
|
||||
overflow: hidden;
|
||||
line-height: 0.3em;
|
||||
}
|
||||
|
||||
._ul-p2 {
|
||||
display: inline-block;
|
||||
width: 0.23em;
|
||||
height: 0.23em;
|
||||
border: 0.05em solid black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
._q::before {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._q::after {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._sub {
|
||||
font-size: smaller;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
._sup {
|
||||
font-size: smaller;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
/* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */
|
||||
._abbr,
|
||||
._b,
|
||||
._code,
|
||||
._del,
|
||||
._em,
|
||||
._i,
|
||||
._ins,
|
||||
._label,
|
||||
._q,
|
||||
._span,
|
||||
._strong,
|
||||
._sub,
|
||||
._sup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-WEIXIN || MP-QQ */
|
||||
.__bdo,
|
||||
.__bdi,
|
||||
.__ruby,
|
||||
.__rt {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
._video {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
._video::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -15px 0 0 -15px;
|
||||
content: '';
|
||||
border-color: transparent transparent transparent white;
|
||||
border-style: solid;
|
||||
border-width: 15px 0 15px 30px;
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user