This commit is contained in:
leonZ 2024-08-03 19:57:17 +08:00
commit 8da1c3403e
469 changed files with 68032 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/unpackage
/utils/request-1.js
.DS_Store

20
.hbuilderx/launch.json Normal file
View File

@ -0,0 +1,20 @@
{ // launch.json configurations app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"default" :
{
"launchtype" : "remote"
},
"h5" :
{
"launchtype" : "remote"
},
"mp-weixin" :
{
"launchtype" : "remote"
},
"type" : "uniCloud"
}
]
}

66
App.vue Normal file
View 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>

0
README.md Normal file
View File

33
api/address.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
import paginate from './paginate'
export { paginate }

View File

@ -0,0 +1,7 @@
export default {
content: [], // 列表数据
currentPage: 1, // 当前页码
totalPages: 1, // 最大页码
pageSize: 15, // 每页记录数
totalElements: 0, // 总记录数
}

View 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 }
])

View 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 }
])

View 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 }
])

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

View 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 }
])

View File

@ -0,0 +1,9 @@
import Enum from '../enum'
/**
* 枚举类配送方式
* DeliveryTypeEnum
*/
export default new Enum([
{ key: 'EXPRESS', name: '快递配送', value: 10 }
])

View 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 }
])

View 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' },
])

View File

@ -0,0 +1,10 @@
import Enum from '../enum'
/**
* 枚举类订单支付状态
* PayStatusEnum
*/
export default new Enum([
{ key: 'PENDING', name: '待支付', value: 10 },
{ key: 'SUCCESS', name: '已支付', value: 20 }
])

View 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' },
])

View 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 }
])

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

View 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 }
])

View 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: '已取消' }
])

View File

@ -0,0 +1,10 @@
import Enum from '../../enum'
/**
* 枚举类售后类型
* RefundTypeEnum
*/
export default new Enum([
{ key: 'RETURN', name: '退货退款', value: 'return' },
{ key: 'EXCHANGE', name: '换货', value: 'exchange' }
])

View File

@ -0,0 +1,9 @@
import AuditStatusEnum from './AuditStatus'
import RefundStatusEnum from './RefundStatus'
import RefundTypeEnum from './RefundType'
export {
AuditStatusEnum,
RefundStatusEnum,
RefundTypeEnum
}

View 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'
}])

View 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 }
])

View File

@ -0,0 +1,3 @@
import PageCategoryStyleEnum from './Style'
export { PageCategoryStyleEnum }

57
common/model/Region.js Normal file
View 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
View 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
}

View 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 });
}
}
});

View File

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"zan-btn": "../btn/index"
}
}

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

View 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;
}

View 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";
}

Binary file not shown.

View 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);

View 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
}
]
}

View 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="&#58887;" 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="&#58896;" 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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
View 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 });
}
}
});

View File

@ -0,0 +1,3 @@
{
"component": true
}

24
components/btn/index.wxml Normal file
View 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>

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

View 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);
}
}
});

View File

@ -0,0 +1,3 @@
export const RED = '#f44';
export const BLUE = '#1989fa';
export const GREEN = '#07c160';

View 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 };

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

View File

@ -0,0 +1 @@
.van-clearfix::after{content:'';display:table;clear:both}

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

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

View File

View 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
View 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
View 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
View 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
});
}
}
});

View File

@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"pop-manager": "../pop-manager/index",
"zan-button": "../btn/index"
}
}

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

View 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;
}

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

View 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"
},
// skuid
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('');
// 0sku
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;
},
// skusku,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>

View 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
},
// inputrpx
size: {
type: [Number, String],
default: 26
},
//
color: {
type: String,
default: '#323233'
},
// inputrpx
inputWidth: {
type: [Number, String],
default: 80
},
// inputrpx
inputHeight: {
type: [Number, String],
default: 50
},
// index使numberbox使forindex
index: {
type: [Number, String],
default: ''
},
// disabledOR
// disabledfalsedisabledInputtrue
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) {
// valueinputVal
if(!this.changeFromInner) {
this.inputVal = v1;
// inputValthis.handleChange()changeFromInnertrue
// this.$nextTick
// changeFromInnerfalse
this.$nextTick(function(){
this.changeFromInner = false;
})
}
},
inputVal(v1, v2) {
//
if (v1 == '') return;
let value = 0;
// minmax使
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, // 使propsvalueprops
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-90min
// props min0
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;
}
// inputv-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>

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

View 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(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&amp;/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>

View 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;
}

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

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

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

View 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