Commit 78df9d10 authored by superDragon's avatar superDragon

feat:更新

parents
.DS_Store
node_modules
unpackage
/dist
vue.config
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
{ // 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": [{
"type": "uniCloud",
"default": {
"launchtype": "local"
}
}
]
}
<script>
import store from "@/store";
import socket from "@/config/socket";
// #ifdef H5
import {
h5Login
} from "@/config/html5Utils";
// #endif
// #ifdef APP-PLUS
import APPUpdate from "@/plugins/APPUpdate";
// #endif
export default {
onLaunch: function(e) {
//取出缓存数据
store.commit("setCacheData");
// #ifdef MP-WEIXIN
//获取二维码携带的参数
if (store.state.userInfo.token) {
socket.init();
}
// #endif
// #ifdef H5
if (store.state.userInfo.token) {
socket.init();
} else {
h5Login("force", () => {
socket.init();
});
}
// #endif
// #ifdef APP-PLUS
// if (store.state.userInfo.token) {
// socket.init();
// }
APPUpdate();
// #endif
},
onShow: function(e) {
// #ifdef MP-WEIXIN
//获取二维码携带的参数
let scene = decodeURIComponent(e.query.scene);
scene = scene.split("&");
let data = {
//场景值
scene: e.scene
};
scene.forEach(item => {
let arr = item.split("=");
if (arr.length == 2) {
data[arr[0]] = arr[1];
}
});
store.commit("setChatScenesInfo", Object.assign(e.query, data));
//小程序更新
if (uni.getUpdateManager) {
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(function(res) {
// 请求完新版本信息的回调
// console.log(res.hasUpdate);
});
updateManager.onUpdateReady(function(res) {
uni.showModal({
title: "更新提示",
content: "新版本已经准备好,是否重启应用?",
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(function(res) {
// 新的版本下载失败
uni.showModal({
title: "已经有新版本了哟~",
content: "新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~",
showCancel: false
});
});
}
// #endif
},
onHide: function() {}
};
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
@import "./style/common.scss";
@import "./style/input.scss";
page {
height: 100%;
background-color: #f5f5f5;
}
/* #endif */
/* #ifdef H5 */
//修复H5底部导航挡住内容bug
uni-app {
height: auto;
}
//修复H5输入框上下不居中bug
.uni-input-form {
height: 100%;
}
//去除地图上高德地图标识符
.amap-copyright {
display: none !important;
}
.amap-logo {
display: none !important;
}
.amap-ui-control-zoom {
width: 60upx !important;
}
.amap-ui-control-zoom>* {
width: 60upx !important;
height: 60upx !important;
line-height: 60upx !important;
}
.amap-ui-control-theme-dark {
display: none !important;
}
/* #endif */
</style>
# uni-app项目模板(不喜勿喷)
> 一个5年的web前端开源的uni-app快速开发模板,参考学习一同进步
> 建议uni-app使用时间达到1年以上的程序员来学习
### 使用步骤
1. 下载下来,解压成文件夹
2. 把项目包丢到HBuilder X里面
3. HBuilder X要安装scss/sass编译插件
4. 运行项目
### `觉得不错,给个5星好评吧`
| `QQ交流群(607391225)` | `微信交流群(加我好友备注"进群")` |
| ----------------------------|--------------------------- |
|![QQ交流群](http://qn.kemean.cn//upload/202004/14/15868301778472k7oubi6.png)|![微信交流群](https://qn.kemean.cn/upload/202010/13/weiXin_group_code.jpg)|
| QQ群号:607391225 |微信号:zhou0612wei|
### `开源不易,需要花费很多精力,打个赏吧`
| `微信打赏码` | `支付宝打赏码` |
| ----------------------------|--------------------------- |
|![微信打赏二维码](http://qn.kemean.cn/upload/202006/17/15923814750253qjayobp.png)|![支付宝打赏二维码](http://qn.kemean.cn/upload/202006/17/1592381515304aezjp7h3.jpg)|
### [H5预览地址](http://8.129.186.35/index.html)
### [安卓APP安装包下载地址1.0.1(可体验版本更新)](http://qn.kemean.cn/upload/202006/10/1591785853646tulgw1o4.apk)
### [安卓APP安装包下载地址1.1.0(最新版)](http://qn.kemean.cn/upload/202008/19/1597832954922s442039o.apk)
### [Demo版GitHub地址](https://github.com/zhouwei1994/uni-app-demo)
### [空项目模板GitHub地址(开发用这个)](https://github.com/zhouwei1994/uni-app-template)
## 聊天案例项目
### [聊天案例项目H5地址](http://8.129.186.35/chat/index.html)
### [聊天案例项目安卓APP安装包下载地址](http://qn.kemean.cn/upload/202008/20/1597887768449e63qg66n.apk)
### 文件说明
1. components/chat-emojis.nvue 表情组件(表情、收藏表情图、表情包)
2. components/chat-message.nvue 消息显示及类型组件
3. components/chat-preview.nvue 图片及视频预览组件
4. components/chat-tabbar.nvue 底部操作组件(语音、输入框、发送)
5. utils/emojis.js 表情数据
6. utils/utils.js 工具
7. chat.nvue 聊天主页面
8. config.js 聊天配置页面及对接页面(所有的聊天对接都提取到这里了)
### 功能
1. 发 文本和表情
2. 发 表情图片
3. 发 图片
4. 发 视频
5. 发 语音
6. 发 位置
7. 发 文件
8. 发 商品
9. 禁言
### 支持聊天类型
1. 单聊
2. 群聊
### 消息渲染
1. 发送图片、视频、文件先预渲染到页面(有上传进度),模仿微信上传图片
2. 发送消息有发送中、发送失败(可点击重发)、发送成功
3. 已读、未读(有样式、后端没实现)
## 架构功能清单
### 组件示例
1. 瀑布流列表
2. 防抖音滑动视频(带进度加载)
3. 项目主题色介绍
4. 头部导航示例
5. 上拉加载,下拉刷新列表
6. 地区选择
7. 弹窗输入框
8. 滑动操作
9. 富文本编译
### SDK示例
1. 接口请求
2. APP版本更新
3. 支付
4. 分享
5. 小程序登录
6. 公众号登录
7. 登录拦截
8. 获取当前位置
9. 图片上传/文件上传
10. 七牛云图片上传/文件上传
11. 保存图片到相册
12. webSocket封装介绍
13. 公众号SDK
14. APP权限判断和跳转到系统设置
15. 常用工具
### 模板页面
1. 登录
2. 注册
3. 忘记密码
4. 绑定手机号
5. 协议
### 项目结构
``` bash
├── components // 组件
│   ├── common // 公共组件
│   └── module // 项目组件
├── config // 配置 // vuex主文件
│   ├── baseUrl.js // 项目配置
│   ├── html5Utils.js // H5相关的功能(公众号支付、公众号登录)
│   ├── login.js // 小程序登录js代码部分和登录拦截器代码
│   ├── requestConfig.js // 接口请求配置
│   ├── socket.js // webSocket相关代码
│   └── utils.js // 项目相关工具(公众号分享、小程序分享数据处理、支付、获取经纬度、支付分配)
├── pages // 项目页面
├── plugins // 公共SDK(基本上不需要改)
│   ├── APPUpdate // APP版本更新
│   ├── request // 接口请求封装
│   ├── share // APP分享
│   ├── md5.js // md5加密
│   ├── permission.js // APP权限判断和打开手机系统设置
│   ├── utils.js // 工具(时间转换、APP和小程序获取经纬度代码)
│   └── wxJsSDK.js // 微信公众号SDK去权限获取页面
├── static // 公共文件
│   ├── demo // 本项目相关的图片(可删除)
│   ├── icon // 项目图标
│   ├── mp-h5 // H5第三方包(公众号JS-SDK)
│   ├── mp-weixin // 微信小程序第三方包
│   ├── share // 分享SDK的图标
│   └── zhouWei-navBar // 导航组件的图标
├── store // vuex商店
│ ├── modules // vuex分类
│ │ ├── common.js // vuex通用数据管理
│ │ ├── user.js // vuex用户数据管理
│   │ └── order.js // vuex订单数据管理
│   └── index.js // vuex方法集合
├── style
│ ├── common.scss // 公共样式文件
│ ├── input.scss // 公共表单样式
│ ├── mixin.scss // 样式配置文件
│ └── table.scss // 本项目相关的css(可删除)
├── unpackage // 项目编译后的文件
├── App.vue // 项目主界面
├── main.js // 程序入口文件,加载各种公共组件
├── manifest.json // uni-app项目类型及环境配置
├── pages.json // 项目路由及项目界面配置
├── README.md // 项目介绍文件
└── template.h5.html // 项目发布的时候使用的文件
```
<template>
<popup v-model="currentValue">
<view class="addresTitle">
<text @click="currentValue = false">取消</text>
<view>所在地区</view>
<text @click="onConfirm">确定</text>
</view>
<z-address :dataList="addressVal" @change="addressChange" :length="length" :force="force"></z-address>
</popup>
</template>
<script>
import Popup from './popup.vue';
import zAddress from './address.vue';
export default {
components: {
Popup,
zAddress
},
props: {
dataList: {
type: Array,
default() {
return [];
}
},
value: {
type: Boolean,
default: false
},
length: {
type: Number,
default: 2
},
force:{
type: Boolean,
default: true
}
},
created() {
if (typeof this.value !== 'undefined') {
this.currentValue = this.value;
}
if (this.dataList instanceof Array) {
this.addressVal = this.dataList;
}
},
watch: {
value(val) {
this.currentValue = val;
},
currentValue(val) {
this.$emit(val ? 'on-show' : 'on-hide');
this.$emit('input', val);
},
dataList(val) {
this.addressVal = val;
}
},
data() {
return {
currentValue: false,
//选出的值
addressVal: []
};
},
methods: {
addressChange(val) {
console.log(val);
this.addressVal = val;
},
onConfirm() {
if (parseInt(this.length) <= this.addressVal.length || !this.force && this.addressVal.length > 0) {
this.currentValue = false;
this.$emit('change', this.addressVal);
} else {
uni.showToast({
title: '请选择',
icon: 'none'
});
}
}
},
mounted() {}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
.addresTitle {
display: flex;
justify-content: space-between;
height: 88upx;
line-height: 88upx;
border-bottom: 2upx solid #ebebeb;
padding: 0 20upx;
background-color: #FFF;
}
.addresTitle view {
font-size: 32upx;
}
.addresTitle text {
width: 80upx;
flex-shrink: 0;
text-align: center;
}
.addresTitle text {
font-size: 28upx;
color: #999;
}
.addresTitle text:last-child {
color: $themeColor;
}
</style>
<template>
<view>
<view class="addres_select_val">
<view>
<view v-for="(item, index) of addressVal" :key="index" :class="{ select: addressIndex == index }" @click="selectType(index)" k>{{ item.name }}</view>
<view
:class="{ select: selectState == addressIndex }"
v-show="selectState < length || (selectState >= length && selectState < 3 && !force)"
@click="selectType(selectState)"
>
请选择
</view>
</view>
</view>
<scroll-view class="addres_box" scroll-y :scroll-top="scrollTop">
<view>
<view
v-for="(item, index) of addressList"
:key="index"
:class="{ select: addressVal.length > addressIndex && item.objId == addressVal[addressIndex].objId }"
@click="selectClick(item)"
>
{{ item.name }}
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
props: {
//选中数据
dataList: {
type: Array,
default() {
return [];
}
},
//联动长度[省,市,区]
length: {
type: Number,
default() {
return 3;
}
},
//是否强制选择,如果length=2,force = false 选择到市的时候就可以确定了,但是还可以选择到区
force: {
type: Boolean,
default() {
return true;
}
}
},
created() {
if (this.dataList instanceof Array) {
this.addressVal = this.dataList;
this.selectState = this.dataList.length;
}
},
watch: {
dataList(val) {
this.addressVal = val;
this.selectState = val.length;
}
},
data() {
return {
//选出的值
addressVal: [],
//当前选择
addressIndex: 0,
//选择的值
addressList: [],
//请选择的显示
selectState: 0,
scrollTop:0
};
},
methods: {
getRegion(pid) {
//请求数据
this.$http.get('api/kemean/aid/region', { pid: pid }, { load: false }).then(data => {
if (data.length > 0) {
this.addressList = data;
// this.$refs.scroll.scrollTop = '0px';
this.scrollTop = Math.random();
} else {
this.$emit('change', this.addressVal);
}
});
},
//切换对应的类型
selectType(index) {
this.addressIndex = index;
var len = this.addressVal.length;
if (index == 0) {
this.getRegion(0);
} else {
this.getRegion(this.addressVal[index - 1].objId);
}
if (len == this.length) {
this.selectState = this.length;
} else if (len == this.length && index == this.length && this.force) {
this.selectState = index;
} else {
this.selectState = index + 1;
}
},
//选择
selectClick(item) {
if (this.addressIndex == 0) {
this.addressVal = [];
} else {
this.addressVal.splice(this.addressIndex, this.addressVal.length - 1);
}
this.addressVal.push(item);
if (this.addressVal.length < this.length || (this.addressVal.length < 3 && !this.force)) {
this.getRegion(item.objId);
this.addressIndex++;
}
if (this.addressVal.length >= this.length || !this.force) {
this.$emit('change', this.addressVal);
}
this.selectState = this.addressVal.length;
}
},
mounted() {
this.getRegion(0);
}
};
</script>
<style lang="scss" scoped>
@import "@/style/mixin.scss";
.addres_select_val {
padding: 0upx 10upx;
border-bottom: 1upx solid #ebebeb;
box-sizing: border-box;
background-color: #fff;
}
.addres_select_val > view {
display: flex;
flex-wrap: wrap;
}
.addres_select_val > view > view {
margin-left: 20upx;
padding: 0upx 10upx;
height: 72upx;
line-height: 72upx;
border-bottom: 2upx solid #fff;
box-sizing: border-box;
font-size: 28upx;
}
.addres_select_val > view > view:first-child {
margin-left: 0upx;
}
.addres_select_val > view > view.select {
border-bottom: 2upx solid $themeColor;
color: $themeColor;
}
.addres_box {
padding: 0upx 20upx;
height: 420upx;
overflow-y: auto;
background-color: #fff;
}
.addres_box view > view {
height: 72upx;
line-height: 72upx;
font-size: 28upx;
}
.addres_box view > view.select {
color: $themeColor;
}
</style>
<template>
<view>
<!-- 登录弹窗 -->
<view class="loginMask" v-if="loginPopupShow" @click="closePopup"></view>
<view class="loginPopup" v-if="loginPopupShow">
<view class="loginBox">
<image class="logo" :src="base.logoUrl"></image>
<view class="platformName">{{ base.platformName }}</view>
<view class="description" v-if="base.description">{{ base.description }}</view>
</view>
<button type="primary" hover-class="active" open-type="getUserInfo" @getuserinfo="onAuthorization">授权登录</button>
</view>
</view>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import base from '@/config/baseUrl';
// #ifdef MP-WEIXIN
import { getUserInfo } from '@/config/login';
// #endif
let clear;
export default {
data() {
return {
base: base
};
},
computed: {
...mapState(['userInfo', 'loginPopupShow'])
},
methods: {
...mapMutations(['setUserInfo', 'setLoginPopupShow']),
//授权登录
onAuthorization: function(e) {
if (e.detail.errMsg == 'getUserInfo:ok') {
var userInfo = e.detail;
this.setLoginPopupShow(false);
getUserInfo(userInfo, 'authorized');
}
},
closeLogin() {
if (this.loginPopupShow && this.userInfo.token) {
this.setLoginPopupShow(false);
}
},
//关闭弹窗
closePopup() {
this.setLoginPopupShow(false);
}
}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
.loginMask {
position: fixed;
top: 0upx;
left: 0upx;
right: 0upx;
bottom: 0upx;
background-color: rgba(0, 0, 0, 0.4);
z-index: 10;
}
.loginPopup {
position: fixed;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
width: 500upx;
background-color: #fff;
border-radius: 20upx;
overflow: hidden;
z-index: 11;
.loginBox {
padding: 30upx 15upx 40upx 15upx;
display: flex;
flex-direction: column;
align-items: center;
.logo {
width: 160upx;
height: 160upx;
border-radius: 20%;
}
.platformName {
font-size: 24upx;
color: #999;
margin-top: 10upx;
}
.description {
margin-top: 15upx;
font-size: 30upx;
color: #333;
}
}
button {
border-radius: 0upx;
background-color: $themeColor;
}
.active {
background-color: $themeColor;
opacity: 0.8;
}
}
</style>
<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-->
<div :id="'rtf'+uid"></div>
<!--#endif-->
<!--#ifndef H5-->
<trees :nodes="nodes" :lazy-load="lazyLoad" />
<!--#endif-->
</view>
<!--#endif-->
</view>
</template>
<script>
// #ifndef H5 || APP-PLUS-NVUE
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
var rpx = uni.getSystemInfoSync().screenWidth / 750,
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|Array} html 富文本数据
* @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
* @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
* @property {Number} compress 压缩等级
* @property {String} domain 图片、视频等链接的主域名
* @property {Boolean} lazyLoad 是否开启图片懒加载
* @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 链接点击事件
* @example <jyf-parser :html="html"></jyf-parser>
* @author JinYufeng
* @version 20200513
* @listens MIT
*/
export default {
name: 'parser',
data() {
return {
// #ifdef H5
uid: this._uid,
// #endif
// #ifdef APP-PLUS-NVUE
height: 1,
// #endif
// #ifndef APP-PLUS-NVUE
showAm: '',
imgs: [],
// #endif
nodes: []
}
},
// #ifndef H5 || APP-PLUS-NVUE
components: {
trees
},
// #endif
props: {
'html': null,
'autopause': {
type: Boolean,
default: true
},
'autoscroll': Boolean,
'autosetTitle': {
type: Boolean,
default: true
},
// #ifndef H5 || APP-PLUS-NVUE
'compress': Number,
'useCache': Boolean,
// #endif
'domain': String,
'lazyLoad': Boolean,
'selectable': Boolean,
'tagStyle': Object,
'showWithAnimation': Boolean,
'useAnchor': Boolean
},
watch: {
html(html) {
this.setContent(html);
}
},
mounted() {
// 图片数组
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 = '';
for (var j = 0, 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
}
}
// #ifdef H5
this.document = document.getElementById('rtf' + this._uid);
// #endif
// #ifndef H5 || APP-PLUS-NVUE
if (dom) this.document = new dom(this);
// #endif
// #ifdef APP-PLUS-NVUE
this.document = this.$refs.web;
this.$nextTick(() => {
// #endif
if (this.html) this.setContent(this.html);
// #ifdef APP-PLUS-NVUE
})
// #endif
},
beforeDestroy() {
// #ifdef H5
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: {
// #ifdef H5 || APP-PLUS-NVUE
_Dom2Str(nodes) {
var str = '';
for (var node of nodes) {
if (node.type == 'text')
str += node.text;
else {
str += ('<' + node.name);
for (var attr in node.attrs || {})
str += (' ' + attr + '="' + node.attrs[attr] + '"');
if (!node.children || !node.children.length) str += '>';
else str += ('>' + this._Dom2Str(node.children) + '</' + node.name + '>');
}
}
return str;
},
_handleHtml(html, append) {
if (typeof html != 'string') html = this._Dom2Str(html.nodes || html);
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($) * rpx + 'px');
return html;
},
// #endif
setContent(html, append) {
// #ifdef APP-PLUS-NVUE
if (!html)
return this.height = 1;
if (append)
this.$refs.web.evalJs("var d=document.createElement('div');d.innerHTML='" + html.replace(/'/g, "\\'") +
"';document.getElementById('parser').appendChild(d)");
else {
html =
'<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1' +
(this.selectable ? '' : ',user-scalable=no') + '"><base href="' + this.domain + '"><div id="parser">' + this._handleHtml(
html) +
'</div><script>"use strict";function post(n){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[n]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}function waitReady(){return new Promise(function(e){var t=document.getElementById("parser"),r=t.scrollHeight,n=setInterval(function(){r==t.scrollHeight?(clearInterval(n),e(r)):r=t.scrollHeight},500)})}' +
(this.showWithAnimation ? 'document.body.style.animation="show .5s",' : '') +
'setTimeout(function(){post({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight+16})},50);</' +
'script>';
this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
}
this.$refs.web.evalJs(
'var e=document.getElementsByTagName("title");e.length&&post({action:"getTitle",title:e[0].innerText});for(var t,o=document.getElementsByTagName("style"),r=0;t=o[r];r++)t.innerHTML=t.innerHTML.replace(/body/g,"#parser");for(var n,i=document.getElementsByTagName("img"),a=[],s=0,c=0;n=i[s];s++)n.onerror=function(){post({action:"error",source:"img",target:this})},n.hasAttribute("ignore")||"A"==n.parentElement.nodeName||(n.i=c++,a.push(n.src),n.onclick=function(){post({action:"preview",img:{i:this.i,src:this.src}})});post({action:"getImgList",imgList:a});for(var m,g=document.getElementsByTagName("a"),l=0;m=g[l];l++)m.onclick=function(){var e,t=this.getAttribute("href");if("#"==t[0]){var o=document.getElementById(t.substr(1));o&&(e=o.offsetTop)}return post({action:"linkpress",href:t,offset:e}),!1};for(var u,d=document.getElementsByTagName("video"),f=0;u=d[f];f++)u.style.maxWidth="100%",u.onerror=function(){post({action:"error",source:"video",target:this})}' +
(this.autopause ? ',u.onplay=function(){for(var e,t=0;e=d[t];t++)e!=this&&e.pause()}' : '') +
';for(var p,h=document.getElementsByTagName("audio"),v=0;p=h[v];v++)p.onerror=function(){post({action:"error",source:"audio",target:this})};' +
(this.autoscroll ?
'for(var y,T=document.getElementsByTagName("table"),E=0;y=T[E];E++){var N=document.createElement("div");N.style.overflow="scroll",y.parentNode.replaceChild(N,y),N.appendChild(y)}' :
'') + ';waitReady().then(function(e){post({action:"ready",height:e+16})})'
)
this.nodes = [1];
// #endif
// #ifdef H5
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++) {
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() {
_ts.$emit('error', {
source: 'img',
target: this,
context: {
setSrc: src => this.src = src
}
});
}
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,
context: 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,
context: this
});
}
// 表格处理
if (this.autoscroll) {
var tables = this.rtf.getElementsByTagName('table');
for (var table of tables) {
var 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
var nodes;
if (!html)
return this.nodes = [];
else if (typeof html == 'string') {
let 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);
} else if (Object.prototype.toString.call(html) == '[object Array]') {
// 非本插件产生的 array 需要进行一些转换
if (html.length && html[0].PoweredBy != 'Parser') {
let parser = new Parser(html, this);
(function f(ns) {
for (var i = 0, n; n = ns[i]; i++) {
if (n.type == 'text') continue;
n.attrs = n.attrs || {};
for (var item in n.attrs)
if (typeof n.attrs[item] != 'string') n.attrs[item] = n.attrs[item].toString();
parser.matchAttr(n, parser);
if (n.children && n.children.length) {
parser.STACK.push(n);
f(n.children);
parser.popNode(parser.STACK.pop());
} else n.children = void 0;
}
})(html);
}
nodes = html;
} else if (typeof html == 'object' && html.nodes) {
nodes = html.nodes;
console.warn('错误的 html 类型:object 类型已废弃');
} else
return console.warn('错误的 html 类型:' + typeof html);
if (append) this.nodes = this.nodes.concat(nodes);
else this.nodes = nodes;
if (nodes.length && nodes[0].title && this.autosetTitle)
uni.setNavigationBarTitle({
title: nodes[0].title
})
this.$nextTick(() => {
this.imgList.length = 0;
this.videoContexts = [];
this.$emit('load');
})
// #endif
var height;
clearInterval(this._timer);
this._timer = setInterval(() => {
// #ifdef H5
this.rect = this.rtf.getBoundingClientRect();
// #endif
// #ifndef H5
// #ifdef APP-PLUS
uni.createSelectorQuery().in(this)
// #endif
// #ifndef APP-PLUS
this.createSelectorQuery()
// #endif
.select('#top').boundingClientRect().exec(res => {
this.rect = res[0];
// #endif
if (this.rect.height == height) {
this.$emit('ready', this.rect)
clearInterval(this._timer);
}
height = this.rect.height;
// #ifndef H5
});
// #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
txt = this.rtf.innerText;
// #endif
// #ifndef H5 || APP-PLUS-NVUE
for (var i = 0, n; n = ns[i++];) {
if (n.type == 'text') txt += n.text.replace(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&amp;/g, '&');
else if (n.type == 'br') txt += '\n';
else {
// 块级标签前后加换行
var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
'0' && n.name[1] < '7');
if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
if (n.children) txt += this.getText(n.children);
if (block && txt[txt.length - 1] != '\n') txt += '\n';
else if (n.name == 'td' || n.name == 'th') txt += '\t';
}
}
// #endif
return txt;
},
navigateTo(obj) {
if (!this.useAnchor)
return obj.fail && obj.fail({
errMsg: '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({
errMsg: 'pageScrollTo:ok'
});
// #endif
// #ifdef H5
if (!obj.id) {
window.scrollTo(0, this.rtf.offsetTop);
return obj.success && obj.success({
errMsg: 'pageScrollTo:ok'
});
}
var target = document.getElementById(obj.id);
if (!target) return obj.fail && obj.fail({
errMsg: 'Label not found'
});
obj.scrollTop = this.rtf.offsetTop + target.offsetTop + (obj.offset || 0);
uni.pageScrollTo(obj);
// #endif
// #ifndef H5 || APP-PLUS-NVUE
var Scroll = (selector, component) => {
uni.createSelectorQuery().in(component ? component : this).select(selector).boundingClientRect().selectViewport()
.scrollOffset()
.exec(res => {
if (!res || !res[0])
return obj.fail && obj.fail({
errMsg: 'Label not found'
});
obj.scrollTop = res[1].scrollTop + res[0].top + (obj.offset || 0);
uni.pageScrollTo(obj);
})
}
if (!obj.id) Scroll('#top');
else {
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
Scroll('#top >>> #' + obj.id + ', #top >>> .' + obj.id);
// #endif
// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
for (var anchor of this.anchors)
if (anchor.id == obj.id)
Scroll('#' + obj.id + ', .' + obj.id, anchor.node);
// #endif
}
// #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 APP-PLUS-NVUE
_message(e) {
// 接收 web-view 消息
var data = e.detail.data[0];
if (data.action == 'load') {
this.$emit('load');
this.height = data.height;
this._text = data.text;
} else if (data.action == 'getTitle') {
if (this.autosetTitle)
uni.setNavigationBarTitle({
title: data.title
})
} else if (data.action == 'getImgList') {
this.imgList.length = 0;
for (var i = data.imgList.length; i--;)
this.imgList.setItem(i, data.imgList[i]);
} else if (data.action == 'preview') {
var preview = true;
data.img.ignore = () => preview = false;
this.$emit('imgtap', data.img);
if (preview)
uni.previewImage({
current: data.img.i,
urls: this.imgList
})
} else if (data.action == 'linkpress') {
var jump = true,
href = data.href;
this.$emit('linkpress', {
href,
ignore: () => jump = false
})
if (jump && href) {
if (href[0] == '#') {
if (this.useAnchor)
weexDom.scrollToElement(this.$refs.web, {
offset: data.offset
})
} else if (href.includes('://'))
plus.runtime.openWeb(href);
else
uni.navigateTo({
url: href
})
}
} else if (data.action == 'error')
this.$emit('error', {
source: data.source,
target: data.target
})
else if (data.action == 'ready') {
this.height = data.height;
this.$nextTick(() => {
uni.createSelectorQuery().in(this).select('#top').boundingClientRect().exec(res => {
this.rect = res[0];
this.$emit('ready', res[0]);
})
})
}
},
// #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>
var cfg = require('./config.js'),
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
class CssHandler {
constructor(tagStyle) {
var styles = Object.assign({}, cfg.userAgentStyles);
for (var item in tagStyle)
styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
this.styles = styles;
}
getStyle(data) {
this.styles = new CssParser(data, this.styles).parse();
}
match(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;
class CssParser {
constructor(data, init) {
this.data = data;
this.floor = 0;
this.i = 0;
this.list = [];
this.res = init;
this.state = this.Space;
}
parse() {
for (var c; c = this.data[this.i]; this.i++)
this.state(c);
return this.res;
}
section() {
return this.data.substring(this.start, this.i);
}
// 状态机
Space(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;
}
Comment() {
this.i = this.data.indexOf('*/', this.i) + 1;
if (!this.i) this.i = this.data.length;
this.state = this.Space;
}
Ignore(c) {
if (c == '{') this.floor++;
else if (c == '}' && !--this.floor) this.state = this.Space;
}
Name(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;
}
NameSpace(c) {
if (c == '{') this.Content();
else if (c == ',') this.Comma();
else if (!cfg.blankChar[c]) this.state = this.Ignore;
}
Comma() {
while (cfg.blankChar[this.data[++this.i]]);
if (this.data[this.i] == '{') this.Content();
else {
this.start = this.i--;
this.state = this.Name;
}
}
Content() {
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;
}
}
/**
* html 解析器
* @tutorial https://github.com/jin-yufeng/Parser
* @version 20200513
* @author JinYufeng
* @listens MIT
*/
var cfg = require('./config.js'),
blankChar = cfg.blankChar,
CssHandler = require('./CssHandler.js'),
windowWidth = uni.getSystemInfoSync().windowWidth;
var emoji;
class MpHtmlParser {
constructor(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 = [];
}
parse() {
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());
if (this.DOM.length) {
this.DOM[0].PoweredBy = 'Parser';
if (this.title) this.DOM[0].title = this.title;
}
return this.DOM;
}
// 设置属性
setAttr() {
var name = this.attrName.toLowerCase();
if (cfg.trustAttrs[name]) {
var val = this.attrVal;
if (val) {
if (name == 'src') this.attrs[name] = this.getUrl(this.decode(val, 'amp'));
else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp');
else this.attrs[name] = val;
} else if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
}
this.attrVal = '';
while (blankChar[this.data[this.i]]) this.i++;
if (this.isClose()) this.setNode();
else {
this.start = this.i;
this.state = this.AttrName;
}
}
// 设置文本节点
setText() {
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)
});
}
// 设置元素节点
setNode() {
var node = {
name: this.tagName.toLowerCase(),
attrs: this.attrs
},
close = cfg.selfClosingTags[node.name];
this.attrs = {};
if (!cfg.ignoreTags[node.name]) {
this.matchAttr(node);
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;
}
// 移除标签
remove(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) + 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'),
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.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;
}
}
}
// 处理属性
matchAttr(node) {
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':
this.bubble();
break;
// #ifdef APP-PLUS
case 'iframe':
case 'embed':
this.bubble();
break;
// #endif
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 '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);
if (!attrs.controls && !attrs.autoplay)
console.warn(`存在没有 controls 属性的 ${node.name} 标签,可能导致无法播放`, node);
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['data-src']) {
attrs.src = attrs.src || attrs['data-src'];
attrs['data-src'] = void 0;
}
if (attrs.src && !attrs.ignore) {
if (this.bubble())
attrs.i = (this.imgNum++).toString();
else attrs.ignore = 'T';
}
if (attrs.ignore) 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 (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'))
this.pre = node.pre = true;
style += `;${key}:${value}`;
}
style = style.substr(1);
if (style) attrs.style = style;
}
// 节点出栈处理
popNode(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();
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)
(function f(ns) {
for (var i = 0, n; n = ns[i]; i++) {
if (n.name == 'th' || n.name == 'td') {
if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style}`;
if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style}`;
} else f(n.children || []);
}
})(childs)
if (this.options.autoscroll) {
var table = Object.assign({}, node);
node.name = 'div';
node.attrs = {
style: 'overflow:scroll'
}
node.children = [table];
}
}
this.CssHandler.pop && this.CssHandler.pop(node);
// 自动压缩
if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div')
siblings[len - 1] = childs[0];
}
// 工具函数
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;
}
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;
}
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;
}
isClose() {
return this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
}
section() {
return this.data.substring(this.start, this.i);
}
parent() {
return this.STACK[this.STACK.length - 1];
}
siblings() {
return this.STACK.length ? this.parent().children : this.DOM;
}
// 状态机
Text(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 == '!') {
this.setText();
this.Comment();
}
}
}
Comment() {
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;
}
TagName(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();
}
}
AttrName(c) {
var blank = blankChar[c];
if (blank) {
this.attrName = this.section();
c = this.data[this.i];
}
if (c == '=') {
if (!blank) this.attrName = this.section();
while (blankChar[this.data[++this.i]]);
this.start = this.i--;
this.state = this.AttrValue;
} else if (blank) this.setAttr();
else if (this.isClose()) {
this.attrName = this.section();
this.setAttr();
}
}
AttrValue(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();
}
EndTag(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);
} 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;
/* 配置文件 */
// #ifdef MP-WEIXIN
const canIUse = wx.canIUse('editor'); // 高基础库标识,用于兼容
// #endif
module.exports = {
// 过滤器函数
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'),
// 块级标签,将被转为 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,basefont,canvas,command,frame,input,isindex,keygen,link,map,meta,param,script,source,style,svg,textarea,title,track,use,wbr'
// #ifdef MP-WEIXIN
+ (canIUse ? ',rp' : '')
// #endif
// #ifndef APP-PLUS
+ ',embed,iframe'
// #endif
),
// 只能被 rich-text 显示的标签
richOnlyTags: makeMap('a,colgroup,fieldset,legend,picture,table'
// #ifdef MP-WEIXIN
+ (canIUse ? ',bdi,bdo,caption,rt,ruby' : '')
// #endif
),
// 自闭合的标签
selfClosingTags: makeMap(
'area,base,basefont,br,col,circle,ellipse,embed,frame,hr,img,input,isindex,keygen,line,link,meta,param,path,polygon,rect,source,track,use,wbr'
),
// 信任的属性
trustAttrs: makeMap(
'align,alt,app-id,author,autoplay,border,cellpadding,cellspacing,class,color,colspan,controls,data-src,dir,face,height,href,id,ignore,loop,media,muted,name,path,poster,rowspan,size,span,src,start,style,type,unit-id,width,xmlns'
),
// bool 型的属性
boolAttrs: makeMap('autoplay,controls,ignore,loop,muted'),
// 信任的标签
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 = {},
list = str.split(',');
for (var i = list.length; i--;)
map[list[i]] = true;
return map;
}
var inlineTags = {
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
}
export default {
useRichText: function(item) {
return !item.c && !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1;
}
}
var inlineTags = {
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
}
module.exports = {
useRichText: function(item) {
return !item.c && !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1;
}
}
<template>
<view class="interlayer">
<block v-for="(n, index) in ns" v-bind:key="index">
<!--图片-->
<view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap="imgtap">
<rich-text :nodes="[{attrs:{src:lazyLoad&&!n.load?placeholder:n.attrs.src,alt:n.attrs.alt||'',width:n.attrs.width||'',style:'max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
<image class="_image" :src="lazyLoad&&!n.load?placeholder:n.attrs.src" :lazy-load="lazyLoad"
:show-menu-by-longpress="!n.attrs.ignore" :data-i="index" 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.name=='video'&&!loadVideo)" :id="n.attrs.id" :class="'_video '+(n.attrs.class||'')"
:style="n.attrs.style" :data-i="index" @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="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[n.i||0]"
:unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="index" data-source="video" @error="error" @play="play" />
<!--音频-->
<audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author"
:autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
:src="n.attrs.source[n.i||0]" :data-i="index" :data-id="n.attrs.id" data-source="audio" @error.native="error"
@play.native="play" />
<!--链接-->
<view v-else-if="n.name=='a'" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
:data-attrs="n.attrs" @tap="linkpress">
<trees class="_span" :nodes="n.children" />
</view>
<!--广告(按需打开注释)-->
<!--#ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO-->
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']"
data-from="ad" @error="error" />-->
<!--#endif-->
<!--#ifdef MP-BAIDU-->
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :appid="n.attrs.appid"
:apid="n.attrs.apid" :type="n.attrs.type" data-from="ad" @error="error" />-->
<!--#endif-->
<!--#ifdef APP-PLUS-->
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :adpid="n.attrs.adpid"
data-from="ad" @error="error" />-->
<!--#endif-->
<!--列表-->
<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>
<!--#ifdef MP-ALIPAY-->
<view class="_li">
<trees :nodes="n.children" :lazyLoad="lazyLoad" />
</view>
<!--#endif-->
<!--#ifndef MP-ALIPAY-->
<trees class="_li" :nodes="n.children" :lazyLoad="lazyLoad" />
<!--#endif-->
</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, i) in n.children" v-bind:key="i" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
<view v-for="(tr, j) in tbody.children" v-bind:key="j" :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" />
<block v-else>
<!--#ifdef MP-ALIPAY-->
<view v-for="(td, k) in tr.children" v-bind:key="k" :class="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')">
<trees :nodes="td.children" />
</view>
<!--#endif-->
<!--#ifndef MP-ALIPAY-->
<trees v-for="(td, k) in tr.children" v-bind:key="k" :class="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
:nodes="td.children" />
<!--#endif-->
</block>
</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 || MP-ALIPAY || APP-PLUS-->
<rich-text v-else-if="handler.useRichText(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
<!--#endif-->
<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
<rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" />
<!--#endif-->
<!--#ifdef MP-ALIPAY-->
<view v-else :id="n.attrs.id" :class="'_'+n.name+' '+(n.attrs.class||'')" :style="n.attrs.style">
<trees :nodes="n.children" :lazyLoad="lazyLoad" />
</view>
<!--#endif-->
<!--#ifndef MP-ALIPAY-->
<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :style="n.attrs.style" :nodes="n.children"
:lazyLoad="lazyLoad" />
<!--#endif-->
</block>
</view>
</template>
<script module="handler" lang="wxs" src="./handler.wxs"></script>
<script module="handler" lang="sjs" src="./handler.sjs"></script>
<script>
global.Parser = {};
import trees from './trees'
export default {
components: {
trees
},
name: 'trees',
data() {
return {
ns: [],
placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
loadVideo:
// #ifdef APP-PLUS
false
// #endif
// #ifndef APP-PLUS
true
// #endif
}
},
props: {
nodes: Array,
lazyLoad: Boolean,
},
watch: {
nodes: {
immediate: true,
handler(val) {
this.ns = val;
// #ifdef APP-PLUS
// APP 上避免 video 错位需要延时渲染
setTimeout(() => {
this.loadVideo = true;
}, 3000)
// #endif
}
}
},
mounted() {
// 获取顶层组件
var _top = this.$parent;
while (_top.$options.name != 'parser') {
if (_top.top) {
_top = _top.top;
break;
}
_top = _top.$parent;
}
this.top = _top;
for (var j = this.nodes.length, item; item = this.nodes[--j];) {
if (item.c) continue;
if (item.name == 'img')
_top.imgList.setItem(item.attrs.i, item.attrs.src);
else if (item.name == 'video' || item.name == 'audio') {
var ctx;
if (item.name == 'video')
ctx = uni.createVideoContext(item.attrs.id
// #ifndef MP-BAIDU
, this
// #endif
);
else if (this.$refs[item.attrs.id])
ctx = this.$refs[item.attrs.id][0];
if (ctx) {
ctx.id = item.attrs.id;
_top.videoContexts.push(ctx);
}
}
// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
if (item.attrs && item.attrs.id) {
_top.anchors = _top.anchors || [];
_top.anchors.push({
id: item.attrs.id,
node: this
})
}
// #endif
}
},
methods: {
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 node = this.ns[e.currentTarget.dataset.i];
if (this.lazyLoad && !node.load)
this.$set(node, 'load', true);
},
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 context, src = '',
target = e.currentTarget,
source = target.dataset.source,
node = this.ns[target.dataset.i];
if (source == 'video' || source == 'audio') {
// 加载其他 source
var index = (node.i || 0) + 1;
if (index < node.attrs.source.length)
this.$set(node, 'i', index);
if (source == 'video') context = uni.createVideoContext(target.id, this);
else if (e.detail.__args__) {
e.detail = e.detail.__args__[0];
context = e.detail.context;
}
} else if (source == 'img')
context = {
setSrc: src => {
node.attrs.src = src;
}
}
this.top && this.top.$emit('error', {
source,
target,
errMsg: e.detail.errMsg,
errCode: e.detail.errCode,
context
});
},
_loadVideo(e) {
var i = e.target.dataset.i;
this.ns[i].lazyLoad = false;
this.ns[i].attrs.autoplay = true;
}
}
}
</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 {
position: relative;
display: inline-block;
max-width: 100%;
}
/* #ifdef MP-WEIXIN */
:host {
display: inline;
}
/* #endif */
/* #ifdef MP */
.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;
}
._blockquote,
._div,
._p,
._ol,
._ul,
._li {
display: block;
}
._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 {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
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 */
._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>
<template>
<view class="mask mask-show" v-if="loadingShow" @touchmove.stop.prevent="preventTouchMove">
<!-- 加载动画开始 -->
<view class="preloader">
<view class="loader"></view>
</view>
<!-- 加载动画结束 -->
<view class="title">数据读取中...</view>
</view>
</template>
<script scoped="true">
import { mapState, mapMutations } from 'vuex';
export default {
computed: {
...mapState(['loadingShow'])
},
methods:{
preventTouchMove(){
console.log('stop user scroll it!');
return;
}
}
};
</script>
<style>
.mask {
/* pointer-events: none; */
position: fixed;
z-index: 99999;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.mask.mask-show {
background: rgba(255,255,255, 0.3);
}
.title {
color: #333;
font-size: 28rpx;
margin-top: 20rpx;
}
.loader {
display: block;
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 3rpx solid transparent;
border-top-color: #9370db;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.loader::before {
content: "";
position: absolute;
top: 5rpx;
left: 5rpx;
right: 5rpx;
bottom: 5rpx;
border-radius: 50%;
border: 3rpx solid transparent;
border-top-color: #ba55d3;
-webkit-animation: spin 3s linear infinite;
animation: spin 3s linear infinite;
}
.loader::after {
content: "";
position: absolute;
top: 15rpx;
left: 15rpx;
right: 15rpx;
bottom: 15rpx;
border-radius: 50%;
border: 3rpx solid transparent;
border-top-color: #ff00ff;
-webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
</style>
/* 下拉刷新区域 */
.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);
}
}
\ No newline at end of file
<!-- 下拉刷新区域 -->
<template>
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.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>
<!--空布局
可作为独立的组件, 不使用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 lang="scss">
@import '@/style/mixin.scss';
/* 无任何数据的空布局 */
.mescroll-empty {
box-sizing: border-box;
width: 100%;
padding: 100rpx 50rpx;
text-align: center;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
}
.mescroll-empty.empty-fixed {
z-index: 99;
position: absolute; /*transform会使fixed失效,最终会降级为absolute */
top: 100rpx;
left: 0;
}
.mescroll-empty .empty-icon {
width: 280rpx;
height: 280rpx;
}
.mescroll-empty .empty-tip {
margin-top: 20rpx;
font-size: 24rpx;
color: gray;
}
.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>
<!-- 回到顶部的按钮 -->
<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>
/* 上拉加载区域 */
.mescroll-upwarp {
min-height: 60rpx;
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);
}
}
\ No newline at end of file
<!-- 上拉加载区域 -->
<template>
<view class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="isUpLoading">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.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>
.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);
}
}
\ No newline at end of file
<template>
<view class="mescroll-body" :style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" @touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" @touchcancel="touchendEvent" >
<view class="mescroll-body-content" :style="{ transform: translateY, transition: transition }">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过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">
<view class="downwarp-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>
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
</view>
</template>
<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';
export default {
components: {
MescrollEmpty,
MescrollTop
},
data() {
return {
mescroll: {optDown:{},optUp:{}}, // mescroll实例
downHight: 0, //下拉刷新: 容器高度
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
downLoadType: 4, // 下拉刷新状态 (inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
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)
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
},
navbar: {
type: Boolean,
default: true
}, // 高度是否减去导航栏和状态栏部分
},
computed: {
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
minHeight(){
let minHeight = 0;
if(this.height > 0){
minHeight = this.toPx(this.height);
}else if(this.height && Number(this.height) < 0){
minHeight = this.toPx('100%') + uni.upx2px(this.height)
} else {
minHeight = this.toPx('100%');
}
if(this.navbar){
return (minHeight - this.statusBarHeight - uni.upx2px(88) )+ 'px'
}else {
return minHeight + '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' : this.downTransition;
},
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(){
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;
},
//注册列表touchstart事件,用于下拉刷新
touchstartEvent(e) {
this.mescroll.touchstartEvent(e);
},
//注册列表touchmove事件,用于下拉刷新
touchmoveEvent(e) {
this.mescroll.touchmoveEvent(e);
},
//注册列表touchend事件,用于下拉刷新
touchendEvent(e) {
this.mescroll.touchendEvent(e);
},
// 点击空布局的按钮回调
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: {
inOffset(mescroll) {
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
},
outOffset(mescroll) {
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(mescroll) {
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
},
// 派发下拉刷新的回调
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);
// mescroll-body在Android小程序下拉会卡顿,无法像mescroll-uni那样通过设置"disableScroll":true解决,只能用动画过渡缓解
// #ifdef MP
if(sys.platform == "android") vm.downTransition = 'transform 200ms'
// #endif
// 因为使用的是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>
@import "./mescroll-body.css";
@import "./components/mescroll-down.css";
@import './components/mescroll-up.css';
</style>
// 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;
// 全局配置
// 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
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
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: "http://www.mescroll.com/img/mescroll-empty.png?v=1", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
tip: '~ 空空如也 ~' // 提示
}
}
}
export default GlobalOption
.mescroll-uni-warp{
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);
}
}
/* mescroll
* version 1.2.8
* 2020-06-28 wenju
* http://www.mescroll.com
*/
export default function MeScroll(options, isScrollBody) {
let me = this;
me.version = '1.2.8'; // 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); // 不显示下拉进度,直接执行下拉回调
}
}
// 自动触发上拉加载
setTimeout(function(){ // 延时确保先执行down的callback,再执行up的callback,因为部分小程序emit是异步,会导致isUpAutoLoad判断有误
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, 此值用于控制最大的误差
fps: 80, // 下拉节流 (值越大每秒刷新频率越高)
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, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
endDownScroll: null, // 结束下拉刷新的回调
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,两者可同时触发;
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
callback: null, // 上拉加载的回调;function(page,mescroll){ }
page: {
num: 1, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
size: 10, // 每页数据的数量
time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
},
noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
offset: 80, // 距底部多远时,触发upCallback
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
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.lastPoint = this.startPoint; // 重置上次move的点
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
this.inTouchend = false; // 标记不是touchend
}
/* 列表touchmove事件 */
MeScroll.prototype.touchmoveEvent = function(e) {
// #ifdef H5
window.isPreventDefault = false // 标记不需要阻止window事件
// #endif
if (!this.optDown.use) return;
if (!this.startPoint) return;
let me = this;
// 节流
let t = new Date().getTime();
if (me.moveTime && t - me.moveTime < me.moveTimeDiff) { // 小于节流时间,则不处理
return;
} else {
me.moveTime = t
if(!me.moveTimeDiff) me.moveTimeDiff = 1000 / me.optDown.fps
}
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; 在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))) {
// 下拉的角度是否在配置的范围内
let angle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
if (angle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
me.inTouchend = true; // 标记执行touchend
me.touchendEvent(); // 提前触发touchend
return;
}
// #ifdef H5
window.isPreventDefault = true // 标记阻止window事件
// #endif
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 += Math.round(diff * me.optDown.outOffsetRate); // 越往下,高度变化越小
} else { // 向上收
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
}
}
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.optDown.endDownScroll && this.optDown.endDownScroll(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 {
let page = this.optUp.page;
page.num = this.startNum; // 重置为第一页
this.showDownScroll(); // 下拉刷新中...
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
}
/* 显示下拉进度布局 */
MeScroll.prototype.showDownScroll = function() {
this.isDownScrolling = true; // 标记下拉中
if (this.optDown.native) {
uni.startPullDownRefresh(); // 系统自带的下拉刷新
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
} else{
this.downHight = this.optDown.offset; // 更新下拉区域高度
this.optDown.showLoading && this.optDown.showLoading(this, this.downHight); // 下拉刷新中...
}
}
/* 显示系统自带的下拉刷新时需要处理的业务 */
MeScroll.prototype.onPullDownRefresh = function() {
this.isDownScrolling = true; // 标记下拉中
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
/* 结束下拉刷新 */
MeScroll.prototype.endDownScroll = function() {
if (this.optDown.native) { // 结束原生下拉刷新
this.isDownScrolling = false;
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
uni.stopPullDownRefresh();
return
}
let me = this;
// 结束下拉刷新的方法
let endScroll = function() {
me.downHight = 0;
me.isDownScrolling = false;
me.optDown.endDownScroll && me.optDown.endDownScroll(me);
!me.isScrollBody && me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
}
// 结束下拉刷新时的回调
let delay = 0;
if (me.optDown.afterLoading) delay = me.optDown.afterLoading(me); // 结束下拉刷新的延时,单位ms
if (typeof delay === 'number' && delay > 0) {
setTimeout(endScroll, delay);
} else {
endScroll();
}
}
/* 锁定下拉刷新: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.isBounce) me.setBounce(false); // 不允许bounce时,需禁止window的touchmove事件
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
me.startNum = me.optUp.page.num; // 记录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;
}
/* 设置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 {
this.optUp.page.num += 1;
// 还有下一页
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
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
}
/* 是否允许下拉回弹(橡皮筋效果); true或null为允许; false禁止bounce */
MeScroll.prototype.setBounce = function(isBounce) {
// #ifdef H5
if (isBounce === false) {
this.optUp.isBounce = false; // 禁止
// 标记当前页使用了mescroll (需延时,确保page已切换)
setTimeout(function() {
let uniPageDom = document.getElementsByTagName('uni-page')[0];
uniPageDom && uniPageDom.setAttribute('use_mescroll', true)
}, 30);
// 避免重复添加事件
if (window.isSetBounce) return;
window.isSetBounce = true;
// 需禁止window的touchmove事件才能有效的阻止bounce
window.bounceTouchmove = function(e) {
if(!window.isPreventDefault) return; // 根据标记判断是否阻止
let el = e.target;
// 当前touch的元素及父元素是否要拦截touchmove事件
let isPrevent = true;
while (el !== document.body && el !== document) {
if (el.tagName === 'UNI-PAGE') { // 只扫描当前页
if (!el.getAttribute('use_mescroll')) {
isPrevent = false; // 如果当前页没有使用mescroll,则不阻止
}
break;
}
let cls = el.classList;
if (cls) {
if (cls.contains('mescroll-touch')) { // 采用scroll-view 此处不能过滤mescroll-uni,否则下拉仍然有回弹
isPrevent = false; // mescroll-touch无需拦截touchmove事件
break;
} else if (cls.contains('mescroll-touch-x') || cls.contains('mescroll-touch-y')) {
// 如果配置了水平或者垂直滑动
let curX = e.touches ? e.touches[0].pageX : e.clientX; // 当前第一个手指距离列表顶部的距离x
let curY = e.touches ? e.touches[0].pageY : e.clientY; // 当前第一个手指距离列表顶部的距离y
if (!this.preWinX) this.preWinX = curX; // 设置上次移动的距离x
if (!this.preWinY) this.preWinY = curY; // 设置上次移动的距离y
// 计算两点之间的角度
let x = Math.abs(this.preWinX - curX);
let y = Math.abs(this.preWinY - curY);
let z = Math.sqrt(x * x + y * y);
this.preWinX = curX; // 记录本次curX的值
this.preWinY = curY; // 记录本次curY的值
if (z !== 0) {
let angle = Math.asin(y / z) / Math.PI * 180; // 角度区间 [0,90]
if ((angle <= 45 && cls.contains('mescroll-touch-x')) || (angle > 45 && cls.contains('mescroll-touch-y'))) {
isPrevent = false; // 水平滑动或者垂直滑动,不拦截touchmove事件
break;
}
}
}
}
el = el.parentNode; // 继续检查其父元素
}
// 拦截touchmove事件:是否可以被禁用&&是否已经被禁用 (这里不使用me.preventDefault(e)的方法,因为某些情况下会报找不到方法的异常)
if (isPrevent && e.cancelable && !e.defaultPrevented && typeof e.preventDefault === "function") e.preventDefault();
}
window.addEventListener('touchmove', window.bounceTouchmove, {
passive: false
});
} else {
this.optUp.isBounce = true; // 允许
if (window.bounceTouchmove) {
window.removeEventListener('touchmove', window.bounceTouchmove);
window.bounceTouchmove = null;
window.isSetBounce = false;
}
}
// #endif
}
<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" @touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" @touchcancel="touchendEvent" :scroll-y='scrollable' :enable-back-to-top="true">
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-uni-content" :style="{'transform': translateY, 'transition': transition}">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过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">
<view class="downwarp-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>
</scroll-view>
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
</view>
</template>
<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';
export default {
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(){
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获取是否向上滑动
})
},
//注册列表touchstart事件,用于下拉刷新
touchstartEvent(e) {
this.mescroll.touchstartEvent(e);
},
//注册列表touchmove事件,用于下拉刷新
touchmoveEvent(e) {
this.mescroll.touchmoveEvent(e);
},
//注册列表touchend事件,用于下拉刷新
touchendEvent(e) {
this.mescroll.touchendEvent(e);
},
// 点击空布局的按钮回调
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(mescroll) {
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
},
outOffset(mescroll) {
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(mescroll) {
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
vm.downResetTimer && clearTimeout(vm.downResetTimer)
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
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>
/**
* 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;
/**
* 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对象
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 (mescroll-mixins.js)
// 自动加载当前tab的数据
if(this.i === this.index){
this.isInit = true; // 标记为true
this.mescroll.triggerDownScroll();
}
},
}
}
export default MescrollMoreItemMixin;
/**
* 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;
<template>
<view>
<view class="popupClick" @click="onPopupShow()"><slot></slot></view>
<popup v-model="currentValue">
<view class="multiple_choice_title">
<text @click="currentValue = false">取消</text>
<view>{{title}}</view>
<text @click="onConfirm">确定</text>
</view>
<scroll-view scroll-y="true" class="multiple_choice_scroll">
<view class="multiple_choice_box">
<view class="multiple_choice_content">
<view class="multiple_choice_item" v-for="(item,index) of rangeList" :key="index" @click="onSelect(index)">
<view class="select" :class="{active: item.select }"></view>
<view class="value">{{item[rangeKey]}}</view>
</view>
</view>
</view>
</scroll-view>
</popup>
</view>
</template>
<script>
import Popup from './popup.vue';
export default {
components: {
Popup
},
props: {
value: {
type: Boolean,
default: false
},
title: {
type: String,
default: ""
},
range: {
type: Array,
default: function(){
return []
}
},
rangeKey: {
type: String,
default: "name"
},
},
created() {
if (typeof this.value !== 'undefined') {
this.currentValue = this.value;
}
this.rangeList = this.range.map(item => {
item.select = false;
return item;
});
},
watch: {
value(val) {
this.currentValue = val;
},
currentValue(val) {
this.$emit(val ? 'on-show' : 'on-hide');
this.$emit('input', val);
},
range(val){
this.rangeList = val.map(item => {
item.select = false;
return item;
});
}
},
data() {
return {
currentValue: false,
rangeList: []
};
},
methods: {
onPopupShow(){
this.currentValue = true;
},
onSelect(index){
let item = this.rangeList[index];
item.select = !item.select;
this.$set(this.rangeList, index, item);
},
onConfirm(){
let resultList = this.rangeList.filter(item => {
if(item.select){
return true;
} else {
return false;
}
});
if(resultList.length > 0){
this.currentValue = false;
this.$emit("change", resultList);
} else {
uni.showToast({
title: "请选择",
icon: "none"
});
}
}
},
mounted() {}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
.multiple_choice_title {
display: flex;
justify-content: space-between;
height: 88upx;
line-height: 88upx;
border-bottom: 2upx solid #ebebeb;
padding: 0 20upx;
background-color: #FFF;
}
.multiple_choice_title view {
font-size: 32upx;
}
.multiple_choice_title text {
width: 80upx;
flex-shrink: 0;
text-align: center;
}
.multiple_choice_title text {
font-size: 28upx;
color: #999;
}
.multiple_choice_title text:last-child {
color: $themeColor;
}
.multiple_choice_scroll {
background-color: #FFF;
max-height: 60vh;
min-height: 30vh;
}
.multiple_choice_box {
display: flex;
justify-content: center;
align-items: center;
min-height: 30vh;
}
.multiple_choice_content {
.multiple_choice_item {
height: 100rpx;
padding: 0 30rpx;
font-size: 30rpx;
display: flex;
align-items: center;
// &:nth-child(2n) {
// background-color: #f7f7f7;
// }
.select {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
// background-image: url(../../static/icon/ic_notselected.png);
// background-size: 100% 100%;
flex-shrink: 0;
border-radius: 50%;
border: 2rpx solid #ccc;
&.active {
border: 2rpx solid $themeColor;
background-color: $themeColor;
text-align: center;
line-height: 38rpx;
transform:rotate(15deg);
}
&.active::before {
content: "√";
color: #FFF;
}
}
.value {
width: calc(100% - 55rpx);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
}
}
}
</style>
<template>
<view @touchmove="onTouchMove">
<!-- 遮罩层动画 -->
<view class="mask" @click="hideOnBlur && (currentValue = false)" :style="{top:maskTop, bottom:maskBottom, zIndex: zIndex}" v-if="currentValue"></view>
<!-- 显示信息层 -->
<view class="popup_box" :class="{'bottom': type == 'bottom' ,'center':type == 'center', top: type == 'top'}" :style="{opacity:opacity,transform:transform,top:popupTop, bottom:popupBottom,zIndex: zIndex + 1}">
<slot></slot>
</view>
</view>
</template>
<script>
export default {
props: {
//是否显示
value: {
type: Boolean,
default: function() {
return false;
}
},
//点击遮罩层关闭弹窗
hideOnBlur: {
type: Boolean,
default: function() {
return true;
}
},
//禁止页面滚动(H5生效)
scroll: {
type: Boolean,
default: true
},
// 类型
// bottom 靠下
// center 居中
// top 靠上
type: {
type: String,
default: function() {
return "bottom";
}
},
// 偏移
offset: {
type: Number,
default: function() {
return 0;
}
},
// index
zIndex: {
type: Number,
default: function() {
return 500;
}
},
},
created() {
this.systemInfo = uni.getSystemInfoSync();
if (typeof this.value !== "undefined") {
this.currentValue = this.value;
this.setAnimation(this.value);
}
},
watch: {
value(val) {
this.currentValue = val;
this.setAnimation(val);
},
currentValue(val) {
this.$emit("change", val);
this.$emit("input", val);
}
},
data() {
return {
// 传进来的值
currentValue: false,
opacity: 0,
popupTop: "inherit",
popupBottom: "inherit",
maskTop: "0rpx",
maskBottom: "0rpx",
transform: "",
systemInfo: {},
};
},
methods: {
onTouchMove: function(event) {
!this.scroll && event.preventDefault();
},
getPxRpx(px){
let ratio = 750 / this.systemInfo.screenWidth;
return ratio * px;
},
setAnimation(val) {
if (this.type == "bottom") {
if (val) {
this.transform = "translateY(0%)";
this.opacity = 1;
this.popupTop = "inherit";
if(this.offset > 0){
this.popupBottom = this.offset + "rpx";
this.maskBottom = this.offset + "rpx";
} else {
this.popupBottom = this.getPxRpx(this.systemInfo.windowBottom) + "rpx";
this.maskBottom = "0rpx";
}
} else {
this.opacity = 0;
this.transform = "translateY(100%)";
setTimeout(() => {
this.popupTop = "inherit";
this.popupBottom = "0rpx";
this.maskTop = "0rpx";
this.maskBottom = "0rpx";
},400);
}
} else if (this.type == "center") {
if (val) {
this.opacity = 1;
this.transform = "translateX(-50%) translateY(-50%) scale(1)";
this.popupTop = "50%";
} else {
this.opacity = 0;
this.popupTop = "50%";
this.transform = "translateX(-50%) translateY(-50%) scale(0)";
}
} else if (this.type == "top") {
if (val) {
this.transform = "translateY(0%)";
this.opacity = 1;
this.popupBottom = "inherit";
if(this.offset > 0){
this.popupTop = (this.offset + this.getPxRpx(this.systemInfo.statusBarHeight)) + "rpx";
this.maskTop = this.popupTop;
} else {
this.popupTop = "0rpx";
this.maskTop = "0rpx";
}
} else {
this.opacity = 0;
this.transform = "translateY(-100%)";
setTimeout(() => {
this.popupTop = "0rpx";
this.popupBottom = "inherit";
this.maskTop = "0rpx";
this.maskBottom = "0rpx";
},400);
}
}
}
}
};
</script>
<style lang="scss" scoped>
/*遮罩层*/
.mask {
position: fixed;
z-index: 500;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
transition: all 0.4s;
}
.popup_box {
position: fixed;
max-width: 100%;
max-height: 100%;
min-height: 50rpx;
z-index: 501;
opacity: 0;
font-size: 28rpx;
transition: all 0.4s;
&.bottom {
left: 0rpx;
bottom: 0rpx;
min-width: 100%;
transform: translateY(100%);
}
&.center {
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
}
&.top {
left: 0rpx;
top: 0rpx;
right: 0rpx;
min-width: 100%;
transform: translateY(100%);
}
}
</style>
<template>
<view>
<view class="popupClick" @click="onPopupShow()"><slot></slot></view>
<view class="popupMask" v-if="popupShow" @click="onPopupHide"></view>
<view class="popupContentBox" v-if="popupShow">
<view class="close" @click="onPopupHide">×</view>
<view class="title">{{ popupConfig.title }}</view>
<view class="popupContent">
<view class="introduce">{{ popupConfig.tips }}</view>
<input
class="input"
:type="popupConfig.inputType"
adjust-position="true"
:password="popupConfig.password"
v-model="popupInput"
:placeholder="popupConfig.placeholder"
:maxlength="popupConfig.maxlength"
focus="true"
placeholder-style="color:#999"
:confirm-type="popupConfig.confirmType"
/>
</view>
<view class="popupBut">
<button @click="onConfirm">{{ popupConfig.confirmText }}</button>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
value: {
type: String,
default: function() {
return '';
}
},
options: {
type: Object,
default: function() {
return {};
}
}
},
data() {
return {
popupConfig: {
title: '操作',
tips: "请输入",
confirmText: '确认',
placeholder: '',
password: false,
inputType: 'text',
maxlength: 140,
confirmType: "done"
},
popupInput: '',
popupShow: false
};
},
//第一次加载
created() {
if(this.value){
this.popupInput = this.value;
}
if(this.options && typeof(this.options) == "object"){
this.popupConfig = Object.assign(this.popupConfig, this.options);
}
},
watch:{
value(val){
this.popupInput = val;
},
options(val){
if(val && typeof(val) == "object"){
this.popupConfig = Object.assign(this.popupConfig, val);
}
}
},
//方法
methods: {
//打开弹窗
onPopupShow(value,options) {
if(value){
this.popupInput = value;
}
if(options && typeof(options) == "object"){
this.popupConfig = Object.assign(this.popupConfig, options);
}
this.popupShow = true;
},
//关闭弹窗
onPopupHide() {
this.popupShow = false;
},
onConfirm() {
if (this.popupInput == '') {
uni.showToast({
title: '请输入',
icon: 'none'
});
return;
}
this.$emit('confirm', {
close:() => {
this.popupShow = false;
},
value:this.popupInput
});
}
}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
.popupMask {
position: fixed;
top: 0upx;
left: 0upx;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 510;
animation: popupMask 0.4s;
}
.popupContentBox {
position: fixed;
top: 30%;
left: 10%;
width: 80%;
transform: translateY(-50%);
background-color: #fff;
z-index: 511;
animation: popupContentBox 0.4s;
}
.popupContentBox .close {
position: absolute;
top: 10upx;
right: 15upx;
color: #999;
font-size: 42upx;
line-height: 40upx;
}
.popupContentBox .title {
text-align: center;
height: 80upx;
line-height: 80upx;
font-size: 34upx;
color: #666;
}
.popupContentBox .popupContent {
padding: 30upx 40upx;
}
.popupContentBox .popupContent .input {
width: 100%;
border-radius: 10upx;
border: 1px solid #eee;
height: 80upx;
font-size: 30upx;
padding: 0 20upx;
box-sizing: border-box;
}
.popupContentBox .popupContent .introduce {
font-size: 28upx;
color: #999;
padding-bottom: 10upx;
}
.popupContentBox .popupBut {
padding: 20upx 20upx 20upx 20upx;
}
.popupContentBox .popupBut button {
background-color:$themeColor;
color: #fff;
}
@keyframes popupMask {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes popupContentBox {
0% {
opacity: 0;
transform: translateY(-60%);
}
100% {
opacity: 1;
transform: translateY(-50%);
}
}
</style>
<template>
<view>
<!-- 加载动画组件 -->
<z-loading></z-loading>
<!-- #ifdef MP-WEIXIN -->
<!-- 小程序登录弹窗组件 -->
<applets-login></applets-login>
<!-- #endif -->
</view>
</template>
<script>
import zLoading from "@/components/common/loading.vue"
// #ifdef MP-WEIXIN
import appletsLogin from "@/components/common/applets-login.vue"
// #endif
export default {
components:{
zLoading,
// #ifdef MP-WEIXIN
appletsLogin
// #endif
}
};
</script>
<style lang="scss" scoped>
</style>
# swipe-action 滑动操作
### `觉得不错,给个5星好评吧`
### QQ交流群(加群持续更新) 607391225
![QQ交流群](http://qn.kemean.cn//upload/202004/14/15868301778472k7oubi6.png)
# [点击跳转-本插件示例](https://ext.dcloud.net.cn/plugin?id=2009)
# [点击跳转-5年的web前端开源的uni-app快速开发模板-下载看文档](https://ext.dcloud.net.cn/plugin?id=2009)
### 使用时不懂的请看上面链接的插件示例
### 示例代码
```
<template>
<view>
<swipe-action :options="options" :show="show"><view class="swipe_action">滑动</view></swipe-action>
<swipe-action :options="options" disabled><view class="swipe_action">禁止滑动</view></swipe-action>
<view v-for="(item, index) of 3" :key="index" class="swipe_action_list">
<swipe-action :options="options3" :index="index" @button="onButton">
<view class="swipe_action">滑动列表{{ index + 1 }}</view>
</swipe-action>
</view>
</view>
</template>
<script>
import swipeAction from '@/components/zhouWei-swipeAction';
export default {
components: {
swipeAction
},
data() {
return {
options: [
{
text: '删除',
style: {
backgroundColor: '#dd524d'
}
},
{
text: '取消',
style: {
backgroundColor: '#007aff'
}
}
],
show: true
};
},
methods: {
onButton(e) {
uni.showToast({
title: '您点击了滑动列表' + (e.index + 1) + '的第' + (e.buttonIndex + 1) + '个按钮,按钮为‘' + e.content.text + '’',
icon: 'none'
});
}
},
}
</script>
```
### 使用说明
| 名称 | 类型 | 默认值 | 描述 |
| ----------|--------------- | ------------- | -------------------|
| options | Array | [] | 查看options参数说明 |
| disabled | Boolean | false | 是否禁止滑动 |
| show | Boolean | false | 是否打开 |
| autoClose | Boolean | true | 点击后是否自动关闭 |
| index | Number | 0 | 循环的时候的索引值,通过@button传递出去 |
### options参数说明
| 名称 | 类型 | 描述 |
| ------------------------|--------------- | -------------------|
| text | String | 按钮名称 |
| style | Object | 按钮样式 |
| style.backgroundColor | String | 按钮背景颜色 |
| style.fontSize | String | 按钮字体大小 |
| style.color | String | 按钮字体颜色 |
### 事件
| 名称 | 描述 |
| -----------------| --------------------------|
| button | 左滑按钮点击事件 |
```
按钮左滑按钮点击事件返回值
{
content: "点击按钮的options参数",
index: "循环的时候的索引值",
buttonIndex: "点击按钮的索引值"
}
```
\ No newline at end of file
<template>
<view class="swipe_action_box" @touchstart="onTouchstart" @touchmove="onTouchmove" @touchcancel="onTouchcancel" @touchend="onTouchend">
<view class="swipe_action_item" :style="{ width: (screenWidth + maxWidth) + 'px', transform: 'translateX(' + translateX + 'px)', transition: 'transform ' + animationTime + 'ms cubic-bezier(.165, .84, .44, 1)' }">
<view class="swipe_action_content"><slot></slot></view>
<view class="swipe_action_btn_box" ref="swipeActionBtnBox">
<view v-for="(item,index) of optionsList" :key="index" class="swipe_action_btn" :style="{
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD'
}" @click.stop="onBtn(index,item)">
<text :style="{
fontSize: item.style && item.style.fontSize ? item.style.fontSize : '14px',
color: item.style && item.style.color ? item.style.color : '#FFFFFF'
}">{{ item.text }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
// #ifdef APP-NVUE
const dom = weex.requireModule('dom');
// #endif
export default {
props: {
/**
* 按钮内容
*/
options: {
type: Array,
default () {
return []
}
},
/**
* 禁用
*/
disabled: {
type: Boolean,
default: false
},
/**
* 变量控制开关
*/
show: {
type: Boolean,
default: false
},
/**
* 是否自动关闭
*/
autoClose: {
type: Boolean,
default: true
},
/**
* swipe-action 的索引值
*/
index: {
type: Number,
default: 0
}
},
data() {
return {
//开始触摸时间
startTime: 0,
//开始触摸距离
touchStartX: 0,
//最大距离
maxWidth: 58,
//滑动距离
translateX: 0,
animationTime: 0,
//上次的位置
currentX: 0,
screenWidth: 0,
optionsList: []
};
},
watch:{
show(val){
if(val){
this.animationTime = 350;
this.translateX = -this.maxWidth;
} else {
this.animationTime = 350;
this.translateX = 0;
}
},
options(val){
this.optionsList = val;
},
},
created() {
let systemInfo = uni.getSystemInfoSync();
this.screenWidth = systemInfo.screenWidth;
this.optionsList = this.options;
},
mounted() {
const _this = this;
setTimeout(() => {
// #ifdef APP-NVUE
dom.getComponentRect(this.$refs['swipeActionBtnBox'], (data) => {
_this.maxWidth = data.size.width;
if(_this.show){
_this.animationTime = 350;
_this.translateX = -data.size.width;
}
});
// #endif
// #ifndef APP-NVUE
uni.createSelectorQuery().in(this).selectAll('.swipe_action_btn_box')
.boundingClientRect(data => {
_this.maxWidth = data[0].width;
if(_this.show){
_this.animationTime = 350;
_this.translateX = -data[0].width;
}
}).exec()
// #endif
},500);
},
//方法
methods: {
onBtn(index, item) {
this.$emit('button', {
content: item,
index: this.index,
buttonIndex: index
});
if(this.autoClose){
this.animationTime = 350;
this.translateX = 0;
}
},
// 手指触摸动作开始
onTouchstart(e) {
if(this.disabled){
return;
}
//储存手指触摸坐标,当前时间戳,当前坐标
// #ifdef APP-NVUE
this.touchStartX = e.changedTouches[0].screenX;
// #endif
// #ifndef APP-NVUE
this.touchStartX = e.changedTouches[0].clientX;
// #endif
this.startTime = new Date().getTime();
this.currentX = this.translateX;
},
// 手指触摸后移动
onTouchmove(e) {
if(this.disabled){
return;
}
//手指当前坐标
// #ifdef APP-NVUE
const clientX = e.changedTouches[0].screenX;
// #endif
// #ifndef APP-NVUE
const clientX = e.changedTouches[0].clientX;
// #endif
//计算滑动距离
const difference = this.touchStartX - clientX;
//判断左滑还是右滑
if (difference > 0) {
//计算当前已滑动距离
const leftDifference = this.currentX - Math.abs(difference);
//判断是否大于滑动的最大宽度
if (this.maxWidth < Math.abs(leftDifference)) {
this.animationTime = 0;
this.translateX = -this.maxWidth;
} else {
this.animationTime = 0;
this.translateX = leftDifference;
}
} else {
const rightDifference = this.currentX + Math.abs(difference);
if (0 < rightDifference) {
this.animationTime = 0;
this.translateX = 0;
} else {
this.animationTime = 0;
this.translateX = rightDifference;
}
}
},
// 手指触摸动作被打断,如来电提醒,弹窗
onTouchcancel(e) {
if(this.disabled){
return;
}
// #ifdef APP-NVUE
this.finallySlide(e.changedTouches[0].screenX);
// #endif
// #ifndef APP-NVUE
this.finallySlide(e.changedTouches[0].clientX);
// #endif
},
// 手指触摸动作结束
onTouchend(e) {
if(this.disabled){
return;
}
// #ifdef APP-NVUE
this.finallySlide(e.changedTouches[0].screenX);
// #endif
// #ifndef APP-NVUE
this.finallySlide(e.changedTouches[0].clientX);
// #endif
},
//最终判断滑动
finallySlide(finallyX) {
//手指离开的时间
const endTime = new Date().getTime();
//手机滑动屏幕的总花费时间
const timeDifference = endTime - this.startTime;
//手指触摸总滑动距离
const distanceDifference = this.touchStartX - finallyX;
//判断最终滑动方向
if (distanceDifference > 0) {
//判断是否滑动到左边 滑动距离超过3分之一 或者 滑动时间在300毫秒并且距离在4分之一
if (Math.abs(this.translateX) > this.maxWidth / 2 || (timeDifference < 300 && distanceDifference > this.maxWidth / 4)) {
this.animationTime = 350;
this.translateX = -this.maxWidth;
} else {
this.animationTime = 350;
this.translateX = 0;
}
} else if (distanceDifference < 0) {
//判断是否滑动到右边 滑动距离超过3分之一 或者 滑动时间在300毫秒并且距离在4分之一
if (Math.abs(this.translateX) < this.maxWidth / 2 || (timeDifference < 300 && Math.abs(distanceDifference) > this.maxWidth / 4)) {
this.animationTime = 350;
this.translateX = 0;
} else {
this.animationTime = 350;
this.translateX = -this.maxWidth;
}
}
}
}
};
</script>
<style scoped>
.swipe_action_box {
overflow: hidden;
width: 750rpx;
}
.swipe_action_item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.swipe_action_content {
width: 750rpx;
/* #ifndef APP-NVUE */
flex-shrink: 0;
/* #endif */
}
.swipe_action_btn_box {
/* #ifndef APP-NVUE */
display: flex;
flex-shrink: 0;
/* #endif */
flex-direction: row;
}
.swipe_action_btn {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 30rpx;
}
</style>
<template>
<view>
<view
:class="[navFixed ? 'header_fixed' : '', type == 'transparent' ? 'header_absolute': '', navShadow ? 'header_shadow': '', themeBgColorName]"
:style="[navBgColor ? { backgroundImage: navBgColor } : {}, { paddingTop: statusBarHeight + 'px', color: navFontColor, opacity: transparentValue}]"
>
<view class="header_content">
<view class="header_left_box">
<slot name="left">
<view class="header_left_info" :class="[isTwoBtn ? 'header_btnMongol' : '' , isWhite && isTwoBtn ? 'header_colorWhite_btnMongol' : '']" v-if="back || $slots.left || home">
<view class="header_left_back" :class="[ isTwoBtn ? 'header_btnMongol_left_back' : '' ]" v-if="back && !firstPage" @click="onBackPage">
<image class="header_icon" v-if="isWhite" src="/static/zhouWei-navBar/icon_back_white.png" mode="aspectFit"></image>
<image class="header_icon" v-else src="/static/zhouWei-navBar/icon_back_black.png" mode="aspectFit"></image>
</view>
<text class="header_left_line" :class="[isWhite ? 'header_colorWhite_left_line' : '']" v-if="isTwoBtn"></text>
<view class="header_left_home" :class="[isTwoBtn ? 'header_btnMongol_left_home' : '']" v-if="(firstPage && back) || home" @click="onBackHome">
<image class="header_icon" v-if="isWhite" src="/static/zhouWei-navBar/icon_home_white.png" mode="aspectFit"></image>
<image class="header_icon" v-else src="/static/zhouWei-navBar/icon_home_black.png" mode="aspectFit"></image>
</view>
</view>
</slot>
<view class="header_title" v-if="!titleCenter && ($slots.default || navTitle)" :style="{ color: navFontColor }">
<slot>
<text :style="{ color: navFontColor }">{{ navTitle }}</text>
</slot>
</view>
</view>
<view class="header_title header_title_center" v-if="titleCenter && ($slots.default || navTitle)" :style="{ color: navFontColor }">
<slot>
<text :style="{ color: navFontColor }">{{ navTitle }}</text>
</slot>
</view>
<view class="header_right_info"><slot name="right"></slot></view>
</view>
</view>
<view
class="header_transparentFixed header_fixed"
v-if="type == 'transparentFixed'"
:style="{ paddingTop: statusBarHeight + 'px', color: navTransparentFixedFontColor, opacity: 1 - transparentValue, zIndex: transparentValue < 0.3 ? 100 : 90}"
>
<view class="header_content">
<view class="header_left_box">
<slot name="transparentFixedLeft">
<view
class="header_left_info header_transparentFixed_left_info"
:class="[ isWhite ? 'header_transparentFixed_colorWhite_left_info' : '' ]"
v-if="back || $slots.left || home"
>
<view class="header_left_back" :class="[ isTwoBtn ? 'header_btnMongol_left_back' : '' ]" v-if="back && !firstPage" @click="onBackPage">
<image class="header_icon" v-if="isWhite" src="/static/zhouWei-navBar/icon_back_white.png" mode="aspectFit"></image>
<image class="header_icon" v-else src="/static/zhouWei-navBar/icon_back_black.png" mode="aspectFit"></image>
</view>
<text class="header_left_line" v-if="isTwoBtn"></text>
<view class="header_left_home" :class="[isTwoBtn ? 'header_btnMongol_left_home' : '']" v-if="(firstPage && back) || home" @click="onBackHome">
<image class="header_icon" v-if="isWhite" src="/static/zhouWei-navBar/icon_home_white.png" mode="aspectFit"></image>
<image class="header_icon" v-else src="/static/zhouWei-navBar/icon_home_black.png" mode="aspectFit"></image>
</view>
</view>
</slot>
<view class="header_title" v-if="!titleCenter && (navTitle || $slots.transparentFixed)" :style="{ color: navTransparentFixedFontColor }">
<slot name="transparentFixed">
<text :style="{ color: navTransparentFixedFontColor }">{{ navTitle }}</text>
</slot>
</view>
</view>
<view class="header_title header_title_center" v-if="titleCenter && (navTitle || $slots.transparentFixed)" :style="{ color: navTransparentFixedFontColor }">
<slot name="transparentFixed">
<text :style="{ color: navTransparentFixedFontColor }">{{ navTitle }}</text>
</slot>
</view>
<view class="header_right_info"><slot name="transparentFixedRight"></slot></view>
</view>
</view>
<view v-if="type == 'fixed'" :style="{ paddingTop: statusBarHeight + 'px' }"><view class="header_station"></view></view>
</view>
</template>
<script>
// 主页页面的页面路径
// 关联功能:打开的页面只有一个的时候右上角自动显示返回首页按钮,下面这个数组是排除显示返回首页的页面。
// 主页使用场景:小程序分享出去的页面,用户点击开是分享页面,很多情况下是没有返回首页按钮的
const mainPagePath = ['pages/home/home', 'pages/my/my', 'pages/demo/common', 'pages/template/common', 'pages/apiDemo/common'];
//返回首页的地址
const homePath = '/pages/demo/common';
//白色表达值
const whiteList = ['#FFF', '#fff', '#FFFFFF', '#ffffff', 'white', 'rgb(255,255,255)', 'rgba(255,255,255,1)'];
export default {
props: {
//是否显示返回按钮
// 1000 显示返回按钮
// 2000 不显示返回按钮
// 3000 自定义返回按钮方法,点击返回箭头后会发送一个backClick事件
backState: {
type: [String, Number],
default: function() {
return 1000;
}
},
//是否显示返回首页按钮
// 1000 显示首页按钮
// 2000 不显示首页按钮
// 3000 自定义首页按钮方法,点击首页箭头后会发送一个homeClick事件
homeState: {
type: [String, Number],
default: function() {
return 2000;
}
},
//导航背景色,支持渐变
bgColor: {
type: [String, Array],
default: function() {
return '#FFFFFF';
}
},
// 导航背景色渐变角度
bgColorAngle: {
type: [String, Number],
default: function() {
return 90;
}
},
//导航字体颜色,字体颜色为白色的时候会把手机状态栏设置为白色,否则为黑色
fontColor: {
type: String,
default: function() {
return '#000000';
}
},
//标题是否居中
titleCenter: {
type: Boolean,
default: function() {
return true;
}
},
//标题
title: {
type: String,
default: function() {
return '';
}
},
//类型 fixed为固定 默认
// ordinary 普通的 不固定
// transparent 透明不固定的
//transparentFixed 透明固定的
type: {
type: String,
default: function() {
return 'fixed';
}
},
//透明固定的时候字体颜色
transparentFixedFontColor: {
type: String,
default: function() {
return '#000000';
}
},
// 屏幕滑动距离顶部距离(透明固定导航比传)
scrollTop: {
type: Number,
default: function() {
return 0;
}
},
// 是否显示阴影
shadow: {
type: Boolean,
default: function() {
return false;
}
}
},
data() {
return {
//当前页面是否是第一个页面
firstPage: false,
//透明度值
transparentValue: 1,
//标题
navTitle: '',
//字体色
navFontColor: '#000000',
//背景色
navBgColor: '',
//透明底字体色
navTransparentFixedFontColor: '#000000',
// 导航栏高度
statusBarHeight: 0,
// 上次显示的导航栏颜色
lastFrontColor: '',
themeBgColorName: '',
};
},
computed: {
back() {
return this.backState == 1000 || this.backState == 3000;
},
home() {
return this.homeState == 1000;
},
//导航固定
navFixed() {
if (this.type == 'transparentFixed' || this.type == 'fixed') {
return true;
} else {
return false;
}
},
//导航底部线是否显示
navShadow() {
if (this.bgColor && typeof this.bgColor == 'string') {
return this.shadow && this.type !== 'transparent' && whiteList.includes(this.bgColor);
} else {
return false;
}
},
//导航字体是否是白色颜色
isWhite() {
return whiteList.includes(this.navFontColor);
},
//右上角是否有两个按钮
isTwoBtn() {
return (this.backState == 1000 || this.backState == 3000) && this.homeState == 1000 && !this.firstPage;
}
},
watch: {
title(val) {
this.navTitle = val;
},
fontColor(val) {
this.navFontColor = val;
this.settingColor();
},
bgColor(val) {
this.getNavBgColor(val);
},
transparentFixedFontColor(val) {
this.navTransparentFixedFontColor = val;
},
scrollTop(val) {
this.pageScroll({
scrollTop: val
});
}
},
//第一次加载
created() {
this.navTitle = this.title;
this.navFontColor = this.fontColor;
this.getNavBgColor(this.bgColor);
this.navTransparentFixedFontColor = this.transparentFixedFontColor;
//获取手机状态栏高度
this.statusBarHeight = uni.getSystemInfoSync()['statusBarHeight'];
const _this = this;
this.pageScroll({
scrollTop: this.scrollTop
});
//获取所有页面
let currentPages = getCurrentPages();
let pageLen = currentPages.length;
//判断是否是第一个页面,如果是有设置back为true的页面,将不显示返回箭头,而显示返回首页按钮
if (pageLen == 1 && !mainPagePath.includes(currentPages[0].route)) {
this.firstPage = true;
}
},
//方法
methods: {
//返回上一页面
onBackPage() {
if (this.backState == 3000) {
this.$emit('backClick');
} else {
uni.navigateBack();
}
},
//返回首页
onBackHome() {
if(this.homeState == 3000){
this.$emit('homeClick');
} else {
uni.reLaunch({
url: homePath
});
}
},
pageScroll(e) {
if (this.type == 'transparentFixed') {
if (e.scrollTop && e.scrollTop > 0) {
if (e.scrollTop > 180) {
this.transparentValue = 1;
} else {
this.transparentValue = e.scrollTop / 180;
}
} else {
this.transparentValue = 0;
}
this.settingColor();
}
},
// 获取导航背景颜色
getNavBgColor(val) {
if (typeof val == 'string') {
if (this.type == 'transparent') {
this.navBgColor = '';
} else if (/^#|rgb\(|rgba\(/.test(val)) {
this.navBgColor = 'linear-gradient(90deg,' + val + ',' + val + ')';
} else {
this.themeBgColorName = val;
this.navBgColor = '';
}
} else if (Array.isArray(val) && val.length >= 2) {
let navBgColor = 'linear-gradient(' + this.bgColorAngle + 'deg';
val.forEach(item => {
if (typeof item == 'string') {
navBgColor += ',' + item;
} else if (typeof item == 'object') {
navBgColor += ',' + item.color + ' ' + item.scale;
}
});
navBgColor += ')';
this.navBgColor = navBgColor;
}
},
//设置手机状态栏颜色
settingColor() {
let navColor = this.navFontColor;
if (this.type == 'transparentFixed' && this.transparentValue <= 0.5) {
navColor = this.navTransparentFixedFontColor;
}
let frontColor = '#000000';
if (whiteList.includes(navColor)) {
frontColor = '#ffffff';
}
if (this.lastFrontColor == frontColor) {
return;
}
setTimeout(() => {
this.lastFrontColor = frontColor;
// 改变手机状态栏颜色
uni.setNavigationBarColor({
frontColor: frontColor,
backgroundColor: '#FFFFFF'
});
}, 150);
}
}
};
</script>
<style lang="scss">
.header_content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
/* #ifdef MP */
padding-right: 190rpx;
box-sizing: border-box;
/* #endif */
width: 750rpx;
align-items: flex-end;
justify-content: space-between;
flex-direction: row;
height: 88rpx;
position: relative;
}
.header_station {
height: 88rpx;
}
.header_shadow {
// border-style: solid;
// border-width: 2rpx;
// border-color: #f5f5f5;
box-shadow: 0 0 6rpx 0 #ddd;
}
.header_fixed {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
z-index: 99;
}
.header_absolute {
position: absolute;
top: 0;
left: 0;
z-index: 99;
width: 750rpx;
background-color: transparent !important;
}
.header_left_box {
/* #ifndef APP-PLUS-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
height: 88rpx;
flex: 1;
}
.header_left_line {
height: 30rpx;
width: 2rpx;
background-color: rgba(255, 255, 255, 0.4);
}
.header_left_back {
width: 56rpx;
height: 100%;
/* #ifndef APP-PLUS-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.header_icon {
width: 30rpx;
height: 30rpx;
}
.header_left_home {
width: 56rpx;
height: 100%;
/* #ifndef APP-PLUS-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.header_left_info {
/* #ifndef APP-PLUS-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
height: 56rpx;
margin-left: 16rpx;
}
.header_title {
height: 88rpx;
font-size: 32rpx;
padding-left: 30rpx;
padding-right: 30rpx;
font-weight: 700;
text-overflow: ellipsis;
/* #ifndef APP-PLUS-NVUE */
white-space: nowrap;
display: flex;
overflow: hidden;
/* #endif */
/* #ifdef APP-PLUS-NVUE */
lines: 1;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
/* #ifdef MP */
max-width: calc(100vw - 160rpx);
/* #endif */
}
.header_title_center {
position: absolute;
bottom: 0rpx;
left: 375rpx;
transform: translateX(-50%);
}
.header_right_info {
height: 88rpx;
/* #ifndef APP-PLUS-NVUE */
display: flex;
flex-shrink: 0;
/* #endif */
flex-direction: row;
align-items: center;
}
.header_btnMongol {
border-radius: 33rpx;
border-style: solid;
border-width: 2rpx;
border-color: rgba(0, 0, 0, 0.1);
background-color: rgba(255, 255, 255, 0.7);
/* #ifndef APP-PLUS-NVUE */
box-sizing: border-box;
/* #endif */
}
.header_btnMongol_left_back,
.header_btnMongol_left_home {
width: 70rpx;
}
.header_transparentFixed {
border-bottom-width: 0;
background-color: transparent;
background-image: transparent;
}
.header_transparentFixed_left_info {
border-style: solid;
border-width: 2rpx;
border-color: rgba(0, 0, 0, 0.1);
background-color: rgba(255, 255, 255, 0.7);
border-radius: 33rpx;
/* #ifndef APP-PLUS-NVUE */
box-sizing: border-box;
/* #endif */
}
.header_transparentFixed_colorWhite_left_info {
border-style: solid;
border-width: 2rpx;
border-color: rgba(255, 255, 255, 0.3);
background-color: rgba(0, 0, 0, 0.2);
}
//颜色白色
.header_colorWhite_btnMongol {
border-style: solid;
border-width: 2rpx;
border-color: rgba(255, 255, 255, 0.3);
background-color: rgba(0, 0, 0, 0.2);
}
.header_colorWhite_left_line {
background-color: rgba(255, 255, 255, 0.3);
}
</style>
# zhouWei-navBar 适用于 uni-app 项目的头部导航组件,支持V3编译、nvue编译
导航栏组件,主要用于头部导航,组件名:zhouWei-navBar
本组件目前兼容微信小程序、H5、5+APP。其他平台没试过
本组件支持模式:
1. 普通固定顶部导航
2. 透明导航
3. 透明固定顶部导航
4. 不固定普通导航
5. 颜色渐变导航
本组件内置特殊功能:
1. fontColor字体颜色为白色的时候手机状态栏会自动显示白色,否则显示灰色
2. 页面为第一个页面时左上角自动显示返回主页的图标(具体看组件:zhouWei-navBar/index.vue =>页面script)
3. nvue页面必须在当前页面引入本组件才可以使用
### QQ交流群(学习干货多多) 607391225
![QQ交流群](http://qn.kemean.cn//upload/202004/14/15868301778472k7oubi6.png)
# [点击跳转-5年的web前端开源的uni-app快速开发模板-下载看文档](https://ext.dcloud.net.cn/plugin?id=2009)
### 本组件全局配置(位置:zhouWei-navBar/index.vue =>页面script)
1. 主页页面的页面路径
2. 首页页面路径
```
// 主页页面的页面路径
// 关联功能:打开的页面只有一个的时候右上角自动显示返回首页按钮,下面这个数组是排除显示返回首页的页面。
// 主页使用场景:小程序分享出去的页面,用户点击开是分享页面,很多情况下是没有返回首页按钮的
const mainPagePath = ["pages/navList"];
//返回首页的地址
const homePath = "/pages/navList";
```
### 在main.js引入组件,并注册全局组件
```
import zhouWeiNavBar from "@/components/zhouWei-navBar";
Vue.component("nav-bar", zhouWeiNavBar);
```
### 或者在页面script中引入组件,并注册组件(nvue页面必须是这样引入)
```
import navBar from "@/components/zhouWei-navBar";
export default {
components: {navBar}
}
```
### 案例一
默认特性:左上角有返回箭头,nav-bar导航固定在顶部、标题居中
```
<nav-bar>我的</nav-bar>
```
### 案例二
特性:无返回箭头、字体色为白色、标题左对齐、nav-bar导航透明并不固定在顶部、右边插槽有按钮
```
<nav-bar backState="2000" fontColor="#FFF" :titleCenter="false" type="transparent" title="我的">
<view class="icon_setUp" slot="right">设置</view>
</nav-bar>
```
### 案例三:颜色渐变导航
特性:颜色渐变导航
```
<nav-bar home :bgColor="['#f37402','#0f0']" bgColorAngle="90" fontColor="#FFF" title="颜色渐变导航"></nav-bar>
```
### 案例四:颜色渐变导航
特性:颜色渐变导航
```
<nav-bar :bgColor="bgColorList" bgColorAngle="45" fontColor="#FFF" title="颜色渐变导航"></nav-bar>
<!-- bgColor值 -->
bgColorList:[
{color:"#f37402",scale:"0%"},
{color:"#0f0",scale:"20%"},
{color:"#f00",scale:"80%"},
{color:"#00f",scale:"100%"}
]
```
### 案例五:滑动透明导航
特性:有返回箭头、nav-bar导航透明并固定在顶部、透明状态字体为白色、页面想下滑动nav-bar导航条逐渐变白色、右边插槽有按钮
```
<nav-bar ref="navBar" :scrollTop="scrollTop" transparentFixedFontColor="#FFF" type="transparentFixed" title="我的简历">
<view class="transparent_fixed_preview" slot="transparentFixedRight" @click="onPreview">预览</view> //透明状态下的按钮
<view class="preview" slot="right" @click="onPreview">预览</view> //不状态下的按钮
</nav-bar>
```
```
//设置滚动值方法一:
data() {
return {
scrollTop:0
}
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
}
//设置滚动值方法二:
onPageScroll (e) {
this.$refs.navBar.pageScroll(e);
}
```
### 案例六:搜索框|地区选择
特性:无返回箭头、nav-bar导航固定在顶部、地区选择、搜索框
```
<nav-bar backState="2000">
<view slot="left" class="address_select">深圳市</view>
<view slot="right" class="search_box" @click="onPageJump('/pages/home/search')">
<text class="icon_search"></text>
<text class="tips">搜索目的地/职位等</text>
</view>
</nav-bar>
```
### 属性
| 名称 | 类型 | 默认值 | 描述 |
| ----------------------------|--------------- | ------------- | ---------------------------------------------------|
| backState | String | 1000 | 返回上一页面按钮,`1000` 显示返回按钮,`2000` 不显示返回按钮,`3000`自定义返回按钮方法,点击返回箭头后会发送一个`backClick`事件|
| home | Boolean | true | 返回首页按钮(首页地址在源文件里配置) |
| bgColor | String,Array | #FFF | 导航背景颜色,值为数组的时候显示渐变颜色,`bgColor="themeBgColor"`的时候会调用全局`class="themeBgColor"`的样式|
| bgColorAngle | String,Number | 90 | 导航背景颜色渐变角度(`bgColor`为数组生效) |
| fontColor | String | #000 | 导航字体颜色,(当颜色为白色的时候导航状态栏和图片为白色的)|
| titleCenter | Boolean | true | 标题`title`居中 |
| title | String | -- | 标题`title`值 |
| transparentFixedFontColor | String | #000 | 导航`type`类型为`transparentFixed`时透明状态下的字体颜色 |
| type | String | fixed | 导航类型,可选:1.`fixed`固定导航 2.`ordinary`不固定导航 3.`transparent`透明不固定导航 4.`transparentFixed`透明固定导航|
| scrollTop | Number | 0 | 导航`type`类型为`transparentFixed`时页面滚动值(`具体看上面的案例五`)|
| shadow | Boolean | true | 是否显示底边阴影 |
### bgColor数组值为JSON的参数
| 名称 | 类型 | 默认值 | 描述 |
| ----------------------------|--------------- | ------------- | ---------------------------------------------------|
| color | String | -- | 渐变颜色值 |
| scale | String | -- | 渐变比例(百分比%) |
### 插槽
| 名称 | 描述 |
| ----------------------|-------------------------------------------------------------------|
| left | 左插槽 |
| default | 中间标题插槽(`type`类型为`transparentFixed`时插槽只会穿透到实色背景下) |
| right | 右插槽 |
| transparentFixed | 导航`type`类型为`transparentFixed`时透明状态下中间插槽 |
| transparentFixedRight | 导航`type`类型为`transparentFixed`时透明状态下右插槽 |
| transparentFixedRight | 导航`type`类型为`transparentFixed`时透明状态下右插槽 |
### 事件(type类型为transparentFixed时可用)
| 名称 | 参数 | 描述 |
| -----------------|------------------ | --------------------------|
| backClick | 返回上一页按钮方法 | `backState=3000`时生效 |
<template>
<view style="display: flex;align-items: center;justify-content: center;">
<img src="static/icon/nodata.png" alt="" style="width:300rpx;height:230rpx;" />
</view>
</template>
<script>
export default {
name:"notData",
data() {
return {
};
}
}
</script>
<style>
</style>
<template>
<view class="_tab-box" :style="{fontSize: defaultConfig.fontSize + 'rpx', color: defaultConfig.color}">
<scroll-view id="_scroll" :scroll-x="true" class="scroll-view-h" scroll-with-animation :scroll-left="slider.scrollLeft">
<view class="_scroll-content">
<view class="_tab-item-box" :class="[defaultConfig.itemWidth ? '_clamp' : '_flex']">
<block v-for="(item, index) in tabList" :key="index" >
<view
class="_item"
:id="'_tab_'+index"
:class="{ '_active': tagIndex === index }"
:style="{color: tagIndex == index ? defaultConfig.activeColor : defaultConfig.color, 'width': defaultConfig.itemWidth ? defaultConfig.itemWidth + 'rpx' : ''}"
@click="tabClick(index,item)">{{ item[defaultConfig.key] || item }}</view>
</block>
</view>
<!-- <view class="_underline" :style="{
transform: 'translateX(' + slider.left + 'px)',
width: slider.width + 'px',
height: defaultConfig.underLineHeight + 'rpx',
backgroundColor: defaultConfig.underLineColor,
}" /> -->
</view>
</scroll-view>
</view>
</template>
<script>
export default {
name: 'liuyuno-tabs',
props: {
tabData: {
type: Array,
default: () => []
},
activeIndex: {
type: Number,
default: 0
},
config: {
type: Object,
default:() => {
return {}
}
},
},
data() {
return {
tabList: [],
tagIndex: 0,
slider: {
left: 0,
width: 0,
scrollLeft: 0
},
scorll: {},
defaultConfig: {
// 要显示的key
key: 'title',
// 字体大小 rpx
fontSize: 26,
// 字体颜色
color: '#313131',
// 激活字体颜色
activeColor: '#1881e1',
// item宽度 0为自动
itemWidth: 0,
// 下划线左右边距,文字宽度加边距 rpx
underLinePadding: 10,
// 下划线宽度 rpx 注意:设置了此值 underLinePadding 失效
underLineWidth: 0,
// 下划线高度 rpx
underLineHeight: 4,
// 下划线颜色
underLineColor: '#1881e1',
},
};
},
watch: {
tabData(value) {
this.updateData();
setTimeout(() => {
this.updateTabWidth();
}, 0);
},
config(value) {
this.updateConfig();
},
},
mounted() {
this.updateConfig();
this.updateData();
this.tagIndex = this.activeIndex;
this.$nextTick(() => {
this.calcScrollPosition();
})
},
methods: {
updateData() {
let data = [];
if (typeof(this.tabData[0])=='string') {
this.tabData.forEach((item, index) => {
data.push({
name: item,
})
});
this.defaultConfig.key = 'name';
} else {
data = JSON.parse(JSON.stringify(this.tabData));
}
this.tabList = data;
},
updateConfig() {
this.defaultConfig = Object.assign(this.defaultConfig, this.config);
},
calcScrollPosition() {
const query = uni.createSelectorQuery().in(this);
query.select('#_scroll').boundingClientRect((res) => {
this.scorll = res;
this.updateTabWidth();
}).exec();
},
updateTabWidth(index = 0) {
let data = this.tabList;
if (data.length == 0) return false;
const query = uni.createSelectorQuery().in(this);
query.select('#_tab_' + index).boundingClientRect((res) => {
data[index]._slider = {
width: res.width,
left: res.left,
scrollLeft: res.left - (data[index - 1] ? data[index - 1]._slider.width : 0),
};
if (this.tagIndex == index) {
this.tabToIndex(this.tagIndex);
}
index++;
if (data.length > index) {
this.updateTabWidth(index);
}
}).exec();
},
tabToIndex(index) {
let _slider = this.tabList[index]._slider;
let width = uni.upx2px(this.defaultConfig.underLineWidth);
if (!width) {
if (this.defaultConfig.itemWidth) {
width = uni.upx2px(this.defaultConfig.itemWidth);
} else {
width = this.tabList[index][this.defaultConfig.key].length * uni.upx2px(this.defaultConfig.fontSize);
}
width += uni.upx2px(this.defaultConfig.underLinePadding) * 2;
}
let scorll_left = this.scorll.left || 0;
this.slider = {
left: _slider.left - scorll_left + (_slider.width - width) / 2,
width: width,
scrollLeft: _slider.scrollLeft - scorll_left,
}
},
tabClick(index,item) {
this.tagIndex = index;
this.tabToIndex(index);
this.$emit('tabClick', item);
}
}
}
</script>
<style lang="scss" scoped>
._tab-box {
width: 100%;
display: flex;
font-size: 26rpx;
position: relative;
height: 90rpx;
line-height: 90rpx;
z-index: 10;
.scroll-view-h{
white-space:nowrap;
width: 100%;
height: 100%;
box-sizing: border-box;
._scroll-content {
width: 100%;
height: 100%;
position:relative;
._tab-item-box {
height: 100%;
&._flex {
display: flex;
._item {
flex: 1;
}
}
&._clamp {
._item {
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
}
._item {
height: 100%;
display: inline-block;
text-align: center;
padding: 0 30rpx;
position: relative;
text-align: center;
color: #333;
&._active {
color: #1881e1;
border-bottom: 2px solid #1881e1;
}
}
}
._underline {
height: 4rpx;
background-color: #1881e1;
border-radius: 6rpx;
transition: .5s;
position: absolute;
bottom: 0;
}
}
}
}
</style>
@font-face {font-family: "iconfont";
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAMoAAsAAAAABzAAAALaAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDHAqBbIFSATYCJAMQCwoABCAFhG0HRhtCBsgOJUGS2IBhSABBPHyN/f7c3UVEk2hkSKLNE9NNSrBEgkhrJIYS7JuG/v7Pv7cbyBNUBB7u+/D8Cczd0qrAKui2UweV2+2Jz3M5bQIFMr+d5TbWojXpyo96AcZbAxpr0xYuoAS5pXYRBwav/GubCdQbFslkvbiyEawVpFcgLhUyDqx7fkWJUrVC9czEIh5YqE136RTAvf5+/IP4qCWpysjA3eMiEXJ/OnyLyVeVK52SRnqdC9E0MmaAQpzOxveZiYhjpr65uTmgVq3EVTXhP2MqFWvjw2L6l0dIMlGNkBvBrJfI/HTgBMHPcILEzxiSDAjR1faT88CGzx5UBz2+19V1ZP+Ku9i8v3xj0oPb8/DYW/HyOvCSHUz14N7Vleu2waXja7bOy1cOj1ridz1feeqBe5fDdm2dr93ynUqZbj7cXLr14cePR99QnyP4HxIbUmdfHxcc//rLnQ8Udwy/eHERdvFCLbSSeTLhHOI8cZIp+nn9/Hx4LPDc6fx1uum/+27vJ0s30CNb5dvUA0B+MVUAkD+bPkH2b3y7s5Xb45T5z7qhgFfGPmpTAdMCxqRgKWqlwJxSyom1lFzBJZWv6AX1SKhXD4SmafflCl0XOJtQq+uAr8ZQi6zWCL2wZ1ClwSyq1VpCvWkl0xt0oMxEqcOUOQeh1X0kzZ6QtTqjF/ZnVOn2F9VaI6PeXrgt2GAszmwLKHIMRcIPEYWg00rMzmxNZj3K+tQc7co1txmpUREicdGxw1QJapFeMcTYL4tnTCIS1WlIsbMdqtU6oqc6JQosWs6YPj0mRpq7UbSg04CtNRTiMEhE8IYQCgIdLUkWZmxHn6+HZPqocegKZSHaDFFGivqJONFiAfQSjRZE+ZVrjPrJxGMYCSGhdDSIYseF1DSuQ+jn2ykhARNNfiCqly7GyydBNdHP6zR/uAjqkcVLpMhRokrVgRwFla1SwWmZrm9Q6awaMwAAAA==') format('woff2')
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-check:before {
content: "\e60d";
}
.icon-arrow-down:before {
content: "\e62a";
}
.icon-arrow-up:before {
content: "\e62f";
}
\ No newline at end of file
.loader-one {
width: 50rpx;
height: 50rpx;
position: relative;
-webkit-animation: loading-one 1s infinite linear;
animation: loading-one 1s infinite linear;
}
.loader-one,
.loader-one:after {
border-radius: 50%;
}
@-webkit-keyframes loading-one {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes loading-one {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
\ No newline at end of file
/*!
author:kooboy_li@163.com
MIT licensed
*/
let base = 19968,
middle = (40896 - base) / 2;
let EMPTY = '';
let COMA = ',';
let chars = (() => {
let a = [];
for (let i = 33; i < 127; i++) {
if (i != 34 && i != 92 && i != 45) {
a.push(String.fromCharCode(i));
}
}
return a.join(EMPTY);
})();
let SDB = {
"a": {
"yi": "!]#R$!$q(3(p)[*2*g+6+d.C.q0[0w1L2<717l8B8E9?:8;V;[;e;{<)<+<S<]=9>.>4??@~A`BbC:CGC^CiDMDjDkF!H/H;JaL?M.M2MoNCN|OgO|P$P)PBPyQ~R%R.S.T;T<TBTqT|UQUXU}V[WCXgYCYDY_YdYuZ9Zs];]j]p]q^.^@^S^w^x_,_T`H`J`ga)a8aQb9budJddgoh9hqi2itj&jEjRj]jzk>k^l$l<mLmdnDoEoMoQoop3p5pWp`qSr.u'uLv]wIxXy_y~{z}`~r-$=-$X-$Y-%!-%0-%j-&^-&s-'t-(<-)2-*n-+6-+f-/M-/N-0.-2|-3u-4b-4c-4m-5E-5N-5Z-5l-6&-6+-7*-70-73-8F-8R-8g-:*-:5",
"ding": "!2%%&_&x'u=:=h@NC`H?LQNkQ3Xo^Gn?osrUsNvAwKxKy9-!T-$6-$v-%O-&b-(+-9%-9(",
"zheng": "!S#(#D/]031$456+=L?OIzYM[']I^g_.eUl}m~qJsHulwuxU-!?-,d-3D",
"kao": "<dLWr5x7-!J-,7-/Y-/s-2'",
"qiao": "#+$4&.&1'7'Y'z($(),B,{0c7y8<:H<8<YE{F0GdKYMCZP]Y_8_zd.d/d{e5fGfHfUmKmrmvp#t>t?uJv$vMyE|R}a-!}-#&-#8-#L-#b-$Q-%?-+q-,6-,8",
"yu": "#V$l%S&9&I('(7(=)))m*#*$*B+2+F+v,0,b,i.W0.1F232L2a3(384>6P8n;';i;y<1>(>)>]@iB<B?BDBEC'C*CoCpELE^HIHJHTIpJIJ`KXL&L1LxMbMqNXNqPdPsQ<RFT?U(URV7WnX:Z?ZT[6[H]!]~_7_J_``Za#eXg;h#hVhuiyj!j#k9kDkMl#lClUlmmUnFoAp(pzqnrSsSt0vJwszp{_|N}!}$}I}t~(~,~.~w-$D-$]-%^-&j-';-'k-(3-(H-(v-*1-*Z-+#-+d-+{-.1-.2-.<-.K-.[-.e-/d-0=-0P-1:-1m-39-3`-3b-3e-41-5e-5}-6/-6;-6p-7:-7Z-:(-:2-:F",
"qi": "!8%&%>&X&m&s'2'X'd'f(9(c(i(j)@)l+'+M.).+1y1{2=3K4c6&6'6)606<6B6`9`9{:a<g>`?`AgCLCuD%D2F2GyH&H1I;K~LkLuM&MYO0O3O9P8PbPcQqR5S2SCU0U~V%XYY&Z}[G^P`7cUc}dEeNgOj$j)l?m:n4p,sOuRv.y'{/|i}1~P-$B-%Y-)|-)}-*K-+G-+H-,m-.@-.M-/|-0y-2D-2c-4W-4`-4h-7a-7p-9c-9i",
"shang": ")Y6V9cJvR8UqXJXa])asbQc,s,uSvz-#+-.;",
"xia": "#Y#w&,&;'''I)1.u/j7=:[<'B[ByCtL'NmNyQOR([0`(cLh[iRkVt/t_u4uezFzM|W|{~d-&)-*4-.}-0a-5;-8S",
"han": "#,.m/h:l<P>MFrGXJqNrOUPCPqPrQ|]@`+`2h1lBlZnXp*r;rWrkz9{4{B}x-#c-#y-$;-$l-$y-%Q-%n-(i-(x-)i-/!-3*-5B-9V",
"wan": "#=$0&o.]0F4@5X5b6*628u9p<K?e?h@IChFqG!G7H2HHJzL=O5Q'RQ`;a:b<bGeHh&h)rMr^s'slu!-$E-%V",
"mo": "!`#$#&#y$%%P'e(T*N3v5$517`8R=6?XA5E6FZF~JLM;MgP+RTRcU6]'](_j`s`x`y`za+qkuDyR|G-!e-'g-($-(U-*R-+k-,(-.U-.k-.{-8/-80-8K-8L",
"zhang": "#~(#.:2o3N>k@,JhR`b$b`knmtujz'z0}<-#+-'I-*Q-16-7m",
"san": "3T3q3w3x7~uJuwzA-'n-([-,s",
"ji": "#r%''l'y)3)d)o*Z+'+9+G+M+T+Z+^+g+x._.c/R090d1S1W2;43484J4R5C5w6)6C6`7f7s878H8t8w9J9X9Z9{;8;<;B;C=(=2>6?YA$B+CHD0D8DbE:EQF2I*I|JEJnKKL)L:LkLzMdN'N5N:NiQ6QyRrUWVcVnWPWQWtX6XEXYXuY(ZAZ|[/]O]e^F^J^U^~`)b#b0c*ckc}dee!e$e9e>eyf+fXfrg)hFhriMjZlrqmr)sRt%uov3vevw|@};}N}g~!~+~F~{-!&-!u-#N-$%-&a-'u-(,-*x-+]-,W-.?-.V-._-.d-.g-/+-0$-0H-1%-1/-10-1^-1o-2/-2@-3'-4)-4o-5>-5H-5U-6,-6J-7/-7P-9e-9g-9h-9i-9j-:l",
"bu": "0$192,FKJgT=UYZ^e+hhjmm8mFoGpGp}sjw]w{-'7-'E-/m-3#-4.-6=",
"fou": "4I:L:O:Q~1-3:",
"mian": "!G!d#4$U$W$]3Y5X6A6_6o9g9w@qB/CkG!H_Q;-!L-!M-!P-/_-7y-7z-8'-8,-8q-8r",
"gai": "):5=5LD,ErI!J1Z'_/`TaYaac!lnpcw[|O}1",
"chou": "!+#n$N+0/y0}2:4e5/6#9jB*B.GNLfUmZ+^3^5_4e%e4fWkan]nbo.o6oU}u~$~*-.X-/>",
"zhuan": "%H'S'V.K0k1B1H1r2?7Z<r@RA7IDRsVk[J]Tb3b<c8gThai'mp-%+-%u-'p-(]-14",
"qie": "%>+7+f,8.#.|0K0p2O>#DNE1P.ccd]eMlpt8y>-0&",
"ju": "!Z$L$w%R*W,c,l/e1~3&3J8#:t=#=`=k@FBGC0DlD}FeGAIaIkJbMrN[OVP`RDTlU|W>Y`[$^Z`Ua*ccc{dWd]dae#e@eFeff8fSg*g<guh~l'lXmIoOq(rps%vXw_x|y;zb|m}o-#/-#:-&4-&Q-)<-)?-)d-*z-+0-/.-/:-3[-48-4S-4k-5.-9H-9K-9x-:@",
"pi": "#M%D'C(5(6)L*F*K+;.n1C4M8}:y;/;2;A<,<{>a@'@2@KA%C|DQO+O]O^PvR!REScU'UfZw]m`l`na'i[l_m;p<pYpyqCqyr*s1s;trx4{8|*|=|p}F-!!-#,-)@-,H-.p-/#-/3-2#-3>-4F-6'-63-91",
"shi": "!E!Q!e#?$p%$&+'$([(](q*^.&/5/n0[1w204z<gBNBQG)I:ISIUJ3NlN{Q>QQR9VYW2W@W^X2XNYxY{ZI[:[<[v]X^l^{^}_p`DaDbmgqi8ixjdk!kNkpl(lkntoMo^ocoeofp5ppq%q&q*q4qbr=t9x/-&^-&_-&}-'<-'@-(*-(8-)!-)H-+,-/<-0?-0d-0o-0p-2:-2O-3+-38-57-6M-9C-9E",
"qiu": "*6*7+a0r3k4D5]6j>7CaCeF`HEJXMhNgNjONP;QMQ_RfSWUUX?XUXqXrajc$d'jpjskXl]n@o.oup:r?-#5-#6-$8-/'-/k-0W-0X-1,-2Z-4v-7&-9U-:Y-:Z-:]",
"bing": "!n)F*4+/,>.75@DsOcZ7l`puqar||>-!:-!q-#,-#G-''-'C-(D-/O",
"ye": "$>$E(0,a6g=;@?HfSb[]_]lUlfn(oip=rmtDtTtevTx?-!O-!R-$5-%N-'F-'e-(T-*o-4Y-61",
"cong": "$'&Y1>8==g=l=p=vDIE=I2JUK0LsRZZk]$a}a~sKtBuKu_-*)-*V-+Y",
"dong": "&&.r0b5D?7?C@JD|G;I#KwQ([&jV~^-)T-/=-0)-4g-5/-6T-9,",
"si": "'?(b)^)g)p*+.</#40415O6i8l9~;.<|<}>+>0KxL+NLP7PiQnReS&W_`tp1pvp{qTqnr8r`tIuzyB-&6-&R-&^-&c-&s-&{-(:-)L-)q-*8-+.-0.-5j-6`-9N-:o",
"cheng": "#0$,$P&W*O*[*w+A+{,O,v/l5[7#:`?}FQOoS(UKZV_#cHcJk#m$nhrxtkuxv@vWx=xB|2-!A-$h-'w-)o-*>-+B-/u",
"diu": "r2xL-&&",
"liang": "3A3D3{6K@0CRF{Q%Up[,_Oe1h!h2hCiBiHojss-!=-)h-.J-.O",
"you": "(r)O*I7o8W;L;f=5=M>VDKFoFsFwG/KaOOOSPSQLY8ZN_;`qh%hMjWjnk6kPlYmEn3n>ncodp~r3x&x<-),-.y-/1-1p-1z-7N-8P-9D",
"yan": "##%F%L&%&F&T&v(Z,j/u1?2$5t7V;!;h?<@@AsCVCYCZD3FmGpH.JlN_PVQAT$UxV9WUX/XkXmXnY?Z3[U^1^C^E_e_~`B`C`RbDbPc;g/g7kIm#mNmsn5nHnsnyoPoVo`x+z7zkzmzn{A{`{e|}}2}b-%'-%,-%B-%v-'0-(#-)~-*$-*F-*j-*s-+C-.4-.H-.Y-0V-3$-3*-3B-3n-5#-5G-5u-7K-7r-8T-8W-8_-8`-8a-8d-8j-9L-9Q-9w-:1-:N",
"sang": "'EVNts-%2-%{",
"gun": "#<&#'U6F6z9dJ>JpTFTwUu]4h<iF-/2-/g-2<",
"jiu": "+E,*42464]8mB:BCBHBMH7cQnGz){Z-#}-#~-,l-./-01-3!-5w-6I-79-7c-:$",
"ge": "&!/30*4?8r>:?B@}AbB3BwECHxJ1NwOrP'U9UPXM[X[hhLhmq`tetlu.xSyUzTzU{W}4-!S-!s-#F-#`-#j-%f-(A-*%-+t-.3-/K-/U-1u-3T-3z-6g",
"ya": "#B%C&{'I*{,a.g=UDEKqO;T1WEWGY.^[g=i!j4lUp=s=v7x;}f-3C-3c-4U-6O-6V-9o-:;",
"pan": "!&!>!?!H!o'L'x2A76=F>R?$AIH<IrRoT{WBY[d[e{f0rvtpw=zx-#E-$J-4D",
"zhong": "#%(n*8+>+m/V2T4{6b99>j@`BnEkK*O:OBP^R2RKSzTKTNTO[@e^f>ohparHtQv5wbyF-3_-9@",
"jie": "#S%@&{(.*d+=.G0e4J5,599D;k=(@/CfD,G#G`J[LzOFP&P:PTQ=SKSQSqT/TITPTlU4U7UPVQXOXSX}Z%ZWZh]/^K^~_5ckdve=j^qGtNtXz,|1}.-!m-!u-$U-%c-&v-+i-.l-/@-2&-4{-5$",
"feng": "!@%N'40m5v7R:3C$FdHnN.PFSaWI[R^c`?b.c5k'n+n;r[u5uXxs-!$-!4-&%-&J-&L-(w-3(-3,-3F-8)",
"guan": "!'$b$j$k(W)B,Y/f0E6:9&:]:gBVFqIEWSW{X+X.a?bifMh?kmsUu>w7zOzS{,{2}{-'K-(N-0q-1N-1j-2e-2z-6D-7A",
"kuang": "!Y!z$Y%1%r%w(G+}/O/z5'538V8vZ<ZG^y_=aNbpgHgRgXg`j+lHlhn/qUrevy-4>-8>",
"chuan": ",40jA7BYB`BhBxEvale[hIkJp%wQ-5+",
"chan": "&6'W)K)q1N6D7$8*8A8[8_:6;xCODJIHKQQ2RGR_R{S1UeW!W`X3ZMZy]B^+^7_N_bfbi|n2n6o@rTr]uWw3xYz%ze{7{g-#Q-%D-%~-(%-(S-+Z",
"lin": "$B&['t0:393O5{8!<WA?B%GsKEMOaWb{fEf]fgfxhlh}iVk{lgn$utzg|9}C~[-*a-1G-2t-7_-7n",
"zhuo": "#'&Q)a+l,%,V,]102E2`8?:J;&=NE.HtJ:L|SJSsZx[+]6_Fd!nArfvLvOy|-4J-5d-:x",
"zhu": "!a$6$h%^%v'f)!)/*h,@.4.S.T.[.w/P/o0]0n141=1a4n4q5.9+:s;W<EBrD/DVDpE_EmFYHtJQKZMMO`O{QTS>S]SrU;V<YLYoZ;[S_$_B`[aCbhdVdjfRggjMjrk1ljq6q{r}vbwExIx`|x-&r-(~-)=-)]-+2-/H-0E-11-3s-6(-7T-7V-8x",
"ba": "%#.a3#:y;2;N<z>sD5E4GTO$WNYk`LdDdNgjozp?wr~~-!a-&.-.D-.`-/&-/0-1t-1v-1}-9=",
"dan": "!K%$%5)r,S0N1h4V8A=A=B=H=~>q@9ATAVH*JDOkPUTLV?VoXGX~ZK_'a|bBc3f{mHn&nKn~~t-$I-'G-'s-)*-)a-,C-3Z-8H-8b-8i",
"wei": "#o$M%}&0'#'D'M6/6p6r7+8y9f;6>n@gC+D!DOE+FCGBH)I&I(I4INJ]K$KJL7LdMDN0PwQ$QDQHR?T3T6V`WkX$Z)[#[^^*^4_I_^e;fefig@hbj>k<k[m}nvs~t4uGzz{G}&}'}7}Y~n-!#-#Z-#a-#i-#q-#v-#z-$T-&7-'J-'X-'z-+a-+b-+c-.P-/,-/F-/P-0N-0O-2(-2W-2p",
"jing": "#C*?*u,2.8.9.A.E.P.R042v3F3Q5(5q6!9@=_>g?:?k@<E;EtExFiG8HlS/Z`]ge(jTjwrhuYyi|+-!=-!@-!C-!D-!F-$N-$m-%b-*m-03-2M-4:-4a-4d-7e-7o-9I",
"li": "!!!0#A#E%7%_%m%q'|(K(L(Q(^)u)y*%*H,&.$.J.{/c1.1:2Z3$303G3b4)5}7T8Q8g:7;4@*C%DPDbEEF%FDFWF[GUI[I`JFKIM1MKN4OWOnP#PNPlQaR[S*S:STSVS_ULU_VWXhYY]&^,`9`}cPdbf`hzh{i5jDk+l7l;m6n=oBoNogokqAqururzs3tludvuxjyU}V}W}X~&~8-!+-!5-*}-+A-,^-.3-/p-/v-07-1W-1b-1k-26-29-2x-2~-3Q-4X-5I-6F-6l-7f-7k-8A-8Z",
"pie": "$2DmW]u~",
"fu": "%8%[(u(v)U)j*k*o+:.'/$///_0$0=1j3C3d4a4j4u5B5k5p6q7B8L939<:0:o:}<&>N?#@!@D@E@nA3C!CWC}D*DFE'E,E]EpFFF|GKHKHjJXKsNSODOGOXOwPIPMQEQIQWTETsTvU.V(V6ViW+WKWMXpYS[C^H`Va4a{b4bXc(c7cRd=dZegh*hPhRiAiLlIm(m*mmnQowo|pFq<q@t#t5{s{t|?-#]-#x-$`-&(-&.-(n-)F-+/-,P-.5-/)-/8-/X-0^-1|-2[-2}-3%-34-3N-4H-4}-7x-7{-8#-8*-8o-8p",
"nai": "<p<q?L@=CcH4R'VHj[o}sk-9'",
"wu": "$A%*&l)+,D,o0a2tAMB]D#D<EPFSKvMVPLQzS#Z>ZYZZ]U_6_9d9fYj6j~lWm)mep)rQrbrctvwkxc{y|U}6~?~C~`~m-!Z-*'-+R-/j-0j-3i-4/-4@-5,-5f-6j-6s-7)-9G-9W-9X",
"tuo": "%U%V&z0L2J4v?{@$F_H6MUTbT~Y'Yc^QdHdQnVq+r`x1{{|;|<-&d-(.-(z-({-)1-)J-)K-*:-*e-*p-+$-+3-.b-/%-/[-0b-3O-4,-6_-8}-9$-9?",
"zhe": "#'%+%E'P2f2|<f=VHtJ~NoP4PKR9RRRSU%VXW<Yq]*]:^%^0_ucKe`h(h0hei@iUj:j{kurAtMy!-({-/f-5W-75",
"ma": "#X%3'8(e)h;0GsK?N}R+RTRUkku/z2-(u-)N-+!-+9-,r-0n-5P-8.-80",
"me": "-8/-80",
"yao": "!T$R'T(g,3,:,=,F,I,J,e,f/C0^4<7o8Q8s<a>_@eB>CADvFAI0I>J:L]M:M~TgWHWfY/Ya[|[}^6_ngmi6k`kll*l9r!tdwhxRzv}!-!j-%=-&9-&T-'(-'=-*&-0u-1I-2f-3;-3]-5F-5Y-7+-9T-:%",
"zhi": "!7!t$s%=(J(i(k(s(y)2)I)Z*2*>*A*T*^*c+(+)+J+Y,G/k4Q4b5T5W5s6~7^7|9(98;(<0=E=Q=b=}>L>|?+?QA<AJB1B2B5B6CzD$D?E8GeM7N/O3P1P]R@RhTQTTTxTyU{W.WgXCX[XcY9ZB^l`@`A`haAb!b=bbbwdAdYdueTeWf,f_fag6glg}i1i:jDlqm6neoyqrr=r_vsxAy3|)|Z}R}[}j-)!-))-)Q-*?-*L-*Y-+O-0:-31-3S-3m-5+-5^-6a-8m-8y",
"zha": "!l%Q0>4^4g=0D{OPOZX]Yb[(]G]W^ng=o;t*xHzI{N~J-&t-/9-/a-1{-22-9]-9`",
"hu": "(1(~.j0Z1M3!3^545r757G?0AMCtCxD<E$GxI+K%K;NGNHNPNWQ^R)T2X`Xd]<]x^^``gVi3mqo)snt+tK}Z}q~B-$4-$k-'O-,j-.s-0<-0c-1`-2v-32-4?-4x-5)-52-5?-65-6n-7!-7?",
"fa": "#k%O/'/N:q;*;3EeKkLvo1oKstzV{V-,F-,J",
"le": "%f.U1_>5C_{u-$*-'1-(A-1!-1d-2i",
"yue": "$S%!(a){0^0|242S2_373H4<8sAlM{O,O.ZaZc_>cid2dCdFfZgApDqBw2whw}zczd{[-,V-6:-6B-8Y-:^-:m",
"lao": "&)'n,71s3<5>9M<b<c=&=3F'HYP3Rvg.g4hin`oDr(v/x8xa-%8-,9-/W",
"yin": "&#&j'a)Q*a,^/B2{5G6{7V?3DJEGEcF=FHIRK4K8MuO2RLRzU=Y$Y*Y2Zu[M^9cXczh'monipNp]qer/xFx^z{{||/|l|w|~}0}@}Q~W~f~p-!b-!r-$&-$2-&m-&q-(6-)^-+:-/I-5h-9p-:!-:?-:E",
"ping": "%b&'.H0W1Q:T=f>~CXE%F$H(JWMaOQP%Yg^jgrh>mAqa-$^-(w-/(-1w",
"pang": "!o'A1+=/>R?$?=A/B|QmWsd@jf~6~|-0k-2g-:K-:M",
"guai": "0,;%",
"sheng": "!D!^...t7*7q859e=[=x?*E(KM]^aMb1q2t2|#|Y|u-4_-9B",
"hao": "*:.,25<x=ZEMJ$L3L5LWLtNYO<SG[0]z`Y`ym,mhu#y]-%>-%|-0i",
"mie": "!`(D1G1dJxL>SNS~W]vt-1e-3M",
"nie": "1&294(4,=G=|B)B0E!GDMlSX^=e)e?eAezforAs$sJu*vfw9wByVyY{&|c}(-%L-%x-:#",
"xi": "!>#6$3$d%/&(&g'J's(!)P)n*l+7,,,n313z434i5j6H7?7W81878g979U;V;n<2<5<6>c>d@>A6BABBB}FUG]HeI9IbIwJ+JVKzL2NdPjQoQqRYRqSiT!U)UzW9WFWiWlX7XfXjXlZH[K[m]5]F_@`.`/`W`_a(cCcGcfcwesf)fulGlplwm&m4m_n:oIokp2p7pbqLqMqvsYu+ufv&w6wSxJy,z[{5{b}9}?}P}U~#~2~q-!%-&?-'2-'`-'r-(1-(C-*C-*O-*{-.)-/x-0_-1+-1J-2X-2q-46-6*-8I-9O",
"xiang": "!;)*+50U5Q6Y8b9u:U;E;J<4APC{HGHvL<N~RbS4T.VgVsZ(_0`PdqmGmYmZmfqiq|v(w4z&zXzt|H-$3-$9-%R-&g-'+-'{-(&-(?-(b-*w-+_-/C-/~-1<-1L-1g-23-7g",
"shu": "*V*x.0.D2B4#4K5%5^6s9/;,@[BPF(GuIBIeK7LUL`MLNePDShT*UHW'W0`=bOc+e%e0gIhOiOjQmSqIs_u[|I}!~Y-/A-1Z-1a-4G-4p-8@-98-99-:e",
"dou": "$#,[,}1E@#FEKCOI_E`5jym%mMnMpCrIwpzH{.{~|]-'9-(F-.%-.&-.*-.,-..",
"nang": "Sd-(&-()-(^-9b",
"jia": "+L/23l=!?)?u@jF+FuI.P5P>TaU4UI`]a$a]bxdRjGl{m/q#qOrXu,x$x>y`-$a-$e-%c-%d-)B-+5-3J-3q-4(-7i",
"mao": "!M#i$i*:/66e:u<eDDE/E2E3HVJOQ9QNRXT}WY`|a&aSbrgPmkn!nJq>qcsVx,y%-,B-,O-4|",
"mai": "?W?XF>K^LgS{aKaxj(l+~g~h-!'-5{-7t-7u",
"luan": ";D?dAzA{L=NDW~o{r7w@-4'-6G-6h-:y",
"ru": "/M7F8G:1>AEgIYJ6KlLhQJSHU:VGW,inlEm`oSr+x_-%E-&!-1]-3)-3K-3x",
"xue": "$?,(A=C@E@IGLKStTnXd[p_[coe,hdibig~/-!_-#M-18-2k-6%-6^",
"sha": "%4&G052u4O8F8~<<<CFaG`H+K<U]t}xPzazi~S-,[-.h-/q-/r-2=",
"na": "*0.u.x4E9#>WIYIuTJU!Zt`m`pgNlNlypHu7wcyZ~0-!d-.x",
"qian": "'K.(/~0A0t1'2*2D2R2p6+7[8J8q:G;h>b@vA~CnD(EIElF:I%IjK>KLNNO&O8P}VR[*[u]u_q`!`&gSh;i~kjk~p9pEpOq;q?r6sPtYukvqwPwgwtwvx+{x-#U-$z-*+-*/-*=-+U-,y-,z-0x-37-4M-6z-8G-8M",
"suo": "#*1Z1^4Z797U:?;cFaFbJ7P{VJcuk)tatju3u9xi-/b",
"gan": "!3%)*1*t.Y/x1*1}3%4s91>GCmE#T>Y^bJbTcAcTcti}nE-+e-.Q-1T-2w-3*-:i",
"gui": "!q#o$.$C%x%})0)s,E/?1K1T?NERJ;N%P/R*RpU<V{WVX0XPZ!_*aHbod<dng>gEi#lilxuyvlzY{P|M~#-#K-*;-.7-.:-.=-/S-1F-1U-2%-2r-34-:Y-:]",
"jue": "$Z$l$o%6,%525S8#9NA^D=KiKtNnO6RwRxU!WWWbX%X5X>XBXZXiY4Zj]N^f_}a0c[chd<fCfDfwpKv)v:wCyo-)0-,$-2r-3<-3=-5g",
"liao": "$:,A,m,x1g7n8%:@:C=OA#ADJcM(RnRv`1b8f$fJizl&mnopv,wNypz.-&@-&G-,)-5t-77",
"er": "3><m<p=8=T?HEyLoS|U8Z6aBaGbjd3gshtjJl2q_x9|L}M-'/-(=-)Y-,Q-,R-/D-2)-3j-6b",
"chu": "$e%s(/)M.%.)114y9=<~=%A_DCG=IdIoMMNOPQS'XRXe[/`E`Oa,cmf=fTfcmaq3rnxlzW|`}p}|-59-8O-9|-:)-:9",
"kui": "#f#o$C'D,Z,p0v1m22=m=o=s={?NAFI6IJKnLyN1N;NbQY[edpf*fvk;mXt;tJ{0}7-$A-$d-%6-'a-'o-(P-(`-*6-+P-.B",
"yun": "!F'N*;/`/|0y4T6z7!7<7C8z9|<y=?@_D@FLG7IAIVIyK_K|L#L0MIM`QcV@a3b)b@bYc=j1kQm!m7mVmgnRo0o8pVrO|'|d}5~7~i-#?-#g-#n-#{-$(-$/-'N-(p-)'-:'-:0",
"sui": "!q#G#J%G&f)$)t+R+h+p5m7>7h7x8D9V:4AQCyFOFPNxV}Zm]c_QazkFkHl.uqv!vF}*}/}G}H}w-#$-#r-+|-,/",
"gen": "CQEHdc",
"xie": "';(f*&3c4k5+595I5h6g6v7&8>8T92:B:M<3>l?T?V?ZA&LRLTM0Q7QKS+S@SBStTRV*V^W4XKXOXS[B[y^<_Z_mflfnl,lU-!i-!v-#1-#D-#h-$#-%c-/S-2%-9Z-9q-9t-9~-:b",
"zhai": "%X)3,92q<?a@b]q=-9c-9d-9i",
"tou": "4G6sMyjqrItA-$b-&r-+h-8;",
"wang": "!664:h:i:j:k:mFvGmO>P*Q,Znh5iGj+jM-.N",
"kang": "%<+U2v3tg1lJpgugwmz={L-17",
"da": "!W.u/(/S84;H=<EsF*LHS0VCYldzi{j0j7j;k?kZt]tqvZ-!g-!|-#R-:S-:U",
"jiao": "$y$}'~+k,A,K.`/I1o5;8?8]9O:J?E?j@hA9AKB(CaEZE[KTM5NZP!RkR|WWWbX%X5X>Xs]Q]fa`d0dhe3gvh_hfi;i?lvnkoHo]p#q]v*xW-'%-(B-*h-+;-/Q-1>-20-3|-5k-5s-78-:a",
"hai": "5L?Aj9l/lnnro<-'!-'~-)Z-)b-+>-+p",
"heng": "?J?mMZT9vc-3o-4$-6e",
"peng": "%c&'&S'+'Z+,.V1+1@5@8P>~AACgE%FdJRMkRiRjU3eSgbh:s9v{zL-$+-$0-):-*A-,X-,b-,q-4K-6y",
"mu": "!1#N%]+V7`7n:@?.C5DeF~G%O=e/qKqPx!~3~G-#9",
"ting": "/s5l<t=j=z>%>&?qC)FnI7PWQ8ZJ[El=rUxKz`~K-!~-$g-%e-9F",
"qin": "$j$k*'*Q.d5c=>>MD1DAGZG^GkMRO8Q}RJS7TVWJWrZQc]pXpkriwix{}c-!]-$~-)f-+E-/c-33-4L",
"qing": "&/&Z'i046+60:ZDaHzQ#Wr[%]%_Agph+i7m<s4vi-!;-!<-!B-$7-%P-/}-2B-8X",
"bo": "%h&^'x(B(U*L+l081c2%2,3~4m:S>;>t?fA!BuC,DrGWH=I'J{L4MmO^U+U,U6VrW5ZL[d]Rd8d_eKf@m3pxq5qFrVtow0wxw|x(yT-'4-'^-(E-(V-(d-(g-).-)[-*^-+)-+~-,$-/0-1=-1}-42-6k",
"lian": "'K+D2+2P2V6w7b8k94;s<T=Y=n=q=t=u@+AZAcG(G,HLJTKDLELOMsMtQtS=U`UaVUW#We]0f2j?k(n0oPsZsyt`u@vKxfy}-,n-0U-0}-27",
"duo": "&U5|:!BtU0Uncrdfdid~eYg!g#g5plvUx5|E|J}*}3}S-&~-(;-(z-)1-,i-4]",
"men": "#{$*+XGRNEsuwVz1z6{>{M-#!",
"ren": "(o*,*e+#4A4U5)5y8x9$>?@AD)E}FGGDTUU2Y!ZC^I^Vg&gFi&p/p;pRqp-!W-![-#[-#w-&i-'#-(2-.^-3{",
"shen": "!U![$8$r$u%j)#)9,12e2g3T3U3q3w4l96:p:~>i>m?t@BFkHwH}JGK!LCPGPHUNX)Y1YHZ*[2^)_%_L_S_VfylPqRrj-$W-)W-.m-/z-0@-0|-1)-2N-4A-8b",
"ze": "#R#}$n(+*p/,0J1I=0BsKAS?Vz[(].a@b7b]c:jO-&t-6.-9s-:,",
"jin": "!#$j$k%M)8)G.U.m/J4W4`6L70:/B6F&F;GcGkJYM!TWW%WzX<X@]9_sb&bIc#j2j<k8olomp>sTwGy2-!^-'m-(Y-)$-7D-88-::",
"pu": "$5*k+j0$8LBTBUFXGGGaH~IsIt[D]]_|bEfInprtupv=xbyqyu|[-/m",
"reng": "(_DGiu|z",
"zong": "&Y'h+?3P3]4$5z6E6Q6n6x7(7M7X7e7t9%9n<J@MI=J=QU`eePeRf1t!v_-)z-*5-+K-,`-,f-.8-09-0G",
"lun": "&n'k*|6:9&='@4D:GLPk[1^`eBhAi)s.|k-04",
"cang": "15B$BpC<DUI~M#R3b/w8-50-6P",
"zi": "!i!j%()R*/*X*b+E/)2l354F4d6I6W8O9s<u>z?!?MB'CwE5E7ENE`F4GHHuJbL;NsXHYOYP[I_caFa[bzb~cZcpd(h3hQiJmbp&pmsGtRtuy=yO-$s-$t-,I-/{-0r-2P-4e-9)-9f-9i-9u-:D",
"zai": "#^7HGHb+g|i9n^",
"ta": "(d)i2~VAZr]wdBe7etfFfOfpkdkiq+sBt]tex1{'{5{;{={R{o-!s-#*-#B-/?-0t-2d",
"xian": "!:!O#5$<&#(F(h)X*3+D/D0V2k3B4%4|5A5c5t6,6]7J7r8Z8c8q90:%;];d;h?&@oAnA~B;BvDSDwFzG,LOM'M*MpOKO_O}PJT+T0V_W:WRX,ZXZo[O]d`>awbKb^cYdgd}f;fhgBhHnfo'oPqvr#r$rFrqs<sps{uww'xJxMy4zBzC{H|K|a|e|s|v}J~v-#T-$!-$$-%.-%H-'D-(M-(o-/T-1l-21-55-5x-5y-6$-6q-7G-7h-8$-9P",
"cha": "'0*049B=C9CjD}EYEdTAYyY}_1enr't7t[vryDz!-!U-'Z-(O",
"hong": "&*&8.*.>0o334=4P4f5i8o8{;z<!<==CDyF?HoHpL*LXNtXtXy^L`'`*`,gUhNhwi+p[q[rGrYt:z?zXzrzt{I~U~e-!n-.(-.a-3v-6i-8<-8?",
"tong": "!r$@%o&>*]+m.?/Q/i345D5N5`9PA@EjJPO1T,Z,cFj|ndq:qYqjxC-')-/L-2*",
"dai": "0,1n4x7%9AC?OMQ]TdW=Yd^xa7aLbqdff'gCgLg[i%jIk4p0~z-!0-)E-/>-3I-8N-8e",
"ling": "%d)D*M++.5/+4p6@9];K;U<.=KBqD[GiJJJmL%M|OiT(TcUjYVdLgZh/n8oWpts0x)zN|q~;~O~]~a~c-!2-$L-%`-)C-/$-05-2C-3L-6Y-7E-7q-9z-9{-:A-:T",
"chao": "!k,h,r2u6?9b;5<wXDY=]?cdh`mlpSwa-7w-8v-9#",
"chang": ">J@mA+DTGMH!UlUqZfs&sWy+z'z(z0zh{1{a-#d-.0-02-1X-2H-2T-92-:d",
"sa": "8g?^HDK{LYY@fnpQuwwS}A-!c-!s-&,-&P-)&",
"fan": "%0(M/1/40i2A2d6R7i8$;o<[AIBcBfE0KNLPM>N!SOVqXva=bcf<gEg_hThkj5p'v#v?wT-&=-&Z-&n-&o-(5-1E-5r",
"miao": "!J#m*=.10s6e6u7n9z:@D`M$l3-4s-6u",
"yang": "!R#!(C)Y*R4t;E;J;P?5OxQ/YX[T_0gahGqDswt,wX{}|.|y~:~}-!p-&8-&M-&k-',-)G-0]-3a-3t-62-6X",
"ang": ">Xo:-+g",
"wo": "#l&A,R,_6}>I@OAlB!G*HQLgP[Qbe:-(p-:4-:I-:L",
"jian": "!%#9#`$<$D$I&N&b','r'}(&(<(X+D.p/9/g0#0/0Q181k262I3_5U6Z788(899v:9<F>$>S@fB4BoCICSCTETE~G<GrHiI{K5K]L!LVLwN=RGSEU5UcVjVlWAWRW}X#X,YT[.[F[c],][]}^!_Y_v`K`Racaybkc|d?dKdye8ecephvp/p;qXrMrZs(tFtHu!ubv4vDvNvovpw.w1w5xwy:zCzD{J}d-!V-#;-#>-#O-#X-'A-'S-(7-(k-,h-0Y-0`-0h-1(-28-2h-37-40-4R-5@-71-7F-7I-7J-7W",
"fen": "#|%A*9./2x3=3r4S9';M;q;~ARD4IxKmO?O@TGY,`^`ff|hjnOpUvY}K~5-'W-'}-(c-(r-.w-1M-2Q-35-85-8n-9.-9:",
"bin": "%A8I::A)AiNc`X`cahailKvjya~l-$p-%G-%k-,'-,1-,E-,_-,p-.!",
"di": "!u#/%W')'.'{)<)_*U.v/*1=2c4+6c:);X<?=b>;@WDXD_FMG9G_ICJMJrJwJ|M6Q+QVR<TtX*X8XIYW[:[A^q_f`dcee_f/gYjKjtjukLkekwldp0qNuIyj|5}#-!X-#=-$H-(y-+n-,>-0>-69",
"fang": "!I!n(l4Y9*>TBjD;O!Y;^ed@lLp@siwn|,-,?-.v-1s-3E-51",
"pei": ">Q?(JBSwUrUsauc2hyiPnBn{s5y7|%|f~M-)#",
"diao": "$#&a,C,k.B1]5FJML|NhOaXxZ8Zv_M`ro~p_r!r:s*s[vawUxExR}v~D-.c-//-0%-2L-2{-3&-4O-9>",
"dun": "!<!A%J'3(%Pxd;eZf?fKfVjikGkvpLw`-$G-%X-*c",
"wen": "+C+`+z4B4C5X7!7<839)9|=d>^?gD'G!O'O(R/RO`ahShWiNu6zlzqzw{<{D{Q{c~4-#?-#{-%(-'f-)(-)4-.r-0g-0z-2V-36-3G-9<",
"xin": "!=(F?zBID7FkLZSyVtY3Y<gBiWlOo[pIrNv1w,xtyn{w-%/-(q-(t-)$",
"ai": "$F$|%l%~&e'M(:5=CbKGL6MN[K]k]{b2g(r9tWv^yK}1}8~s-!.-!3-'U-(m-1[-3l",
"xiu": "**3h5g7u8,9T;Y?i?yB*B7DuQ{ToV0V1`ur%rBtSu=u^uvxp-&K-'l-(X-,@-,U-/Z-13-3}-6d-9^",
"xu": "!$!*!4!Z#j$l%;)W+@+H3[5K5e5x6T6X6s7)8X9[9_=X=e?4D/IeJ)JXJfKrLxM/NQNTNUO[P$Q:UDX|YBYJYs[8[Z]C^^_3_ia^lUmwnLo*qovn~E-$<-$>-%T-%U-*[-,w-.G-.W-1_",
"tang": "$f'@)f0{3V3j3o;l=)@zA4J4LJQSR$RAcMc~eef&g+m]o=tiu)uTv'wDx[yWyd{1}:-#I-']-'h-5:-96",
"huo": "!V$S$^(*)>)S*Y*_*`+|,W10=$=4AuCJG.IhMTSI[g`0a<bacnlTpesrvhwoy6yyz5}y~R-!,-#S-*0",
"hui": "#J#[$G)N*i+s1;5R8.869I9}<9<:<L<^CxEbF1K2KeL8L9MFN,NuO7OtOuPZR*RyT_V:Z$Z2Z_[L]S]n^#^X_!_<`FaXbyh4iEjUk*qouqvI{6{j}3}S-!Q-$c-%C-%l-'R-/V-1#-81",
"kuai": "/w3}?]AWIJIql|n*-)0-1P-2.",
"cui": "'g,w2z3L4[697>:4<%<@?R?U@.AEANAhG{THVmd#uQ}Y-$|",
"che": "$;%I&?&@=JFjP@g<h~jA-$M",
"chen": "#t&M&t'`*[+A+{5{>FA}EKFRFcK:LmRBTDW6Y7Zz[Q[o^;_V`$arb;c`cad>dKeagKimjHmDo@pAt(|C|o~H-5T-7]-9l-9m-:=",
"xun": "!x$Q*p,^4;8MAjEnF:KLKSL[LaMcRzS%XwY#Y)Yt^R^T_+j%jajlkclsmzoTv`-%A-(}-)U-+%-1?-1H-24-5A",
"chi": "!]!y$).X.y/A0+02133,5W<#<$<D<H<|=@>>?2?D@SE9E|GeO%OHORR;U/U0UkVMYFZ9Zq[t`8aRcBc^d+dfeGj@jBkKkfkrkyl7q7q^qusx~9-&l-(4-(|-+&-.R-3Y-4!-4r-4w-4y-5]-6Z-8(-8C-9k-9v-:<",
"xuan": "!m!x#d$['5)k0R5?7J7d7w9K<G<_HMHNJ8K#L/MQMfPEQ?S<T)U$[;[W]_^&_abRgDi$jkl!q,ratLu?x0yl-#'-&2-)U-)k-0f",
"nu": "%a/.?;-)>-+4",
"bai": "+&.;3;3M51L^W3b:b_-#k",
"gu": "!/$J'B)A*~+P.z010?0u3g75:r:v;Q>K@(AfE)G>GhJ,LSOdOjSeXFYR^h`%a]bxgdgehYi,iXk,nYprpws]wwy.}h-%@-%W-'Y-(Q-+`-/;-0'-2I-3^-5?-6S-7%-9*-9+",
"ni": "!h#P*G2m73=i>$>}@pABA{DqLpOLP.Q!XXZt`~d`h.jhmCpnx3}L~X-(e-0,-2J-7`-:+",
"ban": "*E2s5!9;>PBgBkQ*QvVKd[iciipPqEwfzx|$-!h-$F-%Z-.n-35",
"zhou": "!+#U$x&y062.2@2C3+384:777o8p9:<B>B>o?#B^F@GoI$LfY][a]y^r_4_Manc0gkg{h,i0i<k7m>nCqg~Q-);-)`-)t-*r-+[-0(-3~-6f",
"qu": "$L'o(}.2.F/@2U3?4o5#<1<U<~>u?/AxDlG:HhKbM}O[OfOpQdRDRlSkSpT'T:U&WxX!X&X=YeYjZj^tcjcld%d*fqf}g2gWw<zfzu{i|4-)3-)5-*W-+'-,S-.~-1'-1;-3W-4l-6E-6[-7}-7~-8&-8+-8U-8u-9A-:/-:H",
"ci": "'=(A)%353a5579ESEUG6L;OsQpS3Yp^saqc.c_dSiYiZiaij}m-&y-'*-+l-,%-/+-3V-5C-5D-7'-:6",
"beng": "(l5@657k9iGnO*dtf3jYk2uiygz>-#)",
"ga": "g=onsfwH-.A",
"dian": "&p'v,j1iIiKRPXdXeVewq!x%|8~@-!E-%3-%4-%z-*g-8Q-:8",
"tian": "!:#;'1'H,j4w6D>v@:BRBXGvWmX9atnTr#rFsXx%xM{%{n-!>-!G-'$-3f-5J-5S-8:",
"bi": "#L#M'!(w)L*@*C+;.n.o/E/Y0(0)1/1<2r2y4M4m6>7Q8@8};7<,=a>a>r@lA[BlC|E*F.FJG~H:J<JdKHLLPPR!TiUfVhW$W)X^Yh^v`<a!aEaUbMblc/d|e~fPfQi[kBl)l^mjmym{q1truFvQx2z8z9z:zP{B{C|V-#,-#G-#p-&u-'j-(f-)I-*X-+x-,N-.T-/*-0Z-2S-45-4}-5b-5n-8~-9S",
"zhao": "#'$K.e00:V;#;?>*>1>2GdYf^ucScxorp<q.t6wL-)8-/G-3&",
"shao": "#+*y/r4r6%9>C&CqD^FyHSK}Tjh$la-#&-$)-,Z-/`",
"zuo": "(|*S+!+n/,/p4*7{?'D{F^H`HaJ?Th[(nWp||7-&t",
"ti": "!g#e')'?)Z)|*v/8285f6|9Y9y:{DXF!KgLIUzV&V'[qd)d2eJemexf~g8jxk=kLo&rDt)xy-%$-%r-*2-+m-,0-,L-,]-,a-/^-0B-2U-4;-4w-4y-5L-5M-5i-6r",
"zhan": "$H&b.33*6=9oGQLMN2N`NaOeWyYQZ/]h]l^B`#cghUhgiSl0n|zK~V-%~-&*-&N-&e-'|-*b-*l-.Z-1S-2y-37-60-7=-8i-:h",
"he": "&c()*(0z2i3@4?8r<N<ODdFIGFGzJ1R(SMTmV2WOYGYIYw[z^i`x`yaTbtcIloovq0vgzRzU{){X{m}Z-!7-!8-!9-#7-$P-%f-'&-(@-.|-1u-5$-52-58-6?-7#-72-7v-9n-:>-:`",
"she": "'y(`BJBKBLJuNpOgP(S5Y>^dagakc'cDg~{!{^-#h-)u-7l",
"die": "!g!t&w5M9G<k<l>pB5C6D~PmQ`R@V,V]YU[7_WcbdOdXdreigojNz+-#1-0S-2R-3d",
"gou": "/01%2)3g6t:&<h<i<jD>DhO[U#VBWwX;YNY~_(`ob5bgk_pMqHwl}k-#A-#m",
"kou": "!P!Z#r$$,P.2/W1OD+K=KFp$-5K",
"ning": "$P=R>!DpLevm-,~-64",
"yong": "%p&>A]DcIPP=Yre2e]l@mJmio9rHuVyh}n}~-%*-%s-'x-/y-0w-15-2A-2o-5`",
"wa": "%K,),?,E,`=N@r@xOyTuW1lc-#W-#^-#t-8w",
"ka": "?8U@qV",
"bao": ",<.~6h?,DgGYHcK`L4MJN^OJTeUdV4V5Vf`ib*d8q/w%x.zs~>-!6-&x-&|-(9-)/-+j-,M-/7-1~-3/-3A-6Q-9r-:B",
"huai": "=7N3N8VDVSeE-:f",
"ming": "!C!w#zEDJ'R,WuZ0m^n_q}xT-3.-6L",
"hen": "Y|-!y",
"quan": "$b%u/K0B5<6$7:9mEqI3NAP|SlXLZ#_)dkeIgzi=o5qxv%xO{#-#_-%M-&$-)V-*3-,e-0L-2^-9}",
"tiao": "!~(t),,J,g/!3/4.5F=S?PCdD^H0J@JMJNPnWdZ8Zv_McqdwjCr!rdtyxR-#%-,G-/o-1&-2;-9y-:C",
"xing": "!D#Z&$0Y0g6J@YApBFEuF7FhHrP9T#XVX_[_lMluo+pBqZqwrhwZx6|D|S-'V-(/-)p-+D-/5-0D",
"kan": "!N$=$g%?'^.QG&T%h8ho{4{q-%)-.+-:R-:X",
"lai": "#8#F/X0%2/2MG'H%MSW7Zqaob,c&c4k.mBsgxd-$q-$w-)y-0*-4B-4f-8%",
"kua": "50?>B~Z=d9dlq~-+s",
"gong": "'91*44474=8o;z>[OBXQXba6bZfzg$gtrG-!z-,T-:L-:Q-:W-:u",
"mi": "!s#p$A(w)')w*C1d2b2}3p407c;>;F?bClH{J#K'K/L}N#N6PaU*WZW[WcX1Z.[j[l_g_rjXo4oXoYo_r,z/-!K-66-7X-7Y-7j-82-9&",
"an": "!(!.;)?I@XEzGlHWHgJSUxZS[N_d`k`{r1s:x]zy}+~=-!w-!x-$1-(l-/E-4I-4u-6v-8c",
"lu": "!)#Q$_%|&L&d'])E)J*}+[+o071X1v2!2#2G2H3I6S8^9q:f?9A,AtBmBzCFCMCND.G@JcJeJtKcM[N<NINVR>SYXh[~aVb|d$dseCf#gxh^h|i/i>iTk5nwpis2sascu8uMumvGw&w+yr|A|t~x-%J-%_-)+-)r-*N-,3-.q-.t-00-1i-1r-1y-3w-4E-4P-6!-6>-6U-7;-7C-7M-7b-8l",
"mou": "!|7n:@Oq[[_Ue6t=-#9-3y-8!",
"cun": ".N2nA>lS",
"lv": "$()(*r+~0`5Z5~6S7_7j9q:*@wA(A8A;HkM,NKV=VZm'rJw#xDz_{T-*u-+*-5a",
"zhen": "!X!b!c!}%Y'%)5)T)b+I.A0X264N4w5Y7D7L9,:2=I?%B9H5I5IWJ&LnTpUSWaYKZb^pa2afbWc%g^hZi4iqkOnNoxq$r~s`tOu$u%wJyS|0|_~L-)D-,o-1f-3@-6R-8d",
"ce": "/%/U/^/t0G1W36F/H3HPJA",
"chai": ")&>HCjEJNzS9T[Xz`jp4wY",
"nong": ")v*j+q8C?cAXL,V~]iioipoL-,{-9a",
"hou": "#c$t0q3Z<M<UEVHq`MjgltmWq|t@-'T-+r-/B-0C-1O-2!-2,-9Y",
"jiong": ",M4~5uB#B&MeMjVIjFjjqi-$}-%h-)6-)X",
"tui": "+y=N@tJ^MAM^P?PYQkVL^.eof7jb}D-$n-$o-$r-%m-)l-+u-.L",
"nan": "@^G$HOQe[PcEk]}_~'",
"xiao": "#+&4&:+i,N.l/q0!0O0c1(1b1u5a9R<;>@AGFyHCKyL2LtNBNMP<RPR^S!S[Y5YzZUZl[ke*jom9r@xh~I-&3-*`-+8-+q-,!-,+-.I-3X-3p-5Q-6W",
"bian": "%`&}+8,;/=0S2A2W3W6k6y:$:+B/C4DLHUL{QBV+WTW}[5^/aJbfi^idieifihikiliviwkSl4l5oatPzO-##-#<-0I-0J-2`-3R-5&",
"pian": "0l6y:$<I?K@5WX[9_haIaZdtejsq-)O-)v-*(-**-+?-+N-+w-.z-5&",
"cu": "1P3)7S?xK)XAZDcjcvd%d*eOehf%fAfBo$-%<-7O-7R-7T-7s-8t",
"e": "#3%:%B%Z%y'<(9@bDRF}HXKfO#PBQ)WpY+ZF[?]L^2^___`NgMgii(j8kRkUl%mRpJpjrrt&w*xoyCzn{f|!|3|:-$?-$R-$S-%%-%&-%t-%w-'6-'L-(G-)n-.f-0[-0v-1h-2Y-4&-4<-4=-4z-6o-7$-9[-:3",
"guang": "%w?@B#B&EWgwj}r0-89",
"ku": "%,&5*D,@,T5:9E>{DoU1UbVTdPllnm-+o-/R-:p",
"jun": "&].=/`0<0CFlGCI1O/PeTXWhaeg?m1p^qfr&t'wj|Q}^}l-$j-'8-(J-)m-+F-/]-2?-43-44-47-7U-7^-7d-:Y-:]",
"zu": "(x*J+10H4}95GqHbIkYm^kd6eLtdu2u;yk|6-!f",
"hun": "!F#O#W#]F6I8JyXT[=_2hczo{d-'>-(L-.C-9J",
"su": "';+1+3+],X1U3.324X7K7U:?>,>/@{DWFwJsM+M]MiXWYE[r^o_lcyf(k$khksnZrR-':-*_-+L-/i-1@-5p-6~",
"pai": "0'1z1|IOhBjLtV",
"biao": "'c,!1D@3A,A1AoJoM=T@UoVa[3]#b?seuZw$ycz#-&&-&+-&5-&D-&E-&F-&H-&O-&W-&X-*~-+@-+W-,;-2j-7Q",
"fei": "%[//1!6M9a<A>O>e>r>z>{@GDFGjH$KhP_PuRuUtZp_GaObsvEyP|g~T-!/-!H-!I-&Y-&[-&]-'H-(j-*!-*,-0+-2F-9;",
"bei": "&i&r)`0'3f>wA[DfJ9M8PAU'V;ZLZwa1bVgch@iDlbm5mQqWrPv[wd|=-!l-#,-#C-+k-4N-6x",
"dao": ")=)H)x+B+K005F8h<B<`B_C7GwR7T4T7Umeqg9iwkYoq|b}=}O-.]-1n",
"tan": "'/6D8A:_:eBOBSGtM@TkW(WJZ~]Z]a_R_xa.a>bdm?n~o,oJqQsMx#y8-$x",
"chui": "&U0D@8GPsFtny0|n-$u-:_",
"kong": "%.&V,/0@;gg,sA-#(-4[",
"juan": "!{#2#H5J5V7Z9S:|;=?w@7AaG[K3SfUM^&mTrZras6u0vHy5yXzt}^}l-#'-&k-'.-6m-:w",
"luo": "%T&!&='n/>0M2]5>8f9M:n;@@)@UAwF8H9HYJ5N9OrRHS6UvW~X'Z1dbf`g'kAl:uEw>y/yf}s-$f-('-)_-*P-*k-+=-+X-/K-4#-6)",
"song": "&P.@===yGOY0Z]^b_?jcu1-$@-%[-'[-)e-,c",
"leng": "#>&h++L@eD",
"ben": "/&<(DxRuaUblk/sIy&",
"cai": "#T677P8aGSK+U>a5b[dxeQob",
"ying": "!R$v&C&|(P)c+_2K2^6U7/8`9^:>:X:Y:c=?A:ASDzEAF7F9G0G1H@HAHBHZJKLqMwOmQ0QPQiR0S8SgVPWv[i]M]|adbHc@g:j/m2twv8vky?~_-##-$.-$i-%g-%o-%p-1V-3g-4q-5*-53-5o-5~-67-6C-74-7>",
"ruan": "&u(>6^<o@QgQhDi*|(",
"chun": "#_0_4L8|>U?sA7C~G3G}HRITJZQgSUb(hKn}o/sL|T-0#-0Q-4i-4~-6{",
"ruo": "0P1#DnI}mP-0e-0{-5<",
"dang": "!,#s%2'((2/[1f2&CDF3GbKuN(S)UCW&]b^A_kd&kEvWxB{9~A-8[",
"huang": "'w+e0f1q7O>=C1F#H|Q@RtSuYv[V[f_Xd,kWt3txu}yI},-$,-'P-*.-0T-1A-2]-5q-86-87",
"duan": "${'&.I1`2X6a:#<r@kI/V/fdt.tGyG",
"ou": "*$=*@VA*KPM)MG]3^Yu:-3H-5[-6N",
"zan": ")},'1A1t1x3`W?^'^?apbCc<d4d5e}n1n7n<smuaubv2v<-((-4C",
"za": "%k4^4gAvA|Vpj*q8}r}}~)-$'-.u",
"lou": "$(0x1V=+=1C>IMK(QfRI]1a*g3kxu]yN|F~x-#J-+}-,*-5a",
"sou": "#v2BC;IQJ(L_M?Qum[o3t~uAyH-&:-&<-&S-'c-(R-*<",
"yuan": "!9!f)V.i0F6f7':.;m>CD3DYE?I?I_InLGLdO5PRPzQFQXQrT&TYUiUuV3VF[xa3bYh]iQj=k@kgl8lRnPphs'u(|^-%1-)9-*G-.o-30-3U-4V-5%-54-6K-6]-6}-8s-9!-90-95",
"rong": "+Q+S5E7@9C;^>FEFEfF5J/QhQwQxSDVEghthyb-)R",
"jiang": "(43u3|5P8:9L:F<>=.A2EaH^ILK.LAQjRM[w]=^W`6ngo>oF|H-#P-%5-12-2_",
"bang": "&<'A+%5_749B@uC8IcO!O*OvPt[s_olQlVt^y^-#3-,#",
"shan": "#:'m)K)q+W.s7g7}:D;p;r?pALATBaD&DtR}S,TCWjY%[b]r^ObFc?cVd^gGlAn#p!qtvBwRz4z5z;{@|P|X-'q-*J-+V-/l-1C-1D-2s-2y",
"que": "&5&E&g'6'7(1(N:P:RI]O6c}z}{*{l{p}a-4Q-6t",
"nuo": "+<+u3e3y4&<oU/U0[Y_DelkCt<y#}_~'",
"can": "+W1A3ELBO4Q.SjSn]2uJ-&`-'3-*T-+M-8^-8f",
"lei": "$X'F'b(,(H(I)7)~2j4h5H7Y8S8Y8j:=:d;t<s>5JiKWL.M4N*N+N7N@SPZ:^(^zhxnoqls?vPvvw:yz~<-!*-$O-$_-%7-%}-1Y-6<-9R",
"zao": ",y,~1R3sC#LlMPOC]`d1f4fNk%ktoCwA",
"cao": "3m>9C=C[EwJ_R:VbV|n)uc-*D-94",
"ao": "!T'Y<Q<V<Z=wC]DBK&R=T]Vy]7]8g]kom9uBuMuNz*}>}I-*S-+S-0~-2b-5X-8{",
"cou": "@ThJiK",
"chuang": "'_,H,L,q{+{E",
"piao": "$+).1D7a:;<RF|LiRCo?{3-%9-&A-&B-&V-*U-+W-.S-1.",
"man": "#{$*$c5X7]:<JkJzN)P2R6R]SoVw]>_tmuuCuUye-#!-%;-%y-'i-(Z-,t-,u-1*-2m",
"zun": "8':^U5]Pk|qqv+-1B-2u-4n-5|",
"deng": "$7'q.M/H1pCCW|`:f9l>mxv6yx}E",
"tie": "=VH8OhaPbndXq'qzv>vRx'-&z-'Q-*i",
"seng": "-,v",
"zhuang": "3:3nF)F]UBUZ",
"min": "!B%9&`.}/<1l6O6d:,:wDiSSb'pqs7tEzEzZ{K{S-1$-2n-3P-8q-8r",
"sai": "2'@cb6c9-%#-0_-2X",
"tai": "0+27>h>yB8BeD]GeLjdIl[nSpfw^-&/-)E-+7-/6-2$",
"lan": "17212Q4/8K8i9x;+I<JCL~N$N&VVVxW*W;WDWoX(X4]K^:_{fknxw/wFytz~{h-#Y-%K",
"meng": "$/$T$`(?C.CKFgH'IZKONvPgQZRaSFn'n,rKsby<~?~k-!(-!)-%F-(!-,O-/t-08-68-7@-8q-8r-8z",
"qiong": "#@#x,+,.,d,|/:/FB{EBM%MBP,P0cWdolFqs",
"lie": "5}=]?oEOOzU?csf^j`-&0-,x-.#-/J-1c-3r",
"teng": "2>2F7I@sAHM9N?R1Z@[`l1~u-)S-*B-*v-0s-97",
"long": "!p$a%n&=(R(S,u.!.6/;1*162N=P>'?6E<MxS^S`W8`4bSffu`w)|B}%}T~y-!1-*t-6@-:J-:O-:P-:V",
"rang": ")z+t,$8bMn]s^8^Nfm-.$",
"xiong": "?F?GCrY:YiZ4^a^mb'}e",
"chong": "*86b;:;|B@CBEhNfQRS$T5V)f!ohqhxZ||",
"dui": "&k'O(m5nLb]H]vhsj_v0v9ys{v|j})-$[-3h",
"rui": "#h.h6N8+D6E(KUKVKpMWMXO)P~r<rwuqxx",
"ke": "#u%T&5&~'B'Q(*(;*<+.,U,r6[9t<7C3DdHFLFOTQ5S}TmZi_Hd:gni.o2psqds/wyxQy)zM-$Z-${-%i-%q-){-+I-+y",
"tu": "*)*.*x,6/a@dFNG+GVHsIfcbe&j,jvnjp6porIs!s}wO-(h-)j-4*-49-4T-5!-5O-5z-9M",
"nei": "?~@;lNsE-'5-(I-/e-0!",
"liu": "&B&d'>'[,A6;9l;1;;</IgJ*MERWUTe|kbs#tctzuHu{xuy[ym~%~j-&>-&@-&C-&U-'b-(W-)M-)c-*@-*d-+T-.9-0m-5=-5_-7.-76-7[",
"shou": "6.9h@yC2uA-(_-:s",
"ran": "7v>ZDZIFOEOYTST`Tz-,A-,K",
"gang": "%.&q/{639!:N:W:x>Ep+s)ttwe",
"gua": "506}:z;%>xU<V#Z5[>^|cnedr#rFxM-&'-&1-*9-3k-6c",
"zui": "#G)C+15&8d;$KjRdXHi]nInqo!s$s8",
"qia": "%{'I?1HyU4dUnU-!{-+z",
"mei": "!L!_#)#a({)]+X0h3p;I;T?S?r@PE&FfI@QGTZdMg0mOnlrKtUtZyMyQ~N-#^-.>-.F-5(-7(-8V-8h",
"zhun": "+$,56(>UT8YAZ{_Pj.",
"du": "#K#b*f.T.^1,>DCsFBR&SZSmUyWqZd^$^D_E`3b%bNc)mMo(sDs|v}yL{!{^-!Y-#V-#s-#u-*E-,,-8]-8k",
"kai": "II`7gysvtbt{vCxGxvy@z<{({U-&;",
"hua": "%;&K'B3S8/BWD9D:GgIKK[MzR#XKZ&Ze[4[>]A]o_&`0p(p)rbsdunxN-*]-+<-5m-8=",
"bie": "FTNFObRmVuf6-19-2l-8|-:[",
"pao": "%e(@(O,!?|H>M=TeTfV>dDdTgfq/x.-!N-!o-7Q-7S-7|",
"geng": "56575d6m7,9Q;j;u<v=DFVGfavc1m0-%b-+v-/h-25-4j-6|",
"shua": "<nZR",
"cuo": "#1$z'G79?nFpFtImJ2J}NR[(erk0kzn7o7rEs^t[xqy$-7H-7L",
"la": "%_'R<*@>AbArG?HYM3PfQ4Q[SRi_i`l6v|z$-#0-,k-0F",
"pou": "0$UO",
"tuan": "1H4'V8a%u<-5V-6#",
"zuan": "1B2Y808N8U8e:Kb3fjftq)vxw?w~",
"keng": "%t&3&RZOr>t1uOxg|&",
"gao": "#R#g)4)6)e*m+N+O/v0~3i7E:5;O;TA'B,GILrMHZ[_:m+ryu#xny]-#o-'_-,4-,5-5R-5v-93",
"lang": "&7*n/dC(F{IXJ.JHPOQlZEg%lzl~m.rLu&xzz^{]-)h",
"weng": "#q:b;}=rJ0L(Qstg-56-7,-9_",
"tao": ")?58617A7N9W9kG|PoUhX{Yn[{^MdwhXjPjenzs+|r-!k-!t-#@-#l-#|-&w-'d-'y-)P-)x-9/",
"nao": "%z&q'*?a@&@ZAkP6R~YZ]Ju|x@zJ{O-.'",
"zang": ";S?_AmAyB$I,K@M#abambLbUb}rC-)A-++-,.",
"suan": "(z.b/m0;1BI^nn",
"nian": "':*5*P1Y3*C/K6evf5f[h=iCiS-/4-0;-1x-2K-4%-8B",
"shuai": "7>:4RNTH",
"mang": "!5!6&<&D.ZCvEXEiG4G5M_OvRaSAlDp.rsx:-)g",
"rou": "*!2w3X>3?l@aHdQC]tekhEt$-#2-#f-*7-0R-4t",
"cen": "+W.m1A",
"shuang": "(V7;CPuh}z~b-*M-*y-+^-5c-6A-7B",
"po": "%g%i/70I3'7i<,IlK,jLn%n[o1oKotq9uswMwz|=-$K-%a-)7-,N-.E",
"a": "@@s@x}|:",
"tun": "AYAeC~OlVL`G`IgJ~Z-&h-(0-.j-1q-8J",
"hang": ".k/T5*9HBiDHOAT#UAa9j3lJ-$C-%]-.i",
"shun": "!x$&$1$9BZKo-$:-%S-,g",
"ne": "!vY6^]",
"chuo": "'j0^6?8&9bd!e'e<h6iIirisk:narfu;w!-'B-:&-:G",
"wai": ".O-%:",
"guo": "$Y1K3R68=W=c@6@LA.GJK9N]Q&Q1RVUwV!h7j'kTm=pZszvVxm-'?-(K-(a",
"qiang": "%.157p82;G;R;Z;a;g;w@HCEJjKBLDMvPhVd[ndGeuf.tkuPurx~yAyw-50",
"pen": "<XHm",
"pin": "$V%A(8+w=^LcMaSLa;be-%I-%k-&#-(s-*q",
"ha": "4_NyP'QOqdxQ",
"o": "/}",
"huan": "#.#x4!565b6l8;:(:I;b><EoH#H,I_M<^>`Q`b`va/hpj9k3l/lsn9tCvSyJy{{:{r}i}{-*|-,|-/n-0A-0K-2>-3?-4+-7<",
"ken": "&#>8>Y>fUFV$`S`wsh-:?-:E",
"chuai": "A0ACeb",
"pa": "/b<zBdDrI)Trd7wr",
"se": "+b+r,#3688CULD]Ehnr4tauuxVz[{5~2-&I",
"re": "Dn",
"sun": ".f/L0T1rF<J%L$LNt|}]-&f-&p-5A",
"hei": "-8D-8E",
"de": "OMsoy(",
"kuo": "*Y*_/GH[H]O~r#rFxMz|{k~o-#.-#H-#e-$V-,V-,}",
"ceng": ".Lf:-*f",
"ca": "(E(Ykq",
"zeng": "$~'p.L5z7H7z9n:E;9]DbAc>m|roxk-.6-1K",
"nin": "?[",
"kun": "#7&H);*s+*5oGEPpUEUJUgV.`wo%sCy*z]zj{Y-)w-,<-,=-,D-0/-2G-4^-5'-6w",
"qun": "0<;_;`UVU^e.k&-7U",
"ri": "TMp/p;pd-)%-+(",
"lve": "+4rgrlxr",
"zhui": "&U((.h66729r:'@C@|[!b>c6j_o#s>sQvdy1})}Y-)s-+J-4Z",
"sao": "$O7m8<:A:HAdR4-&<-*#-*I-+Q-,:-0l-1R-2a",
"en": "J!",
"zou": "0&6GG=[)_CcNcOlem@mcn.|h-*H-+1-/w-06-2E-83-:.-:7-:n",
"nv": "2h=TSvSxp8wW",
"nuan": "-'M",
"shuo": "$m&+'$0cIvZaZc_>qttmv~x*",
"niu": "4H9.FxpTwq-!`",
"rao": "+i8)9FF,KdVvk}}B-'v-(>",
"niang": "nuoRoZ",
"shui": "#I)7*q*z@1U[ZaZcZg_>_KzG",
"nve": "&ONJ",
"niao": "@%E>K1T^UGVO-2{-6H",
"kuan": ",s,tAqw(-,&-,2",
"cuan": ",',Q,s,t,z1)1[fLfsrZw;yv",
"te": "?vRgr{xe",
"zen": "]V_y",
"zei": "S;a_bv-0M-1Q-2+",
"zhua": "2(AU-,Y",
"shuan": "5<@]z3{?",
"zhuai": "#1dmi'",
"nou": ";v=,tfv;",
"shai": "/Z121J1e2[[K",
"sen": "Ve",
"run": "$1AOz@zQ{F",
"ei": "ZH_@",
"gei": "5C9J",
"miu": "7n:@]+_w",
"neng": "?LR'",
"fiao": "WL",
"shei": "Zg",
"zhei": "j:",
"nun": "-84"
},
"m": {
"yi": "-:~-:<-:;-:4-:3-:#-:!-9~-9T-92-8u-8R-8N-8I-8+-8(-7O-7M-74-6l-6c-6L-5z-5)-40-2U-2Q-2>-11-0o-/_-..-,o-,B-,3-+q-+[-+<-)X-(o-(5-'w-'k-'=-'#-&6-$'-!?~=}E}1|x{Zz|zzxix6x.x%wKw,v%uPs_rurorEr8r)pppdpXojoioVnxn<mLm=l1j{jvjkj/j(i^i]i6h6gYg0g)g&g%fbf2f1f0f/etepd~dpd;c~c`c@bhbbbTaE_T_>_,^g]|]{]`]/[!Z=Y5XVVTTgT_T7T1SxSsR~RyR;QwQ0Q!PDP6NbN^N,MZMSLXLIL6L$J9I}IUIIHMG?EaEHE4D!CwCFBkBTBEB9B5@2?Y?K?I>K>H>'=a=R;m:~:48c8!7,5g4q3&2}2Y1j1f1`1M1/1'0t.O.K,_,,*x*f(c'G&.&&%b%Y%G%$$b$6$/#x#T!9",
"ding": "-:}-8q-)?-%!vipfkGiydzY2Ik6u+B&^&[%_",
"zheng": "-:}-9O-7L-0#{1{,yjuvsRm*lNlIi;eheZe8e4e3d/`x_v]3[+ZSY8Y2XlVFTYT#Q1C@A!4W3w07.),]%*#C",
"kao": "-:|n{k][#TbL>>R3p/,",
"qiao": "-:|-:(-6A-5v-4=-3(-2[-.@-,2-$H-$5-!q-!=y/y$xkx4rSm+m!k]k%j:iSi(hqbvaT_wVuV6V$T%KgGaF^FKFGEpDSBCBBB;8<2b1C1>.}#e",
"yu": "-:|-:p-9^-9P-9J-9H-80-7t-75-6'-5g-5b-5H-4U-3F-2l-20-1F-+K-)O-)+-(J-%a-$p-$K-$9-!7}]}W}5{7zizNzEvyvwv9v3tytjtetcsqsos@rsq|pyp+p%oQn6m%l8kyklk8jfgvguf%eGdWbtb(aLaKa:`1_1^:]e]d]KZOZ!YmXiTDS`SUS7RpQyNvLAKsJKJJJ;IAH|HmHVDVD:D*D#D!CrC[CDC,B*@K=Q<><<<1;h;_:v9G908=7M7I7A535#2{2R1b1:1(0;/q.(.&,1+G+9+7)n)h)F))(+&*%!$M$D$=#V!A!0",
"qi": "-:{-:r-9{-9E-9;-99-82-8$-5f-5(-3D-2{-1:-0G-.j-(Y-(A-(.-'v-'C-&%-%m-%I-%F-%E-%:-$D-#N-!Z-!B}$zvyHwbw;w1u~t[tFn;n3n$m~l^kkiBg/dpdTcKb<aBa&`3`2`'_b_)^K]OYRY@X?W1TgT1SkSjS1RxQ4PbO@N]MyMCL]LZKhKOJ~JcJ]J[J>J4GyGJE<E,CyCkCjC<AEABA6@a@7@$?+>W<Y<N;5;49h9*6f4x3D,m+N+8)a)](`'4&V&1%K!C",
"shang": "-:z-:t-7k-3C-%S{p{*y:oAo@c8aA`@]}Q^G.BxBZ@P:h9>8l1T",
"xia": "-:y-:s-9u-6I-5e-3w-+T-*+-(v-'m-%n-!!}({Mwtwpm/logkeB_3YST)P~M<Jm4X3z2i.L.,,}*C)1%V%J$8",
"han": "-:x-8S-7J-3>-1A-/j-/i-*P-*J-(^-&C-&9-$k}[{Xtrrbp,n8lJl%dqbm_c_L]cZ(VLV%T]R_R^QRQQPOK9IJCH@l@^@?=k=Z=?<l8k7X6'3I3@1Z01.g,t**&{!p",
"wan": "-:w-:E-5=-/S-.f-+)-+&-&1{0z*x=wlwkv6tJt3ptpen%iGf_f?d[b.b,]1Z9Y|YmY=QOQDQ@Q,NVNNK1J%G9B2@h<m:X7}7<5:3]+&)t)Z%z!n!7",
"mo": "-:w-:5-0z-.(-#S-!l~j~b}!yQy#v!r_rMr$qno}o?iji_hR_(^d[cVzVkVjUHTyR$O4M[FrFMC$C#B~?~?o?e8m640`.8,%#m!x",
"zhang": "-:v-8k-8]-8L-3=-1byErnkDimiIh}hsfnfmfXey`Q]hYEPRNeFt<y<p8Z8Y,$(V$`#}#u",
"san": "-:u-8d-7q-5p-3c-*Q-)m-)l-)k-)j-)ix:iHg#T5AeAdAZ7s/%",
"ji": "-:r-:O-:<-9p-99-8x-8w-8'-8$-6t-4W-41-2w-1:-12-10-0*-/l-/^-/W-/#-.d-.^-.0-,9-*z-*`-*U-)d-)P-)0-)#-'6-&c-%s-%d-$V-#x-!{~z|~|f|.zxz?z(z'xlw;v^vJuZuLs$qAp=p;oJnfnEn(l$l#kRjsj0iBhYh!gEf=f<eseMdxdaczbAb*a!_K_)^k^F^C]@ZRYCXHW1V`VBV$UtU^TgT1RnRmRlP]P[PCOAO@O$N9N5K,JNI*HrHhGoGTFfF3ExEXEPE8E'D[BdB:@O@B>y>]=x<`;t;(9.9(857&6{6d5m3D/;.j+?(>(!&8%{%t%4$X$,#H#>#'!w",
"bu": "-:q-7F-,M-*w-*t-(d-'K-&D{B{?{6zPtOm#izh'gfd,bi[rY}Y{YfQHM,C>C;C:'=",
"fou": "-:q-(cvCBj4H",
"mian": "-:o-42-1d-0w-,S-,H-$`tktQsZqpq#aDNWJ^E=D~@p?|;k;,7G/o",
"gai": "-:n-9w-6e-+u-+t|'uYm@dy^AW%T`QYNaHZGNGM:M8|'{&6!,",
"chou": "-:m-:l-8m-5_-4=-2A-(m~{t/r!iKhld$b3aS_%[_Y%W~N?N=LJJkI|E?B_0h/O.s.p&3&!%l#v!m",
"zhuan": "-:k-7o-3G-3+-2y-.I-)n-%+~EzRyPreq<oXoRc0V28t5%)5(f'.",
"qie": "-:j-7Q-/`-+P-*@-#uw1u{iCcpbkaia8`fZoOZL]I~>;<`,E%g#(",
"ju": "-:j-:?-9m-7n-62-5`-5S-4x-4j-2l-19-0%-.Z-.:-,.-+n-)H-'d-$|{/ztx_uct_tNt$o{nvnqnOnMmqmil`jYj9j7g+e%d<d3d(an`}_E^j];[I[C[;Z^Z@YdY$XMUdUVR3M^M9KxJyI{IzIrHSHDF:EcDPDCC8AqAi?b?L?(>:<g<I;[:o7)4L3o3<30/4/..P.A*e)m%5",
"pi": "-:i-8@-7|-7P-2Z-.R-.9-+>-(h-(c-%5-!.-!,~|~X}2|L{2xhvCs<rwnumblFk7hWcibx_U]G[q[eXcUgS+OXN3N2HPB&B%<@8>7*6e4n2x.Q.G,n)Q'(%k%h%@$p#R!U",
"shi": "-:h-:g-9q-9l-9N-9M-8t-8_-7R-6k-6]-1X-0m-,^-,:-+_-+6-++-*>-);-()-'{-',-%.-#t-#7-!W-!>{>z|y{x<wQvqunu<r4pbpap[pVpMp*oun~ninhm;m7l0l/kQi{iuiQg!f}f|eCdodTc_c[aU^*[.ZyXYS6RXRUQ{QzQhMWLxLrLlL5HiH[H,ELBiAJ>n=}:{:s:W:5:'9v7C7?6n4=4%28.3.+.#,0)$&3$}",
"qiu": "-:f-:^-8m-6#-)u-)9-&+~*|FyTsQl4j2j1cFc&]rWkO%KIHeF8BpAn@s@b?J=o;^:l:k:j2D/T.k+D*'([!P!(",
"bing": "-:e-:W-8h-8b-6u-5B-4T-3Y-1;-0a-0[{mp1ngnZhbhah&cm[vY,W5R0QpN/MQLQLLJnJbGXE:@~4E)r%,",
"ye": "-:d-9z-9.-9&-4e-2_-0U-)7-(u-'%-$W-#,-!]{:zIxqxdwgoVm$jxjw[hZzZ!YyY;X:X6UhUcUUUSURQUPxP@P?P,OmOkMYMXIQHsHpCXBh>i<o8q7w753l3M2N1H0M0,0)).(T'b$V!a",
"cong": "-:c-8f-+r-)K}w}ptPq6eUeTeReJdZcnbUbIa3`^`.PTMxJ`HyG2FgF[D?<G9!8v8L8D8#5V3+1n0H)X(e(a",
"dong": "-:b-6R-4n-3,-0`-0P-0>-,u-,G-,/-'I|/{)y&u^tXr*mSm>lKl?eNc3_H^LZhX<QrNILfJGA8A+>L<j:n4#18.0",
"si": "-:a-9B-7T-7R-7N-6H-5f-5X-4,-3y-2+-1[-03-*#-)x-)5-'F-#m-!u-!M~)ugtdsJqDoaj.gUd%cHa2VMSDNOMeL{J~I/H!C'@X?O?8?*>^>J=,7[6?1</R",
"cheng": "-:`-:%-:$-68-4_-2z-0K-0C-(@{H{A{,zYz#ywu*pSlth%eZb^al`(_v^t^U]A[zZFYPXDWQW,VRVQVFQJQ)N}MnLtJPJ@IyF=F<F9EeETA!?0>e=_=G<8:?7f7d6/05/f*6*2)c%x!'",
"diu": "-:_-:[",
"liang": "-:]-:Y-9)-5x-5[-5>-5%-1G-0B-&J-%y-%7-$Ly2bWY7Q*KJIwG%<e:_",
"you": "-:Z-9#-7w-7=-7'-6X-4;-2*-0t-*p-)f-)c-):-'~-&u-&>~n}bvRuAq=pZo8o6m1lyh[hZh(d]d$c|cqbY`,^xXkTaS4OVM;LAKGK=H{GFD}DJ@8@(?V?>=g;C:b976B/j/i/O.b.D,?,>+Z+Q&g&d%O%!",
"yan": "-:X-9d-5]-4]-4O-3;-1u-1Z-1Y-.a-.[-+:-*O-*F-*/-*$-){-)z-'%-&=-&2-%'-$N-$G-!L~~~a~Q~5{GzAydy7xLx@wMw>vPv>uFu@titfrgr?r9qwqfqWpKmhlEl'kuktk0jUjKjJjIjGg<g*f'f&c$a7_5^~^8]w]?Y3Y'XhXPT3R8QZQ.P`P/O}OrJsJ<IFIEI5FeF'E^E.D`D3C_BhBG@,?P>M<V<&;K;D:i:H9R8y8S5O5I56544k4j3u3X3J3B3?3!2~2U1t1E140Q/U,w,g,d*Q*()V'$!3",
"sang": "-:V-$J-#}WgK{KzGV",
"gun": "-:Ub6JY9W93",
"jiu": "-:T-:8-:7-9|-9v-5A-2p-.H-+|-+e-+]-+A-*(-'U-$wvdo(gogdgc`IZ2XkXBXAW9TGRhO#NTM+LsKwFxD;BW@v@Q5$/b+:%X",
"ge": "-:S-9Q-8R-7!-6|-59-.O-+l-)(-)$-(e-(D-'4-&Y-&?-#s-#`-#J}6yhfLaO^^^R^=]_ZkX*WHULTML4GsE!CW9l9`/T.f,C+k%Y!,",
"ya": "-:R-9>-9<-9:-7m-5K-0W-.$-*H-*G-*A-*?-(s-(H-&n-&'-%;}L}@}9{j{5zbxTuBu3t%q2n,lVlUhKh?[`[ZZZY:XLN'KlIsIFA0A,<r9,8//g.^,[,E+/)}'b%h%J$x$K",
"pan": "-:Q-0S-/B-)R|Ys3i1[O[AW]U=MMGu?t>}>k:c9s84684l0$/w&C&/&,!*",
"zhong": "-:P-8A-83-7x-4Z-0j-/C-$Tz8ysx$vMvHs^o)i)eqe0dddYS`MKC2@=?G4w2k.T$F!>",
"jie": "-:N-8i-8<-5$-4~-4W-4!-3j-2]-/@-/?-/)-,r-,a-*j-*i-(e-&c-%d-%H-$m-$8-#q~zzKz7vevOuit>sasVsCs(qSpIoJnpnln*m|lCkvkbjrjqjgjbhihIeEbr^S^;]_[,YZY+X{XwX?W_UmUOUJS9RINXNJL0KxKoI~I1HqHgHfH8EOB,>j>;:z9b882?/'.6+0)H)8&K&J%g%]%M%##D!~",
"feng": "-:M-8:-5L-4N-2Y-0]-0&-0!-/{-/y-%p{Rz9z5w}w9v7odoYl}l|l5W4MkKPI.E[?m?h=S;Y;#:R8e5Z4i3V3*2g1h/1,O)u%C$B",
"guan": "-:L-58-1=-0l-*vphb@b?af`LXqW*SRJ+G*D(B2>w<v<W8G5Q0E)W(4'e$_$0#Y",
"kuang": "-:L-22-0V-,b-+V-+N-*v-&o}&xNwFm_dJcW^z^y]>RWQkP#L)?N>=0X.X.U",
"chuan": "-:K-7o-3G-2t-.K-$]}Tz3jFjDPMIRIDCbA?@i,H+<)7",
"chan": "-:J-91-2y-2R-1~-1/-/:-.k-.J-.*-*~-%$-$F-!n~P~@xBszr>q3l>kJkCkBjXh{hpgWdu^r^lXsX+W;VqVhU#S9RYJsJXDEB#=v:^967o7n71655d5B2V1I,#&v&u",
"lin": "-:I-9U-2g-0e-00-0/-)v-(l-%PxDlbk,gIgHc=bob)_=_6[MVGS?Q+PGN!FIEmED<U7j7H5h3819+u)S(I'+%o$Z#M#K",
"zhuo": "-:H-4t-.`-.<-+#-)X-%U-%T-!G}+y@v,tYeu[BZ,Y~V3UxU[StSHSESBSAS>Q>L#JuJfJ3J,IoGcDh@j=|=h<b<J7%6S5[4}4N3_3#1>.h)e)N",
"zhu": "-:G-:B-9]-7d-7G-7?-6Z-.&-,t-,n-#T-!z~4|=xpx3qVq!ploNnWnIl*i>[[[WT&StS;OqO1O0O.N>M#LzLFGWFmF,DhDbD^D0BH?&>Q;b7t7Z6s5x5>4V4A3y2^2@1+0x0?,K*K%B$J",
"ba": "-:F-8l-7`-1E-)^-)@-(b-&M-&I|_{[x>wCv0mumIj,fq]o]I]6]#[GZ)O+M'D,5?4R0+.m+@%N#/#!",
"dan": "-:D-8~-7{-7H-2r-2J-/V-,,-+G-*~-*{-'f-&2-%C-%B-$v-$F-!m-!b~[vTsjiofTfOfEb$aga>_q_P]4[VXuV@V?UjRiM/BeBSA*@)>r<?7.1I.@+#'O''%f%:$H#`#N!F",
"wei": "-:C-8{-7p-7e-7A-4s-4V-4Q-3~-2x-2$-*h-*b-*O-)O-'q-%q-%k-$s-$S-$@~.}o}m}S}7y1xJueu7u$snsUsHoqoQnxn_m9m2lHl7kskZk=jljTiniEi9gme=e:avaPaM`?_JYnYUXbX1VEV1S^PuO5L*JqIOINHkD@?g>c>C=n=$;S;N;0:N:08?837i6R6G5|4]4>4$2]2O2F1]0u02/$.r,[,P,I*~)h);'f&H%!$N#X#U",
"jing": "-:A-9C-9+-9'-5r-5%-3A-2O-1N-0K-0C-/9-.~-,y-,k-,[|i|g|cyIvQt:t8t+pojAh|fdfZeeeWb/___NUoT+S&S%Q:Q3PIP0KZJpEvBsBn@I@H>m>%=><87]6#,r,'(^(B(<%($u",
"li": "-:@-6_-5u-5k-5Z-3s-2&-1z-08-/Q-/=-.o-.G-.'-.%-,l-,&-*L-*I-*:-*.-*!-)|-)Z-)X-(z-(2-&U-&0-%g-$C~g~`~A~>|`yrxEu+tat#rjqYq,nAmkm5m.lzjag@b]bXbRbB`l^&]q]gWYU@U3TsTlSpP_P>P%O&NmN^MqMSLnLcLYLUK!JoJdJMG1ECDuDjD_D8D.C/C+?k?[?Z=Y=U=/:/8z7@6C6$5H0a0U/>/=/#.z,~,u*V*$)z(t(_'x'u'l'W%R%F$l#P#A!Y",
"pie": "-:>rVV]V[PHAD8>",
"fu": "-:=-9c-8[-8#-7z-73-5y-5m-5j-5U-46-3v-0d-/|-/J-.R-+h-(=-(6-'[-'S-&E-!s|<|!{]wvwWvVvRuru'tDt,sbr5qJq/pmp3oWmgmFi~ifi7hzh;fwfkeje?c{ct^w]l]J]&]%[Y[QZ+YfV7S}SLS)ORN+M]MGM)M'L1KWJ2IYIPHKA:>~>Y=W<w9c7T4S3d3/0.+2*t*!()&m&b&N&G&@#h!)",
"nai": "-::-6v-4iw:vhv)qxq(g6WVV{M$AC;<%`",
"wu": "-:9-:1-9D-97-8>-8%-6;-5|-4k-2k-2:-1q-.T-,|-,C-+y-+/-*V-(U-(T-(J-(?-(+-&)-%K-#v}4|^zLykxvvxv4u%tjthsurTownkn9n+lmkzk_j6hFgQfudbd`d@c'b[bZbKat_]^[]e]d]]Z4WGTTS7RoROQENvNtNoM&K#FLCVC=B3@[@Z@S?{>T>*=V;^:,874(3C322*1w/V/A+3*4*3(|(P')$h",
"tuo": "-:6-8X-77-6h-6.-'a-'G-%[-%$|Sz;v8sNrRmck'gzet]i]`[H[F[EZMYuV1NgN^MTM8ITI+GbFBF0AvA_@d?`?_?^<k.K",
"zhe": "-:6-9[-*K-&L-%9-$o-#T~h{Er^bp`6]H]'W_ViQGQFNgM=JWFwC%=m<E",
"ma": "-:5-9Q-3o-(r-&!-$%-#4-#3vUs1rWqNhR[cVzNQH2:2/&.c,4+5(x$[$Z",
"me": "-:5~t~j~UhR6I",
"yao": "-:5-:,-8;-6D-5@-4?-3_-2q-+m-)&-'7-$b-$2{}zBwUvGu_svs=pro3o,n/m,l,k#j~h^e;e.cqcRa%[oX2WrW@VdV$PkP'NGN#LuI;HBH9G5C!BA:B9J444&1|0f0(.E,;+r*D(~(l%S$%!t",
"zhi": "-:2-9Y-6f-5^-5C-4|-4d-44-3y-2,-/}-/0-.U-.+-,v-*e-*>-)C-(W-'v-'8|||{|T|:z~z{yFy@x$v%uNtsrGp&m7l0j+iridiahyh5h3ggf5eYeKe4e3dmdUcU`6`*_$^{^E]Y]F]E[u[HZtZpZ]Y{XvWpWVW2V{VvVtUKUJU>TjSMRwRgQ`Q/NOMyMcM2LqLhL6K~K7JjIuI]I*H+GHF_D|DnCAC6BiAJ@t@O@N?v?T?3>V>3<L<!9e9S9B8}8@7~6>4`1_/9,x,^(R'w'[&3%c%;%7${$z$k$E",
"zha": "-:0-48-.N-.=-*C-(w-'X-'?-&K-$j-$O-$Aw/pNd6]s[m[XZdX4WIW#O2M7M1M0LvLlI?H4Fv;X:67u5#4@2N/p&d%.!M!H",
"hu": "-:/-:'-9j-9F-5E-0Z-+U-+L-'h-'W-%n-%Z-$_-$4-$1-#>-#2~k}v|;x1x0x,uGt4sEr]r[oxm[iphxfxfgdFd*d)cGa{^V^6^4^3^/^.^,^']y]9[xWWW$SXRFR<OjN(L8I%I$GEGCCR@9@&?f?7=t<.<+;$9E99986Y5s483S2;1a.J,S)b)+']'['I",
"fa": "-:.-8!-6y-3Z-0R-)]|A{vy)uwm4fKL@FO?X?:=z5R*[)L%8#.#+",
"le": "-:,-9R-8r-,J-)2-#1d}]qH`G5@z??/b+A",
"yue": "-:,-1g-1e-1`-/P-&j-&@-%A-!DvAqrn1m^jec,bubS^]]g]8Y_U@OpOoOWN8LcH`G5FTDj?'5e0J+*",
"lao": "-:,-6~-3!-,j-,i-,>-'$-&N-%z-#p-!}uSr`ljk/cY_f_eYtVZO:L=G5F!=O='8%7a3{/^./*<$f#c",
"yin": "-:+-7}-6^-0t-0;-*c-(j-(V-%o-$d-!T-!+~l~+~$}`}$|&{w{YzXz:w_u=t0t&p:n}lnlLl<jdg^g>fya@`i`B_u_t_0SMO[L:KKEkE@E1DKCwC_BYBGA5>l>d>U<5;~:}:i9}9V6^6]4).],|,j*I(U$7#k#_#:",
"ping": "-:*-5i-0]-/z-/s-'u|Qz1u/ngnZmTi[iJi4heh&`0_zMfEU?6>6=A<D3)*v'F';&_",
"pang": "-:)-*B-#ww}r|o2h9h*ere<S2@<?y9p4i",
"guai": "-:&-)_wVcuc>[KMbLw",
"sheng": "-:%-:$-4H-.X-.Q-,?-+0-(9}=x{x7u*k}_VS[RGQJQIOeMuIyG~E{BzBF?%;k;@:q7G2u/M.N*h)i&y&s&`!'",
"hao": "-:!-65-3k-2)-)6-'j-&_-#k-!t-!Y-!#~xxRvacOblRDR'QBPePaPWP7J!A~Ao=]<Q9j9U7S6c5N5@,/,)+}!y!q!h!f!c!_",
"mie": "-9}-),-':-&Hq7hk^uWeDr9m64504!",
"nie": "-9}-%*-#e-!O~m~D~5~2}#q'q&mFkTjujLivZ&YVY5X[WCVsT|T<MVG@DW=@:Z%+",
"xi": "-9y-6&-5l-3i-3#-1B-0,-+?-+*-*n-*R-(P-'x-'>-'6-&/-%]-$X-$:-!p-![~T~8y?xWwnw'tgs;rCr@nsncn`nRnHkgk9jpj`jZiqiOeceOe9djd_dEcscgcZcKc/bqbeb8ay`r`p_s_r^V^6^4^3]_]O]HW%R:QKQ9Q6PEOzNON)MdLZKSIdIGG{GUFkFRE|EjCzCuCmCMCJA4@e>X>S=f<[;i:+9h9)8p8/8,7N3e3R3K343&2Y2+2)2%1q1P1O1N0}0P/E/?/*.{.t.#+p*r)|(#'h$1!k",
"xiang": "-9x-9,-9(-6}-3*-1}-,4-,$-*0-(x-&r-%L~Ww~uXk5j)h7gqe'abQXP5LWH^F1DH;!8:*E'g'T",
"shu": "-9s-61-5g-55-54-1|-1F-)Y-'3yNyGu[tGq4oNoCnWnIg}gxdWchcgcIbt^X].Z.Z#Y>WBU9T'S|PvPtP*OhO1O0O.N[NBMtL`JtFuFYEpBuBKAaA`?c<O8[8H7m7Z6m5q3Q1k)f(i('%h%e%d#O",
"dou": "-9r-61-1T-1P-(~-(N-&&-%|])S]SPN&J}EwAm=d;n6<.$$v",
"nang": "-9o-1s~d~;~1_QWwU{TkOwD+<n5b5;",
"jia": "-9n-8<-7Q-6w-4X-3X-2]-,}-)S-&?-&4-#F|}{MwIwDsrs,pvpImei,e[eBd^cA^J^G]L[d[Z[,ZWZ8UhSYSVM`M_K/ILHNH:G^ENAz?H>&=L/3,E,B*n*d&f%0$8",
"mao": "-9k-1,-1(-0|-0z-*dzLwksLmOkzi?a=_?^(S/QvPrN4M@I'B!AcAW?9;F/Y/#,J)E#$",
"mai": "-9i-7I-,{-,*-*}-#${Kx5",
"luan": "-9h-9Y-9V-*^}C}Bvmu0qXq:q$m)jOZ[TvOuL2D69K5J59#4#3",
"ru": "-9f-6K-2C-1K-(N-#{-!$vkv[s7qyq)jciX]kZgUTP+NzL(E0@W>7;Q9u6b+^",
"xue": "-9e-.x-(Q-!9|Bxbq>q+mmmMjzf=ckT/SlKvFc?!>u9j7>5y1&.B%L%<",
"sha": "-9b-4c-3?-2H-/,-.t-*+-%t-%^-%H-%4-$R-$0iCgkZEZDW<N{NrK`H?FlCaC3BDAl?w6{2P,v$g",
"na": "-9a-1*-*|-(R-(8-&T-#_v=tx]0[LZxZgYWWL",
"qian": "-9`-9Z-9W-8T-8B-7l-7(-5q-4w-4^-3g-31-2<-/r-/[-.u-+4-)}-&4-#u}/}.zaySy4xZvgt7seqtq`q5n'n!k{kVdObgbLaN`f`<]Z]N[J[1ZCYLY=Y!X7WSVgVbVUU,U+U)O_NwJ_IbH3GiGPF%E7DzD'C~CeCZC7@]@M>$<%81806O5S4B2Z/J/B/2+%!l",
"suo": "-9_-9=-3]-&8-%x-$&-#j-#gu&s(as^$ZEZDW:PjKaJz:E9y+|)w)v)!(]",
"gan": "-9Z-8S-7J-5&-0=-/u-'c|Ro'o%o#o!hfh_dqa5U~T]T6R_NuMDKLH6FNEd@??;<:8f7_7/55+;*w'#%?!T",
"gui": "-9X-6q-3{-/(-/&-.7-.5-+Q-+J-+I-+F-*%}3{zv2u;s?rhrIp|k$jpj`iLhLh,gmf;cMVxVVTiThRCQ2O%M9L.KeIeI`GTGAG<G;FWEQE)DXDQC*@v;;:H4t,Q*A(r(D'q#X#0!|",
"jue": "-9S-50-3f-0X-/R-.?-+@-*,-)X-(Q-!a-!R-!9~I{PxbvAqRqQnLnJlPl@k$j}fmf>_k_Z^b]7Z`Y~Y9V^V;TnSgKTF7F6D[CvAG@:?!5P2|1d1>0S0G000/+z+M+)+'*](n(G%L$3",
"liao": "-9R-2|-!rrLovomo<o5o4nKkLk+k*g]gG`/_^W9VaV9S{SZPLO~FxFA8Q8%4f1;0V0R+q(H%[#g",
"er": "-9L-6v-6T-1r-1a-1_-/1-'B-%hoHoGoFmAg$f~NiLCLBK6FaAt>[>90%*F",
"chu": "-9K-5N-3d-3R-2K-2!-0$-/m-/Y-/I-,t-*)-!o{$x!s>n5hjgXcj`g_SWxW$VoTNS=NEIjI&HoHQF|E}EsESE#DsDdCzCG?@9r9q6x4N/+*+(,&;",
"kui": "-9I-3{-/4-+I-+F-$U-$;-!xwow4s/rBo*mQjVjHb]a.a,_y^BXgQdPyI2I0E&BV:S7x2l.q!/",
"yun": "-9G-7r-3q-1p-+}-+x-(0-&^-$^}xwEvsvEqOb}aca4a)`c]2[yRNQTP}MwHWF@BmBaA&A%@0=3=!:!7W2h2:202(2$1b.Y+(&P",
"sui": "-9A-5#-&F-#U{3wytvr1nwn4kpRqEWC1C0Ab=H9[7+6z5}2C1g0~(/'p",
"gen": "-9@-9?-&pX=X'L7",
"xie": "-9=-9!-7[-4J-4A-4/-39-1{-0s-0c-,w-,+-+'-+!-*k-*Z-)7-$(-!C~e{ry_wrw8w1u)sOq]opnenVnNm$jrgJeFcT`z`p_BZ~ZWZ8X9WnWMV*UiUFT}SWRePXMZLIK@JwI,HOH?H/FlC]?K>p>A;P9h7B695{5!4Q4P3b3E2,0w0s0O,C+k+e)8",
"zhai": "-9;-6B-4f-4*-3E-*K-*C-&c~zw{p{os[u[2YxW/UwU>SiSfH#EL#t",
"tou": "-98-4'-4&{$wNv'sqs@aK]*T0SQ",
"wang": "-97-8v-87-1JvYo7o1o0o/eoeiefe[dldJa}]>RTQ(OEODO=N1JD@J6;+E",
"kang": "-96-8)-+X}|rmkKg|dG`8]f](G=8_4d.a",
"da": "-95-.N-+f-'f-'R-&m-#~-!J{hxrw[v*d'ag_q]nWZVJ@f?}:<4Y0p&@&4$#",
"jiao": "-94-6n-6D-2q-2j-2I-.B-.6-,6-)B-(<-$H-#M-#K-#?-#(-!^-!=~IuUu1r=r6qcm+m(k1k%k!e.e+cJbl_~_i_KZjZTZ5X&W9VlVCV(ToTJT?T,SwSuSgSSQbPgP2LOIpG!>!:l:k9Y8w8<7b5[5C443,2b1>1*.9+l*X(5#e!v!^!V",
"hai": "-93-'V-'0-$#-#h~ey]vOq;pL[!A3=N3[,C",
"heng": "-90-&B-%QuJcXcLbaVKL/FiF&<|42*B",
"peng": "-90-5,-3J-.F-+o-!~zgyqyYfUe|cyc+`%[tZ?Z6YkXpWvW4OTKCJLIlIWGGFn?6<R<D8e8]7Y3Z1h$a!u",
"mu": "-9/-8H-/~-,=|Ey9usuSu%m<i&i!`[`Z[TPVPUO7O'I(FrFqB1AwApAX@#4h/a/_/X/L.S&U&Q&E&:&9&(",
"ting": "-9*-68-60-4C-*M-*7-(]}>t}sxk~hVhJh)gBg?g;dzZ<K]KHH~H(@u=6;]6u453`3^*.&^&[",
"qin": "-9%-64-1^-,8-(g-(f-&#-#f-!Q|w{Fshs.p.p(o~oyogkmkVk2k)hOgbdO`C_G_F]N]5YlX,X$V/UlS@R=J|E`Cg@3:$7'6(*J)R)M#l",
"qing": "-9%-4b-3<-2(-0A-.b-,O-*S-%1}V{1wfp7hQgwgeb4`9YLUpUoQ5PsJ'G/EME/DcBnBFA7A.A(<';w;B916X&x",
"bo": "-9$-8Q-7`-73-6,-2v-2f-.e-.]-,Y-*y-*w-&M-%#-!h~oxbq*k3ibeu`s^|[3ZJWyV=V5UgUfMFM'KYHKEVEUDFBj?E?,=e;};W:L2//l.?,9*q'`'^#2#1!Y!8",
"lian": "-8}-34-.;-+E-+D-#V-!XzUwAvusMrKr/iegjd&cS`F_{^fW.T=SrJhI#GiGSE7DfCHBlBP=%;6:C8j8A777$6p5p5l4<2f1y0z)x)3(X",
"duo": "-8|-8X-02-/2-/$-.c-.V-'`-&y-&e-$O~}~S{|{{z}z]xzxywiwhwHvtvlsNo+lOh1ae_oZrZqYJTET>T8T.O)O(NfN^MrMTMSM&KuIRAh?`?^&D$i",
"men": "-8{-8G-53d8bcbCaz_9_&]VYgS^PZIh@>3=1,+((W",
"ren": "-8z-8y-8s-8U-8F-88-/d-/cx;vSu`n:n2d|dwdv]XO,NiLPLMK6J7/]",
"shen": "-8t-7V-6i-6/-5d-1Q-)l-)k-)j-)i-(V-'i-&}z^u|u>ttsys.qmp_pHorn7lui(fp`t`b]b]4WPTFR4P9P3M:J7J(IHHRA9@+=D<0</;f;e9?671?*g&~&w&q&e$G#{",
"ze": "-8p-6B-4*-/M-.{-'X-%d-%2-%.-#9rcl:iAi#hU[~[2Z$UwRKR7G)C.@q?n?A>n:(9U7C5[!e",
"jin": "-8o-8j-8d-7}-6<-35-2^-2=-01-,y-,k-,[-*X-*'-%o-!F~y{Fzkz6y<xPvFt7rfr9q~p(nyj'j!gbb+`CWfSOQaQ_N7HEG8CTB[>E=q=M:I9$6~6g3h2M0i*Y)y)K(z(d(@(*",
"pu": "-8n-3$-+k-!S}^}O}<{BzPy(]p[rY{V0UvTeTdQ;PiPPP&O*M,FZE_AS=`:17k6T614r3a+v(C$m",
"reng": "-8g]m",
"zong": "-8f-5:-4y-43-3KzFpikxkrkNeJcdaraVY^XXX(W&Q|O>MxJQIKG28L8D8#3+1n1c0{,b,R%E#w",
"fo": "-8e-8;-73|IJl",
"lun": "-8c-7i-6S-4u}l}Y{.t*lSlRb9[{YMJa?j<6:3",
"cang": "-8a-89-7h-5;-3e-07-+OkeD<?i9n6J,**{(p",
"zi": "-8`-4m-17-.h-)%-(W-'t-'r-';-%@-#r-#cupuoudu9qTqNqMqHq1q.l;k[c;NnLKK8InH=Ez?s>W<];o:Y9g9_8a7;/G+J(!&Y&7",
"zai": "-8`-3V-2G-1!-&v}8pOl.]j]>L3>f;>:.4|4{3~&Y&7",
"ta": "-8^-6E-3^-#~-!&~Kz$yyy6vep}lc[HZXW`V&HCG}EqA[?}<c:<9w8^7(6w/`.3+d+V",
"xian": "-8Z-8Y-7b-7,-30-2m-2d-2b-1i-0O-)o-'c-'E-'*-&O-&2-%6-#u-#:{`{!ycxXv`v&uKu>u.t`tZs~r~rOrNr9q`pUo;o:nBmylxlhjthhgCfhf+dI_a_X_R_'ZPYQXsWnWiVhVXVSU6U'QlQNPKNFMhGiFFEtDRC~ArA@>S=E=7:]:C7Q6*574*0l.=,s,G+f+c+U+O*|*s*T*,'3$c#b#Z",
"cha": "-8X-6Q-4D-/,-.t-)e-$A-$$~s{yvbu?o|m}kqj3]a]OZ7Z.XZX5WJW1NsM0LvK?I?GjEB@k,<%s",
"hong": "-8W-)w-).-(X-(K-(;-&{-%}-$)~i{kvXu6pqpjn=j[fvfBa*``XeVNQ[@E?y?<>@=b=S;J;B:R8J7U5(3|31+>+4'T",
"tong": "-8V-7/-6R-4Z-2h-,/-(}-&|-#Z}o|/mNm>m3h:f(c%`P`)Z1Q]P<O<K|KUG+F+AV=P7l4C4#18.~.0+s%%$s",
"dai": "-8P-6G-3W-)g-(B-(4|3{(w[m`ikiViMiFg[edd!_/^1PAMJJCC)ByB+5c,2*y)?'!",
"ling": "-8O-7X-0?-/D-)G-(#}h|>wwuqtKqdmdmVlQjhekY<R)OQMRJeJ5DND)?/<77=5'4O0v0=.I*z){'H!z",
"chao": "-8M-8D-.B-.6-,6-(S-!yjQj?j>ffd9]<VlQiOBGFG!C{9+7z4g303#22/v",
"chang": "-8L-8J-7j-5D-5+-5!-3|-2~-26-1b-*P-)~-%i-#8~v}%z=yZtWrdo=iDgReLd>bDaxT:RBQtPcIiAT<T<P2t,`+6)^)4(h'B&z&R%w",
"sa": "-8K-+2W<VPU:Dw?'>X7s5L",
"fan": "-8E-0)-0(-0'-,1-+R-)a-!hy%v_tDr;r:iwhwdi_h]l[ARvRtNpMLKXJrJAG,FD@w@g?4955t5_3n2E15.l.[(A&O&/&,!.",
"miao": "-8D-,m-)v-$?vDscrPh>gtgSX^NP<#;A+K",
"yang": "-8C-7<-6{-3[-15-,f-,@-*g-'Z|J{xwTukswmrl6l3e`d4cEaA`m^}]T[lXRU<T*RVR2PmNRMHL9I7HvG`FpB|A=A2A'>z>`8N6A4y4D4.2B+6*O)4%Q$|$@#F",
"ang": "-8C-*gn.RLQoN0!5",
"wo": "-8?-4s-4L-*l-(/-'&-%q-$atCtBsfi8^TZYYbYSXKV#SRN8I>@1=#<m<h;V;U7!6`3.,N'|",
"jian": "-8=-6J-5W-5P-4g-4:-3g-2s-2i-2L-14-0L-0<-.q-._-.W-.P-.4-.3-./-.,~6~'|bzmzaz2xovfuRuQp4oDiHhcg8fNfHeDaq^Z^Q^<^9[8Z>YqXmXjX7WmV!UGU'R{PpO_MoM(LEK3JgIfIJICI)HEG]FhFCEKE2DLC&BMBLA]>a>$<z;l;a;&;%:Q8T7P6H625k5f5a2a1l1Q/u/Q/2,g+%*W)<)6!2",
"fen": "-86-3}-2n-/a-(`|v|q|]xuw7vpv;n&ithog,dDa0_gR>OYOSN.K&J*J)F)A>@66}5i4v391=16+{+.",
"bin": "-86-3S-2EpAe}W?UNShJnImGXE:B^BO@r9I6q6Q6M6,+.(j((",
"di": "-85-7@-5a-4F-3:-*D-'}-&t-&$-%R-%&-#O-!(}0|h|d|@{Q{L{8zMyFy;x|w?tqsmrimUkIjoi`hBg:fofjeld#`7]g[g[=YFX]XGW2TLT!R[NfN(MrM3KAK:JCHiG7AI=g<};T9f9=3F/K.V+=*5'1%c##",
"fang": "-84-4}-+^|r{Qzcv5er^%TZS:S(RER6N/@<<M/Z'P",
"pei": "-81-7$-5o-'lzyvZvCuCtOs_k7iid7[eU5S5S,M'LyJUAj?u=F<@.`*=)d",
"diao": "-8/-0@-/f-/G-)1-)!w$njfzfYe~]gYHI|@m)U#p!?",
"dun": "-8.-(a-!2}}|sy!x~x}hNdPb2_mVYV.T2HnF>@'8*4c1@/!+m",
"wen": "-8,-/X-(M-(C-(&-%Jy`vNf)dfde]:X.WRSmQsKNHWH)C$B`@>;z;R:*4s+1*8(}(&$;$.",
"xin": "-8*-7f-5d-5G-!3-!0~%v<r,qFgTe$e#dHawa>SCR9N@N%D$C^4a3$",
"ai": "-8&-2W-09-)h-'!-&q-&5-%Y-$'-#]-#E-!;{SzIyfxUtgtUrxr'kaa9_7_,ZNYaT&T$QqP^P.CxClB.:%9t6U3.03(k(;#]!s!j!]",
"xiu": "-7~-5c-5V-''-$/~|p@mfmPh2N~G09~9F8I4+*P*#(N",
"xu": "-7~-7Y-6Y-6!-49-0x-,F-,E-*Y-)T-)+-'p-$e-$Q-#7-!o-!W}7{Wy,x+v&uxspsArFhHeXckcKc:b(`g^YY4XMTKT@RbRZR!QcP{O^OOLGI9GfC|CtCiCOCKBw@5@4>?=t<>;+;):P9r998W8B433W2H0m+t*N*?&;%T",
"tang": "-7y-5+-4M-3l-3U-1v-.2-&.-${-#.|XzpyukdilaA^cW^V~OsJFH%F{F<@P<T:h:G8n5D3i23100A(u",
"huo": "-7v-6r-5T-.Y-.1-(p-'W-'D-$e-!%~V~Fw^vouWf.f,b'^OZ/Y4UPU4RkO|E9@%>/:f8U6y6Y6+525127+_#@",
"hui": "-7u-7#-2u-1{-+H-+.-'/-&j-$[-#=-!f-!U-!D~p~,~&}u}Fz]xztEsgr7q]onn?n>i*gmg7g5f6f4f3e,e*cDcCc<c(bfau`H_x^sZ|ZQX>VEQSQCP|PQO]KeIOI3GRF4EiEZEQDqBVB>B=B7@n?D>h>g=y;+:S9X897x796y6:5,5)3t3q3k2h0y0q+h*9)G(=(2$~$)",
"kuai": "-7u-6@-2M-/p-&f-!8}:|ez&y'jEgMdXUkRrO]Cq=y79.*+g(2",
"cui": "-7s-5?-3N-04-%I-%>y8lYlWkhdSbE`TV|IxH*GHAg<48P6a331g)p(b%I$L!d",
"che": "-7n-5`-4I-,%-'y-&*|?vln|nGene/]QY.XcVsV>V<7`3r3b3E0C",
"chen": "-7h-4_-3e-2'-#|~ZyWyAw]pGoBdRa>[aY]THS~QAP$L[K_K'J(HUG3D]@+@*2n05)l%P$?$$",
"xun": "-7g-6O-4.-,Q-,A-,)-,(-+5-'3-!k-!P~u|y{=yixYxAw#uHqKq9o`oOmEj@j&j#g=ebe>c]`uXUTfRcP(NqL_KbF`BvB@Au@Y>5=r=l8+7y6V583O2h1{1D130j0Y0Q.5+b*H(L&T",
"chi": "-7c-7M-6b-6P-5E-3@-,W-,K-+_-*]-)<-)3-))-(:-&W-$z-$I-#l-!g-!=|@|)yLw/vBs6n|fsf$eweve6cBc9`X`7_|_2]`[f[!ZvZ/W+TxTCSNNcMPM3G1ChC6C4BX@T?Y;:8g4~4;3U1K.O'A$Y$U$E$2#G",
"xuan": "-7b-2N-/.-)o-)'-'(-$Y-$Mz)wsv&sWrqr.p]f[cobMaQaI_I^nX_SyS'RAR,QeQ$Q#PNK@HxHwEf?#;I8d4M3x2e+L*s*)*&)B(Z'~%/#E#<",
"nu": "-7a-3r-,svjq?ilfed1Wo",
"bai": "-7`-6z-(1-&:hJ[?[>ZwYeX}WAUfUCTAMFLN,n'D#*#)",
"gu": "-7_-3T-2e-0F-)I-'s-'N-&<-&;-%G-#y-#@}gzfx#uhrUq@o&lXl=j5j4d*`~]_]9TSNaM.K<JIHFGzF$D{B<@K?R?=<X6P6./P/@$Q!L!G",
"ni": "-7^-7]-7.-4v-2>-27-+8-(%|Nzsznv/v)t<rkqkq0nznbnEcvb5`{_,]0[i[<Y5UQS$QuQ7PSMPJ&E6@y?)<=9x6o.F,l%b",
"ban": "-7Z-.!-+,|z|Yutn0d2]R]M[OW]W[T[ScSbRJOSN;MM:c/x*>'Y'R$*#[",
"zhou": "-7W-6M-2X-0{-'|-'z-'Q-'5-%X-$i-!G~{v.t/pgjCiceIY%QnQLQ<I|>8<S494'*S'9%W!R!I",
"qu": "-7U-7E-7+-/H-,q-+S-+=-+7-)t-)s-)W-'e|Vt^nnm{m]k?e%`&^0[^SzSIOnOOD=D4CzAL>)<3;[5U3G2o0D(K(8#9",
"ci": "-7T-7B-6m-47-17-/+-/'-'t-'r-%@|*q}j3h<`hN|MILHD&C??56Z*p*k'E'6%=!{",
"beng": "-7S-#w-#+{R{#zgyXw!lBkYX0>v)d)[',&k&j$a",
"ga": "-7Q-'M-#A-#/-!4wIwDoEo>o.RaOM+C",
"dian": "-7K-7:-3m-18-**~M|P{lyBxfw6v~t6t!kXj]jNjMh@ao^!YOTrTWT9I_GqG_FPF5B?<a9k764?*u)s&c&`%1$W$$#L#=#6",
"tian": "-7K-56-1>-(7-%`ylybwYvit=nodgc2b:Y#WPQ?LSB{?U<A<$;2)g(q(.&}&|&h&`&L&F$W",
"bi": "-7D-78-73-5F-45-+c-(h-']-&k-%?-#Y|jzVxgwLvnv:u}twt1r3qonrlFi|huhthPgLg'fRfQfJeme!crciaJaF[YT;SqS+OXO!MUM!K;I<HPB)B(B'B$A|Af?x?C:u9Z9D7q6e2`1p.%+y+x*`(:&W&V&G&5%^%H$T$S#'!o",
"zhao": "-7C-1k-)A-%X-%T-!y|#v+j*]B[@S!Q}P8OBM{J,E*?S6S4T2G0r0:09.7(m!F",
"shao": "-7C-,p-+~-*a-)A-&Vu,oIf^Z'R|NhM?K(7v3m2s17*m",
"zuo": "-7>-72-66-4E-'L-&,-#!|lmtmsj;h0d6YTV4R%M7M&)e",
"ti": "-7;-5N-5'-4R-/!-.n-*;-%H-$y-$3~w~rw?smsPnmnYnVl2foeCe6bnbjb#b!aU^)ZHY*X]XGU>OaONL$JxJCCQB]=0;T8O*5)A'r",
"zhan": "-7:-4>-2y-*s-!IrQnamRl>l)kCkBk.j|d+b0^M^?^5W|SJSGS0RsMjLiL<KmJ?HLFCDEAyAQAO?Q:|)q!F!4!$",
"he": "-79-,_-)$-(v-(/-'o-'Y-'W-'&-&R-%<-$Y-$W-$1-#d-!c-!!|,xVuyp6mJb&[jTROiOZMNL8I@CsA^?]<i;M7R3P2m2'2&0k0e.8,C#^!W!<!:!6!1!,",
"she": "-76-*E-*9w/v|oVfP`_`;^h]'ZyZbYjX:WEWCT|EEDACJ?@=<<f;s9Q5Y,W&*&)&$",
"die": "-74-)M-'>-&t-%H-$j{szSmDl/kIi3c}cPa`^IZbX:QwP!M2HsGUBcAK?I0*/}/n'_&#%q%j%i",
"gou": "-71-3p-0y-+z-)H-'p-%W|C{uwdwcuTs0mnfM[CX%VcN6M^Gm?q:925.C*o%2",
"kou": "-71-0f-.C-,g-)J-)DpCp8fId3]^[|VpTV9@",
"ning": "-70-6>-4a-29-0.-'H-!)r%q!p2p)p'p!ot[4UMM5F/E5?17J6k.<+a&i",
"yong": "-7*-5t-3M-,U-,T-'T-$t-$+-!:{Oz!yCxcrlkUg{gAe{ceb{bzapaC`w`n`:[6XTU}M4LaGQ@}>x=9:p947:5T/{/i&p&l%)#S#8",
"wa": "-7)-/n-,e-(/-'P-'&-&x-%h-%A-#y-#nu5tbt3sGnCije[ZaWUTq>.:;8h'V'D'>&A",
"ka": "-7&-*r-'O-'M-'4-$u{g",
"bao": "-7%-5h-21-/>-.e-.]-,!-+{-+s~ozPzOz@sBqBpcpMp$ohoee&d:[w[kPPP1P&M]614J2<0_.u.h*G",
"huai": "-7#-',|mx^xIe_dC_:^oGhDX<25z",
"ming": "-6x-0g-06-(|-'guEs&`aXxR@PhOFH<>0:7,8",
"hen": "-6s-&p-!3eac6[0.:$y",
"quan": "-6p-1H-/.-.#-,5-,#-%%}X}Q{4w5u:t;q[m?jRf`c0b_b%['Y`WKNxJ:I[H_GLFjD>?F>F:a5841/I/H/7.o.n.m.O)2&I%'",
"tiao": "-6o-%Xr#pWmjmWh4cRZfSQRdQjOLO'NYK.Fo",
"xing": "-6j-5.-1<-/U-&g|0{auft{t5qljAh`f*cxb>aZUYR/P4Nl>Z<u9d2d.N,L)@![",
"kan": "-6g-4G-0r-/r-/]-,D|n{!zGyDlkl)k{b7^D]ELRG]E2CeCc",
"lai": "-6d-5Y-5<-%_u!t2lil_h$eSeHU2NUJiJ0DT=&<)6r5v5r,i)k%R#P#J#?",
"kua": "-6a-'.{iwJuGcGZiQcI:",
"gong": "-6`-6N-1D-1?-,~-+g-+d-)w-%}-$)yepTpQj<j8i2g4f{c1a*``[)[(T^N`L?@V7U1u*R",
"mi": "-6[-0v-0n-0b-'9-#'yzqZpmpDp9m6i:i.hrfifafAcb^dVnU_TyTON2HIG$E6E+@L?{?Z?C>G<9;H9<8o6o6l5n5G1z0B,a+T'h",
"an": "-6W-5J-4<-2D-*P-*J-')-%e-$x{b{&z_sYpwn@n8mXm:hXg~ZnXNQ.PnL'L&A1>M.g*w$V",
"lu": "-6V-33-1C-.H-,N-,<-*q-*o-!N~q~_};|G|5yVyUxMtVmChGgZgFf9f8^7XzW)V)UzUAU/O{MpLeJ#G&FyEuDvDaARAM>s<K<;;p9:9'8.7L6@6)4p1m0T+W+H)Y(Q()'m&n!%",
"mou": "-6U-,c-)x-($-&azLcVTTMEKs/a",
"cun": "-6J-/Z-(~x'qLocdn[%Nj7^!O",
"lv": "-6F-63-4#-38-23-,'-*x-(t-(F-&G|+nTnSnPl(e^`A`5ZcZ*YwS.K*HTDsDoAYA)9M6D3A0]+I",
"zhen": "-6C-67-4)},ygybuMs*p5ndiUiRi<fc[nZlZGXWWOSTR*OJN$MhLVKjK_IHHuHLHAG_FVBb=~:y:$8$72,+*_*^({(q'8&Z&<%9",
"ce": "-6B-4*-1%-1#-*=-*2wZgrc!aY_jZ}TQLl={;O&8",
"chai": "-6?-2@-$h~?j3[UL}.i$'",
"nong": "-6=-2S-0p-&b-!Eg.ZmZAElDG=s7#0o#W",
"hou": "-6:-5*-*8-({-(L-(K-'p-$l|%zQi=e]>b._,A$C",
"jiong": "-69-3'-1.-1$-0}}z|K{;]~]}?M=J7e4t4I3c2T2S1W1.",
"tui": "-6.-6(-3&y'tmo$ftfodrY(F>24",
"nan": "-6+-*|-$r~'~#v=tzstrb^e[sXfPqN*M6H}:d29&a&?",
"xiao": "-6*-5v-5R-3a-.x-,d-'j-'1-&l-&P-$}-$1-#D-#?-#&-!|-!v~c~J~CuUtHqGpPpJoKlGh/fFcJ_iX;V:TPT/SoSnQVQ'P;MiMaLOK+DOCYCLBAB4>B===8<Z8E6!5+5*4,3L2&1L,o+r+o$t$o!i!b",
"bian": "-6)-+9-*u-)UwzmKg1eAdVaX^#]=XSR#@A@@4Z26/o,@+`+,':%8",
"pian": "-6)-4P}ysI^#I=Ht/y/0,@+`(j((",
"cu": "-6%-+$-!csFegd(_YEsB},X$I#z!H",
"e": "-6$-4K-2k-+j-*T-*N-(_-(E-(*-'A-')-&X-!j-!A}s{nzhzIzCv$uzuBtotUtTtSn]n)mGm'm&l+gnc'bOata?^+]DWsWdWNUbTMM}DZCEC(>M=5;9*7)`$V$O!r",
"guang": "-5~-2}-1h-'@{~uIhXhTgOZsVKL+>28)5/4b4_4^3s.d+Y*U",
"ku": "-5}-/3-&Q-$6~R}PzrhDh+gN]dZiZ5OPMgL!I%3N.>$9",
"jun": "-5{-2T-0q-(n-(G|u{NuHollq_;Z3U5TzQPKMJH@F=l6V3G1B*1&'!Q!K!J",
"zu": "-5w-5?-+1-+$-&S-%rlYlAd(S#M1Ix0'*e",
"hun": "-5s-4o}_t9t'o9dMaz`oYDR?Q~JYJRBf=u<d<(;=:t:`9{3O36*9)G",
"su": "-5n-3x-2c-$*~9}/{3y}y|wjs#p@a(a'_lVnKKJ2H;G(F~F8DYBo?2>>=4:&9z828&+F*L(F&r",
"lia": "-5[-5>",
"pai": "-5Q-&sg9eP[NY?JU?p>+;j;8/t.w,q",
"biao": "-5O-36-2/yJtIryi%f!VfNhLjFzEG<.9C66511o0c,k#|",
"fei": "-5M-.m-+M-*4-(i-%8w%vZt@t?nXh8gpgPbH]xSdQxQ%P:OPNLLxJVH5FOF)Di?W<C;x6K.`.H,p%3$]#a",
"bei": "-5I-57-4B-4%-3b-37-,Y-+a-+%-(1-&:w|qIh#bdbGajaR`$X3RMNLNKLyK^K5JUJSIq3:/S/).R,y*/)T!@",
"dao": "-52-/g-/e-/6-)E-#K-!5xSo_oLmvlvk;k:jiiKhld{b=YoYcWqUZO'JkIaGZEIE?AA3;0g'd!+!&",
"tan": "-51-3^-3/-2R-)4-%$-#P-#I-!n-!m|o|a|U{'xxxsxaxKtAfTfOfEdtcfb$_p_W_OY/W=UDTuR5PJP=HYF5EnCUAk:w9H7|7{7.5E4K1G(3$}$^#~#7",
"chui": "-5/-(O}T|9{Va|Y[WcKtJ6ItGl4o",
"kong": "-4{{+qPlfcNb;Y)Iv<n",
"juan": "-4z-,h-,P-,0-*[-((}X}Q{Iw<w5uVtut;jpj`j%iYf`byb%`4Z%Y`OGL%K@J:I[=2<^3M.!+j'C",
"luo": "-4r-1y-.|-'4-%(~<~/o^mHZ*YbW(U@U3U/T{TlOvI^D9>q>O>N;y8^6F3{/(,T+P*M#y#5!Y",
"song": "-4q-3I-0D-)'u8pulDk^kOgydYdAb`a3a$`D_MZ#XXW0N<N:MmM>K%J`HyEF<B9!6v",
"leng": "-4p-0T-%uzdz,lQa1J5I!",
"ben": "-4l-$E|q|pwSwPw.w(YXV8O3LQKXI4?B;|8]4v/8&=",
"cai": "-4h-4,-%N{%tLp?f#]t]qY0NkJZA},Y",
"ying": "-4`-3.-%1-$>-#*-!K~Oz%x#s{sXs9s%quqqq_q%kcj[jWhCgDexdha/_AVwV_U0U&R]R.Q:PwO?MHKpK]J{HvHdFbDMDI=_;E:U:K9d9O8K8F6j6i6N6=6&5~5o5j5M5A2w2r2_1x1)0b*:)*(y(S'i'5'%#j#;!B!;",
"ruan": "-4[zJxQsiVWOUE0):'}",
"chun": "-4Y-&7sda^RPR(PlOONDIBGrFQE(=T<,:[9N8u/6)C",
"ruo": "-4S-)[sskPf]Z:YUI8;y3'0^",
"dang": "-4M-2P-1V-/k-!1}*{fx]swpkl6kdf:aAZUUsTpKiEYD5@|8:7E5D*;(J(6'?%}",
"huang": "-4@-1L-/w-$PzDz.xtw&s[pEl9jBi0e@clcQa_a#`dXTQgQfPBOEHbH7D{@9:x9i8)4:2c2.1J0X+w)((E#i!}!g!Z",
"duan": "-4+-.Uz+s`SFS<IMBIB62j)0",
"ou": "-4(-+=-+7-(q-(K-(3-#;yTd?`EDpC}CSBJB8?l8s1Y'M'0",
"zan": "-4$-2.-1x-1o-'2-$c-!e~:p>[$XOVUU8U*TwR1Q&PYKrEy6E5K't'k'c",
"za": "-4$-+Z-'b-'X-'2-$c-!c~B~:~5i}]s[$NyKr?r?a",
"lou": "-4#-38-.}-$7-#ByMu4tRo{n[kjkEg_`5X)W'HaG#:O9&8~1i'2$5#o#n",
"sou": "-3z-0J-)Q-)N-#z-#R-#QgsghZ#YvWlW>W0V:U`UBJ/DgCn:#,5#s",
"yuan": "-3u-1n-1)-0h-.z-*3-*1-)y-(0-&^-$Y-!<}{}t}Z}R}N}M}D{t{_yawlv6v(sSs:s)qhpep<f[cwbyb,`qXoX8NNJ=HxH>H0ErDk@/<m<*;{;v;r;g:e:F:D5]04,M,6)/",
"rong": "-3t-3`-0u|ts8s'qzp~pFlwkokcjj^WX#WwOyLmHGH.H&GQAsAU9|6%3T1v0b.2)#",
"jiang": "-3p-2a-,7-+Y-+Wz/xew~w+vvvru]oToSkMfrfWfVfSfCVyVeKdGDEoDeBQ@U>P>#;L9A8M.|,&&B%y%n%m",
"bang": "-3n{^ypiNi5h~hme|Z?YrWvS2KEJTJSH@=j/m++",
"shan": "-3h-3)-2R-/F-/<-.a-.E-*~-$q-$F-#H}'{Gy+y*ulubr2nDj|i(f+]zZ;Y3XuXsWaVhV?UySvQ8NrL|LlIVFSEhCI@`7|7p7O5F4B2Z211~.4*b%U%1",
"que": "-3f-*_-*W{Pyty$lgbNa+`KX!T8T.JBH$@j4e0|)O#q!N",
"nuo": "-3Q-1w-$ftxa6_#_!ZLXnWoWbWLK0HJF2Am",
"can": "-3P-2F-)l-)k-)j-)i-$D-#H~:r(q3amah`W`V`U_[XsVqVhO_BtBg;/784z1!0L(9",
"lei": "-3O-24-1t-,J-)q-#1|(z0xOx?rrU|U;G'E]DyDxD/?$>I=+<F5X'z%u$)#Q",
"zao": "-3L-/h-&(-%w-$5-!@`JRfMsLkK>JO7F5&2>1#(](7#&#%",
"cao": "-3L-#GnGk@`v`k`^_DVAUqOgOfG:8x",
"ao": "-3H-/n-*&-#X-#W|H|4xnxkv}vyvwsDs2rZmxmakAjnga`O_@]I]![DVuUeTBM*K=?>9;7M741m1^0Z,!+~(Y",
"cou": "-3D-0:Hl;1",
"chuang": "-3B-/b-/K-/5-.s-.i-.L-$T-!dhvhMd=`|W7O<F+0#/r/k%D$+",
"piao": "-36-.D-,;-#Crteze7`]RuO*Br9/.v'Z!X",
"man": "-32{KyKujtlrpn^i'bc`M`=VrOdG9Fs:V9P928b7<701V,(",
"zun": "-3%-!ix}oPk&[%YzVILEFU5k",
"deng": "-2z-/t-!VxwrHk(_v^@E$7d6/5.1A(O#,",
"tie": "-2s-'yigd+",
"seng": "-2o",
"zhuang": "-2hx9x8x2w)vWv@tphvhShE^a^`^_VDKcKBG6:`8X3H.e.Z",
"min": "-2`-0Q-/E-,X-(&|1uumYl]dfded8bJaWaG`S_`[]YhTUTIT(RSRRPzAH>|;z;+:t8(+1*c)o)j)=$R!D",
"sai": "-2V-#b-#)-!/yod%a2XaAxAb",
"tai": "-2B-0_-)=}e|Mw[wXwOvzqvqCdodQdB`e[pU]SpR]MeE>@f@D@C>{:=4G4F1$*f",
"lan": "-2?-1@-)}-%P-!'~3|hx`xXt(qgqaqUmwkwhgb)_8_'^p[5X/UXU(TmSaS_PpLbHXDDD2D1=^9L8i7K6W5`5W5=5<46121%0n0d0I0@0>($'j",
"meng": "-2;-0k-,Lwaw`qEo2hnh*_._+^qXtUaP)O9K$F;EAANAF:A6h,Z+]'/&X#B!#",
"qiong": "-28-*fr.pza]`!K}F(3(3%2L1}*))J(G's'f",
"lie": "-25-0N-/O-,z-,w-,`-'<-&G{D{CuDjaj=djZeZ_YiUEL;JMA{>_=p403f2A1$0a0[.x,h,V+k+[",
"teng": "-2%i+9]8r1e%6%&",
"long": "-2#-'J-&]~^|7|6xHxGo2n=k6j_j^g.e([9U.QmOxO8LgKDGYDU>t:g9T9%5w0N*Z'n#f",
"rang": "-1}-,$~Nx[xC^mU$5b0K+S'X",
"xiong": "-1m-1j-/q-+v-+p-&zwsdLc?Sy@;>42w2r2#2!",
"chong": "-1l-0Y-#L{*p`oflelde0dc`+_dX<W8?z=K=98X0F*@%&",
"dui": "-1g-1e-1`-%L|$zlymobo]oMcc_n_m_*TET>T2NB6[6G5|5u$P",
"rui": "-1g-1e-1`-)LxFas]0M~KVF.@G)'&t",
"ke": "-1f-/*-.w-,]-,R-+;-)>-'o-'0-$!|Dzqx4u#p^oUmomIkvknjqc4bra<a;XJWsT4M%J1G|F}CcBCBBA/;u;G:>6U4U0!/N//*j%>$O",
"tu": "-1c-1]-0H-/o-(y-&3-%?}n}c}J}I}A}?zezZyvxipvnUlskikFh.gVeVc}bsZ.YYX@W2K?EL@R=C=::r7u(i$r$>",
"nei": "-1I-1*-&TtvA<A;=H",
"liu": "-1C-/N-.8~fy^s5qik`gl^vW9S4S*R}L~LpKnKQH'FHF#>(=w::8Q7V631r1[*a)~)%(v(?&S&>&%%r$(#d",
"shou": "-13-)`-)V-%l-!o{ox)x&pxo[]v]uYITcTN<t.1+n+X$e$&",
"ran": "-1+-1&-(!-##umsKMBF'2y1Z1F*i",
"gang": "-1'-0^-/L-.gzjz4mzmplT^a^`^_]fYKWDNZJEGe;L2z2v/W/:.a%Z",
"gua": "-0~-/8-.r-.S-.A-*m-)F-(/-'s-'&-%0|Ooz[/ZuY6LSK[C`2='a",
"zui": "-0i-*6-'d-#U-!w-!*k<jmQ=O`OGLDG[F]EgEbD@6a(%",
"qia": "-0M-+;-*r-'6})m0iZc.a<[j[7YAXJUhBq>,",
"mei": "-0I~j|vz>yRv#sks]sTs4r<p/l&k|e)[bZBU%S3R&M|LoKFHzHjGgDrBfB0B/?~?o?d=I;?;7;32Q2J11,=+$*0)D$w",
"zhun": "-0E-05-%L}5zwpnnFdPRQ<,:@",
"du": "-0+-.`-+B-)p-#0z<vKv1uTqjh1SsQ4PvN_IcDlBRBNB+=(;n;Z6</s/h/5.y..+i)I'y!E",
"kai": "-/x-/v-/%-.M-,I-#J{ey~w0n3kag2dEc#aB`y`r`GXCPfHfCxCu6U4m3}",
"hua": "-/T-.>-+b-+(-(_-(.-&h-#%{@wGuWs}s|rJrDlaWTV}V+NAMvKfIgGKFX9a7c,7&]&+%~",
"bie": "-/A-/;fGe2`#M'M!$!#I",
"pao": "-/>-+i-'^~o|2w=hA]$[P?.4J4H3d06.M'^%A!S",
"geng": "-/7-&A{TzHlrh=ZIOlK4IX=X2p&M",
"shua": "-//-%j",
"cuo": "-.y-.p-*5wukWkSh!ZKY&WuV4(o$j$'",
"kei": "-.woU",
"la": "-.v-%3-$n~L|8[RXFXEWnUEU2R`MOI6DT:T0['o$A",
"pou": "-.l-'_-&[{]twtO]+]&Z+YGJS/<",
"tuan": "-.I~!}~}K}HyPy&f7`>[}XIVmGLE;;.:m8t2[,F%v%p",
"zuan": "-.)XOTt",
"keng": "-,x-([|t|kvIZCXlVgBF/C",
"gao": "-,Z-(I-(>wRlpWjNHGxGwGdG>E~E3Dm,)!y!t",
"lang": "-,V-&J-$~{Jy[r{llgiSeOIOHO;KRHHG4Cp=[3Y,z*%(s",
"weng": "-,@-#oyxv{kfU!Pd9o'N'&",
"tao": "-+m-)E-'+-%DwPwMw*r}i/fl`j[oYBWXL,JkGtE?><=)<t<H9^6_(w",
"nao": "-+`-'n{cz[wqt.rzq8l{jyjSd0b~bPad_QZVW9VOKkFJ<J,D+Z+Q),",
"zang": "-+Oynw)g(/~",
"suan": "-+C,{$n",
"nian": "-+3-&i-%b{9uOhdg3dNbTa~[SYVVHV,U7HL=;<0:C2q",
"shuai": "-*xixiWW3+I&o",
"mang": "-*<-)*-&Zx(u(o2h*dkb|OENdNSAF@c=i8`/[/D.$$q",
"rou": "-)uslpsXdMAHc;d2K)>'v",
"cen": "-)l-)k-)j-)i{Un#kH@?=1",
"shuang": "-)byOqeq^`NDB>t8R5w5^0&",
"po": "-)8-&M-#6~]|ZvztMoZmlmZg9W]TXR+O*E%?E>q>o>D;*:J8;6F3v,9*l!`",
"a": "-(s-'o-%O-$0",
"tun": "-(k-(7-%L-!`}}|snFhNdP_mRQPFOC@x=335",
"hang": "-([{dwSvIj)dGS8NML/@.",
"shun": "-(ZHnF?",
"ne": "-(R-(8-(%-&T]0%a",
"chuo": "-(Q-&@-%=~Hu!t~t.ssqVa|^2Z}UuCC<q<J",
"wai": "-(/-'&-$gwml7C95z",
"guo": "-(/-'&-%)-$e-#<~.}r}k}f}d}a}U{<zTy>lMi@i$fDf@b1`Y_4XyW6TMMzJ$I:GOD{=#<W;U9#7!,c$<",
"qiang": "-(,-%f-%M-$.-#[y=y3xmrXr0k>gKfVfSfC^P^N^>[zWQW!VySKMlIvGkFdEJ:)8{4[1s/|/z,f,.*{(p%m",
"pen": "-('-$E-$=-!6CN;'6}'Q!=",
"pin": "-&~~Yuatnrvq{[AZ{H]@_/c+!)r",
"ha": "-&wvz",
"yo": "-&`-%c-$B",
"o": "-&X-$a-!H-!%",
"n": "-&)-#a",
"huan": "-%v-$Z-$Y~G}D{_zWw@w2r.q[pYp0okm8l!h]bVaH_I^iYpXQUnU1KyK2GBD%CPCB>1=c<~;c8V7D734/3>2I.[.;,3+R*})9(1'b$d$:",
"ken": "-%V{qxjc*_CX~*I",
"chuai": "-%=XIW}Ch",
"pa": "-%/vLisihd.]oX|NC@r8608)P#!",
"se": "-%,-$,yogK_<Z}VnUrLTGJC5C3>W<x;q7h7g6|6t60)p)&(0#r",
"re": "-$fa[YU;y3g1X",
"sun": "-$DqKq9]EYsW{WzW;H1Gv.',:",
"hei": "-#h-!l7r",
"dia": "-#^",
"de": "-#5}0hBeQe5e1c)bFakR[JW<_##",
"dei": "-#5eQ",
"kuo": "-!`g`]W[:[/ZsUI8U6L",
"ceng": "-!_ntnQk4OcObF*",
"ca": "~s~B[UUWU:",
"zeng": "~7y5y._}OcObF*1R(M'*",
"nin": "~(c^bQ[*",
"kun": "}q|Wzoz`x/x*t'l[lZbwZ0RHQMJv=B8C371U,e)_(g",
"qun": "}jwxpRl~iPCT",
"ri": "}iRjA=",
"lve": "}Go^Y1&2&0",
"zhui": "|[y0w#t]aaXIIt?s'<%|",
"sao": "zus+`k_D]UYNXrWtK(AP:8$4",
"en": "wBmBc5WF20",
"zou": "w3s>Y%X`W~J/J.Hl",
"nv": "vkc7OM@!",
"nuan": "vcPo;`:m2X2W",
"shuo": "v]a'WhT'S|OKGnCn>>470W+p",
"niu": "v?q=dKd0]S]![DN?@8@!4u/e/d.W",
"rao": "u2rA]PU?KkFJ",
"niang": "t|r&qb",
"shui": "t]iTZMYuA$A#@{=.=*",
"nve": "t)%S$%",
"nen": "sirarYc^",
"niao": "s!r+qsnwFq9x",
"kuan": "pBp#ooK)CoCfCd",
"cuan": "jPV'U*T~TwDtD7BU@o6E5K1S0<",
"te": "dsdr`R/F/9",
"zen": "d5VU",
"zei": "^H",
"den": "][]C",
"zhua": "],ZYV#ER0:09",
"shuan": "[&L^GL<s",
"zhuai": "Zz",
"nou": "WoGpE0+^",
"shai": "W<TsQWOt",
"sen": "J8ISGI",
"run": "FE<{8'",
"ei": "Cl",
"chua": "Ci"
}
};
let DB = {
sToC: {},
cToS: {}
};
let sToC = DB.sToC,
cToS = DB.cToS,
ungroup = /-?.{2}/g,
rg = /^-/,
fromX = str => {
let result = 0,
temp = 1;
for (let idx = str.length; idx--;) {
result += temp * (chars.indexOf(str.charAt(idx)));
temp *= 91;
}
return result;
},
fn = (a, f) => {
let p, gs, i, ch, num;
for (p in a) {
if (a.hasOwnProperty(p)) {
gs = a[p].match(ungroup);
for (i = 0; i < gs.length; i++) {
ch = gs[i].replace(rg, '#');
num = fromX(ch);
ch = String.fromCharCode(base + middle + (f ? -num : num));
if (sToC.hasOwnProperty(p)) {
sToC[p] += ch;
} else {
sToC[p] = ch;
}
if (cToS.hasOwnProperty(ch)) {
cToS[ch] += COMA + p;
} else {
cToS[ch] = p;
}
}
}
}
};
fn(SDB.m, 1);
fn(SDB.a);
SDB = null;
export default {
getSpell(chars, polyphone, spliter) {
let cToS = DB.cToS;
let res = [],
pp = typeof (polyphone) == 'function'; //判断polyphone是否是函数
chars = String(chars).split(EMPTY);
for (let i = 0, ch, ss; i < chars.length; i++) {
ch = chars[i];
if (cToS.hasOwnProperty(ch)) {
ss = cToS[ch];
if (~ss.indexOf(COMA)) {
ss = ss.split(COMA);
ss = pp ? polyphone(ch, ss) : '[' + ss + ']';
res.push(ss);
} else {
res.push(ss);
}
} else {
res.push(ch);
}
}
return res.join(spliter || COMA);
},
getChars(spell) {
let sToC = DB.sToC;
if (sToC.hasOwnProperty(spell)) {
return sToC[spell].split(EMPTY);
}
return [];
}
};
\ No newline at end of file
/*! iNoBounce - v0.2.0
* https://github.com/lazd/iNoBounce/
* Copyright (c) 2013 Larry Davis <lazdnet@gmail.com>; Licensed BSD */
(function(global) {
// Stores the Y position where the touch started
var startY = 0;
// Store enabled status
var enabled = false;
var supportsPassiveOption = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function() {
supportsPassiveOption = true;
}
});
window.addEventListener('test', null, opts);
} catch (e) {}
var handleTouchmove = function(evt) {
// Get the element that was scrolled upon
var el = evt.target;
// Allow zooming
var zoom = window.innerWidth / window.document.documentElement.clientWidth;
if (evt.touches.length > 1 || zoom !== 1) {
return;
}
// Check all parent elements for scrollability
while (el !== document.body && el !== document) {
// Get some style properties
var style = window.getComputedStyle(el);
if (!style) {
// If we've encountered an element we can't compute the style for, get out
break;
}
// Ignore range input element
if (el.nodeName === 'INPUT' && el.getAttribute('type') === 'range') {
return;
}
var scrolling = style.getPropertyValue('-webkit-overflow-scrolling');
var overflowY = style.getPropertyValue('overflow-y');
var height = parseInt(style.getPropertyValue('height'), 10);
// Determine if the element should scroll
var isScrollable = scrolling === 'touch' && (overflowY === 'auto' || overflowY === 'scroll');
var canScroll = el.scrollHeight > el.offsetHeight;
if (isScrollable && canScroll) {
// Get the current Y position of the touch
var curY = evt.touches ? evt.touches[0].screenY : evt.screenY;
// Determine if the user is trying to scroll past the top or bottom
// In this case, the window will bounce, so we have to prevent scrolling completely
var isAtTop = (startY <= curY && el.scrollTop === 0);
var isAtBottom = (startY >= curY && el.scrollHeight - el.scrollTop === height);
// Stop a bounce bug when at the bottom or top of the scrollable element
if (isAtTop || isAtBottom) {
evt.preventDefault();
}
// No need to continue up the DOM, we've done our job
return;
}
// Test the next parent
el = el.parentNode;
}
// Stop the bouncing -- no parents are scrollable
evt.preventDefault();
};
var handleTouchstart = function(evt) {
// Store the first Y position of the touch
startY = evt.touches ? evt.touches[0].screenY : evt.screenY;
};
var enable = function() {
// Listen to a couple key touch events
window.addEventListener('touchstart', handleTouchstart, supportsPassiveOption ? { passive : false } : false);
window.addEventListener('touchmove', handleTouchmove, supportsPassiveOption ? { passive : false } : false);
enabled = true;
};
var disable = function() {
// Stop listening
window.removeEventListener('touchstart', handleTouchstart, false);
window.removeEventListener('touchmove', handleTouchmove, false);
enabled = false;
};
var isEnabled = function() {
return enabled;
};
// Enable by default if the browser supports -webkit-overflow-scrolling
// Test this by setting the property with JavaScript on an element that exists in the DOM
// Then, see if the property is reflected in the computed style
var testDiv = document.createElement('div');
document.documentElement.appendChild(testDiv);
testDiv.style.WebkitOverflowScrolling = 'touch';
var scrollSupport = 'getComputedStyle' in window && window.getComputedStyle(testDiv)['-webkit-overflow-scrolling'] === 'touch';
document.documentElement.removeChild(testDiv);
if (scrollSupport) {
enable();
}
// A module to support enabling/disabling iNoBounce
var iNoBounce = {
enable: enable,
disable: disable,
isEnabled: isEnabled
};
if (typeof module !== 'undefined' && module.exports) {
// Node.js Support
module.exports = iNoBounce;
}
if (typeof global.define === 'function') {
// AMD Support
(function(define) {
define('iNoBounce', [], function() { return iNoBounce; });
}(global.define));
}
else {
// Browser support
global.iNoBounce = iNoBounce;
}
}(this));
function isEqual(x, y) {
if (x === y) {
return true
}
if (!(x instanceof Object) || !(y instanceof Object)) {
return false
}
if (x.constructor !== y.constructor) {
return false
}
for (var p in x) {
if (x.hasOwnProperty(p)) {
if (!y.hasOwnProperty(p)) {
return false
}
if (x[p] === y[p]) {
continue
}
if (typeof(x[p]) !== "object") {
return false
}
if (!Object.equals(x[p], y[p])) {
return false
}
}
}
for (p in y) {
if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
return false
}
}
return true
}
module.exports = {
isEqual
}
\ No newline at end of file
<template>
<view class="wyb-table-box">
<view v-if="loading" class="wyb-table-loading-box" :style="{
width: width === 'auto' ? screenWidth : width,
height: height === 'auto' ? '300rpx' : height,
backgroundColor: loaderBgColor,
borderTop: '1px solid' + borderColor,
borderBottom: '1px solid' + borderColor,
borderLeft: showLeftAndRightBorder ? '1px solid' + borderColor : 'none',
borderRight: showLeftAndRightBorder ? '1px solid' + borderColor : 'none'}">
<view class="loader-one" :style="{
width: loaderSize + 'rpx',
height: loaderSize + 'rpx',
borderTop: '3px solid ' + loadingColor.top,
borderRight: '3px solid ' + loadingColor.right,
borderBottom: '3px solid ' + loadingColor.bottom,
borderLeft: '3px solid ' + loadingColor.left}" />
</view>
<view v-if="!loading" class="wyb-table-scroll-view" :style="{
width: width,
height: height,
borderTop: '1px solid' + borderColor,
borderLeft: showLeftAndRightBorder ? '1px solid' + borderColor : 'none',
borderRight: showLeftAndRightBorder ? '1px solid' + borderColor : 'none'}">
<view class="wyb-table-header" :style="{borderBottom: '1px solid' + borderColor}">
<view class="wyb-table-header-item" v-if="enableCheck" :style="{
minWidth: checkColWidth + 'rpx',
maxWidth: checkColWidth + 'rpx',
minHeight: minHeight[0] + 'rpx',
textAlign: textAlign,
justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
fontSize: fontSize[0] + 'rpx',
color: headerFtColor,
padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
backgroundColor: headerBgColor,
borderRight: '1px solid' + borderColor,
zIndex: 30,
left: 0,
color: headerFtColor,
backgroundColor: headerBgColor,
position: 'sticky'}">
<view
class="wyb-table-checkbox"
v-if="enableCheck === 'multiple'"
@tap.stop="onCheckAllTap"
:style="{
width: checkColWidth * 0.5 + 'rpx',
height: checkColWidth * 0.5 + 'rpx',
backgroundColor: checkerBoxBgColor,
border: '1px solid ' + checkerBorderColor}">
<text
class="iconfont icon-check"
v-show="checkAll"
:style="{
color: checkerColor,
backgroundColor: checkerBgColor,
paddingTop: (fontSize[1] || fontSize[0]) * 0.15 + 'rpx',
fontSize: (fontSize[1] || fontSize[0]) + 'rpx'}" />
</view>
</view>
<view ref="iosBug" class="wyb-table-header-item" v-for="(item, index) in headers" :key="item.key" @tap="onHeaderItemTap(index)"
:style="{
minWidth: (item.width || defaultColWidth) + 'rpx',
maxWidth: (item.width || defaultColWidth) + 'rpx',
minHeight: minHeight[0] + 'rpx',
textAlign: textAlign,
justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
fontSize: fontSize[0] + 'rpx',
fontWeight: headerWeight ? 'bold' : 'normal',
color: headerFtColor,
padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
backgroundColor: headerBgColor,
borderRight: index === headers.length - 1 || (!showVertBorder && index !== 0) ? 'none' : '1px solid' + borderColor,
zIndex: index === 0 ? 20 : 0,
left: index === 0 && firstLineFixed ? (enableCheck ? checkColWidth + 'rpx' : 0) : 'auto',
position: index === 0 ? 'sticky' : 'static'}">
<text :style="{marginLeft: autoSortShow(index) && textAlign !== 'left' ? fontSize[0] * 0.65 + 'rpx' : 0}">
{{item.label || emptyString}}
</text>
<view class="wyb-table-header-icon" v-if="autoSortShow(index)">
<text class="iconfont icon-arrow-up" :style="{
color: sortWays[sortWay] === 'asc' && sortActiveKey === item.key ?
headerFtColor : RGBChange(headerFtColor, 0.7, 'light'),
fontWeight: 'normal',
marginBottom: '-12px',
transform: 'scale(0.4)'}" />
<text class="iconfont icon-arrow-down" :style="{
color: sortWays[sortWay] === 'inv' && sortActiveKey === item.key ?
headerFtColor : RGBChange(headerFtColor, 0.7, 'light'),
fontWeight: 'normal',
transform: 'scale(0.4)'}" />
</view>
</view>
</view>
<view class="wyb-table-content">
<view class="wyb-table-content-line" v-for="(content, cIndex) in contentsSort" :key="contentLineKey(content, cIndex)"
:style="{borderTop: cIndex === 0 ? 'none' : '1px solid' + borderColor}">
<view class="wyb-table-content-item" v-if="enableCheck" :style="{
minWidth: checkColWidth + 'rpx',
maxWidth: checkColWidth + 'rpx',
textAlign: textAlign,
justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
minHeight: (minHeight[1] || minHeight[0]) + 'rpx',
padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
borderRight: '1px solid' + borderColor,
zIndex: 21,
color: contentFtColor,
backgroundColor: checkerCellBgColor,
left: 0,
position: 'sticky'}">
<view
class="wyb-table-checkbox"
@tap.stop="onCheckItemTap(cIndex)"
:style="{
width: checkColWidth * 0.5 + 'rpx',
height: checkColWidth * 0.5 + 'rpx',
backgroundColor: checkerBoxBgColor,
border: '1px solid ' + checkerBorderColor}">
<text
class="iconfont icon-check"
v-show="contentsSort[cIndex].checked"
:style="{
color: checkerColor,
backgroundColor: checkerBgColor,
paddingTop: (fontSize[1] || fontSize[0]) * 0.15 + 'rpx',
fontSize: (fontSize[1] || fontSize[0]) + 'rpx'}" />
</view>
</view>
<view
class="wyb-table-content-item"
v-for="(header, hIndex) in headers"
@tap.stop="onContentItemTap(cIndex, hIndex)"
:key="contentItemKey(header, hIndex)"
:style="{
minWidth: (header.width || defaultColWidth) + 'rpx',
maxWidth: (header.width || defaultColWidth) + 'rpx',
textAlign: textAlign,
justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
textDecoration: autoTextDecoration(cIndex, hIndex),
color: autoContentColor(cIndex, hIndex),
backgroundColor: autoContentBgColor(cIndex, hIndex),
minHeight: (minHeight[1] || minHeight[0]) + 'rpx',
padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
borderBottom: cIndex === contents.length - 1 ? '1px solid' + borderColor : 'none',
borderRight: hIndex === headers.length - 1 || (!showVertBorder && hIndex !== 0) ? 'none' : '1px solid' + borderColor,
zIndex: hIndex === 0 ? 20 : 0,
left: enableCheck ? checkColWidth + 'rpx' : 0,
position: hIndex === 0 && firstLineFixed ? 'sticky' : 'static'}">{{autoContentItem(cIndex, hIndex)}}</view>
</view>
<view v-if="computedCol.length !== 0" class="wyb-table-content-line" :style="{
position: bottomComputedFixed ? 'sticky' : 'static',
bottom: 0,
zIndex: 25,
borderTop: '1px solid' + borderColor}">
<view class="wyb-table-content-item" v-if="enableCheck" :style="{
minWidth: checkColWidth + 'rpx',
maxWidth: checkColWidth + 'rpx',
textAlign: textAlign,
justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
minHeight: (minHeight[1] || minHeight[0]) + 'rpx',
padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
borderBottom: '1px solid' + borderColor,
borderRight: '1px solid' + borderColor,
zIndex: 25,
color: contentFtColor,
backgroundColor: checkerCellBgColor,
left: 0,
position: 'sticky'}"></view>
<view class="wyb-table-content-item" v-for="(header, index) in headers" :key="index"
:style="{
minWidth: (header.width || defaultColWidth) + 'rpx',
maxWidth: (header.width || defaultColWidth) + 'rpx',
textAlign: textAlign,
justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
color: contentFtColor,
minHeight: (minHeight[1] || minHeight[0]) + 'rpx',
padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
backgroundColor: index === 0 ? firstColBgColor : contentBgColor,
borderBottom: '1px solid' + borderColor,
borderRight: index === headers.length - 1 || (!showVertBorder && index !== 0) ? 'none' : '1px solid' + borderColor,
zIndex: index === 0 ? 20 : 0,
left: enableCheck ? checkColWidth + 'rpx' : 0,
position: index === 0 && firstLineFixed ? 'sticky' : 'static'}">
{{autoBottomComputedItem(index)}}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import Pinyin from './js/characterToPinyin.js'
import {isEqual} from './js/objEqual.js'
export default {
data() {
return {
bottomComputed: [],
colorList: [],
bgColorList: [],
contentsSort: this.contents.slice(),
oContentsSort: [],
sortWay: 0,
sortKeys: [],
sortActiveKey: '',
sortIsNumbers: [],
checkAll: false,
checkList: [],
onload: true,
event: {
checkType: this.enableCheck,
data: []
},
chars: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
}
},
computed: {
loadingColor() {
let color = this.loaderColor.slice()
let rgbList = this.hexToRgb(color)
let top = 'rgba(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ', 0.3)'
let bottom = 'rgba(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ', 0.3)'
let right = 'rgba(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ', 0.3)'
let left = 'rgb(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ')'
return {
top,
bottom,
right,
left
}
},
contentLineKey() {
return function(content, cIndex) {
return this.randomString(32, this.chars)
}
},
contentItemKey() {
return function(header, hIndex) {
return this.randomString(16, this.chars)
}
},
autoContentItem() {
return function(cIndex, hIndex) {
let content = this.contentsSort[cIndex]
let header = this.headers[hIndex]
let result = ''
if (content[header.key] || content[header.key] === 0) {
result = content[header.key]
if (this.urlCol.length !== 0) {
for (let i in this.urlCol) {
let item = this.urlCol[i]
if (header.key === item.key) {
// 该单元格为链接
result = content[header.key][0]
}
}
}
if (this.formatCol.length !== 0) {
this.formatCol.forEach(item => {
if (header.key === item.key) {
let needRplace = new RegExp(`\#${item['key']}\#`, 'mg')
result = item.template.replace(needRplace, result)
}
})
}
} else {
result = this.emptyString
}
return result
}
},
autoBottomComputedItem() {
return function(index) {
let bottomComputed = {}
let needComputed = []
this.computedCol.forEach(key => {
let computedColData = []
this.contentsSort.forEach(content => {
computedColData.push(content[key] || '0')
})
needComputed.push(computedColData)
})
needComputed.forEach((item, index) => {
let total = 0
item.forEach(num => {
total += parseFloat(num)
})
bottomComputed[this.computedCol[index]] = total
})
let header = this.headers[index]
let result = this.computedCol.includes(header.key) ?
bottomComputed[header.key] : (index === 0 ? '总计' : this.emptyString)
if (this.formatCol.length !== 0) {
this.formatCol.forEach(item => {
if (item.bottomComputedFormat) {
if (header.key === item.key) {
let needRplace = new RegExp(`\#${item['key']}\#`, 'mg')
result = item.template.replace(needRplace, bottomComputed[item.key])
}
}
})
}
return result
}
},
autoTextDecoration() {
return function(cIndex, hIndex) {
let result = 'auto'
let content = this.contentsSort[cIndex]
let header = this.headers[hIndex]
if (this.urlCol.length !== 0) {
for (let i in this.urlCol) {
let item = this.urlCol[i]
if (header.key === item.key) {
// 该单元格为链接
if (content[header.key]) {
result = 'underline'
}
}
}
}
return result
}
},
autoContentBgColor() {
return function(cIndex, hIndex) {
let result = this.contentBgColor
let content = this.contentsSort[cIndex]
let header = this.headers[hIndex]
let keys = []
// 先判断是不是首列,设置基础样式
if (hIndex === 0) {
result = this.firstColBgColor
}
// 再判断条件格式传没传值,设置条件样式
if (this.valueFormat.length !== 0) {
this.valueFormat.forEach(item => {
keys.push(item.key)
})
if (keys.includes(header.key)) {
// 该列开启了条件格式
let key = header.key
let type = this.valueFormat[keys.indexOf(key)].type
let style = this.valueFormat[keys.indexOf(key)].style
let range = this.valueFormat[keys.indexOf(key)].range || ''
switch(type) {
case 'bigger':
if (parseFloat(content[key]) > range) {
if (style.bgColor) result = style.bgColor
}
break
case 'smaller':
if (parseFloat(content[key]) < range) {
if (style.bgColor) result = style.bgColor
}
break
case 'equal':
let val
if (typeof range === 'number') val = parseFloat(content[key])
else val = content[key]
if (val === range) {
if (style.bgColor) result = style.bgColor
}
break
case 'range':
if (parseFloat(content[key]) > range[0] && parseFloat(content[key]) < range[1]){
if (style.bgColor) result = style.bgColor
}
break
case 'average-bigger':
let average = this.getAverage(key)
if (parseFloat(content[key]) > average) {
if (style.bgColor) result = style.bgColor
}
break
case 'average-smaller':
average = this.getAverage(key)
if (parseFloat(content[key]) < average) {
if (style.bgColor) result = style.bgColor
}
break
case 'average-equal':
average = this.getAverage(key)
if (parseFloat(content[key]) === average) {
if (style.bgColor) result = style.bgColor
}
break
}
}
}
return result
}
},
autoContentColor() {
return function(cIndex, hIndex) {
let result = this.contentFtColor
let content = this.contentsSort[cIndex]
let header = this.headers[hIndex]
let keys = []
// 先判断是不是链接,设置基础样式
if (this.urlCol.length !== 0) {
for (let i in this.urlCol) {
let item = this.urlCol[i]
if (header.key === item.key) {
// 该单元格为链接
if (content[header.key]) {
result = this.linkColor
}
}
}
}
// 再判断条件格式传没传值,设置条件样式
if (this.valueFormat.length !== 0) {
this.valueFormat.forEach(item => {
keys.push(item.key)
})
if (keys.includes(header.key)) {
// 该列开启了条件格式
let key = header.key
let type = this.valueFormat[keys.indexOf(key)].type
let style = this.valueFormat[keys.indexOf(key)].style
let range = this.valueFormat[keys.indexOf(key)].range || ''
switch(type) {
case 'bigger':
if (parseFloat(content[key]) > range) {
if (style.color) result = style.color
}
break
case 'smaller':
if (parseFloat(content[key]) < range) {
if (style.color) result = style.color
}
break
case 'equal':
let val
if (typeof range === 'number') val = parseFloat(content[key])
else val = content[key]
if (val === range) {
if (style.color) result = style.color
}
break
case 'range':
if (parseFloat(content[key]) > range[0] && parseFloat(content[key]) < range[1]){
if (style.color) result = style.color
}
break
case 'average-bigger':
let average = this.getAverage(key)
if (parseFloat(content[key]) > average) {
if (style.color) result = style.color
}
break
case 'average-smaller':
average = this.getAverage(key)
if (parseFloat(content[key]) < average) {
if (style.color) result = style.color
}
break
case 'average-equal':
average = this.getAverage(key)
if (parseFloat(content[key]) === average) {
if (style.color) result = style.color
}
break
}
}
}
return result
}
},
autoSortShow() {
return function(hIndex) {
let result = false
let header = this.headers[hIndex]
let keys = []
// 判断排序是否传值
if (this.sortCol.length !== 0 && this.sortKeys.length === 0) {
this.sortCol.forEach(item => {
keys.push(item.key)
})
this.sortKeys = keys
if (keys.includes(header.key)) {
result = true
}
} else if (this.sortCol.length !== 0) {
if (this.sortKeys.includes(header.key)) {
result = true
}
}
return result
}
},
screenWidth() {
return `${uni.getSystemInfoSync()['screenWidth']}px`
}
},
props: {
headers: {
type: Array,
default() {
return [{
key: 'name',
label: '姓名'
}]
}
},
contents: {
type: Array,
default() {
return [{
name: '张三'
}, {
name: '李四'
}]
}
},
emptyString: {
type: String,
default: '-'
},
width: {
type: String,
default: `${uni.getSystemInfoSync().screenWidth}px`
},
height: {
type: String,
default: 'auto'
},
fontSize: {
type: Array,
default() {
return [30]
}
},
defaultColWidth: {
type: Number,
default: 180
},
headerWeight: {
type: Boolean,
default: true
},
minHeight: {
type: Array,
default() {
return [70]
}
},
headerBgColor: {
type: String,
default: '#f1f1f1'
},
contentBgColor: {
type: String,
default: '#fff'
},
headerFtColor: {
type: String,
default: '#3e3e3e'
},
contentFtColor: {
type: String,
default: '#3e3e3e'
},
linkColor: {
type: String,
default: '#0024c8'
},
firstColBgColor: {
type: String,
default: '#f1f1f1'
},
firstLineFixed: {
type: Boolean,
default: false
},
textAlign: {
type: String,
default: 'center'
},
padding: {
type: Array,
default() {
return [5, 10]
}
},
borderColor: {
type: String,
default: '#e1e1e1'
},
urlCol: {
type: Array,
default() {
return []
}
},
computedCol: {
type: Array,
default() {
return []
}
},
bottomComputedFixed: {
type: Boolean,
default: true
},
valueFormat: {
type: Array,
default() {
return []
}
},
formatCol: {
type: Array,
default() {
return []
}
},
showLeftAndRightBorder: {
type: Boolean,
default: false
},
showVertBorder: {
type: Boolean,
default: true
},
sortCol: {
type: Array,
default() {
return []
}
},
sortWays: {
type: Array,
default() {
return ['none', 'asc', 'inv']
}
},
loading: {
type: Boolean,
default: false
},
loaderSize: {
type: [String, Number],
default: 50
},
loaderColor: {
type: String,
default: '#a3a3a3'
},
loaderBgColor: {
type: String,
default: '#f8f8f8'
},
enableCheck: {
type: String,
default: ''
},
checkColWidth: {
type: [String, Number],
default: '70'
},
checkerColor: {
type: String,
default: '#3e3e3e'
},
checkerBorderColor: {
type: String,
default: '#d3d3d3'
},
checkerBgColor: {
type: String,
default: 'rgba(0, 0, 0, 0)'
},
checkerBoxBgColor: {
type: String,
default: 'rgba(0, 0, 0, 0)'
},
checkerCellBgColor: {
type: String,
default: '#f1f1f1'
}
},
watch: {
headers(val) {
this.$forceUpdate()
},
contents(val) {
this.contentsSort = val.slice()
if (this.onload) {
this.contentsSort.forEach(item => {
this.$set(item, 'checked', false)
})
this.oContentsSort = this.contentsSort.slice()
this.onload = false
}
this.$forceUpdate()
}
},
mounted() {
this.contentsSort.forEach(item => {
this.$set(item, 'checked', false)
})
this.oContentsSort = this.contentsSort.slice()
if (this.sortCol.length !== 0) {
this.sortActiveKey = this.sortCol[0].key
uni.setStorageSync('lastSortActiveKey', this.sortActiveKey)
this.doSort(this.sortCol[0].key, this.sortWays[this.sortWay], this.sortCol[0].isNumber)
}
},
methods: {
doSort(key, type, isNumber) {
let arr = this.contentsSort
if (type === 'asc') {
// 升序
if (isNumber) {
arr.sort((a, b) => {
return (parseFloat(a[key].toString().replace(/[^0-9]/ig, "")) || 0) -
(parseFloat(b[key].toString().replace(/[^0-9]/ig, "")) || 0)
})
} else {
arr.sort((a, b) => {
let A = Pinyin.getSpell(a[key].charAt(0), function(charactor, spell) {
return spell[1]
}).charAt(0).charCodeAt()
let B = Pinyin.getSpell(b[key].charAt(0), function(charactor, spell) {
return spell[1]
}).charAt(0).charCodeAt()
return A - B
})
}
} else if (type === 'inv') {
// 倒序
if (isNumber) {
arr.sort((a, b) => {
return (parseFloat(b[key].toString().replace(/[^0-9]/ig, "")) || 0) -
(parseFloat(a[key].toString().replace(/[^0-9]/ig, "")) || 0)
})
} else {
arr.sort((a, b) => {
let A = Pinyin.getSpell(a[key].charAt(0), function(charactor, spell) {
return spell[1]
}).charAt(0).charCodeAt()
let B = Pinyin.getSpell(b[key].charAt(0), function(charactor, spell) {
return spell[1]
}).charAt(0).charCodeAt()
return B - A
})
}
} else {
this.contentsSort = this.oContentsSort.slice()
}
if (this.enableCheck) {
this.event.data.forEach(item => {
this.contentsSort.forEach((content, index) => {
if (isEqual(item.lineData, content)) {
item.index = index
}
})
})
}
this.$forceUpdate()
},
initBottomComputed() {
let result = {}
let needComputed = []
this.computedCol.forEach(key => {
let computedColData = []
this.contentsSort.forEach(content => {
computedColData.push(content[key] || '0')
})
needComputed.push(computedColData)
})
needComputed.forEach((item, index) => {
let total = 0
item.forEach(num => {
total += parseFloat(num)
})
result[this.computedCol[index]] = total
})
this.bottomComputed = result
},
onHeaderItemTap(index) {
let header = this.headers[index]
const lastSortActiveKey = uni.getStorageSync('lastSortActiveKey') || ''
if (this.sortCol.length !== 0) {
if (this.sortKeys.includes(header.key)) {
// 当前列开启了排序
this.sortActiveKey = header.key
uni.setStorageSync('lastSortActiveKey', this.sortActiveKey)
if (this.sortWay < 2 && lastSortActiveKey === this.sortActiveKey) {
this.sortWay++
} else if (lastSortActiveKey !== this.sortActiveKey) {
this.sortWay = 1
} else if (this.sortWay >= 2) {
this.sortWay = 0
}
let isNumber = this.sortCol[this.sortKeys.indexOf(header.key)].isNumber
this.doSort(header.key, this.sortWays[this.sortWay], isNumber)
}
}
},
onContentItemTap(cIndex, hIndex) {
let event = {}
let content = this.contentsSort[cIndex]
let header = this.headers[hIndex]
let keys = []
if (this.urlCol.length !== 0) {
for (let i in this.urlCol) {
let item = this.urlCol[i]
keys.push(item.key)
}
}
if (content[header.key]) {
if (keys.includes(header.key)) {
// 该单元格为链接
switch(this.urlCol[keys.indexOf(header.key)].type) {
case 'route':
let url = content[header.key][1]
if (content[header.key][2]) {
url = `${url}?`
Object.keys(content[header.key][2]).forEach(key => {
url += `&${key}=${content[header['key']][2][key]}`
})
}
uni.navigateTo({url})
break
case 'http':
this.openURL(content[header.key][1])
break
}
} else {
event = {
content: content[header.key],
contentIndex: cIndex,
header: header.label,
headerIndex: hIndex,
key: header.key,
lineData: content
}
this.$emit('onCellClick', event)
}
} else {
event = {
content: '',
contentIndex: cIndex,
header: header.label,
headerIndex: hIndex,
key: header.key,
lineData: content
}
if (keys.includes(header.key)) {
// 该单元格为链接
event['isLink'] = true
}
this.$emit('onCellClick', event)
}
},
onCheckAllTap() {
if (this.enableCheck === 'multiple') {
let checkList = []
this.contentsSort.forEach(item => {
checkList.push(item.checked)
})
this.checkList = checkList
if (!this.checkAll) {
this.checkAll = true
this.contentsSort.forEach(item => {
item.checked = true
})
this.event.data = []
this.contentsSort.forEach((content, index) => {
this.event.data.push({
index,
lineData: content
})
})
} else {
this.checkAll = false
this.event.data = []
this.contentsSort.forEach(item => {
item.checked = false
})
}
this.$emit('onCheck', this.event)
}
},
onCheckItemTap(cIndex) {
let content = this.contentsSort[cIndex]
if (this.enableCheck === 'single') {
this.contentsSort.forEach((item, index) => {
if (cIndex === index) {
item.checked = !item.checked
} else {
item.checked = false
}
})
} else if (this.enableCheck === 'multiple') {
this.contentsSort[cIndex]['checked'] = !this.contentsSort[cIndex]['checked']
}
if (this.contentsSort[cIndex]['checked']) {
if (this.enableCheck === 'single') {
this.event.data = []
}
this.event.data.push({
index: cIndex,
lineData: this.contentsSort[cIndex]
})
} else {
this.event.data.forEach(item => {
if (item.index === cIndex) this.event.data.splice(this.event.data.indexOf(item), 1)
})
if (this.event.data.length === 0) {
this.checkAll = false
}
}
this.$forceUpdate()
this.$emit('onCheck', this.event)
},
openURL(href) {
// #ifdef APP-PLUS
plus.runtime.openURL(href)
// #endif
// #ifdef H5
window.open(href)
// #endif
// #ifdef MP
uni.setClipboardData({
data: href,
success() {
uni.showToast({
title: '网址已复制,请在手机浏览器里粘贴该网址',
icon: 'none'
})
}
})
// #endif
},
getAverage(key) {
let numList = []
this.contentsSort.forEach(content => {
numList.push(parseFloat(content[key]) || 0)
})
return numList.reduce((a, b) => a + b) / numList.length
},
getTotal(key) {
let numList = []
this.contentsSort.forEach(content => {
numList.push(parseFloat(content[key]) || 0)
})
return numList.reduce((a, b) => a + b)
},
RGBChange(color, level, type) {
// 判断颜色类型
let r = 0,
g = 0,
b = 0,
hasAlpha = false,
alpha = 1
if (color.indexOf('#') !== -1) {
// hex转rgb
if (color.length === 4) {
let arr = color.split('')
color = '#' + arr[1] + arr[1] + arr[2] + arr[2] + arr[3] + arr[3]
}
let color16List = [color.substring(1, 3), color.substring(3, 5), color.substring(5, 7)]
r = parseInt(color16List[0], 16)
g = parseInt(color16List[1], 16)
b = parseInt(color16List[2], 16)
} else {
hasAlpha = color.indexOf('a') !== -1
let root = color.slice()
let idx = root.indexOf('(') + 1
root = root.substring(idx)
let firstDotIdx = root.indexOf(',')
r = parseFloat(root.substring(0, firstDotIdx))
root = root.substring(firstDotIdx + 1)
let secondDotIdx = root.indexOf(',')
g = parseFloat(root.substring(0, secondDotIdx))
root = root.substring(secondDotIdx + 1)
if (hasAlpha) {
let thirdDotIdx = root.indexOf(',')
b = parseFloat(root.substring(0, thirdDotIdx))
alpha = parseFloat(root.substring(thirdDotIdx + 1))
} else {
b = parseFloat(root)
}
}
let rgbc = [r, g, b]
// 减淡或加深
for (var i = 0; i < 3; i++)
type === 'light' ? rgbc[i] = Math.floor((255 - rgbc[i]) * level + rgbc[i]) : rgbc[i] = Math.floor(rgbc[i] * (1 -
level))
if (hasAlpha) {
return `rgba(${rgbc[0]}, ${rgbc[1]}, ${rgbc[2]}, ${alpha})`
} else {
return `rgb(${rgbc[0]}, ${rgbc[1]}, ${rgbc[2]})`
}
},
hexToRgb(color) {
if (color.length === 4) {
let arr = color.split('')
color = '#' + arr[1] + arr[1] + arr[2] + arr[2] + arr[3] + arr[3]
}
let color16List = [color.substring(1, 3), color.substring(3, 5), color.substring(5, 7)]
let r = parseInt(color16List[0], 16)
let g = parseInt(color16List[1], 16)
let b = parseInt(color16List[2], 16)
return [r, g, b]
},
randomString(length, chars) {
var result = ''
for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]
return result
}
}
}
</script>
<style>
@import './css/iconfont.css';
@import './css/loader.css';
.ios-header-bug {
height: 0;
width: 1px;
opacity: 0;
}
.wyb-table-scroll-view {
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
.wyb-table-scroll-view::-webkit-scrollbar {
display: none;
/* #ifdef MP-WEIXIN */
width: 0;
height: 0;
/* #endif */
}
.wyb-table-loading-box {
display: flex;
align-items: center;
justify-content: center;
z-index: 500;
}
.wyb-table-header {
position: sticky;
top: 0;
display: grid;
grid-auto-flow: column;
width: max-content;
z-index: 25;
}
.wyb-table-header-item {
flex: 1;
display: flex;
align-items: center;
box-sizing: border-box;
position: relative;
}
.wyb-table-header-icon {
display: flex;
flex-direction: column;
}
.wyb-table-content-line {
display: grid;
grid-auto-flow: column;
width: max-content;
position: relative;
}
.wyb-table-content-item {
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
}
.wyb-table-checkbox {
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.icon-check {
width: 100%;
height: 100%;
position: absolute;
border-radius: 0;
border-radius: 3px;
font-weight: bold;
box-sizing: border-box;
transform: scale(1.1);
}
</style>
let baseUrl = "";
let socketUrl = "";
let host = uni.getStorageSync('HOST')||'http://192.168.1.103:7010'
if (process.env.NODE_ENV === 'development') {
// 开发环境
baseUrl = `${host}/`;
socketUrl = "ws://localhost:8001/";
} else if (process.env.NODE_ENV === 'production') {
// 生产环境
baseUrl = `${host}/`;
socketUrl = "ws://localhost:8001/";
}
const courtConfig = {
//微信小程序appid=
//微信公众号APPID
publicAppId: "",
//请求接口
baseUrl: baseUrl,
//webSocket地址
socketUrl: socketUrl,
//平台名称
platformName: "uniApp-案例",
//项目logo
logoUrl: "https://qn.kemean.cn/upload/201906/19/3f3b4751f3ed4a97be804450c3ec4c79",
//页面分享配置
share: {
title: 'uniApp-案例',
// #ifdef MP-WEIXIN
path: '/pages/home/home', //小程序分享路径
// #endif
// #ifdef H5 || APP-PLUS
//公众号||APP分享
desc: "uniApp-案例", // 分享描述
link: "https://www.kemean.com/sameCity/18031201/index.html", // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: "http://qn.kemean.cn/upload/201901/28/23bedfc34597482292ecd6dc107f6342", // 分享图标
// #endif
}
};
//手机号验证正则表达式
const phoneRegular = /^1\d{10}$/;
//邮箱验证正则表达式
const mailRegular = /^\w+@\w+(\.[a-zA-Z]{2,3}){1,2}$/;
//密码验证正则表达式
const passwordRegular = /^[a-zA-Z0-9]{4,10}$/;
export default Object.assign({
phoneRegular,
mailRegular,
passwordRegular
}, courtConfig);
import base from '@/config/baseUrl';
import store from '@/store';
import $http from '@/config/requestConfig'
import { getLocation, setShare } from '@/plugins/wxJsSDK';
/**
* APP内嵌网页 -- 安卓IOS交互
*/
export const appMutual = (name, query = null, errCallback) => {
if (/android/i.test(navigator.userAgent)) {
if (window.shangChengView) {
if (typeof(query) == "object") {
query = JSON.stringify(query);
}
window.shangChengView[name](query);
} else {
errCallback && errCallback();
}
} else if (/ios|iphone|ipod|pad/i.test(navigator.userAgent)) {
if (window.webkit) {
window.webkit.messageHandlers[name].postMessage(query)
} else {
errCallback && errCallback();
}
}
};
/**
* 获取url中的参数
*/
export const getUrlData = () => {
var strs;
var url = window.location.href; //获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf("?") != -1) {
url = url.substr(url.indexOf("?"));
var str = url.substr(1);
strs = str.split("&");
for (var i = 0; i < strs.length; i++) {
var index = strs[i].indexOf("=");
theRequest[strs[i].slice(0, index)] = unescape(strs[i].slice(index + 1, strs[i].length));
}
}
return theRequest;
}
//公众号微信支付
export const wxPublicPay = (payInfo, callback) => {
$http.get("api/pay/v1/pay_public_wx", {
orderNo: payInfo.orderNo
}).then(data => {
let wxConfigObj = {
appId: data.appId,
timeStamp: data.timeStamp,
nonceStr: data.nonceStr,
package: data.package,
signType: data.signType,
paySign: data.sign
};
function onBridgeReady() {
window.WeixinJSBridge.invoke("getBrandWCPayRequest", wxConfigObj, function(
res
) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
callback && callback(res);
} else // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
if (res.err_msg == "get_brand_wcpay_request:cancel") {
// common.loadWarn('支付遇到问题,您取消了支付');
} else
if (res.err_msg == "get_brand_wcpay_request:fail") {
// common.myConfirm('支付遇到问题,您可能需要重新登录', '', function () {
// obj.wxLoginOAuth();
// });
}
});
}
if (typeof window.WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener("WeixinJSBridgeReady", onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent("WeixinJSBridgeReady", onBridgeReady);
document.attachEvent("onWeixinJSBridgeReady", onBridgeReady);
}
} else {
onBridgeReady();
}
});
};
// 浏览器判断
export const getBrowser = () => {
let ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == "micromessenger") {
return "微信";
}
return "其他";
};
// 获取地址信息(公众号获取 或 内嵌APP获取)
export const getLatLonH5 = function(successCallback, errCallback) {
if (getBrowser() == '微信') {
getLocation().then(res => {
successCallback(res);
}, err => {
console.log("位置信息错误", err);
errCallback("位置信息获取失败");
});
} else {
let clearTime = setTimeout(() => {
errCallback("获取经纬度超时");
}, 5000);
window.getAppLatLon = function(res) {
clearTimeout(clearTime);
successCallback(res);
}
appMutual("getAppLatLon", true);
}
};
// 公众号分享
export const publicShareFun = function (info = {},callback) {
if (getBrowser() == "微信") {
let shareInfo = {
title: info.shareTitle || info.title || base.share.title,
desc: info.desc || info.shareContent || base.share.desc,
imgUrl: info.imgUrl || info.shareImg || base.share.imgUrl,
link: info.link || info.shareUrl || base.share.link,
};
if (store.state.userInfo.token) {
if (shareInfo.link.indexOf("?") >= 0) {
shareInfo.link += "&recommendCode=" + store.state.userInfo.uid;
} else {
shareInfo.link += "?recommendCode=" + store.state.userInfo.uid;
}
}
setShare(shareInfo, callback);
}
}
//公众号获取code
function getLogin(type) {
let urlNow = encodeURIComponent(window.location.href);
let url =
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${
base.publicAppId
}&redirect_uri=${urlNow}&response_type=code&scope=snsapi_userinfo&state=${type}#wechat_redirect`;
window.location.replace(url);
}
//判断是否登录,登录处理
let isGetOpenId = true;
function getRecommendCode() {
var url = window.location.href;
let codeIndex = url.indexOf("recommendCode=");
if (codeIndex >= 0) {
let recommendCode = url.substr(codeIndex + 14);
if (recommendCode.indexOf("&") >= 0) {
return recommendCode.substr(0, recommendCode.indexOf("&"));
} else if (recommendCode.indexOf("?") >= 0) {
return recommendCode.substr(0, recommendCode.indexOf("?"));
} else if (recommendCode.indexOf("/") >= 0) {
return recommendCode.substr(0, recommendCode.indexOf("/"));
} else if (recommendCode.indexOf("#") >= 0) {
return recommendCode.substr(0, recommendCode.indexOf("#"));
}
return recommendCode;
} else {
return;
}
}
function getRecommend() {
var url = window.location.href;
let codeIndex = url.indexOf("recommend=");
if (codeIndex >= 0) {
let recommend = url.substr(codeIndex + 10);
if (recommend.indexOf("&") >= 0) {
return recommend.substr(0, recommend.indexOf("&"));
} else if (recommend.indexOf("?") >= 0) {
return recommend.substr(0, recommend.indexOf("?"));
} else if (recommend.indexOf("/") >= 0) {
return recommend.substr(0, recommend.indexOf("/"));
} else if (recommend.indexOf("#") >= 0) {
return recommend.substr(0, recommend.indexOf("#"));
}
return recommend;
} else {
return;
}
}
export const h5Login = function(type = "judge", callback) {
var getRequest = getUrlData();
let recommendCode = getRecommendCode();
if (recommendCode && recommendCode !== "null" && recommendCode !== "undefined") {
uni.setStorageSync("recommendCode", recommendCode);
} else {
let recommend = getRecommend();
if(recommend && recommend !== "null" && recommend !== "undefined"){
uni.setStorageSync("recommendCode", recommend);
}
}
if (getBrowser() == "微信") {
if (getRequest.code) {
if(isGetOpenId){
isGetOpenId = false;
let httpData = {
code: getRequest.code
};
let recommendCode = uni.getStorageSync("recommendCode");
if(recommendCode){
httpData.recommendUid = recommendCode;
store.commit("setChatScenesInfo", {
recommendCode: recommendCode
});
uni.removeStorageSync("recommendCode");
}
$http.get("api/open/v2/get_public_login", httpData)
.then(result => {
store.commit('setUserInfo', result);
//publicShare();
callback && callback();
uni.showToast({
title: "欢迎回来",
icon: "none"
});
},() => {
isGetOpenId = true;
});
}
} else {
getLogin(type);
}
} else {
if (getRequest.userToken) {
store.commit('setUserInfo', {
token: getRequest.userToken
});
$http.get("api/mime/v1/info").then(res => {
store.commit('setUserInfo', res);
callback && callback();
});
} else {
appMutual("jumpLogin", null, function() {
if (type == "force") {
uni.navigateTo({
url: "/pages/user/login"
});
}else{
uni.showModal({
title:"提示",
content:"您还未登录,请先登录~",
confirmText: "去登录",
cancelText: "再逛会",
success: (res) => {
if(res.confirm){
uni.navigateTo({
url: "/pages/user/login"
});
}
}
});
}
});
}
}
}
import store from '@/store';
import $http from '@/config/requestConfig'
import base from '@/config/baseUrl';
// #ifdef H5
import { h5Login } from '@/config/html5Utils';
// #endif
let code = "";
let loginStart = true;
let userInfo = {
token: ""
};
let lastPageUrl = "";
// 微信小程序登录
function onLogin(type = "judge",callback) {
//判断登录状态
if (loginStart) {
lastPageUrl = "";
loginStart = false;
const _this = this;
let platform;
// #ifdef MP-WEIXIN
platform = 'weixin';
// #endif
// #ifdef MP-ALIPAY
platform = 'alipay';
// #endif
// #ifdef MP-BAIDU
platform = 'baidu';
// #endif
uni.login({
provider: platform,
success: function(loginRes) {
if (loginRes.errMsg == 'login:ok') {
code = loginRes.code;
// 获取用户信息
uni.getUserInfo({
provider: platform,
success: function(infoRes) {
getUserInfo(infoRes, "", callback);
},
fail() {
if(type != "try"){
store.commit('setLoginPopupShow', true);
Object.defineProperty(userInfo, "token", {
get: function(val) {
return {};
},
set: function(newVal) {
callback && callback();
}
});
setTimeout(() => {
loginStart = true;
}, 2000);
}else{
loginStart = true;
}
}
});
}
}
});
}
}
//微信小程序获取用户信息
function getUserInfo(info, type, callback) {
let httpData = {
wxSmallCode: code, //小程序code
iv: info.iv, //小程序加密算法的初始向量
encryptedData: info.encryptedData //包括敏感数据在内的完整用户信息的加密数据
};
// store.state.chatScenesInfo里面是小程序二维码附带的信息
if(store.state.chatScenesInfo.recommendCode){
// 推荐码
httpData.recommendUid = store.state.chatScenesInfo.recommendCode;
}
$http.post('api/open/v1/login', httpData).then(res => {
loginStart = true;
store.commit('setUserInfo', res);
if (type == "authorized") {
userInfo.token = res.token;
store.commit('setLoginPopupShow', false);
}else{
callback && callback();
}
uni.showToast({
title: "登录成功"
});
}, err => {
loginStart = true;
});
}
//判断是否登录(所有端)
function judgeLogin(callback, type = "judge"){
if(store.state.chatScenesInfo.scene == 1154){
uni.showToast({
title: '请前往小程序使用完整服务',
icon: "none"
});
} else {
let storeUserInfo = store.state.userInfo;
if(!storeUserInfo.token){ // nvue页面读取不到vuex里面数据,将取缓存
storeUserInfo = uni.getStorageSync("userInfo");
}
if (type != "force" && storeUserInfo.token) {
callback();
} else if (storeUserInfo.token && !storeUserInfo.phone) {
if (type == "force") {
uni.navigateTo({
url: '/pages/user/bindPhone'
});
} else {
uni.showModal({
title: "提示",
content: "您还未绑定手机号,请先绑定~",
confirmText: "去绑定",
cancelText: "再逛会",
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/user/bindPhone'
});
}
}
});
}
} else {
// #ifdef MP
onLogin(type, callback);
// #endif
// #ifdef APP-PLUS
uni.showModal({
title: "登录提示",
content: "此时此刻需要您登录喔~",
confirmText: "去登录",
cancelText: "再逛会",
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: "/pages/user/login"
});
}
}
});
// #endif
// #ifdef H5
h5Login(type, () => {
callback();
});
// #endif
}
}
}
export {
onLogin,
getUserInfo,
judgeLogin
}
import request from "@/plugins/request";
import store from '@/store';
import base from '@/config/baseUrl';
// #ifdef H5
import {
h5Login
} from '@/config/html5Utils';
// #endif
// #ifdef MP-WEIXIN
import {
onLogin
} from '@/config/login';
// #endif
let version_code = '';
// #ifdef APP-PLUS
import { getCurrentNo } from '@/plugins/APPUpdate';
setTimeout(() => {
getCurrentNo(function(res){
console.log("版本号",res);
version_code = res.versionCode;
});
},200);
// #endif
//可以new多个request来支持多个域名请求
let $http = new request({
//接口请求地址
baseUrl: base.baseUrl,
//服务器本地上传文件地址
fileUrl: base.baseUrl,
// 服务器上传图片默认url
defaultUploadUrl: "api/common/v1/upload_image",
// 服务器上传文件名称
defaultFileName: "file",
//设置请求头(如果使用报错跨域问题,可能是content-type请求类型和后台那边设置的不一致)
header: {
'Content-Type': 'application/json;charset=UTF-8',
'project_token': base.projectToken, //项目token(可删除)
}
});
// 添加获取七牛云token的方法
$http.getQnToken = function(callback){
//该地址需要开发者自行配置(每个后台的接口风格都不一样)
$http.get("api/kemean/aid/qn_upload").then(data => {
/*
*接口返回参数:
*visitPrefix:访问文件的域名
*token:七牛云上传token
*folderPath:上传的文件夹
*region: 地区 默认为:SCN
*/
callback({
visitPrefix: data.visitPrefix,
token: data.token,
folderPath: data.folderPath
});
});
}
//请求开始拦截器
$http.requestStart = function(options) {
console.log("请求开始", options);
if (options.load) {
//打开加载动画
store.commit("setLoadingShow", true);
}
// 图片上传大小限制
if (options.method == "FILE" && options.maxSize) {
// 文件最大字节: options.maxSize 可以在调用方法的时候加入参数
let maxSize = options.maxSize;
for (let item of options.files) {
if (item.size > maxSize) {
setTimeout(() => {
uni.showToast({
title: "图片过大,请重新上传",
icon: "none"
});
}, 500);
return false;
}
}
}
// #ifdef APP-PLUS
// 添加当前版本号
if(version_code){
options.header['version_code'] = version_code;
}
// #endif
//请求前加入token
if (store.state.userInfo.token) {
options.header['user_token'] = store.state.userInfo.token;
};
return options;
}
//请求结束
$http.requestEnd = function(options, resolve) {
//判断当前接口是否需要加载动画
if (options.load) {
// 关闭加载动画
store.commit("setLoadingShow", false);
}
}
let loginPopupNum = 0;
//所有接口数据处理(此方法需要开发者根据各自的接口返回类型修改,以下只是模板)
// $http.dataFactory = async function(res) {
// console.log("接口请求数据", {
// url: res.url,
// resolve: res.response,
// header: res.header,
// data: res.data,
// method: res.method,
// });
// if (res.response.statusCode && res.response.statusCode == 200) {
// let httpData = res.response.data;
// if(typeof(httpData) == "string"){
// httpData = JSON.parse(httpData);
// }
// /*********以下只是模板(及共参考),需要开发者根据各自的接口返回类型修改*********/
// //判断数据是否请求成功
// if (httpData.success || httpData.code == 200) {
// // 返回正确的结果(then接受数据)
// return Promise.resolve(httpData.data);
// } else if (httpData.code == "1000" || httpData.code == "1001" || httpData.code == 1100) {
// // 失败重新请求(最多重新请求3次)
// // if(res.resend < 3){
// // let result = await $http.request({
// // url: res.url,
// // data: res.data,
// // method: res.method,
// // header: res.header,
// // isPrompt: res.isPrompt,//(默认 true 说明:本接口抛出的错误是否提示)
// // load: res.load,//(默认 true 说明:本接口是否提示加载动画)
// // isFactory: res.isFactory, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用)
// // resend: res.resend += 1 // 当前重发次数
// // });
// // // 返回正确的结果(then接受数据)
// // return Promise.resolve(result);
// // }
// // 返回错误的结果(catch接受数据)
// // return Promise.reject({
// // statusCode: 0,
// // errMsg: "【request】" + (httpData.info || httpData.msg)
// // });
// //----------------------------------------分割线---------------------------------------------------
// // 刷新token在重新请求(最多重新请求2次)
// // if(res.resend < 2){
// // let tokenResult = await $http.request({
// // url: "http://localhost:7001/api/common/v1/protocol", // 获取token接口地址
// // data: {
// // type: 1000
// // }, // 获取接口参数
// // method: "GET",
// // load: false,//(默认 true 说明:本接口是否提示加载动画)
// // });
// // // 储存token
// // store.commit("userInfo", tokenResult);
// // let result = await $http.request({
// // url: res.url,
// // data: res.data,
// // method: res.method,
// // header: res.header,
// // isPrompt: res.isPrompt,//(默认 true 说明:本接口抛出的错误是否提示)
// // load: res.load,//(默认 true 说明:本接口是否提示加载动画)
// // isFactory: res.isFactory, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用)
// // resend: res.resend += 1 // 当前重发次数
// // });
// // // 返回正确的结果(then接受数据)
// // return Promise.resolve(result);
// // }
// // 返回错误的结果(catch接受数据)
// // return Promise.reject({
// // statusCode: 0,
// // errMsg: "【request】" + (httpData.info || httpData.msg)
// // });
// store.commit("emptyUserInfo");
// // #ifdef MP-WEIXIN
// onLogin();
// // #endif
// // #ifdef H5
// h5Login("force");
// // #endif
// // #ifdef APP-PLUS
// var content = '此时此刻需要您登录喔~';
// if (httpData.code == "1000") {
// content = '此时此刻需要您登录喔';
// }
// if (loginPopupNum <= 0) {
// loginPopupNum++;
// uni.showModal({
// title: '温馨提示',
// content: content,
// confirmText: "去登录",
// cancelText: "再逛会",
// success: function(res) {
// loginPopupNum--;
// if (res.confirm) {
// uni.navigateTo({
// url: "/pages/user/login"
// });
// }
// }
// });
// }
// // #endif
// // 返回错误的结果(catch接受数据)
// return Promise.reject({
// statusCode: 0,
// errMsg: "【request】" + (httpData.info || httpData.msg)
// });
// } else if (httpData.code == "1004") {
// if (loginPopupNum <= 0) {
// loginPopupNum++;
// uni.showModal({
// title: "提示",
// content: "您还未绑定手机号,请先绑定~",
// confirmText: "去绑定",
// cancelText: "再逛会",
// success: (res) => {
// loginPopupNum--;
// if (res.confirm) {
// uni.navigateTo({
// url: '/pages/user/bindPhone'
// });
// }
// }
// });
// }
// // 返回错误的结果(catch接受数据)
// return Promise.reject({
// statusCode: 0,
// errMsg: "【request】" + (httpData.info || httpData.msg)
// });
// } else { //其他错误提示
// if (res.isPrompt) {
// uni.showToast({
// title: httpData.info || httpData.msg,
// icon: "none",
// duration: 3000
// });
// }
// // 返回错误的结果(catch接受数据)
// return Promise.reject({
// statusCode: 0,
// errMsg: "【request】" + (httpData.info || httpData.msg)
// });
// }
// /*********以上只是模板(及共参考),需要开发者根据各自的接口返回类型修改*********/
// } else {
// // 返回错误的结果(catch接受数据)
// return Promise.reject({
// statusCode: res.response.statusCode,
// errMsg: "【request】数据工厂验证不通过"
// });
// }
// };
$http.requestError = function(e){
// e.statusCode == 0 是参数效验错误抛出的
if(e.statusCode == 0){
throw e;
} else {
uni.showToast({
title: "网络错误,请检查一下网络",
icon: "none"
});
store.commit("setLoadingShow", false);
}
}
export default $http;
import base from '@/config/baseUrl';
import store from '@/store';
class socket {
constructor(options) {
//地址
this.socketUrl = base.socketUrl;
this.socketStart = false;
this.monitorSocketError();
this.monitorSocketClose();
this.socketReceive();
}
init(callback) {
const _this = this;
if (base.socketUrl) {
if(this.socketStart){
console.log('webSocket已经启动了');
}else{
uni.connectSocket({
url: this.socketUrl,
method: 'GET'
});
uni.onSocketOpen((res) => {
this.socketStart = true;
callback && callback();
console.log('WebSocket连接已打开!');
});
setTimeout(() => {
_this.getHeartbeat();
}, 5000);
}
}else{
console.log('config/baseUrl socketUrl为空');
}
}
//Socket给服务器发送消息
send(data, callback) {
const _this = this;
if (store.state.userInfo.uid) {
data.userUid = store.state.userInfo.uid;
}
console.log(data);
uni.sendSocketMessage({
data: JSON.stringify(data),
success: () => {
callback && callback(true);
},
fail: () => {
callback && callback(false);
}
});
}
//Socket接收服务器发送过来的消息
socketReceive() {
const _this = this;
uni.onSocketMessage(function(res) {
let data = JSON.parse(res.data);
console.log('收到服务器内容:', data);
_this.acceptMessage && _this.acceptMessage(data);
});
}
//关闭Socket
closeSocket() {
uni.closeSocket();
_this.socketStart = false;
}
//监听Socket关闭
monitorSocketClose() {
const _this = this;
uni.onSocketClose(function(res) {
console.log('WebSocket 已关闭!');
_this.socketStart = false;
setTimeout(function() {
_this.init();
}, 3000);
});
}
//监听Socket错误
monitorSocketError() {
const _this = this;
uni.onSocketError(function(res) {
_this.socketStart = false;
console.log('WebSocket连接打开失败,请检查!');
});
}
//心跳
getHeartbeat() {
const _this = this;
this.send({
type: "心跳",
userUid: store.state.userInfo.userUid
}, (val) => {
setTimeout(() => {
if (val) {
_this.getHeartbeat();
} else {
_this.init();
}
}, 10000);
});
}
};
const mySocket = new socket();
export default mySocket;
import $http from '@/config/requestConfig'
import store from '@/store';
import base from '@/config/baseUrl';
import { getAppWxLatLon } from '@/plugins/utils';
// #ifdef H5
import { getLatLonH5, publicShareFun, wxPublicPay, getBrowser,appMutual } from '@/config/html5Utils';
// 公众号分享
export const publicShare = publicShareFun;
// #endif
// #ifdef APP-PLUS
import appShareFun, {closeShare} from '@/plugins/share';
// APP分享
export const appShare = function(data,callbcak){
return appShareFun({
shareTitle: data.shareTitle || base.share.title,
shareUrl: data.shareUrl || base.share.link,
shareContent: data.shareContent || base.share.desc,
shareImg: data.shareImg || base.share.imgUrl,
},callbcak);
};
export const closeAppShare = closeShare;
// #endif
// #ifdef MP-WEIXIN
// 微信小程序分享
export const wxShare = function (data = {}) {
let shareInfo = {
title: data.title || base.share.title,
};
if(data.path && typeof(data.path) == "string"){
shareInfo.path = data.path;
} else if(data.path != 1){
shareInfo.path = "pages/home/home";
}
if(data.imageUrl){
shareInfo.imageUrl = data.imageUrl;
}
let userInfo = store.state.userInfo;
if(!(userInfo && userInfo.uid)){
userInfo = uni.getStorageSync("userInfo");
}
if (userInfo && userInfo.uid) {
if(data.query && typeof(data.query) == "object"){
data.query.recommendCode = userInfo.uid;
} else {
data.query = {
recommendCode: userInfo.uid
};
}
}
if(data.query && typeof(data.query) == "object"){
Object.keys(data.query).forEach((key,index) => {
if(index > 0 && shareInfo.query){
shareInfo.query += "&" + key + "=" + data.query[key];
shareInfo.path += "&" + key + "=" + data.query[key];
} else {
shareInfo.query = key + "=" + data.query[key];
shareInfo.path += "?" + key + "=" + data.query[key];
}
});
}
return shareInfo;
}
// #endif
//支付(APP微信支付、APP支付宝支付、微信小程序支付)
export const setPay = function(payInfo, callback) {
let httpUrl = "";
if (payInfo.type == 'wxpay') {
// APP微信支付
httpUrl = 'api/pay/v1/pay_sign_wx'
} else if (payInfo.type == 'alipay') {
// APP支付宝支付
httpUrl = 'api/pay/v1/pay_sign_ali'
} else if (payInfo.type == 'smallPay') {
// 微信小程序支付
httpUrl = 'api/pay/v1/small_pay_sign_wx'
}
$http.get(httpUrl, {
orderNo: payInfo.orderNo
}).then(data => {
let payData = {
success: function(res) {
callback && callback({
success: true,
data: res
});
console.log('success:' + JSON.stringify(res));
},
fail: function(err) {
callback && callback({
success: false,
data: err
});
console.log('fail:' + JSON.stringify(err));
}
};
if (payInfo.type == 'smallPay') {
// 小程序
payData.provider = 'wxpay';
payData.timeStamp = data.timeStamp;
payData.nonceStr = data.nonceStr;
payData.package = data.package;
// payData.package = "prepay_id=" + data.prepayid;
payData.signType = "MD5";
payData.paySign = data.sign;
} else if (payInfo.type == 'wxpay') {
// app微信
payData.provider = 'wxpay';
payData.orderInfo = data;
} else if (payInfo.type == 'alipay') {
// app 支付宝
payData.provider = 'alipay';
payData.orderInfo = data;
} else if (payInfo.type == 'baidu') {
payData.provider = 'baidu';
payData.orderInfo = data;
}
console.log("支付参数", payData);
uni.requestPayment(payData);
}, err => {
callback && callback({
success: false,
data: err
});
});
}
// 支付统一分配
export const setPayAssign = function(orderInfo, callback) {
orderInfo.price = orderInfo.price || orderInfo.pricePay;
orderInfo.title = orderInfo.title || orderInfo.orderTitle;
//支付
// #ifdef APP-PLUS
uni.navigateTo({
url: '/pages/home/weChatPay?orderNo=' + orderInfo.orderNo + '&price=' + orderInfo.price + '&title=' + orderInfo.title
});
// #endif
// #ifdef MP-WEIXIN
setPay({
...orderInfo,
type: "smallPay"
}, res => {
if(res.success){
uni.redirectTo({
url: "/pages/shopCar/paySuccess?orderNo=" + orderInfo.orderNo
});
}
});
// #endif
// #ifdef H5
if (getBrowser() === '微信') {
wxPublicPay({
orderNo: orderInfo.orderNo
}, function(){
uni.redirectTo({
url: "/pages/shopCar/paySuccess?orderNo=" + orderInfo.orderNo
});
});
} else {
appMutual('setJumpPay', orderInfo);
}
// #endif
}
// 获取地址信息 (微信小程序、APP、公众号)
export const getLatLon = function(tip){
return new Promise((resolve, reject) => {
const successProcess = function(res){
store.commit("setCurrentAddress", {
latitude: res.latitude,
longitude: res.longitude
});
resolve(res);
};
const errProcess = function(err){
reject(err);
if(tip){
uni.showToast({
title: err,
icon: "none"
});
}
};
// #ifdef H5
getLatLonH5(successProcess,errProcess);
// #endif
// #ifndef H5
getAppWxLatLon(successProcess,errProcess);
// #endif
});
}
import Vue from 'vue'
import App from './App'
//数据管理中心
import store from '@/store'
Vue.prototype.$store = store;
// 工具
import '@/plugins/utils.js';
//权限配置中心
import base from '@/config/baseUrl'
import * as sql from '@/plugins/sqlite/index.js'
Vue.prototype.$sql = sql;
Vue.prototype.$base = base;
//挂载全局http请求
import $http from '@/config/requestConfig'
Vue.prototype.$http = $http;
// #ifdef MP-WEIXIN
//挂载全局微信分享
import { wxShare } from '@/config/utils'
Vue.prototype.wxShare = wxShare;
// #endif
//判断是否登录
import { judgeLogin } from '@/config/login';
Vue.prototype.judgeLogin = judgeLogin;
Vue.config.productionTip = false;
// #ifdef H5
//微信SDK
import '@/plugins/wxJsSDK.js';
// #endif
//全局组件
import zhouWeiNavBar from "@/components/common/zhouWei-navBar";
Vue.component("nav-bar", zhouWeiNavBar);
import publicModule from "@/components/common/public-module.vue";
Vue.component("public-module", publicModule);
import MescrollBody from "@/components/common/mescroll-uni/mescroll-body.vue";
Vue.component("mescroll-body", MescrollBody);
App.mpType = 'app'
const app = new Vue({
store,
...App
})
app.$mount();
{
"name" : "会议通",
"appid" : "__UNI__9863234",
"description" : "干部任免会议通",
"transformPx" : false,
"icons" : [
{
"sizes" : "分辨率,192x192",
"src" : "图片路径"
}
],
"versionName" : "1.0.0",
"versionCode" : 100,
"app-plus" : {
"screenOrientation" : [
"portrait-primary",
"landscape-primary",
"portrait-secondary",
"landscape-secondary"
],
"compatible" : {
"ignoreVersion" : true
},
"privacy" : {
"prompt" : "template",
"template" : {
"title" : "用户协议和隐私政策",
"message" : "请你务必审慎阅读、充分理解“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a>《用户协议》</a>和<a >《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept" : "同意",
"buttonRefuse" : "暂不同意"
}
},
"modules" : {
"Maps" : {},
"Payment" : {},
"Push" : {},
"OAuth" : {},
"Messaging" : {},
"Share" : {},
"VideoPlayer" : {},
"SQLite" : {}
},
"distribute" : {
"android" : {
"permissionPhoneState" : {
"request" : "none",
"prompt" : "为保证您正常、安全地使用,需要获取设备识别码(部分手机提示为获取手机号码)使用权限,请允许。"
},
"permissionExternalStorage" : {
"request" : "always",
"prompt" : "应用保存运行状态等信息,需要获取读写手机存储(系统提示为访问设备上的照片、媒体内容和文件)权限,请允许。"
},
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
},
"sdkConfigs" : {
"ad" : {}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
},
"splashscreen" : {
"androidStyle" : "common"
}
},
"compilerVersion" : 3,
"nvueLaunchMode" : "fast"
},
// 5+App特有相关
"quickapp" : {},
// 快应用特有相关
"mp-weixin" : {
"setting" : {
"urlCheck" : false,
"es6" : true,
"postcss" : true,
"minified" : true
},
"usingComponents" : true,
"appid" : "wx4277c1145515b274",
"permission" : {
"scope.userLocation" : {
"desc" : "你的位置信息将用于小程序位置接口的效果展示"
}
}
},
"h5" : {
"template" : "template.h5.html",
"router" : {
"mode" : "hash",
"base" : "/"
},
"optimization" : {
"treeShaking" : {
"enable" : true
}
}
}
}
// 小程序特有相关
{
"pages": [
{
"path": "pages/home/home",
"style": {
"navigationBarTitleText": "干部人事任免建议方案",
"enablePullDownRefresh": true,
"app-plus": {
"bounce": "none",
"titleNView": {
"buttons": [
{
"type": "none",
"text": " ",
"float": "right"
}
]
}
}
}
},
{
"path": "pages/service/service",
"style": {
"navigationBarTitleText": "接口域名配置"
}
},
{
"path": "pages/my/my",
"style": {
"navigationBarTitleText": "个人中心"
}
},
{
"path": "pages/user/bind",
"style": {
"navigationBarTitleText": "绑定"
}
},
{
"path": "pages/user/protocol",
"style": {
"navigationBarTitleText": "协议"
}
},
{
"path": "pages/home/webView",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "会议",
"enablePullDownRefresh": false
}
},
{
"path": "pages/file/file",
"style": {
"navigationBarTitleText": "附件",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {
"navigationBarTitleText": "人事调整方案信息共享程序",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#1881e1",
"backgroundColor": "#FFFFFF"
},
// "tabBar": {
// "color": "#444444",
// "selectedColor": "#FF80AB",
// "borderStyle": "black",
// "backgroundColor": "#ffffff",
// "list": [
// {
// "pagePath": "pages/home/home",
// "iconPath": "static/icon/tab/icon_home.png",
// "selectedIconPath": "static/icon/tab/icon_home_c.png",
// "text": "确报"
// },
// {
// "pagePath": "pages/service/service",
// "iconPath": "static/icon/tab/icon_wode.png",
// "selectedIconPath": "static/icon/tab/icon_wode_c.png",
// "text": "自助服务"
// },
// {
// "pagePath": "pages/my/my",
// "iconPath": "static/icon/tab/icon_xiaoxi.png",
// "selectedIconPath": "static/icon/tab/icon_xiaoxi_c.png",
// "text": "个人中心"
// }
// ]
// },
"condition": { //模式配置,仅开发期间生效
"current": 0, //当前激活的模式(list 的索引项)
"list": [
{
"name": "测试页", //模式名称
"path": "pages/home/home", //启动页面,必选
"query": "" //启动参数,在页面的onLoad函数里面得到
}
]
}
}
\ No newline at end of file
<template>
<view>
<!-- <nav-bar title="会议详情"></nav-bar> -->
<!-- 公共组件-每个页面必须引入 -->
<public-module></public-module>
<wyb-table
ref="table"
:headers="headers"
:contents="contents"
height="600vh"
width="100vw"
:url-col="urlCol"
/>
<notData v-if="!contents.length"/>
</view>
</template>
<script>
export default {
data() {
return {
tableName: 'buss_person',
headers: [
{
label: '序号',
key: 'index',
},
{
label: '姓名',
key: 'name',
},
{
label: '性别',
key: 'sex',
},
{
label: '出生年月',
key: 'birth',
},
{
label: '政治面貌',
key: 'zzmm',
},
{
label: '学历',
key: 'xl',
},
{
label: '现任职务(职级)',
key: 'xrzw',
},
{
label: '拟任职务(职级)',
key: 'nrzw',
},
{
label: '拟免职务(职级)',
key: 'nmzw',
},
{
label: '备注',
key: 'remark',
},
{
label: '附件',
key: 'url',
},
],
contents: [
{
index: 1,
name: '刘一',
sex: '',
birth: '',
zzmm: '',
xl: '本科',
xrzw: '99',
nrzw: '',
nmzw: '',
remark: '',
link: ['查看', '/pages/file/file', { id: '' }],
},
],
urlCol: [
{
type: 'route',
key: 'url',
},
{
type: 'http',
key: 'link',
},
],
}
},
onLoad(e) {},
onShow() {},
onLoad: function (option) {
console.log(option.id)
this.loadData(option.id)
uni.setNavigationBarTitle({
title: option.name,
})
},
//方法
methods: {
onPageJump(url) {
uni.navigateTo({
url: url,
})
},
loadData(id) {
console.log('🐛 ~ file: detail.vue ~ line 109 ~ loadData ~ id', id)
this.$sql
.selectDataList('share', this.tableName, { meeting_id: id })
.then((res) => {
console.log(res)
let arr = []
res &&
res.map((item, index) => {
arr.push({
index: index + 1,
...item,
url: ['查看', '/pages/file/file', { id: item.id }],
})
})
this.contents = arr
})
},
},
//页面下来刷新
onPullDownRefresh() {},
//页面上拉触底
onReachBottom() {},
//用户点击分享
onShareAppMessage(e) {
return this.wxShare()
},
}
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
</style>
<template>
<view>
<!-- <nav-bar title="附件"></nav-bar> -->
<!-- 公共组件-每个页面必须引入 -->
<tabs @tabClick="change" :tabData="list"></tabs>
<view v-for="item in img">
<img v-bind:src="item.image" alt="" style="width:100vw" />
</view>
<notData v-if="!list.length"/>
<public-module></public-module>
</view>
</template>
<script>
import tabs from '@/components/tabs/tabs.vue'
export default {
components: { tabs },
data() {
return {
img: [],
tableName: 'buss_annex',
list: [],
}
},
onLoad(e) {},
onShow() {},
onLoad: function (option) {
console.log(option.id)
this.loadData(option.id)
},
//方法
methods: {
change(item) {
// this.active = item.index
this.img=[]
this.getImg(item.id)
},
onPageJump(url) {
uni.navigateTo({
url: url,
})
},
getImg(id) {
this.$sql
.selectDataList('share', 'buss_annex_pic', { annex_id: id })
.then((resq) => {
let arr = []
resq.map(item=>{
arr.push({ item,image:"data:image/png;base64,"+item.image})
}
)
this.img=arr
})
},
loadData(id) {
this.$sql.selectDataList('share', this.tableName, {person_id:id}).then((res) => {
console.log(res)
let arr = []
res.map((item, index) => {
if(index==0){
this.getImg(item.id)
}
arr.push({ title: item.name,id:item.id, item: {...item,index:index}, img: [] })
})
this.list = arr
})
},
},
//页面下来刷新
onPullDownRefresh() {},
//页面上拉触底
onReachBottom() {},
//用户点击分享
onShareAppMessage(e) {
return this.wxShare()
},
}
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
</style>
<template>
<view class="">
<!-- <nav-bar title="人事调整方案信息共享程序"></nav-bar> -->
<!-- 公共组件-每个页面必须引入 -->
<public-module></public-module>
<!-- <text @click="onPageJump('/pages/my/my')"> 测试页{{last_update_time}}</text> -->
<!-- @down="downCallback"
:down="downOption"
@up="upCallback"
@init="mescrollInit" -->
<!-- <mescroll-body
ref="mescrollRef"
:down="downOption"
@up="upCallback"
@down="downCallback"
>
<view
class="news_list"
v-for="item in dataList"
:key="item.id"
@click="onPageJump('/pages/detail/detail?id=' + item.id)"
>
<view class="title"
><text>{{ item.name }}</text>
<view class="title_link">
<text>查看</text>
</view></view
>
</view>
</mescroll-body> -->
<!-- <button type="primary" @click="laodDb">加载数据</button> -->
<view
class="news_list"
v-for="item in dataList"
:key="item.id"
@click="
onPageJump('/pages/detail/detail?name=' + item.name + '&id=' + item.id)
"
>
<view class="title"
><text>{{ item.name }}</text>
<view class="title_link">
<text>查看</text>
</view></view
>
</view>
<notData v-if="!dataList.length" />
</view>
</template>
<script>
import MescrollMixin from '@/components/common/mescroll-uni/mescroll-mixins.js'
export default {
mixins: [MescrollMixin], // 使用mixin (在main.js注册全局组件)
data() {
return {
downOption: {
auto: false, //是否在初始化后,自动执行downCallback; 默认true
},
last_update_time: '',
tableName: 'buss_meeting',
dataList: [],
click: 10,
}
},
onShow() {
this.click = 10
},
onNavigationBarButtonTap(e) {
console.log('success', e)
this.click--
if (this.click === 0) {
uni.navigateTo({
url: '/pages/service/service',
})
}
},
async onLoad() {
uni.stopPullDownRefresh()
this.$http.get('sys/getLastTime').then((res) => {
this.last_update_time = res.data.data
})
await this.getDBInfo()
},
//页面下来刷新
async onPullDownRefresh() {
uni.stopPullDownRefresh()
this.$http.get('sys/getLastTime').then((res) => {
this.last_update_time = res.data.data
}).finally(()=>this.$store.commit('setLoadingShow', false))
await this.getDBInfo()
},
methods: {
// 获取数据库文件
getDBInfo() {
let self = this
self.$sql.isTable('share', 'sys_conf').then((res) => {
console.log(res)
if (res) {
self.$sql.selectDataList('share', 'sys_conf').then((resq) => {
console.log(
'🐛 ~ file: home.vue ~ line 86 ~ self.$sql.isTable ~ resq',
resq
)
if (
resq.length &&
resq[0].last_update_time == self.last_update_time
) {
self.openDb()
self.loadData()
console.log('已经同步')
} else {
self.laodDb()
}
})
} else {
self.laodDb()
}
}).catch(()=>{
self.laodDb()
}).finally(()=>this.$store.commit('setLoadingShow', false))
// uni.getFileInfo({
// filePath: '_doc/uniapp_save/share.sqlite',
// success: function (reson) {
// console.log(reson)
// },
// fail: function (err) {
// console.log(err)
// self.laodDb()
// },
// })
},
laodDb() {
console.log(11)
this.$store.commit('setLoadingShow', true)
let self = this
// let url = 'https://lajw.watone.com.cn/linanjiwei/share/share'
// let url = 'http://192.168.11.176:7010/sys/downloadDB'
const value = uni.getStorageSync('HOST') || 'http://192.168.1.103:7010'
let url = `${value}/sys/downloadDB`
uni.removeSavedFile({
filePath: '_doc/uniapp_save/share.sqlite',
complete: function (res) {
console.log(res)
uni.request({
url: url,
method: 'HEAD',
success: function (response) {
let dtask = plus.downloader.createDownload(url, {
filename: '_doc/uniapp_save/share.sqlite',
})
dtask.addEventListener('statechanged', (task) => {
if (!dtask) {
return
}
switch (task.state) {
case 1:
console.log('开始下载')
break
case 2:
console.log('链接到服务器...')
break
case 3:
break
case 4:
console.log('监听下载完成')
uni.stopPullDownRefresh()
setTimeout(function () {
self.$sql.openDb('share').then(() => {
self.$sql
.selectDataList('share', self.tableName, {
status: 1,
})
.then((res) => {
console.log(res)
self.dataList = res
uni.stopPullDownRefresh()
self.$store.commit('setLoadingShow', false)
self.mescroll.endSuccess(res.length)
})
})
}, 2000)
break
}
})
dtask.start()
},
})
},
})
},
// 打开数据库
openDb() {
if (this.$sql.isOpen('share')) return
this.$sql.openDb('share').then((res) => {
console.log(res)
})
},
onPageJump(url) {
uni.navigateTo({
url: url,
})
},
/*下拉刷新的回调 */
downCallback() {
//联网加载数据
this.loadData(1)
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
//联网加载数据
this.loadData()
},
loadData(id) {
this.$sql
.selectDataList('share', this.tableName, { status: 1 })
.then((res) => {
console.log(res)
this.dataList = res
uni.stopPullDownRefresh()
this.mescroll.endSuccess(res.length)
this.$store.commit('setLoadingShow', false)
})
// this.$http
// .post('api/articles/v1/articles_list', {
// pageNo: pageNo,
// pageSize: 10
// },{
// load:false
// }).then(res => {
// uni.stopPullDownRefresh();
// //联网成功的回调,隐藏下拉刷新和上拉加载的状态;
// //mescroll会根据传的参数,自动判断列表如果无任何数据,则提示空;列表无下一页数据,则提示无更多数据;
// //方法一(推荐): 后台接口有返回列表的总页数 totalPage
// this.mescroll.endByPage(res.data.length, res.pages); //必传参数(当前页的数据个数, 总页数)
// //方法二(推荐): 后台接口有返回列表的总数据量 totalSize
// //this.mescroll.endBySize(res.data.length, res.count); //必传参数(当前页的数据个数, 总数据量)
// //方法三(推荐): 您有其他方式知道是否有下一页 hasNext
// //this.mescroll.endSuccess(res.data.length, hasNext); //必传参数(当前页的数据个数, 是否有下一页true/false)
// //方法四 (不推荐),会存在一个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据当前页的数据个数判断,则需翻到第三页才会知道无更多数据.
// // this.mescroll.endSuccess(res.data.length);
// if (pageNo == 1) {
// this.dataList = res.data;
// } else {
// this.dataList = this.dataList.concat(res.data);
// }
// }).catch(() => {
// //联网失败, 结束加载
// this.mescroll.endErr();
// });
},
onJumpWebview(url, id) {
window.open(url)
},
},
}
</script>
<style scoped lang="scss">
@import '@/style/mixin.scss';
/*说明*/
.notice {
font-size: 30upx;
padding: 40upx 0;
border-bottom: 1upx solid #eee;
text-align: center;
}
/*展示上拉加载的数据列表*/
.news_list {
padding: 30rpx;
background-color: #fff;
margin-bottom: 20rpx;
.title {
font-size: 28rpx;
color: #333;
font-weight: bold;
@include bov(2);
}
.content {
margin-top: 10rpx;
font-size: 24rpx;
color: #999;
@include bov(3);
}
.title_link {
display: inline-block;
text-align: right;
float: right;
color: #1881e1;
}
}
</style>
<template>
<view>
<nav-bar title="模板页面"></nav-bar>
<!-- 公共组件-每个页面必须引入 -->
<public-module></public-module>
</view>
</template>
<script>
export default {
data() {
return {
};
},
onLoad(e) {
},
onShow() {
},
//方法
methods: {
onPageJump(url) {
uni.navigateTo({
url: url
});
}
},
//页面下来刷新
onPullDownRefresh() {},
//页面上拉触底
onReachBottom() {},
//用户点击分享
onShareAppMessage(e) {
return this.wxShare();
}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
</style>
<template>
<web-view :src="webViewUrl"></web-view>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
data() {
return {
};
},
//第一次加载
onLoad(e) {
},
//页面显示
onShow() {},
computed: {
...mapState(['webViewUrl'])
},
//方法
methods: {},
//页面隐藏
onHide() {},
//页面卸载
onUnload() {},
//页面下来刷新
onPullDownRefresh() {},
//页面上拉触底
onReachBottom() {},
//用户点击分享
onShareAppMessage(e) {
return this.wxShare();
}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
</style>
<template>
<view>
<!-- <nav-bar backState="2000" title="我的"></nav-bar> -->
<!-- 公共组件-每个页面必须引入 -->
<public-module></public-module>
<button type="primary" @click="laodDb">加载远程DB</button>
{{ res }}
<view class=""> 表名 </view>
<radio-group @change="radioChange">
<label
class="uni-list-cell uni-list-cell-pd"
v-for="(item, index) in tableList"
:key="index"
>
<view>
<radio :value="index" :checked="index == radioIndex" />{{ item.name }}
</view>
</label>
</radio-group>
<button type="primary" @click="dbType">检查数据库打开状态</button>
<button type="primary" @click="openDb">打开数据库</button>
<button type="primary" @click="closeDb">关闭数据库</button>
<input
class="input_box"
type="text"
value=""
v-model="valInp"
placeholder="请输入聊天内容"
/>
<button type="primary" @click="addSql">添加数据</button>
<button type="primary" @click="setSql">修改数据</button>
<input
class="input_box"
type="text"
value=""
v-model="valTab"
placeholder="请输入表名"
/>
<button type="primary" @click="addExecuteSql">添加表</button>
<button type="primary" @click="tapMerge">合并数据</button>
<button type="primary" @click="delSql">删除数据库数据</button>
<button type="primary" @click="getTab">查询所有数据表名</button>
<button type="primary" @click="tapIsTab">查询表是否存在</button>
<button type="primary" @click="getNum">获取表数据总条数</button>
<button type="primary" @click="getSql">获取数据库数据</button>
<button type="primary" @click="getBranch">获取分页数据库数据+排序</button>
{{ chatTableList }}
{{ tableList }}
</view>
</template>
<script>
export default {
data() {
return {
res: null,
valTab: '',
radioIndex: 0,
valInp: '',
chatTableList: [],
tableList: [],
chatTableListLength: 0,
tableName: '',
}
},
async onLoad() {
await this.openDb()
await this.getTab()
},
methods: {
onLine() {
var img = new Image()
img.src = 'https://www.baidu.com/favicon.ico?_t=' + Date.now()
return new Promise((resolve, reject) => {
img.onload = function () {
resolve(true)
}
img.onerror = function () {
resolve(false)
}
})
},
laodDb() {
console.log(11)
// let url = 'https://lajw.watone.com.cn/linanjiwei/share/share'
// let url = 'http://192.168.1.103:7010/sys/downloadDB'
const value = uni.getStorageSync('HOST') || 'http://192.168.11.176:7010'
let url = `${value}/sys/downloadDB`
uni.removeSavedFile({
filePath: '_doc/uniapp_save/share.sqlite',
complete: function (res) {
console.log(res)
uni.request({
url: url,
method: 'HEAD',
success: function (response) {
console.log(
'🐛 ~ file: my.vue ~ line 79 ~ laodDb ~ response',
response
)
let dtask = plus.downloader.createDownload(
url,
{
filename: '_doc/uniapp_save/share.sqlite',
},
function (download, status) {
if (status == 200) {
plus.runtime.openFile(download.filename)
} else {
//下载失败
plus.downloader.clear() //清除下载任务
}
}
)
dtask.start()
},
})
},
})
// uni.downloadFile({
// // url: 'http://192.168.11.176:7010/sys/downloadDB', //下载地址接口返回
// url: 'https://lajw.watone.com.cn/linanjiwei/share/share',//下载地址接口返回
// success: (data) => {
// console.log('🐛 ~ file: my.vue ~ line 77 ~ laodDb ~ data', data)
// if (data.statusCode === 200) {
// //文件保存到本地
// uni.saveFile({
// tempFilePath: data.tempFilePath, //临时路径
// success: function (res) {
// console.log('🐛 ~ file: my.vue ~ line 82 ~ laodDb ~ res', res)
// uni.showToast({
// icon: 'none',
// mask: true,
// title: '文件已保存:' + res.savedFilePath, //保存路径
// duration: 3000,
// })
// },
// })
// }
// },
// fail: (err) => {
// console.log(err)
// uni.showToast({
// icon: 'none',
// mask: true,
// title: '失败请重新下载',
// })
// },
// })
},
radioChange(val) {
this.tableName = this.tableList[val.detail.value].name
console.log(this.tableList)
},
tapMerge() {
let tabs = [
{
local_id: '3_wtsu83kox2',
id: '3_wtsu83koxx',
chat_friend_id: '兔兔',
content: 2,
},
{
local_id: '3_hq5uyx9vrd',
id: '3_hq5uyx9vrd',
chat_friend_id: '兔兔',
content: 2,
},
]
// 只会插入不重复的
this.$sql.mergeSql('share', this.tableName, tabs).then((res) => {
console.log(res)
})
},
tapIsTab() {
this.$sql.isTable('share', this.tableName).then((res) => {
console.log(res)
})
},
getTab() {
this.$sql.getTable('share').then((res) => {
console.log(res)
this.tableList = res
})
},
addExecuteSql() {
console.log(this.valTab)
if (!this.valTab || this.valTab == '') return
this.$sql.addTab('share', this.valTab).then((res) => {
console.log(res)
this.valTab = ''
this.getTab()
})
},
delSql() {
this.$sql
.deleteInformationType('share', this.tableName, {
chat_i: 2,
})
.then((res) => {
console.log(res)
})
},
dbType() {
uni.showToast({
icon: 'none',
title: this.$sql.isOpen('share') ? '数据库已打开' : '数据库已关闭',
})
},
closeDb() {
this.$sql.closeSQL().then((res) => {
console.log(res)
})
},
openDb() {
if (this.$sql.isOpen('share')) return
this.$sql.openDb('share').then((res) => {
console.log(res)
})
},
setSql() {
this.$sql
.updateSQL(
'share',
this.tableName,
{
chat_friend_id: 'Texas3333666',
content: '3333666',
},
'local_id',
'3_bpu8sufahoe'
)
.then((res) => {
console.log('1111')
})
},
addSql() {
console.log(this.valInp)
console.log(this.tableName)
let _this = this
if (this.valInp == '') {
uni.showToast({
icon: 'none',
title: '请输入聊天内容',
})
return
}
let objMsg = {}
let local_id = 3 + '_' + this.getLocalId()
let content = {}
content.voiceTime = 0
content.url = ''
content.tempFilePath = ''
content.text = this.valInp
objMsg.id = local_id
objMsg.chat_friend_id = 3
objMsg.user_id = 1
objMsg.local_id = local_id
objMsg.is_read = 0
objMsg.status = 0
objMsg.send_time = parseInt(new Date().getTime() / 1000)
objMsg.msg_type = 1
objMsg.content = JSON.stringify(content)
this.$sql
.addTabItem('share', _this.tableName, {
id: objMsg.id,
local_id: objMsg.id,
content: '2',
chat_friend_id: _this.valInp,
})
.then((res) => {
_this.valInp = ''
console.log(_this.valInp)
})
},
// 获取本地id
getLocalId() {
return Math.random().toString(36).slice(2)
},
getSql() {
this.$sql.selectDataList('share', this.tableName, {}).then((res) => {
console.log(res)
this.chatTableList = res
})
},
getBranch() {
this.$sql
.getDataList('share', this.tableName, 1, 20, 'chat_i', 'desc')
.then((res) => {
console.log(res)
this.chatTableList = res
})
.catch((err) => {
console.log(err)
})
},
getNum() {
this.$sql.getCount('share', this.tableName).then((res) => {
this.chatTableListLength = res[0].num
})
},
},
//页面下来刷新
onPullDownRefresh() {},
//页面上拉触底
onReachBottom() {},
//用户点击分享
onShareAppMessage(e) {
return this.wxShare()
},
}
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
</style>
<template>
<view>
<nav-bar backState="2000" title="接口配置服务"></nav-bar>
<!-- 公共组件-每个页面必须引入 -->
<public-module></public-module>
<input :value="value" @input="change"></input>
<button @click="Yuming"> 更改域名</button>
<button @click="onPageJump('/pages/my/my')"> 数据库</button>
</view>
</template>
<script>
export default {
data() {
return {
value: 'http://192.168.11.176:7010',
}
},
onLoad(e) {},
onShow() {},
//方法
methods: {
onPageJump(url) {
uni.navigateTo({
url: url,
})
},
change(e) {
console.log('🐛 ~ file: service.vue ~ line 31 ~ change ~ value', e)
try {
uni.setStorageSync('HOST', e.detail.value)
} catch (e) {
// error
}
},
Yuming() {
uni.setStorageSync('HOST', e.detail.value)
},
},
//页面下来刷新
onPullDownRefresh() {},
//页面上拉触底
onReachBottom() {},
//用户点击分享
onShareAppMessage(e) {
return this.wxShare()
},
}
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
</style>
<template>
<view>
<nav-bar title="绑定"></nav-bar>
<!-- 公共组件-每个页面必须引入 -->
<public-module></public-module>
</view>
</template>
<script>
export default {
data() {
return {
};
},
//方法
methods: {
onPageJump(url) {
uni.navigateTo({
url: url
});
}
}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
</style>
<template>
<view class="protocol_page">
<nav-bar title="协议"></nav-bar>
<!-- 公共组件-每个页面必须引入 -->
<public-module></public-module>
<view class="title">{{title}}</view>
<jyf-parser ref="article"></jyf-parser>
</view>
</template>
<script>
import jyfParser from '@/components/common/jyf-parser/jyf-parser.vue';
export default {
components: { jyfParser },
data() {
return {
type:1000,
title:"用户协议"
};
},
//第一次加载
onLoad(e) {
if(e.type){
this.type = parseInt(e.type);
let title;
switch (this.type) {
case 1000:
title = "登录注册用户协议";
break;
}
this.title = title;
}
this.pageData();
},
//页面显示
onShow() {},
//方法
methods: {
pageData() {
this.$http
.get('api/common/v1/protocol', {
type: this.type
})
.then(res => {
this.$refs.article.setContent(res);
});
}
},
//页面隐藏
onHide() {},
//页面卸载
onUnload() {},
//页面下来刷新
onPullDownRefresh() {},
//页面上拉触底
onReachBottom() {},
//用户点击分享
onShareAppMessage(e) {
return this.wxShare();
}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
.protocol_page {
background-color: #fff;
padding: 30upx;
font-size: 30upx;
line-height: 180%;
.title {
font-size: 50upx;
padding-bottom: 30upx;
}
}
</style>
# APP版本更新、强制更新、漂亮的更新界面、IOS更新(跳转IOS store)、wgt更新
| `QQ交流群(607391225)` | `微信交流群(加我好友备注"进群")` |
| ----------------------------|--------------------------- |
|![QQ交流群](http://qn.kemean.cn//upload/202004/14/15868301778472k7oubi6.png)|![微信交流群](https://qn.kemean.cn/upload/202010/13/weiXin_group_code.jpg)|
| QQ群号:607391225 |微信号:zhou0612wei|
### [点击跳转-插件示例](https://ext.dcloud.net.cn/plugin?id=2009)
### [点击跳转-5年的web前端开源的uni-app快速开发模板-下载看文档](https://ext.dcloud.net.cn/plugin?id=2009)
### 常见问题
1.安卓apk下载完成后没有更新APP?
答:问题是因为没有添加APP安装应用的权限,解决方法在`manifest.json`文件里面`APP模块权限配置``Android打包权限配置`勾选以下权限
```
<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>
<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>
```
若还有问题请看[安装apk无法执行的解决方案](https://ask.dcloud.net.cn/article/35703 "安装apk无法执行的解决方案")
2.APP更新后版本号没变,还是之前的版本号?
答:可能是更新的安装包没有升级版本号,`manifest.json`文件里面基本设置`应用版本号``应用版本名称`需要升高(保持一直减少问题)
3.APP更新后没有覆盖之前的APP?
答:可能是更新的安装包`包名`和APP的`包名`不一样
4.弹窗的图标不显示?
答:检查图片是不是放项目资源文件`static`,然后重新运行项目
5.版本号是在前端对比还是在后端接口对比?
答:当前案例是本地的版本号通过接口传递给后台,是后台对比的,若需要前端对比,请在接口返回数据的地方修改,不更新就不要调用`callback`方法
6.本地的版本号比接口的版本号高还弹窗升级窗口?
答:当前案例是本地的版本号通过接口传递给后台,后台对比是否需要升级,不需要升级就不要返回数据(特别是需要wgt更新的,建议这种方式)
### 第一步配置APP更新接口
`APPUpdate.js`里面`getServerNo`函数方法配置更新接口
```
let httpData = {
version:version
};
if (platform == "android") {
httpData.type = 1101;
} else {
httpData.type = 1102;
}
/* 接口入参说明
* version: 应用当前版本号(已自动获取)
* type:平台(1101是安卓,1102是IOS)
*/
// 可以用自己项目的请求方法
$http.get("api/kemean/aid/app_version", httpData).then(res => {
/*接口出参说明 (res数据说明)
* | 参数名称 | 一定返回 | 类型 | 描述
* | -------------|--------- | --------- | ------------- |
* | versionCode | y | int | 版本号 |
* | versionName | y | String | 版本名称 |
* | versionInfo | y | String | 版本信息 \n 换行(例如:1.修改了bug1 \n 2.修改了bug2 \n 3.修改了bug3) |
* | forceUpdate | y | boolean | 是否强制更新 |
* | downloadUrl | y | String | 版本下载链接 `IOS安装包更新请放跳转store应用商店链接,安卓apk和wgt文件放文件下载链接` |
*/
// 只要callback上面的数据就ok(返回数据就表示接口允许更新)
// 示例返回数据
callback && callback({
versionCode: 101,
versionName: "1.0.1",
versionInfo: "1.修改了bug1 \n 2.修改了bug2 \n 3.修改了bug3",
forceUpdate: false,
downloadUrl: "http://www.xxx.com/download/123",
});
});
```
### 第二步 使用方法
```
// App.vue页面
// #ifdef APP-PLUS
import APPUpdate from "@/plugins/APPUpdate";
// #endif
onLaunch: function(e) {
// #ifdef APP-PLUS
APPUpdate();
// #endif
}
```
### 第三步 添加APP安装应用的权限
`manifest.json`文件里面`APP模块权限配置``Android打包权限配置`勾选以下权限
```
<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>
<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>
```
### 修改弹窗的主题色或弹窗图标
`APPUpdate.js`里面上面`$mainColor`常量中定义主题颜色,`$iconUrl`常量中定义图标地址
### 检查APP是否有新版本(一般在设置页面使用)
```
// #ifdef APP-PLUS
import APPUpdate, { getCurrentNo } from "@/plugins/APPUpdate";
// #endif
export default {
data() {
return {
version: "" // 版本号
};
},
//第一次加载
onLoad(e) {
// #ifdef APP-PLUS
getCurrentNo(res => {
// 进页面获取当前APP版本号(用于页面显示)
this.version = res.version;
});
// #endif
},
//方法
methods: {
// 检查APP是否有新版本
onAPPUpdate() {
// true 没有新版本的时候有提示,默认:false
APPUpdate(true);
}
}
}
```
\ No newline at end of file
// #ifdef APP-PLUS
/**** 此文件说明请看注释 *****/
// 可以用自己项目的请求方法
// 请求配置说明:https://ext.dcloud.net.cn/plugin?id=822
import $http from '@/config/requestConfig';
/**** 结束 *****/
const platform = uni.getSystemInfoSync().platform;
// 主颜色
const $mainColor = "FF5B78";
// 弹窗图标url
const $iconUrl = "/static/icon/ic_ar.png";
// 获取当前应用的版本号
export const getCurrentNo = function(callback) {
// 获取本地应用资源版本号
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
callback && callback({
versionCode: inf.versionCode,
versionName: inf.version
});
});
}
// 发起ajax请求获取服务端版本号
const getServerNo = function(version,isPrompt = false, callback) {
let httpData = {
version: version.versionCode,
versionName: version.versionName
};
if (platform == "android") {
httpData.type = 1101;
} else {
httpData.type = 1102;
}
/* 接口入参说明
* version: 应用当前版本号(已自动获取)
* versionName: 应用当前版本名称(已自动获取)
* type:平台(1101是安卓,1102是IOS)
*/
/****************以下是示例*******************/
// 可以用自己项目的请求方法
$http.get("api/common/v1/app_version", httpData,{
isPrompt: isPrompt
}).then(res => {
/* res的数据说明
* | 参数名称 | 一定返回 | 类型 | 描述
* | -------------|--------- | --------- | ------------- |
* | versionCode | y | int | 版本号 |
* | versionName | y | String | 版本名称 |
* | versionInfo | y | String | 版本信息 |
* | updateType | y | String | forcibly = 强制更新, solicit = 弹窗确认更新, silent = 静默更新 |
* | downloadUrl | y | String | 版本下载链接(IOS安装包更新请放跳转store应用商店链接,安卓apk和wgt文件放文件下载链接) |
*/
if (res && res.downloadUrl) {
// 兼容之前的版本
if(res.updateType){
callback && callback(res);
} else {
if(res.forceUpdate){
res.updateType = "forcibly";
} else {
res.updateType = "solicit";
}
callback && callback(res);
}
} else if (isPrompt) {
uni.showToast({
title: "暂无新版本",
icon: "none"
});
}
});
/****************以上是示例*******************/
}
// 从服务器下载应用资源包(wgt文件)
const getDownload = function(data) {
let dtask;
if(data.updateType == 'forcibly' || data.updateType == 'solicit'){
let popupData = {
progress: true,
buttonNum: 2
};
if(data.updateType == 'forcibly'){
popupData.buttonNum = 0;
}
let lastProgressValue = 0;
let popupObj = downloadPopup(popupData);
dtask = plus.downloader.createDownload(data.downloadUrl, {
filename: "_doc/update/"
}, function(download, status) {
if (status == 200) {
popupObj.change({
progressValue: 100,
progressTip:"正在安装文件...",
progress: true,
buttonNum: 0
});
plus.runtime.install(download.filename, {}, function() {
popupObj.change({
contentText: "应用资源更新完成!",
buttonNum: 1,
progress: false
});
}, function(e) {
popupObj.cancel();
plus.nativeUI.alert("安装文件失败[" + e.code + "]:" + e.message);
});
} else {
popupObj.change({
contentText: "文件下载失败...",
buttonNum: 1,
progress: false
});
}
});
dtask.start();
dtask.addEventListener("statechanged", function(task, status) {
switch (task.state) {
case 1: // 开始
popupObj.change({
progressValue:0,
progressTip:"准备下载...",
progress: true
});
break;
case 2: // 已连接到服务器
popupObj.change({
progressValue:0,
progressTip:"开始下载...",
progress: true
});
break;
case 3:
const progress = parseInt(task.downloadedSize / task.totalSize * 100);
if(progress - lastProgressValue >= 2){
lastProgressValue = progress;
popupObj.change({
progressValue:progress,
progressTip: "已下载" + progress + "%",
progress: true
});
}
break;
}
});
// 取消下载
popupObj.cancelDownload = function(){
dtask && dtask.abort();
uni.showToast({
title: "已取消下载",
icon:"none"
});
}
// 重启APP
popupObj.reboot = function(){
plus.runtime.restart();
}
} else if(data.updateType == "silent"){
dtask = plus.downloader.createDownload(data.downloadUrl, {
filename: "_doc/update/"
}, function(download, status) {
if (status == 200) {
plus.runtime.install(download.filename, {}, function() {
}, function(e) {
plus.nativeUI.alert("安装文件失败[" + e.code + "]:" + e.message);
});
} else {
plus.nativeUI.alert("文件下载失败...");
}
});
dtask.start();
}
}
// 文字换行
function drawtext(text, maxWidth) {
let textArr = text.split("");
let len = textArr.length;
// 上个节点
let previousNode = 0;
// 记录节点宽度
let nodeWidth = 0;
// 文本换行数组
let rowText = [];
// 如果是字母,侧保存长度
let letterWidth = 0;
// 汉字宽度
let chineseWidth = 14;
// otherFont宽度
let otherWidth = 7;
for (let i = 0; i < len; i++) {
if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) {
if(letterWidth > 0){
if(nodeWidth + chineseWidth + letterWidth * otherWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i)
});
previousNode = i;
nodeWidth = chineseWidth;
letterWidth = 0;
} else {
nodeWidth += chineseWidth + letterWidth * otherWidth;
letterWidth = 0;
}
} else {
if(nodeWidth + chineseWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i)
});
previousNode = i;
nodeWidth = chineseWidth;
}else{
nodeWidth += chineseWidth;
}
}
} else {
if(/\n/g.test(textArr[i])){
rowText.push({
type: "break",
content: text.substring(previousNode, i)
});
previousNode = i + 1;
nodeWidth = 0;
letterWidth = 0;
}else if(textArr[i] == "\\" && textArr[i + 1] == "n"){
rowText.push({
type: "break",
content: text.substring(previousNode, i)
});
previousNode = i + 2;
nodeWidth = 0;
letterWidth = 0;
}else if(/[a-zA-Z0-9]/g.test(textArr[i])){
letterWidth += 1;
if(nodeWidth + letterWidth * otherWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i + 1 - letterWidth)
});
previousNode = i + 1 - letterWidth;
nodeWidth = letterWidth * otherWidth;
letterWidth = 0;
}
} else{
if(nodeWidth + otherWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i)
});
previousNode = i;
nodeWidth = otherWidth;
}else{
nodeWidth += otherWidth;
}
}
}
}
if (previousNode < len) {
rowText.push({
type: "text",
content: text.substring(previousNode, len)
});
}
return rowText;
}
// 是否更新弹窗
function updatePopup(data, callback) {
// 弹窗遮罩层
let maskLayer = new plus.nativeObj.View("maskLayer", { //先创建遮罩层
top: '0px',
left: '0px',
height: '100%',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.5)'
});
// 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心
const screenWidth = plus.screen.resolutionWidth;
const screenHeight = plus.screen.resolutionHeight;
//弹窗容器宽度
const popupViewWidth = screenWidth * 0.7;
// 弹窗容器的Padding
const viewContentPadding = 20;
// 弹窗容器的宽度
const viewContentWidth = parseInt(popupViewWidth - (viewContentPadding * 2));
// 描述的列表
const descriptionList = drawtext(data.versionInfo, viewContentWidth);
// 弹窗容器高度
let popupViewHeight = 80 + 20 + 20 + 90 + 10;
let popupViewContentList = [{
src: $iconUrl,
id: "logo",
tag: "img",
position: {
top: "0px",
left: (popupViewWidth - 124) / 2 + "px",
width: "124px",
height: "80px",
}
},
{
tag: 'font',
id: 'title',
text: "发现新版本" + data.versionName,
textStyles: {
size: '18px',
color: "#333",
weight: "bold",
whiteSpace: "normal"
},
position: {
top: '90px',
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: "30px",
}
}];
const textHeight = 18;
let contentTop = 130;
descriptionList.forEach((item,index) => {
if(index > 0){
popupViewHeight += textHeight;
contentTop += textHeight;
}
popupViewContentList.push({
tag: 'font',
id: 'content' + index + 1,
text: item.content,
textStyles: {
size: '14px',
color: "#666",
lineSpacing: "50%",
align: "left"
},
position: {
top: contentTop + "px",
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: textHeight + "px",
}
});
if(item.type == "break"){
contentTop += 10;
popupViewHeight += 10;
}
});
if(data.updateType == "forcibly"){
popupViewContentList.push({
tag: 'rect', //绘制底边按钮
rectStyles:{
radius: "6px",
color: $mainColor
},
position:{
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: "30px"
}
});
popupViewContentList.push({
tag: 'font',
id: 'confirmText',
text: "立即升级",
textStyles: {
size: '14px',
color: "#FFF",
lineSpacing: "0%",
},
position: {
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: "30px"
}
});
} else {
// 绘制底边按钮
popupView.drawRect({
radius: "3px",
borderColor: "#f1f1f1",
borderWidth: "1px",
}, {
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px",
});
// 绘制底边按钮
popupView.drawRect({
radius: "3px",
color: $mainColor,
}, {
bottom: viewContentPadding + 'px',
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px",
});
popupViewContentList.push({
tag: 'font',
id: 'cancelText',
text: "暂不升级",
textStyles: {
size: '14px',
color: "#666",
lineSpacing: "0%",
whiteSpace: "normal"
},
position: {
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px",
}
});
popupViewContentList.push({
tag: 'font',
id: 'confirmText',
text: "立即升级",
textStyles: {
size: '14px',
color: "#FFF",
lineSpacing: "0%",
whiteSpace: "normal"
},
position: {
bottom: viewContentPadding + 'px',
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px",
}
});
}
// 弹窗内容
let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单
tag: "rect",
top: (screenHeight - popupViewHeight) / 2 + "px",
left: '15%',
height: popupViewHeight + "px",
width: "70%"
});
// 绘制白色背景
popupView.drawRect({
color: "#FFFFFF",
radius: "8px"
}, {
top: "40px",
height: popupViewHeight - 40 + "px",
});
popupView.draw(popupViewContentList);
popupView.addEventListener("click", function(e) {
let maxTop = popupViewHeight - viewContentPadding;
let maxLeft = popupViewWidth - viewContentPadding;
let buttonWidth = (viewContentWidth - viewContentPadding) / 2;
if (e.clientY > maxTop - 30 && e.clientY < maxTop) {
if(data.updateType == "forcibly"){
if(e.clientX > viewContentPadding && e.clientX < maxLeft){
// 立即升级
maskLayer.hide();
popupView.hide();
callback && callback();
}
} else {
// 暂不升级
if (e.clientX > viewContentPadding && e.clientX < maxLeft - buttonWidth - viewContentPadding) {
maskLayer.hide();
popupView.hide();
} else if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) {
// 立即升级
maskLayer.hide();
popupView.hide();
callback && callback();
}
}
}
});
if(data.updateType == "solicit"){
// 点击遮罩层
maskLayer.addEventListener("click", function() { //处理遮罩层点击
maskLayer.hide();
popupView.hide();
});
}
// 显示弹窗
maskLayer.show();
popupView.show();
}
// 文件下载的弹窗绘图
function downloadPopupDrawing(data){
// 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心
const screenWidth = plus.screen.resolutionWidth;
const screenHeight = plus.screen.resolutionHeight;
//弹窗容器宽度
const popupViewWidth = screenWidth * 0.7;
// 弹窗容器的Padding
const viewContentPadding = 20;
// 弹窗容器的宽度
const viewContentWidth = popupViewWidth - (viewContentPadding * 2);
// 弹窗容器高度
let popupViewHeight = viewContentPadding * 3 + 60;
let progressTip = data.progressTip || "准备下载...";
let contentText = data.contentText || "正在为您更新,请耐心等待";
let elementList = [
{
tag: 'rect', //背景色
color: '#FFFFFF',
rectStyles:{
radius: "8px"
}
},
{
tag: 'font',
id: 'title',
text: "升级APP",
textStyles: {
size: '16px',
color: "#333",
weight: "bold",
verticalAlign: "middle",
whiteSpace: "normal"
},
position: {
top: viewContentPadding + 'px',
height: "30px",
}
},
{
tag: 'font',
id: 'content',
text: contentText,
textStyles: {
size: '14px',
color: "#333",
verticalAlign: "middle",
whiteSpace: "normal"
},
position: {
top: viewContentPadding * 2 + 30 + 'px',
height: "20px",
}
}
];
// 是否有进度条
if(data.progress){
popupViewHeight += viewContentPadding + 40;
elementList = elementList.concat([
{
tag: 'font',
id: 'progressValue',
text: progressTip,
textStyles: {
size: '14px',
color: $mainColor,
whiteSpace: "normal"
},
position: {
top: viewContentPadding * 4 + 20 + 'px',
height: "30px"
}
},
{
tag: 'rect', //绘制进度条背景
id: 'progressBg',
rectStyles:{
radius: "4px",
borderColor: "#f1f1f1",
borderWidth: "1px",
},
position:{
top: viewContentPadding * 4 + 60 + 'px',
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: "8px"
}
},
]);
}
if (data.buttonNum == 2) {
popupViewHeight += viewContentPadding + 30;
elementList = elementList.concat([
{
tag: 'rect', //绘制底边按钮
rectStyles:{
radius: "3px",
borderColor: "#f1f1f1",
borderWidth: "1px",
},
position:{
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px"
}
},
{
tag: 'rect', //绘制底边按钮
rectStyles:{
radius: "3px",
color: $mainColor
},
position:{
bottom: viewContentPadding + 'px',
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px"
}
},
{
tag: 'font',
id: 'cancelText',
text: "取消下载",
textStyles: {
size: '14px',
color: "#666",
lineSpacing: "0%",
whiteSpace: "normal"
},
position: {
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px",
}
},
{
tag: 'font',
id: 'confirmText',
text: "后台下载",
textStyles: {
size: '14px',
color: "#FFF",
lineSpacing: "0%",
whiteSpace: "normal"
},
position: {
bottom: viewContentPadding + 'px',
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px",
width: (viewContentWidth - viewContentPadding) / 2 + "px",
height: "30px",
}
}
]);
}
if (data.buttonNum == 1) {
popupViewHeight += viewContentPadding + 40;
elementList = elementList.concat([
{
tag: 'rect', //绘制底边按钮
rectStyles:{
radius: "6px",
color: $mainColor
},
position:{
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: "40px"
}
},
{
tag: 'font',
id: 'confirmText',
text: "关闭",
textStyles: {
size: '14px',
color: "#FFF",
lineSpacing: "0%",
},
position: {
bottom: viewContentPadding + 'px',
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: "40px"
}
}
]);
}
return {
popupViewHeight:popupViewHeight,
popupViewWidth:popupViewWidth,
screenHeight:screenHeight,
viewContentWidth:viewContentWidth,
viewContentPadding:viewContentPadding,
elementList: elementList
};
}
// 文件下载的弹窗
function downloadPopup(data) {
// 弹窗遮罩层
let maskLayer = new plus.nativeObj.View("maskLayer", { //先创建遮罩层
top: '0px',
left: '0px',
height: '100%',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.5)'
});
let popupViewData = downloadPopupDrawing(data);
// 弹窗内容
let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单
tag: "rect",
top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px",
left: '15%',
height: popupViewData.popupViewHeight + "px",
width: "70%",
});
let progressValue = 0;
let progressTip = 0;
let contentText = 0;
let buttonNum = 2;
if(data.buttonNum >= 0){
buttonNum = data.buttonNum;
}
popupView.draw(popupViewData.elementList);
let callbackData = {
change: function(res) {
let progressElement = [];
if(res.progressValue){
progressValue = res.progressValue;
// 绘制进度条
progressElement.push({
tag: 'rect', //绘制进度条背景
id: 'progressValueBg',
rectStyles:{
radius: "4px",
color: $mainColor
},
position:{
top: popupViewData.viewContentPadding * 4 + 60 + 'px',
left: popupViewData.viewContentPadding + "px",
width: popupViewData.viewContentWidth * (res.progressValue / 100) + "px",
height: "8px"
}
});
}
if(res.progressTip){
progressTip = res.progressTip;
progressElement.push({
tag: 'font',
id: 'progressValue',
text: res.progressTip,
textStyles: {
size: '14px',
color: $mainColor,
whiteSpace: "normal"
},
position: {
top: popupViewData.viewContentPadding * 4 + 20 + 'px',
height: "30px"
}
});
}
if(res.contentText){
contentText = res.contentText;
progressElement.push({
tag: 'font',
id: 'content',
text: res.contentText,
textStyles: {
size: '16px',
color: "#333",
whiteSpace: "normal"
},
position: {
top: popupViewData.viewContentPadding * 2 + 30 + 'px',
height: "30px",
}
});
}
if(res.buttonNum >= 0 && buttonNum != res.buttonNum){
buttonNum = res.buttonNum;
popupView.reset();
popupViewData = downloadPopupDrawing(Object.assign({
progressValue:progressValue,
progressTip:progressTip,
contentText:contentText,
},res));
let newElement = [];
popupViewData.elementList.map((item,index) => {
let have = false;
progressElement.forEach((childItem,childIndex) => {
if(item.id == childItem.id){
have = true;
}
});
if(!have){
newElement.push(item);
}
});
progressElement = newElement.concat(progressElement);
popupView.setStyle({
tag: "rect",
top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px",
left: '15%',
height: popupViewData.popupViewHeight + "px",
width: "70%",
});
popupView.draw(progressElement);
}else{
popupView.draw(progressElement);
}
},
cancel: function() {
maskLayer.hide();
popupView.hide();
}
}
popupView.addEventListener("click", function(e) {
let maxTop = popupViewData.popupViewHeight - popupViewData.viewContentPadding;
let maxLeft = popupViewData.popupViewWidth - popupViewData.viewContentPadding;
if (e.clientY > maxTop - 40 && e.clientY < maxTop) {
if(buttonNum == 1){
// 单按钮
if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft) {
maskLayer.hide();
popupView.hide();
callbackData.reboot();
}
}else if(buttonNum == 2){
// 双按钮
let buttonWidth = (popupViewData.viewContentWidth - popupViewData.viewContentPadding) / 2;
if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft - buttonWidth - popupViewData.viewContentPadding) {
maskLayer.hide();
popupView.hide();
callbackData.cancelDownload();
} else if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) {
maskLayer.hide();
popupView.hide();
}
}
}
});
// 显示弹窗
maskLayer.show();
popupView.show();
// 改变进度条
return callbackData;
}
export default function(isPrompt = false) {
getCurrentNo(versionInfo => {
getServerNo(versionInfo,isPrompt, res => {
if (res.updateType == "forcibly" || res.updateType == "silent") {
if (/\.wgt$/i.test(res.downloadUrl)) {
getDownload(res);
} else if(/\.html$/i.test(res.downloadUrl)){
plus.runtime.openURL(res.downloadUrl);
} else {
if (platform == "android") {
getDownload(res);
} else {
plus.runtime.openURL(res.downloadUrl);
}
}
} else if(res.updateType == "solicit"){
updatePopup(res, function() {
if (/\.wgt$/i.test(res.downloadUrl)) {
getDownload(res);
} else if(/\.html$/i.test(res.downloadUrl)){
plus.runtime.openURL(res.downloadUrl);
} else {
if (platform == "android") {
getDownload(res);
} else {
plus.runtime.openURL(res.downloadUrl);
}
}
});
}
});
});
}
// #endif
\ No newline at end of file
function getLocalFilePath(path) {
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) {
return path
}
if (path.indexOf('file://') === 0) {
return path
}
if (path.indexOf('/storage/emulated/0/') === 0) {
return path
}
if (path.indexOf('/') === 0) {
var localFilePath = plus.io.convertAbsoluteFileSystem(path)
if (localFilePath !== path) {
return localFilePath
} else {
path = path.substr(1)
}
}
return '_www/' + path
}
export function pathToBase64(path) {
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
var canvas = document.createElement('canvas')
var c2x = canvas.getContext('2d')
var img = new Image
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
c2x.drawImage(img, 0, 0)
resolve(canvas.toDataURL())
}
img.onerror = reject
img.src = path
return
}
if (typeof plus === 'object') {
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) {
entry.file(function(file) {
var fileReader = new plus.io.FileReader()
fileReader.onload = function(data) {
resolve(data.target.result)
}
fileReader.onerror = function(error) {
reject(error)
}
fileReader.readAsDataURL(file)
}, function(error) {
reject(error)
})
}, function(error) {
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
wx.getFileSystemManager().readFile({
filePath: path,
encoding: 'base64',
success: function(res) {
resolve('data:image/png;base64,' + res.data)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
export function base64ToPath(base64) {
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
base64 = base64.split(',')
var type = base64[0].match(/:(.*?);/)[1]
var str = atob(base64[1])
var n = str.length
var array = new Uint8Array(n)
while (n--) {
array[n] = str.charCodeAt(n)
}
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], { type: type })))
}
var extName = base64.match(/data\:\S+\/(\S+);/)
if (extName) {
extName = extName[1]
} else {
reject(new Error('base64 error'))
}
var fileName = Date.now() + '.' + extName
if (typeof plus === 'object') {
var bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, function() {
var filePath = '_doc/uniapp_temp/' + fileName
bitmap.save(filePath, {}, function() {
bitmap.clear()
resolve(filePath)
}, function(error) {
bitmap.clear()
reject(error)
})
}, function(error) {
bitmap.clear()
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
var filePath = wx.env.USER_DATA_PATH + '/' + fileName
wx.getFileSystemManager().writeFile({
filePath: filePath,
data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
encoding: 'base64',
success: function() {
resolve(filePath)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
\ No newline at end of file
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 1.1 Copyright (C) Paul Johnston 1999 - 2002.
* Code also contributed by Greg Holt
* See http://pajhome.org.uk/site/legal.html for details.
*/
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF)
var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
return (msw << 16) | (lsw & 0xFFFF)
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt))
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function cmn(q, a, b, x, s, t) {
return safe_add(rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b)
}
function ff(a, b, c, d, x, s, t) {
return cmn((b & c) | ((~b) & d), a, b, x, s, t)
}
function gg(a, b, c, d, x, s, t) {
return cmn((b & d) | (c & (~d)), a, b, x, s, t)
}
function hh(a, b, c, d, x, s, t) {
return cmn(b ^ c ^ d, a, b, x, s, t)
}
function ii(a, b, c, d, x, s, t) {
return cmn(c ^ (b | (~d)), a, b, x, s, t)
}
/*
* Calculate the MD5 of an array of little-endian words, producing an array
* of little-endian words.
*/
function coreMD5(x) {
var a = 1732584193
var b = -271733879
var c = -1732584194
var d = 271733878
for (var i = 0; i < x.length; i += 16) {
var olda = a
var oldb = b
var oldc = c
var oldd = d
a = ff(a, b, c, d, x[i + 0], 7, -680876936)
d = ff(d, a, b, c, x[i + 1], 12, -389564586)
c = ff(c, d, a, b, x[i + 2], 17, 606105819)
b = ff(b, c, d, a, x[i + 3], 22, -1044525330)
a = ff(a, b, c, d, x[i + 4], 7, -176418897)
d = ff(d, a, b, c, x[i + 5], 12, 1200080426)
c = ff(c, d, a, b, x[i + 6], 17, -1473231341)
b = ff(b, c, d, a, x[i + 7], 22, -45705983)
a = ff(a, b, c, d, x[i + 8], 7, 1770035416)
d = ff(d, a, b, c, x[i + 9], 12, -1958414417)
c = ff(c, d, a, b, x[i + 10], 17, -42063)
b = ff(b, c, d, a, x[i + 11], 22, -1990404162)
a = ff(a, b, c, d, x[i + 12], 7, 1804603682)
d = ff(d, a, b, c, x[i + 13], 12, -40341101)
c = ff(c, d, a, b, x[i + 14], 17, -1502002290)
b = ff(b, c, d, a, x[i + 15], 22, 1236535329)
a = gg(a, b, c, d, x[i + 1], 5, -165796510)
d = gg(d, a, b, c, x[i + 6], 9, -1069501632)
c = gg(c, d, a, b, x[i + 11], 14, 643717713)
b = gg(b, c, d, a, x[i + 0], 20, -373897302)
a = gg(a, b, c, d, x[i + 5], 5, -701558691)
d = gg(d, a, b, c, x[i + 10], 9, 38016083)
c = gg(c, d, a, b, x[i + 15], 14, -660478335)
b = gg(b, c, d, a, x[i + 4], 20, -405537848)
a = gg(a, b, c, d, x[i + 9], 5, 568446438)
d = gg(d, a, b, c, x[i + 14], 9, -1019803690)
c = gg(c, d, a, b, x[i + 3], 14, -187363961)
b = gg(b, c, d, a, x[i + 8], 20, 1163531501)
a = gg(a, b, c, d, x[i + 13], 5, -1444681467)
d = gg(d, a, b, c, x[i + 2], 9, -51403784)
c = gg(c, d, a, b, x[i + 7], 14, 1735328473)
b = gg(b, c, d, a, x[i + 12], 20, -1926607734)
a = hh(a, b, c, d, x[i + 5], 4, -378558)
d = hh(d, a, b, c, x[i + 8], 11, -2022574463)
c = hh(c, d, a, b, x[i + 11], 16, 1839030562)
b = hh(b, c, d, a, x[i + 14], 23, -35309556)
a = hh(a, b, c, d, x[i + 1], 4, -1530992060)
d = hh(d, a, b, c, x[i + 4], 11, 1272893353)
c = hh(c, d, a, b, x[i + 7], 16, -155497632)
b = hh(b, c, d, a, x[i + 10], 23, -1094730640)
a = hh(a, b, c, d, x[i + 13], 4, 681279174)
d = hh(d, a, b, c, x[i + 0], 11, -358537222)
c = hh(c, d, a, b, x[i + 3], 16, -722521979)
b = hh(b, c, d, a, x[i + 6], 23, 76029189)
a = hh(a, b, c, d, x[i + 9], 4, -640364487)
d = hh(d, a, b, c, x[i + 12], 11, -421815835)
c = hh(c, d, a, b, x[i + 15], 16, 530742520)
b = hh(b, c, d, a, x[i + 2], 23, -995338651)
a = ii(a, b, c, d, x[i + 0], 6, -198630844)
d = ii(d, a, b, c, x[i + 7], 10, 1126891415)
c = ii(c, d, a, b, x[i + 14], 15, -1416354905)
b = ii(b, c, d, a, x[i + 5], 21, -57434055)
a = ii(a, b, c, d, x[i + 12], 6, 1700485571)
d = ii(d, a, b, c, x[i + 3], 10, -1894986606)
c = ii(c, d, a, b, x[i + 10], 15, -1051523)
b = ii(b, c, d, a, x[i + 1], 21, -2054922799)
a = ii(a, b, c, d, x[i + 8], 6, 1873313359)
d = ii(d, a, b, c, x[i + 15], 10, -30611744)
c = ii(c, d, a, b, x[i + 6], 15, -1560198380)
b = ii(b, c, d, a, x[i + 13], 21, 1309151649)
a = ii(a, b, c, d, x[i + 4], 6, -145523070)
d = ii(d, a, b, c, x[i + 11], 10, -1120210379)
c = ii(c, d, a, b, x[i + 2], 15, 718787259)
b = ii(b, c, d, a, x[i + 9], 21, -343485551)
a = safe_add(a, olda)
b = safe_add(b, oldb)
c = safe_add(c, oldc)
d = safe_add(d, oldd)
}
return [a, b, c, d]
}
/*
* Convert an array of little-endian words to a hex string.
*/
function binl2hex(binarray) {
var hex_tab = "0123456789abcdef"
var str = ""
for (var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF)
}
return str
}
/*
* Convert an array of little-endian words to a base64 encoded string.
*/
function binl2b64(binarray) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var str = ""
for (var i = 0; i < binarray.length * 32; i += 6) {
str += tab.charAt(((binarray[i >> 5] << (i % 32)) & 0x3F) |
((binarray[i >> 5 + 1] >> (32 - i % 32)) & 0x3F))
}
return str
}
/*
* Convert an 8-bit character string to a sequence of 16-word blocks, stored
* as an array, and append appropriate padding for MD4/5 calculation.
* If any of the characters are >255, the high byte is silently ignored.
*/
function str2binl(str) {
var nblk = ((str.length + 8) >> 6) + 1 // number of 16-word blocks
var blks = new Array(nblk * 16)
for (var i = 0; i < nblk * 16; i++) blks[i] = 0
for (var i = 0; i < str.length; i++)
blks[i >> 2] |= (str.charCodeAt(i) & 0xFF) << ((i % 4) * 8)
blks[i >> 2] |= 0x80 << ((i % 4) * 8)
blks[nblk * 16 - 2] = str.length * 8
return blks
}
/*
* Convert a wide-character string to a sequence of 16-word blocks, stored as
* an array, and append appropriate padding for MD4/5 calculation.
*/
function strw2binl(str) {
var nblk = ((str.length + 4) >> 5) + 1 // number of 16-word blocks
var blks = new Array(nblk * 16)
for (var i = 0; i < nblk * 16; i++) blks[i] = 0
for (var i = 0; i < str.length; i++)
blks[i >> 1] |= str.charCodeAt(i) << ((i % 2) * 16)
blks[i >> 1] |= 0x80 << ((i % 2) * 16)
blks[nblk * 16 - 2] = str.length * 16
return blks
}
/*
* External interface
*/
function md5(str) { return binl2hex(coreMD5(str2binl(str))) }
function hexMD5w(str) { return binl2hex(coreMD5(strw2binl(str))) }
function b64MD5(str) { return binl2b64(coreMD5(str2binl(str))) }
function b64MD5w(str) { return binl2b64(coreMD5(strw2binl(str))) }
/* Backward compatibility */
function calcMD5(str) { return binl2hex(coreMD5(str2binl(str))) }
module.exports = md5
\ No newline at end of file
/**
* 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启
*/
var isIos
// #ifdef APP-PLUS
isIos = (plus.os.name == "iOS")
// #endif
// 判断推送权限是否开启
function judgeIosPermissionPush() {
var result = 0;
var UIApplication = plus.ios.import("UIApplication");
var app = UIApplication.sharedApplication();
var enabledTypes = 0;
if (app.currentUserNotificationSettings) {
var settings = app.currentUserNotificationSettings();
enabledTypes = settings.plusGetAttribute("types");
if (enabledTypes == 0) {
console.log("推送权限没有开启");
} else {
result = 1;
console.log("已经开启推送功能!")
}
plus.ios.deleteObject(settings);
} else {
enabledTypes = app.enabledRemoteNotificationTypes();
if (enabledTypes == 0) {
console.log("推送权限没有开启!");
} else {
result = 1;
console.log("已经开启推送功能!")
}
console.log("enabledTypes2:" + enabledTypes);
}
plus.ios.deleteObject(app);
plus.ios.deleteObject(UIApplication);
return result;
}
// 判断定位权限是否开启
function judgeIosPermissionLocation() {
var result = 0;
var cllocationManger = plus.ios.import("CLLocationManager");
var status = cllocationManger.authorizationStatus();
result = (status != 2) ? 1 : 0;
console.log("定位权限开启:" + result);
// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
/* var enable = cllocationManger.locationServicesEnabled();
var status = cllocationManger.authorizationStatus();
console.log("enable:" + enable);
console.log("status:" + status);
if (enable && status != 2) {
result = true;
console.log("手机定位服务已开启且已授予定位权限");
} else {
console.log("手机系统的定位没有打开或未给予定位权限");
} */
plus.ios.deleteObject(cllocationManger);
return {
result: result,
permissionName: "定位"
};
}
// 判断麦克风权限是否开启
function judgeIosPermissionRecord() {
var result = 0;
var avaudiosession = plus.ios.import("AVAudioSession");
var avaudio = avaudiosession.sharedInstance();
var permissionStatus = avaudio.recordPermission();
console.log("permissionStatus:" + permissionStatus);
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
console.log("麦克风权限没有开启");
} else {
result = 1;
console.log("麦克风权限已经开启");
}
plus.ios.deleteObject(avaudiosession);
return {
result: result,
permissionName: "麦克风"
};
}
// 判断相机权限是否开启
function judgeIosPermissionCamera() {
var result = 0;
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = 1;
console.log("相机权限已经开启");
} else {
console.log("相机权限没有开启");
}
plus.ios.deleteObject(AVCaptureDevice);
return {
result: result,
permissionName: "相机"
};
}
// 判断相册权限是否开启
function judgeIosPermissionPhotoLibrary() {
var result = 0;
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
var authStatus = PHPhotoLibrary.authorizationStatus();
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = 1;
console.log("相册权限已经开启");
} else {
console.log("相册权限没有开启");
}
plus.ios.deleteObject(PHPhotoLibrary);
return {
result: result,
permissionName: "相册"
};
}
// 判断通讯录权限是否开启
function judgeIosPermissionContact() {
var result = 0;
var CNContactStore = plus.ios.import("CNContactStore");
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
if (cnAuthStatus == 3) {
result = 1;
console.log("通讯录权限已经开启");
} else {
console.log("通讯录权限没有开启");
}
plus.ios.deleteObject(CNContactStore);
return {
result: result,
permissionName: "通讯录"
};
}
// 判断日历权限是否开启
function judgeIosPermissionCalendar() {
var result = 0;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
if (ekAuthStatus == 3) {
result = 1;
console.log("日历权限已经开启");
} else {
console.log("日历权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return {
result: result,
permissionName: "日历"
};
}
// 判断备忘录权限是否开启
function judgeIosPermissionMemo() {
var result = 0;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
if (ekAuthStatus == 3) {
result = 1;
console.log("备忘录权限已经开启");
} else {
console.log("备忘录权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return {
result: result,
permissionName: "备忘录"
};
}
// Android权限查询
function requestAndroidPermission(permissionID, permissionName) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
function (resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
var deniedPresentPermission = resultObj.deniedPresent[i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve({
result: result,
permissionName: permissionName
});
// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
// if (result != 1) {
// gotoAppPermissionSetting()
// }
},
function (error) {
console.log('申请权限错误:' + error.code + " = " + error.message);
resolve({
code: error.code,
message: error.message
});
}
);
});
}
// 使用一个方法,根据参数判断权限
function judgePermission(permissionID, callback) {
function handle(res) {
callback && callback(res.result);
if (res.result === -1) {
uni.showModal({
title: "提示",
content: "您还未开通" + res.permissionName + "权限,是否去应用设置里开通~",
confirmText: "去开通",
cancelText: "再逛会",
success: (data) => {
if (data.confirm) {
gotoAppPermissionSetting();
}
}
});
}
}
if (permissionID == "location") { // 位置
if (isIos) {
handle(judgeIosPermissionLocation());
} else {
requestAndroidPermission("android.permission.ACCESS_FINE_LOCATION", "位置").then(handle);
}
} else if (permissionID == "camera") { // 摄像头
if (isIos) {
handle(judgeIosPermissionCamera());
} else {
requestAndroidPermission("android.permission.CAMERA", "摄像头").then(handle);
}
} else if (permissionID == "photoLibrary") { // 相册
if (isIos) {
handle(judgeIosPermissionPhotoLibrary());
} else {
requestAndroidPermission("android.permission.READ_EXTERNAL_STORAGE", "相册读取").then(handle);
}
} else if (permissionID == "record") { // 麦克风
if (isIos) {
handle(judgeIosPermissionRecord());
} else {
requestAndroidPermission("android.permission.RECORD_AUDIO", "麦克风").then(handle);
}
} else if (permissionID == "push") { // 推送
if (isIos) {
handle(judgeIosPermissionPush());
} else {
handle(1);
}
} else if (permissionID == "contact") { // 通讯录
if (isIos) {
handle(judgeIosPermissionContact());
} else {
requestAndroidPermission("android.permission.READ_CONTACTS", "通讯录读取").then(handle);
}
} else if (permissionID == "calendar") { // 日历
if (isIos) {
handle(judgeIosPermissionCalendar());
} else {
requestAndroidPermission("android.permission.READ_CALENDAR", "日历读取").then(handle);
}
} else if (permissionID == "memo") { // 备忘录
if (isIos) {
handle(judgeIosPermissionMemo());
} else {
handle(1);
}
} else if (permissionID == "call_phone") { // 拨打电话
if (isIos) {
handle(1);
} else {
requestAndroidPermission("android.permission.CALL_PHONE", "拨打电话").then(handle);
}
}
}
// 跳转到**应用**的权限页面
function gotoAppPermissionSetting() {
if (isIos) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
// console.log(plus.device.vendor);
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
}
// 检查系统的设备服务是否开启
// var checkSystemEnableLocation = async function () {
function checkSystemEnableLocation() {
if (isIos) {
var result = false;
var cllocationManger = plus.ios.import("CLLocationManager");
var result = cllocationManger.locationServicesEnabled();
console.log("系统定位开启:" + result);
plus.ios.deleteObject(cllocationManger);
return result;
} else {
var context = plus.android.importClass("android.content.Context");
var locationManager = plus.android.importClass("android.location.LocationManager");
var main = plus.android.runtimeMainActivity();
var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
console.log("系统定位开启:" + result);
return result
}
}
module.exports = {
judgePermission: judgePermission,
requestAndroidPermission: requestAndroidPermission,
checkSystemEnableLocation: checkSystemEnableLocation,
gotoAppPermissionSetting: gotoAppPermissionSetting
}
import { mergeConfig, dispatchRequest, jsonpRequest} from "./utils.js";
export default class request {
constructor(options) {
//请求公共地址
this.baseUrl = options.baseUrl || "";
//公共文件上传请求地址
this.fileUrl = options.fileUrl || "";
// 超时时间
this.timeout = options.timeout || 6000;
// 服务器上传图片默认url
this.defaultUploadUrl = options.defaultUploadUrl || "";
// 服务器上传文件名称
this.defaultFileName = options.defaultFileName || "";
//默认请求头
this.header = options.header || {};
//默认配置
this.config = options.config || {
isPrompt: true,
load: true,
isFactory: true,
resend: 0
};
}
//post请求
post(url = '', data = {}, options = {}) {
return this.request({
method: "POST",
data: data,
url: url,
...options
});
}
//get请求
get(url = '', data = {}, options = {}) {
return this.request({
method: "GET",
data: data,
url: url,
...options
});
}
//put请求
put(url = '', data = {}, options = {}) {
return this.request({
method: "PUT",
data: data,
url: url,
...options
});
}
//delete请求
delete(url = '', data = {}, options = {}) {
return this.request({
method: "DELETE",
data: data,
url: url,
...options
});
}
//jsonp请求(只限于H5使用)
jsonp(url = '', data = {}, options = {}) {
return this.request({
method: "JSONP",
data: data,
url: url,
...options
});
}
//接口请求方法
async request(data) {
// 请求数据
let requestInfo,
// 是否运行过请求开始钩子
runRequestStart = false;
try {
if (!data.url) {
throw { errMsg: "【request】缺失数据url", statusCode: 0}
}
// 数据合并
requestInfo = mergeConfig(this, data);
// 代表之前运行到这里
runRequestStart = true;
//请求前回调
if (this.requestStart) {
let requestStart = this.requestStart(requestInfo);
if (typeof requestStart == "object") {
let changekeys = ["data", "header", "isPrompt", "load", "isFactory"];
changekeys.forEach(key => {
requestInfo[key] = requestStart[key];
});
} else {
throw {
errMsg: "【request】请求开始拦截器未通过",
statusCode: 0,
data: requestInfo.data,
method: requestInfo.method,
header: requestInfo.header,
url: requestInfo.url,
}
}
}
let requestResult = {};
if(requestInfo.method == "JSONP"){
requestResult = await jsonpRequest(requestInfo);
} else {
requestResult = await dispatchRequest(requestInfo);
}
//是否用外部的数据处理方法
if (requestInfo.isFactory && this.dataFactory) {
//数据处理
let result = await this.dataFactory({
...requestInfo,
response: requestResult
});
return Promise.resolve(result);
} else {
return Promise.resolve(requestResult);
}
} catch (err){
this.requestError && this.requestError(err);
return Promise.reject(err);
} finally {
// 如果请求开始未运行到,请求结束也不运行
if(runRequestStart){
this.requestEnd && this.requestEnd(requestInfo);
}
}
}
}
// 获取合并的数据
export const mergeConfig = function(_this, options) {
//判断url是不是链接
let urlType = /^(http|https):\/\//.test(options.url);
let config = Object.assign({
timeout: _this.timeout
}, _this.config, options);
if (options.method == "FILE") {
config.url = urlType ? options.url : _this.fileUrl + options.url;
} else {
config.url = urlType ? options.url : _this.baseUrl + options.url;
}
//请求头
if (options.header) {
config.header = Object.assign({}, _this.header, options.header);
} else {
config.header = Object.assign({}, _this.header);
}
return config;
}
// 请求
export const dispatchRequest = function(requestInfo) {
return new Promise((resolve, reject) => {
let requestAbort = true;
let requestData = {
url: requestInfo.url,
header: requestInfo.header, //加入请求头
success: (res) => {
requestAbort = false;
resolve(res);
},
fail: (err) => {
requestAbort = false;
if(err.errMsg == "request:fail abort"){
reject({
errMsg: "请求超时,请重新尝试",
statusCode: 0,
});
} else {
reject(err);
}
}
};
//请求类型
if (requestInfo.method) {
requestData.method = requestInfo.method;
}
if (requestInfo.data) {
requestData.data = requestInfo.data;
}
// #ifdef MP-WEIXIN || MP-ALIPAY
if (requestInfo.timeout) {
requestData.timeout = requestInfo.timeout;
}
// #endif
if (requestInfo.dataType) {
requestData.dataType = requestInfo.dataType;
}
// #ifndef APP-PLUS || MP-ALIPAY
if (requestInfo.responseType) {
requestData.responseType = requestInfo.responseType;
}
// #endif
// #ifdef H5
if (requestInfo.withCredentials) {
requestData.withCredentials = requestInfo.withCredentials;
}
// #endif
let requestTask = uni.request(requestData);
setTimeout(() => {
if(requestAbort){
requestTask.abort();
}
}, requestInfo.timeout)
})
}
// jsonp请求
export const jsonpRequest = function(requestInfo) {
return new Promise((resolve, reject) => {
let dataStr = '';
Object.keys(requestInfo.data).forEach(key => {
dataStr += key + '=' + requestInfo.data[key] + '&';
});
//匹配最后一个&并去除
if (dataStr !== '') {
dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));
}
requestInfo.url = requestInfo.url + '?' + dataStr;
let callbackName = "callback" + Math.ceil(Math.random() * 1000000);
// #ifdef H5
window[callbackName] = function(data) {
resolve(data);
}
let script = document.createElement("script");
script.src = requestInfo.url + "&callback=" + callbackName;
document.head.appendChild(script);
// 及时删除,防止加载过多的JS
document.head.removeChild(script);
// #endif
});
}
\ No newline at end of file
/***************纯粹的数据请求(如果使用这种可以删除掉fileUpload.js)******************/
// import request from "./core/request.js";
// export default request;
/********数据请求同时继承了文件上传(包括七牛云上传)************/
import upload from "./upload/upload.js";
export default upload;
\ No newline at end of file
# request请求、配置简单、批量上传图片、视频、超强适应性(支持多域名请求)
1. 配置简单、源码清晰注释多、适用于一项目多域名请求、第三方请求、七牛云图片上传、本地服务器图片上传等等
2. 支持请求`get``post``put``delete`
3. 自动显示请求加载动画(可单个接口关闭)
4. 全局`api`数据处理函数,只回调请求正确的数据(可单个接口关闭)
5. 未登录或登录失效自动拦截并调用登录方法(可单个接口关闭)
6. 全局自动提示接口抛出的错误信息(可单个接口关闭)
7. 支持 Promise
8. 支持拦截器
9. 支持七牛云文件(图片、视频)批量上传
10. 支持本地服务器文件(图片、视频)批量上传
11. 支持上传文件拦截过滤
12. 支持上传文件进度监听
13. 支持上传文件单张成功回调
| `QQ交流群(607391225)` | `微信交流群(加我好友备注"进群")` |
| ----------------------------|--------------------------- |
|![QQ交流群](http://qn.kemean.cn//upload/202004/14/15868301778472k7oubi6.png)|![微信交流群](https://qn.kemean.cn/upload/202010/13/weiXin_group_code.jpg)|
| QQ群号:607391225 |微信号:zhou0612wei|
### [点击跳转-插件示例](https://ext.dcloud.net.cn/plugin?id=2009)
### [点击跳转-5年的web前端开源的uni-app快速开发模板-下载看文档](https://ext.dcloud.net.cn/plugin?id=2009)
### 常见问题
1.接口请求成功了,没有返回数据或者数据是走的catch回调
答:`requestConfig.js` 请求配置文件里面,有一个`$http.dataFactory`方法,里面写的只是参考示例,`此方法需要开发者根据各自的接口返回类型修改`
2.官方的方法有数据,本插件方法请求报错跨域问题
答:`requestConfig.js` 请求配置文件里面,`header`请求头设置的`content-type`请求类型需求和后台保持一致
3.登录后用户`token`怎么设置?
答:`requestConfig.js` 请求配置文件里面,`$http.requestStart`请求开始拦截器里面设置
4.怎么判断上传的文件(图片)太大?怎么过滤掉太大的文件(图片)?
答:`requestConfig.js` 请求配置文件里面,`$http.requestStart`请求开始拦截器里面设置
5.接口请求成功了,一直提示“网络错误,请检查一下网络”?
答:`requestConfig.js` 请求配置文件里面,有一个`$http.dataFactory`方法,里面写的只是参考示例,`此方法需要开发者根据各自的接口返回类型修改`
### 本次更新注意事项
1. 所有的headers都改成了header(和官方统一)
2. 七牛云的获取token等信息提取到了`requestConfig.js`文件,参考如下
### 文件说明
1. `request => core` 请求方法的目录
2. `request => core => request.js` 请求方法的class文件
3. `request => core => utils.js` 请求方法的源码文件
4. `request => upload` 上传方法的目录
5. `request => upload => upload.js` 上传方法的class文件
6. `request => upload => utils.js` 上传方法源码文件
7. `request => upload => qiniuUploader.js` 七牛云官方上传文件
8. `request => index.js` 输出方法的文件
9. `requestConfig.js` 请求配置文件(具体看代码)
### 在main.js引入并挂在Vue上
```
import $http from '@/zhouWei-request/requestConfig';
Vue.prototype.$http = $http;
```
### `requestConfig.js`配置说明(requestConfig.js有配置示例)
```
import request from "@/plugins/request";
//可以new多个request来支持多个域名请求
let $http = new request({
//接口请求地址
baseUrl: "https://twin-ui.com/", //示例域名,请自行设计
//服务器本地上传文件地址
fileUrl: "https://twin-ui.com/", //示例域名,请自行设计
// 服务器上传图片默认url
defaultUploadUrl: "api/common/v1/upload_image",
//设置请求头(如果使用报错跨域问题,可能是content-type请求类型和后台那边设置的不一致)
header: {
'Content-Type': 'application/json;charset=UTF-8'
},
// 请求超时时间(默认6000)
timeout: 6000,
// 默认配置(可不写)
config: {
// 是否自动提示错误
isPrompt: true,
// 是否显示加载动画
load: true,
// 是否使用数据工厂
isFactory: true,
// ... 可写更多配置
}
});
// 添加获取七牛云token的方法
$http.getQnToken = function(callback){
//该地址需要开发者自行配置(每个后台的接口风格都不一样)
$http.get("api/common/v1/qn_upload").then(data => {
/*
*接口返回参数:
*visitPrefix:访问文件的域名
*token:七牛云上传token
*folderPath:上传的文件夹
*region: 地区 默认为:SCN
*/
callback({
visitPrefix: data.visitPrefix,
token: data.token,
folderPath: data.folderPath
});
});
}
//当前接口请求数
let requestNum = 0;
//请求开始拦截器
$http.requestStart = function(options) {
if (options.load) {
if (requestNum <= 0) {
//打开加载动画
uni.showLoading({
title: '加载中',
mask: true
});
}
requestNum += 1;
}
// 图片上传大小限制
if (options.method == "FILE" && options.maxSize) {
// 文件最大字节: options.maxSize 可以在调用方法的时候加入参数
let maxSize = options.maxSize;
for (let item of options.files) {
if (item.size > maxSize) {
setTimeout(() => {
uni.showToast({
title: "图片过大,请重新上传",
icon: "none"
});
}, 500);
return false;
}
}
}
//请求前加入token
options.header['token'] = "你的项目token";
return options; // return false 表示请求拦截,不会继续请求
}
//请求结束
$http.requestEnd = function(options) {
//判断当前接口是否需要加载动画
if (options.load) {
requestNum = requestNum - 1;
if (requestNum <= 0) {
uni.hideLoading();
}
}
}
//所有接口数据处理(可在接口里设置不调用此方法)
//此方法需要开发者根据各自的接口返回类型修改,以下只是模板
$http.dataFactory = async function(res) {
console.log("接口请求数据", {
url: res.url,
resolve: res.response,
header: res.header,
data: res.data,
method: res.method,
});
if (res.response.statusCode && res.response.statusCode == 200) {
let httpData = res.response.data;
if (typeof (httpData) == "string") {
httpData = JSON.parse(httpData);
}
// 开始----------------------以下是示例-请认真阅读代码-----------------------开始
//判断数据是否请求成功
if (httpData.success || httpData.code == 200) { // 重点------判断接口请求是否成功,成功就返回成功的数据
// ---重点---返回正确的结果(then接受数据)---重点---
return Promise.resolve(httpData);
} else {
//其他错误提示
if (res.isPrompt) { // 是否提示
uni.showToast({
title: httpData.info || httpData.msg, // 重点------把接口返回的错误抛出显示
icon: "none",
duration: 3000
});
}
// ---重点---返回错误的结果(catch接受数据)----重点---
return Promise.reject({
statusCode: 0,
errMsg: "【request】" + (httpData.info || httpData.msg)
});
}
// 结束----------------------以上是示例-请认真阅读代码-----------------------结束
} else {
// 返回错误的结果(catch接受数据)
return Promise.reject({
statusCode: res.response.statusCode,
errMsg: "【request】数据工厂验证不通过"
});
}
};
// 错误回调(所有错误都在这里)
$http.requestError = function (e) {
if (e.statusCode === 0) {
throw e;
} else {
uni.showToast({
title: "网络错误,请检查一下网络",
icon: "none"
});
}
}
```
### 通用请求方法
```
this.$http.request({
url: 'aid/region',
method: "GET", // POST、GET、PUT、DELETE、JSONP,具体说明查看官方文档
data: {pid:0},
timeout: 30000, // 默认 30000 说明:超时时间,单位 ms,具体说明查看官方文档
dataType: "json", // 默认 json 说明:如果设为 json,会尝试对返回的数据做一次 JSON.parse,具体说明查看官方文档
responseType: "text", // 默认 text 说明:设置响应的数据类型。合法值:text、arraybuffer,具体说明查看官方文档
withCredentials: false, // 默认 false 说明:跨域请求时是否携带凭证(cookies),具体说明查看官方文档
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示)
load: true,//(默认 true 说明:本接口是否提示加载动画)
header: { //默认 无 说明:请求头
'Content-Type': 'application/json'
},
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用)
}).then(function (response) {
//这里只会在接口是成功状态返回
}).catch(function (error) {
//这里只会在接口是失败状态返回,不需要去处理错误提示
console.log(error);
});
```
### get请求 正常写法
```
this.$http.get('aid/region',{pid:0}).
then(function (response) {
//这里只会在接口是成功状态返回
}).catch(function (error) {
//这里只会在接口是失败状态返回,不需要去处理错误提示
console.log(error);
});
```
### post请求 async写法
```
async request(){
let data = await this.$http.post('aid/region',{pid:0});
console.log(data);
}
```
### 其他功能配置项
```
let data = await this.$http.post(
'http://www.aaa.com/aid/region', //可以直接放链接(将不启用全局定义域名)
{
pid:0
},
{
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示)
load: true,//(默认 true 说明:本接口是否提示加载动画)
header: { //默认 无 说明:请求头
'Content-Type': 'application/json'
},
isFactory: true //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用)
}
);
```
### `requestConfig.js`可以设置服务器上传图片默认url
```
//可以new多个request来支持多个域名请求
let $http = new request({
//服务器本地上传文件地址
fileUrl: base.baseUrl,
// 服务器上传图片默认url
defaultUploadUrl: "api/common/v1/upload_image",
});
```
```
// 上传可以不用传递url(使用全局的上传图片url)
this.$http.urlImgUpload({
name:"后台接受文件key名称", //默认 file
count:"最大选择数",//默认 9
sizeType:"选择压缩图原图,默认两个都选",//默认 ['original', 'compressed']
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera']
data:"而外参数" //可不填,
});
// 上传可以不用传递url(使用全局的上传图片url)
this.$http.urlVideoUpload({
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera']
compressed:"是否压缩所选的视频源文件,默认值为 true,需要压缩",//默认 false
maxDuration: "拍摄视频最长拍摄时间,单位秒。最长支持 60 秒", //默认 60
camera: '前置还是后置摄像头', //'front'、'back',默认'back'
name:"后台接受文件key名称", //默认 file
data:"而外参数" //可不填,
});
// 上传可以不用传递url(使用全局的上传图片url)
this.$http.urlFileUpload({
files: [], // 必填 临时文件路径 格式: [{path: "图片地址"}]
data:"向服务器传递的参数", //可不填
name:"后台接受文件key名称", //默认 file
});
```
### 本地服务器图片上传(支持多张上传)
```
this.$http.urlImgUpload('flie/upload',{
name:"后台接受文件key名称", //默认 file
count:"最大选择数",//默认 9
sizeType:"选择压缩图原图,默认两个都选",//默认 ['original', 'compressed']
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera']
data:"而外参数" //可不填,
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示)
load: true,//(默认 true 说明:本接口是否提示加载动画)
header: { //默认 无 说明:请求头
'Content-Type': 'application/json'
},
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用)
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制)
onSelectComplete: res => {
console.log("选择完成返回:",res);
},
onEachUpdate: res => {
console.log("单张上传成功返回:",res);
},
onProgressUpdate: res => {
console.log("上传进度返回:",res);
}
}).then(res => {
console.log("全部上传完返回结果:",res);
});
```
### 本地服务器视频上传
```
this.$http.urlVideoUpload('flie/upload',{
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera']
compressed:"是否压缩所选的视频源文件,默认值为 true,需要压缩",//默认 false
maxDuration: "拍摄视频最长拍摄时间,单位秒。最长支持 60 秒", //默认 60
camera: '前置还是后置摄像头', //'front'、'back',默认'back'
name:"后台接受文件key名称", //默认 file
data:"而外参数" //可不填,
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示)
load: true,//(默认 true 说明:本接口是否提示加载动画)
header: { //默认 无 说明:请求头
'Content-Type': 'application/json'
},
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用)
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制)
onProgressUpdate: res => {
console.log("上传进度返回:",res);
},
onSelectComplete: res => {
console.log("选择完成返回:",res);
},
}).then(res => {
console.log("全部上传完返回结果:",res);
});
```
### 本地服务器文件上传(支持多张上传)
```
this.$http.urlFileUpload("flie/upload",{
files: [], // 必填 临时文件路径 格式: [{path: "图片地址"}]
data:"向服务器传递的参数", //可不填
name:"后台接受文件key名称", //默认 file
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示)
load: true,//(默认 true 说明:本接口是否提示加载动画)
header: { //默认 无 说明:请求头
'Content-Type': 'application/json'
},
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用)
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制)
onEachUpdate: res => {
console.log("单张上传成功返回:",res);
},
onProgressUpdate: res => {
console.log("上传进度返回:",res);
}
}).then(res => {
console.log("全部上传完返回结果:",res);
});
```
### 七牛云图片上传(支持多张上传)
```
this.$http.qnImgUpload({
count:"最大选择数", // 默认 9
sizeType:"选择压缩图原图,默认两个都选", // 默认 ['original', 'compressed']
sourceType:"选择相机拍照或相册上传 默认两个都选", // 默认 ['album','camera']
load: true, //(默认 true 说明:本接口是否提示加载动画)
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制)
onSelectComplete: res => {
console.log("选择完成返回:",res);
},
onEachUpdate: res => {
console.log("单张上传成功返回:",res);
},
onProgressUpdate: res => {
console.log("上传进度返回:",res);
}
}).then(res => {
console.log("全部上传完返回结果:",res);
});
```
### 七牛云视频上传
```
this.$http.qnVideoUpload({
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera']
compressed:"是否压缩所选的视频源文件,默认值为 true,需要压缩",//默认 false
maxDuration: "拍摄视频最长拍摄时间,单位秒。最长支持 60 秒", //默认 60
camera: '前置还是后置摄像头', //'front'、'back',默认'back'
load: true,//(默认 true 说明:本接口是否提示加载动画)
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制)
onSelectComplete: res => {
console.log("选择完成返回:",res);
},
onProgressUpdate: res => {
console.log("上传进度返回:",res);
}
}).then(res => {
console.log("全部上传完返回结果:",res);
});
```
### 七牛云文件上传(支持多张上传)
```
this.$http.qnFileUpload(
{
files:[], // 必填 临时文件路径 格式: [{path: "图片地址"}]
load: true, //(默认 true 说明:本接口是否提示加载动画)
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制)
onEachUpdate: res => {
console.log("单张上传成功返回:",res);
},
onProgressUpdate: res => {
console.log("上传进度返回:",res);
}
}).then(res => {
console.log("全部上传完返回结果:",res);
});
```
### jsonp 跨域请求(只支持H5)
```
let data = await this.$http.jsonp('http://www.aaa.com/aid/region',{pid:0}, {
isFactory: false, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用)
});
```
// created by gpake
(function () {
var config = {
qiniuRegion: '',
qiniuImageURLPrefix: '',
qiniuUploadToken: '',
qiniuUploadTokenURL: '',
qiniuUploadTokenFunction: null,
qiniuShouldUseQiniuFileName: false
}
module.exports = {
init: init,
upload: upload,
}
// 在整个程序生命周期中,只需要 init 一次即可
// 如果需要变更参数,再调用 init 即可
function init(options) {
config = {
qiniuRegion: '',
qiniuImageURLPrefix: '',
qiniuUploadToken: '',
qiniuUploadTokenURL: '',
qiniuUploadTokenFunction: null,
qiniuShouldUseQiniuFileName: false
};
updateConfigWithOptions(options);
}
function updateConfigWithOptions(options) {
if (options.region) {
config.qiniuRegion = options.region;
} else {
console.error('qiniu uploader need your bucket region');
}
if (options.uptoken) {
config.qiniuUploadToken = options.uptoken;
} else if (options.uptokenURL) {
config.qiniuUploadTokenURL = options.uptokenURL;
} else if (options.uptokenFunc) {
config.qiniuUploadTokenFunction = options.uptokenFunc;
}
if (options.domain) {
config.qiniuImageURLPrefix = options.domain;
}
config.qiniuShouldUseQiniuFileName = options.shouldUseQiniuFileName
}
function upload(filePath, success, fail, options, progress, cancelTask) {
if (null == filePath) {
console.error('qiniu uploader need filePath to upload');
return;
}
if (options) {
updateConfigWithOptions(options);
}
if (config.qiniuUploadToken) {
doUpload(filePath, success, fail, options, progress, cancelTask);
} else if (config.qiniuUploadTokenURL) {
getQiniuToken(function () {
doUpload(filePath, success, fail, options, progress, cancelTask);
});
} else if (config.qiniuUploadTokenFunction) {
config.qiniuUploadToken = config.qiniuUploadTokenFunction();
if (null == config.qiniuUploadToken && config.qiniuUploadToken.length > 0) {
console.error('qiniu UploadTokenFunction result is null, please check the return value');
return
}
doUpload(filePath, success, fail, options, progress, cancelTask);
} else {
console.error('qiniu uploader need one of [uptoken, uptokenURL, uptokenFunc]');
return;
}
}
function doUpload(filePath, success, fail, options, progress, cancelTask) {
if (null == config.qiniuUploadToken && config.qiniuUploadToken.length > 0) {
console.error('qiniu UploadToken is null, please check the init config or networking');
return
}
var url = uploadURLFromRegionCode(config.qiniuRegion);
var fileName = filePath.split('//')[1];
if (options && options.key) {
fileName = options.key;
}
var formData = {
'token': config.qiniuUploadToken
};
if (!config.qiniuShouldUseQiniuFileName) {
formData['key'] = fileName
}
var uploadTask = wx.uploadFile({
url: url,
filePath: filePath,
name: 'file',
formData: formData,
success: function (res) {
var dataString = res.data
if (res.data.hasOwnProperty('type') && res.data.type === 'Buffer') {
dataString = String.fromCharCode.apply(null, res.data.data)
}
try {
var dataObject = JSON.parse(dataString);
//do something
var imageUrl = config.qiniuImageURLPrefix + '/' + dataObject.key;
dataObject.imageURL = imageUrl;
if (success) {
success(dataObject);
}
} catch (e) {
console.log('parse JSON failed, origin String is: ' + dataString)
if (fail) {
fail(e);
}
}
},
fail: function (error) {
console.error(error);
if (fail) {
fail(error);
}
}
})
uploadTask.onProgressUpdate((res) => {
progress && progress(res)
})
cancelTask && cancelTask(() => {
uploadTask.abort()
})
}
function getQiniuToken(callback) {
wx.request({
url: config.qiniuUploadTokenURL,
success: function (res) {
var token = res.data.uptoken;
if (token && token.length > 0) {
config.qiniuUploadToken = token;
if (callback) {
callback();
}
} else {
console.error('qiniuUploader cannot get your token, please check the uptokenURL or server')
}
},
fail: function (error) {
console.error('qiniu UploadToken is null, please check the init config or networking: ' + error);
}
})
}
function uploadURLFromRegionCode(code) {
var uploadURL = null;
switch (code) {
case 'ECN': uploadURL = 'https://up.qbox.me'; break;
case 'NCN': uploadURL = 'https://up-z1.qbox.me'; break;
case 'SCN': uploadURL = 'https://up-z2.qbox.me'; break;
case 'NA': uploadURL = 'https://up-na0.qbox.me'; break;
case 'ASG': uploadURL = 'https://up-as0.qbox.me'; break;
default: console.error('please make the region is with one of [ECN, SCN, NCN, NA, ASG]');
}
return uploadURL;
}
})();
\ No newline at end of file
import request from "./../core/request.js";
const {
chooseImage,
chooseVideo,
qiniuUpload,
urlUpload
} = require("./utils");
import {
mergeConfig
} from "./../core/utils.js";
export default class fileUpload extends request {
constructor(props) {
// 调用实现父类的构造函数
super(props);
}
//七牛云上传图片
async qnImgUpload(options = {}) {
let files;
try {
files = await chooseImage(options);
// 选择完成回调
options.onSelectComplete && options.onSelectComplete(files);
} catch (err) {
this.requestError && this.requestError(err);
return Promise.reject(err);
}
if (files) {
return this.qnFileUpload({
...options,
files: files
});
}
}
//七牛云上传视频
async qnVideoUpload(options = {}) {
let files;
try {
files = await chooseVideo(options);
// 选择完成回调
options.onSelectComplete && options.onSelectComplete(files);
} catch (err) {
this.requestError && this.requestError(err);
return Promise.reject(err);
}
if (files) {
return this.qnFileUpload({
...options,
files: files
});
}
}
//七牛云文件上传(支持多张上传)
async qnFileUpload(options = {}) {
let requestInfo;
try {
// 数据合并
requestInfo = {
...this.config,
...options,
header: {},
method: "FILE"
};
//请求前回调
if (this.requestStart) {
let requestStart = this.requestStart(requestInfo);
if (typeof requestStart == "object") {
let changekeys = ["load", "files"];
changekeys.forEach(key => {
requestInfo[key] = requestStart[key];
});
} else {
throw {
errMsg: "【request】请求开始拦截器未通过",
statusCode: 0,
data: requestInfo.data,
method: requestInfo.method,
header: requestInfo.header,
url: requestInfo.url,
}
}
}
let requestResult = await qiniuUpload(requestInfo, this.getQnToken);
return Promise.resolve(requestResult);
} catch (err) {
this.requestError && this.requestError(err);
return Promise.reject(err);
} finally {
this.requestEnd && this.requestEnd(requestInfo);
}
}
//本地服务器图片上传
async urlImgUpload() {
let options = {};
if (arguments[0]) {
if (typeof(arguments[0]) == "string") {
options.url = arguments[0];
} else if (typeof(arguments[0]) == "object") {
options = Object.assign(options, arguments[0]);
}
}
if (arguments[1] && typeof(arguments[1]) == "object") {
options = Object.assign(options, arguments[1]);
}
try {
options.files = await chooseImage(options);
// 选择完成回调
options.onSelectComplete && options.onSelectComplete(options.files);
} catch (err) {
this.requestError && this.requestError(err);
return Promise.reject(err);
}
if (options.files) {
return this.urlFileUpload(options);
}
}
//本地服务器上传视频
async urlVideoUpload() {
let options = {};
if (arguments[0]) {
if (typeof(arguments[0]) == "string") {
options.url = arguments[0];
} else if (typeof(arguments[0]) == "object") {
options = Object.assign(options, arguments[0]);
}
}
if (arguments[1] && typeof(arguments[1]) == "object") {
options = Object.assign(options, arguments[1]);
}
try {
options.files = await chooseVideo(options);
// 选择完成回调
options.onSelectComplete && options.onSelectComplete(options.files);
} catch (err) {
this.requestError && this.requestError(err);
return Promise.reject(err);
}
if (options.files) {
return this.urlFileUpload(options);
}
}
//本地服务器文件上传方法
async urlFileUpload() {
let requestInfo = {
method: "FILE"
};
if (arguments[0]) {
if (typeof(arguments[0]) == "string") {
requestInfo.url = arguments[0];
} else if (typeof(arguments[0]) == "object") {
requestInfo = Object.assign(requestInfo, arguments[0]);
}
}
if (arguments[1] && typeof(arguments[1]) == "object") {
requestInfo = Object.assign(requestInfo, arguments[1]);
}
if (!requestInfo.url && this.defaultUploadUrl) {
requestInfo.url = this.defaultUploadUrl;
}
if (!requestInfo.name && this.defaultFileName) {
requestInfo.name = this.defaultFileName;
}
// 请求数据
// 是否运行过请求开始钩子
let runRequestStart = false;
try {
if (!requestInfo.url) {
throw {
errMsg: "【request】文件上传缺失数据url",
statusCode: 0,
data: requestInfo.data,
method: requestInfo.method,
header: requestInfo.header,
url: requestInfo.url,
}
}
// 数据合并
requestInfo = mergeConfig(this, requestInfo);
// 代表之前运行到这里
runRequestStart = true;
//请求前回调
if (this.requestStart) {
let requestStart = this.requestStart(requestInfo);
if (typeof requestStart == "object") {
let changekeys = ["data", "header", "isPrompt", "load", "isFactory", "files"];
changekeys.forEach(key => {
requestInfo[key] = requestStart[key];
});
} else {
throw {
errMsg: "【request】请求开始拦截器未通过",
statusCode: 0,
data: requestInfo.data,
method: requestInfo.method,
header: requestInfo.header,
url: requestInfo.url,
}
}
}
let requestResult = await urlUpload(requestInfo, this.dataFactory);
return Promise.resolve(requestResult);
} catch (err) {
this.requestError && this.requestError(err);
return Promise.reject(err);
} finally {
if (runRequestStart) {
this.requestEnd && this.requestEnd(requestInfo);
}
}
}
}
const qiniuUploader = require("./qiniuUploader");
//七牛云上传文件命名
export const randomChar = function(l, url = "") {
const x = "0123456789qwertyuioplkjhgfdsazxcvbnm";
let tmp = "";
let time = new Date();
for (let i = 0; i < l; i++) {
tmp += x.charAt(Math.ceil(Math.random() * 100000000) % x.length);
}
return (
"file/" +
url +
time.getTime() +
tmp
);
}
//图片选择
export const chooseImage = function(data) {
return new Promise((resolve, reject) => {
uni.chooseImage({
count: data.count || 9, //默认9
sizeType: data.sizeType || ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: data.sourceType || ['album', 'camera'], //从相册选择
success: function(res) {
resolve(res.tempFiles);
},
fail: err => {
reject({
errMsg: err.errMsg,
errCode: err.errCode,
statusCode: 0,
});
}
});
});
}
//视频选择
export const chooseVideo = function(data) {
return new Promise((resolve, reject) => {
uni.chooseVideo({
sourceType: data.sourceType || ['album', 'camera'], //从相册选择
compressed: data.compressed || false, //是否压缩所选的视频源文件,默认值为 true,需要压缩。
maxDuration: data.maxDuration || 60, //拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。
camera: data.camera || 'back', //'front'、'back',默认'back'
success: function(res) {
let files = [{
path: res.tempFilePath
}];
// #ifdef APP-PLUS || H5 || MP-WEIXIN
files[0].duration = res.duration;
files[0].size = res.size;
files[0].height = res.height;
files[0].width = res.width;
// #endif
// #ifdef H5
files[0].name = res.name;
// #endif
resolve(files);
},
fail: err => {
reject({
errMsg: err.errMsg,
errCode: err.errCode,
statusCode: 0,
});
}
});
});
}
// 七牛云上传
export const qiniuUpload = function(requestInfo, getQnToken) {
return new Promise((resolve, reject) => {
if (Array.isArray(requestInfo.files)) {
let len = requestInfo.files.length;
let fileList = new Array;
if (getQnToken) {
getQnToken(qnRes => {
/*
*接口返回参数:
*visitPrefix:访问文件的域名
*token:七牛云上传token
*folderPath:上传的文件夹
*region: 地区 默认为:SCN
*/
let prefixLen = qnRes.visitPrefix.length;
if(qnRes.visitPrefix.charAt(prefixLen - 1) == '/'){
qnRes.visitPrefix = qnRes.visitPrefix.substring(0, prefixLen - 1)
}
uploadFile(0);
function uploadFile(i) {
let item = requestInfo.files[i];
let updateUrl = randomChar(10, qnRes.folderPath);
let fileData = {
fileIndex: i,
files: requestInfo.files,
...item
};
if (item.name) {
fileData.name = item.name;
let nameArr = item.name.split(".");
updateUrl += "." + nameArr[nameArr.length - 1];
}
// 交给七牛上传
qiniuUploader.upload(item.path || item, (res) => {
fileData.url = res.imageURL;
requestInfo.onEachUpdate && requestInfo.onEachUpdate({
url: res.imageURL,
...fileData
});
fileList.push(res.imageURL);
if (len - 1 > i) {
uploadFile(i + 1);
} else {
resolve(fileList);
}
}, (error) => {
reject(error);
}, {
region: qnRes.region || 'SCN', //地区
domain: qnRes.visitPrefix, // bucket 域名,下载资源时用到。
key: updateUrl,
uptoken: qnRes.token, // 由其他程序生成七牛 uptoken
uptokenURL: 'UpTokenURL.com/uptoken' // 上传地址
}, (res) => {
console.log(requestInfo);
requestInfo.onProgressUpdate && requestInfo.onProgressUpdate(Object.assign({}, fileData, res));
// console.log('上传进度', res.progress)
// console.log('已经上传的数据长度', res.totalBytesSent)
// console.log('预期需要上传的数据总长度', res.totalBytesExpectedToSend)
});
}
});
} else {
reject({
errMsg: "请添加七牛云回调方法:getQnToken",
statusCode: 0
});
}
} else {
reject({
errMsg: "files 必须是数组类型",
statusCode: 0
});
};
});
}
// 服务器URL上传
export const urlUpload = function(requestInfo, dataFactory) {
return new Promise((resolve, reject) => {
// 本地文件上传去掉默认Content-Type
if (requestInfo.header['Content-Type']) {
delete requestInfo.header['Content-Type'];
}
// 本地文件上传去掉默认Content-Type
if (requestInfo.header['content-type']) {
delete requestInfo.header['content-type'];
}
if (Array.isArray(requestInfo.files)) {
// #ifdef APP-PLUS || H5
let files = [];
let fileData = {
files: requestInfo.files,
name: requestInfo.name || "file"
};
requestInfo.files.forEach(item => {
let fileInfo = {
name: requestInfo.name || "file",
};
if(item.path){
fileInfo.uri = item.path;
} else {
fileInfo.file = item;
}
files.push(fileInfo);
});
let config = {
url: requestInfo.url,
files: files,
header: requestInfo.header, //加入请求头
success: (response) => {
//是否用外部的数据处理方法
if (requestInfo.isFactory && dataFactory) {
//数据处理
dataFactory({
...requestInfo,
response: response,
}).then(data => {
requestInfo.onEachUpdate && requestInfo.onEachUpdate({
data: data,
...fileData
});
resolve(data);
},err => {
reject(err);
});
} else {
requestInfo.onEachUpdate && requestInfo.onEachUpdate({
data: response,
...fileData
});
resolve(response);
}
},
fail: (err) => {
reject(err);
}
};
if (requestInfo.data) {
config.formData = requestInfo.data;
}
const uploadTask = uni.uploadFile(config);
uploadTask.onProgressUpdate(res => {
requestInfo.onProgressUpdate && requestInfo.onProgressUpdate(Object.assign({}, fileData, res));
});
// #endif
// #ifdef MP
const len = requestInfo.files.length - 1;
let fileList = new Array;
fileUpload(0);
function fileUpload(i) {
let item = requestInfo.files[i];
let fileData = {
fileIndex: i,
files: requestInfo.files,
...item
};
let config = {
url: requestInfo.url,
filePath: item.path,
header: requestInfo.header, //加入请求头
name: requestInfo.name || "file",
success: (response) => {
//是否用外部的数据处理方法
if (requestInfo.isFactory && dataFactory) {
//数据处理
dataFactory({
...requestInfo,
response: response,
}).then(data => {
fileList.push(data);
requestInfo.onEachUpdate && requestInfo.onEachUpdate({
data: data,
...fileData
});
if (len <= i) {
resolve(fileList);
} else {
fileUpload(i + 1);
}
},err => {
reject(err);
});
} else {
requestInfo.onEachUpdate && requestInfo.onEachUpdate({
data: response,
...fileData
});
fileList.push(response);
if (len <= i) {
resolve(fileList);
} else {
fileUpload(i + 1);
}
}
},
fail: (err) => {
reject(err);
}
};
if (requestInfo.data) {
config.formData = requestInfo.data;
}
const uploadTask = uni.uploadFile(config);
uploadTask.onProgressUpdate(res => {
requestInfo.onProgressUpdate && requestInfo.onProgressUpdate(Object.assign({}, fileData, res));
});
}
// #endif
} else {
reject({
errMsg: "files 必须是数组类型",
statusCode: 0
});
}
});
}
// #ifdef APP-PLUS
let alphaBg, shareMenu, showState = false;
// 关闭弹窗
export const closeShare = function(){
alphaBg && alphaBg.close();
alphaBg && shareMenu.close();
if(showState){
showState = false;
return true
} else {
showState = false;
return false
}
}
// 复制
function onCopy(item, shareInfo,callback) {
let copyInfo = shareInfo.shareUrl || shareInfo.shareContent || shareInfo.shareImg;
if (copyInfo) {
uni.setClipboardData({
data: copyInfo,
success:() => {
uni.showToast({
title: "已复制到剪贴板",
icon: "none"
});
callback && callback(item);
}
});
}
}
// 更多
function onMore(item, shareInfo,callback) {
plus.share.sendWithSystem({
type: "text",
title: shareInfo.shareTitle || "",
href: shareInfo.shareUrl || "",
content: shareInfo.shareContent || "",
},function(res){
callback && callback(item);
},function(err){
console.log(err);
});
}
// 分享
function onShare(item, shareInfo,callback) {
if (shareInfo.type == undefined) {
shareInfo.type = item.type;
}
let shareObj = {
provider: item.provider,
type: shareInfo.type,
success: (res) => {
callback && callback(item);
console.log("success:" + JSON.stringify(res));
},
fail: (err) => {
console.log("分享失败,参数缺失 fail:" + JSON.stringify(err));
}
};
if (shareInfo.shareTitle) {
shareObj.title = shareInfo.shareTitle;
}else if(item.provider == "qq"){
uni.showToast({
title: "缺失分享的标题",
icon: "none"
});
return;
}
if(shareInfo.type == 0 || item.provider == "qq"){
if (shareInfo.shareUrl) {
shareObj.href = shareInfo.shareUrl;
}else{
uni.showToast({
title: "缺失分享的地址",
icon: "none"
});
return;
}
}
if([0,1,3,4].includes(shareInfo.type)){
if (shareInfo.shareContent) {
shareObj.summary = shareInfo.shareContent;
}else{
uni.showToast({
title: "缺失分享的描述",
icon: "none"
});
return;
}
}
if([0,2,5].includes(shareInfo.type)){
if (shareInfo.shareImg) {
shareObj.imageUrl = shareInfo.shareImg;
}else{
uni.showToast({
title: "缺失分享的图片",
icon: "none"
});
return;
}
}
if([3,4].includes(shareInfo.type)){
if (shareInfo.mediaUrl) {
shareObj.mediaUrl = shareInfo.mediaUrl;
}else{
uni.showToast({
title: "缺失分享的音视频地址",
icon: "none"
});
return;
}
}
if(shareInfo.type == 5){
if (shareInfo.appId && shareInfo.appPath && shareInfo.appWebUrl) {
shareObj.miniProgram = {
id:shareInfo.appId,
path:shareInfo.appPath,
webUrl:shareInfo.appWebUrl,
};
if(shareInfo.appType){
shareObj.miniProgram.type = shareInfo.appType;
}
}else{
uni.showToast({
title: "缺失分享小程序的参数",
icon: "none"
});
return;
}
}
if (item.scene) {
shareObj.scene = item.scene;
}
uni.share(shareObj);
}
let otherShareList = [
{
icon: "/static/share/icon_copy.png",
text: "复制",
provider: "copy",
onClick: onCopy
},
{
icon: "/static/share/icon_more.png",
text: "更多",
provider: "more",
onClick: onMore
}
];
let platformShareList = [];
// 获取服务商支持的分享
uni.getProvider({
service: 'share',
success: function (res) {
if (res.provider.includes('sinaweibo')) {
platformShareList = [{
icon: "/static/share/icon_weibo.png",
text: "新浪微博",
onClick: onShare,
provider: "sinaweibo",
type: 0
}].concat(platformShareList);
}
if (res.provider.includes('qq')) {
platformShareList = [{
icon: "/static/share/icon_qq.png",
text: "QQ",
onClick: onShare,
provider: "qq",
type: 1
}].concat(platformShareList);
}
if (res.provider.includes('weixin')) {
platformShareList = [{
icon: "/static/share/icon_weixin.png",
text: "微信好友",
onClick: onShare,
provider: "weixin",
scene: "WXSceneSession",
type: 0
},
{
icon: "/static/share/icon_pengyouquan.png",
text: "朋友圈",
onClick: onShare,
provider: "weixin",
scene: "WXSenceTimeline",
type: 0
},
{
icon: "/static/share/ic_xiaochengxu.png",
text: "小程序",
onClick: onShare,
provider: "weixin",
scene: "WXSceneSession",
type: 5
}].concat(platformShareList);
}
}
});
// 根据type类型过滤掉不支持的平台
function platformFilter(data){
let platformList = [];
let supportList = [
["weixin","sinaweibo"],
["weixin","sinaweibo","qq"],
["weixin","sinaweibo","qq"],
["weixin","qq"],
["weixin","sinaweibo"],
["weixin"],
];
let currentSupport = [];
if(data.type >= 0 && data.type <= 5){
currentSupport = supportList[data.type];
}
platformShareList.forEach((item,index) => {
if(data.type >= 0 && data.type <= 5){
if(currentSupport.includes(item.provider)){
if(item.provider == "weixin"){
if(item.text == "小程序"){
if(data.type == 5){
platformList.push(item);
}
}else if(data.type !== 5){
platformList.push(item);
}
} else {
platformList.push(item);
}
}
}else{
if(item.provider == "weixin"){
if(item.text == "小程序"){
if(data.appId && data.appPath){
platformList.push(item);
}
}else {
platformList.push(item);
}
} else {
platformList.push(item);
}
}
});
return platformList.concat(otherShareList);
}
// 数据处理
function dataFactory(shareInfo = {}) {
let config = {
...shareInfo
};
config.shareTitle = shareInfo.shareTitle || "";
config.shareUrl = shareInfo.shareUrl || "";
config.shareContent = shareInfo.shareContent || "分享的描述";
config.shareImg = shareInfo.shareImg || "分享的图片";
return config;
}
export default function (shareInfo, callback) {
shareInfo = dataFactory(shareInfo);
// 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心
let screenWidth = plus.screen.resolutionWidth
//以360px宽度屏幕为例,上下左右边距及2排按钮边距留25像素,图标宽度55像素,同行图标间的间距在360宽的屏幕是30px,但需要动态计算,以此原则计算4列图标分别的left位置
//图标下的按钮文字距离图标5像素,文字大小12像素
//底部取消按钮高度固定为44px
//TODO 未处理横屏和pad,这些情况6个图标应该一排即可
let marginTop = 25, //上间距
marginLeft = 25, //左间距
iconWidth = 55, //图标宽宽
iconHeight = 55, //图标高度
icontextSpace = 10, //图标与文字间距
textHeight = 12 //文字大小
let left1 = marginLeft / 360 * screenWidth;
let colNumber = Math.floor(screenWidth / (iconWidth + marginLeft));
let i = (screenWidth - (iconWidth + marginLeft) * colNumber - marginLeft) / (colNumber + 1);
let initMargin = marginLeft + i;
let itemWidth = iconWidth + initMargin;
let itemHeight = iconHeight + icontextSpace + textHeight + marginTop;
let textTop = iconHeight + icontextSpace;
alphaBg = new plus.nativeObj.View("alphaBg", { //先创建遮罩层
top: '0px',
left: '0px',
height: '100%',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.5)'
});
alphaBg.addEventListener("click", function () { //处理遮罩层点击
alphaBg.close();
shareMenu.close();
});
let shareList = platformFilter(shareInfo);
shareMenu = new plus.nativeObj.View("shareMenu", { //创建底部图标菜单
bottom: '0px',
left: '0px',
height: Math.ceil(shareList.length / colNumber) * itemHeight + marginTop + 44 + 1 + 'px',
width: '100%',
backgroundColor: 'rgb(255,255,255)'
});
//绘制底部图标菜单的内容
shareMenu.draw([{
tag: 'rect', //菜单顶部的分割灰线
color: '#e7e7e7',
position: {
top: '0px',
height: '1px'
}
},
{
tag: 'font',
id: 'sharecancel', //底部取消按钮的文字
text: '取消分享',
textStyles: {
size: '14px'
},
position: {
bottom: '0px',
height: '44px'
}
},
{
tag: 'rect', //底部取消按钮的顶部边线
color: '#e7e7e7',
position: {
bottom: '45px',
height: '1px'
}
}
]);
shareList.map((v, k) => {
let time = new Date().getTime();
let row = Math.floor(k / colNumber);
let col = k % colNumber;
let item = [{
src: v.icon,
id: Math.random() * 1000 + time,
tag: "img",
position: {
top: row * itemHeight + marginTop,
left: col * itemWidth + initMargin,
width: iconWidth,
height: iconWidth
}
}, {
text: v.text,
id: Math.random() * 1000 + time,
tag: "font",
textStyles: {
size: textHeight
},
position: {
top: row * itemHeight + textTop,
left: col * itemWidth + initMargin,
width: iconWidth,
height: iconWidth
}
}];
shareMenu.draw(item);
});
shareMenu.addEventListener("click", function (e) { //处理底部图标菜单的点击事件,根据点击位置触发不同的逻辑
if (e.screenY > plus.screen.resolutionHeight - 44) { //点击了底部取消按钮
alphaBg.close();
shareMenu.close();
} else if (e.clientX < 5 || e.clientX > screenWidth - 5 || e.clientY < 5) {
//屏幕左右边缘5像素及菜单顶部5像素不处理点击
} else { //点击了图标按钮
let x = e.clientX;
let y = e.clientY;
let colIdx = Math.floor(x / itemWidth);
let rowIdx = Math.floor(y / itemHeight);
let tapIndex = colIdx + rowIdx * colNumber;
shareList[tapIndex].onClick(shareList[tapIndex], shareInfo,callback);
}
});
alphaBg.show();
shareMenu.show();
showState = true;
return {
close: function(){
alphaBg && alphaBg.close();
alphaBg && shareMenu.close();
showState = false;
}
};
};
// #endif
\ No newline at end of file
# APP分享、微博分享、QQ分享、微信好友、朋友圈
| `QQ交流群(607391225)` | `微信交流群(加我好友备注"进群")` |
| ----------------------------|--------------------------- |
|![QQ交流群](http://qn.kemean.cn//upload/202004/14/15868301778472k7oubi6.png)|![微信交流群](https://qn.kemean.cn/upload/202010/13/weiXin_group_code.jpg)|
| QQ群号:607391225 |微信号:zhou0612wei|
### [点击跳转-插件示例](https://ext.dcloud.net.cn/plugin?id=2009)
### [点击跳转-5年的web前端开源的uni-app快速开发模板-下载看文档](https://ext.dcloud.net.cn/plugin?id=2009)
### 使用方法 第一步
`manifest.json`文件里面的`App SDK配置``分享`配置对应的平台参数(`不配置参数`在自定义基座里面只会显示`复制``更多`, 配置之后要`重新打包``生效`
### 常见问题
1. 运行示例报错
答:不要在H5、小程序等浏览器上面运行,此插件只适合在android、IOS上运行
2. 分享图标不显示
答:插件里面static文件里面的图片放到根目录的static文件里面, 直接导入插件,结构有问题
3. 分享出去的图片不显示
答:本分享插件使用的官方分享API,有分享问题,仔细研究官方的分享API
### 使用方法 第二步
```
<template>
<button type="default" @click="onShare">APP分享</button>
</template>
<script>
// 引入方法
import appShare, { closeShare } from "@/plugins/share.js"
export default {
methods: {
onShare(){
let shareData = {
shareUrl:"https://kemean.com/",
shareTitle:"分享的标题",
shareContent:"分享的描述",
shareImg:"http://qn.kemean.cn//upload/202004/18/1587189024467w6xj18b1.jpg",
appId : "wxd0e0881530ee4444", // 默认不传type的时候,必须传appId和appPath才会显示小程序图标
appPath : "pages/home/home",
appWebUrl : "https://kemean.com/",
};
// 调用
let shareObj = appShare(shareData,res => {
console.log("分享成功回调",res);
// 分享成功后关闭弹窗
// 第一种关闭弹窗的方式
closeShare();
});
setTimeout(() => {
// 第二种关闭弹窗的方式
shareObj.close();
},5000);
}
}
}
</script>
```
### 插件说明
| 参数名称 | 类型 | 默认值 | 描述
| -------------- |---------- | ------------ | --------------------------------------- |
| shareUrl | String | -- | 分享的地址`(type 为 0 时必传)` |
| shareTitle | String | -- | 分享的标题 |
| shareContent | String | 分享的描述 | 分享的描述`(type 为 1 时必传)` |
| shareImg | String | 分享的图片 | 分享的图片`(type 为 0、2、5 时必传)` |
| mediaUrl | String | -- | 分享的音视频地址`(type 为 3、4 时必传)` |
| type | Number | 参考平台默认值| 分享形式,如图文、纯文字、纯图片、音乐、视频、小程序等,具体参考下面type说明|
### type 值说明
| 值 | 说明 | 支持平台 |
| ------- |--------- | ------------- |
| 0 | 图文 | 微信、新浪微博 |
| 1 | 纯文字 | 全平台支持 |
| 2 | 纯图片 | 全平台支持 |
| 3 | 音乐 | 微信、QQ |
| 4 | 视频 | 微信、新浪微博 |
| 5 | 小程序 | 微信聊天界面 |
### 平台默认值
| 平台 | 默认值 |
| ---------- |--------- |
| 新浪微博 | 0 |
| 微信好友 | 0 |
| 微信朋友圈 | 0 |
| QQ | 1 |
| 微信小程序 | 5 |
### 分享小程序必传参数`(type 为 5 时必传)`
注意:`小程序必须是在微信开放平台与App绑定的才行`
| 参数名称 | 类型 | 默认值 | 描述
| -------------- |---------- | ------------ | --------------------------- |
| appId | String | -- | 微信小程序原始id (比传) |
| appPath | String | -- | 点击链接进入的页面 (比传) |
| appWebUrl | String | "" | 兼容低版本的网页链接(比传) |
| appType | Number | 0 | 微信小程序版本类型,可取值: 0-正式版; 1-测试版; 2-体验版。 默认值为0 |
export const openDb = (name) => {
//如果数据库存在则打开,不存在则创建。
return new Promise((resolve, reject) => {
plus.sqlite.openDatabase({
name: name, //数据库名称
path: `_doc/uniapp_save/${name}.sqlite`, //数据库地址
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
})
}
// 查询所有数据表名
export const getTable = (name) => {
return new Promise((resolve, reject) => {
plus.sqlite.selectSql({
name: name,
sql: "select * FROM sqlite_master where type='table'",
success(e) {
resolve(e);
},
fail(e) {
console.log(e)
reject(e);
}
})
})
}
// 查询表数据总条数
export const getCount = (name, tabName) => {
return new Promise((resolve, reject) => {
plus.sqlite.selectSql({
name: name,
sql: "select count(*) as num from " + tabName,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
})
}
// 查询表是否存在
export const isTable = (name,tabName) => {
return new Promise((resolve, reject) => {
plus.sqlite.selectSql({
name: name,
sql: `select count(*) as isTable FROM sqlite_master where type='table' and name='${tabName}'`,
success(e) {
resolve(e[0].isTable ? true : false);
},
fail(e) {
console.log(e)
reject(e);
}
})
})
}
// 修改数据
export const updateSQL = (name, tabName, setData, setName, setVal) => {
if (JSON.stringify(setData) !== '{}') {
let dataKeys = Object.keys(setData)
let setStr = ''
dataKeys.forEach((item, index) => {
console.log(setData[item])
setStr += (
`${item} = ${JSON.stringify(setData[item])}${dataKeys.length - 1 !== index ? "," : ""}`)
})
return new Promise((resolve, reject) => {
plus.sqlite.executeSql({
name: name,
sql: `update ${tabName} set ${setStr} where ${setName} = "${setVal}"`,
success(e) {
resolve(e);
},
fail(e) {
console.log(e)
reject(e);
}
})
})
} else {
return new Promise((resolve, reject) => {
reject("错误")
});
}
}
//删除数据库数据
export const deleteInformationType = (name,tabName,setData) => {
if (JSON.stringify(setData) !== '{}') {
let dataKeys = Object.keys(setData)
let setStr = ''
dataKeys.forEach((item, index) => {
console.log(setData[item])
setStr += (
`${item}=${JSON.stringify(setData[item])}${dataKeys.length - 1 !== index ? " and " : ""}`)
})
return new Promise((resolve, reject) => {
plus.sqlite.executeSql({
name: name,
sql: `delete from ${tabName} where ${setStr}`,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
})
} else {
return new Promise((resolve, reject) => {
reject("错误")
});
}
}
//关闭数据库
export const closeSQL = (name) => {
return new Promise((resolve, reject) => {
plus.sqlite.closeDatabase({
name: 'share',
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
})
}
//监听数据库是否开启
export const isOpen = (name) => {
let open = plus.sqlite.isOpenDatabase({
name: name,
path: `_doc/uniapp_save/${name}.sqlite`,
})
return open;
}
// 创建表
export const addTab = (name,tabName) => {
// tabName不能用数字作为表格名的开头
return new Promise((resolve, reject) => {
plus.sqlite.executeSql({
name: name,
// sql: 'create table if not exists dataList("list" INTEGER PRIMARY KEY AUTOINCREMENT,"id" TEXT,"name" TEXT,"gender" TEXT,"avatar" TEXT)',
sql: `create table if not exists ${tabName}("chat_i" INTEGER PRIMARY KEY AUTOINCREMENT,"local_id" TEXT NOT NULL UNIQUE,"id" TEXT,"chat_friend_id" TEXT,"content" INTEGER)`,
success(e) {
resolve(e);
},
fail(e) {
console.log(e)
reject(e);
}
})
})
}
// 添加数据
export const addTabItem = (name,tabName,obj) => {
if (obj) {
let keys = Object.keys(obj)
let keyStr = keys.toString()
let valStr = ''
keys.forEach((item, index) => {
if (keys.length - 1 == index) {
valStr += ('"' + obj[item] + '"')
} else {
valStr += ('"' + obj[item] + '",')
}
})
console.log(valStr)
let sqlStr = `insert into ${tabName}(${keyStr}) values(${valStr})`
console.log(sqlStr)
return new Promise((resolve, reject) => {
plus.sqlite.executeSql({
name: name,
sql: sqlStr,
success(e) {
resolve(e);
},
fail(e) {
console.log(e)
reject(e);
}
})
})
} else {
return new Promise((resolve, reject) => {
reject("错误")
})
}
}
// 合并数据
export const mergeSql = (name,tabName,tabs) => {
if (!tabs || tabs.length == 0) {
return new Promise((resolve, reject) => {
reject("错误")
})
}
let itemValStr = ''
tabs.forEach((item, index) => {
let itemKey = Object.keys(item)
let itemVal = ''
itemKey.forEach((key, i) => {
if (itemKey.length - 1 == i) {
if (typeof item[key] == 'object') {
itemVal += (`'${JSON.stringify(item[key])}'`)
} else {
itemVal += (`'${item[key]}'`)
}
} else {
if (typeof item[key] == 'object') {
itemVal += (`'${JSON.stringify(item[key])}',`)
} else {
itemVal += (`'${item[key]}',`)
}
}
})
if (tabs.length - 1 == index) {
itemValStr += ('(' + itemVal + ')')
} else {
itemValStr += ('(' + itemVal + '),')
}
})
let keys = Object.keys(tabs[0])
let keyStr = keys.toString()
return new Promise((resolve, reject) => {
plus.sqlite.executeSql({
name: name,
sql: `insert or ignore into ${tabName} (${keyStr}) values ${itemValStr}`,
success(e) {
resolve(e);
},
fail(e) {
console.log(e)
reject(e);
}
})
})
}
// 获取分页数据库数据
export const getDataList = async (name, tabName, num, size,byName,byType) => {
let count = 0
let sql = ''
let numindex = 0
await getCount(name, tabName).then((resNum) => {
count = Math.ceil(resNum[0].num / size)
})
if(((num - 1) * size) == 0) {
numindex = 0
} else {
numindex = ((num - 1) * size) + 1
}
sql = `select * from ${tabName}`
if(byName && byType) {
// desc asc
sql += ` order by ${byName} ${byType}`
}
sql += ` limit ${numindex},${size}`
if (count < num - 1) {
return new Promise((resolve, reject) => {
reject("无数据")
});
} else {
return new Promise((resolve, reject) => {
plus.sqlite.selectSql({
name: name,
// sql: "select * from userInfo limit 3 offset 3",
sql:sql ,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
})
}
}
//查询数据库数据
export const selectDataList = (name,tabName,setData,byName,byType) => {
let setStr = ''
let sql = ''
if (JSON.stringify(setData) !== '{}') {
let dataKeys = Object.keys(setData)
dataKeys.forEach((item, index) => {
console.log(setData[item])
setStr += (
`${item}=${JSON.stringify(setData[item])}${dataKeys.length - 1 !== index ? " and " : ""}`)
})
sql = `select * from ${tabName} where ${setStr}`
} else {
sql = `select * from ${tabName}`
}
if(byName && byType) {
// desc asc
sql += ` order by ${byName} ${byType}`
}
console.log(sql)
if (tabName !== undefined) {
return new Promise((resolve, reject) => {
plus.sqlite.selectSql({
name: name,
sql: sql,
success(e) {
resolve(e);
},
fail(e) {
console.log(e)
reject(e);
}
})
})
} else {
return new Promise((resolve, reject) => {
reject("错误")
});
}
}
// #ifdef APP-PLUS
import { judgePermission } from './permission'
// #endif
import Vue from 'vue';
// 身份证格式校验
export const checkIdCard = function(sIdCard) {
//Wi 加权因子 Xi 余数0~10对应的校验码 Pi省份代码
let Wi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2],
Xi = [1, 0, "X", 9, 8, 7, 6, 5, 4, 3, 2],
Pi = [11, 12, 13, 14, 15, 21, 22, 23, 31, 32, 33, 34, 35, 36, 37, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54,
61, 62, 63, 64, 65, 71, 81, 82, 91
],
checkStatus = 0;
// 检查身份证长度
if(sIdCard.length == 18){
checkStatus += 1;
}
//检验输入的省份编码是否有效
if(checkStatus >= 1){
let p2 = sIdCard.substr(0, 2);
for (let i = 0; i < Pi.length; i++) {
if (Pi[i] == p2) {
checkStatus += 1;
}
}
}
//检验18位身份证号码出生日期是否有效
//parseFloat过滤前导零,年份必需大于等于1900且小于等于当前年份,用Date()对象判断日期是否有效。
if(checkStatus >= 2){
let year = parseFloat(sIdCard.substr(6, 4));
let month = parseFloat(sIdCard.substr(10, 2));
let day = parseFloat(sIdCard.substr(12, 2));
let checkDay = new Date(year, month - 1, day);
let nowDay = new Date();
if (1900 <= year && year <= nowDay.getFullYear() && month == (checkDay.getMonth() + 1) && day == checkDay
.getDate()) {
checkStatus += 1;
}
}
//检验校验码是否有效
if(checkStatus >= 3){
let aIdCard = sIdCard.split("");
let sum = 0;
for (let j = 0; j < Wi.length; j++) {
sum += Wi[j] * aIdCard[j]; //线性加权求和
}
let index = sum % 11; //求模,可能为0~10,可求对应的校验码是否于身份证的校验码匹配
if (Xi[index] == aIdCard[17].toUpperCase()) {
checkStatus += 1;
}
}
if (checkStatus == 4) {
return true;
} else {
return false;
}
};
/**
* 时间转换为XX前
*/
export const clickDateDiff = function (value) {
var result;
var minute = 1000 * 60;
var hour = minute * 60;
var day = hour * 24;
var month = day * 30;
var now = new Date().getTime();
var diffValue = parseInt(now) - parseInt(value);
if (diffValue < 0) {
return;
}
var monthC = diffValue / month;
var weekC = diffValue / (7 * day);
var dayC = diffValue / day;
var hourC = diffValue / hour;
var minC = diffValue / minute;
if (monthC >= 1) {
result = "" + parseInt(monthC) + '月前';
} else if (weekC >= 1) {
result = "" + parseInt(weekC) + '周前';
} else if (dayC >= 1) {
result = "" + parseInt(dayC) + '天前';
} else if (hourC >= 1) {
result = "" + parseInt(hourC) + '小时前';
} else if (minC >= 1) {
result = "" + parseInt(minC) + '分钟前';
} else {
result = '刚刚';
}
return result;
};
/**
* 时间戳转换为想要的时间格式
*/
//时间戳转换为时间 format('yyyy-MM-dd hh:mm:ss')
//时间格式转换
Date.prototype.format = function (fmt = 'yyyy-MM-dd hh:mm:ss') { //author: meizz
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[
k]).substr(("" + o[k]).length)));
return fmt;
}
// 保存图片
let settingWritePhotosAlbum = false;
export const saveImg = function(url,callback) {
if (url) {
// #ifdef MP-WEIXIN
if (settingWritePhotosAlbum) {
uni.getSetting({
success: res => {
if (res.authSetting['scope.writePhotosAlbum']) {
uni.showLoading({
title: '正在下载'
});
uni.downloadFile({
url: url,
success: data => {
if (data.statusCode == 200) {
uni.saveImageToPhotosAlbum({
filePath: data.tempFilePath,
success: () => {
uni.hideLoading();
callback && callback();
uni.showToast({
title: '保存成功'
});
},
fail(e) {
uni.hideLoading();
tip({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: "none"
});
}
},
fail(e) {
uni.hideLoading();
uni.showToast({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
} else {
uni.showModal({
title: '提示',
content: '请先在设置页面打开“保存相册”使用权限',
confirmText: '去设置',
cancelText: '算了',
success: data => {
if (data.confirm) {
uni.openSetting();
}
}
});
}
}
});
} else {
settingWritePhotosAlbum = true;
uni.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
uni.showLoading({
title: '正在下载'
});
uni.downloadFile({
url: url,
success: data => {
if (data.statusCode == 200) {
uni.saveImageToPhotosAlbum({
filePath: data.tempFilePath,
success: () => {
uni.hideLoading();
callback && callback();
uni.showToast({
title: '保存成功'
});
},
fail(e) {
uni.hideLoading();
tip({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: "none"
});
}
},
fail(e) {
uni.hideLoading();
uni.showToast({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
}
});
}
// #endif
// #ifdef H5
uni.showLoading({
title: '正在下载'
});
uni.downloadFile({
url: url,
success: data => {
uni.hideLoading();
if (data.statusCode == 200) {
callback && callback();
window.open(data.tempFilePath);
} else {
uni.showToast({
title: '下载失败',
icon: "none"
});
}
},
fail(e) {
uni.hideLoading();
uni.showToast({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
// #endif
// #ifdef APP-PLUS
uni.showLoading({
title: '正在下载'
});
uni.saveImageToPhotosAlbum({
filePath: url,
success: () => {
uni.hideLoading();
callback && callback();
uni.showToast({
title: '保存成功'
});
},
fail(e) {
uni.hideLoading();
uni.showToast({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
// #endif
} else {
uni.showToast({
title: '未找到图片',
icon: 'none'
});
}
};
// 保存视频
function tip(data){
setTimeout(() => {
uni.showToast(data);
},500);
}
export const saveVideo = function(url,callback) {
if (url) {
// #ifdef MP-WEIXIN
if (settingWritePhotosAlbum) {
uni.getSetting({
success: res => {
if (res.authSetting['scope.writePhotosAlbum']) {
// let urlArr = url.split("/");
// let updateUrl = urlArr[urlArr.length - 1];
// let filePath = wx.env.USER_DATA_PATH + '/' + updateUrl;
uni.showLoading({
title: '正在下载'
});
uni.downloadFile({
url: url,
// filePath: filePath,
success: data => {
if (data.statusCode == 200) {
uni.saveVideoToPhotosAlbum({
filePath: data.tempFilePath,
success: () => {
uni.hideLoading();
callback && callback();
tip({
title: '保存成功'
});
},
fail(e) {
uni.hideLoading();
tip({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
} else {
uni.hideLoading();
tip({
title: '下载失败',
icon: "none"
});
}
},
fail(e) {
uni.hideLoading();
tip({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
} else {
uni.showModal({
title: '提示',
content: '请先在设置页面打开“保存相册”使用权限',
confirmText: '去设置',
cancelText: '算了',
success: data => {
if (data.confirm) {
uni.openSetting();
}
}
});
}
}
});
} else {
settingWritePhotosAlbum = true;
uni.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
// let urlArr = url.split("/");
// let updateUrl = urlArr[urlArr.length - 1];
// let filePath = wx.env.USER_DATA_PATH + '/' + updateUrl;
uni.showLoading({
title: '正在下载'
});
uni.downloadFile({
url: url,
// filePath: filePath,
success: data => {
if (data.statusCode == 200) {
uni.saveVideoToPhotosAlbum({
filePath: data.tempFilePath,
success: () => {
uni.hideLoading();
callback && callback();
tip({
title: '保存成功'
});
},
fail(e) {
console.log("-----------------2", e);
uni.hideLoading();
tip({
title: '下载失败,错误原因:'+ e.errMsg,
icon: "none"
});
}
});
} else {
uni.hideLoading();
tip({
title: '下载失败,错误原因:'+ data.errMsg,
icon: "none"
});
}
},
fail(e) {
console.log("-----------------", e);
uni.hideLoading();
tip({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
}
});
}
// #endif
// #ifdef H5
uni.showLoading({
title: '正在下载'
});
uni.downloadFile({
url: url,
success: data => {
uni.hideLoading();
if (data.statusCode == 200) {
callback && callback();
window.open(data.tempFilePath);
} else {
tip({
title: '下载失败',
icon: "none"
});
}
},
fail(e) {
uni.hideLoading();
tip({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
// #endif
// #ifdef APP-PLUS
uni.showLoading({
title: '正在下载'
});
uni.saveVideoToPhotosAlbum({
filePath: url,
success: () => {
uni.hideLoading();
callback && callback();
tip({
title: '保存成功'
});
},
fail(e) {
uni.hideLoading();
tip({
title: '下载失败,错误原因:' + e.errMsg,
icon: "none"
});
}
});
// #endif
} else {
tip({
title: '未找到视频',
icon: 'none'
});
}
};
// 微信小程序获取定位权限判断
function wxAppletsLocation(successCallback, errCallback) {
uni.getSetting({
success: res => {
if (res.authSetting['scope.userLocation']) {
uni.getLocation({
type: 'gcj02',
success: res => {
successCallback(res);
},
fail: (err) => {
console.log("位置信息错误", err);
errCallback("位置信息获取失败");
}
});
} else {
errCallback("“位置信息”未授权");
uni.showModal({
title: '提示',
content: '请先在设置页面打开“位置信息”使用权限',
confirmText: '去设置',
cancelText: '再逛会',
success: res => {
if (res.confirm) {
uni.openSetting();
}
}
});
}
}
});
}
// 获取地址信息
let locationAuthorize = true;
export const getAppWxLatLon = function(successCallback, errCallback) {
const _this = this;
// #ifdef MP
if (locationAuthorize) {
uni.authorize({
scope: 'scope.userLocation',
success: () => {
wxAppletsLocation(successCallback, errCallback);
locationAuthorize = false;
},
fail: () => {
locationAuthorize = false;
}
});
} else {
wxAppletsLocation(successCallback, errCallback);
}
// #endif
// #ifdef APP-PLUS
judgePermission("location", function(result) {
if (result == 1) {
uni.getLocation({
type: 'gcj02',
success: res => {
// store.commit("setCurrentAddress", {
// latitude: res.latitude,
// longitude: res.longitude
// });
successCallback(res);
},
fail: (err) => {
console.log("位置信息错误", err);
errCallback("位置信息获取失败");
}
});
}
});
// #endif
}
//金额过滤
Vue.filter('money', function(val) {
if (val) {
let value = Math.round(parseFloat(val) * 100) / 100;
let valMoney = value.toString().split(".");
if (valMoney.length == 1) {
value = value.toString() + ".00";
return value;
}
if (valMoney.length > 1) {
if (valMoney[1].length < 2) {
value = value.toString() + "0";
}
return value;
}
return value;
} else {
return "0.00";
}
});
//时间格式化
Vue.filter('timeFormat', function(val, fmt = 'yyyy-MM-dd hh:mm:ss') {
if (val) {
return new Date(val).format(fmt);
} else {
return "";
}
});
// #ifdef APP-PLUS
// 文字换行
function drawtext(text, maxWidth) {
let textArr = text.split("");
let len = textArr.length;
// 上个节点
let previousNode = 0;
// 记录节点宽度
let nodeWidth = 0;
// 文本换行数组
let rowText = [];
// 如果是字母,侧保存长度
let letterWidth = 0;
// 汉字宽度
let chineseWidth = 16;
// otherFont宽度
let otherWidth = 8;
for (let i = 0; i < len; i++) {
if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) {
if(letterWidth > 0){
if(nodeWidth + chineseWidth + letterWidth * otherWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i)
});
previousNode = i;
nodeWidth = chineseWidth;
letterWidth = 0;
} else {
nodeWidth += chineseWidth + letterWidth * otherWidth;
letterWidth = 0;
}
} else {
if(nodeWidth + chineseWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i)
});
previousNode = i;
nodeWidth = chineseWidth;
}else{
nodeWidth += chineseWidth;
}
}
} else {
if(/\n/g.test(textArr[i])){
rowText.push({
type: "break",
content: text.substring(previousNode, i)
});
previousNode = i + 1;
nodeWidth = 0;
letterWidth = 0;
}else if(textArr[i] == "\\" && textArr[i + 1] == "n"){
rowText.push({
type: "break",
content: text.substring(previousNode, i)
});
previousNode = i + 2;
nodeWidth = 0;
letterWidth = 0;
}else if(/[a-zA-Z0-9]/g.test(textArr[i])){
letterWidth += 1;
if(nodeWidth + letterWidth * otherWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i + 1 - letterWidth)
});
previousNode = i + 1 - letterWidth;
nodeWidth = letterWidth * otherWidth;
letterWidth = 0;
}
} else{
if(nodeWidth + otherWidth > maxWidth){
rowText.push({
type: "text",
content: text.substring(previousNode, i)
});
previousNode = i;
nodeWidth = otherWidth;
}else{
nodeWidth += otherWidth;
}
}
}
}
if (previousNode < len) {
rowText.push({
type: "text",
content: text.substring(previousNode, len)
});
}
return rowText;
}
// 重写app弹窗
uni.showModal = function(options){
let optionsObj = Object.assign({
title: "提示",
content: "自定义内容",
align: "center", // 对齐方式 left/center/right
cancelText: "取消", // 取消按钮的文字
cancelColor: "#8F8F8F", // 取消按钮颜色
confirmText: "确定", // 确认按钮文字
confirmColor: "#1C79D6", // 确认按钮颜色
showCancel: true, // 是否显示取消按钮,默认为 true
}, options);
// 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心
const screenWidth = plus.screen.resolutionWidth;
const screenHeight = plus.screen.resolutionHeight;
//弹窗容器宽度
const popupViewWidth = screenWidth * 0.8;
// 弹窗容器的Padding
const viewContentPadding = 20;
// 弹窗容器的宽度
const viewContentWidth = parseInt(popupViewWidth - (viewContentPadding * 2));
// 描述的列表
const descriptionList = drawtext(optionsObj.content, viewContentWidth);
// 弹窗高度
let popupViewHeight = 168;
// 弹窗遮罩层
let maskLayer = new plus.nativeObj.View("maskLayer", { //先创建遮罩层
top: '0px',
left: '0px',
height: '100%',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.5)'
});
let popupViewContentList = [{
tag: 'font',
id: 'title',
text: optionsObj.title,
textStyles: {
size: '18px',
color: "#333",
weight: "bold",
whiteSpace: "normal"
},
position: {
top: viewContentPadding + "px",
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: "30px",
}
}];
const textHeight = 22;
let contentTop = 65;
descriptionList.forEach((item,index) => {
if(index > 0){
popupViewHeight += textHeight;
contentTop += textHeight;
}
popupViewContentList.push({
tag: 'font',
id: 'content' + index + 1,
text: item.content,
textStyles: {
size: '16px',
color: "#333",
lineSpacing: "50%",
align: optionsObj.align
},
position: {
top: contentTop + "px",
left: viewContentPadding + "px",
width: viewContentWidth + "px",
height: textHeight + "px",
}
});
if(item.type == "break"){
contentTop += 10;
popupViewHeight += 10;
}
});
popupViewContentList.push({
tag: 'rect',
id: 'lineTop',
rectStyles: {
color: "#f1f1f1",
},
position: {
top: contentTop + 50 + "px",
left: "0px",
width: "100%",
height: "1px",
}
});
if(optionsObj.showCancel){
popupViewContentList.push({
tag: 'rect',
id: 'line',
rectStyles: {
color: "#f1f1f1",
},
position: {
top: contentTop + 50 + "px",
left: popupViewWidth / 2 + "px",
width: "1px",
height: "50px",
}
});
popupViewContentList.push({
tag: 'font',
id: 'cancelText',
text: optionsObj.cancelText,
textStyles: {
size: '16px',
color: optionsObj.cancelColor,
},
position: {
top: contentTop + 50 + "px",
left: "0px",
width: popupViewWidth / 2 + "px",
height: "50px",
}
});
popupViewContentList.push({
tag: 'font',
id: 'confirmText',
text: optionsObj.confirmText,
textStyles: {
size: '16px',
color: optionsObj.confirmColor,
},
position: {
top: contentTop + 50 + "px",
left: popupViewWidth / 2 + "px",
width: popupViewWidth / 2 + "px",
height: "50px",
}
});
} else {
popupViewContentList.push({
tag: 'font',
id: 'confirmText',
text: optionsObj.confirmText,
textStyles: {
size: '16px',
color: optionsObj.confirmColor,
},
position: {
top: contentTop + 50 + "px",
left: "0px",
width: "100%",
height: "50px",
}
});
}
// 弹窗内容
let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单
tag: "rect",
top: (screenHeight - popupViewHeight) / 2 + "px",
left: '10%',
height: popupViewHeight + "px",
width: "80%"
});
// 绘制白色背景
popupView.drawRect({
color: "#FFFFFF",
radius: "8px"
}, {
top: "0px",
height: popupViewHeight + "px",
});
popupView.draw(popupViewContentList);
popupView.addEventListener("click", function(e) {
if(optionsObj.showCancel){
if (e.clientY > popupViewHeight - 50 && e.clientX < popupViewWidth / 2) {
// 取消
maskLayer.close();
popupView.close();
options.success && options.success({confirm: false, cancel: true});
} else if(e.clientY > popupViewHeight - 50 && e.clientX > popupViewWidth / 2){
// 确定
maskLayer.close();
popupView.close();
options.success && options.success({confirm: true, cancel: false});
}
} else {
if (e.clientY > popupViewHeight - 50) {
// 确定
maskLayer.close();
popupView.close();
options.success && options.success({confirm: true, cancel: false});
}
}
});
// 显示弹窗
maskLayer.show();
popupView.show();
options.complete && options.complete();
};
// #endif
\ No newline at end of file
// 获取微信公众号SDK权限
//接口请求方法
import $http from '@/config/requestConfig';
import base from '@/config/baseUrl';
import { publicShareFun } from '@/config/html5Utils';
//获取地理位置
export const getLocation = () => {
return new Promise((resolve, reject) => {
//配置校验成功后执行
jWeixin.ready(function () {
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
jWeixin.getLocation({
type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: function (res) {
console.log(res);
resolve(res);
},
fail: (err) => {
reject(err);
}
});
});
});
}
//设置分享信息
export const setShare = (data, callback) => {
//配置校验成功后执行
jWeixin.ready(function () {
if (!data.link) {
let url = window.location.href;
let index = url.indexOf("?");
if (index != -1) {
if (url.indexOf("#") != -1 && url.indexOf("#") > index) {
url = url.substring(0, index) + url.substring(url.indexOf("#"));
} else {
url = url.substr(0, index);
}
}
data.link = url;
}
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
jWeixin.updateAppMessageShareData({
title: data.title, // 分享标题
desc: data.desc, // 分享描述
link: data.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: data.imgUrl, // 分享图标
success: function () {
// 设置成功
callback && callback();
}
});
jWeixin.updateTimelineShareData({
title: data.title, // 分享标题
link: data.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: data.imgUrl, // 分享图标
success: function () {
// 设置成功
callback && callback();
}
});
});
}
//微信扫一扫
export const scanQRCode = ( callback,needResult = 0) => {
//配置校验成功后执行
jWeixin.ready(function () {
jWeixin.scanQRCode({
needResult: needResult, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
success: function (res) {
callback && callback(res);
}
});
});
}
window.onload = function () {
// 配置文件里面没有publicAppId将不激活微信SDK功能
if (!base.publicAppId) {
return;
}
//获取当前页面地址
let url = window.location.href;
url = url.substring(0, url.indexOf("#"));
//获取微信公众号SDK权限的签名、随机数、时间戳
$http.post("api/open/signature", {
url: url
}).then(res => {
// 微信SDK配置
jWeixin.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: base.publicAppId, // 必填,公众号的唯一标识
timestamp: res.timestamp, // 必填,生成签名的时间戳
nonceStr: res.noncestr, // 必填,生成签名的随机串
signature: res.signature,// 必填,签名
jsApiList: [
"getLocation",
"updateAppMessageShareData",
"updateTimelineShareData",
'onMenuShareAppMessage', //旧的接口,即将废弃
'onMenuShareTimeline' //旧的接口,即将废弃
] // 必填,需要使用的JS接口列表
});
//设置分享内容
publicShareFun();
});
//配置校验失败后执行
jWeixin.error(function (res) {
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
console.log(res);
});
};
\ No newline at end of file
!function(e,n){"function"==typeof define&&(define.amd||define.cmd)?define(function(){return n(e)}):n(e,!0)}(this,function(e,n){function i(n,i,t){e.WeixinJSBridge?WeixinJSBridge.invoke(n,o(i),function(e){c(n,e,t)}):u(n,t)}function t(n,i,t){e.WeixinJSBridge?WeixinJSBridge.on(n,function(e){t&&t.trigger&&t.trigger(e),c(n,e,i)}):t?u(n,t):u(n,i)}function o(e){return e=e||{},e.appId=C.appId,e.verifyAppId=C.appId,e.verifySignType="sha1",e.verifyTimestamp=C.timestamp+"",e.verifyNonceStr=C.nonceStr,e.verifySignature=C.signature,e}function r(e){return{timeStamp:e.timestamp+"",nonceStr:e.nonceStr,package:e.package,paySign:e.paySign,signType:e.signType||"SHA1"}}function a(e){return e.postalCode=e.addressPostalCode,delete e.addressPostalCode,e.provinceName=e.proviceFirstStageName,delete e.proviceFirstStageName,e.cityName=e.addressCitySecondStageName,delete e.addressCitySecondStageName,e.countryName=e.addressCountiesThirdStageName,delete e.addressCountiesThirdStageName,e.detailInfo=e.addressDetailInfo,delete e.addressDetailInfo,e}function c(e,n,i){"openEnterpriseChat"==e&&(n.errCode=n.err_code),delete n.err_code,delete n.err_desc,delete n.err_detail;var t=n.errMsg;t||(t=n.err_msg,delete n.err_msg,t=s(e,t),n.errMsg=t),(i=i||{})._complete&&(i._complete(n),delete i._complete),t=n.errMsg||"",C.debug&&!i.isInnerInvoke&&alert(JSON.stringify(n));var o=t.indexOf(":");switch(t.substring(o+1)){case"ok":i.success&&i.success(n);break;case"cancel":i.cancel&&i.cancel(n);break;default:i.fail&&i.fail(n)}i.complete&&i.complete(n)}function s(e,n){var i=e,t=v[i];t&&(i=t);var o="ok";if(n){var r=n.indexOf(":");"confirm"==(o=n.substring(r+1))&&(o="ok"),"failed"==o&&(o="fail"),-1!=o.indexOf("failed_")&&(o=o.substring(7)),-1!=o.indexOf("fail_")&&(o=o.substring(5)),"access denied"!=(o=(o=o.replace(/_/g," ")).toLowerCase())&&"no permission to execute"!=o||(o="permission denied"),"config"==i&&"function not exist"==o&&(o="ok"),""==o&&(o="fail")}return n=i+":"+o}function d(e){if(e){for(var n=0,i=e.length;n<i;++n){var t=e[n],o=h[t];o&&(e[n]=o)}return e}}function u(e,n){if(!(!C.debug||n&&n.isInnerInvoke)){var i=v[e];i&&(e=i),n&&n._complete&&delete n._complete,console.log('"'+e+'",',n||"")}}function l(e){if(!(k||w||C.debug||x<"6.0.2"||V.systemType<0)){var n=new Image;V.appId=C.appId,V.initTime=A.initEndTime-A.initStartTime,V.preVerifyTime=A.preVerifyEndTime-A.preVerifyStartTime,N.getNetworkType({isInnerInvoke:!0,success:function(e){V.networkType=e.networkType;var i="https://open.weixin.qq.com/sdk/report?v="+V.version+"&o="+V.isPreVerifyOk+"&s="+V.systemType+"&c="+V.clientVersion+"&a="+V.appId+"&n="+V.networkType+"&i="+V.initTime+"&p="+V.preVerifyTime+"&u="+V.url;n.src=i}})}}function p(){return(new Date).getTime()}function f(n){T&&(e.WeixinJSBridge?n():S.addEventListener&&S.addEventListener("WeixinJSBridgeReady",n,!1))}function m(){N.invoke||(N.invoke=function(n,i,t){e.WeixinJSBridge&&WeixinJSBridge.invoke(n,o(i),t)},N.on=function(n,i){e.WeixinJSBridge&&WeixinJSBridge.on(n,i)})}function g(e){if("string"==typeof e&&e.length>0){var n=e.split("?")[0],i=e.split("?")[1];return n+=".html",void 0!==i?n+"?"+i:n}}if(!e.jWeixin){var h={config:"preVerifyJSAPI",onMenuShareTimeline:"menu:share:timeline",onMenuShareAppMessage:"menu:share:appmessage",onMenuShareQQ:"menu:share:qq",onMenuShareWeibo:"menu:share:weiboApp",onMenuShareQZone:"menu:share:QZone",previewImage:"imagePreview",getLocation:"geoLocation",openProductSpecificView:"openProductViewWithPid",addCard:"batchAddCard",openCard:"batchViewCard",chooseWXPay:"getBrandWCPayRequest",openEnterpriseRedPacket:"getRecevieBizHongBaoRequest",startSearchBeacons:"startMonitoringBeacons",stopSearchBeacons:"stopMonitoringBeacons",onSearchBeacons:"onBeaconsInRange",consumeAndShareCard:"consumedShareCard",openAddress:"editAddress"},v=function(){var e={};for(var n in h)e[h[n]]=n;return e}(),S=e.document,I=S.title,y=navigator.userAgent.toLowerCase(),_=navigator.platform.toLowerCase(),k=!(!_.match("mac")&&!_.match("win")),w=-1!=y.indexOf("wxdebugger"),T=-1!=y.indexOf("micromessenger"),M=-1!=y.indexOf("android"),P=-1!=y.indexOf("iphone")||-1!=y.indexOf("ipad"),x=function(){var e=y.match(/micromessenger\/(\d+\.\d+\.\d+)/)||y.match(/micromessenger\/(\d+\.\d+)/);return e?e[1]:""}(),A={initStartTime:p(),initEndTime:0,preVerifyStartTime:0,preVerifyEndTime:0},V={version:1,appId:"",initTime:0,preVerifyTime:0,networkType:"",isPreVerifyOk:1,systemType:P?1:M?2:-1,clientVersion:x,url:encodeURIComponent(location.href)},C={},L={_completes:[]},B={state:0,data:{}};f(function(){A.initEndTime=p()});var O=!1,E=[],N={config:function(e){C=e,u("config",e);var n=!1!==C.check;f(function(){if(n)i(h.config,{verifyJsApiList:d(C.jsApiList)},function(){L._complete=function(e){A.preVerifyEndTime=p(),B.state=1,B.data=e},L.success=function(e){V.isPreVerifyOk=0},L.fail=function(e){L._fail?L._fail(e):B.state=-1};var e=L._completes;return e.push(function(){l()}),L.complete=function(n){for(var i=0,t=e.length;i<t;++i)e[i]();L._completes=[]},L}()),A.preVerifyStartTime=p();else{B.state=1;for(var e=L._completes,t=0,o=e.length;t<o;++t)e[t]();L._completes=[]}}),m()},ready:function(e){0!=B.state?e():(L._completes.push(e),!T&&C.debug&&e())},error:function(e){x<"6.0.2"||(-1==B.state?e(B.data):L._fail=e)},checkJsApi:function(e){var n=function(e){var n=e.checkResult;for(var i in n){var t=v[i];t&&(n[t]=n[i],delete n[i])}return e};i("checkJsApi",{jsApiList:d(e.jsApiList)},(e._complete=function(e){if(M){var i=e.checkResult;i&&(e.checkResult=JSON.parse(i))}e=n(e)},e))},onMenuShareTimeline:function(e){t(h.onMenuShareTimeline,{complete:function(){i("shareTimeline",{title:e.title||I,desc:e.title||I,img_url:e.imgUrl||"",link:e.link||location.href,type:e.type||"link",data_url:e.dataUrl||""},e)}},e)},onMenuShareAppMessage:function(e){t(h.onMenuShareAppMessage,{complete:function(n){"favorite"===n.scene?i("sendAppMessage",{title:e.title||I,desc:e.desc||"",link:e.link||location.href,img_url:e.imgUrl||"",type:e.type||"link",data_url:e.dataUrl||""}):i("sendAppMessage",{title:e.title||I,desc:e.desc||"",link:e.link||location.href,img_url:e.imgUrl||"",type:e.type||"link",data_url:e.dataUrl||""},e)}},e)},onMenuShareQQ:function(e){t(h.onMenuShareQQ,{complete:function(){i("shareQQ",{title:e.title||I,desc:e.desc||"",img_url:e.imgUrl||"",link:e.link||location.href},e)}},e)},onMenuShareWeibo:function(e){t(h.onMenuShareWeibo,{complete:function(){i("shareWeiboApp",{title:e.title||I,desc:e.desc||"",img_url:e.imgUrl||"",link:e.link||location.href},e)}},e)},onMenuShareQZone:function(e){t(h.onMenuShareQZone,{complete:function(){i("shareQZone",{title:e.title||I,desc:e.desc||"",img_url:e.imgUrl||"",link:e.link||location.href},e)}},e)},updateTimelineShareData:function(e){i("updateTimelineShareData",{title:e.title,link:e.link,imgUrl:e.imgUrl},e)},updateAppMessageShareData:function(e){i("updateAppMessageShareData",{title:e.title,desc:e.desc,link:e.link,imgUrl:e.imgUrl},e)},startRecord:function(e){i("startRecord",{},e)},stopRecord:function(e){i("stopRecord",{},e)},onVoiceRecordEnd:function(e){t("onVoiceRecordEnd",e)},playVoice:function(e){i("playVoice",{localId:e.localId},e)},pauseVoice:function(e){i("pauseVoice",{localId:e.localId},e)},stopVoice:function(e){i("stopVoice",{localId:e.localId},e)},onVoicePlayEnd:function(e){t("onVoicePlayEnd",e)},uploadVoice:function(e){i("uploadVoice",{localId:e.localId,isShowProgressTips:0==e.isShowProgressTips?0:1},e)},downloadVoice:function(e){i("downloadVoice",{serverId:e.serverId,isShowProgressTips:0==e.isShowProgressTips?0:1},e)},translateVoice:function(e){i("translateVoice",{localId:e.localId,isShowProgressTips:0==e.isShowProgressTips?0:1},e)},chooseImage:function(e){i("chooseImage",{scene:"1|2",count:e.count||9,sizeType:e.sizeType||["original","compressed"],sourceType:e.sourceType||["album","camera"]},(e._complete=function(e){if(M){var n=e.localIds;try{n&&(e.localIds=JSON.parse(n))}catch(e){}}},e))},getLocation:function(e){},previewImage:function(e){i(h.previewImage,{current:e.current,urls:e.urls},e)},uploadImage:function(e){i("uploadImage",{localId:e.localId,isShowProgressTips:0==e.isShowProgressTips?0:1},e)},downloadImage:function(e){i("downloadImage",{serverId:e.serverId,isShowProgressTips:0==e.isShowProgressTips?0:1},e)},getLocalImgData:function(e){!1===O?(O=!0,i("getLocalImgData",{localId:e.localId},(e._complete=function(e){if(O=!1,E.length>0){var n=E.shift();wx.getLocalImgData(n)}},e))):E.push(e)},getNetworkType:function(e){var n=function(e){var n=e.errMsg;e.errMsg="getNetworkType:ok";var i=e.subtype;if(delete e.subtype,i)e.networkType=i;else{var t=n.indexOf(":"),o=n.substring(t+1);switch(o){case"wifi":case"edge":case"wwan":e.networkType=o;break;default:e.errMsg="getNetworkType:fail"}}return e};i("getNetworkType",{},(e._complete=function(e){e=n(e)},e))},openLocation:function(e){i("openLocation",{latitude:e.latitude,longitude:e.longitude,name:e.name||"",address:e.address||"",scale:e.scale||28,infoUrl:e.infoUrl||""},e)},getLocation:function(e){e=e||{},i(h.getLocation,{type:e.type||"wgs84"},(e._complete=function(e){delete e.type},e))},hideOptionMenu:function(e){i("hideOptionMenu",{},e)},showOptionMenu:function(e){i("showOptionMenu",{},e)},closeWindow:function(e){i("closeWindow",{},e=e||{})},hideMenuItems:function(e){i("hideMenuItems",{menuList:e.menuList},e)},showMenuItems:function(e){i("showMenuItems",{menuList:e.menuList},e)},hideAllNonBaseMenuItem:function(e){i("hideAllNonBaseMenuItem",{},e)},showAllNonBaseMenuItem:function(e){i("showAllNonBaseMenuItem",{},e)},scanQRCode:function(e){i("scanQRCode",{needResult:(e=e||{}).needResult||0,scanType:e.scanType||["qrCode","barCode"]},(e._complete=function(e){if(P){var n=e.resultStr;if(n){var i=JSON.parse(n);e.resultStr=i&&i.scan_code&&i.scan_code.scan_result}}},e))},openAddress:function(e){i(h.openAddress,{},(e._complete=function(e){e=a(e)},e))},openProductSpecificView:function(e){i(h.openProductSpecificView,{pid:e.productId,view_type:e.viewType||0,ext_info:e.extInfo},e)},addCard:function(e){for(var n=e.cardList,t=[],o=0,r=n.length;o<r;++o){var a=n[o],c={card_id:a.cardId,card_ext:a.cardExt};t.push(c)}i(h.addCard,{card_list:t},(e._complete=function(e){var n=e.card_list;if(n){for(var i=0,t=(n=JSON.parse(n)).length;i<t;++i){var o=n[i];o.cardId=o.card_id,o.cardExt=o.card_ext,o.isSuccess=!!o.is_succ,delete o.card_id,delete o.card_ext,delete o.is_succ}e.cardList=n,delete e.card_list}},e))},chooseCard:function(e){i("chooseCard",{app_id:C.appId,location_id:e.shopId||"",sign_type:e.signType||"SHA1",card_id:e.cardId||"",card_type:e.cardType||"",card_sign:e.cardSign,time_stamp:e.timestamp+"",nonce_str:e.nonceStr},(e._complete=function(e){e.cardList=e.choose_card_info,delete e.choose_card_info},e))},openCard:function(e){for(var n=e.cardList,t=[],o=0,r=n.length;o<r;++o){var a=n[o],c={card_id:a.cardId,code:a.code};t.push(c)}i(h.openCard,{card_list:t},e)},consumeAndShareCard:function(e){i(h.consumeAndShareCard,{consumedCardId:e.cardId,consumedCode:e.code},e)},chooseWXPay:function(e){i(h.chooseWXPay,r(e),e)},openEnterpriseRedPacket:function(e){i(h.openEnterpriseRedPacket,r(e),e)},startSearchBeacons:function(e){i(h.startSearchBeacons,{ticket:e.ticket},e)},stopSearchBeacons:function(e){i(h.stopSearchBeacons,{},e)},onSearchBeacons:function(e){t(h.onSearchBeacons,e)},openEnterpriseChat:function(e){i("openEnterpriseChat",{useridlist:e.userIds,chatname:e.groupName},e)},launchMiniProgram:function(e){i("launchMiniProgram",{targetAppId:e.targetAppId,path:g(e.path),envVersion:e.envVersion},e)},miniProgram:{navigateBack:function(e){e=e||{},f(function(){i("invokeMiniProgramAPI",{name:"navigateBack",arg:{delta:e.delta||1}},e)})},navigateTo:function(e){f(function(){i("invokeMiniProgramAPI",{name:"navigateTo",arg:{url:e.url}},e)})},redirectTo:function(e){f(function(){i("invokeMiniProgramAPI",{name:"redirectTo",arg:{url:e.url}},e)})},switchTab:function(e){f(function(){i("invokeMiniProgramAPI",{name:"switchTab",arg:{url:e.url}},e)})},reLaunch:function(e){f(function(){i("invokeMiniProgramAPI",{name:"reLaunch",arg:{url:e.url}},e)})},postMessage:function(e){f(function(){i("invokeMiniProgramAPI",{name:"postMessage",arg:e.data||{}},e)})},getEnv:function(n){f(function(){n({miniprogram:"miniprogram"===e.__wxjs_environment})})}}},b=1,R={};return S.addEventListener("error",function(e){if(!M){var n=e.target,i=n.tagName,t=n.src;if(("IMG"==i||"VIDEO"==i||"AUDIO"==i||"SOURCE"==i)&&-1!=t.indexOf("wxlocalresource://")){e.preventDefault(),e.stopPropagation();var o=n["wx-id"];if(o||(o=b++,n["wx-id"]=o),R[o])return;R[o]=!0,wx.ready(function(){wx.getLocalImgData({localId:t,success:function(e){n.src=e.localData}})})}}},!0),S.addEventListener("load",function(e){if(!M){var n=e.target,i=n.tagName;n.src;if("IMG"==i||"VIDEO"==i||"AUDIO"==i||"SOURCE"==i){var t=n["wx-id"];t&&(R[t]=!1)}}},!0),n&&(e.wx=e.jWeixin=N),N}});
\ No newline at end of file
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const files = require.context("./modules", false, /\.js$/);
let modules = {
state: {},
mutations: {},
actions: {}
};
files.keys().forEach((key) => {
Object.assign(modules.state, files(key)["state"]);
Object.assign(modules.mutations, files(key)["mutations"]);
Object.assign(modules.actions, files(key)["actions"]);
});
const store = new Vuex.Store(modules);
export default store;
export const state = {
//webView地址
webViewUrl: "",
loadingShow: false,
//微信场景参数
chatScenesInfo: {},
//登录弹窗状态
loginPopupShow: false,
//当前位置
currentAddress: {
areaName: "请选择",
areaId: ''
},
};
//缓存浏览器的数据名称
const cacheNameList = ["userInfo", "webViewUrl"];
let clearTime;
export const mutations = {
//取出缓存数据(打开APP就取出)
setCacheData(state) {
for (let name of cacheNameList) {
let data;
// #ifndef H5
data = uni.getStorageSync(name);
// #endif
// #ifdef H5
data = sessionStorage.getItem(name) || localStorage.getItem(name);
// #endif
if (data) {
// #ifdef H5
try {
data = JSON.parse(data);
} catch (e) {
}
// #endif
state[name] = data;
}
}
},
//WebView地址
setWebViewUrl(state, data) {
if (data) {
state.webViewUrl = data;
// #ifdef H5
window.sessionStorage.setItem('webViewUrl', data);
// #endif
}
},
//数据加载状态
setLoadingShow(state, data) {
state.loadingShow = data;
},
//微信场景参数
setChatScenesInfo(state, data) {
if (data) {
state.chatScenesInfo = Object.assign({}, state.chatScenesInfo, data);
}
},
//登录弹窗状态
setLoginPopupShow(state, data) {
state.loginPopupShow = data;
},
//当前地址
setCurrentAddress(state, data) {
if (data) {
state.currentAddress = Object.assign(state.currentAddress, data);
let addressInfo = {
"provinceId": state.currentAddress.provinceId,
"provinceName": state.currentAddress.provinceName,
"cityId": state.currentAddress.cityId,
"cityName": state.currentAddress.cityName,
"areaId": state.currentAddress.areaId,
"areaName": state.currentAddress.areaName,
};
uni.setStorageSync('currentAddress', addressInfo);
}
}
};
export const actions = {
};
export const state = {
};
export const mutations = {
};
export const actions = {
};
export const state = {
//用户数据
userInfo: {},
};
export const mutations = {
//储存用户信息
setUserInfo(state, data) {
if (data) {
state.userInfo = Object.assign({}, state.userInfo,data);
// #ifdef H5
window.sessionStorage.setItem('userInfo', JSON.stringify(state.userInfo));
// #endif
// #ifndef H5
uni.setStorageSync('userInfo', state.userInfo);
// #endif
}
},
// 退出APP
emptyUserInfo(state) {
state.userInfo = {};
// #ifdef H5
window.sessionStorage.removeItem("userInfo");
// #endif
// #ifndef H5
uni.removeStorageSync("userInfo");
// #endif
},
};
export const actions = {
};
@import '@/style/mixin.scss';
view,
page,
text,
button,
image,
textarea,
scroll-view,input {
box-sizing: border-box;
}
image {
display: block;
}
button {
margin: 0;
padding: 0;
background-color: #FFFF;
}
button::after {
border: none;
}
// 主题背景色
.themeBgColor {
background-color: $themeColor;
}
// 主题字体色
.themeFontColor {
color: $themeColor !important;
}
\ No newline at end of file
.input_form_box {
.input_title {
font-size: 32upx;
color: #333333;
padding: 30upx;
&.required::before {
content: '*';
font-size: 28upx;
color: #f73333;
}
}
.input_box {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 0 30upx;
background-color: #fff;
&.line {
border-top: 1upx solid #f7f7f7;
}
&.btm_line {
border-bottom: 1upx solid #e5e5e5;
}
.name {
font-size: 28upx;
color: #333333;
min-width: 120upx;
flex-shrink: 0;
padding: 30upx 0;
&.required::before {
content: '*';
font-size: 28upx;
color: #f73333;
}
}
.prompt {
width: 100%;
padding-bottom: 20upx;
font-size: 28upx;
color: #999999;
transform: translateY(-10upx);
}
picker {
flex: 1;
}
}
.input_info {
padding: 30upx 0;
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
> text {
font-size: 24upx;
color: #555555;
margin-left: 10upx;
flex-shrink: 0;
}
> view {
font-size: 28upx;
color: #333;
margin-left: 10upx;
flex-shrink: 0;
}
input {
width: 100%;
text-align: right;
font-size: 28upx;
}
button {
flex-shrink: 0;
min-width: 146upx;
padding: 0 30upx;
height: 60upx;
line-height: 60upx;
background-color: $themeColor;
border-radius: 8upx;
font-size: 24upx;
color: #ffffff;
margin-left: 20upx;
}
}
.switch {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
&::after {
content: "";
@include bis('https://qn.kemean.cn/upload/201908/05/5f85ccc4de404cafb30b15dedef41e8b', 100% 100%);
width: 108upx;
height: 58upx;
}
&.active::after {
background-image: url('https://qn.kemean.cn/upload/201908/05/eef318fa5c9f4692a1bd6ef6edd9be10');
}
}
.radio_box {
padding: 30upx 0;
flex: 1;
display: flex;
justify-content: flex-end;
view {
display: flex;
align-items: center;
font-size: 28upx;
color: #333333;
margin-left: 90upx;
&:first-child {
margin-left: 0;
}
&::before {
content: '';
width: 38upx;
height: 38upx;
margin-right: 15upx;
border: 2upx solid #eee;
border-radius: 50%;
box-sizing: border-box;
}
&.active::before {
border: 12upx solid $themeColor;
}
}
}
.select_info {
padding: 30upx 0;
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
.value {
font-size: 28upx;
color: #333;
text-align: right;
}
.select {
font-size: 28upx;
color: #ccc;
}
.head_img {
width: 100upx;
height: 100upx;
border-radius: 10upx;
}
&::after {
content: '';
@include bis('http://qn.kemean.cn/upload/202009/17/1600306951172qyyjj3hh.png', 100% 100%);
width: 12upx;
height: 20upx;
margin-left: 24upx;
flex-shrink: 0;
}
}
.time_limit_box {
width: 100%;
display: flex;
align-items: center;
margin-top: -30rpx;
.value {
flex: 1;
font-size: 28upx;
color: #333;
text-align: right;
padding: 30rpx 0;
}
.to {
font-size: 28rpx;
color: #333333;
padding: 0 30rpx;
}
.select {
flex: 1;
font-size: 28upx;
color: #ccc;
padding: 30rpx 0;
}
}
.upload_info {
width: 100%;
display: flex;
flex-wrap: wrap;
padding-bottom: 10upx;
.upload_img {
width: 200upx;
height: 200upx;
background-color: #ffffff;
border-radius: 4upx;
margin-right: 16upx;
margin-bottom: 16upx;
overflow: hidden;
position: relative;
&:nth-child(3n) {
margin-right: 0;
}
image {
width: 100%;
height: 100%;
}
.delete {
position: absolute;
top: 0upx;
right: 0upx;
width: 44upx;
height: 44upx;
background-image: url('https://qn.kemean.cn/upload/201908/05/df40b98b77fc4c42a5e0327c62975e29');
background-position: center center;
background-repeat: no-repeat;
background-size: 100% 100%;
}
&.upload {
border: none;
background-color: #f7f7f7;
background-image: url(./static/icon/ic_upload.png);
background-position: center center;
background-repeat: no-repeat;
background-size: 60% 60%;
}
}
}
.textarea_info {
width: 100%;
padding-bottom: 30upx;
font-size: 28upx;
color: #333;
textarea {
width: 100%;
height: 154upx;
font-size: 28upx;
line-height: 150%;
}
}
}
.placeholder {
color: #ccc;
}
.protocol {
margin-top: 20upx;
display: flex;
justify-content: center;
align-items: center;
font-size: 28upx;
color: #666;
text {
color: $themeColor;
}
view {
@include theme('unselected_img', './');
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
width: 34upx;
height: 34upx;
margin-right: 10upx;
&.active {
@include theme('radio_img', './');
}
}
}
.form_but {
margin-top: 80upx;
padding: 30upx;
display: flex;
justify-content: center;
button {
flex: 1;
height: 88upx;
background-color: #f0f0f0;
border-radius: 8upx;
line-height: 88upx;
font-size: 34upx;
color: #cccccc;
&.active {
@include theme("btn_bg");
color: #ffffff;
}
&::after {
border: none;
}
}
}
// -----------------------导航条-------------------------------
.cell_list {
background-color: #fff;
padding: 30upx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1upx solid #f5f5f5;
&:active {
background-color: #f5f5f5;
}
&.interval {
margin-bottom: 20upx;
border-bottom: 0;
}
.cell_left {
font-size: 28upx;
color: #333333;
display: flex;
align-items: center;
image {
width: 30upx;
height: 30upx;
margin-right: 20upx;
}
}
.cell_right {
font-size: 28upx;
color: #333333;
display: flex;
align-items: center;
&.arrow::after {
content: '';
@include bis('./static/icon/me_lise_more.png', 100% 100%);
width: 12upx;
height: 22upx;
margin-left: 20upx;
}
image {
width: 36upx;
height: 36upx;
margin-left: 20upx;
}
}
}
\ No newline at end of file
@charset "utf-8";
//主题色
$themeColor: #ea552d;
@mixin theme($type,$path:''){
@if $type == "btn_bg" {
background-image: linear-gradient(90deg, #ea552d 0%, #f19837 100%);
} @else if $type == "unselected_img" {
background-image: url($path + "static/icon/ic_gender_unselected.png");
} @else if $type == "check_img" {
background-image: url($path + "static/icon/ic_agreed.png");
} @else if $type == "radio_img" {
background-image: url($path + "static/icon/ic_gender_selected.png");
}
}
// 背景图片地址和大小
@mixin bis($url, $size: cover) {
background-image: url($url);
background-repeat: no-repeat;
background-position: center center;
background-size: $size;
}
// 头像
@mixin ic($width, $height) {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
flex-shrink: 0;
width: $width;
height: $height;
}
// 单行省略号
@mixin toe() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
}
// 多行省略号
@mixin bov($num:2) {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $num;
-webkit-box-orient: vertical;
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<script>
var UA = window.navigator.userAgent.toLowerCase();
var isAndroid = UA.indexOf('android') > 0;
var isIOS = /iphone|ipad|ipod|ios/.test(UA);
if (!(isAndroid || isIOS)) {
// 正式发布的时候使用,开发期间不启用。
// window.location.href = '/demo/picture/website/pcguide.html';
}
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
})
</script>
<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
</head>
<body>
<!-- 该文件为 H5 平台的模板 HTML,并非应用入口,请勿直接运行此文件。 -->
<!-- 详见文档:https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
<noscript>
<strong>Please enable JavaScript to continue.</strong>
</noscript>
<div id="app"></div>
<!-- 微信SDK文件 -->
<script src="<%= BASE_URL %>static/mp-h5/jweixin-1.4.0.js"></script>
<!-- 高德地图文件 -->
<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=859eeef6f37229fba3afb895542a1e04&plugin=AMap.ToolBar"></script>
<script src="//webapi.amap.com/ui/1.0/main.js?v=1.0.11"></script>
<!-- built files will be auto injected -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?fe3b7a223fc08c795f0f4b6350703e6f";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html>
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:24upx;
$uni-font-size-base:28upx;
$uni-font-size-lg:32upx;
/* 图片尺寸 */
$uni-img-size-sm:40upx;
$uni-img-size-base:52upx;
$uni-img-size-lg:80upx;
/* Border Radius */
$uni-border-radius-sm: 4upx;
$uni-border-radius-base: 6upx;
$uni-border-radius-lg: 12upx;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 10px;
$uni-spacing-row-base: 20upx;
$uni-spacing-row-lg: 30upx;
/* 垂直间距 */
$uni-spacing-col-sm: 8upx;
$uni-spacing-col-base: 16upx;
$uni-spacing-col-lg: 24upx;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:40upx;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:36upx;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:30upx;
\ No newline at end of file
1. 包名:cn.com.watone.zzb.share
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment