1. 删除 yudao-ui-app-tmp 和 yudao-ui-app-v1,用户 app 以 yudao-ui-app 为准。
2. 修改 auth.js 的 /auth 路径
|
@ -1,31 +0,0 @@
|
||||||
/*
|
|
||||||
* Eslint config file
|
|
||||||
* Documentation: https://eslint.org/docs/user-guide/configuring/
|
|
||||||
* Install the Eslint extension before using this feature.
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
es6: true,
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
ecmaFeatures: {
|
|
||||||
modules: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2018,
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
globals: {
|
|
||||||
wx: true,
|
|
||||||
App: true,
|
|
||||||
Page: true,
|
|
||||||
getCurrentPages: true,
|
|
||||||
getApp: true,
|
|
||||||
Component: true,
|
|
||||||
requirePlugin: true,
|
|
||||||
requireMiniProgram: true,
|
|
||||||
},
|
|
||||||
// extends: 'eslint:recommended',
|
|
||||||
rules: {},
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
临时项目,作为测试微信小程序登陆之用
|
|
|
@ -1,19 +0,0 @@
|
||||||
// app.js
|
|
||||||
App({
|
|
||||||
onLaunch() {
|
|
||||||
// 展示本地存储能力
|
|
||||||
const logs = wx.getStorageSync('logs') || []
|
|
||||||
logs.unshift(Date.now())
|
|
||||||
wx.setStorageSync('logs', logs)
|
|
||||||
|
|
||||||
// 登录
|
|
||||||
wx.login({
|
|
||||||
success: res => {
|
|
||||||
// 发送 res.code 到后台换取 openId, sessionKey, unionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
globalData: {
|
|
||||||
userInfo: null
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"pages":[
|
|
||||||
"pages/index/index",
|
|
||||||
"pages/logs/logs"
|
|
||||||
],
|
|
||||||
"window":{
|
|
||||||
"backgroundTextStyle":"light",
|
|
||||||
"navigationBarBackgroundColor": "#fff",
|
|
||||||
"navigationBarTitleText": "Weixin",
|
|
||||||
"navigationBarTextStyle":"black"
|
|
||||||
},
|
|
||||||
"style": "v2",
|
|
||||||
"sitemapLocation": "sitemap.json"
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/**app.wxss**/
|
|
||||||
.container {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 200rpx 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
// index.js
|
|
||||||
|
|
||||||
const common=require('../../utils/common.js')
|
|
||||||
// 获取应用实例
|
|
||||||
const app = getApp()
|
|
||||||
|
|
||||||
Page({
|
|
||||||
data: {
|
|
||||||
motto: 'Hello World',
|
|
||||||
userInfo: {},
|
|
||||||
hasUserInfo: false,
|
|
||||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
|
||||||
canIUseGetUserProfile: false,
|
|
||||||
canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName'), // 如需尝试获取用户信息可改为false
|
|
||||||
holderText: 'to be auth'
|
|
||||||
},
|
|
||||||
// 事件处理函数
|
|
||||||
bindViewTap() {
|
|
||||||
wx.navigateTo({
|
|
||||||
url: '../logs/logs'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onLoad() {
|
|
||||||
if (wx.getUserProfile) {
|
|
||||||
this.setData({
|
|
||||||
canIUseGetUserProfile: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getUserProfile(e) {
|
|
||||||
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
|
|
||||||
wx.getUserProfile({
|
|
||||||
desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
|
|
||||||
success: (res) => {
|
|
||||||
console.log(res)
|
|
||||||
this.setData({
|
|
||||||
userInfo: res.userInfo,
|
|
||||||
hasUserInfo: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getUserInfo(e) {
|
|
||||||
// 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
|
|
||||||
console.log(e)
|
|
||||||
this.setData({
|
|
||||||
userInfo: e.detail.userInfo,
|
|
||||||
hasUserInfo: true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 小程序登录 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
|
|
||||||
wxLogin(e){
|
|
||||||
let page=this;
|
|
||||||
wx.login({
|
|
||||||
success (res) {
|
|
||||||
console.log("res:")
|
|
||||||
console.log(res)
|
|
||||||
if (res.code) {
|
|
||||||
//发起网络请求
|
|
||||||
console.log('发起网络请求'+common.baseurl)
|
|
||||||
wx.request({
|
|
||||||
url: common.baseurl+'/api/social-login2',
|
|
||||||
method: "POST",
|
|
||||||
data: {
|
|
||||||
code: res.code,
|
|
||||||
state: 'empty',
|
|
||||||
type: 33,
|
|
||||||
username: '15601691300',
|
|
||||||
password: 'admin123'
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
'content-type': 'application/json' // 默认值
|
|
||||||
},
|
|
||||||
success: function(res) {
|
|
||||||
console.log(res.data)
|
|
||||||
let holder="auth success, token:"+res.data.data.token
|
|
||||||
page.setData({holderText: holder})
|
|
||||||
},
|
|
||||||
fail: function(data){
|
|
||||||
console.error("请求出错");
|
|
||||||
console.error(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log('登录失败!' + res.errMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"usingComponents": {}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
<!--index.wxml-->
|
|
||||||
<view class="container">
|
|
||||||
<view class="userinfo">
|
|
||||||
<block wx:if="{{canIUseOpenData}}">
|
|
||||||
|
|
||||||
|
|
||||||
</block>
|
|
||||||
<block wx:elif="{{!hasUserInfo}}">
|
|
||||||
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
|
|
||||||
<button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
|
|
||||||
<view wx:else> 请使用1.4.4及以上版本基础库 </view>
|
|
||||||
</block>
|
|
||||||
<block wx:else>
|
|
||||||
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
|
|
||||||
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
<view class="usermotto">
|
|
||||||
|
|
||||||
</view>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<text style="position: relative; left: 1rpx; top: -476rpx">授权登录测试1024</text>
|
|
||||||
|
|
||||||
<button style="position: relative; left: 0rpx; top: -361rpx" type="primary" id="login-button" bindtap="wxLogin">点击授权登录</button>
|
|
||||||
|
|
||||||
<text style="position: relative; left: 1rpx; top: -272rpx" id="login-user-id">{{holderText}}</text>
|
|
||||||
</view>
|
|
|
@ -1,19 +0,0 @@
|
||||||
/**index.wxss**/
|
|
||||||
.userinfo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userinfo-avatar {
|
|
||||||
overflow: hidden;
|
|
||||||
width: 128rpx;
|
|
||||||
height: 128rpx;
|
|
||||||
margin: 20rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usermotto {
|
|
||||||
margin-top: 200px;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
// logs.js
|
|
||||||
const util = require('../../utils/util.js')
|
|
||||||
|
|
||||||
Page({
|
|
||||||
data: {
|
|
||||||
logs: []
|
|
||||||
},
|
|
||||||
onLoad() {
|
|
||||||
this.setData({
|
|
||||||
logs: (wx.getStorageSync('logs') || []).map(log => {
|
|
||||||
return {
|
|
||||||
date: util.formatTime(new Date(log)),
|
|
||||||
timeStamp: log
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"navigationBarTitleText": "查看启动日志",
|
|
||||||
"usingComponents": {}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
<!--logs.wxml-->
|
|
||||||
<view class="container log-list">
|
|
||||||
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
|
|
||||||
<text class="log-item">{{index + 1}}. {{log.date}}</text>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
|
@ -1,8 +0,0 @@
|
||||||
.log-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 40rpx;
|
|
||||||
}
|
|
||||||
.log-item {
|
|
||||||
margin: 10rpx;
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
{
|
|
||||||
"description": "项目配置文件",
|
|
||||||
"packOptions": {
|
|
||||||
"ignore": [
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"value": ".eslintrc.js"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"setting": {
|
|
||||||
"bundle": false,
|
|
||||||
"userConfirmedBundleSwitch": false,
|
|
||||||
"urlCheck": true,
|
|
||||||
"scopeDataCheck": false,
|
|
||||||
"coverView": true,
|
|
||||||
"es6": true,
|
|
||||||
"postcss": true,
|
|
||||||
"compileHotReLoad": false,
|
|
||||||
"lazyloadPlaceholderEnable": false,
|
|
||||||
"preloadBackgroundData": false,
|
|
||||||
"minified": true,
|
|
||||||
"autoAudits": false,
|
|
||||||
"newFeature": false,
|
|
||||||
"uglifyFileName": false,
|
|
||||||
"uploadWithSourceMap": true,
|
|
||||||
"useIsolateContext": true,
|
|
||||||
"nodeModules": false,
|
|
||||||
"enhance": true,
|
|
||||||
"useMultiFrameRuntime": true,
|
|
||||||
"useApiHook": true,
|
|
||||||
"useApiHostProcess": true,
|
|
||||||
"showShadowRootInWxmlPanel": true,
|
|
||||||
"packNpmManually": false,
|
|
||||||
"enableEngineNative": false,
|
|
||||||
"packNpmRelationList": [],
|
|
||||||
"minifyWXSS": true,
|
|
||||||
"showES6CompileOption": false,
|
|
||||||
"minifyWXML": true
|
|
||||||
},
|
|
||||||
"compileType": "miniprogram",
|
|
||||||
"libVersion": "2.19.4",
|
|
||||||
"appid": "wx44d047d87e6284d8",
|
|
||||||
"appid1": "wx63c280fe3248a3e7",
|
|
||||||
"projectname": "mini-program-test",
|
|
||||||
"debugOptions": {
|
|
||||||
"hidedInDevtools": []
|
|
||||||
},
|
|
||||||
"scripts": {},
|
|
||||||
"staticServerOptions": {
|
|
||||||
"baseURL": "",
|
|
||||||
"servePath": ""
|
|
||||||
},
|
|
||||||
"isGameTourist": false,
|
|
||||||
"condition": {
|
|
||||||
"search": {
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"conversation": {
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"game": {
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"plugin": {
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"gamePlugin": {
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"miniprogram": {
|
|
||||||
"list": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
|
|
||||||
"rules": [{
|
|
||||||
"action": "allow",
|
|
||||||
"page": "*"
|
|
||||||
}]
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
baseurl: "http://127.0.0.1:28080"
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
const formatTime = date => {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = date.getMonth() + 1
|
|
||||||
const day = date.getDate()
|
|
||||||
const hour = date.getHours()
|
|
||||||
const minute = date.getMinutes()
|
|
||||||
const second = date.getSeconds()
|
|
||||||
|
|
||||||
return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatNumber = n => {
|
|
||||||
n = n.toString()
|
|
||||||
return n[1] ? n : `0${n}`
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
formatTime
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
|
||||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
|
||||||
"version": "0.0",
|
|
||||||
"configurations": [{
|
|
||||||
"default" :
|
|
||||||
{
|
|
||||||
"launchtype" : "local"
|
|
||||||
},
|
|
||||||
"h5" :
|
|
||||||
{
|
|
||||||
"launchtype" : "local"
|
|
||||||
},
|
|
||||||
"type" : "uniCloud"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
<script>
|
|
||||||
import Vue from 'vue'
|
|
||||||
import { getAuthToken } from '@/common/js/util.js'
|
|
||||||
let __timerId = 0;
|
|
||||||
export default {
|
|
||||||
onLaunch() {
|
|
||||||
uni.getSystemInfo({
|
|
||||||
success: e=> {
|
|
||||||
this.initSize(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.initLogin();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 初始化登陆状态
|
|
||||||
async initLogin(){
|
|
||||||
const token = getAuthToken()
|
|
||||||
if (!token) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 通过设置 Token 的方式,触发加载用户信息
|
|
||||||
this.$store.commit('setToken', {
|
|
||||||
token
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 存储设备信息 参考colorUI
|
|
||||||
* @param {Object}
|
|
||||||
*/
|
|
||||||
initSize(e){
|
|
||||||
const systemInfo = e;
|
|
||||||
let navigationBarHeight;
|
|
||||||
let custom = {};
|
|
||||||
// #ifndef MP
|
|
||||||
custom = {height: 36,width: 88};
|
|
||||||
navigationBarHeight = 44;
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP
|
|
||||||
custom = wx.getMenuButtonBoundingClientRect();
|
|
||||||
navigationBarHeight = custom.bottom + custom.top - e.statusBarHeight * 2;
|
|
||||||
// #endif
|
|
||||||
systemInfo.custom = custom;
|
|
||||||
systemInfo.navigationBarHeight = navigationBarHeight;
|
|
||||||
Vue.prototype.systemInfo = systemInfo;
|
|
||||||
},
|
|
||||||
//打开全局定时器
|
|
||||||
openTimer(){
|
|
||||||
this.closeTimer();
|
|
||||||
__timerId = setInterval(()=>{
|
|
||||||
this.$store.commit('setStateAttr', {
|
|
||||||
key: 'timerIdent',
|
|
||||||
val: !this.$store.state.timerIdent
|
|
||||||
})
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
//关闭定时器
|
|
||||||
closeTimer(){
|
|
||||||
if(__timerId != 0){
|
|
||||||
clearInterval(__timerId);
|
|
||||||
__timerId = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
onShow() {
|
|
||||||
console.log('app show');
|
|
||||||
this.openTimer();
|
|
||||||
},
|
|
||||||
onHide() {
|
|
||||||
this.closeTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
/*每个页面公共css */
|
|
||||||
@import "@/uni_modules/uview-ui/index.scss";
|
|
||||||
@import url("./common/css/common.css");
|
|
||||||
@import url("./common/css/icon.css");
|
|
||||||
</style>
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { request } from '@/common/js/request.js'
|
|
||||||
|
|
||||||
// 获得用户的基本信息
|
|
||||||
export function getUserInfo() {
|
|
||||||
return request({
|
|
||||||
url: 'member/user/profile/get',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改
|
|
||||||
export function updateNickname(nickname) {
|
|
||||||
return request({
|
|
||||||
url: 'member/user/profile/update-nickname',
|
|
||||||
method: 'post',
|
|
||||||
header: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
nickname
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { request } from '@/common/js/request.js'
|
|
||||||
|
|
||||||
// 手机号 + 密码登陆
|
|
||||||
export function login(mobile, password) {
|
|
||||||
return request({
|
|
||||||
url: 'login',
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
mobile, password
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手机号 + 验证码登陆
|
|
||||||
export function smsLogin(mobile, code) {
|
|
||||||
return request({
|
|
||||||
url: 'sms-login',
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
mobile, code
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送手机验证码
|
|
||||||
export function sendSmsCode(mobile, scene) {
|
|
||||||
return request({
|
|
||||||
url: 'send-sms-code',
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
mobile, scene
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
view,
|
|
||||||
scroll-view,
|
|
||||||
swiper,
|
|
||||||
swiper-item,
|
|
||||||
cover-view,
|
|
||||||
cover-image,
|
|
||||||
icon,
|
|
||||||
text,
|
|
||||||
rich-text,
|
|
||||||
progress,
|
|
||||||
button,
|
|
||||||
checkbox,
|
|
||||||
form,
|
|
||||||
input,
|
|
||||||
label,
|
|
||||||
radio,
|
|
||||||
slider,
|
|
||||||
switch,
|
|
||||||
textarea,
|
|
||||||
navigator,
|
|
||||||
audio,
|
|
||||||
camera,
|
|
||||||
image,
|
|
||||||
video {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
image{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
text{
|
|
||||||
line-height: 1;
|
|
||||||
/* font-family: Helvetica Neue, Helvetica, sans-serif; */
|
|
||||||
}
|
|
||||||
button{
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
background-color: rgba(0,0,0,0) !important;
|
|
||||||
}
|
|
||||||
button:after{
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
.bottom-fill{
|
|
||||||
height: constant(safe-area-inset-bottom);
|
|
||||||
height: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
.fix-bot{
|
|
||||||
box-sizing: content-box;
|
|
||||||
padding-bottom: constant(safe-area-inset-bottom);
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 边框 */
|
|
||||||
.round{
|
|
||||||
position: relative;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
}
|
|
||||||
.round:after{
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
transform: scale(.5) translate(-50%,-50%);
|
|
||||||
border: 1px solid #878787;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.b-b:after{
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
left: 0;
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 0;
|
|
||||||
content: '';
|
|
||||||
transform: scaleY(.5);
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
.b-t:before{
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 0;
|
|
||||||
content: '';
|
|
||||||
transform: scaleY(.5);
|
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
.b-r:after{
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 0;
|
|
||||||
content: '';
|
|
||||||
transform: scaleX(.5);
|
|
||||||
border-right: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
.b-l:before{
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 0;
|
|
||||||
content: '';
|
|
||||||
transform: scaleX(.5);
|
|
||||||
border-left: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
.b-b, .b-t, .b-l, .b-r{
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
/* 点击态 */
|
|
||||||
.hover-gray {
|
|
||||||
background: #fafafa !important;
|
|
||||||
}
|
|
||||||
.hover-dark {
|
|
||||||
background: #f0f0f0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover-opacity {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
.clamp {
|
|
||||||
/* #ifdef APP-PLUS-NVUE */
|
|
||||||
lines: 1;
|
|
||||||
/* #endif */
|
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: block;
|
|
||||||
/* #endif */
|
|
||||||
}
|
|
||||||
.clamp2 {
|
|
||||||
/* #ifdef APP-PLUS-NVUE */
|
|
||||||
lines: 2;
|
|
||||||
/* #endif */
|
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
overflow: hidden;
|
|
||||||
/* #endif */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 布局 */
|
|
||||||
.row{
|
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
display:flex;
|
|
||||||
/* #endif */
|
|
||||||
flex-direction:row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.column{
|
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
display:flex;
|
|
||||||
/* #endif */
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.center{
|
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
display:flex;
|
|
||||||
/* #endif */
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.fill{
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
/* input */
|
|
||||||
.placeholder{
|
|
||||||
color: #999 !important;
|
|
||||||
}
|
|
|
@ -1,271 +0,0 @@
|
||||||
@font-face {
|
|
||||||
font-family: "mix-icon";
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
src: url('https://at.alicdn.com/t/font_1913318_2ui3nitf38x.ttf') format('truetype'); // TODO 芋艿: icon 怎么搞?
|
|
||||||
}
|
|
||||||
|
|
||||||
.mix-icon {
|
|
||||||
font-family: "mix-icon" !important;
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-fanhui:before {
|
|
||||||
content: "\e7d5";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shoujihaoma:before {
|
|
||||||
content: "\e7ec";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-close:before {
|
|
||||||
content: "\e60f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xingbie-nv:before {
|
|
||||||
content: "\e60e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-wuliuyunshu:before {
|
|
||||||
content: "\e7ed";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-jingpin:before {
|
|
||||||
content: "\e608";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-zhangdanmingxi01:before {
|
|
||||||
content: "\e637";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-tixian1:before {
|
|
||||||
content: "\e625";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-chongzhi:before {
|
|
||||||
content: "\e605";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-wodezhanghu_zijinjilu:before {
|
|
||||||
content: "\e615";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-tixian:before {
|
|
||||||
content: "\e6ab";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-qianbao:before {
|
|
||||||
content: "\e6c4";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-guanbi1:before {
|
|
||||||
content: "\e61a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-daipingjia:before {
|
|
||||||
content: "\e604";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-daifahuo:before {
|
|
||||||
content: "\e6bd";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-yue:before {
|
|
||||||
content: "\e600";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-wxpay:before {
|
|
||||||
content: "\e602";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-alipay:before {
|
|
||||||
content: "\e603";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-tishi:before {
|
|
||||||
content: "\e662";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shoucang-1:before {
|
|
||||||
content: "\e607";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-gouwuche:before {
|
|
||||||
content: "\e657";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shoucang:before {
|
|
||||||
content: "\e645";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-home:before {
|
|
||||||
content: "\e60c";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .icon-bangzhu1:before {
|
|
||||||
content: "\e63d"; // 帮助
|
|
||||||
} */
|
|
||||||
|
|
||||||
.icon-xingxing:before {
|
|
||||||
content: "\e70b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shuxiangliebiao:before {
|
|
||||||
content: "\e635";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-hengxiangliebiao:before {
|
|
||||||
content: "\e636";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-guanbi2:before {
|
|
||||||
content: "\e7be";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-down:before {
|
|
||||||
content: "\e65c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-arrow-top:before {
|
|
||||||
content: "\e63e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xiaoxi:before {
|
|
||||||
content: "\e634";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-saoma:before {
|
|
||||||
content: "\e655";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-dizhi1:before {
|
|
||||||
content: "\e618";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-ditu-copy:before {
|
|
||||||
content: "\e609";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-lajitong:before {
|
|
||||||
content: "\e682";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-bianji:before {
|
|
||||||
content: "\e60d"; // 编辑
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-yanzhengma1:before {
|
|
||||||
content: "\e613";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-yanjing:before {
|
|
||||||
content: "\e65b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-mima:before {
|
|
||||||
content: "\e628";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-biyan:before {
|
|
||||||
content: "\e633";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-iconfontweixin:before {
|
|
||||||
content: "\e611";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shouye:before {
|
|
||||||
content: "\e626";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-daifukuan:before {
|
|
||||||
content: "\e68f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-pinglun-copy:before {
|
|
||||||
content: "\e612";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-lishijilu:before {
|
|
||||||
content: "\e6b9";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shoucang_xuanzhongzhuangtai:before {
|
|
||||||
content: "\e6a9";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-share:before {
|
|
||||||
content: "\e656";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shezhi1:before {
|
|
||||||
content: "\e61d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-shouhoutuikuan:before {
|
|
||||||
content: "\e631";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-dizhi:before {
|
|
||||||
content: "\e614";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-yishouhuo:before {
|
|
||||||
content: "\e71a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xuanzhong:before {
|
|
||||||
content: "\e632";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xiangzuo:before {
|
|
||||||
content: "\e653";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-iconfontxingxing:before {
|
|
||||||
content: "\e6b0";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-jia2:before {
|
|
||||||
content: "\e60a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-sousuo:before {
|
|
||||||
content: "\e7ce";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xiala:before {
|
|
||||||
content: "\e644";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xia:before {
|
|
||||||
content: "\e62d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon--jianhao:before {
|
|
||||||
content: "\e60b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-you:before {
|
|
||||||
content: "\e606";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-yk_yuanquan:before {
|
|
||||||
content: "\e601";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-xing:before {
|
|
||||||
content: "\e627";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-guanbi:before {
|
|
||||||
content: "\e71d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-loading:before {
|
|
||||||
content: "\e646";
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
import store from '@/store'
|
|
||||||
import { msg, getAuthToken } from './util'
|
|
||||||
|
|
||||||
const BASE_URL = 'http://127.0.0.1:28080/api/';
|
|
||||||
|
|
||||||
export const request = (options) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// 发起请求
|
|
||||||
const authToken = getAuthToken();
|
|
||||||
uni.request({
|
|
||||||
url: BASE_URL + options.url,
|
|
||||||
method: options.method || 'GET',
|
|
||||||
data: options.data || {},
|
|
||||||
header: {
|
|
||||||
...options.header,
|
|
||||||
'Authorization': authToken ? `Bearer ${authToken}` : ''
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
res = res[1];
|
|
||||||
const statusCode = res.statusCode;
|
|
||||||
if (statusCode !== 200) {
|
|
||||||
msg('请求失败,请重试');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = res.data.code;
|
|
||||||
const message = res.data.msg;
|
|
||||||
// Token 过期,引导重新登陆
|
|
||||||
if (code === 401) {
|
|
||||||
msg('登录信息已过期,请重新登录');
|
|
||||||
store.commit('logout');
|
|
||||||
// reject('无效的登录信息');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 系统异常
|
|
||||||
if (code === 500) {
|
|
||||||
msg('系统异常,请稍后重试');
|
|
||||||
reject(new Error(message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 其它失败情况
|
|
||||||
if (code > 0) {
|
|
||||||
msg(message);
|
|
||||||
// 提供 code + msg,可以基于 code 做进一步的处理。当然,一般情况下是不需要的。
|
|
||||||
// 不需要的场景:手机登录时,密码不正确;
|
|
||||||
// 需要的场景:微信登录时,未绑定手机,后端会返回一个 code 码,前端需要基于它跳转到绑定手机界面;
|
|
||||||
reject({
|
|
||||||
'code': code,
|
|
||||||
'msg': message
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 处理成功,则只返回成功的 data 数据,不返回 code 和 msg
|
|
||||||
resolve(res.data.data);
|
|
||||||
}).catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
let _debounceTimeout = null,
|
|
||||||
_throttleRunning = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 防抖
|
|
||||||
* 参考文章 https://juejin.cn/post/6844903669389885453
|
|
||||||
*
|
|
||||||
* @param {Function} 执行函数
|
|
||||||
* @param {Number} delay 延时ms
|
|
||||||
*/
|
|
||||||
export const debounce = (fn, delay=500) => {
|
|
||||||
clearTimeout(_debounceTimeout);
|
|
||||||
_debounceTimeout = setTimeout(() => {
|
|
||||||
fn();
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节流
|
|
||||||
* 参考文章 https://juejin.cn/post/6844903669389885453
|
|
||||||
*
|
|
||||||
* @param {Function} 执行函数
|
|
||||||
* @param {Number} delay 延时ms
|
|
||||||
*/
|
|
||||||
export const throttle = (fn, delay=500) => {
|
|
||||||
if(_throttleRunning){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_throttleRunning = true;
|
|
||||||
fn();
|
|
||||||
setTimeout(() => {
|
|
||||||
_throttleRunning = false;
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* toast 提示
|
|
||||||
*
|
|
||||||
* @param {String} title 标题
|
|
||||||
* @param {Object} param 拓展参数
|
|
||||||
* @param {Integer} param.duration 持续时间
|
|
||||||
* @param {Boolean} param.mask 是否遮罩
|
|
||||||
* @param {Boolean} param.icon 图标
|
|
||||||
*/
|
|
||||||
export const msg = (title = '', param={}) => {
|
|
||||||
if (!title) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.showToast({
|
|
||||||
title,
|
|
||||||
duration: param.duration || 1500,
|
|
||||||
mask: param.mask || false,
|
|
||||||
icon: param.icon || 'none' // TODO 芋艿:是否要区分下 error 的提示,或者专门的封装
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查登录
|
|
||||||
*
|
|
||||||
* @param {Boolean} options.nav 如果未登陆,是否跳转到登陆页。默认为 true
|
|
||||||
* @return {Boolean} 是否登陆
|
|
||||||
*/
|
|
||||||
export const isLogin = (options = {}) => {
|
|
||||||
const token = getAuthToken();
|
|
||||||
if (token) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// 若 nav 不为 false,则进行跳转登陆页
|
|
||||||
if (options.nav !== false) {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/auth/login'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得认证 Token
|
|
||||||
*
|
|
||||||
* @return 认证 Token
|
|
||||||
*/
|
|
||||||
export const getAuthToken = () => {
|
|
||||||
return uni.getStorageSync('token');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验参数
|
|
||||||
*
|
|
||||||
* @param {String} 字符串
|
|
||||||
* @param {String} 数据的类型。例如说 mobile 手机号、tel 座机 TODO 芋艿:是否组件里解决
|
|
||||||
*/
|
|
||||||
export const checkStr = (str, type) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'mobile': //手机号码
|
|
||||||
return /^1[3|4|5|6|7|8|9][0-9]{9}$/.test(str);
|
|
||||||
case 'tel': //座机
|
|
||||||
return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
|
|
||||||
case 'card': //身份证
|
|
||||||
return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str);
|
|
||||||
case 'mobileCode': //6位数字验证码
|
|
||||||
return /^[0-9]{6}$/.test(str)
|
|
||||||
case 'pwd': //密码以字母开头,长度在6~18之间,只能包含字母、数字和下划线
|
|
||||||
return /^([a-zA-Z0-9_]){6,18}$/.test(str)
|
|
||||||
case 'payPwd': //支付密码 6位纯数字
|
|
||||||
return /^[0-9]{6}$/.test(str)
|
|
||||||
case 'postal': //邮政编码
|
|
||||||
return /[1-9]\d{5}(?!\d)/.test(str);
|
|
||||||
case 'QQ': //QQ号
|
|
||||||
return /^[1-9][0-9]{4,9}$/.test(str);
|
|
||||||
case 'email': //邮箱
|
|
||||||
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
|
|
||||||
case 'money': //金额(小数点2位)
|
|
||||||
return /^\d*(?:\.\d{0,2})?$/.test(str);
|
|
||||||
case 'URL': //网址
|
|
||||||
return /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/.test(str)
|
|
||||||
case 'IP': //IP
|
|
||||||
return /((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))/.test(str);
|
|
||||||
case 'date': //日期时间
|
|
||||||
return /^(\d{4})\-(\d{2})\-(\d{2}) (\d{2})(?:\:\d{2}|:(\d{2}):(\d{2}))$/.test(str) || /^(\d{4})\-(\d{2})\-(\d{2})$/
|
|
||||||
.test(str)
|
|
||||||
case 'number': //数字
|
|
||||||
return /^[0-9]$/.test(str);
|
|
||||||
case 'english': //英文
|
|
||||||
return /^[a-zA-Z]+$/.test(str);
|
|
||||||
case 'chinese': //中文
|
|
||||||
return /^[\\u4E00-\\u9FA5]+$/.test(str);
|
|
||||||
case 'lower': //小写
|
|
||||||
return /^[a-z]+$/.test(str);
|
|
||||||
case 'upper': //大写
|
|
||||||
return /^[A-Z]+$/.test(str);
|
|
||||||
case 'HTML': //HTML标记
|
|
||||||
return /<("[^"]*"|'[^']*'|[^'">])*>/.test(str);
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
// import {request} from '@/common/js/request'
|
|
||||||
|
|
||||||
export default{
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
page: 0, // 页码
|
|
||||||
pageNum: 6, // 每页加载数据量
|
|
||||||
loadingType: 1, // 加载类型。0 加载前;1 加载中;2 没有更多
|
|
||||||
isLoading: false, // 刷新数据
|
|
||||||
loaded: false, // 加载完毕
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* 打印日志,方便调试
|
|
||||||
*
|
|
||||||
* @param {Object} data 数据
|
|
||||||
*/
|
|
||||||
log(data) {
|
|
||||||
console.log(JSON.parse(JSON.stringify(data)))
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* navigatorTo 跳转页面
|
|
||||||
*
|
|
||||||
* @param {String} url
|
|
||||||
* @param {Object} options 可选参数
|
|
||||||
* @param {Boolean} options.login 是否检测登录
|
|
||||||
*/
|
|
||||||
navTo(url, options={}) {
|
|
||||||
this.$util.throttle(() => {
|
|
||||||
if (!url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 如果需要登陆,并且未登陆,则跳转到登陆界面
|
|
||||||
if ((~url.indexOf('login=1') || options.login) && !this.$store.getters.hasLogin){
|
|
||||||
url = '/pages/auth/login';
|
|
||||||
}
|
|
||||||
// 跳转到指定 url 地址
|
|
||||||
uni.navigateTo({
|
|
||||||
url
|
|
||||||
})
|
|
||||||
}, 300)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* $request云函数请求 TODO 芋艿:需要改成自己的
|
|
||||||
* @param {String} module
|
|
||||||
* @param {String} operation
|
|
||||||
* @param {Boolean} data 请求参数
|
|
||||||
* @param {Boolean} ext 附加参数
|
|
||||||
* @param {Boolean} ext.showLoading 是否显示loading状态,默认不显示
|
|
||||||
* @param {Boolean} ext.hideLoading 是否关闭loading状态,默认关闭
|
|
||||||
* @param {Boolean} ext.login 未登录拦截
|
|
||||||
* @param {Boolean} ext.setLoaded 加载完成是设置页面加载完毕
|
|
||||||
*/
|
|
||||||
$request(module, operation, data={}, ext={}){
|
|
||||||
if(ext.login && !this.$util.isLogin()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(ext.showLoading){
|
|
||||||
this.isLoading = true;
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject)=> {
|
|
||||||
request(module, operation, data, ext).then(result => {
|
|
||||||
if(ext.hideLoading !== false){
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
setTimeout(()=>{
|
|
||||||
if(this.setLoaded !== false){
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
this.$refs.confirmBtn && this.$refs.confirmBtn.stop();
|
|
||||||
resolve(result);
|
|
||||||
}).catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
imageOnLoad(data, key){ // TODO 芋艿:需要改成自己的
|
|
||||||
setTimeout(()=>{
|
|
||||||
this.$set(data, 'loaded', true);
|
|
||||||
}, 100)
|
|
||||||
},
|
|
||||||
showPopup(key){ // TODO 芋艿:需要改成自己的
|
|
||||||
this.$util.throttle(()=>{
|
|
||||||
this.$refs[key].open();
|
|
||||||
}, 200)
|
|
||||||
},
|
|
||||||
hidePopup(key){ // TODO 芋艿:需要改成自己的
|
|
||||||
this.$refs[key].close();
|
|
||||||
},
|
|
||||||
stopPrevent(){}, // TODO 芋艿:需要改成自己的
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,630 +0,0 @@
|
||||||
<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>
|
|
||||||
// #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 = uni.getSystemInfoSync().windowWidth,
|
|
||||||
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 20200719
|
|
||||||
* @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
|
|
||||||
// #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>';
|
|
||||||
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.src),a.onclick=function(){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(){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
|
|
||||||
})
|
|
||||||
// 图片处理
|
|
||||||
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';
|
|
||||||
var src = img.getAttribute('src');
|
|
||||||
if (this.domain && src) {
|
|
||||||
if (src[0] == '/') {
|
|
||||||
if (src[1] == '/')
|
|
||||||
img.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
|
|
||||||
else img.src = this.domain + src;
|
|
||||||
} else if (!src.includes('://')) img.src = this.domain + '/' + src;
|
|
||||||
}
|
|
||||||
if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
|
|
||||||
img.i = j++;
|
|
||||||
_ts.imgList.push(img.src || img.getAttribute('data-src'));
|
|
||||||
img.onclick = function() {
|
|
||||||
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() {
|
|
||||||
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++];) {
|
|
||||||
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)
|
|
||||||
audio.onerror = function() {
|
|
||||||
_ts.$emit('error', {
|
|
||||||
source: 'audio',
|
|
||||||
target: this
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 表格处理
|
|
||||||
if (this.autoscroll) {
|
|
||||||
var tables = this.rtf.getElementsByTagName('table');
|
|
||||||
for (var table of tables) {
|
|
||||||
let div = document.createElement('div');
|
|
||||||
div.style.overflow = 'scroll';
|
|
||||||
table.parentNode.replaceChild(div, table);
|
|
||||||
div.appendChild(table);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!append) this.document.appendChild(this.rtf);
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.nodes = [1];
|
|
||||||
this.$emit('load');
|
|
||||||
});
|
|
||||||
setTimeout(() => this.showAm = '', 500);
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
// #ifndef H5 || MP-360
|
|
||||||
var nodes;
|
|
||||||
if (!html) return this.nodes = [];
|
|
||||||
var parser = new Parser(html, this);
|
|
||||||
// 缓存读取
|
|
||||||
if (this.useCache) {
|
|
||||||
var hashVal = hash(html);
|
|
||||||
if (cache[hashVal])
|
|
||||||
nodes = cache[hashVal];
|
|
||||||
else {
|
|
||||||
nodes = parser.parse();
|
|
||||||
cache[hashVal] = nodes;
|
|
||||||
}
|
|
||||||
} else nodes = parser.parse();
|
|
||||||
this.$emit('parse', nodes);
|
|
||||||
if (append) this.nodes = this.nodes.concat(nodes);
|
|
||||||
else this.nodes = nodes;
|
|
||||||
if (nodes.length && nodes.title && this.autosetTitle)
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: nodes.title
|
|
||||||
})
|
|
||||||
if (this.imgList) this.imgList.length = 0;
|
|
||||||
this.videoContexts = [];
|
|
||||||
this.$nextTick(() => {
|
|
||||||
(function f(cs) {
|
|
||||||
for (var i = cs.length; i--;) {
|
|
||||||
if (cs[i].top) {
|
|
||||||
cs[i].controls = [];
|
|
||||||
cs[i].init();
|
|
||||||
f(cs[i].$children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(this.$children)
|
|
||||||
this.$emit('load');
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
var height;
|
|
||||||
clearInterval(this._timer);
|
|
||||||
this._timer = setInterval(() => {
|
|
||||||
// #ifdef H5 || MP-360
|
|
||||||
this.rect = this.rtf.getBoundingClientRect();
|
|
||||||
// #endif
|
|
||||||
// #ifndef H5 || MP-360
|
|
||||||
uni.createSelectorQuery().in(this)
|
|
||||||
.select('#_top').boundingClientRect().exec(res => {
|
|
||||||
if (!res) return;
|
|
||||||
this.rect = res[0];
|
|
||||||
// #endif
|
|
||||||
if (this.rect.height == height) {
|
|
||||||
this.$emit('ready', this.rect)
|
|
||||||
clearInterval(this._timer);
|
|
||||||
}
|
|
||||||
height = this.rect.height;
|
|
||||||
// #ifndef H5 || MP-360
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
}, 350);
|
|
||||||
if (this.showWithAnimation && !append) this.showAm = 'animation:_show .5s';
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
// 获取文本内容
|
|
||||||
getText(ns = this.nodes) {
|
|
||||||
var txt = '';
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
txt = this._text;
|
|
||||||
// #endif
|
|
||||||
// #ifdef H5 || MP-360
|
|
||||||
txt = this.rtf.innerText;
|
|
||||||
// #endif
|
|
||||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
|
||||||
for (var i = 0, n; n = ns[i++];) {
|
|
||||||
if (n.type == 'text') txt += n.text.replace(/ /g, '\u00A0').replace(/</g, '<').replace(/>/g, '>')
|
|
||||||
.replace(/&/g, '&');
|
|
||||||
else if (n.type == 'br') txt += '\n';
|
|
||||||
else {
|
|
||||||
// 块级标签前后加换行
|
|
||||||
var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
|
|
||||||
'0' && n.name[1] < '7');
|
|
||||||
if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
|
|
||||||
if (n.children) txt += this.getText(n.children);
|
|
||||||
if (block && txt[txt.length - 1] != '\n') txt += '\n';
|
|
||||||
else if (n.name == 'td' || n.name == 'th') txt += '\t';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
return txt;
|
|
||||||
},
|
|
||||||
// 锚点跳转
|
|
||||||
in (obj) {
|
|
||||||
if (obj.page && obj.selector && obj.scrollTop) this._in = obj;
|
|
||||||
},
|
|
||||||
navigateTo(obj) {
|
|
||||||
if (!this.useAnchor) return obj.fail && obj.fail('Anchor is disabled');
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
if (!obj.id)
|
|
||||||
weexDom.scrollToElement(this.$refs.web);
|
|
||||||
else
|
|
||||||
this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
|
|
||||||
'");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop+' + (obj.offset || 0) + '})');
|
|
||||||
obj.success && obj.success();
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
var d = ' ';
|
|
||||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
|
||||||
d = '>>>';
|
|
||||||
// #endif
|
|
||||||
var selector = uni.createSelectorQuery().in(this._in ? this._in.page : this).select((this._in ? this._in.selector :
|
|
||||||
'#_top') + (obj.id ? `${d}#${obj.id},${this._in?this._in.selector:'#_top'}${d}.${obj.id}` : '')).boundingClientRect();
|
|
||||||
if (this._in) selector.select(this._in.selector).scrollOffset().select(this._in.selector).boundingClientRect();
|
|
||||||
else selector.selectViewport().scrollOffset();
|
|
||||||
selector.exec(res => {
|
|
||||||
if (!res[0]) return obj.fail && obj.fail('Label not found')
|
|
||||||
var scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + (obj.offset || 0);
|
|
||||||
if (this._in) this._in.page[this._in.scrollTop] = scrollTop;
|
|
||||||
else uni.pageScrollTo({
|
|
||||||
scrollTop,
|
|
||||||
duration: 300
|
|
||||||
})
|
|
||||||
obj.success && obj.success();
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
// 获取视频对象
|
|
||||||
getVideoContext(id) {
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
if (!id) return this.videoContexts;
|
|
||||||
else
|
|
||||||
for (var i = this.videoContexts.length; i--;)
|
|
||||||
if (this.videoContexts[i].id == id) return this.videoContexts[i];
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
// #ifdef H5 || APP-PLUS-NVUE || MP-360
|
|
||||||
_handleHtml(html, append) {
|
|
||||||
if (!append) {
|
|
||||||
// 处理 tag-style 和 userAgentStyles
|
|
||||||
var style = '<style>@keyframes _show{0%{opacity:0}100%{opacity:1}}img{max-width:100%}';
|
|
||||||
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: scroll;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #endif */
|
|
||||||
</style>
|
|
|
@ -1,97 +0,0 @@
|
||||||
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.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;
|
|
||||||
}
|
|
|
@ -1,534 +0,0 @@
|
||||||
/**
|
|
||||||
* html 解析器
|
|
||||||
* @tutorial https://github.com/jin-yufeng/Parser
|
|
||||||
* @version 20200719
|
|
||||||
* @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]) {
|
|
||||||
if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1;
|
|
||||||
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 tmp = [];
|
|
||||||
for (let i = text.length, c; c = text[--i];)
|
|
||||||
if (!blankChar[c] || (!blankChar[tmp[0]] && (c = ' '))) tmp.unshift(c);
|
|
||||||
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];
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.c = void 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (attrs.align) {
|
|
||||||
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.includes('-webkit') || value.includes('-moz') || value.includes('-ms') || value.includes('-o') || 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 : 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 += '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);
|
|
||||||
if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src;
|
|
||||||
var i = j;
|
|
||||||
while (this.data[j] != '<') j--;
|
|
||||||
src = this.data.substring(j, i).replace("viewbox", "viewBox") + 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: (/vertical[^;]+/.exec(node.attrs.style) || []).shift(),
|
|
||||||
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.name == 'div' || node.name == 'p' || node.name[0] == 't') {
|
|
||||||
if (len > 1 && siblings[len - 2].text == ' ')
|
|
||||||
siblings.splice(--len - 1, 1);
|
|
||||||
if (childs.length && childs[childs.length - 1].text == ' ')
|
|
||||||
childs.pop();
|
|
||||||
}
|
|
||||||
// 处理列表
|
|
||||||
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 = attrs.cellpadding,
|
|
||||||
spacing = attrs.cellspacing,
|
|
||||||
border = attrs.border;
|
|
||||||
if (node.c) {
|
|
||||||
this.bubble();
|
|
||||||
attrs.style = (attrs.style || '') + ';display:table';
|
|
||||||
if (!padding) padding = 2;
|
|
||||||
if (!spacing) spacing = 2;
|
|
||||||
}
|
|
||||||
if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
|
|
||||||
if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
|
|
||||||
if (border || padding || node.c)
|
|
||||||
(function f(ns) {
|
|
||||||
for (var i = 0, n; n = ns[i]; i++) {
|
|
||||||
if (n.type == 'text') continue;
|
|
||||||
var style = n.attrs.style || '';
|
|
||||||
if (node.c && n.name[0] == 't') {
|
|
||||||
n.c = 1;
|
|
||||||
style += ';display:table-' + (n.name == 'th' || n.name == 'td' ? 'cell' : (n.name == 'tr' ? 'row' : 'row-group'));
|
|
||||||
}
|
|
||||||
if (n.name == 'th' || n.name == 'td') {
|
|
||||||
if (border) style = `border:${border}px solid gray;${style}`;
|
|
||||||
if (padding) style = `padding:${padding}px;${style}`;
|
|
||||||
} else f(n.children || []);
|
|
||||||
if (style) n.attrs.style = style;
|
|
||||||
}
|
|
||||||
})(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;
|
|
|
@ -1,93 +0,0 @@
|
||||||
/* 配置文件 */
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
const canIUse = wx.canIUse('editor'); // 高基础库标识,用于兼容
|
|
||||||
// #endif
|
|
||||||
module.exports = {
|
|
||||||
// 出错占位图
|
|
||||||
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,section' + (
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
canIUse ? '' :
|
|
||||||
// #endif
|
|
||||||
',pre')),
|
|
||||||
// 将被移除的标签
|
|
||||||
ignoreTags: makeMap(
|
|
||||||
'area,base,canvas,frame,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
+ (canIUse ? ',rp' : '')
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS
|
|
||||||
+ ',iframe'
|
|
||||||
// #endif
|
|
||||||
),
|
|
||||||
// 只能被 rich-text 显示的标签
|
|
||||||
richOnlyTags: makeMap('a,colgroup,fieldset,legend,table'
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
+ (canIUse ? ',bdi,bdo,caption,rt,ruby' : '')
|
|
||||||
// #endif
|
|
||||||
),
|
|
||||||
// 自闭合的标签
|
|
||||||
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'
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
+ (canIUse ? ',bdi,bdo,caption,pre,rt,ruby' : '')
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
+ ',embed,iframe'
|
|
||||||
// #endif
|
|
||||||
),
|
|
||||||
// 默认的标签样式
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,500 +0,0 @@
|
||||||
<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="imgtap">
|
|
||||||
<rich-text v-if="controls[i]!=0" :nodes="[{attrs:{src:loading&&(controls[i]||0)<2?loading:(lazyLoad&&!controls[i]?placeholder:(controls[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&&!controls[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))&&controls[i]==undefined" :id="n.attrs.id" :class="'_video '+(n.attrs.class||'')"
|
|
||||||
:style="n.attrs.style" :data-i="i" @tap="_loadVideo" />
|
|
||||||
<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||controls[i]==0"
|
|
||||||
:controls="!n.attrs.autoplay||n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[controls[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.autoplay||n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
|
|
||||||
:src="n.attrs.source[controls[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="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'">
|
|
||||||
<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" :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 {
|
|
||||||
controls: [],
|
|
||||||
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.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.controls, 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.controls[i]) {
|
|
||||||
// #ifdef QUICKAPP-WEBVIEW
|
|
||||||
this.$set(this.controls, i, 0);
|
|
||||||
this.$nextTick(function() {
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS
|
|
||||||
this.$set(this.controls, i, 1);
|
|
||||||
// #endif
|
|
||||||
// #ifdef QUICKAPP-WEBVIEW
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
} else if (this.loading && this.controls[i] != 2) {
|
|
||||||
// #ifdef QUICKAPP-WEBVIEW
|
|
||||||
this.$set(this.controls, i, 0);
|
|
||||||
this.$nextTick(function() {
|
|
||||||
// #endif
|
|
||||||
this.$set(this.controls, 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.controls[i] ? this.controls[i].i + 1 : 1;
|
|
||||||
if (index < this.nodes[i].attrs.source.length)
|
|
||||||
this.$set(this.controls, 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.controls, i, 3);
|
|
||||||
}
|
|
||||||
this.top && this.top.$emit('error', {
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
errMsg: e.detail.errMsg
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_loadVideo(e) {
|
|
||||||
this.$set(this.controls, 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;
|
|
||||||
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 {
|
|
||||||
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>
|
|
|
@ -1,55 +0,0 @@
|
||||||
/* 下拉刷新区域 */
|
|
||||||
.mescroll-downwarp {
|
|
||||||
position: absolute;
|
|
||||||
top: -100%;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 下拉刷新--内容区,定位于区域底部 */
|
|
||||||
.mescroll-downwarp .downwarp-content {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 60rpx;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 下拉刷新--提示文本 */
|
|
||||||
.mescroll-downwarp .downwarp-tip {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-left: 16rpx;
|
|
||||||
/* color: gray; 已在style设置color,此处删去*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 下拉刷新--旋转进度条 */
|
|
||||||
.mescroll-downwarp .downwarp-progress {
|
|
||||||
display: inline-block;
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid gray;
|
|
||||||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 旋转动画 */
|
|
||||||
.mescroll-downwarp .mescroll-rotate {
|
|
||||||
animation: mescrollDownRotate 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mescrollDownRotate {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
<!-- 下拉刷新区域 -->
|
|
||||||
<template>
|
|
||||||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
|
|
||||||
<view class="downwarp-content">
|
|
||||||
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view>
|
|
||||||
<view class="downwarp-tip">{{downText}}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
option: Object , // down的配置项
|
|
||||||
type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
|
|
||||||
rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
|
||||||
mOption(){
|
|
||||||
return this.option || {}
|
|
||||||
},
|
|
||||||
// 是否在加载中
|
|
||||||
isDownLoading(){
|
|
||||||
return this.type === 3
|
|
||||||
},
|
|
||||||
// 旋转的角度
|
|
||||||
downRotate(){
|
|
||||||
return 'rotate(' + 360 * this.rate + 'deg)'
|
|
||||||
},
|
|
||||||
// 文本提示
|
|
||||||
downText(){
|
|
||||||
switch (this.type){
|
|
||||||
case 1: return this.mOption.textInOffset;
|
|
||||||
case 2: return this.mOption.textOutOffset;
|
|
||||||
case 3: return this.mOption.textLoading;
|
|
||||||
case 4: return this.mOption.textLoading;
|
|
||||||
default: return this.mOption.textInOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@import "./mescroll-down.css";
|
|
||||||
</style>
|
|
|
@ -1,27 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
|
|
||||||
<mix-empty :type="option.type" :backgroundColor="option.backgroundColor"></mix-empty>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import mixEmpty from '@/components/mix-empty/mix-empty.vue'
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
mixEmpty
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
// empty的配置项: 默认为GlobalOption.up.empty
|
|
||||||
option: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,95 +0,0 @@
|
||||||
<!--空布局
|
|
||||||
|
|
||||||
可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理:
|
|
||||||
import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue';
|
|
||||||
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
|
|
||||||
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
|
|
||||||
<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
|
|
||||||
<view v-if="tip" class="empty-tip">{{ tip }}</view>
|
|
||||||
<view v-if="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// 引入全局配置
|
|
||||||
import GlobalOption from './../mescroll-uni-option.js';
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
// empty的配置项: 默认为GlobalOption.up.empty
|
|
||||||
option: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 使用computed获取配置,用于支持option的动态配置
|
|
||||||
computed: {
|
|
||||||
// 图标
|
|
||||||
icon() {
|
|
||||||
return this.option.icon == null ? GlobalOption.up.empty.icon : this.option.icon; // 此处不使用短路求值, 用于支持传空串不显示图标
|
|
||||||
},
|
|
||||||
// 文本提示
|
|
||||||
tip() {
|
|
||||||
return this.option.tip == null ? GlobalOption.up.empty.tip : this.option.tip; // 此处不使用短路求值, 用于支持传空串不显示文本提示
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 点击按钮
|
|
||||||
emptyClick() {
|
|
||||||
this.$emit('emptyclick');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* 无任何数据的空布局 */
|
|
||||||
.mescroll-empty {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
padding: 30vh 50rpx 100rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-empty.empty-fixed {
|
|
||||||
z-index: 99;
|
|
||||||
position: absolute; /*transform会使fixed失效,最终会降级为absolute */
|
|
||||||
top: 100rpx;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-empty .empty-icon {
|
|
||||||
width: 170rpx;
|
|
||||||
height: 170rpx;
|
|
||||||
transform: translateX(16rpx);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-empty .empty-tip {
|
|
||||||
margin-top: 20rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-empty .empty-btn {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 40rpx;
|
|
||||||
min-width: 200rpx;
|
|
||||||
padding: 18rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
border: 1rpx solid #e04b28;
|
|
||||||
border-radius: 60rpx;
|
|
||||||
color: #e04b28;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-empty .empty-btn:active {
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,83 +0,0 @@
|
||||||
<!-- 回到顶部的按钮 -->
|
|
||||||
<template>
|
|
||||||
<image
|
|
||||||
v-if="mOption.src"
|
|
||||||
class="mescroll-totop"
|
|
||||||
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-totop-safearea': mOption.safearea}]"
|
|
||||||
:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
|
|
||||||
:src="mOption.src"
|
|
||||||
mode="widthFix"
|
|
||||||
@click="toTopClick"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
// up.toTop的配置项
|
|
||||||
option: Object,
|
|
||||||
// 是否显示
|
|
||||||
value: false
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
|
||||||
mOption(){
|
|
||||||
return this.option || {}
|
|
||||||
},
|
|
||||||
// 优先显示左边
|
|
||||||
left(){
|
|
||||||
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
|
|
||||||
},
|
|
||||||
// 右边距离 (优先显示左边)
|
|
||||||
right() {
|
|
||||||
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addUnit(num){
|
|
||||||
if(!num) return 0;
|
|
||||||
if(typeof num === 'number') return num + 'rpx';
|
|
||||||
return num
|
|
||||||
},
|
|
||||||
toTopClick() {
|
|
||||||
this.$emit('input', false); // 使v-model生效
|
|
||||||
this.$emit('click'); // 派发点击事件
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* 回到顶部的按钮 */
|
|
||||||
.mescroll-totop {
|
|
||||||
z-index: 9990;
|
|
||||||
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
|
|
||||||
right: 20rpx;
|
|
||||||
bottom: 120rpx;
|
|
||||||
width: 72rpx;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 50%;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.5s; /* 过渡 */
|
|
||||||
margin-bottom: var(--window-bottom); /* css变量 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 适配 iPhoneX */
|
|
||||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
|
|
||||||
.mescroll-totop-safearea {
|
|
||||||
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
|
|
||||||
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示 -- 淡入 */
|
|
||||||
.mescroll-totop-in {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏 -- 淡出且不接收事件*/
|
|
||||||
.mescroll-totop-out {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,47 +0,0 @@
|
||||||
/* 上拉加载区域 */
|
|
||||||
.mescroll-upwarp {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-height: 110rpx;
|
|
||||||
padding: 30rpx 0;
|
|
||||||
text-align: center;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*提示文本 */
|
|
||||||
.mescroll-upwarp .upwarp-tip,
|
|
||||||
.mescroll-upwarp .upwarp-nodata {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
vertical-align: middle;
|
|
||||||
/* color: gray; 已在style设置color,此处删去*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-upwarp .upwarp-tip {
|
|
||||||
margin-left: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*旋转进度条 */
|
|
||||||
.mescroll-upwarp .upwarp-progress {
|
|
||||||
display: inline-block;
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid gray;
|
|
||||||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 旋转动画 */
|
|
||||||
.mescroll-upwarp .mescroll-rotate {
|
|
||||||
animation: mescrollUpRotate 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mescrollUpRotate {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
<!-- 上拉加载区域 -->
|
|
||||||
<template>
|
|
||||||
<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
|
|
||||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
|
||||||
<view v-show="isUpLoading">
|
|
||||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view>
|
|
||||||
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
|
|
||||||
</view>
|
|
||||||
<!-- 无数据 -->
|
|
||||||
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
option: Object, // up的配置项
|
|
||||||
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了)
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
|
||||||
mOption() {
|
|
||||||
return this.option || {};
|
|
||||||
},
|
|
||||||
// 加载中
|
|
||||||
isUpLoading() {
|
|
||||||
return this.type === 1;
|
|
||||||
},
|
|
||||||
// 没有更多了
|
|
||||||
isUpNoMore() {
|
|
||||||
return this.type === 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@import './mescroll-up.css';
|
|
||||||
</style>
|
|
|
@ -1,14 +0,0 @@
|
||||||
.mescroll-body {
|
|
||||||
position: relative; /* 下拉刷新区域相对自身定位 */
|
|
||||||
height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
|
|
||||||
overflow: hidden; /* 遮住顶部下拉刷新区域 */
|
|
||||||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 适配 iPhoneX */
|
|
||||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
|
|
||||||
.mescroll-safearea {
|
|
||||||
padding-bottom: constant(safe-area-inset-bottom);
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,344 +0,0 @@
|
||||||
<template>
|
|
||||||
<view
|
|
||||||
class="mescroll-body mescroll-render-touch"
|
|
||||||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
|
|
||||||
@touchstart="wxsBiz.touchstartEvent"
|
|
||||||
@touchmove="wxsBiz.touchmoveEvent"
|
|
||||||
@touchend="wxsBiz.touchendEvent"
|
|
||||||
@touchcancel="wxsBiz.touchendEvent"
|
|
||||||
:change:prop="wxsBiz.propObserver"
|
|
||||||
:prop="wxsProp"
|
|
||||||
>
|
|
||||||
<!-- 状态栏 -->
|
|
||||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
|
||||||
|
|
||||||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
|
||||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
|
||||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
|
|
||||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
|
||||||
<view class="downwarp-content" :change:prop="renderBiz.propObserver" :prop="wxsProp">
|
|
||||||
<!-- <view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view> -->
|
|
||||||
<view class="downwarp-tip">
|
|
||||||
<image v-show="downText === '下拉刷新'" style="width:80rpx;height:86rpx" src="/static/loading/hamster.png"></image>
|
|
||||||
<image v-show="downText !== '下拉刷新'" style="width:80rpx;height:86rpx" src="/static/loading/hamster.gif"></image>
|
|
||||||
</view>
|
|
||||||
<!-- {{downText}} -->
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 列表内容 -->
|
|
||||||
<slot></slot>
|
|
||||||
|
|
||||||
<!-- 空布局 -->
|
|
||||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
|
||||||
|
|
||||||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
|
||||||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
|
||||||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
|
||||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
|
||||||
<view v-show="upLoadType===1" :style="{height: upLoadType===1 ? 'auto' : 0}" style="display: flex;align-items: center;justify-content: center;overflow: hidden">
|
|
||||||
<!-- <view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view> -->
|
|
||||||
<image style="width: 64rpx;height: 68rpx" src="/static/loading/hamster.gif"></image>
|
|
||||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
|
||||||
</view>
|
|
||||||
<!-- 无数据 -->
|
|
||||||
<!-- <view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> -->
|
|
||||||
<view v-if="upLoadType===2" class="mix-nodata center">
|
|
||||||
<image class="logo" src="/static/logo-b-w.png"></image>
|
|
||||||
<text>国云网络提供技术支持</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
|
|
||||||
<!-- #ifdef H5 -->
|
|
||||||
<!-- <view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> -->
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 适配iPhoneX -->
|
|
||||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
|
||||||
|
|
||||||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
|
|
||||||
<!-- <mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> -->
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 微信小程序, app, h5使用wxs -->
|
|
||||||
<!-- #ifdef MP-WEIXIN || APP-PLUS || H5-->
|
|
||||||
<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- app, h5使用renderjs -->
|
|
||||||
<!-- #ifdef APP-PLUS || H5 -->
|
|
||||||
<script module="renderBiz" lang="renderjs">
|
|
||||||
import renderBiz from './wxs/renderjs.js';
|
|
||||||
export default {
|
|
||||||
mixins: [renderBiz]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// 引入mescroll-uni.js,处理核心逻辑
|
|
||||||
import MeScroll from './mescroll-uni.js';
|
|
||||||
// 引入全局配置
|
|
||||||
import GlobalOption from './mescroll-uni-option.js';
|
|
||||||
// 引入空布局组件
|
|
||||||
import MescrollEmpty from './components/mescroll-empty.vue';
|
|
||||||
// 引入回到顶部组件
|
|
||||||
import MescrollTop from './components/mescroll-top.vue';
|
|
||||||
// 引入兼容wxs(含renderjs)写法的mixins
|
|
||||||
import WxsMixin from './wxs/mixins.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [WxsMixin],
|
|
||||||
components: {
|
|
||||||
MescrollEmpty,
|
|
||||||
MescrollTop
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mescroll: {optDown:{},optUp:{}}, // mescroll实例
|
|
||||||
downHight: 0, //下拉刷新: 容器高度
|
|
||||||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
|
|
||||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
|
||||||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
|
|
||||||
isShowEmpty: false, // 是否显示空布局
|
|
||||||
isShowToTop: false, // 是否显示回到顶部按钮
|
|
||||||
windowHeight: 0, // 可使用窗口的高度
|
|
||||||
windowBottom: 0, // 可使用窗口的底部位置
|
|
||||||
statusBarHeight: 0 // 状态栏高度
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
down: Object, // 下拉刷新的参数配置
|
|
||||||
up: Object, // 上拉加载的参数配置
|
|
||||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
|
||||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
|
||||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
|
||||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
|
||||||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
|
||||||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
|
||||||
minHeight(){
|
|
||||||
return this.toPx(this.height || '100%') + 'px'
|
|
||||||
},
|
|
||||||
// 下拉布局往下偏移的距离 (px)
|
|
||||||
numTop() {
|
|
||||||
return this.toPx(this.top)
|
|
||||||
},
|
|
||||||
padTop() {
|
|
||||||
return this.numTop + 'px';
|
|
||||||
},
|
|
||||||
// 上拉布局往上偏移 (px)
|
|
||||||
numBottom() {
|
|
||||||
return this.toPx(this.bottom);
|
|
||||||
},
|
|
||||||
padBottom() {
|
|
||||||
return this.numBottom + 'px';
|
|
||||||
},
|
|
||||||
// 是否为重置下拉的状态
|
|
||||||
isDownReset() {
|
|
||||||
return this.downLoadType === 3 || this.downLoadType === 4;
|
|
||||||
},
|
|
||||||
// 过渡
|
|
||||||
transition() {
|
|
||||||
return this.isDownReset ? 'transform 300ms' : '';
|
|
||||||
},
|
|
||||||
translateY() {
|
|
||||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
|
||||||
},
|
|
||||||
// 是否在加载中
|
|
||||||
isDownLoading(){
|
|
||||||
return this.downLoadType === 3
|
|
||||||
},
|
|
||||||
// 旋转的角度
|
|
||||||
downRotate(){
|
|
||||||
return 'rotate(' + 360 * this.downRate + 'deg)'
|
|
||||||
},
|
|
||||||
// 文本提示
|
|
||||||
downText(){
|
|
||||||
if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
|
|
||||||
switch (this.downLoadType){
|
|
||||||
case 1: return this.mescroll.optDown.textInOffset;
|
|
||||||
case 2: return this.mescroll.optDown.textOutOffset;
|
|
||||||
case 3: return this.mescroll.optDown.textLoading;
|
|
||||||
case 4: return this.mescroll.optDown.textLoading;
|
|
||||||
default: return this.mescroll.optDown.textInOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//number,rpx,upx,px,% --> px的数值
|
|
||||||
toPx(num) {
|
|
||||||
if (typeof num === 'string') {
|
|
||||||
if (num.indexOf('px') !== -1) {
|
|
||||||
if (num.indexOf('rpx') !== -1) {
|
|
||||||
// "10rpx"
|
|
||||||
num = num.replace('rpx', '');
|
|
||||||
} else if (num.indexOf('upx') !== -1) {
|
|
||||||
// "10upx"
|
|
||||||
num = num.replace('upx', '');
|
|
||||||
} else {
|
|
||||||
// "10px"
|
|
||||||
return Number(num.replace('px', ''));
|
|
||||||
}
|
|
||||||
} else if (num.indexOf('%') !== -1) {
|
|
||||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
|
||||||
let rate = Number(num.replace('%', '')) / 100;
|
|
||||||
return this.windowHeight * rate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num ? uni.upx2px(Number(num)) : 0;
|
|
||||||
},
|
|
||||||
// 点击空布局的按钮回调
|
|
||||||
emptyClick() {
|
|
||||||
this.$emit('emptyclick', this.mescroll);
|
|
||||||
},
|
|
||||||
// 点击回到顶部的按钮回调
|
|
||||||
toTopClick() {
|
|
||||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
|
||||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
|
||||||
created() {
|
|
||||||
let vm = this;
|
|
||||||
|
|
||||||
let diyOption = {
|
|
||||||
// 下拉刷新的配置
|
|
||||||
down: {
|
|
||||||
auto: false,
|
|
||||||
inOffset() {
|
|
||||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
|
||||||
},
|
|
||||||
outOffset() {
|
|
||||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
|
||||||
},
|
|
||||||
onMoving(mescroll, rate, downHight) {
|
|
||||||
// 下拉过程中的回调,滑动过程一直在执行;
|
|
||||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
|
||||||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
|
|
||||||
},
|
|
||||||
showLoading(mescroll, downHight) {
|
|
||||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
|
||||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
|
||||||
},
|
|
||||||
endDownScroll() {
|
|
||||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
|
||||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
|
||||||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
|
|
||||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
|
|
||||||
if(vm.downLoadType === 4) vm.downLoadType = 0
|
|
||||||
},300)
|
|
||||||
},
|
|
||||||
// 派发下拉刷新的回调
|
|
||||||
callback: function(mescroll) {
|
|
||||||
vm.$emit('down', mescroll);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 上拉加载的配置
|
|
||||||
up: {
|
|
||||||
// 显示加载中的回调
|
|
||||||
showLoading() {
|
|
||||||
vm.upLoadType = 1;
|
|
||||||
},
|
|
||||||
// 显示无更多数据的回调
|
|
||||||
showNoMore() {
|
|
||||||
vm.upLoadType = 2;
|
|
||||||
},
|
|
||||||
// 隐藏上拉加载的回调
|
|
||||||
hideUpScroll(mescroll) {
|
|
||||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
|
||||||
},
|
|
||||||
// 空布局
|
|
||||||
empty: {
|
|
||||||
onShow(isShow) {
|
|
||||||
// 显示隐藏的回调
|
|
||||||
vm.isShowEmpty = isShow;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 回到顶部
|
|
||||||
toTop: {
|
|
||||||
onShow(isShow) {
|
|
||||||
// 显示隐藏的回调
|
|
||||||
vm.isShowToTop = isShow;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 派发上拉加载的回调
|
|
||||||
callback: function(mescroll) {
|
|
||||||
vm.$emit('up', mescroll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置
|
|
||||||
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
|
|
||||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
|
||||||
|
|
||||||
// 初始化MeScroll对象
|
|
||||||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
|
|
||||||
// init回调mescroll对象
|
|
||||||
vm.$emit('init', vm.mescroll);
|
|
||||||
|
|
||||||
// 设置高度
|
|
||||||
const sys = uni.getSystemInfoSync();
|
|
||||||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
|
||||||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
|
||||||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
|
||||||
// 使down的bottomOffset生效
|
|
||||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
|
||||||
|
|
||||||
// 因为使用的是page的scroll,这里需自定义scrollTo
|
|
||||||
vm.mescroll.resetScrollTo((y, t) => {
|
|
||||||
if(typeof y === 'string'){
|
|
||||||
// 滚动到指定view (y必须为元素的id,不带#)
|
|
||||||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
|
|
||||||
uni.createSelectorQuery().select('#'+y).boundingClientRect(function(rect){
|
|
||||||
let top = rect.top
|
|
||||||
top += vm.mescroll.getScrollTop()
|
|
||||||
uni.pageScrollTo({
|
|
||||||
scrollTop: top,
|
|
||||||
duration: t
|
|
||||||
})
|
|
||||||
}).exec()
|
|
||||||
},30)
|
|
||||||
} else{
|
|
||||||
// 滚动到指定位置 (y必须为数字)
|
|
||||||
uni.pageScrollTo({
|
|
||||||
scrollTop: y,
|
|
||||||
duration: t
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
|
||||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
|
||||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import "./mescroll-body.css";
|
|
||||||
@import "./components/mescroll-down.css";
|
|
||||||
@import './components/mescroll-up.css';
|
|
||||||
|
|
||||||
.mix-nodata{
|
|
||||||
height: 52rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
|
|
||||||
.logo{
|
|
||||||
width: 34rpx;
|
|
||||||
height: 34rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,65 +0,0 @@
|
||||||
// mescroll-body 和 mescroll-uni 通用
|
|
||||||
|
|
||||||
// import MescrollUni from "./mescroll-uni.vue";
|
|
||||||
// import MescrollBody from "./mescroll-body.vue";
|
|
||||||
|
|
||||||
const MescrollMixin = {
|
|
||||||
// components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册
|
|
||||||
// MescrollUni,
|
|
||||||
// MescrollBody
|
|
||||||
// },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mescroll: null //mescroll实例对象
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|
||||||
onPullDownRefresh(){
|
|
||||||
this.mescroll && this.mescroll.onPullDownRefresh();
|
|
||||||
},
|
|
||||||
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|
||||||
onPageScroll(e) {
|
|
||||||
this.mescroll && this.mescroll.onPageScroll(e);
|
|
||||||
},
|
|
||||||
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|
||||||
onReachBottom() {
|
|
||||||
this.mescroll && this.mescroll.onReachBottom();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// mescroll组件初始化的回调,可获取到mescroll对象
|
|
||||||
mescrollInit(mescroll) {
|
|
||||||
this.mescroll = mescroll;
|
|
||||||
this.mescrollInitByRef(); // 兼容字节跳动小程序
|
|
||||||
},
|
|
||||||
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序: http://www.mescroll.com/qa.html?v=20200107#q26)
|
|
||||||
mescrollInitByRef() {
|
|
||||||
if(!this.mescroll || !this.mescroll.resetUpScroll){
|
|
||||||
let mescrollRef = this.$refs.mescrollRef;
|
|
||||||
if(mescrollRef) this.mescroll = mescrollRef.mescroll
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 下拉刷新的回调 (mixin默认resetUpScroll)
|
|
||||||
downCallback() {
|
|
||||||
if(this.mescroll.optUp.use){
|
|
||||||
this.mescroll.resetUpScroll()
|
|
||||||
}else{
|
|
||||||
setTimeout(()=>{
|
|
||||||
this.mescroll.endSuccess();
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 上拉加载的回调
|
|
||||||
upCallback() {
|
|
||||||
// mixin默认延时500自动结束加载
|
|
||||||
setTimeout(()=>{
|
|
||||||
this.mescroll.endErr();
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MescrollMixin;
|
|
|
@ -1,33 +0,0 @@
|
||||||
// 全局配置
|
|
||||||
// mescroll-body 和 mescroll-uni 通用
|
|
||||||
const GlobalOption = {
|
|
||||||
down: {
|
|
||||||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|
||||||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|
||||||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|
||||||
textLoading: '加载中 ...', // 加载中的提示文本
|
|
||||||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|
||||||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|
||||||
},
|
|
||||||
up: {
|
|
||||||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|
||||||
textLoading: '加载中 ...', // 加载中的提示文本
|
|
||||||
textNoMore: '- 我也是有底线的 -', // 没有更多数据的提示文本
|
|
||||||
offset: 80, // 距底部多远时,触发upCallback
|
|
||||||
toTop: {
|
|
||||||
// 回到顶部按钮,需配置src才显示
|
|
||||||
src: "http://www.mescroll.com/img/mescroll-totop.png?v=1", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
|
||||||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
|
||||||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|
||||||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|
||||||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|
||||||
},
|
|
||||||
empty: {
|
|
||||||
use: true, // 是否显示空布局
|
|
||||||
icon: "/static/empty/hamster.png", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|
||||||
tip: '~ 空空如也 ~' // 提示
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GlobalOption
|
|
|
@ -1,36 +0,0 @@
|
||||||
.mescroll-uni-warp{
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-uni-content{
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mescroll-uni {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 200rpx;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 定位的方式固定高度 */
|
|
||||||
.mescroll-uni-fixed{
|
|
||||||
z-index: 1;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: auto; /* 使right生效 */
|
|
||||||
height: auto; /* 使bottom生效 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 适配 iPhoneX */
|
|
||||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
|
|
||||||
.mescroll-safearea {
|
|
||||||
padding-bottom: constant(safe-area-inset-bottom);
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,788 +0,0 @@
|
||||||
/* mescroll
|
|
||||||
* version 1.3.0
|
|
||||||
* 2020-07-10 wenju
|
|
||||||
* http://www.mescroll.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function MeScroll(options, isScrollBody) {
|
|
||||||
let me = this;
|
|
||||||
me.version = '1.3.0'; // mescroll版本号
|
|
||||||
me.options = options || {}; // 配置
|
|
||||||
me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
|
|
||||||
|
|
||||||
me.isDownScrolling = false; // 是否在执行下拉刷新的回调
|
|
||||||
me.isUpScrolling = false; // 是否在执行上拉加载的回调
|
|
||||||
let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
|
|
||||||
|
|
||||||
// 初始化下拉刷新
|
|
||||||
me.initDownScroll();
|
|
||||||
// 初始化上拉加载,则初始化
|
|
||||||
me.initUpScroll();
|
|
||||||
|
|
||||||
// 自动加载
|
|
||||||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|
||||||
// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
|
|
||||||
if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) {
|
|
||||||
if (me.optDown.autoShowLoading) {
|
|
||||||
me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
|
|
||||||
} else {
|
|
||||||
me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 自动触发上拉加载
|
|
||||||
if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback
|
|
||||||
setTimeout(function(){
|
|
||||||
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
|
|
||||||
},100)
|
|
||||||
}
|
|
||||||
}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 配置参数:下拉刷新 */
|
|
||||||
MeScroll.prototype.extendDownScroll = function(optDown) {
|
|
||||||
// 下拉刷新的配置
|
|
||||||
MeScroll.extend(optDown, {
|
|
||||||
use: true, // 是否启用下拉刷新; 默认true
|
|
||||||
auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
|
|
||||||
native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|
||||||
autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
|
|
||||||
isLock: false, // 是否锁定下拉刷新,默认false;
|
|
||||||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|
||||||
startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差
|
|
||||||
inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
|
||||||
outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
|
||||||
bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
|
|
||||||
minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
|
|
||||||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|
||||||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|
||||||
textLoading: '加载中 ...', // 加载中的提示文本
|
|
||||||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
|
|
||||||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
|
||||||
inited: null, // 下拉刷新初始化完毕的回调
|
|
||||||
inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
|
|
||||||
outOffset: null, // 下拉的距离大于offset那一刻的回调
|
|
||||||
onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
|
|
||||||
beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
|
|
||||||
showLoading: null, // 显示下拉刷新进度的回调
|
|
||||||
afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用)
|
|
||||||
beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
|
|
||||||
endDownScroll: null, // 结束下拉刷新的回调
|
|
||||||
afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用)
|
|
||||||
callback: function(mescroll) {
|
|
||||||
// 下拉刷新的回调;默认重置上拉加载列表为第一页
|
|
||||||
mescroll.resetUpScroll();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 配置参数:上拉加载 */
|
|
||||||
MeScroll.prototype.extendUpScroll = function(optUp) {
|
|
||||||
// 上拉加载的配置
|
|
||||||
MeScroll.extend(optUp, {
|
|
||||||
use: true, // 是否启用上拉加载; 默认true
|
|
||||||
auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
|
|
||||||
isLock: false, // 是否锁定上拉加载,默认false;
|
|
||||||
isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
|
|
||||||
callback: null, // 上拉加载的回调;function(page,mescroll){ }
|
|
||||||
page: {
|
|
||||||
num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
|
|
||||||
size: 10, // 每页数据的数量
|
|
||||||
time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
|
|
||||||
},
|
|
||||||
noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
|
|
||||||
offset: 80, // 距底部多远时,触发upCallback
|
|
||||||
textLoading: '加载中 ...', // 加载中的提示文本
|
|
||||||
textNoMore: '-- 我也是有底线的 --', // 没有更多数据的提示文本
|
|
||||||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
|
|
||||||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
|
||||||
inited: null, // 初始化完毕的回调
|
|
||||||
showLoading: null, // 显示加载中的回调
|
|
||||||
showNoMore: null, // 显示无更多数据的回调
|
|
||||||
hideUpScroll: null, // 隐藏上拉加载的回调
|
|
||||||
errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
|
|
||||||
toTop: {
|
|
||||||
// 回到顶部按钮,需配置src才显示
|
|
||||||
src: null, // 图片路径,默认null (绝对路径或网络图)
|
|
||||||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
|
|
||||||
duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
|
|
||||||
btnClick: null, // 点击按钮的回调
|
|
||||||
onShow: null, // 是否显示的回调
|
|
||||||
zIndex: 9990, // fixed定位z-index值
|
|
||||||
left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|
||||||
right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|
||||||
bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|
||||||
safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
|
|
||||||
width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|
||||||
radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|
||||||
},
|
|
||||||
empty: {
|
|
||||||
use: true, // 是否显示空布局
|
|
||||||
icon: null, // 图标路径
|
|
||||||
tip: '~ 暂无相关数据 ~', // 提示
|
|
||||||
btnText: '', // 按钮
|
|
||||||
btnClick: null, // 点击按钮的回调
|
|
||||||
onShow: null, // 是否显示的回调
|
|
||||||
fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
|
|
||||||
top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
|
|
||||||
zIndex: 99 // fixed定位z-index值
|
|
||||||
},
|
|
||||||
onScroll: false // 是否监听滚动事件
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 配置参数 */
|
|
||||||
MeScroll.extend = function(userOption, defaultOption) {
|
|
||||||
if (!userOption) return defaultOption;
|
|
||||||
for (let key in defaultOption) {
|
|
||||||
if (userOption[key] == null) {
|
|
||||||
let def = defaultOption[key];
|
|
||||||
if (def != null && typeof def === 'object') {
|
|
||||||
userOption[key] = MeScroll.extend({}, def); // 深度匹配
|
|
||||||
} else {
|
|
||||||
userOption[key] = def;
|
|
||||||
}
|
|
||||||
} else if (typeof userOption[key] === 'object') {
|
|
||||||
MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return userOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 简单判断是否配置了颜色 (非透明,非白色) */
|
|
||||||
MeScroll.prototype.hasColor = function(color) {
|
|
||||||
if(!color) return false;
|
|
||||||
let c = color.toLowerCase();
|
|
||||||
return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------初始化下拉刷新------- */
|
|
||||||
MeScroll.prototype.initDownScroll = function() {
|
|
||||||
let me = this;
|
|
||||||
// 配置参数
|
|
||||||
me.optDown = me.options.down || {};
|
|
||||||
if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
|
||||||
me.extendDownScroll(me.optDown);
|
|
||||||
|
|
||||||
// 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
|
|
||||||
if(me.isScrollBody && me.optDown.native){
|
|
||||||
me.optDown.use = false
|
|
||||||
}else{
|
|
||||||
me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
|
|
||||||
}
|
|
||||||
|
|
||||||
me.downHight = 0; // 下拉区域的高度
|
|
||||||
|
|
||||||
// 在页面中加入下拉布局
|
|
||||||
if (me.optDown.use && me.optDown.inited) {
|
|
||||||
// 初始化完毕的回调
|
|
||||||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|
||||||
me.optDown.inited(me);
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 列表touchstart事件 */
|
|
||||||
MeScroll.prototype.touchstartEvent = function(e) {
|
|
||||||
if (!this.optDown.use) return;
|
|
||||||
|
|
||||||
this.startPoint = this.getPoint(e); // 记录起点
|
|
||||||
this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
|
|
||||||
this.startAngle = 0; // 初始角度
|
|
||||||
this.lastPoint = this.startPoint; // 重置上次move的点
|
|
||||||
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
|
|
||||||
this.inTouchend = false; // 标记不是touchend
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 列表touchmove事件 */
|
|
||||||
MeScroll.prototype.touchmoveEvent = function(e) {
|
|
||||||
if (!this.optDown.use) return;
|
|
||||||
let me = this;
|
|
||||||
|
|
||||||
let scrollTop = me.getScrollTop(); // 当前滚动条的距离
|
|
||||||
let curPoint = me.getPoint(e); // 当前点
|
|
||||||
|
|
||||||
let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|
||||||
|
|
||||||
// 向下拉 && 在顶部
|
|
||||||
// mescroll-body,直接判定在顶部即可
|
|
||||||
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
|
|
||||||
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
|
|
||||||
if (moveY > 0 && (
|
|
||||||
(me.isScrollBody && scrollTop <= 0)
|
|
||||||
||
|
|
||||||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
|
|
||||||
)) {
|
|
||||||
// 可下拉的条件
|
|
||||||
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
|
|
||||||
me.optUp.isBoth))) {
|
|
||||||
|
|
||||||
// 下拉的初始角度是否在配置的范围内
|
|
||||||
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
|
|
||||||
if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
|
|
||||||
|
|
||||||
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
|
|
||||||
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
|
|
||||||
me.inTouchend = true; // 标记执行touchend
|
|
||||||
me.touchendEvent(); // 提前触发touchend
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
me.preventDefault(e); // 阻止默认事件
|
|
||||||
|
|
||||||
let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
|
|
||||||
|
|
||||||
// 下拉距离 < 指定距离
|
|
||||||
if (me.downHight < me.optDown.offset) {
|
|
||||||
if (me.movetype !== 1) {
|
|
||||||
me.movetype = 1; // 加入标记,保证只执行一次
|
|
||||||
me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
|
|
||||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
|
||||||
}
|
|
||||||
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
|
|
||||||
|
|
||||||
// 指定距离 <= 下拉距离
|
|
||||||
} else {
|
|
||||||
if (me.movetype !== 2) {
|
|
||||||
me.movetype = 2; // 加入标记,保证只执行一次
|
|
||||||
me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
|
|
||||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
|
||||||
}
|
|
||||||
if (diff > 0) { // 向下拉
|
|
||||||
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
|
|
||||||
} else { // 向上收
|
|
||||||
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
me.downHight = Math.round(me.downHight) // 取整
|
|
||||||
let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
|
|
||||||
me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
me.lastPoint = curPoint; // 记录本次移动的点
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 列表touchend事件 */
|
|
||||||
MeScroll.prototype.touchendEvent = function(e) {
|
|
||||||
if (!this.optDown.use) return;
|
|
||||||
// 如果下拉区域高度已改变,则需重置回来
|
|
||||||
if (this.isMoveDown) {
|
|
||||||
if (this.downHight >= this.optDown.offset) {
|
|
||||||
// 符合触发刷新的条件
|
|
||||||
this.triggerDownScroll();
|
|
||||||
} else {
|
|
||||||
// 不符合的话 则重置
|
|
||||||
this.downHight = 0;
|
|
||||||
this.endDownScrollCall(this);
|
|
||||||
}
|
|
||||||
this.movetype = 0;
|
|
||||||
this.isMoveDown = false;
|
|
||||||
} else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
|
|
||||||
let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|
||||||
// 上滑
|
|
||||||
if (isScrollUp) {
|
|
||||||
// 需检查滑动的角度
|
|
||||||
let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
|
|
||||||
if (angle > 80) {
|
|
||||||
// 检查并触发上拉
|
|
||||||
this.triggerUpScroll(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 根据点击滑动事件获取第一个手指的坐标 */
|
|
||||||
MeScroll.prototype.getPoint = function(e) {
|
|
||||||
if (!e) {
|
|
||||||
return {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e.touches && e.touches[0]) {
|
|
||||||
return {
|
|
||||||
x: e.touches[0].pageX,
|
|
||||||
y: e.touches[0].pageY
|
|
||||||
}
|
|
||||||
} else if (e.changedTouches && e.changedTouches[0]) {
|
|
||||||
return {
|
|
||||||
x: e.changedTouches[0].pageX,
|
|
||||||
y: e.changedTouches[0].pageY
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x: e.clientX,
|
|
||||||
y: e.clientY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 计算两点之间的角度: 区间 [0,90]*/
|
|
||||||
MeScroll.prototype.getAngle = function(p1, p2) {
|
|
||||||
let x = Math.abs(p1.x - p2.x);
|
|
||||||
let y = Math.abs(p1.y - p2.y);
|
|
||||||
let z = Math.sqrt(x * x + y * y);
|
|
||||||
let angle = 0;
|
|
||||||
if (z !== 0) {
|
|
||||||
angle = Math.asin(y / z) / Math.PI * 180;
|
|
||||||
}
|
|
||||||
return angle
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 触发下拉刷新 */
|
|
||||||
MeScroll.prototype.triggerDownScroll = function() {
|
|
||||||
if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) {
|
|
||||||
//return true则处于完全自定义状态
|
|
||||||
} else {
|
|
||||||
this.showDownScroll(); // 下拉刷新中...
|
|
||||||
!this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示下拉进度布局 */
|
|
||||||
MeScroll.prototype.showDownScroll = function() {
|
|
||||||
this.isDownScrolling = true; // 标记下拉中
|
|
||||||
if (this.optDown.native) {
|
|
||||||
uni.startPullDownRefresh(); // 系统自带的下拉刷新
|
|
||||||
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
|
|
||||||
} else{
|
|
||||||
this.downHight = this.optDown.offset; // 更新下拉区域高度
|
|
||||||
this.showDownLoadingCall(this.downHight); // 下拉刷新中...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MeScroll.prototype.showDownLoadingCall = function(downHight) {
|
|
||||||
this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中...
|
|
||||||
this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示系统自带的下拉刷新时需要处理的业务 */
|
|
||||||
MeScroll.prototype.onPullDownRefresh = function() {
|
|
||||||
this.isDownScrolling = true; // 标记下拉中
|
|
||||||
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
|
|
||||||
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结束下拉刷新 */
|
|
||||||
MeScroll.prototype.endDownScroll = function() {
|
|
||||||
if (this.optDown.native) { // 结束原生下拉刷新
|
|
||||||
this.isDownScrolling = false;
|
|
||||||
this.endDownScrollCall(this);
|
|
||||||
uni.stopPullDownRefresh();
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let me = this;
|
|
||||||
// 结束下拉刷新的方法
|
|
||||||
let endScroll = function() {
|
|
||||||
me.downHight = 0;
|
|
||||||
me.isDownScrolling = false;
|
|
||||||
me.endDownScrollCall(me);
|
|
||||||
if(!me.isScrollBody){
|
|
||||||
me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
|
|
||||||
me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 结束下拉刷新时的回调
|
|
||||||
let delay = 0;
|
|
||||||
if (me.optDown.beforeEndDownScroll) delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms
|
|
||||||
if (typeof delay === 'number' && delay > 0) {
|
|
||||||
setTimeout(endScroll, delay);
|
|
||||||
} else {
|
|
||||||
endScroll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MeScroll.prototype.endDownScrollCall = function() {
|
|
||||||
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
|
|
||||||
this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
|
|
||||||
MeScroll.prototype.lockDownScroll = function(isLock) {
|
|
||||||
if (isLock == null) isLock = true;
|
|
||||||
this.optDown.isLock = isLock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */
|
|
||||||
MeScroll.prototype.lockUpScroll = function(isLock) {
|
|
||||||
if (isLock == null) isLock = true;
|
|
||||||
this.optUp.isLock = isLock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------初始化上拉加载------- */
|
|
||||||
MeScroll.prototype.initUpScroll = function() {
|
|
||||||
let me = this;
|
|
||||||
// 配置参数
|
|
||||||
me.optUp = me.options.up || {use: false}
|
|
||||||
if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
|
||||||
me.extendUpScroll(me.optUp);
|
|
||||||
|
|
||||||
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
|
|
||||||
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
|
|
||||||
me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
|
|
||||||
|
|
||||||
// 初始化完毕的回调
|
|
||||||
if (me.optUp.inited) {
|
|
||||||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|
||||||
me.optUp.inited(me);
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*滚动到底部的事件 (仅mescroll-body生效)*/
|
|
||||||
MeScroll.prototype.onReachBottom = function() {
|
|
||||||
if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
|
|
||||||
if (!this.optUp.isLock && this.optUp.hasNext) {
|
|
||||||
this.triggerUpScroll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*列表滚动事件 (仅mescroll-body生效)*/
|
|
||||||
MeScroll.prototype.onPageScroll = function(e) {
|
|
||||||
if (!this.isScrollBody) return;
|
|
||||||
|
|
||||||
// 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
|
|
||||||
this.setScrollTop(e.scrollTop);
|
|
||||||
|
|
||||||
// 顶部按钮的显示隐藏
|
|
||||||
if (e.scrollTop >= this.optUp.toTop.offset) {
|
|
||||||
this.showTopBtn();
|
|
||||||
} else {
|
|
||||||
this.hideTopBtn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*列表滚动事件*/
|
|
||||||
MeScroll.prototype.scroll = function(e, onScroll) {
|
|
||||||
// 更新滚动条的位置
|
|
||||||
this.setScrollTop(e.scrollTop);
|
|
||||||
// 更新滚动内容高度
|
|
||||||
this.setScrollHeight(e.scrollHeight);
|
|
||||||
|
|
||||||
// 向上滑还是向下滑动
|
|
||||||
if (this.preScrollY == null) this.preScrollY = 0;
|
|
||||||
this.isScrollUp = e.scrollTop - this.preScrollY > 0;
|
|
||||||
this.preScrollY = e.scrollTop;
|
|
||||||
|
|
||||||
// 上滑 && 检查并触发上拉
|
|
||||||
this.isScrollUp && this.triggerUpScroll(true);
|
|
||||||
|
|
||||||
// 顶部按钮的显示隐藏
|
|
||||||
if (e.scrollTop >= this.optUp.toTop.offset) {
|
|
||||||
this.showTopBtn();
|
|
||||||
} else {
|
|
||||||
this.hideTopBtn();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 滑动监听
|
|
||||||
this.optUp.onScroll && onScroll && onScroll()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 触发上拉加载 */
|
|
||||||
MeScroll.prototype.triggerUpScroll = function(isCheck) {
|
|
||||||
if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) {
|
|
||||||
// 是否校验在底部; 默认不校验
|
|
||||||
if (isCheck === true) {
|
|
||||||
let canUp = false;
|
|
||||||
// 还有下一页 && 没有锁定 && 不在下拉中
|
|
||||||
if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) {
|
|
||||||
if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
|
|
||||||
canUp = true; // 标记可上拉
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (canUp === false) return;
|
|
||||||
}
|
|
||||||
this.showUpScroll(); // 上拉加载中...
|
|
||||||
this.optUp.page.num++; // 预先加一页,如果失败则减回
|
|
||||||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
|
||||||
this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
|
||||||
this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|
||||||
this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|
||||||
this.optUp.callback(this); // 执行回调,联网加载数据
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示上拉加载中 */
|
|
||||||
MeScroll.prototype.showUpScroll = function() {
|
|
||||||
this.isUpScrolling = true; // 标记上拉加载中
|
|
||||||
this.optUp.showLoading && this.optUp.showLoading(this); // 回调
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示上拉无更多数据 */
|
|
||||||
MeScroll.prototype.showNoMore = function() {
|
|
||||||
this.optUp.hasNext = false; // 标记无更多数据
|
|
||||||
this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏上拉区域**/
|
|
||||||
MeScroll.prototype.hideUpScroll = function() {
|
|
||||||
this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结束上拉加载 */
|
|
||||||
MeScroll.prototype.endUpScroll = function(isShowNoMore) {
|
|
||||||
if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
|
|
||||||
if (isShowNoMore) {
|
|
||||||
this.showNoMore(); // isShowNoMore=true,显示无更多数据
|
|
||||||
} else {
|
|
||||||
this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.isUpScrolling = false; // 标记结束上拉加载
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 重置上拉加载列表为第一页
|
|
||||||
*isShowLoading 是否显示进度布局;
|
|
||||||
* 1.默认null,不传参,则显示上拉加载的进度布局
|
|
||||||
* 2.传参true, 则显示下拉刷新的进度布局
|
|
||||||
* 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据)
|
|
||||||
*/
|
|
||||||
MeScroll.prototype.resetUpScroll = function(isShowLoading) {
|
|
||||||
if (this.optUp && this.optUp.use) {
|
|
||||||
let page = this.optUp.page;
|
|
||||||
this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
|
|
||||||
this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
|
|
||||||
page.num = this.startNum; // 重置为第一页
|
|
||||||
page.time = null; // 重置时间为空
|
|
||||||
if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
|
|
||||||
if (isShowLoading == null) {
|
|
||||||
this.removeEmpty(); // 移除空布局
|
|
||||||
this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
|
|
||||||
} else {
|
|
||||||
this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
|
||||||
this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
|
||||||
this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|
||||||
this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|
||||||
this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 设置page.num的值 */
|
|
||||||
MeScroll.prototype.setPageNum = function(num) {
|
|
||||||
this.optUp.page.num = num - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 设置page.size的值 */
|
|
||||||
MeScroll.prototype.setPageSize = function(size) {
|
|
||||||
this.optUp.page.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 联网回调成功,结束下拉刷新和上拉加载
|
|
||||||
* dataSize: 当前页的数据量(必传)
|
|
||||||
* totalPage: 总页数(必传)
|
|
||||||
* systime: 服务器时间 (可空)
|
|
||||||
*/
|
|
||||||
MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) {
|
|
||||||
let hasNext;
|
|
||||||
if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
|
|
||||||
this.endSuccess(dataSize, hasNext, systime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 联网回调成功,结束下拉刷新和上拉加载
|
|
||||||
* dataSize: 当前页的数据量(必传)
|
|
||||||
* totalSize: 列表所有数据总数量(必传)
|
|
||||||
* systime: 服务器时间 (可空)
|
|
||||||
*/
|
|
||||||
MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) {
|
|
||||||
let hasNext;
|
|
||||||
if (this.optUp.use && totalSize != null) {
|
|
||||||
let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
|
|
||||||
hasNext = loadSize < totalSize; // 是否还有下一页
|
|
||||||
}
|
|
||||||
this.endSuccess(dataSize, hasNext, systime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 联网回调成功,结束下拉刷新和上拉加载
|
|
||||||
* dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页
|
|
||||||
* hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据.
|
|
||||||
* systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录
|
|
||||||
*/
|
|
||||||
MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) {
|
|
||||||
let me = this;
|
|
||||||
// 结束下拉刷新
|
|
||||||
if (me.isDownScrolling) me.endDownScroll();
|
|
||||||
|
|
||||||
// 结束上拉加载
|
|
||||||
if (me.optUp.use) {
|
|
||||||
let isShowNoMore; // 是否已无更多数据
|
|
||||||
if (dataSize != null) {
|
|
||||||
let pageNum = me.optUp.page.num; // 当前页码
|
|
||||||
let pageSize = me.optUp.page.size; // 每页长度
|
|
||||||
// 如果是第一页
|
|
||||||
if (pageNum === 1) {
|
|
||||||
if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
|
|
||||||
}
|
|
||||||
if (dataSize < pageSize || hasNext === false) {
|
|
||||||
// 返回的数据不满一页时,则说明已无更多数据
|
|
||||||
me.optUp.hasNext = false;
|
|
||||||
if (dataSize === 0 && pageNum === 1) {
|
|
||||||
// 如果第一页无任何数据且配置了空布局
|
|
||||||
isShowNoMore = false;
|
|
||||||
me.showEmpty();
|
|
||||||
} else {
|
|
||||||
// 总列表数少于配置的数量,则不显示无更多数据
|
|
||||||
let allDataSize = (pageNum - 1) * pageSize + dataSize;
|
|
||||||
if (allDataSize < me.optUp.noMoreSize) {
|
|
||||||
isShowNoMore = false;
|
|
||||||
} else {
|
|
||||||
isShowNoMore = true;
|
|
||||||
}
|
|
||||||
me.removeEmpty(); // 移除空布局
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 还有下一页
|
|
||||||
isShowNoMore = false;
|
|
||||||
me.optUp.hasNext = true;
|
|
||||||
me.removeEmpty(); // 移除空布局
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏上拉
|
|
||||||
me.endUpScroll(isShowNoMore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 回调失败,结束下拉刷新和上拉加载 */
|
|
||||||
MeScroll.prototype.endErr = function(errDistance) {
|
|
||||||
// 结束下拉,回调失败重置回原来的页码和时间
|
|
||||||
if (this.isDownScrolling) {
|
|
||||||
let page = this.optUp.page;
|
|
||||||
if (page && this.prePageNum) {
|
|
||||||
page.num = this.prePageNum;
|
|
||||||
page.time = this.prePageTime;
|
|
||||||
}
|
|
||||||
this.endDownScroll();
|
|
||||||
}
|
|
||||||
// 结束上拉,回调失败重置回原来的页码
|
|
||||||
if (this.isUpScrolling) {
|
|
||||||
this.optUp.page.num--;
|
|
||||||
this.endUpScroll(false);
|
|
||||||
// 如果是mescroll-body,则需往回滚一定距离
|
|
||||||
if(this.isScrollBody && errDistance !== 0){ // 不处理0
|
|
||||||
if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
|
|
||||||
this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示空布局 */
|
|
||||||
MeScroll.prototype.showEmpty = function() {
|
|
||||||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 移除空布局 */
|
|
||||||
MeScroll.prototype.removeEmpty = function() {
|
|
||||||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示回到顶部的按钮 */
|
|
||||||
MeScroll.prototype.showTopBtn = function() {
|
|
||||||
if (!this.topBtnShow) {
|
|
||||||
this.topBtnShow = true;
|
|
||||||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏回到顶部的按钮 */
|
|
||||||
MeScroll.prototype.hideTopBtn = function() {
|
|
||||||
if (this.topBtnShow) {
|
|
||||||
this.topBtnShow = false;
|
|
||||||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 获取滚动条的位置 */
|
|
||||||
MeScroll.prototype.getScrollTop = function() {
|
|
||||||
return this.scrollTop || 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 记录滚动条的位置 */
|
|
||||||
MeScroll.prototype.setScrollTop = function(y) {
|
|
||||||
this.scrollTop = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动到指定位置 */
|
|
||||||
MeScroll.prototype.scrollTo = function(y, t) {
|
|
||||||
this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义scrollTo */
|
|
||||||
MeScroll.prototype.resetScrollTo = function(myScrollTo) {
|
|
||||||
this.myScrollTo = myScrollTo
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动条到底部的距离 */
|
|
||||||
MeScroll.prototype.getScrollBottom = function() {
|
|
||||||
return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 计步器
|
|
||||||
star: 开始值
|
|
||||||
end: 结束值
|
|
||||||
callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器;
|
|
||||||
t: 计步时长,传0则直接回调end值;不传则默认300ms
|
|
||||||
rate: 周期;不传则默认30ms计步一次
|
|
||||||
* */
|
|
||||||
MeScroll.prototype.getStep = function(star, end, callback, t, rate) {
|
|
||||||
let diff = end - star; // 差值
|
|
||||||
if (t === 0 || diff === 0) {
|
|
||||||
callback && callback(end);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
t = t || 300; // 时长 300ms
|
|
||||||
rate = rate || 30; // 周期 30ms
|
|
||||||
let count = t / rate; // 次数
|
|
||||||
let step = diff / count; // 步长
|
|
||||||
let i = 0; // 计数
|
|
||||||
let timer = setInterval(function() {
|
|
||||||
if (i < count - 1) {
|
|
||||||
star += step;
|
|
||||||
callback && callback(star, timer);
|
|
||||||
i++;
|
|
||||||
} else {
|
|
||||||
callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
|
|
||||||
clearInterval(timer);
|
|
||||||
}
|
|
||||||
}, rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动容器的高度 */
|
|
||||||
MeScroll.prototype.getClientHeight = function(isReal) {
|
|
||||||
let h = this.clientHeight || 0
|
|
||||||
if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
|
|
||||||
h = this.getBodyHeight()
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
MeScroll.prototype.setClientHeight = function(h) {
|
|
||||||
this.clientHeight = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动内容的高度 */
|
|
||||||
MeScroll.prototype.getScrollHeight = function() {
|
|
||||||
return this.scrollHeight || 0;
|
|
||||||
}
|
|
||||||
MeScroll.prototype.setScrollHeight = function(h) {
|
|
||||||
this.scrollHeight = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* body的高度 */
|
|
||||||
MeScroll.prototype.getBodyHeight = function() {
|
|
||||||
return this.bodyHeight || 0;
|
|
||||||
}
|
|
||||||
MeScroll.prototype.setBodyHeight = function(h) {
|
|
||||||
this.bodyHeight = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 阻止浏览器默认滚动事件 */
|
|
||||||
MeScroll.prototype.preventDefault = function(e) {
|
|
||||||
// 小程序不支持e.preventDefault, 已在wxs中禁止
|
|
||||||
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
|
|
||||||
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
|
|
||||||
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
|
|
||||||
}
|
|
|
@ -1,408 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mescroll-uni-warp">
|
|
||||||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-into-view="scrollToViewId" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true">
|
|
||||||
<view class="mescroll-uni-content mescroll-render-touch"
|
|
||||||
@touchstart="wxsBiz.touchstartEvent"
|
|
||||||
@touchmove="wxsBiz.touchmoveEvent"
|
|
||||||
@touchend="wxsBiz.touchendEvent"
|
|
||||||
@touchcancel="wxsBiz.touchendEvent"
|
|
||||||
:change:prop="wxsBiz.propObserver"
|
|
||||||
:prop="wxsProp">
|
|
||||||
<!-- 状态栏 -->
|
|
||||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
|
||||||
|
|
||||||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
|
||||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
|
||||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
|
|
||||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
|
||||||
<view class="downwarp-content" :change:prop="renderBiz.propObserver" :prop="wxsProp">
|
|
||||||
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
|
|
||||||
<view class="downwarp-tip">{{downText}}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 列表内容 -->
|
|
||||||
<slot></slot>
|
|
||||||
|
|
||||||
<!-- 空布局 -->
|
|
||||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
|
||||||
|
|
||||||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
|
||||||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
|
||||||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
|
||||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
|
||||||
<view v-show="upLoadType===1">
|
|
||||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
|
|
||||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
|
||||||
</view>
|
|
||||||
<!-- 无数据 -->
|
|
||||||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
|
|
||||||
<!-- #ifdef H5 -->
|
|
||||||
<!-- <view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> -->
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 适配iPhoneX -->
|
|
||||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
|
|
||||||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 微信小程序, app, h5使用wxs -->
|
|
||||||
<!-- #ifdef MP-WEIXIN || APP-PLUS || H5-->
|
|
||||||
<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- app, h5使用renderjs -->
|
|
||||||
<!-- #ifdef APP-PLUS || H5 -->
|
|
||||||
<script module="renderBiz" lang="renderjs">
|
|
||||||
import renderBiz from './wxs/renderjs.js';
|
|
||||||
export default {
|
|
||||||
mixins:[renderBiz]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// 引入mescroll-uni.js,处理核心逻辑
|
|
||||||
import MeScroll from './mescroll-uni.js';
|
|
||||||
// 引入全局配置
|
|
||||||
import GlobalOption from './mescroll-uni-option.js';
|
|
||||||
// 引入空布局组件
|
|
||||||
import MescrollEmpty from './components/mescroll-empty.vue';
|
|
||||||
// 引入回到顶部组件
|
|
||||||
import MescrollTop from './components/mescroll-top.vue';
|
|
||||||
// 引入兼容wxs(含renderjs)写法的mixins
|
|
||||||
import WxsMixin from './wxs/mixins.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [WxsMixin],
|
|
||||||
components: {
|
|
||||||
MescrollEmpty,
|
|
||||||
MescrollTop
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mescroll: {optDown:{},optUp:{}}, // mescroll实例
|
|
||||||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
|
|
||||||
downHight: 0, //下拉刷新: 容器高度
|
|
||||||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
|
|
||||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
|
||||||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
|
|
||||||
isShowEmpty: false, // 是否显示空布局
|
|
||||||
isShowToTop: false, // 是否显示回到顶部按钮
|
|
||||||
scrollTop: 0, // 滚动条的位置
|
|
||||||
scrollAnim: false, // 是否开启滚动动画
|
|
||||||
windowTop: 0, // 可使用窗口的顶部位置
|
|
||||||
windowBottom: 0, // 可使用窗口的底部位置
|
|
||||||
windowHeight: 0, // 可使用窗口的高度
|
|
||||||
statusBarHeight: 0, // 状态栏高度
|
|
||||||
scrollToViewId: '' // 滚动到指定view的id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
down: Object, // 下拉刷新的参数配置
|
|
||||||
up: Object, // 上拉加载的参数配置
|
|
||||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
|
||||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
|
||||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
|
||||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
|
||||||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
|
||||||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 是否使用fixed定位 (当height有值,则不使用)
|
|
||||||
isFixed(){
|
|
||||||
return !this.height && this.fixed
|
|
||||||
},
|
|
||||||
// mescroll的高度
|
|
||||||
scrollHeight(){
|
|
||||||
if (this.isFixed) {
|
|
||||||
return "auto"
|
|
||||||
} else if(this.height){
|
|
||||||
return this.toPx(this.height) + 'px'
|
|
||||||
}else{
|
|
||||||
return "100%"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 下拉布局往下偏移的距离 (px)
|
|
||||||
numTop() {
|
|
||||||
return this.toPx(this.top)
|
|
||||||
},
|
|
||||||
fixedTop() {
|
|
||||||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
|
|
||||||
},
|
|
||||||
padTop() {
|
|
||||||
return !this.isFixed ? this.numTop + 'px' : 0
|
|
||||||
},
|
|
||||||
// 上拉布局往上偏移 (px)
|
|
||||||
numBottom() {
|
|
||||||
return this.toPx(this.bottom)
|
|
||||||
},
|
|
||||||
fixedBottom() {
|
|
||||||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
|
|
||||||
},
|
|
||||||
padBottom() {
|
|
||||||
return !this.isFixed ? this.numBottom + 'px' : 0
|
|
||||||
},
|
|
||||||
// 是否为重置下拉的状态
|
|
||||||
isDownReset(){
|
|
||||||
return this.downLoadType===3 || this.downLoadType===4
|
|
||||||
},
|
|
||||||
// 过渡
|
|
||||||
transition() {
|
|
||||||
return this.isDownReset ? 'transform 300ms' : '';
|
|
||||||
},
|
|
||||||
translateY() {
|
|
||||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
|
||||||
},
|
|
||||||
// 列表是否可滑动
|
|
||||||
scrollable(){
|
|
||||||
return this.downLoadType===0 || this.isDownReset
|
|
||||||
},
|
|
||||||
// 是否在加载中
|
|
||||||
isDownLoading(){
|
|
||||||
return this.downLoadType === 3
|
|
||||||
},
|
|
||||||
// 旋转的角度
|
|
||||||
downRotate(){
|
|
||||||
return 'rotate(' + 360 * this.downRate + 'deg)'
|
|
||||||
},
|
|
||||||
// 文本提示
|
|
||||||
downText(){
|
|
||||||
if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
|
|
||||||
switch (this.downLoadType){
|
|
||||||
case 1: return this.mescroll.optDown.textInOffset;
|
|
||||||
case 2: return this.mescroll.optDown.textOutOffset;
|
|
||||||
case 3: return this.mescroll.optDown.textLoading;
|
|
||||||
case 4: return this.mescroll.optDown.textLoading;
|
|
||||||
default: return this.mescroll.optDown.textInOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//number,rpx,upx,px,% --> px的数值
|
|
||||||
toPx(num){
|
|
||||||
if(typeof num === "string"){
|
|
||||||
if (num.indexOf('px') !== -1) {
|
|
||||||
if(num.indexOf('rpx') !== -1) { // "10rpx"
|
|
||||||
num = num.replace('rpx', '');
|
|
||||||
} else if(num.indexOf('upx') !== -1) { // "10upx"
|
|
||||||
num = num.replace('upx', '');
|
|
||||||
} else { // "10px"
|
|
||||||
return Number(num.replace('px', ''))
|
|
||||||
}
|
|
||||||
}else if (num.indexOf('%') !== -1){
|
|
||||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
|
||||||
let rate = Number(num.replace("%","")) / 100
|
|
||||||
return this.windowHeight * rate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num ? uni.upx2px(Number(num)) : 0
|
|
||||||
},
|
|
||||||
//注册列表滚动事件,用于下拉刷新和上拉加载
|
|
||||||
scroll(e) {
|
|
||||||
this.mescroll.scroll(e.detail, () => {
|
|
||||||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 点击空布局的按钮回调
|
|
||||||
emptyClick() {
|
|
||||||
this.$emit('emptyclick', this.mescroll)
|
|
||||||
},
|
|
||||||
// 点击回到顶部的按钮回调
|
|
||||||
toTopClick() {
|
|
||||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
|
||||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
|
||||||
},
|
|
||||||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
|
|
||||||
setClientHeight() {
|
|
||||||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
|
|
||||||
this.isExec = true; // 避免多次获取
|
|
||||||
this.$nextTick(() => { // 确保dom已渲染
|
|
||||||
let query = uni.createSelectorQuery();
|
|
||||||
// #ifndef MP-ALIPAY
|
|
||||||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
|
|
||||||
// #endif
|
|
||||||
let view = query.select('#' + this.viewId);
|
|
||||||
view.boundingClientRect(data => {
|
|
||||||
this.isExec = false;
|
|
||||||
if (data) {
|
|
||||||
this.mescroll.setClientHeight(data.height);
|
|
||||||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
|
|
||||||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setClientHeight()
|
|
||||||
}, this.clientNum * 100)
|
|
||||||
}
|
|
||||||
}).exec();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
|
||||||
created() {
|
|
||||||
let vm = this;
|
|
||||||
|
|
||||||
let diyOption = {
|
|
||||||
// 下拉刷新的配置
|
|
||||||
down: {
|
|
||||||
inOffset() {
|
|
||||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
|
||||||
},
|
|
||||||
outOffset() {
|
|
||||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
|
||||||
},
|
|
||||||
onMoving(mescroll, rate, downHight) {
|
|
||||||
// 下拉过程中的回调,滑动过程一直在执行;
|
|
||||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
|
||||||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
|
|
||||||
},
|
|
||||||
showLoading(mescroll, downHight) {
|
|
||||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
|
||||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
|
||||||
},
|
|
||||||
endDownScroll() {
|
|
||||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
|
||||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
|
||||||
vm.downResetTimer && clearTimeout(vm.downResetTimer)
|
|
||||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
|
|
||||||
if(vm.downLoadType===4) vm.downLoadType = 0
|
|
||||||
},300)
|
|
||||||
},
|
|
||||||
// 派发下拉刷新的回调
|
|
||||||
callback: function(mescroll) {
|
|
||||||
vm.$emit('down', mescroll)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 上拉加载的配置
|
|
||||||
up: {
|
|
||||||
// 显示加载中的回调
|
|
||||||
showLoading() {
|
|
||||||
vm.upLoadType = 1;
|
|
||||||
},
|
|
||||||
// 显示无更多数据的回调
|
|
||||||
showNoMore() {
|
|
||||||
vm.upLoadType = 2;
|
|
||||||
},
|
|
||||||
// 隐藏上拉加载的回调
|
|
||||||
hideUpScroll(mescroll) {
|
|
||||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
|
||||||
},
|
|
||||||
// 空布局
|
|
||||||
empty: {
|
|
||||||
onShow(isShow) { // 显示隐藏的回调
|
|
||||||
vm.isShowEmpty = isShow;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 回到顶部
|
|
||||||
toTop: {
|
|
||||||
onShow(isShow) { // 显示隐藏的回调
|
|
||||||
vm.isShowToTop = isShow;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 派发上拉加载的回调
|
|
||||||
callback: function(mescroll) {
|
|
||||||
vm.$emit('up', mescroll);
|
|
||||||
// 更新容器的高度 (多mescroll的情况)
|
|
||||||
vm.setClientHeight()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置
|
|
||||||
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
|
|
||||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
|
||||||
|
|
||||||
// 初始化MeScroll对象
|
|
||||||
vm.mescroll = new MeScroll(myOption);
|
|
||||||
vm.mescroll.viewId = vm.viewId; // 附带id
|
|
||||||
// init回调mescroll对象
|
|
||||||
vm.$emit('init', vm.mescroll);
|
|
||||||
|
|
||||||
// 设置高度
|
|
||||||
const sys = uni.getSystemInfoSync();
|
|
||||||
if(sys.windowTop) vm.windowTop = sys.windowTop;
|
|
||||||
if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
|
||||||
if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
|
||||||
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
|
||||||
// 使down的bottomOffset生效
|
|
||||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
|
||||||
|
|
||||||
// 因为使用的是scrollview,这里需自定义scrollTo
|
|
||||||
vm.mescroll.resetScrollTo((y, t) => {
|
|
||||||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
|
|
||||||
if(typeof y === 'string'){ // 第一个参数如果为字符串,则使用scroll-into-view
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
// 微信小程序暂不支持slot里面的scroll-into-view,只能计算位置实现
|
|
||||||
uni.createSelectorQuery().select('#'+vm.viewId).boundingClientRect(function(rect){
|
|
||||||
let mescrollTop = rect.top // mescroll到顶部的距离
|
|
||||||
uni.createSelectorQuery().select('#'+y).boundingClientRect(function(rect){
|
|
||||||
let curY = vm.mescroll.getScrollTop()
|
|
||||||
let top = rect.top - mescrollTop
|
|
||||||
top += curY
|
|
||||||
if(!vm.isFixed) top -= vm.numTop
|
|
||||||
vm.scrollTop = curY;
|
|
||||||
vm.$nextTick(function() {
|
|
||||||
vm.scrollTop = top
|
|
||||||
})
|
|
||||||
}).exec()
|
|
||||||
}).exec()
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
if (vm.scrollToViewId != y) {
|
|
||||||
vm.scrollToViewId = y;
|
|
||||||
} else{
|
|
||||||
vm.scrollToViewId = ''; // scrollToViewId必须变化才会生效,所以此处先置空再赋值
|
|
||||||
vm.$nextTick(function(){
|
|
||||||
vm.scrollToViewId = y;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let curY = vm.mescroll.getScrollTop()
|
|
||||||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
|
|
||||||
vm.scrollTop = curY;
|
|
||||||
vm.$nextTick(function() {
|
|
||||||
vm.scrollTop = y
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
|
|
||||||
vm.scrollTop = step
|
|
||||||
}, t)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
|
||||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
|
||||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// 设置容器的高度
|
|
||||||
this.setClientHeight()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@import "./mescroll-uni.css";
|
|
||||||
@import "./components/mescroll-down.css";
|
|
||||||
@import './components/mescroll-up.css';
|
|
||||||
</style>
|
|
|
@ -1,23 +0,0 @@
|
||||||
/**
|
|
||||||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
|
|
||||||
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
|
|
||||||
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
|
|
||||||
*/
|
|
||||||
const MescrollCompMixin = {
|
|
||||||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|
||||||
onPageScroll(e) {
|
|
||||||
let item = this.$refs["mescrollItem"];
|
|
||||||
if(item && item.mescroll) item.mescroll.onPageScroll(e);
|
|
||||||
},
|
|
||||||
onReachBottom() {
|
|
||||||
let item = this.$refs["mescrollItem"];
|
|
||||||
if(item && item.mescroll) item.mescroll.onReachBottom();
|
|
||||||
},
|
|
||||||
// 当down的native: true时, 还需传递此方法进到子组件
|
|
||||||
onPullDownRefresh(){
|
|
||||||
let item = this.$refs["mescrollItem"];
|
|
||||||
if(item && item.mescroll) item.mescroll.onPullDownRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MescrollCompMixin;
|
|
|
@ -1,51 +0,0 @@
|
||||||
/**
|
|
||||||
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
|
|
||||||
*/
|
|
||||||
const MescrollMoreItemMixin = {
|
|
||||||
// 支付宝小程序不支持props的mixin,需写在具体的页面中
|
|
||||||
// #ifndef MP-ALIPAY
|
|
||||||
props:{
|
|
||||||
i: Number, // 每个tab页的专属下标
|
|
||||||
index: { // 当前tab的下标
|
|
||||||
type: Number,
|
|
||||||
default(){
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
downOption:{
|
|
||||||
auto:false // 不自动加载
|
|
||||||
},
|
|
||||||
upOption:{
|
|
||||||
auto:false // 不自动加载
|
|
||||||
},
|
|
||||||
isInit: false // 当前tab是否已初始化
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch:{
|
|
||||||
// 监听下标的变化
|
|
||||||
index(val){
|
|
||||||
if (this.i === val && !this.isInit) {
|
|
||||||
this.isInit = true; // 标记为true
|
|
||||||
this.mescroll && this.mescroll.triggerDownScroll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
|
|
||||||
mescrollInit(mescroll) {
|
|
||||||
this.mescroll = mescroll;
|
|
||||||
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
|
|
||||||
// 自动加载当前tab的数据
|
|
||||||
if(this.i === this.index){
|
|
||||||
this.isInit = true; // 标记为true
|
|
||||||
this.mescroll.triggerDownScroll();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MescrollMoreItemMixin;
|
|
|
@ -1,56 +0,0 @@
|
||||||
/**
|
|
||||||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
|
|
||||||
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
|
|
||||||
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
|
|
||||||
*/
|
|
||||||
const MescrollMoreMixin = {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tabIndex: 0 // 当前tab下标
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|
||||||
onPageScroll(e) {
|
|
||||||
let mescroll = this.getMescroll(this.tabIndex);
|
|
||||||
mescroll && mescroll.onPageScroll(e);
|
|
||||||
},
|
|
||||||
onReachBottom() {
|
|
||||||
let mescroll = this.getMescroll(this.tabIndex);
|
|
||||||
mescroll && mescroll.onReachBottom();
|
|
||||||
},
|
|
||||||
// 当down的native: true时, 还需传递此方法进到子组件
|
|
||||||
onPullDownRefresh(){
|
|
||||||
let mescroll = this.getMescroll(this.tabIndex);
|
|
||||||
mescroll && mescroll.onPullDownRefresh();
|
|
||||||
},
|
|
||||||
methods:{
|
|
||||||
// 根据下标获取对应子组件的mescroll
|
|
||||||
getMescroll(i){
|
|
||||||
if(!this.mescrollItems) this.mescrollItems = [];
|
|
||||||
if(!this.mescrollItems[i]) {
|
|
||||||
// v-for中的refs
|
|
||||||
let vForItem = this.$refs["mescrollItem"];
|
|
||||||
if(vForItem){
|
|
||||||
this.mescrollItems[i] = vForItem[i]
|
|
||||||
}else{
|
|
||||||
// 普通的refs,不可重复
|
|
||||||
this.mescrollItems[i] = this.$refs["mescrollItem"+i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let item = this.mescrollItems[i]
|
|
||||||
return item ? item.mescroll : null
|
|
||||||
},
|
|
||||||
// 切换tab,恢复滚动条位置
|
|
||||||
tabChange(i){
|
|
||||||
let mescroll = this.getMescroll(i);
|
|
||||||
if(mescroll){
|
|
||||||
// 延时(比$nextTick靠谱一些),确保元素已渲染
|
|
||||||
setTimeout(()=>{
|
|
||||||
mescroll.scrollTo(mescroll.getScrollTop(),0)
|
|
||||||
},30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MescrollMoreMixin;
|
|
|
@ -1,23 +0,0 @@
|
||||||
// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果, 适用于h5和renderjs (下拉刷新时禁止)
|
|
||||||
const bounce = {
|
|
||||||
// false: 禁止bounce; true:允许bounce
|
|
||||||
setBounce: function(isBounce){
|
|
||||||
window.$isMescrollBounce = isBounce
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 引入即自动初始化 (仅初始化一次)
|
|
||||||
if(window && window.$isMescrollBounce == null){
|
|
||||||
// 是否允许bounce, 默认允许
|
|
||||||
window.$isMescrollBounce = true
|
|
||||||
// 每次点击时重置bounce
|
|
||||||
window.addEventListener('touchstart', function(){
|
|
||||||
window.$isMescrollBounce = true
|
|
||||||
}, {passive: true})
|
|
||||||
// 滑动中标记是否禁止bounce (如:下拉刷新时禁止)
|
|
||||||
window.addEventListener('touchmove', function(e){
|
|
||||||
!window.$isMescrollBounce && e.preventDefault() // 禁止bounce
|
|
||||||
}, {passive: false})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default bounce;
|
|
|
@ -1,102 +0,0 @@
|
||||||
// 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信
|
|
||||||
const WxsMixin = {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
// 传入wxs视图层的数据 (响应式)
|
|
||||||
wxsProp: {
|
|
||||||
optDown:{}, // 下拉刷新的配置
|
|
||||||
scrollTop:0, // 滚动条的距离
|
|
||||||
bodyHeight:0, // body的高度
|
|
||||||
isDownScrolling:false, // 是否正在下拉刷新中
|
|
||||||
isUpScrolling:false, // 是否正在上拉加载中
|
|
||||||
isScrollBody:true, // 是否为mescroll-body滚动
|
|
||||||
isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新
|
|
||||||
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 标记调用wxs视图层的方法
|
|
||||||
callProp: {
|
|
||||||
callType: '', // 方法名
|
|
||||||
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs)
|
|
||||||
// #ifndef MP-WEIXIN || APP-PLUS || H5
|
|
||||||
wxsBiz: {
|
|
||||||
//注册列表touchstart事件,用于下拉刷新
|
|
||||||
touchstartEvent: e=> {
|
|
||||||
this.mescroll.touchstartEvent(e);
|
|
||||||
},
|
|
||||||
//注册列表touchmove事件,用于下拉刷新
|
|
||||||
touchmoveEvent: e=> {
|
|
||||||
this.mescroll.touchmoveEvent(e);
|
|
||||||
},
|
|
||||||
//注册列表touchend事件,用于下拉刷新
|
|
||||||
touchendEvent: e=> {
|
|
||||||
this.mescroll.touchendEvent(e);
|
|
||||||
},
|
|
||||||
propObserver(){}, // 抹平wxs的写法
|
|
||||||
callObserver(){} // 抹平wxs的写法
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js)
|
|
||||||
// #ifndef APP-PLUS || H5
|
|
||||||
renderBiz: {
|
|
||||||
propObserver(){} // 抹平renderjs的写法
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// wxs视图层调用逻辑层的回调
|
|
||||||
wxsCall(msg){
|
|
||||||
if(msg.type === 'setWxsProp'){
|
|
||||||
// 更新wxsProp数据 (值改变才触发更新)
|
|
||||||
this.wxsProp = {
|
|
||||||
optDown: this.mescroll.optDown,
|
|
||||||
scrollTop: this.mescroll.getScrollTop(),
|
|
||||||
bodyHeight: this.mescroll.getBodyHeight(),
|
|
||||||
isDownScrolling: this.mescroll.isDownScrolling,
|
|
||||||
isUpScrolling: this.mescroll.isUpScrolling,
|
|
||||||
isUpBoth: this.mescroll.optUp.isBoth,
|
|
||||||
isScrollBody:this.mescroll.isScrollBody,
|
|
||||||
t: Date.now()
|
|
||||||
}
|
|
||||||
}else if(msg.type === 'setLoadType'){
|
|
||||||
// 设置inOffset,outOffset的状态
|
|
||||||
this.downLoadType = msg.downLoadType
|
|
||||||
}else if(msg.type === 'triggerDownScroll'){
|
|
||||||
// 主动触发下拉刷新
|
|
||||||
this.mescroll.triggerDownScroll();
|
|
||||||
}else if(msg.type === 'endDownScroll'){
|
|
||||||
// 结束下拉刷新
|
|
||||||
this.mescroll.endDownScroll();
|
|
||||||
}else if(msg.type === 'triggerUpScroll'){
|
|
||||||
// 主动触发上拉加载
|
|
||||||
this.mescroll.triggerUpScroll(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// #ifdef MP-WEIXIN || APP-PLUS || H5
|
|
||||||
// 配置主动触发wxs显示加载进度的回调
|
|
||||||
this.mescroll.optDown.afterLoading = ()=>{
|
|
||||||
this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
|
||||||
}
|
|
||||||
// 配置主动触发wxs隐藏加载进度的回调
|
|
||||||
this.mescroll.optDown.afterEndDownScroll = ()=>{
|
|
||||||
this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
|
||||||
setTimeout(()=>{
|
|
||||||
if(this.downLoadType === 4 || this.downLoadType === 0){
|
|
||||||
this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
|
||||||
}
|
|
||||||
},320)
|
|
||||||
}
|
|
||||||
// 初始化wxs的数据
|
|
||||||
this.wxsCall({type: 'setWxsProp'})
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WxsMixin;
|
|
|
@ -1,92 +0,0 @@
|
||||||
// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce
|
|
||||||
// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止)
|
|
||||||
// https://uniapp.dcloud.io/frame?id=renderjs
|
|
||||||
|
|
||||||
// 与wxs的me实例一致
|
|
||||||
var me = {}
|
|
||||||
|
|
||||||
// 初始化window对象的touch事件 (仅初始化一次)
|
|
||||||
if(window && !window.$mescrollRenderInit){
|
|
||||||
window.$mescrollRenderInit = true
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('touchstart', function(e){
|
|
||||||
if (me.disabled()) return;
|
|
||||||
me.startPoint = me.getPoint(e); // 记录起点
|
|
||||||
}, {passive: true})
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('touchmove', function(e){
|
|
||||||
if (me.disabled()) return;
|
|
||||||
if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce
|
|
||||||
|
|
||||||
var curPoint = me.getPoint(e); // 当前点
|
|
||||||
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|
||||||
// 向下拉
|
|
||||||
if (moveY > 0) {
|
|
||||||
// 可下拉的条件
|
|
||||||
if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) {
|
|
||||||
|
|
||||||
// 只有touch在mescroll的view上面,才禁止bounce
|
|
||||||
var el = e.target;
|
|
||||||
var isMescrollTouch = false;
|
|
||||||
while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") {
|
|
||||||
var cls = el.classList;
|
|
||||||
if (cls && cls.contains('mescroll-render-touch')) {
|
|
||||||
isMescrollTouch = true
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
el = el.parentNode; // 继续检查其父元素
|
|
||||||
}
|
|
||||||
// 禁止bounce (不会对swiper和iOS侧滑返回造成影响)
|
|
||||||
if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {passive: false})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 获取滚动条的位置 */
|
|
||||||
me.getScrollTop = function() {
|
|
||||||
return me.scrollTop || 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 是否禁用下拉刷新 */
|
|
||||||
me.disabled = function(){
|
|
||||||
return !me.optDown || !me.optDown.use || me.optDown.native
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 根据点击滑动事件获取第一个手指的坐标 */
|
|
||||||
me.getPoint = function(e) {
|
|
||||||
if (!e) {
|
|
||||||
return {x: 0,y: 0}
|
|
||||||
}
|
|
||||||
if (e.touches && e.touches[0]) {
|
|
||||||
return {x: e.touches[0].pageX,y: e.touches[0].pageY}
|
|
||||||
} else if (e.changedTouches && e.changedTouches[0]) {
|
|
||||||
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
|
|
||||||
} else {
|
|
||||||
return {x: e.clientX,y: e.clientY}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 监听逻辑层数据的变化 (实时更新数据)
|
|
||||||
*/
|
|
||||||
function propObserver(wxsProp) {
|
|
||||||
me.optDown = wxsProp.optDown
|
|
||||||
me.scrollTop = wxsProp.scrollTop
|
|
||||||
me.isDownScrolling = wxsProp.isDownScrolling
|
|
||||||
me.isUpScrolling = wxsProp.isUpScrolling
|
|
||||||
me.isUpBoth = wxsProp.isUpBoth
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 导出模块 */
|
|
||||||
const renderBiz = {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
propObserver: propObserver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default renderBiz;
|
|
|
@ -1,267 +0,0 @@
|
||||||
// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
|
|
||||||
// https://uniapp.dcloud.io/frame?id=wxs
|
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
|
|
||||||
|
|
||||||
// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
|
|
||||||
var me = {}
|
|
||||||
|
|
||||||
// ------ 自定义下拉刷新动画 start ------
|
|
||||||
|
|
||||||
/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */
|
|
||||||
me.onMoving = function (ins, rate, downHight){
|
|
||||||
ins.requestAnimationFrame(function () {
|
|
||||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
|
||||||
transform: 'translateY(' + downHight + 'px)',
|
|
||||||
transition: ''
|
|
||||||
})
|
|
||||||
// 环形进度条
|
|
||||||
var progress = ins.selectComponent('.mescroll-wxs-progress')
|
|
||||||
progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示下拉刷新进度 */
|
|
||||||
me.showLoading = function (ins){
|
|
||||||
me.downHight = me.optDown.offset
|
|
||||||
ins.requestAnimationFrame(function () {
|
|
||||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
|
||||||
transform: 'translateY(' + me.downHight + 'px)',
|
|
||||||
transition: 'transform 300ms'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结束下拉 */
|
|
||||||
me.endDownScroll = function (ins){
|
|
||||||
me.downHight = 0;
|
|
||||||
me.isDownScrolling = false;
|
|
||||||
ins.requestAnimationFrame(function () {
|
|
||||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
|
||||||
transform: 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
|
|
||||||
transition: 'transform 300ms'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
|
|
||||||
me.clearTransform = function (ins){
|
|
||||||
ins.requestAnimationFrame(function () {
|
|
||||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
|
||||||
transform: '',
|
|
||||||
transition: ''
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------ 自定义下拉刷新动画 end ------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 监听逻辑层数据的变化 (实时更新数据)
|
|
||||||
*/
|
|
||||||
function propObserver(wxsProp) {
|
|
||||||
me.optDown = wxsProp.optDown
|
|
||||||
me.scrollTop = wxsProp.scrollTop
|
|
||||||
me.bodyHeight = wxsProp.bodyHeight
|
|
||||||
me.isDownScrolling = wxsProp.isDownScrolling
|
|
||||||
me.isUpScrolling = wxsProp.isUpScrolling
|
|
||||||
me.isUpBoth = wxsProp.isUpBoth
|
|
||||||
me.isScrollBody = wxsProp.isScrollBody
|
|
||||||
me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 监听逻辑层数据的变化 (调用wxs的方法)
|
|
||||||
*/
|
|
||||||
function callObserver(callProp, oldValue, ins) {
|
|
||||||
if (me.disabled()) return;
|
|
||||||
if(callProp.callType){
|
|
||||||
// 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
|
|
||||||
if(callProp.callType === 'showLoading'){
|
|
||||||
me.showLoading(ins)
|
|
||||||
}else if(callProp.callType === 'endDownScroll'){
|
|
||||||
me.endDownScroll(ins)
|
|
||||||
}else if(callProp.callType === 'clearTransform'){
|
|
||||||
me.clearTransform(ins)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* touch事件
|
|
||||||
*/
|
|
||||||
function touchstartEvent(e, ins) {
|
|
||||||
if (me.disabled()) return true;
|
|
||||||
|
|
||||||
me.downHight = 0; // 下拉的距离
|
|
||||||
me.startPoint = me.getPoint(e); // 记录起点
|
|
||||||
me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
|
|
||||||
me.startAngle = 0; // 初始角度
|
|
||||||
me.lastPoint = me.startPoint; // 重置上次move的点
|
|
||||||
me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
|
|
||||||
me.inTouchend = false; // 标记不是touchend
|
|
||||||
|
|
||||||
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
|
|
||||||
}
|
|
||||||
|
|
||||||
function touchmoveEvent(e, ins) {
|
|
||||||
var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
|
|
||||||
|
|
||||||
if (me.disabled()) return isPrevent;
|
|
||||||
|
|
||||||
var scrollTop = me.getScrollTop(); // 当前滚动条的距离
|
|
||||||
var curPoint = me.getPoint(e); // 当前点
|
|
||||||
|
|
||||||
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|
||||||
|
|
||||||
// 向下拉 && 在顶部
|
|
||||||
// mescroll-body,直接判定在顶部即可
|
|
||||||
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
|
|
||||||
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
|
|
||||||
if (moveY > 0 && (
|
|
||||||
(me.isScrollBody && scrollTop <= 0)
|
|
||||||
||
|
|
||||||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
|
|
||||||
)) {
|
|
||||||
// 可下拉的条件
|
|
||||||
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
|
|
||||||
me.isUpBoth))) {
|
|
||||||
|
|
||||||
// 下拉的角度是否在配置的范围内
|
|
||||||
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
|
|
||||||
if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
|
|
||||||
|
|
||||||
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
|
|
||||||
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
|
|
||||||
me.inTouchend = true; // 标记执行touchend
|
|
||||||
touchendEvent(e, ins); // 提前触发touchend
|
|
||||||
return isPrevent;
|
|
||||||
}
|
|
||||||
|
|
||||||
isPrevent = false // 小程序是return false
|
|
||||||
|
|
||||||
var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
|
|
||||||
|
|
||||||
// 下拉距离 < 指定距离
|
|
||||||
if (me.downHight < me.optDown.offset) {
|
|
||||||
if (me.movetype !== 1) {
|
|
||||||
me.movetype = 1; // 加入标记,保证只执行一次
|
|
||||||
// me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
|
|
||||||
me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
|
|
||||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
|
||||||
}
|
|
||||||
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
|
|
||||||
|
|
||||||
// 指定距离 <= 下拉距离
|
|
||||||
} else {
|
|
||||||
if (me.movetype !== 2) {
|
|
||||||
me.movetype = 2; // 加入标记,保证只执行一次
|
|
||||||
// me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
|
|
||||||
me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
|
|
||||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
|
||||||
}
|
|
||||||
if (diff > 0) { // 向下拉
|
|
||||||
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
|
|
||||||
} else { // 向上收
|
|
||||||
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
me.downHight = Math.round(me.downHight) // 取整
|
|
||||||
var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
|
|
||||||
// me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
|
|
||||||
me.onMoving(ins, rate, me.downHight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
me.lastPoint = curPoint; // 记录本次移动的点
|
|
||||||
|
|
||||||
return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
|
|
||||||
}
|
|
||||||
|
|
||||||
function touchendEvent(e, ins) {
|
|
||||||
if (me.disabled()) return true;
|
|
||||||
// 如果下拉区域高度已改变,则需重置回来
|
|
||||||
if (me.isMoveDown) {
|
|
||||||
if (me.downHight >= me.optDown.offset) {
|
|
||||||
// 符合触发刷新的条件
|
|
||||||
me.downHight = me.optDown.offset; // 更新下拉区域高度
|
|
||||||
// me.triggerDownScroll();
|
|
||||||
me.callMethod(ins, {type: 'triggerDownScroll'})
|
|
||||||
} else {
|
|
||||||
// 不符合的话 则重置
|
|
||||||
me.downHight = 0;
|
|
||||||
// me.optDown.endDownScroll && me.optDown.endDownScroll(me);
|
|
||||||
me.callMethod(ins, {type: 'endDownScroll'})
|
|
||||||
}
|
|
||||||
me.movetype = 0;
|
|
||||||
me.isMoveDown = false;
|
|
||||||
} else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
|
|
||||||
var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|
||||||
// 上滑
|
|
||||||
if (isScrollUp) {
|
|
||||||
// 需检查滑动的角度
|
|
||||||
var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
|
|
||||||
if (angle > 80) {
|
|
||||||
// 检查并触发上拉
|
|
||||||
// me.triggerUpScroll(true);
|
|
||||||
me.callMethod(ins, {type: 'triggerUpScroll'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 是否禁用下拉刷新 */
|
|
||||||
me.disabled = function(){
|
|
||||||
return !me.optDown || !me.optDown.use || me.optDown.native
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 根据点击滑动事件获取第一个手指的坐标 */
|
|
||||||
me.getPoint = function(e) {
|
|
||||||
if (!e) {
|
|
||||||
return {x: 0,y: 0}
|
|
||||||
}
|
|
||||||
if (e.touches && e.touches[0]) {
|
|
||||||
return {x: e.touches[0].pageX,y: e.touches[0].pageY}
|
|
||||||
} else if (e.changedTouches && e.changedTouches[0]) {
|
|
||||||
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
|
|
||||||
} else {
|
|
||||||
return {x: e.clientX,y: e.clientY}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 计算两点之间的角度: 区间 [0,90]*/
|
|
||||||
me.getAngle = function (p1, p2) {
|
|
||||||
var x = Math.abs(p1.x - p2.x);
|
|
||||||
var y = Math.abs(p1.y - p2.y);
|
|
||||||
var z = Math.sqrt(x * x + y * y);
|
|
||||||
var angle = 0;
|
|
||||||
if (z !== 0) {
|
|
||||||
angle = Math.asin(y / z) / Math.PI * 180;
|
|
||||||
}
|
|
||||||
return angle
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 获取滚动条的位置 */
|
|
||||||
me.getScrollTop = function() {
|
|
||||||
return me.scrollTop || 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 获取body的高度 */
|
|
||||||
me.getBodyHeight = function() {
|
|
||||||
return me.bodyHeight || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 调用逻辑层的方法 */
|
|
||||||
me.callMethod = function(ins, param) {
|
|
||||||
if(ins) ins.callMethod('wxsCall', param)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 导出模块 */
|
|
||||||
module.exports = {
|
|
||||||
propObserver: propObserver,
|
|
||||||
callObserver: callObserver,
|
|
||||||
touchstartEvent: touchstartEvent,
|
|
||||||
touchmoveEvent: touchmoveEvent,
|
|
||||||
touchendEvent: touchendEvent
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
<template>
|
|
||||||
<uni-popup ref="popup" type="bottom">
|
|
||||||
<view class="content">
|
|
||||||
<view v-if="data.title" class="cell b-b center title">
|
|
||||||
<text >{{ data.title }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="cell b-b center" v-for="(item, index) in data.list" :key="index" @click="confirm(item)">
|
|
||||||
<text>{{ item.text }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="cell center cancel-btn" @click="close">
|
|
||||||
<text>取消</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</uni-popup>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 底部选择菜单
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
data: {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//选择回调
|
|
||||||
confirm(item){
|
|
||||||
this.$util.throttle(()=>{
|
|
||||||
this.$emit('onConfirm', item)
|
|
||||||
})
|
|
||||||
this.close();
|
|
||||||
},
|
|
||||||
open(data){
|
|
||||||
this.data = data;
|
|
||||||
this.$refs.popup.open();
|
|
||||||
},
|
|
||||||
close(){
|
|
||||||
this.$refs.popup.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.content{
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 16rpx 16rpx 0 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.cell{
|
|
||||||
min-height: 88rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:after{
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
left: 0;
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 0;
|
|
||||||
content: '';
|
|
||||||
transform: scaleY(.5);
|
|
||||||
border-bottom: 1px solid #f5f5f5;
|
|
||||||
}
|
|
||||||
&:last-child{
|
|
||||||
height: 96rpx;
|
|
||||||
border-top: 12rpx solid #f7f7f7;
|
|
||||||
}
|
|
||||||
&.title{
|
|
||||||
height: 100rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,154 +0,0 @@
|
||||||
<template>
|
|
||||||
<view
|
|
||||||
class="mix-btn-content"
|
|
||||||
:class="{
|
|
||||||
disabled: loading || disabled || dead,
|
|
||||||
}"
|
|
||||||
:style="[
|
|
||||||
{marginTop: marginTop}
|
|
||||||
]"
|
|
||||||
@click="confirm"
|
|
||||||
>
|
|
||||||
<image v-if="loading" class="loading-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAE3UlEQVRoQ82Z7ZEQNwyGrQoCFQQqgFRAqCBcBSEVQCqAqyBJBTkqACoAKsilAo4KQioQ8+zIO16tvbaWM4P/7lrSq2/JkiYeVX2SUnpmLP4SkTez2MkswgbitaN/MQvMTCBo/xcH5IOI/DxDeWEgqvq0cJfLloZV9X1K6VEEiKo+NNpvo5YLAYm4SxSIqt5JKf2TUrqXUvosIncjlosCqbnLTUrpJxH5XDI+AeRlSumF0Qi7YBTIn4VblXLjYgiynggQVcUKWAOrcB6LCK45fKJAYPixQh1rYBWss5wgELIbqZrzSkSIw9AJATEBW1Z5IyIXUSCqShZ7Z/f+Tyk9LBUyiuYMEMyP5n+oMFldYtQiqopLka04OxedBsSsgun/rjC5EZH7o65lqTzT+WTW2CSN00CKtgKCtBXVoGtoHL6/iciVql6llH51glAflliwdEu85QBf7vUEt8SQvPttXMv5a6YJcUy+BrIJUvp2yX9JnQ1apeuVsdZNtwacvi1nx02744GUubwUDuvAGAutpm9ofRXKwOQMdJWta0L9VzAg4123rKGqWBbZyJr5/C4iyLQcD+R5SumPA/NilZci8qpwDx/43cbQ3COncZQD390xRVAkfX8G/+elUj0Q/JWYeNDxVf5BI9cmFFpHW6vWB3ydTEWq3cWFWQyF+nrywRS5i9tq+rVsgtlqKbaUsav9HiD/XVWxABbKSYBfqC9YoJkMmnXEtIJf5sGoJlM3SEeBWOdLhS/jgOuXxKfv5TzdbkE010ETviWH1m0C8YnmrVlhky1biukCyRct8AD0Y0Hs1lzL6hf0yYpPpzaNlqlyYL+PMht1szP/DVvkDPFveWcBYnHg24mWHPRTSx35no5YtqADjZxmEYsQqf1rsYJSy/R7RJaO4BIgrbbkUCYRmeKWqkrrMgoiy7gA6bUlNUCfRMTn+681xnJfVdFwr7PwvC4AAnqWCrU6UROOKvtkZsayVD+qGGL2ZnUPA5QntRYR1jTNLnWU84z/pvj5DEF7NMNArKmjraZ1oAM+NZp6wcwjyFb/nnHbYSCWFmmtyyBfR9eexnrf3ZBGzKKkoT4L2l0gB8MN92c2jdCnNGym0pZCjtp4NM9scLQsO72+aRRDBKcclHMQrssscthN7IAUQ74fbkrerG4gfusPN8afoc63TEyFKK661amNumz9WmmYGsKQ4/e8xA7AYUJr300AqsodVkMIVxt3SSjw8fWtutWJLB92A79VYt/ibLYb1suxh1rrj1sV7fbGpelt7IZHOQdxh9XSSnMECAM/brQrhJUlGzKsQFz7s8aT3Su3L4eLa/sfi+dnhw2fXdZy7Uo3Dip7LVzvXnYtt43cZLhKs9p9SrBxI7t1ex3Uy/XO5MSRb/83a88OEHo8rJxdhonzcUSG8t9uHWkRVlWSQrk429WUIyAWX34ZPrT/rcl0CkjjLXHnGj0gBqZ8NK0+441Y6SwQ1p1lq1IN1kEgfhl+qsiGgVSCtPnKNALErFI+QRym43CL0mghCNDyTYPfmhoMAMG6BH5uTcLviCGLVBYVh69Mo0BqhTW6EwgBMYb5rb078gaBlC8B4a46DMTAEKDXvZ4qAiS7Mu3L1MFqJAVWpr4ytYa1HOF5yiKjDFq91uj9yH9TgZgbLiPB7O3LF2dInShyDo35AAAAAElFTkSuQmCC"></image>
|
|
||||||
<text v-if="icon" class="mix-icon" :class="icon" :style="{fontSize: iconSize + 'rpx'}"></text>
|
|
||||||
<text class="mix-text">{{ text }}</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 按钮组件
|
|
||||||
* @prop text 按钮显示文字
|
|
||||||
* @prop icon 按钮图标
|
|
||||||
* @prop iconSize 按钮显示文字
|
|
||||||
* @prop isConfirm 点击后是否处理loading状态
|
|
||||||
* @prop disabled 按钮禁用
|
|
||||||
* @prop marginTop 按钮上边距
|
|
||||||
*/
|
|
||||||
let stopTimer = null;
|
|
||||||
export default {
|
|
||||||
name: 'MixButton',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
dead: false,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
default: '提交'
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
iconSize: {
|
|
||||||
type: Number,
|
|
||||||
default: 32
|
|
||||||
},
|
|
||||||
isConfirm: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
marginTop: {
|
|
||||||
type: String,
|
|
||||||
default: '0rpx'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
stop(){
|
|
||||||
if(stopTimer){
|
|
||||||
clearTimeout(stopTimer);
|
|
||||||
stopTimer = null;
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
death(){
|
|
||||||
this.loading = false;
|
|
||||||
this.dead = true;
|
|
||||||
},
|
|
||||||
confirm(){
|
|
||||||
if(this.dead){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(this.loading || this.disabled){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(this.isConfirm){
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
stopTimer = setTimeout(()=>{
|
|
||||||
this.loading = false;
|
|
||||||
clearTimeout(stopTimer);
|
|
||||||
stopTimer = null;
|
|
||||||
}, 10000)
|
|
||||||
}
|
|
||||||
this.$emit('onConfirm');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='scss'>
|
|
||||||
.mix-btn-content{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 610rpx;
|
|
||||||
height: 88rpx;
|
|
||||||
margin: 0 auto;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
background-color: $base-color;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:after{
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 25%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 85%;
|
|
||||||
height: 85%;
|
|
||||||
background: linear-gradient(131deg, rgba(255,115,138,1) 0%, rgba(255,83,111,1) 100%);
|
|
||||||
border-radius: 100rpx;
|
|
||||||
opacity: 0.4;
|
|
||||||
filter:blur(10rpx);
|
|
||||||
}
|
|
||||||
&.disabled {
|
|
||||||
opacity: .65;
|
|
||||||
}
|
|
||||||
.mix-text{
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.mix-icon{
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
}
|
|
||||||
.loading-icon{
|
|
||||||
width: 34rpx;
|
|
||||||
height: 34rpx;
|
|
||||||
transform-origin:50% 50%;
|
|
||||||
margin-right: 16rpx;
|
|
||||||
animation: rotate 2s linear infinite;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes rotate{
|
|
||||||
from {
|
|
||||||
transform:rotate(0deg)
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform:rotate(360deg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,113 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mix-get-code" @click="getCode">
|
|
||||||
<view v-if="loading" class="loading">
|
|
||||||
<mix-icon-loading size="28rpx" color="#0083ff"></mix-icon-loading>
|
|
||||||
</view>
|
|
||||||
<text class="text" :class="{disabled: timeDown > 0}">
|
|
||||||
{{ timeDown > 0 ? '重新获取 ' + timeDown + 's' : '获取验证码' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 手机验证码
|
|
||||||
* @prop mobile 手机号
|
|
||||||
* @prop templateCode 短信模版id
|
|
||||||
*/
|
|
||||||
import {checkStr} from '@/common/js/util'
|
|
||||||
export default {
|
|
||||||
//获取手机验证码
|
|
||||||
name: 'MixMobileCode',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
timeDown: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
mobile: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
templateCode: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: String,
|
|
||||||
default: '用户注册' //设置支付密码
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//获取验证码
|
|
||||||
async getCode(){
|
|
||||||
if(this.timeDown > 0){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$util.throttle(()=>{
|
|
||||||
const mobile = this.mobile || this.$store.state.userInfo.username;;
|
|
||||||
if(!checkStr(mobile, 'mobile')){
|
|
||||||
this.$util.msg('手机号码格式不正确');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loading = true;
|
|
||||||
this.$request('smsCode', 'send', {
|
|
||||||
mobile,
|
|
||||||
action: this.action, //uni短信必填
|
|
||||||
TemplateCode: this.templateCode, //阿里云必填
|
|
||||||
}).then(response=>{
|
|
||||||
this.$util.msg(response.msg);
|
|
||||||
this.loading = false;
|
|
||||||
if(response.status === 1){
|
|
||||||
this.countDown(60);
|
|
||||||
}
|
|
||||||
}).catch(err=>{
|
|
||||||
this.$util.msg('验证码发送失败');
|
|
||||||
this.loading = false;
|
|
||||||
console.log(err);
|
|
||||||
})
|
|
||||||
}, 2000)
|
|
||||||
},
|
|
||||||
//倒计时
|
|
||||||
countDown(timer){
|
|
||||||
timer --;
|
|
||||||
this.timeDown = timer;
|
|
||||||
if(timer > 0){
|
|
||||||
setTimeout(()=>{
|
|
||||||
this.countDown(timer);
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.mix-get-code{
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
height: 36rpx;
|
|
||||||
|
|
||||||
&:before{
|
|
||||||
content: '';
|
|
||||||
width: 0;
|
|
||||||
height: 40;
|
|
||||||
border-right: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
.loading{
|
|
||||||
margin-right: 8rpx;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
line-height: 28rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #40a2ff;
|
|
||||||
|
|
||||||
&.disabled{
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,209 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mix-empty" :style="{backgroundColor: backgroundColor}">
|
|
||||||
<view v-if="type==='cart'" class="cart column center">
|
|
||||||
<image class="icon" src="/static/empty/cart.png"></image>
|
|
||||||
<text class="title">{{ hasLogin ? '空空如也' : '先去登录嘛' }}</text>
|
|
||||||
<text class="text">别忘了买点什么犒赏一下自己哦</text>
|
|
||||||
<view class="btn center" @click="onCartBtnClick">
|
|
||||||
<text>{{ hasLogin ? '随便逛逛' : '去登录' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-else-if="type==='address'" class="address column center">
|
|
||||||
<image class="icon" src="/static/empty/address.png"></image>
|
|
||||||
<text class="text">找不到您的收货地址哦,先去添加一个吧~</text>
|
|
||||||
<view class="btn center" @click="navTo('manage')">
|
|
||||||
<text class="mix-icon icon-jia2"></text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-else-if="type==='favorite'" class="favorite column center">
|
|
||||||
<image class="icon" src="/static/empty/favorite.png"></image>
|
|
||||||
<text class="text">收藏夹空空的,先去逛逛吧~</text>
|
|
||||||
<view class="btn center" @click="switchTab('/pages/tabbar/home')">
|
|
||||||
<text>随便逛逛</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-else class="default column center">
|
|
||||||
<image class="icon" src="/static/empty/default.png"></image>
|
|
||||||
<text class="text">{{ text }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 缺省显示
|
|
||||||
* @prop text 缺省文字提示
|
|
||||||
* @prop type 缺省类型
|
|
||||||
* @prop backgroundColor 缺省页面背景色
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
computed: {
|
|
||||||
hasLogin(){
|
|
||||||
return !!this.$store.getters.hasLogin;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
default: '暂时没有数据'
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
backgroundColor: {
|
|
||||||
type: String,
|
|
||||||
default: 'rgba(0,0,0,0)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onCartBtnClick(){
|
|
||||||
if(this.hasLogin){
|
|
||||||
uni.switchTab({
|
|
||||||
url: '/pages/tabbar/home'
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
this.navTo('/pages/auth/login');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
switchTab(url){
|
|
||||||
uni.switchTab({
|
|
||||||
url
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.mix-empty{
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
animation: show .5s 1;
|
|
||||||
}
|
|
||||||
@keyframes show{
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.default{
|
|
||||||
padding-top: 26vh;
|
|
||||||
/* #ifdef H5 */
|
|
||||||
padding-top: 30vh;
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
.icon{
|
|
||||||
width: 460rpx;
|
|
||||||
height: 342rpx;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
margin-top: 10rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.cart{
|
|
||||||
padding-top: 14vh;
|
|
||||||
/* #ifdef H5 */
|
|
||||||
padding-top: 18vh;
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
.icon{
|
|
||||||
width: 320rpx;
|
|
||||||
height: 320rpx;
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
margin: 50rpx 0 26rpx;
|
|
||||||
font-size: 34rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
.btn{
|
|
||||||
width: 320rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
margin-top: 80rpx;
|
|
||||||
text-indent: 2rpx;
|
|
||||||
letter-spacing: 2rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
background: linear-gradient(to bottom right, #ffb2bf, $base-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.address{
|
|
||||||
padding-top: 6vh;
|
|
||||||
/* #ifdef H5 */
|
|
||||||
padding-top: 10vh;
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
.icon{
|
|
||||||
width: 380rpx;
|
|
||||||
height: 380rpx;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
width: 400rpx;
|
|
||||||
margin-top: 40rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.btn{
|
|
||||||
position: fixed;
|
|
||||||
left: 50%;
|
|
||||||
bottom: 120rpx;
|
|
||||||
width: 110rpx;
|
|
||||||
height: 110rpx;
|
|
||||||
background-color: $base-color;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
box-shadow: 2rpx 2rpx 10rpx rgba(255, 83, 111, .5);
|
|
||||||
}
|
|
||||||
.icon-jia2{
|
|
||||||
font-size: 50rpx;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.favorite{
|
|
||||||
padding-top: 6vh;
|
|
||||||
/* #ifdef H5 */
|
|
||||||
padding-top: 10vh;
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
.icon{
|
|
||||||
width: 360rpx;
|
|
||||||
height: 360rpx;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
width: 400rpx;
|
|
||||||
margin-top: 40rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.btn{
|
|
||||||
width: 320rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
margin-top: 40rpx;
|
|
||||||
text-indent: 2rpx;
|
|
||||||
letter-spacing: 2rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
background: linear-gradient(to bottom right, #ffb2bf, $base-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mix-icon-loading">
|
|
||||||
<view
|
|
||||||
class="loading-icon"
|
|
||||||
:style="{
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
borderRightColor: color
|
|
||||||
}"
|
|
||||||
></view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 菊花loading小图标
|
|
||||||
* @prop size 尺寸,单位rpx
|
|
||||||
* @prop color 颜色
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
name: 'MixIconLoading',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
size: {
|
|
||||||
type: String,
|
|
||||||
default: '26rpx'
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: '#999'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='scss'>
|
|
||||||
.mix-icon-loading{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.loading-icon{
|
|
||||||
width: 28rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
border: 4rpx solid #ddd;
|
|
||||||
animation: mix-loading 1.8s steps(12) infinite;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
}
|
|
||||||
@keyframes mix-loading{
|
|
||||||
from {
|
|
||||||
transform:rotate(0deg)
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(1turn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,117 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="content">
|
|
||||||
<view class="mix-list-cell" :class="border" @click="onClick" hover-class="cell-hover" :hover-stay-time="50">
|
|
||||||
<text
|
|
||||||
v-if="icon"
|
|
||||||
class="cell-icon mix-icon"
|
|
||||||
:style="[{
|
|
||||||
color: iconColor,
|
|
||||||
}]"
|
|
||||||
:class="icon"
|
|
||||||
></text>
|
|
||||||
<text class="cell-tit clamp">{{ title }}</text>
|
|
||||||
<text v-if="tips" class="cell-tip" :style="{color: tipsColor}">{{ tips }}</text>
|
|
||||||
<text class="cell-more mix-icon"
|
|
||||||
:class="typeList[navigateType]"
|
|
||||||
></text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 简单封装了下, 应用范围比较狭窄,可以在此基础上进行扩展使用
|
|
||||||
* 比如加入image, iconSize可控等
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
typeList: {
|
|
||||||
left: 'icon-zuo',
|
|
||||||
right: 'icon-you',
|
|
||||||
up: 'icon-shang',
|
|
||||||
down: 'icon-xia'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: '标题'
|
|
||||||
},
|
|
||||||
tips: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
tipsColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#999'
|
|
||||||
},
|
|
||||||
navigateType: {
|
|
||||||
type: String,
|
|
||||||
default: 'right'
|
|
||||||
},
|
|
||||||
border: {
|
|
||||||
type: String,
|
|
||||||
default: 'b-b'
|
|
||||||
},
|
|
||||||
hoverClass: {
|
|
||||||
type: String,
|
|
||||||
default: 'cell-hover'
|
|
||||||
},
|
|
||||||
iconColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#333'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onClick(){
|
|
||||||
this.$emit('onClick');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='scss'>
|
|
||||||
.mix-list-cell{
|
|
||||||
display:flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 96rpx;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
position:relative;
|
|
||||||
|
|
||||||
&.cell-hover{
|
|
||||||
background:#fafafa;
|
|
||||||
}
|
|
||||||
&.b-b{
|
|
||||||
&:after{
|
|
||||||
left: 30rpx;
|
|
||||||
border-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.cell-icon{
|
|
||||||
align-self: center;
|
|
||||||
width: 60rpx;
|
|
||||||
font-size: 38rpx;
|
|
||||||
}
|
|
||||||
.cell-more{
|
|
||||||
align-self: center;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-left: 16rpx;
|
|
||||||
}
|
|
||||||
.cell-tit{
|
|
||||||
flex: 1;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #333;
|
|
||||||
margin-right:10rpx;
|
|
||||||
}
|
|
||||||
.cell-tip{
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,60 +0,0 @@
|
||||||
<template>
|
|
||||||
<view>
|
|
||||||
<view v-show="status !== 2" class="mix-load-more center">
|
|
||||||
<image class="loading-icon" src="/static/loading/hamster.gif"></image>
|
|
||||||
<text class="text">{{ textList[status] }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-show="status === 2" class="mix-load-more center">
|
|
||||||
<image class="logo" src="/static/logo-b-w.png"></image>
|
|
||||||
<text class="text">国云网络提供技术支持</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 上划加载更多
|
|
||||||
* @prop {Number} status 0加载前,1加载中,2没有更多
|
|
||||||
* @prop {Array} textList ['加载前提示', '加载中提示', '加载完提示']
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
name: "mix-load-more",
|
|
||||||
props: {
|
|
||||||
status: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
textList: {
|
|
||||||
type: Array,
|
|
||||||
default () {
|
|
||||||
return [
|
|
||||||
'上拉显示更多',
|
|
||||||
'正在加载 ..',
|
|
||||||
'我也是有底线的~'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.mix-load-more{
|
|
||||||
width: 750rpx;
|
|
||||||
height: 110rpx;
|
|
||||||
}
|
|
||||||
.loading-icon{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 68rpx;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
.logo{
|
|
||||||
width: 34rpx;
|
|
||||||
height: 34rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,114 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mix-loading center">
|
|
||||||
<view v-if="!isTimeout" class="center">
|
|
||||||
<view v-if="mask" class="mask" @click.stop.prevent="stopPrevent" @touchmove.stop.prevent="stopPrevent"></view>
|
|
||||||
<!-- 黑底菊花 -->
|
|
||||||
<view v-if="type === 1" class="chry column center">
|
|
||||||
<view class="icon"></view>
|
|
||||||
<text class="tit">{{ title }}</text>
|
|
||||||
</view>
|
|
||||||
<!-- 仓鼠 -->
|
|
||||||
<image v-else-if="type === 2" class="hamster" src="/static/loading/hamster.gif"></image>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* loading
|
|
||||||
* @prop type 1 黑底菊花 2 小胖仓鼠
|
|
||||||
* @prop mask 遮罩层
|
|
||||||
* @prop timeout 超时时间(秒) 默认10s
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
name: 'MixIconLoading',
|
|
||||||
data(){
|
|
||||||
return {
|
|
||||||
isTimeout: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
type: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
mask: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
timeout: {
|
|
||||||
type: Number,
|
|
||||||
default: 10
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: '请稍候'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this._timer = setTimeout(()=>{
|
|
||||||
if(!this.isLoading){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.isTimeout = true;
|
|
||||||
uni.showToast({
|
|
||||||
title: '加载超时,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}, this.timeout * 1000)
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
this._timer && clearTimeout(this._timer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='scss'>
|
|
||||||
.mix-loading{
|
|
||||||
position: fixed;
|
|
||||||
left: 50vw;
|
|
||||||
top: 46vh;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
.mask{
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
.chry{
|
|
||||||
min-width: 120rpx;
|
|
||||||
min-height: 116rpx;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
background-color: rgba(17,17,17,.7);
|
|
||||||
|
|
||||||
.icon{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
animation: mix-loading 1s steps(12) infinite;
|
|
||||||
}
|
|
||||||
.tit{
|
|
||||||
margin: 10rpx 0 6rpx;
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #e9e9e9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes mix-loading{
|
|
||||||
from {
|
|
||||||
transform:rotate(0deg)
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(1turn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.hamster{
|
|
||||||
width: 106rpx;
|
|
||||||
height: 120rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,105 +0,0 @@
|
||||||
<template>
|
|
||||||
<uni-popup ref="popup">
|
|
||||||
<view class="pop-content">
|
|
||||||
<text class="title">{{ title }}</text>
|
|
||||||
<view class="con center">
|
|
||||||
<text class="text">{{ text }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="btn-group row b-t">
|
|
||||||
<view class="btn center" @click="close">
|
|
||||||
<text>{{ cancelText }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="btn center b-l" @click="confirm">
|
|
||||||
<text>{{ confirmText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</uni-popup>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 确认对话框
|
|
||||||
* @prop title 标题
|
|
||||||
* @prop text 提示内容
|
|
||||||
* @prop cancelText 取消按钮文字
|
|
||||||
* @prop confirmText 确认按钮文字
|
|
||||||
* @event onConfirm 确认按钮点击时触发
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
title: String,
|
|
||||||
text: String,
|
|
||||||
cancelText: {
|
|
||||||
type: String,
|
|
||||||
default: '取消'
|
|
||||||
},
|
|
||||||
confirmText: {
|
|
||||||
type: String,
|
|
||||||
default: '确定'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
confirm(){
|
|
||||||
this.$emit('onConfirm');
|
|
||||||
this.close();
|
|
||||||
},
|
|
||||||
open(){
|
|
||||||
this.$refs.popup.open();
|
|
||||||
},
|
|
||||||
close(){
|
|
||||||
this.$refs.popup.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='scss'>
|
|
||||||
.pop-content{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 540rpx;
|
|
||||||
padding-top: 36rpx;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.title{
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333;
|
|
||||||
line-height: 48rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.con{
|
|
||||||
padding: 36rpx 40rpx 54rpx;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
width: 460rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #333;
|
|
||||||
line-height: 40rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.btn-group{
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.btn{
|
|
||||||
flex: 1;
|
|
||||||
height: 88rpx;
|
|
||||||
line-height: 88rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #777;
|
|
||||||
|
|
||||||
&:last-child{
|
|
||||||
color: #007aff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,139 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="nav-bar b-b">
|
|
||||||
<view
|
|
||||||
class="nav-item"
|
|
||||||
v-for="(item, index) in navs"
|
|
||||||
:key="index"
|
|
||||||
@click="navChange(index)"
|
|
||||||
>
|
|
||||||
<view class="tit-wrap">
|
|
||||||
<text class="tit" :class="{'active': current == index}">{{ item.name }}</text>
|
|
||||||
<text v-if="counts.length > index && counts[index] > 0" class="number">{{ counts[index] }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="line" :class="{'line--show': current === index}"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 顶部tab栏
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data(){
|
|
||||||
return {
|
|
||||||
countList: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
navs: {
|
|
||||||
type: Array,
|
|
||||||
default(){
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
current: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
counts: {
|
|
||||||
type: Array,
|
|
||||||
default(){
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
navChange(index){
|
|
||||||
this.$emit('onChange', index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='scss'>
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
view{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
/* #endif */
|
|
||||||
.fill-view{
|
|
||||||
height: 84rpx;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.nav-bar{
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: flex;
|
|
||||||
/* #endif */
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
width: 750rpx;
|
|
||||||
height: 84rpx;
|
|
||||||
background-color: #fff;
|
|
||||||
z-index: 90;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
/* #ifdef H5 */
|
|
||||||
top: var(--window-top);
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
&:after{
|
|
||||||
border-color: #f7f7f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.nav-item{
|
|
||||||
flex: 1;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 84rpx;
|
|
||||||
padding-top: 4rpx;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.tit-wrap{
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.number{
|
|
||||||
position: absolute;
|
|
||||||
right: -20rpx;
|
|
||||||
top: 0px;
|
|
||||||
min-width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
padding: 0 6rpx;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 28rpx;
|
|
||||||
border: 4rpx solid #fff;
|
|
||||||
background-color: $base-color;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.tit{
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.active{
|
|
||||||
color: #ff4443;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.line{
|
|
||||||
width: 34rpx;
|
|
||||||
height: 4rpx;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
background-color: #ff4443;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
&--show{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,180 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="uni-numbox">
|
|
||||||
<view class="uni-numbox-minus"
|
|
||||||
@click="_calcValue('subtract')"
|
|
||||||
>
|
|
||||||
<text class="mix-icon icon--jianhao" :class="minDisabled?'uni-numbox-disabled': ''" ></text>
|
|
||||||
</view>
|
|
||||||
<input
|
|
||||||
class="uni-numbox-value"
|
|
||||||
type="number"
|
|
||||||
:disabled="inputDisabled"
|
|
||||||
:value="inputValue"
|
|
||||||
@blur="_onBlur"
|
|
||||||
>
|
|
||||||
|
|
||||||
<view
|
|
||||||
class="uni-numbox-plus"
|
|
||||||
@click="_calcValue('add')"
|
|
||||||
>
|
|
||||||
<text class="mix-icon icon-jia2" :class="maxDisabled?'uni-numbox-disabled': ''" ></text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* index 当前行下标
|
|
||||||
* value 默认值
|
|
||||||
* min 最小值
|
|
||||||
* max 最大值
|
|
||||||
* step 步进值
|
|
||||||
* disabled 禁用
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
name: 'uni-number-box',
|
|
||||||
props: {
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
min: {
|
|
||||||
type: Number,
|
|
||||||
default: -Infinity
|
|
||||||
},
|
|
||||||
max: {
|
|
||||||
type: Number,
|
|
||||||
default: 99
|
|
||||||
},
|
|
||||||
step: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
inputDisabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
inputValue: this.value,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created(){
|
|
||||||
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
maxDisabled(){
|
|
||||||
return this.inputValue >= this.max;
|
|
||||||
},
|
|
||||||
minDisabled(){
|
|
||||||
return this.inputValue <= this.min;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
inputValue(number) {
|
|
||||||
const data = {
|
|
||||||
number: number,
|
|
||||||
index: this.index
|
|
||||||
}
|
|
||||||
this.$emit('eventChange', data);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
_calcValue(type) {
|
|
||||||
let value = this.inputValue;
|
|
||||||
let newValue = 0;
|
|
||||||
let step = this.step;
|
|
||||||
|
|
||||||
if(type === 'subtract'){
|
|
||||||
newValue = value - step;
|
|
||||||
|
|
||||||
if(newValue < this.min){
|
|
||||||
newValue = this.min
|
|
||||||
if(this.min > 1){
|
|
||||||
this.$api.msg(this.limit_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else if(type === 'add'){
|
|
||||||
newValue = value + step;
|
|
||||||
|
|
||||||
if(newValue > this.max){
|
|
||||||
newValue = this.max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(newValue === value){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.inputValue = newValue;
|
|
||||||
},
|
|
||||||
_onBlur(event) {
|
|
||||||
let value = event.detail.value;
|
|
||||||
let constValue = value;
|
|
||||||
if (!value) {
|
|
||||||
this.inputValue = 0;
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value = +value;
|
|
||||||
if (value > this.max) {
|
|
||||||
value = this.max;
|
|
||||||
} else if (value < this.min) {
|
|
||||||
value = this.min
|
|
||||||
}
|
|
||||||
|
|
||||||
if(constValue != value){
|
|
||||||
this.inputValue = constValue;
|
|
||||||
this.$nextTick(()=>{
|
|
||||||
this.inputValue = value
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
this.inputValue = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.uni-numbox {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
height: 50rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-numbox-minus,
|
|
||||||
.uni-numbox-plus {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 50rpx;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 1;
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
}
|
|
||||||
.uni-numbox-minus .mix-icon,
|
|
||||||
.uni-numbox-plus .mix-icon{
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-numbox-value {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #fff;
|
|
||||||
width: 60rpx;
|
|
||||||
height: 50rpx;
|
|
||||||
min-height: 50rpx;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-numbox-disabled.mix-icon {
|
|
||||||
color: #C0C4CC;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,53 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mix-price-view" :style="{fontSize: size - 8 + 'rpx'}">
|
|
||||||
<text>¥</text>
|
|
||||||
<text class="price" :style="{fontSize: size + 'rpx'}">{{ priceArr[0] }}</text>
|
|
||||||
<text>.{{ priceArr[1] }}</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 价格显示组件
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
priceArr: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
price: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: Number,
|
|
||||||
default: 36
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
price(){
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
render(){
|
|
||||||
const price = parseFloat(this.price).toFixed(2);
|
|
||||||
this.priceArr = (''+price).split('.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.mix-price-view{
|
|
||||||
color: $base-color;
|
|
||||||
}
|
|
||||||
.price{
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,137 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="mix-timeline">
|
|
||||||
<view class="cell" v-for="(item, index) in list" :key="index">
|
|
||||||
<view class="left column center">
|
|
||||||
<text class="time">{{ item.time | date('H:i') }}</text>
|
|
||||||
<text class="date">{{ item.time | date('m/d') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="cen center">
|
|
||||||
<view class="circle center"></view>
|
|
||||||
</view>
|
|
||||||
<view class="right column">
|
|
||||||
<text class="title">{{ item.title }}</text>
|
|
||||||
<text v-if="item.tip" class="tip">{{ item.tip }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 时间轴
|
|
||||||
* {
|
|
||||||
* title: 标题
|
|
||||||
* tip: 小字
|
|
||||||
* time: 时间戳
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
list: {
|
|
||||||
type: Array,
|
|
||||||
default(){
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.mix-timeline{
|
|
||||||
|
|
||||||
}
|
|
||||||
.cell{
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 30rpx 0;
|
|
||||||
|
|
||||||
&:first-child .circle{
|
|
||||||
&:before{
|
|
||||||
background-color: $base-color;
|
|
||||||
}
|
|
||||||
&:after{
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 28rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
background-color: #f9e0eb;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:last-child .right:before{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.left{
|
|
||||||
|
|
||||||
.time{
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #333;
|
|
||||||
line-height: 44rpx;
|
|
||||||
}
|
|
||||||
.date{
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.cen{
|
|
||||||
width: 80rpx;
|
|
||||||
height: 44rpx;
|
|
||||||
|
|
||||||
.circle{
|
|
||||||
width: 16rpx;
|
|
||||||
height: 16rpx;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&:before{
|
|
||||||
content: '';
|
|
||||||
width: 16rpx;
|
|
||||||
height: 16rpx;
|
|
||||||
background-color: #ddd;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
position: relative;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.right{
|
|
||||||
flex: 1;
|
|
||||||
padding-bottom: 30rpx;
|
|
||||||
position: relative;
|
|
||||||
min-height: 96rpx;
|
|
||||||
|
|
||||||
&:before{
|
|
||||||
content: '';
|
|
||||||
width: 2rpx;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #ddd;
|
|
||||||
transform: translate(-42rpx, 22rpx);
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
line-height: 44rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.tip{
|
|
||||||
margin-top: 6rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,200 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="upload-content">
|
|
||||||
<view
|
|
||||||
class="upload-item"
|
|
||||||
v-for="(item, index) in imageList"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<image class="upload-img" :src="item.filePath" mode="aspectFill" @click="previewImage(index)"></image>
|
|
||||||
<!-- 删除按钮 -->
|
|
||||||
<image class="upload-del-btn"
|
|
||||||
@click.stop.prevent="showDelModal(index)"
|
|
||||||
src="data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkZGNjhCRTZFMzgwRTExRUFCNkUwOEM2Q0Y3RjUzQTE2IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkZGNjhCRTZGMzgwRTExRUFCNkUwOEM2Q0Y3RjUzQTE2Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RkY2OEJFNkMzODBFMTFFQUI2RTA4QzZDRjdGNTNBMTYiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RkY2OEJFNkQzODBFMTFFQUI2RTA4QzZDRjdGNTNBMTYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCAAoACgDAREAAhEBAxEB/8QAawABAQEBAQAAAAAAAAAAAAAABggEAQIBAQEAAAAAAAAAAAAAAAAAAAABEAAABQMCAwgBBAMAAAAAAAABEQIDBBMFBgASIRQVMVFhcSIyBwhi8EFCM1M0FhEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AovNs2sWHWJy8XhwUspHYyyghdedEBFLbaREDES8gDiOglbNfsBn2RSHEwpi7JbREaUWEoUObfzfAnFD3kQeGqgH/ANBf6/MdSlV+yrXc39p+7cegeYV9gM+x2Q2mbMXe7aAhVizVCtzb+D4m4ke4zDw0FU4VmtjzGxt3izuCplQih5lYE404HubcTxIQPyENRUrfYDNZGRZ9MhpcEbbZFqhRWj9NRAk+vzU4BH3AGqjH8fYFj2Z2S4W+LPXFzllYvW2I8KUxpUdKAFTaRI6pmPb2FwLcIAP6DeutdE5J7q9bl+R2DVqmWzb36Bp8hfHuPYVj1vhzbguTnUlaXp0FlSVRo0ZSRJCxLdU3bSHdx48CIRDR9f8ANZGO59DhqcELbe1phSmj9NRYkwvzS4JH3COgB5BX69cuY/v5p+qR++ord2+Og82Vu7uXeGizA8N1F5HJBHOrVP0bNvEz0FZpVS2NuLtCvm1VoEEqECMv2H+FXZ+qeoqTr91rrU3rdbq9ZfPcwdWqfq3n++qjuPcx1+2ct/sc2xR7ffUTt7OPboHf2AwqRjufzJiWxC23tapsV0vTUWJvo80uCZdwhoM3x58h2DCrBcZkO3LkZzIWLMCe8CFRo0ZSANSQPdU3GYFx4cSMBAU5e7u5dxvK5jw3UXuYGbvGrVPdv39pnoGGffIVmzOwW6TPtymc3irBmbc2QQmPJipQIApxIDuq7i/iQAfEiSAbvr/hUjIs/hzFNiNtsi0zZTpemogTYR5qcAy7gHQVTmuFWPMbG5Z7w2KmVCC2XkCTjTge1xtXEhA/IQ1FStmv1/z/AB2Q4qHDXe7aAjSlQkitzb+bAG4ke8jDx1UAwx6/i/y4W2VzH+Gg5v7S9u09A8wr6/5/kUhtUyGuyW0RCrKmpFDm38GBJxQ9xkHjoKpwrCrHh1jbs9nbFLKRFbzyxNx1wfc44rgYiXkAaiv/2Q=="
|
|
||||||
mode="scaleToFill">
|
|
||||||
</image>
|
|
||||||
<!-- 进度 -->
|
|
||||||
<view class="upload-progress" v-if="item.progress < 100">{{item.progress}}%</view>
|
|
||||||
</view>
|
|
||||||
<!-- 新增按钮 -->
|
|
||||||
<view class="upload-add-btn" v-if="rduLength > 0" @click="chooseImage"></view>
|
|
||||||
|
|
||||||
<mix-modal ref="mixModal" title="提示" text="确定要放弃这张图片么" @onConfirm="delImage"></mix-modal>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
imageList: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
count: {
|
|
||||||
type: Number,
|
|
||||||
default: 5 //单次可选择的图片数量
|
|
||||||
},
|
|
||||||
length: {
|
|
||||||
type: Number,
|
|
||||||
default: 1 //可上传总数量
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
rduLength(){
|
|
||||||
return this.length - this.imageList.length;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//选择图片
|
|
||||||
chooseImage(){
|
|
||||||
uni.chooseImage({
|
|
||||||
count: this.rduLength < this.count ? this.rduLength : this.count, //最多可以选择的图片张数,默认9
|
|
||||||
sizeType: ['original', 'compressed'], //original 原图,compressed 压缩图,默认二者都有
|
|
||||||
sourceType: ['album', 'camera'], //album 从相册选图,camera 使用相机,默认二者都有
|
|
||||||
success: res=> {
|
|
||||||
this.uploadFiles(res.tempFilePaths);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//上传图片
|
|
||||||
async uploadFiles(files){
|
|
||||||
const item = {
|
|
||||||
filePath: files[0],
|
|
||||||
progress: 0
|
|
||||||
}
|
|
||||||
this.imageList.push(item);
|
|
||||||
const result = await uniCloud.uploadFile({
|
|
||||||
filePath: item.filePath,
|
|
||||||
cloudPath: + new Date() + ('000000' + Math.floor(Math.random() * 999999)).slice(-6) + '.jpg',
|
|
||||||
onUploadProgress: e=> {
|
|
||||||
item.progress = Math.round(
|
|
||||||
(e.loaded * 100) / e.total
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(!result.fileID){
|
|
||||||
this.$util.msg('图片上传失败,上传任务已终止');
|
|
||||||
this.imageList.pop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tempFiles = await uniCloud.getTempFileURL({
|
|
||||||
fileList: [result.fileID]
|
|
||||||
})
|
|
||||||
const tempFile = tempFiles.fileList[0];
|
|
||||||
if(tempFile.download_url || tempFile.fileID){
|
|
||||||
item.url = tempFile.download_url || tempFile.fileID;
|
|
||||||
this.$emit('onChange', {
|
|
||||||
list: this.imageList,
|
|
||||||
index: this.index
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
this.$util.msg('图片上传失败,上传任务已终止');
|
|
||||||
this.imageList.pop();
|
|
||||||
}
|
|
||||||
files.shift();
|
|
||||||
if(files.length > 0){
|
|
||||||
this.uploadFiles(files);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//删除图片
|
|
||||||
showDelModal(index){
|
|
||||||
this.curIndex = index;
|
|
||||||
this.$refs.mixModal.open();
|
|
||||||
},
|
|
||||||
delImage(index){
|
|
||||||
this.imageList.splice(this.curIndex, 1);
|
|
||||||
this.$emit('onChange', {
|
|
||||||
list: this.imageList,
|
|
||||||
index: this.index
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//预览图片
|
|
||||||
previewImage(index){
|
|
||||||
const urls = this.imageList.map(item=> item.filePath);
|
|
||||||
uni.previewImage({
|
|
||||||
current: index,
|
|
||||||
urls: urls
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.upload-content{
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
.upload-item{
|
|
||||||
position: relative;
|
|
||||||
width:148rpx;
|
|
||||||
height:148rpx;
|
|
||||||
margin-right:28rpx;
|
|
||||||
margin-bottom:24rpx;
|
|
||||||
|
|
||||||
&:nth-child(4n){
|
|
||||||
margin-right:0;
|
|
||||||
}
|
|
||||||
.upload-img{
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
border-radius:8rpx;
|
|
||||||
}
|
|
||||||
.upload-del-btn{
|
|
||||||
position: absolute;
|
|
||||||
right:-16rpx;
|
|
||||||
top:-14rpx;
|
|
||||||
z-index: 5;
|
|
||||||
width:36rpx;
|
|
||||||
height:36rpx;
|
|
||||||
border: 4rpx solid #fff;
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
.upload-progress{
|
|
||||||
position: absolute;
|
|
||||||
left:0;
|
|
||||||
top:0;
|
|
||||||
display:flex;
|
|
||||||
align-items:center;
|
|
||||||
justify-content: center;
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
background-color: rgba(0,0,0,.4);
|
|
||||||
color:#fff;
|
|
||||||
font-size:24rpx;
|
|
||||||
border-radius:8rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.upload-add-btn {
|
|
||||||
position: relative;
|
|
||||||
float:left;
|
|
||||||
width: 148rpx;
|
|
||||||
height: 148rpx;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
z-index: 85;
|
|
||||||
border-radius:8rpx;
|
|
||||||
background:#f7f7f7;
|
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
content: " ";
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 4rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
background-color: #d6d6d6;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
width: 60rpx;
|
|
||||||
height: 4rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,97 +0,0 @@
|
||||||
<template>
|
|
||||||
<uni-popup ref="uniPopup" type="bottom">
|
|
||||||
<view class="content">
|
|
||||||
<text class="mix-icon icon-guanbi" @click="close"></text>
|
|
||||||
<view class="center title">
|
|
||||||
<text>输入支付密码</text>
|
|
||||||
</view>
|
|
||||||
<view class="input center">
|
|
||||||
<view class="item center" :class="{has: pwd.length > index}" v-for="(item, index) in 6" :key="index"></view>
|
|
||||||
</view>
|
|
||||||
<view class="reset-btn center" @click="navTo('/pages/auth/payPassword')">
|
|
||||||
<text>重置密码</text>
|
|
||||||
</view>
|
|
||||||
<number-keyboard ref="keybord" @onChange="onNumberChange"></number-keyboard>
|
|
||||||
</view>
|
|
||||||
</uni-popup>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 支付密码键盘
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
pwd: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
pwd(pwd){
|
|
||||||
if(pwd.length === 0){
|
|
||||||
this.$refs.keybord.val = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
open(){
|
|
||||||
this.$refs.uniPopup.open();
|
|
||||||
},
|
|
||||||
close(){
|
|
||||||
this.$refs.uniPopup.close();
|
|
||||||
},
|
|
||||||
onNumberChange(pwd){
|
|
||||||
this.pwd = pwd;
|
|
||||||
if(pwd.length >= 6){
|
|
||||||
this.$emit('onConfirm', pwd.substring(0,6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.content{
|
|
||||||
border-radius: 20rpx 20rpx 0 0;
|
|
||||||
background-color: #fff;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
height: 110rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.input{
|
|
||||||
padding: 30rpx 0 60rpx;
|
|
||||||
|
|
||||||
.item{
|
|
||||||
width: 88rpx;
|
|
||||||
height: 88rpx;
|
|
||||||
margin: 0 10rpx;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4rpx;
|
|
||||||
}
|
|
||||||
.has:after{
|
|
||||||
content: '';
|
|
||||||
width: 16rpx;
|
|
||||||
height: 16rpx;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.reset-btn{
|
|
||||||
padding-bottom: 20rpx;
|
|
||||||
margin-top: -10rpx;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #007aff;
|
|
||||||
}
|
|
||||||
.icon-guanbi{
|
|
||||||
position: absolute;
|
|
||||||
left: 10rpx;
|
|
||||||
top: 24rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,25 +0,0 @@
|
||||||
import message from './message.js';
|
|
||||||
// 定义 type 类型:弹出类型:top/bottom/center
|
|
||||||
const config = {
|
|
||||||
// 顶部弹出
|
|
||||||
top:'top',
|
|
||||||
// 底部弹出
|
|
||||||
bottom:'bottom',
|
|
||||||
// 居中弹出
|
|
||||||
center:'center',
|
|
||||||
// 消息提示
|
|
||||||
message:'top',
|
|
||||||
// 对话框
|
|
||||||
dialog:'center',
|
|
||||||
// 分享
|
|
||||||
share:'bottom',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data(){
|
|
||||||
return {
|
|
||||||
config:config
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mixins: [message],
|
|
||||||
}
|
|
|
@ -1,302 +0,0 @@
|
||||||
<template>
|
|
||||||
<view v-if="showPopupState" class="uni-popup" :class="[popupstyle]" @touchmove.stop.prevent="clear">
|
|
||||||
<uni-transition v-if="maskShow" :mode-class="['fade']" :styles="maskClass" :maskBackgroundColor="maskBackgroundColor" :duration="duration" :show="showTrans"
|
|
||||||
@click="onTap" />
|
|
||||||
<uni-transition :mode-class="ani" :styles="transClass" maskBackgroundColor="rgba(,0,0,0,0)" :duration="duration" :show="showTrans" @click="onTap">
|
|
||||||
<view class="uni-popup__wrapper-box" @click.stop="clear">
|
|
||||||
<slot />
|
|
||||||
</view>
|
|
||||||
</uni-transition>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import uniTransition from '../uni-transition/uni-transition.vue'
|
|
||||||
/**
|
|
||||||
* PopUp 弹出层
|
|
||||||
* @description 弹出层组件,为了解决遮罩弹层的问题
|
|
||||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
|
||||||
* @property {String} type = [top|center|bottom] 弹出方式
|
|
||||||
* @value top 顶部弹出
|
|
||||||
* @value center 中间弹出
|
|
||||||
* @value bottom 底部弹出
|
|
||||||
* @value message 消息提示
|
|
||||||
* @value dialog 对话框
|
|
||||||
* @value share 底部分享示例
|
|
||||||
* @property {Boolean} animation = [ture|false] 是否开启动画
|
|
||||||
* @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
|
|
||||||
* @event {Function} change 打开关闭弹窗触发,e={show: false}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'UniPopup',
|
|
||||||
components: {
|
|
||||||
uniTransition
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
// 开启动画
|
|
||||||
animation: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
|
|
||||||
// message: 消息提示 ; dialog : 对话框
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'center'
|
|
||||||
},
|
|
||||||
// maskClick
|
|
||||||
maskClick: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
maskBackgroundColor: {
|
|
||||||
type: String,
|
|
||||||
default: 'rgba(0, 0, 0, .5)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
popup: this
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
/**
|
|
||||||
* 监听type类型
|
|
||||||
*/
|
|
||||||
type: {
|
|
||||||
handler: function(newVal) {
|
|
||||||
this[this.config[newVal]]()
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 监听遮罩是否可点击
|
|
||||||
* @param {Object} val
|
|
||||||
*/
|
|
||||||
maskClick(val) {
|
|
||||||
this.mkclick = val
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
duration: 300,
|
|
||||||
ani: [],
|
|
||||||
showPopupState: false,
|
|
||||||
showTrans: false,
|
|
||||||
maskClass: {
|
|
||||||
'position': 'fixed',
|
|
||||||
'bottom': 0,
|
|
||||||
'top': 0,
|
|
||||||
'left': 0,
|
|
||||||
'right': 0
|
|
||||||
},
|
|
||||||
transClass: {
|
|
||||||
'position': 'fixed',
|
|
||||||
'left': 0,
|
|
||||||
'right': 0,
|
|
||||||
},
|
|
||||||
maskShow: true,
|
|
||||||
mkclick: true,
|
|
||||||
popupstyle: 'top',
|
|
||||||
config: {
|
|
||||||
top:'top',
|
|
||||||
// 底部弹出
|
|
||||||
bottom:'bottom',
|
|
||||||
// 居中弹出
|
|
||||||
center:'center'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.mkclick = this.maskClick
|
|
||||||
if (this.animation) {
|
|
||||||
this.duration = 300
|
|
||||||
} else {
|
|
||||||
this.duration = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clear(e) {
|
|
||||||
// TODO nvue 取消冒泡
|
|
||||||
e.stopPropagation()
|
|
||||||
},
|
|
||||||
open() {
|
|
||||||
this.showPopupState = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
new Promise(resolve => {
|
|
||||||
clearTimeout(this.timer)
|
|
||||||
this.timer = setTimeout(() => {
|
|
||||||
this.showTrans = true
|
|
||||||
// fixed by mehaotian 兼容 app 端
|
|
||||||
this.$nextTick(() => {
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
}, 50);
|
|
||||||
}).then(res => {
|
|
||||||
// 自定义打开事件
|
|
||||||
clearTimeout(this.msgtimer)
|
|
||||||
this.msgtimer = setTimeout(() => {
|
|
||||||
this.customOpen && this.customOpen()
|
|
||||||
}, 100)
|
|
||||||
this.$emit('change', {
|
|
||||||
show: true,
|
|
||||||
type: this.type
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
close(type) {
|
|
||||||
this.showTrans = false
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$emit('change', {
|
|
||||||
show: false,
|
|
||||||
type: this.type
|
|
||||||
})
|
|
||||||
clearTimeout(this.timer)
|
|
||||||
// 自定义关闭事件
|
|
||||||
this.customOpen && this.customClose()
|
|
||||||
this.timer = setTimeout(() => {
|
|
||||||
this.showPopupState = false
|
|
||||||
}, 300)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onTap() {
|
|
||||||
if (!this.mkclick) return
|
|
||||||
this.close()
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 顶部弹出样式处理
|
|
||||||
*/
|
|
||||||
top() {
|
|
||||||
this.popupstyle = 'top'
|
|
||||||
this.ani = ['slide-top']
|
|
||||||
this.transClass = {
|
|
||||||
'position': 'fixed',
|
|
||||||
'left': 0,
|
|
||||||
'right': 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 底部弹出样式处理
|
|
||||||
*/
|
|
||||||
bottom() {
|
|
||||||
this.popupstyle = 'bottom'
|
|
||||||
this.ani = ['slide-bottom']
|
|
||||||
this.transClass = {
|
|
||||||
'position': 'fixed',
|
|
||||||
'left': 0,
|
|
||||||
'right': 0,
|
|
||||||
'bottom': 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 中间弹出样式处理
|
|
||||||
*/
|
|
||||||
center() {
|
|
||||||
this.popupstyle = 'center'
|
|
||||||
this.ani = ['zoom-out', 'fade']
|
|
||||||
this.transClass = {
|
|
||||||
'position': 'fixed',
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
'display': 'flex',
|
|
||||||
'flexDirection': 'column',
|
|
||||||
/* #endif */
|
|
||||||
'bottom': 0,
|
|
||||||
'left': 0,
|
|
||||||
'right': 0,
|
|
||||||
'top': 0,
|
|
||||||
'justifyContent': 'center',
|
|
||||||
'alignItems': 'center'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.uni-popup {
|
|
||||||
position: fixed;
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
z-index: 99;
|
|
||||||
/* #endif */
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-popup__mask {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: $uni-bg-color-mask;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mask-ani {
|
|
||||||
transition-property: opacity;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-top-mask {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-bottom-mask {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-center-mask {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-popup__wrapper {
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: block;
|
|
||||||
/* #endif */
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top {
|
|
||||||
/* #ifdef H5 */
|
|
||||||
top: var(--window-top);
|
|
||||||
/* #endif */
|
|
||||||
/* #ifndef H5 */
|
|
||||||
top: 0;
|
|
||||||
/* #endif */
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-popup__wrapper-box {
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: block;
|
|
||||||
/* #endif */
|
|
||||||
position: relative;
|
|
||||||
/* iphonex 等安全区设置,底部安全区适配 */
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
padding-bottom: constant(safe-area-inset-bottom);
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
/* #endif */
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-ani {
|
|
||||||
// transition: transform 0.3s;
|
|
||||||
transition-property: transform, opacity;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.uni-top-content {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-bottom-content {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-center-content {
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,245 +0,0 @@
|
||||||
const BindingX = uni.requireNativePlugin('bindingx');
|
|
||||||
const dom = uni.requireNativePlugin('dom');
|
|
||||||
const animation = uni.requireNativePlugin('animation');
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
right: 0,
|
|
||||||
button: [],
|
|
||||||
preventGesture: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
show(newVal) {
|
|
||||||
if (!this.position || JSON.stringify(this.position) === '{}') return;
|
|
||||||
if (this.autoClose) return
|
|
||||||
if (this.isInAnimation) return
|
|
||||||
if (newVal) {
|
|
||||||
this.open()
|
|
||||||
} else {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.swipeaction.children !== undefined) {
|
|
||||||
this.swipeaction.children.push(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.boxSelector = this.getEl(this.$refs['selector-box-hock']);
|
|
||||||
this.selector = this.getEl(this.$refs['selector-content-hock']);
|
|
||||||
this.buttonSelector = this.getEl(this.$refs['selector-button-hock']);
|
|
||||||
this.position = {}
|
|
||||||
this.x = 0
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getSelectorQuery()
|
|
||||||
}, 200)
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.timing) {
|
|
||||||
BindingX.unbind({
|
|
||||||
token: this.timing.token,
|
|
||||||
eventType: 'timing'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.eventpan) {
|
|
||||||
BindingX.unbind({
|
|
||||||
token: this.eventpan.token,
|
|
||||||
eventType: 'pan'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.swipeaction.children.forEach((item, index) => {
|
|
||||||
if (item === this) {
|
|
||||||
this.swipeaction.children.splice(index, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onClick(index, item) {
|
|
||||||
this.$emit('click', {
|
|
||||||
content: item,
|
|
||||||
index
|
|
||||||
})
|
|
||||||
},
|
|
||||||
touchstart(e) {
|
|
||||||
if (this.isInAnimation) return
|
|
||||||
if (this.stop) return
|
|
||||||
this.stop = true
|
|
||||||
if (this.autoClose) {
|
|
||||||
this.swipeaction.closeOther(this)
|
|
||||||
}
|
|
||||||
let endWidth = this.right
|
|
||||||
let boxStep = `(x+${this.x})`
|
|
||||||
let pageX = `${boxStep}> ${-endWidth} && ${boxStep} < 0?${boxStep}:(x+${this.x} < 0? ${-endWidth}:0)`
|
|
||||||
|
|
||||||
let props = [{
|
|
||||||
element: this.selector,
|
|
||||||
property: 'transform.translateX',
|
|
||||||
expression: pageX
|
|
||||||
}]
|
|
||||||
|
|
||||||
let left = 0
|
|
||||||
for (let i = 0; i < this.options.length; i++) {
|
|
||||||
let buttonSelectors = this.getEl(this.$refs['button-hock'][i]);
|
|
||||||
if (this.button.length === 0 || !this.button[i] || !this.button[i].width) return
|
|
||||||
let moveMix = endWidth - left
|
|
||||||
left += this.button[i].width
|
|
||||||
let step = `(${this.x}+x)/${endWidth}`
|
|
||||||
let moveX = `(${step}) * ${moveMix}`
|
|
||||||
let pageButtonX = `${moveX}&& (x+${this.x} > ${-endWidth})?${moveX}:${-moveMix}`
|
|
||||||
props.push({
|
|
||||||
element: buttonSelectors,
|
|
||||||
property: 'transform.translateX',
|
|
||||||
expression: pageButtonX
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.eventpan = this._bind(this.boxSelector, props, 'pan', (e) => {
|
|
||||||
if (e.state === 'end') {
|
|
||||||
this.x = e.deltaX + this.x;
|
|
||||||
if (this.x < -endWidth) {
|
|
||||||
this.x = -endWidth
|
|
||||||
}
|
|
||||||
if (this.x > 0) {
|
|
||||||
this.x = 0
|
|
||||||
}
|
|
||||||
this.stop = false
|
|
||||||
this.bindTiming();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
touchend(e) {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.isopen && !this.isDrag && !this.isInAnimation) {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
bindTiming() {
|
|
||||||
if (this.isopen) {
|
|
||||||
this.move(this.x, -this.right)
|
|
||||||
} else {
|
|
||||||
this.move(this.x, -40)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move(left, value) {
|
|
||||||
if (left >= value) {
|
|
||||||
this.close()
|
|
||||||
} else {
|
|
||||||
this.open()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 开启swipe
|
|
||||||
*/
|
|
||||||
open() {
|
|
||||||
this.animation(true)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 关闭swipe
|
|
||||||
*/
|
|
||||||
close() {
|
|
||||||
this.animation(false)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 开启关闭动画
|
|
||||||
* @param {Object} type
|
|
||||||
*/
|
|
||||||
animation(type) {
|
|
||||||
this.isDrag = true
|
|
||||||
let endWidth = this.right
|
|
||||||
let time = 200
|
|
||||||
this.isInAnimation = true;
|
|
||||||
|
|
||||||
let exit = `t>${time}`;
|
|
||||||
let translate_x_expression = `easeOutExpo(t,${this.x},${type?(-endWidth-this.x):(-this.x)},${time})`
|
|
||||||
let props = [{
|
|
||||||
element: this.selector,
|
|
||||||
property: 'transform.translateX',
|
|
||||||
expression: translate_x_expression
|
|
||||||
}]
|
|
||||||
|
|
||||||
let left = 0
|
|
||||||
for (let i = 0; i < this.options.length; i++) {
|
|
||||||
let buttonSelectors = this.getEl(this.$refs['button-hock'][i]);
|
|
||||||
if (this.button.length === 0 || !this.button[i] || !this.button[i].width) return
|
|
||||||
let moveMix = endWidth - left
|
|
||||||
left += this.button[i].width
|
|
||||||
let step = `${this.x}/${endWidth}`
|
|
||||||
let moveX = `(${step}) * ${moveMix}`
|
|
||||||
let pageButtonX = `easeOutExpo(t,${moveX},${type ? -moveMix + '-' + moveX: 0 + '-' + moveX},${time})`
|
|
||||||
props.push({
|
|
||||||
element: buttonSelectors,
|
|
||||||
property: 'transform.translateX',
|
|
||||||
expression: pageButtonX
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.timing = BindingX.bind({
|
|
||||||
eventType: 'timing',
|
|
||||||
exitExpression: exit,
|
|
||||||
props: props
|
|
||||||
}, (e) => {
|
|
||||||
if (e.state === 'end' || e.state === 'exit') {
|
|
||||||
this.x = type ? -endWidth : 0
|
|
||||||
this.isInAnimation = false;
|
|
||||||
|
|
||||||
this.isopen = this.isopen || false
|
|
||||||
if (this.isopen !== type) {
|
|
||||||
this.$emit('change', type)
|
|
||||||
}
|
|
||||||
this.isopen = type
|
|
||||||
this.isDrag = false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 绑定 BindingX
|
|
||||||
* @param {Object} anchor
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {Object} fn
|
|
||||||
*/
|
|
||||||
_bind(anchor, props, eventType, fn) {
|
|
||||||
return BindingX.bind({
|
|
||||||
anchor,
|
|
||||||
eventType,
|
|
||||||
props
|
|
||||||
}, (e) => {
|
|
||||||
typeof(fn) === 'function' && fn(e)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取ref
|
|
||||||
* @param {Object} el
|
|
||||||
*/
|
|
||||||
getEl(el) {
|
|
||||||
return el.ref
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取节点信息
|
|
||||||
*/
|
|
||||||
getSelectorQuery() {
|
|
||||||
dom.getComponentRect(this.$refs['selector-content-hock'], (data) => {
|
|
||||||
if (this.position.content) return
|
|
||||||
this.position.content = data.size
|
|
||||||
})
|
|
||||||
for (let i = 0; i < this.options.length; i++) {
|
|
||||||
dom.getComponentRect(this.$refs['button-hock'][i], (data) => {
|
|
||||||
if (!this.button) {
|
|
||||||
this.button = []
|
|
||||||
}
|
|
||||||
if (this.options.length === this.button.length) return
|
|
||||||
this.button.push(data.size)
|
|
||||||
this.right += data.size.width
|
|
||||||
if (this.autoClose) return
|
|
||||||
if (this.show) {
|
|
||||||
this.open()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
/**
|
|
||||||
* 监听页面内值的变化,主要用于动态开关swipe-action
|
|
||||||
* @param {Object} newValue
|
|
||||||
* @param {Object} oldValue
|
|
||||||
* @param {Object} ownerInstance
|
|
||||||
* @param {Object} instance
|
|
||||||
*/
|
|
||||||
function sizeReady(newValue, oldValue, ownerInstance, instance) {
|
|
||||||
var state = instance.getState()
|
|
||||||
state.position = JSON.parse(newValue)
|
|
||||||
if (!state.position || state.position.length === 0) return
|
|
||||||
var show = state.position[0].show
|
|
||||||
state.left = state.left || state.position[0].left;
|
|
||||||
// 通过用户变量,开启或关闭
|
|
||||||
if (show) {
|
|
||||||
openState(true, instance, ownerInstance)
|
|
||||||
} else {
|
|
||||||
openState(false, instance, ownerInstance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始触摸操作
|
|
||||||
* @param {Object} e
|
|
||||||
* @param {Object} ins
|
|
||||||
*/
|
|
||||||
function touchstart(e, ins) {
|
|
||||||
var instance = e.instance;
|
|
||||||
var state = instance.getState();
|
|
||||||
var pageX = e.touches[0].pageX;
|
|
||||||
// 开始触摸时移除动画类
|
|
||||||
instance.removeClass('ani');
|
|
||||||
var owner = ins.selectAllComponents('.button-hock')
|
|
||||||
for (var i = 0; i < owner.length; i++) {
|
|
||||||
owner[i].removeClass('ani');
|
|
||||||
}
|
|
||||||
// state.position = JSON.parse(instance.getDataset().position);
|
|
||||||
state.left = state.left || state.position[0].left;
|
|
||||||
// 获取最终按钮组的宽度
|
|
||||||
state.width = pageX - state.left;
|
|
||||||
ins.callMethod('closeSwipe')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始滑动操作
|
|
||||||
* @param {Object} e
|
|
||||||
* @param {Object} ownerInstance
|
|
||||||
*/
|
|
||||||
function touchmove(e, ownerInstance) {
|
|
||||||
var instance = e.instance;
|
|
||||||
var disabled = instance.getDataset().disabled
|
|
||||||
var state = instance.getState()
|
|
||||||
// fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复
|
|
||||||
disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false;
|
|
||||||
|
|
||||||
if (disabled) return
|
|
||||||
var pageX = e.touches[0].pageX;
|
|
||||||
move(pageX - state.width, instance, ownerInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 结束触摸操作
|
|
||||||
* @param {Object} e
|
|
||||||
* @param {Object} ownerInstance
|
|
||||||
*/
|
|
||||||
function touchend(e, ownerInstance) {
|
|
||||||
var instance = e.instance;
|
|
||||||
var disabled = instance.getDataset().disabled
|
|
||||||
var state = instance.getState()
|
|
||||||
|
|
||||||
// fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复
|
|
||||||
disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false;
|
|
||||||
|
|
||||||
if (disabled) return
|
|
||||||
// 滑动过程中触摸结束,通过阙值判断是开启还是关闭
|
|
||||||
// fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13
|
|
||||||
moveDirection(state.left, -40, instance, ownerInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置移动距离
|
|
||||||
* @param {Object} value
|
|
||||||
* @param {Object} instance
|
|
||||||
* @param {Object} ownerInstance
|
|
||||||
*/
|
|
||||||
function move(value, instance, ownerInstance) {
|
|
||||||
var state = instance.getState()
|
|
||||||
// 获取可滑动范围
|
|
||||||
var x = Math.max(-state.position[1].width, Math.min((value), 0));
|
|
||||||
state.left = x;
|
|
||||||
instance.setStyle({
|
|
||||||
transform: 'translateX(' + x + 'px)',
|
|
||||||
'-webkit-transform': 'translateX(' + x + 'px)'
|
|
||||||
})
|
|
||||||
// 折叠按钮动画
|
|
||||||
buttonFold(x, instance, ownerInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动方向判断
|
|
||||||
* @param {Object} left
|
|
||||||
* @param {Object} value
|
|
||||||
* @param {Object} ownerInstance
|
|
||||||
* @param {Object} ins
|
|
||||||
*/
|
|
||||||
function moveDirection(left, value, ins, ownerInstance) {
|
|
||||||
var state = ins.getState()
|
|
||||||
var position = state.position
|
|
||||||
var isopen = state.isopen
|
|
||||||
if (!position[1].width) {
|
|
||||||
openState(false, ins, ownerInstance)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 如果已经是打开状态,进行判断是否关闭,还是保留打开状态
|
|
||||||
if (isopen) {
|
|
||||||
if (-left <= position[1].width) {
|
|
||||||
openState(false, ins, ownerInstance)
|
|
||||||
} else {
|
|
||||||
openState(true, ins, ownerInstance)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 如果是关闭状态,进行判断是否打开,还是保留关闭状态
|
|
||||||
if (left <= value) {
|
|
||||||
openState(true, ins, ownerInstance)
|
|
||||||
} else {
|
|
||||||
openState(false, ins, ownerInstance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置按钮移动距离
|
|
||||||
* @param {Object} value
|
|
||||||
* @param {Object} instance
|
|
||||||
* @param {Object} ownerInstance
|
|
||||||
*/
|
|
||||||
function buttonFold(value, instance, ownerInstance) {
|
|
||||||
var ins = ownerInstance.selectAllComponents('.button-hock');
|
|
||||||
var state = instance.getState();
|
|
||||||
var position = state.position;
|
|
||||||
var arr = [];
|
|
||||||
var w = 0;
|
|
||||||
for (var i = 0; i < ins.length; i++) {
|
|
||||||
if (!ins[i].getDataset().button) return
|
|
||||||
var btnData = JSON.parse(ins[i].getDataset().button)
|
|
||||||
|
|
||||||
// fix by mehaotian TODO 在 app-vue 中,字符串转对象,需要转两次,这里先这么兼容
|
|
||||||
if (typeof(btnData) === 'string') {
|
|
||||||
btnData = JSON.parse(btnData)
|
|
||||||
}
|
|
||||||
|
|
||||||
var button = btnData[i] && btnData[i].width || 0
|
|
||||||
w += button
|
|
||||||
arr.push(-w)
|
|
||||||
// 动态计算按钮组每个按钮的折叠动画移动距离
|
|
||||||
var distance = arr[i - 1] + value * (arr[i - 1] / position[1].width)
|
|
||||||
if (i != 0) {
|
|
||||||
ins[i].setStyle({
|
|
||||||
transform: 'translateX(' + distance + 'px)',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开启状态
|
|
||||||
* @param {Boolean} type
|
|
||||||
* @param {Object} ins
|
|
||||||
* @param {Object} ownerInstance
|
|
||||||
*/
|
|
||||||
function openState(type, ins, ownerInstance) {
|
|
||||||
var state = ins.getState()
|
|
||||||
var position = state.position
|
|
||||||
if (state.isopen === undefined) {
|
|
||||||
state.isopen = false
|
|
||||||
}
|
|
||||||
// 只有状态有改变才会通知页面改变状态
|
|
||||||
if (state.isopen !== type) {
|
|
||||||
// 通知页面,已经打开
|
|
||||||
ownerInstance.callMethod('change', {
|
|
||||||
open: type
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 设置打开和移动状态
|
|
||||||
state.isopen = type
|
|
||||||
|
|
||||||
|
|
||||||
// 添加动画类
|
|
||||||
ins.addClass('ani');
|
|
||||||
var owner = ownerInstance.selectAllComponents('.button-hock')
|
|
||||||
for (var i = 0; i < owner.length; i++) {
|
|
||||||
owner[i].addClass('ani');
|
|
||||||
}
|
|
||||||
// 设置最终移动位置
|
|
||||||
move(type ? -position[1].width : 0, ins, ownerInstance)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
sizeReady: sizeReady,
|
|
||||||
touchstart: touchstart,
|
|
||||||
touchmove: touchmove,
|
|
||||||
touchend: touchend
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isshow: false,
|
|
||||||
viewWidth: 0,
|
|
||||||
buttonWidth: 0,
|
|
||||||
disabledView: false,
|
|
||||||
x: 0,
|
|
||||||
transition: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
show(newVal) {
|
|
||||||
if (this.autoClose) return
|
|
||||||
if (newVal) {
|
|
||||||
this.open()
|
|
||||||
} else {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.swipeaction.children !== undefined) {
|
|
||||||
this.swipeaction.children.push(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.swipeaction.children.forEach((item, index) => {
|
|
||||||
if (item === this) {
|
|
||||||
this.swipeaction.children.splice(index, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.isopen = false
|
|
||||||
this.transition = true
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getQuerySelect()
|
|
||||||
}, 50)
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onClick(index, item) {
|
|
||||||
this.$emit('click', {
|
|
||||||
content: item,
|
|
||||||
index
|
|
||||||
})
|
|
||||||
},
|
|
||||||
touchstart(e) {
|
|
||||||
let {
|
|
||||||
pageX,
|
|
||||||
pageY
|
|
||||||
} = e.changedTouches[0]
|
|
||||||
this.transition = false
|
|
||||||
this.startX = pageX
|
|
||||||
if (this.autoClose) {
|
|
||||||
this.swipeaction.closeOther(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
touchmove(e) {
|
|
||||||
let {
|
|
||||||
pageX,
|
|
||||||
} = e.changedTouches[0]
|
|
||||||
this.slide = this.getSlide(pageX)
|
|
||||||
if (this.slide === 0) {
|
|
||||||
this.disabledView = false
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
touchend(e) {
|
|
||||||
this.stop = false
|
|
||||||
this.transition = true
|
|
||||||
if (this.isopen) {
|
|
||||||
if (this.moveX === -this.buttonWidth) {
|
|
||||||
this.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.move()
|
|
||||||
} else {
|
|
||||||
if (this.moveX === 0) {
|
|
||||||
this.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.move()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
open() {
|
|
||||||
this.x = this.moveX
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.x = -this.buttonWidth
|
|
||||||
this.moveX = this.x
|
|
||||||
|
|
||||||
if(!this.isopen){
|
|
||||||
this.isopen = true
|
|
||||||
this.$emit('change', true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.x = this.moveX
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.x = 0
|
|
||||||
this.moveX = this.x
|
|
||||||
if(this.isopen){
|
|
||||||
this.isopen = false
|
|
||||||
this.$emit('change', false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
move() {
|
|
||||||
if (this.slide === 0) {
|
|
||||||
this.open()
|
|
||||||
} else {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChange(e) {
|
|
||||||
let x = e.detail.x
|
|
||||||
this.moveX = x
|
|
||||||
if (x >= this.buttonWidth) {
|
|
||||||
this.disabledView = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.x = this.buttonWidth
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getSlide(x) {
|
|
||||||
if (x >= this.startX) {
|
|
||||||
this.startX = x
|
|
||||||
return 1
|
|
||||||
} else {
|
|
||||||
this.startX = x
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
getQuerySelect() {
|
|
||||||
const query = uni.createSelectorQuery().in(this);
|
|
||||||
query.selectAll('.viewWidth-hook').boundingClientRect(data => {
|
|
||||||
|
|
||||||
this.viewWidth = data[0].width
|
|
||||||
this.buttonWidth = data[1].width
|
|
||||||
this.transition = false
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.transition = true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!this.buttonWidth) {
|
|
||||||
this.disabledView = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.autoClose) return
|
|
||||||
if (this.show) {
|
|
||||||
this.open()
|
|
||||||
}
|
|
||||||
}).exec();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
// #ifdef APP-NVUE
|
|
||||||
const dom = weex.requireModule('dom');
|
|
||||||
// #endif
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
uniShow: false,
|
|
||||||
left: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
moveLeft() {
|
|
||||||
return `translateX(${this.left}px)`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
show(newVal) {
|
|
||||||
if (!this.position || JSON.stringify(this.position) === '{}') return;
|
|
||||||
if (this.autoClose) return
|
|
||||||
if (newVal) {
|
|
||||||
this.$emit('change', true)
|
|
||||||
this.open()
|
|
||||||
} else {
|
|
||||||
this.$emit('change', false)
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.position = {}
|
|
||||||
if (this.swipeaction.children !== undefined) {
|
|
||||||
this.swipeaction.children.push(this)
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getSelectorQuery()
|
|
||||||
}, 100)
|
|
||||||
},
|
|
||||||
beforeDestoy() {
|
|
||||||
this.swipeaction.children.forEach((item, index) => {
|
|
||||||
if (item === this) {
|
|
||||||
this.swipeaction.children.splice(index, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onClick(index, item) {
|
|
||||||
this.$emit('click', {
|
|
||||||
content: item,
|
|
||||||
index
|
|
||||||
})
|
|
||||||
this.close()
|
|
||||||
},
|
|
||||||
touchstart(e) {
|
|
||||||
const {
|
|
||||||
pageX
|
|
||||||
} = e.touches[0]
|
|
||||||
if (this.disabled) return
|
|
||||||
const left = this.position.content.left
|
|
||||||
if (this.autoClose) {
|
|
||||||
this.swipeaction.closeOther(this)
|
|
||||||
}
|
|
||||||
this.width = pageX - left
|
|
||||||
if (this.isopen) return
|
|
||||||
if (this.uniShow) {
|
|
||||||
this.uniShow = false
|
|
||||||
this.isopen = true
|
|
||||||
this.openleft = this.left + this.position.button.width
|
|
||||||
}
|
|
||||||
},
|
|
||||||
touchmove(e, index) {
|
|
||||||
if (this.disabled) return
|
|
||||||
const {
|
|
||||||
pageX
|
|
||||||
} = e.touches[0]
|
|
||||||
this.setPosition(pageX)
|
|
||||||
},
|
|
||||||
touchend() {
|
|
||||||
if (this.disabled) return
|
|
||||||
if (this.isopen) {
|
|
||||||
this.move(this.openleft, 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.move(this.left, -40)
|
|
||||||
},
|
|
||||||
setPosition(x, y) {
|
|
||||||
if (!this.position.button.width) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// this.left = x - this.width
|
|
||||||
this.setValue(x - this.width)
|
|
||||||
},
|
|
||||||
setValue(value) {
|
|
||||||
// 设置最大最小值
|
|
||||||
this.left = Math.max(-this.position.button.width, Math.min(parseInt(value), 0))
|
|
||||||
this.position.content.left = this.left
|
|
||||||
if (this.isopen) {
|
|
||||||
this.openleft = this.left + this.position.button.width
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move(left, value) {
|
|
||||||
if (left >= value) {
|
|
||||||
this.$emit('change', false)
|
|
||||||
this.close()
|
|
||||||
} else {
|
|
||||||
this.$emit('change', true)
|
|
||||||
this.open()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
open() {
|
|
||||||
this.uniShow = true
|
|
||||||
this.left = -this.position.button.width
|
|
||||||
this.setValue(-this.position.button.width)
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.uniShow = true
|
|
||||||
this.setValue(0)
|
|
||||||
setTimeout(() => {
|
|
||||||
this.uniShow = false
|
|
||||||
this.isopen = false
|
|
||||||
}, 300)
|
|
||||||
},
|
|
||||||
getSelectorQuery() {
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
const views = uni.createSelectorQuery()
|
|
||||||
.in(this)
|
|
||||||
views
|
|
||||||
.selectAll('.selector-query-hock')
|
|
||||||
.boundingClientRect(data => {
|
|
||||||
this.position.content = data[1]
|
|
||||||
this.position.button = data[0]
|
|
||||||
if (this.autoClose) return
|
|
||||||
if (this.show) {
|
|
||||||
this.open()
|
|
||||||
} else {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec()
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
dom.getComponentRect(this.$refs['selector-content-hock'], (data) => {
|
|
||||||
if (this.position.content) return
|
|
||||||
this.position.content = data.size
|
|
||||||
})
|
|
||||||
dom.getComponentRect(this.$refs['selector-button-hock'], (data) => {
|
|
||||||
if (this.position.button) return
|
|
||||||
this.position.button = data.size
|
|
||||||
if (this.autoClose) return
|
|
||||||
if (this.show) {
|
|
||||||
this.open()
|
|
||||||
} else {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
position: [],
|
|
||||||
button: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
pos() {
|
|
||||||
return JSON.stringify(this.position)
|
|
||||||
},
|
|
||||||
btn() {
|
|
||||||
return JSON.stringify(this.button)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
show(newVal) {
|
|
||||||
if (this.autoClose) return
|
|
||||||
let valueObj = this.position[0]
|
|
||||||
if (!valueObj) {
|
|
||||||
this.init()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
valueObj.show = newVal
|
|
||||||
this.$set(this.position, 0, valueObj)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.swipeaction.children !== undefined) {
|
|
||||||
this.swipeaction.children.push(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init()
|
|
||||||
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.swipeaction.children.forEach((item, index) => {
|
|
||||||
if (item === this) {
|
|
||||||
this.swipeaction.children.splice(index, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getSize()
|
|
||||||
this.getButtonSize()
|
|
||||||
}, 50)
|
|
||||||
},
|
|
||||||
closeSwipe(e) {
|
|
||||||
if (!this.autoClose) return
|
|
||||||
this.swipeaction.closeOther(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
change(e) {
|
|
||||||
this.$emit('change', e.open)
|
|
||||||
let valueObj = this.position[0]
|
|
||||||
if (valueObj.show !== e.open) {
|
|
||||||
valueObj.show = e.open
|
|
||||||
this.$set(this.position, 0, valueObj)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick(index, item) {
|
|
||||||
this.$emit('click', {
|
|
||||||
content: item,
|
|
||||||
index
|
|
||||||
})
|
|
||||||
},
|
|
||||||
appTouchStart(){},
|
|
||||||
appTouchEnd(){},
|
|
||||||
getSize() {
|
|
||||||
const views = uni.createSelectorQuery().in(this)
|
|
||||||
views
|
|
||||||
.selectAll('.selector-query-hock')
|
|
||||||
.boundingClientRect(data => {
|
|
||||||
if (this.autoClose) {
|
|
||||||
data[0].show = false
|
|
||||||
} else {
|
|
||||||
data[0].show = this.show
|
|
||||||
}
|
|
||||||
this.position = data
|
|
||||||
})
|
|
||||||
.exec()
|
|
||||||
},
|
|
||||||
getButtonSize() {
|
|
||||||
const views = uni.createSelectorQuery().in(this)
|
|
||||||
views
|
|
||||||
.selectAll('.button-hock')
|
|
||||||
.boundingClientRect(data => {
|
|
||||||
this.button = data
|
|
||||||
})
|
|
||||||
.exec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,270 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="uni-swipe">
|
|
||||||
<!-- 在微信小程序 app vue端 h5 使用wxs 实现-->
|
|
||||||
<!-- #ifdef APP-VUE || MP-WEIXIN || H5 -->
|
|
||||||
<view class="uni-swipe_content">
|
|
||||||
<view :data-disabled="disabled" :data-position="pos" :change:prop="swipe.sizeReady" :prop="pos" class="uni-swipe_move-box selector-query-hock move-hock"
|
|
||||||
@touchstart="swipe.touchstart" @touchmove="swipe.touchmove" @touchend="swipe.touchend" @change="change">
|
|
||||||
<view class="uni-swipe_box">
|
|
||||||
<slot />
|
|
||||||
</view>
|
|
||||||
<view ref="selector-button-hock" class="uni-swipe_button-group selector-query-hock move-hock">
|
|
||||||
<!-- 使用 touchend 解决 ios 13 不触发按钮事件的问题-->
|
|
||||||
<view v-for="(item,index) in options" :data-button="btn" :key="index" :style="{
|
|
||||||
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
|
|
||||||
fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'
|
|
||||||
}"
|
|
||||||
class="uni-swipe_button button-hock" @touchend="onClick(index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}">{{ item.text }}</text></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- app nvue端 使用 bindingx -->
|
|
||||||
<!-- #ifdef APP-NVUE -->
|
|
||||||
<view ref="selector-box-hock" class="uni-swipe_content" @horizontalpan="touchstart" @touchend="touchend">
|
|
||||||
<view ref="selector-button-hock" class="uni-swipe_button-group selector-query-hock move-hock" :style="{width:right+'px'}">
|
|
||||||
<view ref="button-hock" v-for="(item,index) in options" :key="index" :style="{
|
|
||||||
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',left: right+'px'}"
|
|
||||||
class="uni-swipe_button " @click.stop="onClick(index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'}">{{ item.text }}</text></view>
|
|
||||||
</view>
|
|
||||||
<view ref='selector-content-hock' class="uni-swipe_move-box selector-query-hock">
|
|
||||||
<view class="uni-swipe_box">
|
|
||||||
<slot />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 在非 app 端、非微信小程序、支付宝小程序、h5端使用 js -->
|
|
||||||
<!-- #ifndef APP-PLUS || MP-WEIXIN || MP-ALIPAY || H5 -->
|
|
||||||
<view class="uni-swipe_content">
|
|
||||||
<view ref="selector-button-hock" class="uni-swipe_button-group selector-query-hock move-hock">
|
|
||||||
<view v-for="(item,index) in options" :data-button="btn" :key="index" :style="{
|
|
||||||
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
|
|
||||||
fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'
|
|
||||||
}"
|
|
||||||
class="uni-swipe_button button-hock" @click.stop="onClick(index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}">{{ item.text }}</text></view>
|
|
||||||
</view>
|
|
||||||
<view ref='selector-content-hock' class="selector-query-hock" @touchstart="touchstart" @touchmove="touchmove"
|
|
||||||
@touchend="touchend" :class="{'ani':uniShow}" :style="{transform:moveLeft}">
|
|
||||||
<view class="uni-swipe_move-box" >
|
|
||||||
<view class="uni-swipe_box">
|
|
||||||
<slot />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
<!-- #ifdef MP-ALIPAY -->
|
|
||||||
<view class="uni-swipe-box" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend">
|
|
||||||
<view class="viewWidth-hook">
|
|
||||||
<movable-area v-if="viewWidth !== 0" class="movable-area" :style="{width:(viewWidth-buttonWidth)+'px'}">
|
|
||||||
<movable-view class="movable-view" direction="horizontal" :animation="!transition" :style="{width:viewWidth+'px'}"
|
|
||||||
:class="[transition?'transition':'']" :x="x" :disabled="disabledView" @change="onChange">
|
|
||||||
<view class="movable-view-box">
|
|
||||||
<slot></slot>
|
|
||||||
</view>
|
|
||||||
</movable-view>
|
|
||||||
</movable-area>
|
|
||||||
</view>
|
|
||||||
<view ref="selector-button-hock" class="uni-swipe_button-group viewWidth-hook">
|
|
||||||
<view v-for="(item,index) in options" :data-button="btn" :key="index" :style="{
|
|
||||||
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
|
|
||||||
fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'
|
|
||||||
}"
|
|
||||||
class="uni-swipe_button button-hock" @click.stop="onClick(index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}">{{ item.text }}</text></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<script src="./index.wxs" module="swipe" lang="wxs"></script>
|
|
||||||
<script>
|
|
||||||
// #ifdef APP-VUE|| MP-WEIXIN || H5
|
|
||||||
import mpwxs from './mpwxs'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
import bindingx from './bindingx.js'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef APP-PLUS|| MP-WEIXIN || MP-ALIPAY || H5
|
|
||||||
import mixins from './mpother'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
import mpalipay from './mpalipay'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SwipeActionItem 滑动操作子组件
|
|
||||||
* @description 通过滑动触发选项的容器
|
|
||||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=181
|
|
||||||
* @property {Boolean} show = [true|false] 开启关闭组件,auto-close = false 时生效
|
|
||||||
* @property {Boolean} disabled = [true|false] 是否禁止滑动
|
|
||||||
* @property {Boolean} autoClose = [true|false] 其他组件开启的时候,当前组件是否自动关闭
|
|
||||||
* @property {Array} options 组件选项内容及样式
|
|
||||||
* @event {Function} click 点击选项按钮时触发事件,e = {content,index} ,content(点击内容)、index(下标)
|
|
||||||
* @event {Function} change 组件打开或关闭时触发,true:开启状态;false:关闭状态
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// #ifdef APP-VUE|| MP-WEIXIN||H5
|
|
||||||
mixins: [mpwxs],
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
mixins: [bindingx],
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef APP-PLUS|| MP-WEIXIN || MP-ALIPAY || H5
|
|
||||||
mixins: [mixins],
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
mixins: [mpalipay],
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
props: {
|
|
||||||
/**
|
|
||||||
* 按钮内容
|
|
||||||
*/
|
|
||||||
options: {
|
|
||||||
type: Array,
|
|
||||||
default () {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 禁用
|
|
||||||
*/
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 变量控制开关
|
|
||||||
*/
|
|
||||||
show: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 是否自动关闭
|
|
||||||
*/
|
|
||||||
autoClose: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inject: ['swipeaction']
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.uni-swipe {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-swipe-box {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-swipe_content {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-swipe_move-box {
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: flex;
|
|
||||||
/* #endif */
|
|
||||||
position: relative;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-swipe_box {
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
width: 100%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
/* #endif */
|
|
||||||
/* #ifdef APP-NVUE */
|
|
||||||
flex: 1;
|
|
||||||
/* #endif */
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-swipe_button-group {
|
|
||||||
/* #ifndef APP-VUE|| MP-WEIXIN||H5 */
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 0;
|
|
||||||
/* #endif */
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
/* #endif */
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-swipe_button {
|
|
||||||
/* #ifdef APP-NVUE */
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
/* #endif */
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
display: flex;
|
|
||||||
/* #endif */
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uni-swipe_button-text {
|
|
||||||
/* #ifndef APP-NVUE */
|
|
||||||
flex-shrink: 0;
|
|
||||||
/* #endif */
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ani {
|
|
||||||
transition-property: transform;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #ifdef MP-ALIPAY */
|
|
||||||
.movable-area {
|
|
||||||
width: 300px;
|
|
||||||
height: 100%;
|
|
||||||
height: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movable-view {
|
|
||||||
position: relative;
|
|
||||||
width: 160%;
|
|
||||||
height: 45px;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.transition {
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movable-view-box {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
/* #endif */
|
|
||||||
</style>
|
|
|
@ -1,58 +0,0 @@
|
||||||
<template>
|
|
||||||
<view>
|
|
||||||
<slot></slot>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* SwipeAction 滑动操作
|
|
||||||
* @description 通过滑动触发选项的容器
|
|
||||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=181
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
swipeaction: this
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.children = []
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
closeOther(vm) {
|
|
||||||
let children = this.children
|
|
||||||
children.forEach((item, index) => {
|
|
||||||
if (vm === item) return
|
|
||||||
// 支付宝执行以下操作
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
if (item.isopen) {
|
|
||||||
item.close()
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// app vue 端、h5 、微信、支付宝 执行以下操作
|
|
||||||
// #ifdef APP-VUE || H5 || MP-WEIXIN
|
|
||||||
let position = item.position[0]
|
|
||||||
let show = position.show
|
|
||||||
if (show) {
|
|
||||||
position.show = false
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// nvue 执行以下操作
|
|
||||||
// #ifdef APP-NVUE || MP-BAIDU || MP-QQ || MP-TOUTIAO
|
|
||||||
item.close()
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,290 +0,0 @@
|
||||||
<template>
|
|
||||||
<view
|
|
||||||
v-if="isShow"
|
|
||||||
ref="ani"
|
|
||||||
class="uni-transition"
|
|
||||||
:class="[ani.in]"
|
|
||||||
:style="'transform:' +transform+';'+stylesObject"
|
|
||||||
@click="change"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
const animation = uni.requireNativePlugin('animation');
|
|
||||||
// #endif
|
|
||||||
/**
|
|
||||||
* Transition 过渡动画
|
|
||||||
* @description 简单过渡动画组件
|
|
||||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
|
|
||||||
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
|
|
||||||
* @property {Array} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
|
|
||||||
* @value fade 渐隐渐出过渡
|
|
||||||
* @value slide-top 由上至下过渡
|
|
||||||
* @value slide-right 由右至左过渡
|
|
||||||
* @value slide-bottom 由下至上过渡
|
|
||||||
* @value slide-left 由左至右过渡
|
|
||||||
* @value zoom-in 由小到大过渡
|
|
||||||
* @value zoom-out 由大到小过渡
|
|
||||||
* @property {Number} duration 过渡动画持续时间
|
|
||||||
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
name: 'uniTransition',
|
|
||||||
props: {
|
|
||||||
show: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
modeClass: {
|
|
||||||
type: Array,
|
|
||||||
default () {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
duration: {
|
|
||||||
type: Number,
|
|
||||||
default: 300
|
|
||||||
},
|
|
||||||
styles: {
|
|
||||||
type: Object,
|
|
||||||
default () {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
maskBackgroundColor: {
|
|
||||||
type: String,
|
|
||||||
default: 'rgba(0, 0, 0, 0.4)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isShow: false,
|
|
||||||
transform: '',
|
|
||||||
ani: { in: '',
|
|
||||||
active: ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
show: {
|
|
||||||
handler(newVal) {
|
|
||||||
if (newVal) {
|
|
||||||
this.open()
|
|
||||||
} else {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
stylesObject() {
|
|
||||||
let styles = {
|
|
||||||
...this.styles,
|
|
||||||
backgroundColor: this.maskBackgroundColor,
|
|
||||||
'transition-duration': this.duration / 1000 + 's'
|
|
||||||
}
|
|
||||||
let transfrom = ''
|
|
||||||
for (let i in styles) {
|
|
||||||
let line = this.toLine(i)
|
|
||||||
transfrom += line + ':' + styles[i] + ';'
|
|
||||||
}
|
|
||||||
return transfrom
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
// this.timer = null
|
|
||||||
// this.nextTick = (time = 50) => new Promise(resolve => {
|
|
||||||
// clearTimeout(this.timer)
|
|
||||||
// this.timer = setTimeout(resolve, time)
|
|
||||||
// return this.timer
|
|
||||||
// });
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
change() {
|
|
||||||
this.$emit('click', {
|
|
||||||
detail: this.isShow
|
|
||||||
})
|
|
||||||
},
|
|
||||||
open() {
|
|
||||||
clearTimeout(this.timer)
|
|
||||||
this.isShow = true
|
|
||||||
this.transform = ''
|
|
||||||
this.ani.in = ''
|
|
||||||
for (let i in this.getTranfrom(false)) {
|
|
||||||
if (i === 'opacity') {
|
|
||||||
this.ani.in = 'fade-in'
|
|
||||||
} else {
|
|
||||||
this.transform += `${this.getTranfrom(false)[i]} `
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this._animation(true)
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
close(type) {
|
|
||||||
clearTimeout(this.timer)
|
|
||||||
this._animation(false)
|
|
||||||
},
|
|
||||||
_animation(type) {
|
|
||||||
let styles = this.getTranfrom(type)
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
if(!this.$refs['ani']) return
|
|
||||||
animation.transition(this.$refs['ani'].ref, {
|
|
||||||
styles,
|
|
||||||
duration: this.duration, //ms
|
|
||||||
timingFunction: 'ease',
|
|
||||||
needLayout: false,
|
|
||||||
delay: 0 //ms
|
|
||||||
}, () => {
|
|
||||||
if (!type) {
|
|
||||||
this.isShow = false
|
|
||||||
}
|
|
||||||
this.$emit('change', {
|
|
||||||
detail: this.isShow
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
this.transform = ''
|
|
||||||
for (let i in styles) {
|
|
||||||
if (i === 'opacity') {
|
|
||||||
this.ani.in = `fade-${type?'out':'in'}`
|
|
||||||
} else {
|
|
||||||
this.transform += `${styles[i]} `
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.timer = setTimeout(() => {
|
|
||||||
if (!type) {
|
|
||||||
this.isShow = false
|
|
||||||
}
|
|
||||||
this.$emit('change', {
|
|
||||||
detail: this.isShow
|
|
||||||
})
|
|
||||||
|
|
||||||
}, this.duration)
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
},
|
|
||||||
getTranfrom(type) {
|
|
||||||
let styles = {
|
|
||||||
transform: ''
|
|
||||||
}
|
|
||||||
this.modeClass.forEach((mode) => {
|
|
||||||
switch (mode) {
|
|
||||||
case 'fade':
|
|
||||||
styles.opacity = type ? 1 : 0
|
|
||||||
break;
|
|
||||||
case 'slide-top':
|
|
||||||
styles.transform += `translateY(${type?'0':'-100%'}) `
|
|
||||||
break;
|
|
||||||
case 'slide-right':
|
|
||||||
styles.transform += `translateX(${type?'0':'100%'}) `
|
|
||||||
break;
|
|
||||||
case 'slide-bottom':
|
|
||||||
styles.transform += `translateY(${type?'0':'100%'}) `
|
|
||||||
break;
|
|
||||||
case 'slide-left':
|
|
||||||
styles.transform += `translateX(${type?'0':'-100%'}) `
|
|
||||||
break;
|
|
||||||
case 'zoom-in':
|
|
||||||
styles.transform += `scale(${type?1:0.8}) `
|
|
||||||
break;
|
|
||||||
case 'zoom-out':
|
|
||||||
styles.transform += `scale(${type?1:1.2}) `
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return styles
|
|
||||||
},
|
|
||||||
_modeClassArr(type) {
|
|
||||||
let mode = this.modeClass
|
|
||||||
if (typeof(mode) !== "string") {
|
|
||||||
let modestr = ''
|
|
||||||
mode.forEach((item) => {
|
|
||||||
modestr += (item + '-' + type + ',')
|
|
||||||
})
|
|
||||||
return modestr.substr(0, modestr.length - 1)
|
|
||||||
} else {
|
|
||||||
return mode + '-' + type
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// getEl(el) {
|
|
||||||
// console.log(el || el.ref || null);
|
|
||||||
// return el || el.ref || null
|
|
||||||
// },
|
|
||||||
toLine(name) {
|
|
||||||
return name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.uni-transition {
|
|
||||||
transition-timing-function: ease;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
transition-property: transform, opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-in {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-top-in {
|
|
||||||
/* transition-property: transform, opacity; */
|
|
||||||
transform: translateY(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-top-active {
|
|
||||||
transform: translateY(0);
|
|
||||||
/* opacity: 1; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-right-in {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-right-active {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-bottom-in {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-bottom-active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-left-in {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-left-active {
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoom-in-in {
|
|
||||||
transform: scale(0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoom-out-active {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoom-out-in {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
</style>
|
|
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1,14 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
|
||||||
<title></title>
|
|
||||||
<!--preload-links-->
|
|
||||||
<!--app-context-->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"><!--app-html--></div>
|
|
||||||
<script type="module" src="/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,50 +0,0 @@
|
||||||
import App from './App'
|
|
||||||
|
|
||||||
import uView from '@/uni_modules/uview-ui'
|
|
||||||
Vue.use(uView)
|
|
||||||
|
|
||||||
// 全局 Mixin
|
|
||||||
import mixin from './common/mixin/mixin'
|
|
||||||
Vue.mixin(mixin)
|
|
||||||
|
|
||||||
// 全局 Util
|
|
||||||
import {
|
|
||||||
msg,
|
|
||||||
isLogin,
|
|
||||||
debounce,
|
|
||||||
throttle,
|
|
||||||
prePage,
|
|
||||||
date
|
|
||||||
} from '@/common/js/util'
|
|
||||||
Vue.prototype.$util = {
|
|
||||||
msg,
|
|
||||||
isLogin,
|
|
||||||
debounce,
|
|
||||||
throttle,
|
|
||||||
prePage,
|
|
||||||
date
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全局 Store
|
|
||||||
import store from './store'
|
|
||||||
Vue.prototype.$store = store
|
|
||||||
|
|
||||||
// #ifndef VUE3
|
|
||||||
import Vue from 'vue'
|
|
||||||
Vue.config.productionTip = false
|
|
||||||
App.mpType = 'app'
|
|
||||||
const app = new Vue({
|
|
||||||
...App
|
|
||||||
})
|
|
||||||
app.$mount()
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef VUE3
|
|
||||||
import { createSSRApp } from 'vue'
|
|
||||||
export function createApp() {
|
|
||||||
const app = createSSRApp(App)
|
|
||||||
return {
|
|
||||||
app
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"name" : "ruoyi-vue-ui",
|
|
||||||
"appid" : "__UNI__764D04C",
|
|
||||||
"description" : "",
|
|
||||||
"versionName" : "1.0.0",
|
|
||||||
"versionCode" : "100",
|
|
||||||
"transformPx" : false,
|
|
||||||
/* 5+App特有相关 */
|
|
||||||
"app-plus" : {
|
|
||||||
"usingComponents" : true,
|
|
||||||
"nvueStyleCompiler" : "uni-app",
|
|
||||||
"compilerVersion" : 3,
|
|
||||||
"splashscreen" : {
|
|
||||||
"alwaysShowBeforeRender" : true,
|
|
||||||
"waiting" : true,
|
|
||||||
"autoclose" : true,
|
|
||||||
"delay" : 0
|
|
||||||
},
|
|
||||||
/* 模块配置 */
|
|
||||||
"modules" : {},
|
|
||||||
/* 应用发布信息 */
|
|
||||||
"distribute" : {
|
|
||||||
/* android打包配置 */
|
|
||||||
"android" : {
|
|
||||||
"permissions" : [
|
|
||||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
|
||||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
|
||||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
|
||||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
/* ios打包配置 */
|
|
||||||
"ios" : {},
|
|
||||||
/* SDK配置 */
|
|
||||||
"sdkConfigs" : {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/* 快应用特有相关 */
|
|
||||||
"quickapp" : {},
|
|
||||||
/* 小程序特有相关 */
|
|
||||||
"mp-weixin" : {
|
|
||||||
"appid" : "",
|
|
||||||
"setting" : {
|
|
||||||
"urlCheck" : false
|
|
||||||
},
|
|
||||||
"usingComponents" : true
|
|
||||||
},
|
|
||||||
"mp-alipay" : {
|
|
||||||
"usingComponents" : true
|
|
||||||
},
|
|
||||||
"mp-baidu" : {
|
|
||||||
"usingComponents" : true
|
|
||||||
},
|
|
||||||
"mp-toutiao" : {
|
|
||||||
"usingComponents" : true
|
|
||||||
},
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
},
|
|
||||||
"vueVersion" : "2"
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
"easycom": {
|
|
||||||
"^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue"
|
|
||||||
},
|
|
||||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
|
||||||
{
|
|
||||||
"path": "pages/index/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "uni-app"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/tabbar/user",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "我的",
|
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/auth/login",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "登录",
|
|
||||||
"navigationStyle":"custom",
|
|
||||||
"app-plus": {
|
|
||||||
"animationType": "slide-in-bottom"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path" : "pages/set/userInfo",
|
|
||||||
"style" : {
|
|
||||||
"navigationBarTitleText": "个人资料"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"globalStyle": {
|
|
||||||
"navigationBarTextStyle": "black",
|
|
||||||
"navigationBarTitleText": "uni-app",
|
|
||||||
"navigationBarBackgroundColor": "#F8F8F8",
|
|
||||||
"backgroundColor": "#F8F8F8"
|
|
||||||
},
|
|
||||||
"tabBar": {
|
|
||||||
"color": "#666",
|
|
||||||
"selectedColor": "#FF5A5F",
|
|
||||||
"borderStyle": "black",
|
|
||||||
"list": [{
|
|
||||||
"text": "首页",
|
|
||||||
"pagePath": "pages/index/index",
|
|
||||||
"iconPath": "static/tarbar/index.png",
|
|
||||||
"selectedIconPath": "static/tarbar/index-active.png"
|
|
||||||
}, {
|
|
||||||
"text": "商品",
|
|
||||||
"pagePath": "pages/product/list",
|
|
||||||
"iconPath": "static/tarbar/product.png",
|
|
||||||
"selectedIconPath": "static/tarbar/product-active.png"
|
|
||||||
}, {
|
|
||||||
"text": "我的",
|
|
||||||
"pagePath": "pages/tabbar/user",
|
|
||||||
"iconPath": "static/tarbar/ucenter.png",
|
|
||||||
"selectedIconPath": "static/tarbar/ucenter-active.png"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,326 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="app">
|
|
||||||
<!-- 左上角的 x 关闭 -->
|
|
||||||
<view class="back-btn mix-icon icon-guanbi" @click="navBack"></view>
|
|
||||||
|
|
||||||
<!-- 用户协议 -->
|
|
||||||
<view class="agreement center">
|
|
||||||
<text class="mix-icon icon-xuanzhong" :class="{active: agreement}" @click="checkAgreement"></text>
|
|
||||||
<text @click="checkAgreement">请认真阅读并同意</text>
|
|
||||||
<text class="title" @click="navToAgreementDetail(1)">《用户服务协议》</text>
|
|
||||||
<text class="title" @click="navToAgreementDetail(2)">《隐私权政策》</text>
|
|
||||||
</view>
|
|
||||||
<!-- 登录表单 -->
|
|
||||||
<view class="wrapper">
|
|
||||||
<view class="left-top-sign">LOGIN</view>
|
|
||||||
<view class="welcome">手机登录/注册</view>
|
|
||||||
<!-- 手机验证码登录 -->
|
|
||||||
<view class="input-content">
|
|
||||||
<u--form labelPosition="left" :model="form" :rules="rules" ref="form" errorType="toast">
|
|
||||||
<u-form-item prop="mobile" borderBottom>
|
|
||||||
<u--input type="number" v-model="form.mobile" placeholder="请输入手机号" border="none"></u--input>
|
|
||||||
</u-form-item>
|
|
||||||
<!-- 判断使用验证码还是密码 -->
|
|
||||||
<u-form-item prop="code" borderBottom v-if="loginType == 'code'">
|
|
||||||
<u--input type="number" v-model="form.code" placeholder="请输入验证码" border="none"></u--input>
|
|
||||||
<u-button slot="right" @tap="getCode" :text="uCode.tips" type="success" size="mini" :disabled="uCode.disabled"></u-button>
|
|
||||||
<u-code ref="uCode" @change="codeChange" seconds="60" @start="uCode.disabled === true" @end="uCode.disabled === false"></u-code>
|
|
||||||
</u-form-item>
|
|
||||||
<u-form-item prop="password" borderBottom v-else>
|
|
||||||
<u--input password v-model="form.password" placeholder="请输入密码" border="none"></u--input>
|
|
||||||
</u-form-item>
|
|
||||||
</u--form>
|
|
||||||
|
|
||||||
<u-button class="login-button" text="立即登录" type="error" shape="circle" @click="mobileLogin"
|
|
||||||
:loading="loading"></u-button>
|
|
||||||
<!-- 切换登陆 -->
|
|
||||||
<view class="login-type" v-if="loginType == 'code'" @click="setLoginType('password')">账号密码登录</view>
|
|
||||||
<view class="login-type" v-else @click="setLoginType('code')">免密登录</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 快捷登录 -->
|
|
||||||
<!-- #ifdef APP-PLUS || MP-WEIXIN -->
|
|
||||||
<view class="other-wrapper">
|
|
||||||
<view class="line center">
|
|
||||||
<text class="title">快捷登录</text>
|
|
||||||
</view>
|
|
||||||
<view class="list row">
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
|
||||||
<view class="item column center" @click="mpWxGetUserInfo">
|
|
||||||
<image class="icon" src="/static/icon/login-wx.png"></image>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- #ifdef APP-PLUS -->
|
|
||||||
<view class="item column center" style="width: 180rpx;" @click="loginByWxApp">
|
|
||||||
<image class="icon" src="/static/icon/login-wx.png"></image>
|
|
||||||
<text>微信登录</text>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { checkStr } from '@/common/js/util'
|
|
||||||
import { login, smsLogin, sendSmsCode } from '@/api/system/auth.js'
|
|
||||||
import loginMpWx from './mixin/login-mp-wx.js'
|
|
||||||
import loginAppWx from './mixin/login-app-wx.js'
|
|
||||||
|
|
||||||
export default{
|
|
||||||
mixins: [loginMpWx, loginAppWx],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
agreement: true,
|
|
||||||
loginType: 'code', // 登录方式,code 验证码;password 密码
|
|
||||||
loading: false, // 表单提交
|
|
||||||
rules: {
|
|
||||||
mobile: [{
|
|
||||||
required: true,
|
|
||||||
message: '请输入手机号'
|
|
||||||
}, {
|
|
||||||
validator: (rule, value, callback) => {
|
|
||||||
return uni.$u.test.mobile(value);
|
|
||||||
},
|
|
||||||
message: '手机号码不正确'
|
|
||||||
}],
|
|
||||||
code: [],
|
|
||||||
password: []
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
mobile: '',
|
|
||||||
code: '',
|
|
||||||
password: '',
|
|
||||||
},
|
|
||||||
uCode: {
|
|
||||||
tips: '',
|
|
||||||
disable: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLoad() {
|
|
||||||
this.setLoginType(this.loginType);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 手机号登录
|
|
||||||
mobileLogin() {
|
|
||||||
if (!this.agreement) {
|
|
||||||
this.$util.msg('请阅读并同意用户服务及隐私协议');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$refs.form.validate().then(() => {
|
|
||||||
this.loading = true;
|
|
||||||
// 执行登陆
|
|
||||||
const { mobile, code, password} = this.form;
|
|
||||||
const loginPromise = this.loginType == 'password' ? login(mobile, password) :
|
|
||||||
smsLogin(mobile, code);
|
|
||||||
loginPromise.then(data => {
|
|
||||||
// 登陆成功
|
|
||||||
this.loginSuccessCallBack(data);
|
|
||||||
}).catch(errors => {
|
|
||||||
}).finally(() => {
|
|
||||||
this.loading = false;
|
|
||||||
})
|
|
||||||
}).catch(errors => {
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// 登陆成功的处理逻辑
|
|
||||||
loginSuccessCallBack(data) {
|
|
||||||
this.$util.msg('登录成功');
|
|
||||||
this.$store.commit('setToken', data);
|
|
||||||
// TODO 芋艿:如果当前页是第一页,则无法返回。期望是能够回到首页
|
|
||||||
setTimeout(()=>{
|
|
||||||
uni.navigateBack();
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
navBack() {
|
|
||||||
uni.navigateBack({
|
|
||||||
delta: 1
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setLoginType(loginType) {
|
|
||||||
this.loginType = loginType;
|
|
||||||
// 修改校验规则
|
|
||||||
this.rules.code = [];
|
|
||||||
this.rules.password = [];
|
|
||||||
if (loginType == 'code') {
|
|
||||||
this.rules.code = [{
|
|
||||||
required: true,
|
|
||||||
message: '请输入验证码'
|
|
||||||
}, {
|
|
||||||
min: 4,
|
|
||||||
max: 6,
|
|
||||||
message: '验证码不正确'
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
this.rules.password = [{
|
|
||||||
required: true,
|
|
||||||
message: '请输入密码'
|
|
||||||
}, {
|
|
||||||
min: 4,
|
|
||||||
max: 16,
|
|
||||||
message: '密码不正确'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//同意协议
|
|
||||||
checkAgreement(){
|
|
||||||
this.agreement = !this.agreement;
|
|
||||||
},
|
|
||||||
//打开协议
|
|
||||||
navToAgreementDetail(type){
|
|
||||||
this.navTo('/pages/public/article?param=' + JSON.stringify({
|
|
||||||
module: 'article',
|
|
||||||
operation: 'getAgreement',
|
|
||||||
data: {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
codeChange(text) {
|
|
||||||
this.uCode.tips = text;
|
|
||||||
},
|
|
||||||
getCode() {
|
|
||||||
// 处于发送中,则跳过
|
|
||||||
if (!this.$refs.uCode.canGetCode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 校验手机号
|
|
||||||
this.$refs.form.validateField('mobile', errors => {
|
|
||||||
if (errors.length > 0) {
|
|
||||||
uni.$u.toast(errors[0].message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 发送验证码 TODO 芋艿,枚举类
|
|
||||||
sendSmsCode(this.form.mobile, 1).then(data => {
|
|
||||||
uni.$u.toast('验证码已发送');
|
|
||||||
// 通知验证码组件内部开始倒计时
|
|
||||||
this.$refs.uCode.start();
|
|
||||||
}).catch(erros => {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
page {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style scoped lang='scss'>
|
|
||||||
.app {
|
|
||||||
padding-top: 15vh;
|
|
||||||
position:relative;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.wrapper {
|
|
||||||
position:relative;
|
|
||||||
z-index: 90;
|
|
||||||
padding-bottom: 40rpx;
|
|
||||||
.welcome {
|
|
||||||
position:relative;
|
|
||||||
left: 50rpx;
|
|
||||||
top: -90rpx;
|
|
||||||
font-size: 46rpx;
|
|
||||||
color: #555;
|
|
||||||
text-shadow: 1px 0px 1px rgba(0,0,0,.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.back-btn {
|
|
||||||
position:absolute;
|
|
||||||
left: 20rpx;
|
|
||||||
top: calc(var(--status-bar-height) + 20rpx);
|
|
||||||
z-index: 90;
|
|
||||||
padding: 20rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
.left-top-sign {
|
|
||||||
font-size: 120rpx;
|
|
||||||
color: #f8f8f8;
|
|
||||||
position: relative;
|
|
||||||
left: -12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 手机登录部分 */
|
|
||||||
.input-content {
|
|
||||||
padding: 0 60rpx;
|
|
||||||
.login-button {
|
|
||||||
margin-top: 30rpx;
|
|
||||||
}
|
|
||||||
.login-type {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #40a2ff;
|
|
||||||
margin-top: 20rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 其他登录方式 */
|
|
||||||
.other-wrapper{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 20rpx;
|
|
||||||
margin-top: 80rpx;
|
|
||||||
.line{
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
.title {
|
|
||||||
margin: 0 32rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
&:before, &:after{
|
|
||||||
content: '';
|
|
||||||
width: 160rpx;
|
|
||||||
height: 0;
|
|
||||||
border-top: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.item{
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #606266;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 0;
|
|
||||||
&:after{
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.icon{
|
|
||||||
width: 90rpx;
|
|
||||||
height: 90rpx;
|
|
||||||
margin: 0 24rpx 16rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.agreement{
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 6vh;
|
|
||||||
z-index: 1;
|
|
||||||
width: 750rpx;
|
|
||||||
height: 90rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
.mix-icon{
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #ccc;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
margin-top: 1px;
|
|
||||||
&.active{
|
|
||||||
color: $base-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
color: #40a2ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,73 +0,0 @@
|
||||||
export default{
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* 微信App登录
|
|
||||||
* "openId": "o0yywwGWxtBCvBuE8vH4Naof0cqU",
|
|
||||||
* "nickName": "S .",
|
|
||||||
* "gender": 1,
|
|
||||||
* "city": "临沂",
|
|
||||||
* "province": "山东",
|
|
||||||
* "country": "中国",
|
|
||||||
* "avatarUrl": "http://thirdwx.qlogo.cn/mmopen/vi_32/xqpCtHRBBmdlf201Fykhtx7P7JcicIbgV3Weic1oOvN6iaR3tEbuu74f2fkKQWXvzK3VDgNTZzgf0g8FqPvq8LCNQ/132",
|
|
||||||
* "unionId": "oYqy4wmMcs78x9P-tsyMeM3MQ1PU"
|
|
||||||
*/
|
|
||||||
loginByWxApp(userInfoData){
|
|
||||||
if(!this.agreement){
|
|
||||||
this.$util.msg('请阅读并同意用户服务及隐私协议');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$util.throttle(async ()=>{
|
|
||||||
let [err, res] = await uni.login({
|
|
||||||
provider: 'weixin'
|
|
||||||
})
|
|
||||||
if(err){
|
|
||||||
console.log(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.getUserInfo({
|
|
||||||
provider: 'weixin',
|
|
||||||
success: async res=>{
|
|
||||||
const response = await this.$request('user', 'loginByWeixin', {
|
|
||||||
userInfo: res.userInfo,
|
|
||||||
}, {
|
|
||||||
showLoading: true
|
|
||||||
});
|
|
||||||
if(response.status === 0){
|
|
||||||
this.$util.msg(response.msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(response.hasBindMobile && response.data.token){
|
|
||||||
this.loginSuccessCallBack({
|
|
||||||
token: response.data.token,
|
|
||||||
tokenExpired: response.data.tokenExpired
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
this.navTo('/pages/auth/bindMobile?data='+JSON.stringify(response.data))
|
|
||||||
}
|
|
||||||
plus.oauth.getServices(oauthRes=>{
|
|
||||||
oauthRes[0].logout(logoutRes => {
|
|
||||||
console.log(logoutRes);
|
|
||||||
}, error => {
|
|
||||||
console.log(error);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fail(err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
export default{
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
data(){
|
|
||||||
return {
|
|
||||||
mpCodeTimer: 0,
|
|
||||||
mpWxCode: '',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
timerIdent(){
|
|
||||||
return this.$store.state.timerIdent;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
timerIdent(){
|
|
||||||
this.mpCodeTimer ++;
|
|
||||||
if(this.mpCodeTimer % 30 === 0){
|
|
||||||
this.getMpWxCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onShow(){
|
|
||||||
this.getMpWxCode();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
//微信小程序登录
|
|
||||||
mpWxGetUserInfo(){
|
|
||||||
if(!this.agreement){
|
|
||||||
this.$util.msg('请阅读并同意用户服务及隐私协议');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$util.throttle(()=>{
|
|
||||||
uni.getUserProfile({
|
|
||||||
desc: '用于展示您的头像及昵称',
|
|
||||||
success: async profileRes=> {
|
|
||||||
const res = await this.$request('user', 'loginByWeixin', {
|
|
||||||
code: this.mpWxCode,
|
|
||||||
...profileRes.userInfo
|
|
||||||
}, {
|
|
||||||
showLoading: true
|
|
||||||
});
|
|
||||||
if(res.status === 0){
|
|
||||||
this.$util.msg(res.msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(res.hasBindMobile && res.data.token){
|
|
||||||
this.loginSuccessCallBack({
|
|
||||||
token: res.data.token,
|
|
||||||
tokenExpired: res.data.tokenExpired
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
this.navTo('/pages/auth/bindMobile?data='+JSON.stringify(res.data))
|
|
||||||
}
|
|
||||||
console.log(res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//获取code
|
|
||||||
getMpWxCode(){
|
|
||||||
uni.login({
|
|
||||||
provider: 'weixin',
|
|
||||||
success: res=> {
|
|
||||||
this.mpWxCode = res.code;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="content">
|
|
||||||
<image class="logo" src="/static/logo.png"></image>
|
|
||||||
<view class="text-area">
|
|
||||||
<text class="title">{{title}}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
title: 'Hello'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLoad() {
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 200rpx;
|
|
||||||
width: 200rpx;
|
|
||||||
margin-top: 200rpx;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-bottom: 50rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-area {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #8f8f94;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,633 +0,0 @@
|
||||||
(function(global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
||||||
typeof define === 'function' && define.amd ? define(factory) :
|
|
||||||
(global.weCropper = factory());
|
|
||||||
}(this, (function() {
|
|
||||||
'use strict';
|
|
||||||
var device = void 0;
|
|
||||||
var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
|
|
||||||
|
|
||||||
function firstLetterUpper(str) {
|
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTouchState(instance) {
|
|
||||||
for (var _len = arguments.length, arg = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
||||||
arg[_key - 1] = arguments[_key];
|
|
||||||
}
|
|
||||||
|
|
||||||
TOUCH_STATE.forEach(function(key, i) {
|
|
||||||
if (arg[i] !== undefined) {
|
|
||||||
instance[key] = arg[i];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function validator(instance, o) {
|
|
||||||
Object.defineProperties(instance, o);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDevice() {
|
|
||||||
if (!device) {
|
|
||||||
device = wx.getSystemInfoSync();
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) {
|
|
||||||
return typeof obj;
|
|
||||||
} : function(obj) {
|
|
||||||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" :
|
|
||||||
typeof obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var classCallCheck = function(instance, Constructor) {
|
|
||||||
if (!(instance instanceof Constructor)) {
|
|
||||||
throw new TypeError("Cannot call a class as a function");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var createClass = function() {
|
|
||||||
function defineProperties(target, props) {
|
|
||||||
for (var i = 0; i < props.length; i++) {
|
|
||||||
var descriptor = props[i];
|
|
||||||
descriptor.enumerable = descriptor.enumerable || false;
|
|
||||||
descriptor.configurable = true;
|
|
||||||
if ("value" in descriptor) descriptor.writable = true;
|
|
||||||
Object.defineProperty(target, descriptor.key, descriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(Constructor, protoProps, staticProps) {
|
|
||||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
||||||
if (staticProps) defineProperties(Constructor, staticProps);
|
|
||||||
return Constructor;
|
|
||||||
};
|
|
||||||
}();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var slicedToArray = function() {
|
|
||||||
function sliceIterator(arr, i) {
|
|
||||||
var _arr = [];
|
|
||||||
var _n = true;
|
|
||||||
var _d = false;
|
|
||||||
var _e = undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
|
|
||||||
_arr.push(_s.value);
|
|
||||||
|
|
||||||
if (i && _arr.length === i) break;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
_d = true;
|
|
||||||
_e = err;
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (!_n && _i["return"]) _i["return"]();
|
|
||||||
} finally {
|
|
||||||
if (_d) throw _e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(arr, i) {
|
|
||||||
if (Array.isArray(arr)) {
|
|
||||||
return arr;
|
|
||||||
} else if (Symbol.iterator in Object(arr)) {
|
|
||||||
return sliceIterator(arr, i);
|
|
||||||
} else {
|
|
||||||
throw new TypeError("Invalid attempt to destructure non-iterable instance");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}();
|
|
||||||
|
|
||||||
var tmp = {};
|
|
||||||
|
|
||||||
var DEFAULT = {
|
|
||||||
id: {
|
|
||||||
default: 'cropper',
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.id;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
if (typeof value !== 'string') {}
|
|
||||||
tmp.id = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
default: 750,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.width;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.width = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
default: 750,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.height;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.height = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scale: {
|
|
||||||
default: 2.5,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.scale;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.scale = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
zoom: {
|
|
||||||
default: 5,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.zoom;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.zoom = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
src: {
|
|
||||||
default: 'cropper',
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.src;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.src = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cut: {
|
|
||||||
default: {},
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.cut;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.cut = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReady: {
|
|
||||||
default: null,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.ready;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.ready = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBeforeImageLoad: {
|
|
||||||
default: null,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.beforeImageLoad;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.beforeImageLoad = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onImageLoad: {
|
|
||||||
default: null,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.imageLoad;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.imageLoad = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBeforeDraw: {
|
|
||||||
default: null,
|
|
||||||
get: function get$$1() {
|
|
||||||
return tmp.beforeDraw;
|
|
||||||
},
|
|
||||||
set: function set$$1(value) {
|
|
||||||
tmp.beforeDraw = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function prepare() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var _getDevice = getDevice(),
|
|
||||||
windowWidth = _getDevice.windowWidth;
|
|
||||||
|
|
||||||
self.attachPage = function() {
|
|
||||||
var pages = getCurrentPages();
|
|
||||||
var pageContext = pages[pages.length - 1];
|
|
||||||
pageContext.wecropper = self;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.createCtx = function() {
|
|
||||||
var id = self.id;
|
|
||||||
|
|
||||||
if (id) {
|
|
||||||
self.ctx = wx.createCanvasContext(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.deviceRadio = windowWidth / 750;
|
|
||||||
self.deviceRadio = self.deviceRadio.toFixed(2)
|
|
||||||
}
|
|
||||||
function observer() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
|
|
||||||
|
|
||||||
self.on = function(event, fn) {
|
|
||||||
if (EVENT_TYPE.indexOf(event) > -1) {
|
|
||||||
if (typeof fn === 'function') {
|
|
||||||
event === 'ready' ? fn(self) : self['on' + firstLetterUpper(event)] = fn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function methods() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var deviceRadio = self.deviceRadio;
|
|
||||||
|
|
||||||
var boundWidth = self.width;
|
|
||||||
var boundHeight = self.height;
|
|
||||||
var _self$cut = self.cut,
|
|
||||||
_self$cut$x = _self$cut.x,
|
|
||||||
x = _self$cut$x === undefined ? 0 : _self$cut$x,
|
|
||||||
_self$cut$y = _self$cut.y,
|
|
||||||
y = _self$cut$y === undefined ? 0 : _self$cut$y,
|
|
||||||
_self$cut$width = _self$cut.width,
|
|
||||||
width = _self$cut$width === undefined ? boundWidth : _self$cut$width,
|
|
||||||
_self$cut$height = _self$cut.height,
|
|
||||||
height = _self$cut$height === undefined ? boundHeight : _self$cut$height;
|
|
||||||
|
|
||||||
|
|
||||||
self.updateCanvas = function() {
|
|
||||||
if (self.croperTarget) {
|
|
||||||
|
|
||||||
|
|
||||||
self.ctx.drawImage(self.croperTarget, self.imgLeft, self.imgTop, self.scaleWidth, self.scaleHeight);
|
|
||||||
}
|
|
||||||
typeof self.onBeforeDraw === 'function' && self.onBeforeDraw(self.ctx, self);
|
|
||||||
|
|
||||||
self.setBoundStyle();
|
|
||||||
self.ctx.draw();
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.pushOrign = function(src) {
|
|
||||||
self.src = src;
|
|
||||||
|
|
||||||
typeof self.onBeforeImageLoad === 'function' && self.onBeforeImageLoad(self.ctx, self);
|
|
||||||
|
|
||||||
uni.getImageInfo({
|
|
||||||
src: src,
|
|
||||||
success: function success(res) {
|
|
||||||
var innerAspectRadio = res.width / res.height;
|
|
||||||
self.croperTarget = res.path || src;
|
|
||||||
if (innerAspectRadio < width / height) {
|
|
||||||
self.rectX = x;
|
|
||||||
self.baseWidth = width;
|
|
||||||
self.baseHeight = width / innerAspectRadio;
|
|
||||||
self.rectY = y - Math.abs((height - self.baseHeight) / 2);
|
|
||||||
} else {
|
|
||||||
self.rectY = y;
|
|
||||||
self.baseWidth = height * innerAspectRadio;
|
|
||||||
self.baseHeight = height;
|
|
||||||
self.rectX = x - Math.abs((width - self.baseWidth) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imgLeft = self.rectX;
|
|
||||||
self.imgTop = self.rectY;
|
|
||||||
self.scaleWidth = self.baseWidth;
|
|
||||||
self.scaleHeight = self.baseHeight;
|
|
||||||
|
|
||||||
self.updateCanvas();
|
|
||||||
|
|
||||||
typeof self.onImageLoad === 'function' && self.onImageLoad(self.ctx, self);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.update();
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.getCropperImage = function() {
|
|
||||||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
|
||||||
args[_key] = arguments[_key];
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = self.id;
|
|
||||||
|
|
||||||
var ARG_TYPE = toString.call(args[0]);
|
|
||||||
switch (ARG_TYPE) {
|
|
||||||
case '[object Object]':
|
|
||||||
var _args$0$quality = args[0].quality,
|
|
||||||
quality = _args$0$quality === undefined ? 10 : _args$0$quality;
|
|
||||||
|
|
||||||
uni.canvasToTempFilePath({
|
|
||||||
canvasId: id,
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
fileType: "jpg",
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
destWidth: width * quality / (deviceRadio * 10),
|
|
||||||
destHeight: height * quality / (deviceRadio * 10),
|
|
||||||
success: function success(res) {
|
|
||||||
typeof args[args.length - 1] === 'function' && args[args.length - 1](res.tempFilePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case '[object Function]':
|
|
||||||
uni.canvasToTempFilePath({
|
|
||||||
canvasId: id,
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
fileType: "jpg",
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
destWidth: width,
|
|
||||||
destHeight: height,
|
|
||||||
success: function success(res) {
|
|
||||||
|
|
||||||
typeof args[args.length - 1] === 'function' && args[args.length - 1](res.tempFilePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var self = this;
|
|
||||||
if (!self.src) return;
|
|
||||||
|
|
||||||
self.__oneTouchStart = function(touch) {
|
|
||||||
self.touchX0 = touch.x;
|
|
||||||
self.touchY0 = touch.y;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.__oneTouchMove = function(touch) {
|
|
||||||
var xMove = void 0,
|
|
||||||
yMove = void 0;
|
|
||||||
if (self.touchended) {
|
|
||||||
return self.updateCanvas();
|
|
||||||
}
|
|
||||||
xMove = touch.x - self.touchX0;
|
|
||||||
yMove = touch.y - self.touchY0;
|
|
||||||
|
|
||||||
var imgLeft = self.rectX + xMove;
|
|
||||||
var imgTop = self.rectY + yMove;
|
|
||||||
|
|
||||||
self.outsideBound(imgLeft, imgTop);
|
|
||||||
|
|
||||||
self.updateCanvas();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.__twoTouchStart = function(touch0, touch1) {
|
|
||||||
var xMove = void 0,
|
|
||||||
yMove = void 0,
|
|
||||||
oldDistance = void 0;
|
|
||||||
|
|
||||||
self.touchX1 = self.rectX + self.scaleWidth / 2;
|
|
||||||
self.touchY1 = self.rectY + self.scaleHeight / 2;
|
|
||||||
|
|
||||||
xMove = touch1.x - touch0.x;
|
|
||||||
yMove = touch1.y - touch0.y;
|
|
||||||
oldDistance = Math.sqrt(xMove * xMove + yMove * yMove);
|
|
||||||
|
|
||||||
self.oldDistance = oldDistance;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.__twoTouchMove = function(touch0, touch1) {
|
|
||||||
var xMove = void 0,
|
|
||||||
yMove = void 0,
|
|
||||||
newDistance = void 0;
|
|
||||||
var scale = self.scale,
|
|
||||||
zoom = self.zoom;
|
|
||||||
|
|
||||||
xMove = touch1.x - touch0.x;
|
|
||||||
yMove = touch1.y - touch0.y;
|
|
||||||
newDistance = Math.sqrt(xMove * xMove + yMove * yMove
|
|
||||||
|
|
||||||
// 使用0.005的缩放倍数具有良好的缩放体验
|
|
||||||
);
|
|
||||||
self.newScale = self.oldScale + 0.001 * zoom * (newDistance - self.oldDistance);
|
|
||||||
|
|
||||||
// 设定缩放范围
|
|
||||||
self.newScale <= 1 && (self.newScale = 1);
|
|
||||||
self.newScale >= scale && (self.newScale = scale);
|
|
||||||
|
|
||||||
self.scaleWidth = self.newScale * self.baseWidth;
|
|
||||||
self.scaleHeight = self.newScale * self.baseHeight;
|
|
||||||
var imgLeft = self.touchX1 - self.scaleWidth / 2;
|
|
||||||
var imgTop = self.touchY1 - self.scaleHeight / 2;
|
|
||||||
|
|
||||||
self.outsideBound(imgLeft, imgTop);
|
|
||||||
|
|
||||||
self.updateCanvas();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.__xtouchEnd = function() {
|
|
||||||
self.oldScale = self.newScale;
|
|
||||||
self.rectX = self.imgLeft;
|
|
||||||
self.rectY = self.imgTop;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var handle = {
|
|
||||||
touchStart: function touchStart(e) {
|
|
||||||
var self = this;
|
|
||||||
var _e$touches = slicedToArray(e.touches, 2),
|
|
||||||
touch0 = _e$touches[0],
|
|
||||||
touch1 = _e$touches[1];
|
|
||||||
|
|
||||||
if (!touch0.x) {
|
|
||||||
touch0.x = touch0.clientX;
|
|
||||||
touch0.y = touch0.clientY;
|
|
||||||
if (touch1) {
|
|
||||||
touch1.x = touch1.clientX;
|
|
||||||
touch1.y = touch1.clientY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTouchState(self, true, null, null);
|
|
||||||
self.__oneTouchStart(touch0);
|
|
||||||
if (e.touches.length >= 2) {
|
|
||||||
self.__twoTouchStart(touch0, touch1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
touchMove: function touchMove(e) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var _e$touches2 = slicedToArray(e.touches, 2),
|
|
||||||
touch0 = _e$touches2[0],
|
|
||||||
touch1 = _e$touches2[1];
|
|
||||||
if (!touch0.x) {
|
|
||||||
touch0.x = touch0.clientX;
|
|
||||||
touch0.y = touch0.clientY;
|
|
||||||
if (touch1) {
|
|
||||||
touch1.x = touch1.clientX;
|
|
||||||
touch1.y = touch1.clientY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTouchState(self, null, true);
|
|
||||||
if (e.touches.length === 1) {
|
|
||||||
self.__oneTouchMove(touch0);
|
|
||||||
}
|
|
||||||
if (e.touches.length >= 2) {
|
|
||||||
self.__twoTouchMove(touch0, touch1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
touchEnd: function touchEnd(e) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
setTouchState(self, false, false, true);
|
|
||||||
self.__xtouchEnd();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function cut() {
|
|
||||||
var self = this;
|
|
||||||
var deviceRadio = self.deviceRadio;
|
|
||||||
|
|
||||||
var boundWidth = self.width;
|
|
||||||
var boundHeight = self.height;
|
|
||||||
var _self$cut = self.cut,
|
|
||||||
_self$cut$x = _self$cut.x,
|
|
||||||
x = _self$cut$x === undefined ? 0 : _self$cut$x,
|
|
||||||
_self$cut$y = _self$cut.y,
|
|
||||||
y = _self$cut$y === undefined ? 0 : _self$cut$y,
|
|
||||||
_self$cut$width = _self$cut.width,
|
|
||||||
width = _self$cut$width === undefined ? boundWidth : _self$cut$width,
|
|
||||||
_self$cut$height = _self$cut.height,
|
|
||||||
height = _self$cut$height === undefined ? boundHeight : _self$cut$height;
|
|
||||||
|
|
||||||
|
|
||||||
self.outsideBound = function(imgLeft, imgTop) {
|
|
||||||
self.imgLeft = imgLeft >= x ? x : self.scaleWidth + imgLeft - x <= width ? x + width - self.scaleWidth : imgLeft;
|
|
||||||
|
|
||||||
self.imgTop = imgTop >= y ? y : self.scaleHeight + imgTop - y <= height ? y + height - self.scaleHeight : imgTop;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setBoundStyle = function() {
|
|
||||||
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
||||||
_ref$color = _ref.color,
|
|
||||||
color = _ref$color === undefined ? '#04b00f' : _ref$color,
|
|
||||||
_ref$mask = _ref.mask,
|
|
||||||
mask = _ref$mask === undefined ? 'rgba(0, 0, 0, 0.5)' : _ref$mask,
|
|
||||||
_ref$lineWidth = _ref.lineWidth,
|
|
||||||
lineWidth = _ref$lineWidth === undefined ? 1 : _ref$lineWidth;
|
|
||||||
|
|
||||||
self.ctx.beginPath();
|
|
||||||
self.ctx.setFillStyle(mask);
|
|
||||||
self.ctx.fillRect(0, 0, x, boundHeight);
|
|
||||||
self.ctx.fillRect(x, 0, width, y);
|
|
||||||
self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
|
|
||||||
self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
|
|
||||||
self.ctx.fill();
|
|
||||||
self.ctx.beginPath();
|
|
||||||
self.ctx.setStrokeStyle(color);
|
|
||||||
self.ctx.setLineWidth(lineWidth);
|
|
||||||
self.ctx.moveTo(x - lineWidth, y + 10 - lineWidth);
|
|
||||||
self.ctx.lineTo(x - lineWidth, y - lineWidth);
|
|
||||||
self.ctx.lineTo(x + 10 - lineWidth, y - lineWidth);
|
|
||||||
self.ctx.stroke();
|
|
||||||
self.ctx.beginPath();
|
|
||||||
self.ctx.setStrokeStyle(color);
|
|
||||||
self.ctx.setLineWidth(lineWidth);
|
|
||||||
self.ctx.moveTo(x - lineWidth, y + height - 10 + lineWidth);
|
|
||||||
self.ctx.lineTo(x - lineWidth, y + height + lineWidth);
|
|
||||||
self.ctx.lineTo(x + 10 - lineWidth, y + height + lineWidth);
|
|
||||||
self.ctx.stroke();
|
|
||||||
self.ctx.beginPath();
|
|
||||||
self.ctx.setStrokeStyle(color);
|
|
||||||
self.ctx.setLineWidth(lineWidth);
|
|
||||||
self.ctx.moveTo(x + width - 10 + lineWidth, y - lineWidth);
|
|
||||||
self.ctx.lineTo(x + width + lineWidth, y - lineWidth);
|
|
||||||
self.ctx.lineTo(x + width + lineWidth, y + 10 - lineWidth);
|
|
||||||
self.ctx.stroke();
|
|
||||||
self.ctx.beginPath();
|
|
||||||
self.ctx.setStrokeStyle(color);
|
|
||||||
self.ctx.setLineWidth(lineWidth);
|
|
||||||
self.ctx.moveTo(x + width + lineWidth, y + height - 10 + lineWidth);
|
|
||||||
self.ctx.lineTo(x + width + lineWidth, y + height + lineWidth);
|
|
||||||
self.ctx.lineTo(x + width - 10 + lineWidth, y + height + lineWidth);
|
|
||||||
self.ctx.stroke();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var __version__ = '1.1.4';
|
|
||||||
|
|
||||||
var weCropper = function() {
|
|
||||||
function weCropper(params) {
|
|
||||||
classCallCheck(this, weCropper);
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var _default = {};
|
|
||||||
|
|
||||||
validator(self, DEFAULT);
|
|
||||||
|
|
||||||
Object.keys(DEFAULT).forEach(function(key) {
|
|
||||||
_default[key] = DEFAULT[key].default;
|
|
||||||
});
|
|
||||||
Object.assign(self, _default, params);
|
|
||||||
|
|
||||||
self.prepare();
|
|
||||||
self.attachPage();
|
|
||||||
self.createCtx();
|
|
||||||
self.observer();
|
|
||||||
self.cutt();
|
|
||||||
self.methods();
|
|
||||||
self.init();
|
|
||||||
self.update();
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
createClass(weCropper, [{
|
|
||||||
key: 'init',
|
|
||||||
value: function init() {
|
|
||||||
var self = this;
|
|
||||||
var src = self.src;
|
|
||||||
|
|
||||||
|
|
||||||
self.version = __version__;
|
|
||||||
|
|
||||||
typeof self.onReady === 'function' && self.onReady(self.ctx, self);
|
|
||||||
|
|
||||||
if (src) {
|
|
||||||
self.pushOrign(src);
|
|
||||||
}
|
|
||||||
setTouchState(self, false, false, false);
|
|
||||||
|
|
||||||
self.oldScale = 1;
|
|
||||||
self.newScale = 1;
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
}]);
|
|
||||||
return weCropper;
|
|
||||||
}();
|
|
||||||
|
|
||||||
Object.assign(weCropper.prototype, handle);
|
|
||||||
|
|
||||||
|
|
||||||
weCropper.prototype.prepare = prepare;
|
|
||||||
weCropper.prototype.observer = observer;
|
|
||||||
weCropper.prototype.methods = methods;
|
|
||||||
weCropper.prototype.cutt = cut;
|
|
||||||
weCropper.prototype.update = update;
|
|
||||||
|
|
||||||
return weCropper;
|
|
||||||
|
|
||||||
})));
|
|
|
@ -1,223 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="content">
|
|
||||||
<view class="title-view" :style="{height: navigationBarHeight + 'px'}">
|
|
||||||
<navigator open-type="navigateBack" class="back-btn mix-icon icon-xiangzuo"></navigator>
|
|
||||||
<text class="title">裁剪</text>
|
|
||||||
<text class="empty"></text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="cropper-wrapper">
|
|
||||||
<canvas
|
|
||||||
class="cropper"
|
|
||||||
disable-scroll="true"
|
|
||||||
@touchstart="touchStart"
|
|
||||||
@touchmove="touchMove"
|
|
||||||
@touchend="touchEnd"
|
|
||||||
:style="{ width: cropperOpt.width, height: cropperOpt.height }"
|
|
||||||
canvas-id="cropper"
|
|
||||||
></canvas>
|
|
||||||
</view>
|
|
||||||
<view class="cropper-buttons">
|
|
||||||
<view class="btn upload" @tap="uploadTap">重选</view>
|
|
||||||
<view class="btn getCropperImage" @tap="getCropperImage">确定</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import weCropper from './cut.js';
|
|
||||||
const device = uni.getSystemInfoSync();
|
|
||||||
const width = device.windowWidth;
|
|
||||||
const height = device.windowHeight;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
cropperOpt: {
|
|
||||||
id: 'cropper',
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
scale: 2.5,
|
|
||||||
zoom: 8,
|
|
||||||
cut: {
|
|
||||||
x: (width - 200) / 2,
|
|
||||||
y: (height - this.systemInfo.navigationBarHeight - this.systemInfo.statusBarHeight - 200) / 2,
|
|
||||||
width: 200,
|
|
||||||
height: 200
|
|
||||||
}
|
|
||||||
},
|
|
||||||
weCropper: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
navigationBarHeight(){
|
|
||||||
console.log(this.systemInfo.navigationBarHeight);
|
|
||||||
return this.systemInfo.navigationBarHeight;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLoad(option) {
|
|
||||||
// do something
|
|
||||||
const cropperOpt = this.cropperOpt;
|
|
||||||
const src = option.src;
|
|
||||||
console.log(src);
|
|
||||||
if (src) {
|
|
||||||
Object.assign(cropperOpt, {
|
|
||||||
src
|
|
||||||
});
|
|
||||||
|
|
||||||
this.weCropper = new weCropper(cropperOpt)
|
|
||||||
.on('ready', function(ctx) {})
|
|
||||||
.on('beforeImageLoad', ctx => {
|
|
||||||
/* uni.showToast({
|
|
||||||
title: '上传中',
|
|
||||||
icon: 'loading',
|
|
||||||
duration: 3000
|
|
||||||
}); */
|
|
||||||
})
|
|
||||||
.on('imageLoad', ctx => {
|
|
||||||
uni.hideToast();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
touchStart(e) {
|
|
||||||
this.weCropper.touchStart(e);
|
|
||||||
},
|
|
||||||
touchMove(e) {
|
|
||||||
this.weCropper.touchMove(e);
|
|
||||||
},
|
|
||||||
touchEnd(e) {
|
|
||||||
this.weCropper.touchEnd(e);
|
|
||||||
},
|
|
||||||
convertBase64UrlToBlob(dataURI, type) {
|
|
||||||
var binary = atob(dataURI.split(',')[1]);
|
|
||||||
var array = [];
|
|
||||||
for (var i = 0; i < binary.length; i++) {
|
|
||||||
array.push(binary.charCodeAt(i));
|
|
||||||
}
|
|
||||||
return new Blob([new Uint8Array(array)], {
|
|
||||||
type: type
|
|
||||||
}, {
|
|
||||||
filename: '1111.jpg'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
blobToDataURL(blob) {
|
|
||||||
var a = new FileReader();
|
|
||||||
a.readAsDataURL(blob); //读取文件保存在result中
|
|
||||||
a.onload = function(e) {
|
|
||||||
var getRes = e.target.result; //读取的结果在result中
|
|
||||||
console.log(getRes);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getCropperImage() {
|
|
||||||
let _this = this;
|
|
||||||
this.weCropper.getCropperImage(avatar => {
|
|
||||||
if (avatar) {
|
|
||||||
this.$util.prePage().setAvatar(avatar);
|
|
||||||
uni.navigateBack();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log('获取图片失败,请稍后重试');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
uploadTap() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
uni.chooseImage({
|
|
||||||
count: 1, // 默认9
|
|
||||||
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
|
|
||||||
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
|
|
||||||
success(res) {
|
|
||||||
let src = res.tempFilePaths[0];
|
|
||||||
// 获取裁剪图片资源后,给data添加src属性及其值
|
|
||||||
self.weCropper.pushOrign(src);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
page, .content{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: #000;
|
|
||||||
padding-top: var(--status-bar-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-view{
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
.back-btn{
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 42px;
|
|
||||||
height: 40px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
font-size: 17px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.empty{
|
|
||||||
width: 42px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cropper {
|
|
||||||
width: 100%;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cropper-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cropper-buttons {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
|
||||||
|
|
||||||
.btn{
|
|
||||||
width: 100px;
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
font-size: 15px;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&.upload{
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.getCropperImage{
|
|
||||||
padding-right: 20px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,271 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="app">
|
|
||||||
<view class="cell">
|
|
||||||
<text class="tit fill">头像</text>
|
|
||||||
<view class="avatar-wrap" @click="chooseImage">
|
|
||||||
<image class="avatar" :src="tempAvatar || userInfo.avatar || '/static/icon/default-avatar.png'" mode="aspectFill"></image>
|
|
||||||
<!-- 进度遮盖 -->
|
|
||||||
<view class="progress center"
|
|
||||||
:class="{
|
|
||||||
'no-transtion': uploadProgress === 0,
|
|
||||||
show: uploadProgress != 100
|
|
||||||
}"
|
|
||||||
:style="{
|
|
||||||
width: uploadProgress + '%',
|
|
||||||
height: uploadProgress + '%',
|
|
||||||
}"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="cell b-b">
|
|
||||||
<text class="tit fill">昵称</text>
|
|
||||||
<input class="input" v-model="userInfo.nickname" type="text" maxlength="8" placeholder="请输入昵称" placeholder-class="placeholder">
|
|
||||||
</view>
|
|
||||||
<u-cell-group>
|
|
||||||
<u-cell title="昵称" :value="userInfo.nickname" isLink @click="nicknameClick()"></u-cell>
|
|
||||||
</u-cell-group>
|
|
||||||
|
|
||||||
<u-modal :show="nicknameOpen" title="修改昵称" showCancelButton @confirm="nicknameSubmit" @cancel="nicknameCancel">
|
|
||||||
<view class="slot-content">
|
|
||||||
<u--form labelPosition="left" :model="nicknameForm" :rules="nicknameRules" ref="nicknameForm" errorType="toast">
|
|
||||||
<u-form-item prop="nickname">
|
|
||||||
<u--input v-model="nicknameForm.nickname" placeholder="请输入昵称" border="none"></u--input>
|
|
||||||
</u-form-item>
|
|
||||||
</u--form>
|
|
||||||
</view>
|
|
||||||
</u-modal>
|
|
||||||
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
uploadProgress: 100, //头像上传进度
|
|
||||||
tempAvatar: '',
|
|
||||||
userInfo: {},
|
|
||||||
nicknameOpen: false,
|
|
||||||
nicknameForm: {
|
|
||||||
nickname: ''
|
|
||||||
},
|
|
||||||
nicknameRules: {
|
|
||||||
nickname: [{
|
|
||||||
required: true,
|
|
||||||
message: '请输入昵称'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
curUserInfo(){
|
|
||||||
return this.$store.state.userInfo
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
curUserInfo(curUserInfo){
|
|
||||||
const {avatar, nickname, gender} = curUserInfo;
|
|
||||||
this.userInfo = {avatar, nickname, gender,};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLoad() {
|
|
||||||
const {avatar, nickname, gender, anonymous} = this.curUserInfo;
|
|
||||||
this.userInfo = {avatar, nickname, gender};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
nicknameClick() {
|
|
||||||
this.nicknameOpen = true;
|
|
||||||
this.nicknameForm.nickname = this.userInfo.nickname;
|
|
||||||
},
|
|
||||||
nicknameCancel() {
|
|
||||||
this.nicknameOpen = false;
|
|
||||||
},
|
|
||||||
nicknameSubmit() {
|
|
||||||
this.$refs.nicknameForm.validate().then(() => {
|
|
||||||
this.loading = true;
|
|
||||||
// 执行登陆
|
|
||||||
const { mobile, code, password} = this.form;
|
|
||||||
const loginPromise = this.loginType == 'password' ? login(mobile, password) :
|
|
||||||
smsLogin(mobile, code);
|
|
||||||
loginPromise.then(data => {
|
|
||||||
// 登陆成功
|
|
||||||
this.loginSuccessCallBack(data);
|
|
||||||
}).catch(errors => {
|
|
||||||
}).finally(() => {
|
|
||||||
this.loading = false;
|
|
||||||
})
|
|
||||||
}).catch(errors => {
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// 提交修改
|
|
||||||
async confirm() {
|
|
||||||
// 校验信息是否变化
|
|
||||||
const {uploadProgress, userInfo, curUserInfo} = this;
|
|
||||||
let isUpdate = false;
|
|
||||||
for (let key in userInfo) {
|
|
||||||
if(userInfo[key] !== curUserInfo[key]){
|
|
||||||
isUpdate = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isUpdate === false) {
|
|
||||||
this.$util.msg('信息未修改');
|
|
||||||
this.$refs.confirmBtn.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!userInfo.avatar) {
|
|
||||||
this.$util.msg('请上传头像');
|
|
||||||
this.$refs.confirmBtn.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (uploadProgress !== 100) {
|
|
||||||
this.$util.msg('请等待头像上传完毕');
|
|
||||||
this.$refs.confirmBtn.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!userInfo.nickname) {
|
|
||||||
this.$util.msg('请输入您的昵称');
|
|
||||||
this.$refs.confirmBtn.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const res = await this.$request('user', 'update', userInfo);
|
|
||||||
this.$refs.confirmBtn.stop();
|
|
||||||
this.$util.msg(res.msg);
|
|
||||||
if(res.status === 1){
|
|
||||||
this.$store.dispatch('getUserInfo'); //刷新用户信息
|
|
||||||
setTimeout(()=>{
|
|
||||||
uni.navigateBack();
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 选择头像
|
|
||||||
chooseImage(){
|
|
||||||
uni.chooseImage({
|
|
||||||
count: 1,
|
|
||||||
success: res=> {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `./cutImage/cut?src=${res.tempFilePaths[0]}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// 裁剪回调
|
|
||||||
async setAvatar(filePath){
|
|
||||||
this.tempAvatar = filePath;
|
|
||||||
this.uploadProgress = 0;
|
|
||||||
const result = await uniCloud.uploadFile({
|
|
||||||
filePath: filePath,
|
|
||||||
cloudPath: + new Date() + ('000000' + Math.floor(Math.random() * 999999)).slice(-6) + '.jpg',
|
|
||||||
onUploadProgress: e=> {
|
|
||||||
this.uploadProgress = Math.round(
|
|
||||||
(e.loaded * 100) / e.total
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(!result.fileID){
|
|
||||||
this.$util.msg('头像上传失败');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(typeof uniCloud.getTempFileURL === 'undefined'){
|
|
||||||
this.userInfo.avatar = result.fileID;
|
|
||||||
}else{
|
|
||||||
const tempFiles = await uniCloud.getTempFileURL({
|
|
||||||
fileList: [result.fileID]
|
|
||||||
})
|
|
||||||
const tempFile = tempFiles.fileList[0];
|
|
||||||
if(tempFile.download_url || tempFile.fileID){
|
|
||||||
this.userInfo.avatar = tempFile.download_url || tempFile.fileID;
|
|
||||||
}else{
|
|
||||||
this.$util.msg('头像上传失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.app{
|
|
||||||
padding-top: 16rpx;
|
|
||||||
}
|
|
||||||
.cell{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 110rpx;
|
|
||||||
padding: 0 40rpx;
|
|
||||||
|
|
||||||
&:first-child{
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
}
|
|
||||||
&:after{
|
|
||||||
left: 40rpx;
|
|
||||||
right: 40rpx;
|
|
||||||
border-color: #d8d8d8;
|
|
||||||
}
|
|
||||||
.tit{
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.avatar-wrap{
|
|
||||||
width: 120rpx;
|
|
||||||
height: 120rpx;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.avatar{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
}
|
|
||||||
.progress{
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 100rpx;
|
|
||||||
height: 100rpx;
|
|
||||||
box-shadow: rgba(0,0,0,.6) 0px 0px 0px 2005px;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
transition: .5s;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
&.no-transtion{
|
|
||||||
transition: 0s;
|
|
||||||
}
|
|
||||||
&.show{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.input{
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
switch{
|
|
||||||
margin: 0;
|
|
||||||
transform: scale(0.8) translateX(10rpx);
|
|
||||||
transform-origin: center right;
|
|
||||||
}
|
|
||||||
.tip{
|
|
||||||
margin-left: 20rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
.checkbox{
|
|
||||||
padding: 12rpx 0 12rpx 40rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
|
|
||||||
.mix-icon{
|
|
||||||
margin-right: 12rpx;
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
.icon-xuanzhong{
|
|
||||||
color: $base-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|