feat: 前端全量更新 - 含所有本次需求
- 移除秒合约页面及UI - 合约页Tab改为Position Held/Transaction Record - 存款/提款/转账页增加BTC/ETH - 资产页重构(用户信息+盈亏+多语言) - 注册页改版(匹配新设计稿) - 日文全面翻译+首页菜单多语言 - 代理专属注册链接支持(?code=) - 10个locale文件同步更新
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
## 3.0.1(2021-05-13)
|
||||
1. 跳转颜色和图标配置
|
||||
2. 新增静默更新
|
||||
## 3.0.0(2021-05-12)
|
||||
1. 支持uni_modules
|
||||
@@ -0,0 +1,849 @@
|
||||
// #ifdef APP-PLUS
|
||||
import componentConfig from "../updataConfig.js"
|
||||
const platform = uni.getSystemInfoSync().platform;
|
||||
// 主颜色
|
||||
const $mainColor = componentConfig.appUpdateColor ? componentConfig.appUpdateColor : "#FF5B78";
|
||||
// 弹窗图标url
|
||||
const $iconUrl = componentConfig.appUpdateIcon ? componentConfig.appUpdateIcon : "/static/ic_ar.png";
|
||||
|
||||
// 获取当前应用的版本号
|
||||
export const getCurrentNo = function(callback) {
|
||||
// 获取本地应用资源版本号
|
||||
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
|
||||
callback && callback({
|
||||
versionCode: inf.versionCode,
|
||||
versionName: inf.version
|
||||
});
|
||||
});
|
||||
}
|
||||
// 从服务器下载应用资源包(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) {
|
||||
// console.log('data对象',data);
|
||||
if (status == 200) {
|
||||
popupObj.change({
|
||||
progressValue: 100,
|
||||
progressTip:"Installing files...",
|
||||
progress: true,
|
||||
buttonNum: 0
|
||||
});
|
||||
plus.runtime.install(download.filename, {}, function() {
|
||||
popupObj.change({
|
||||
contentText: "App update complete!",
|
||||
buttonNum: 1,
|
||||
progress: false
|
||||
});
|
||||
}, function(e) {
|
||||
popupObj.cancel();
|
||||
plus.nativeUI.alert("Failed install file[" + e.code + "]:" + e.message);
|
||||
});
|
||||
} else {
|
||||
popupObj.change({
|
||||
contentText: "File download failed...",
|
||||
buttonNum: 1,
|
||||
progress: false
|
||||
});
|
||||
}
|
||||
});
|
||||
// console.log('对象dtask',dtask);
|
||||
dtask.start();
|
||||
dtask.addEventListener("statechanged", function(task, status) {
|
||||
// console.log(task)
|
||||
switch (task.state) {
|
||||
case 1: // 开始
|
||||
popupObj.change({
|
||||
progressValue:0,
|
||||
progressTip:"Ready download...",
|
||||
progress: true
|
||||
});
|
||||
break;
|
||||
case 2: // 已连接到服务器
|
||||
popupObj.change({
|
||||
progressValue:0,
|
||||
progressTip:"Start download...",
|
||||
progress: true
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
const progress = parseInt(task.downloadedSize / data.totalSize * 100);
|
||||
// console.log(task);
|
||||
// console.log('data对象',data);
|
||||
if(progress - lastProgressValue >= 2){
|
||||
lastProgressValue = progress;
|
||||
popupObj.change({
|
||||
progressValue:progress,
|
||||
progressTip: "Download" + progress + "%",
|
||||
progress: true
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
// 取消下载
|
||||
popupObj.cancelDownload = function(){
|
||||
dtask && dtask.abort();
|
||||
uni.showToast({
|
||||
title: "Download canceled",
|
||||
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() {
|
||||
console.log("App update complete");
|
||||
}, function(e) {
|
||||
plus.nativeUI.alert("Failed to install file[" + e.code + "]:" + e.message);
|
||||
});
|
||||
} else {
|
||||
plus.nativeUI.alert("File download failed...");
|
||||
}
|
||||
});
|
||||
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: "New Version" + 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: "Upgrade",
|
||||
textStyles: {
|
||||
size: '14px',
|
||||
color: "#FFF",
|
||||
lineSpacing: "0%",
|
||||
},
|
||||
position: {
|
||||
bottom: viewContentPadding + 'px',
|
||||
left: viewContentPadding + "px",
|
||||
width: viewContentWidth + "px",
|
||||
height: "30px"
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 绘制底边按钮
|
||||
popupViewContentList.push({
|
||||
tag: 'rect',
|
||||
id: 'cancelBox',
|
||||
rectStyles: {
|
||||
radius: "3px",
|
||||
borderColor: "#f1f1f1",
|
||||
borderWidth: "1px",
|
||||
},
|
||||
position: {
|
||||
bottom: viewContentPadding + 'px',
|
||||
left: viewContentPadding + "px",
|
||||
width: (viewContentWidth - viewContentPadding) / 2 + "px",
|
||||
height: "30px",
|
||||
}
|
||||
});
|
||||
popupViewContentList.push({
|
||||
tag: 'rect',
|
||||
id: 'confirmBox',
|
||||
rectStyles: {
|
||||
radius: "3px",
|
||||
color: $mainColor,
|
||||
},
|
||||
position: {
|
||||
bottom: viewContentPadding + 'px',
|
||||
left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px",
|
||||
width: (viewContentWidth - viewContentPadding) / 2 + "px",
|
||||
height: "30px",
|
||||
}
|
||||
});
|
||||
popupViewContentList.push({
|
||||
tag: 'font',
|
||||
id: 'cancelText',
|
||||
text: "Not Upgrade",
|
||||
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: "Upgrade",
|
||||
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 || "Ready download...";
|
||||
let contentText = data.contentText || "Updating for you, please wait";
|
||||
let elementList = [
|
||||
{
|
||||
tag: 'rect', //背景色
|
||||
color: '#FFFFFF',
|
||||
rectStyles:{
|
||||
radius: "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'font',
|
||||
id: 'title',
|
||||
text: "Upgrade 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: "Cancel",
|
||||
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: "Back",
|
||||
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: "Close",
|
||||
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 => {
|
||||
componentConfig.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);
|
||||
// 在App Store Connect中的App Store下的app信息,可找到appleId
|
||||
// let appleId = '1600874657'
|
||||
// plus.runtime.launchApplication({
|
||||
// action: `itms-apps://itunes.apple.com/cn/app/id${appleId}?mt=8`
|
||||
// }, function(e) {
|
||||
// console.log('Open system default browser failed: ' + e.message);
|
||||
// });
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
// 在App Store Connect中的App Store下的app信息,可找到appleId
|
||||
// let appleId = '1600874657'
|
||||
// plus.runtime.launchApplication({
|
||||
// action: `itms-apps://itunes.apple.com/cn/app/id${appleId}?mt=8`
|
||||
// }, function(e) {
|
||||
// console.log('Open system default browser failed: ' + e.message);
|
||||
// });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"id": "zhouWei-APPUpdate",
|
||||
"displayName": "APP版本更新、强制更新、静默更新、下载进度(wgt更新)",
|
||||
"version": "3.0.1",
|
||||
"description": "APP版本更新、强制更新、静默更新、漂亮弹窗、下载进度(wgt更新)",
|
||||
"keywords": [
|
||||
"APP版本更新",
|
||||
"强制更新",
|
||||
"版本更新",
|
||||
"静默更新"
|
||||
],
|
||||
"repository": "https://github.com/zhouwei1994/uni-app-demo",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"JS SDK",
|
||||
"通用 SDK"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": "465081029"
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "<uses-permission android:name=\\\"android.permission.INSTALL_PACKAGES\\\"/> \n<uses-permission android:name=\\\"android.permission.REQUEST_INSTALL_PACKAGES\\\"/>"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "n",
|
||||
"Android Browser": "n",
|
||||
"微信浏览器(Android)": "n",
|
||||
"QQ浏览器(Android)": "n"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "n",
|
||||
"IE": "n",
|
||||
"Edge": "n",
|
||||
"Firefox": "n",
|
||||
"Safari": "n"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "n",
|
||||
"阿里": "n",
|
||||
"百度": "n",
|
||||
"字节跳动": "n",
|
||||
"QQ": "n"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "n",
|
||||
"联盟": "n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
# APP版本更新、强制更新、漂亮的更新界面、IOS更新(跳转IOS store)、wgt更新
|
||||
|
||||
| `QQ交流群(607391225)` | `微信交流群(加我好友备注"进群")` |
|
||||
| ----------------------------|--------------------------- |
|
||||
|||
|
||||
| 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更新接口(可以参考上面的示例)
|
||||
在项目目录下`config/componentConfig.js`里面如下配置
|
||||
```
|
||||
// 此方法是接口请求方法
|
||||
import $http from '@/config/requestConfig'
|
||||
export default {
|
||||
// 发起ajax请求获取服务端版本号
|
||||
getServerNo: (version, isPrompt = false, callback) => {
|
||||
let httpData = {
|
||||
version: version.versionCode,
|
||||
// 版本名称
|
||||
versionName: version.versionName,
|
||||
// setupPage参数说明(判断用户是不是从设置页面点击的更新,如果是设置页面点击的更新,有不要用静默更新了,不然用户点击没反应很奇怪的)
|
||||
setupPage: isPrompt
|
||||
};
|
||||
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) {
|
||||
// 兼容之前的版本(updateType是新版才有的参数)
|
||||
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"
|
||||
});
|
||||
}
|
||||
});
|
||||
/****************以上是示例*******************/
|
||||
},
|
||||
// 弹窗主颜色(不填默认粉色)
|
||||
appUpdateColor: "f00",
|
||||
// 弹窗图标(不填显示默认图标,链接配置示例如: '/static/demo/ic_attention.png')
|
||||
appUpdateIcon: ''
|
||||
}
|
||||
```
|
||||
|
||||
### 第二步 使用方法
|
||||
```
|
||||
// App.vue页面
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
import APPUpdate from '@/uni_modules/zhouWei-APPUpdate/js_sdk/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/index.js`里面上面`$mainColor`常量中定义主题颜色,`$iconUrl`常量中定义图标地址
|
||||
|
||||
### 检查APP是否有新版本(一般在设置页面使用)
|
||||
```
|
||||
// #ifdef APP-PLUS
|
||||
import APPUpdate, { getCurrentNo } from '@/uni_modules/zhouWei-APPUpdate/js_sdk/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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,93 @@
|
||||
/**** 此文件说明请看注释 *****/
|
||||
// 可以用自己项目的请求方法
|
||||
// 请求配置说明:https://ext.dcloud.net.cn/plugin?id=822
|
||||
import {
|
||||
req
|
||||
} from '@/api/index';
|
||||
/**** 结束 *****/
|
||||
const platform = uni.getSystemInfoSync().platform;
|
||||
export default {
|
||||
// 发起ajax请求获取服务端版本号
|
||||
getServerNo: (version, isPrompt = false, callback) => {
|
||||
let httpData = {
|
||||
version: version.versionCode,
|
||||
// 版本名称
|
||||
versionName: version.versionName,
|
||||
// setupPage参数说明(判断用户是不是从设置页面点击的更新,如果是设置页面点击的更新,有不要用静默更新了,不然用户点击没反应很奇怪的)
|
||||
setupPage: isPrompt
|
||||
};
|
||||
if (platform == "android") {
|
||||
httpData.type = 1101;
|
||||
} else {
|
||||
httpData.type = 1102;
|
||||
}
|
||||
/* 接口入参说明
|
||||
* version: 应用当前版本号(已自动获取)
|
||||
* versionName: 应用当前版本名称(已自动获取)
|
||||
* type:平台(1101是安卓,1102是IOS)
|
||||
*/
|
||||
/****************以下是示例*******************/
|
||||
// 可以用自己项目的请求方法(接口自己找后台要,插件不提供)
|
||||
// console.log(httpData)
|
||||
req("/publics/version").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文件放文件下载链接) |
|
||||
*/
|
||||
// console.log('下载链接', res)
|
||||
if (res.code == 1) {
|
||||
if (Number(res.data.newversion) > httpData.version) {
|
||||
let temp = {
|
||||
versionCode: res.data.newversion,
|
||||
versionName: res.data.oldversion,
|
||||
versionInfo: res.data.content,
|
||||
updateType: 'solicit',
|
||||
downloadUrl: res.data.downloadurl,
|
||||
// downloadUrl:'https://dldir1.qq.com/weixin/android/weixin8016android2040.apk',
|
||||
totalSize: res.data.packagesize
|
||||
}
|
||||
if (res.data.enforce == 1) {
|
||||
temp.updateType = "forcibly";
|
||||
} else {
|
||||
temp.updateType = "solicit";
|
||||
}
|
||||
callback && callback(temp);
|
||||
|
||||
} else if (isPrompt) {
|
||||
uni.showToast({
|
||||
title: "You have the newest version",
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
}
|
||||
// if (res && res.data.downloadurl) {
|
||||
// // 兼容之前的版本(updateType是新版才有的参数)
|
||||
// if (res.updateType) {
|
||||
// callback && callback(res.data);
|
||||
// } else {
|
||||
// if (res.data.enforce == 1) {
|
||||
// res.data.updateType = "forcibly";
|
||||
// } else {
|
||||
// res.data.updateType = "solicit";
|
||||
// }
|
||||
// callback && callback(res.data);
|
||||
// }
|
||||
// } else if (isPrompt) {
|
||||
// uni.showToast({
|
||||
// title: "暂无新版本",
|
||||
// icon: "none"
|
||||
// });
|
||||
// }
|
||||
});
|
||||
/****************以上是示例*******************/
|
||||
},
|
||||
// 弹窗主颜色(不填默认粉色)
|
||||
appUpdateColor: "#FF5B78",
|
||||
// 弹窗图标(不填显示默认图标,链接配置示例如: '/static/ic_ar.png')
|
||||
appUpdateIcon: ''
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
/**
|
||||
* 设置粘贴板数据
|
||||
* @param {String} text 要设置的字符串
|
||||
* 如果未设置参数,则清空数据
|
||||
*/
|
||||
function setClipboardText(text){
|
||||
try{
|
||||
var os = plus.os.name;
|
||||
text = text||'';
|
||||
if('iOS' == os){
|
||||
// var UIPasteboard = plus.ios.importClass('UIPasteboard');
|
||||
// var pasteboard = UIPasteboard.generalPasteboard();
|
||||
// pasteboard.setValueforPasteboardType(text, 'public.utf8-plain-text');
|
||||
var pasteboard = plus.ios.invoke('UIPasteboard', 'generalPasteboard');
|
||||
plus.ios.invoke(pasteboard, 'setValue:forPasteboardType:', text, 'public.utf8-plain-text');
|
||||
}else{
|
||||
var main = plus.android.runtimeMainActivity();
|
||||
// var Context = plus.android.importClass('android.content.Context');
|
||||
// var clip = main.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
var clip = main.getSystemService('clipboard');
|
||||
plus.android.invoke(clip, 'setText', text);
|
||||
}
|
||||
}catch(e){
|
||||
console.error('error @setClipboardText!!');
|
||||
}
|
||||
}
|
||||
|
||||
function getClipboardText(){
|
||||
try{
|
||||
var os = plus.os.name;
|
||||
if('iOS' == os){
|
||||
var pasteboard = plus.ios.invoke('UIPasteboard', 'generalPasteboard');
|
||||
return plus.ios.invoke(pasteboard, 'valueForPasteboardType:', 'public.utf8-plain-text')
|
||||
}else{
|
||||
var main = plus.android.runtimeMainActivity();
|
||||
var clip = main.getSystemService('clipboard');
|
||||
return plus.android.invoke(clip, 'getText');
|
||||
}
|
||||
}catch(e){
|
||||
console.error('error @getClipboardText!!');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
setText: setClipboardText,
|
||||
getText: getClipboardText
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
let country = [
|
||||
{
|
||||
label: '台灣',
|
||||
name:'台灣 +886',
|
||||
value: '886'
|
||||
},
|
||||
{
|
||||
label: 'England',
|
||||
name:'England +44',
|
||||
value: '44'
|
||||
},
|
||||
{
|
||||
label: 'Монгол',
|
||||
name:'Монгол +976',
|
||||
value: '976'
|
||||
},
|
||||
{
|
||||
label: 'Malaysia',
|
||||
name:'Malaysia +60',
|
||||
value: '60'
|
||||
},
|
||||
{
|
||||
label: '대한민국',
|
||||
name:'대한민국 +82',
|
||||
value: '82'
|
||||
},
|
||||
{
|
||||
label: 'La France',
|
||||
name:'La France +33',
|
||||
value: '33'
|
||||
},
|
||||
{
|
||||
label: 'Italia',
|
||||
name:'Italia +39',
|
||||
value: '39'
|
||||
},
|
||||
{
|
||||
label: 'Deutschland',
|
||||
name:'Deutschland +49',
|
||||
value: '49'
|
||||
},
|
||||
{
|
||||
label: '日本',
|
||||
name:'日本 +81',
|
||||
value: '81'
|
||||
},
|
||||
// {
|
||||
// label: 'ang Pilipinas',
|
||||
// name:'ang Pilipinas +63',
|
||||
// value: '63'
|
||||
// },
|
||||
// {
|
||||
// label: 'Republic',
|
||||
// name:'Republic +65',
|
||||
// value: '65'
|
||||
// },
|
||||
// {
|
||||
// label: 'Việt Nam',
|
||||
// name:'Việt Nam +84',
|
||||
// value: '84'
|
||||
// },
|
||||
// {
|
||||
// label: 'မြန်မာ',
|
||||
// name:'မြန်မာ +95',
|
||||
// value: '95'
|
||||
// },
|
||||
// {
|
||||
// label: 'ປະເທດລາວ',
|
||||
// name:'ປະເທດລາວ +856',
|
||||
// value: '856'
|
||||
// },
|
||||
// {
|
||||
// label: 'ประเทศไทย',
|
||||
// name:'ประเทศไทย +66',
|
||||
// value: '66'
|
||||
// },
|
||||
// {
|
||||
// label: 'বাংলা',
|
||||
// name:'বাংলা +880',
|
||||
// value: '880'
|
||||
// },
|
||||
// {
|
||||
// label: '中国',
|
||||
// name:'中国 +86',
|
||||
// value: '86'
|
||||
// },
|
||||
]
|
||||
|
||||
export {
|
||||
country
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<!-- 自定义的下拉刷新view -->
|
||||
<template>
|
||||
<view class="refresher-container">
|
||||
<!-- 这里的图片请换成自己项目的图片 -->
|
||||
<!-- <image class="refresher-image" mode="" src="@/static/gif.gif"></image> -->
|
||||
<text class="refresher-text">{{statusText}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusText() {
|
||||
// 获取当前语言
|
||||
let currentLang = uni.getStorageSync('Language') || 'en-us';
|
||||
|
||||
// 语言映射:将项目语言代码映射到显示文本
|
||||
const langMap = {
|
||||
'zh-cn': {
|
||||
0: '继续下拉刷新',
|
||||
1: '松开立即刷新',
|
||||
2: '正在刷新...',
|
||||
3: '刷新成功'
|
||||
},
|
||||
'en-us': {
|
||||
0: 'Pull down to refresh',
|
||||
1: 'Release to refresh',
|
||||
2: 'Refreshing...',
|
||||
3: 'Refresh succeeded'
|
||||
},
|
||||
'jp': {
|
||||
0: '引き続きプルダウンして更新',
|
||||
1: '手を離してすぐに更新',
|
||||
2: '更新中...',
|
||||
3: '更新成功'
|
||||
},
|
||||
'kr': {
|
||||
0: '계속 아래로 당겨 새로 고침',
|
||||
1: '손을 놓고 즉시 새로 고침',
|
||||
2: '새로 고침 중...',
|
||||
3: '새로 고침 성공'
|
||||
},
|
||||
'mn': {
|
||||
0: 'Үргэлжлүүлэн доош татаж шинэчлэх',
|
||||
1: 'Гараа суллаад шууд шинэчлэх',
|
||||
2: 'Шинэчилж байна...',
|
||||
3: 'Шинэчлэлт амжилттай'
|
||||
},
|
||||
'ms': {
|
||||
0: 'Teruskan tarik ke bawah untuk muat semula',
|
||||
1: 'Lepaskan untuk muat semula',
|
||||
2: 'Memuatkan semula...',
|
||||
3: 'Muat semula berjaya'
|
||||
},
|
||||
'fre': {
|
||||
0: 'Continuez à tirer vers le bas pour actualiser',
|
||||
1: 'Relâchez pour actualiser',
|
||||
2: 'Actualisation...',
|
||||
3: 'Actualisation réussie'
|
||||
},
|
||||
'ger': {
|
||||
0: 'Weiter nach unten ziehen zum Aktualisieren',
|
||||
1: 'Loslassen zum Aktualisieren',
|
||||
2: 'Aktualisierung...',
|
||||
3: 'Aktualisierung erfolgreich'
|
||||
},
|
||||
'ily': {
|
||||
0: 'Continua a trascinare verso il basso per aggiornare',
|
||||
1: 'Rilascia per aggiornare',
|
||||
2: 'Aggiornamento...',
|
||||
3: 'Aggiornamento riuscito'
|
||||
}
|
||||
};
|
||||
|
||||
// 获取对应语言的文本,如果没有则使用英文
|
||||
const texts = langMap[currentLang] || langMap['en-us'];
|
||||
return texts[this.status] || '';
|
||||
}
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: Number,
|
||||
default: function() {
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.refresher-container {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
height: 82rpx;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.refresher-image {
|
||||
/* margin-top: 10rpx; */
|
||||
width: 108rpx;
|
||||
height: 82rpx;
|
||||
}
|
||||
|
||||
.refresher-text {
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #3ECEB2;
|
||||
/* background-image:-webkit-linear-gradient(45deg,#063AEE,#6415C2,#E40286); */
|
||||
/* -webkit-background-clip:text; */
|
||||
/* -webkit-text-fill-color:transparent; */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,633 @@
|
||||
<template>
|
||||
<view>
|
||||
<slot v-if="!nodes.length" />
|
||||
<!--#ifdef APP-PLUS-NVUE-->
|
||||
<web-view id="_top" ref="web" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
|
||||
<!--#endif-->
|
||||
<!--#ifndef APP-PLUS-NVUE-->
|
||||
<view id="_top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')">
|
||||
<!--#ifdef H5 || MP-360-->
|
||||
<div :id="'rtf'+uid"></div>
|
||||
<!--#endif-->
|
||||
<!--#ifndef H5 || MP-360-->
|
||||
<trees :nodes="nodes" :lazyLoad="lazyLoad" :loading="loadingImg" />
|
||||
<!--#endif-->
|
||||
</view>
|
||||
<!--#endif-->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
import trees from './libs/trees';
|
||||
var cache = {},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
|
||||
// #endif
|
||||
Parser = require('./libs/MpHtmlParser.js');
|
||||
var dom;
|
||||
// 计算 cache 的 key
|
||||
function hash(str) {
|
||||
for (var i = str.length, val = 5381; i--;)
|
||||
val += (val << 5) + str.charCodeAt(i);
|
||||
return val;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5 || APP-PLUS-NVUE || MP-360
|
||||
var rpx = uni.getSystemInfoSync().windowWidth / 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} html 富文本数据
|
||||
* @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
|
||||
* @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
|
||||
* @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
|
||||
* @property {Number} compress 压缩等级
|
||||
* @property {String} domain 图片、视频等链接的主域名
|
||||
* @property {Boolean} lazyLoad 是否开启图片懒加载
|
||||
* @property {String} loadingImg 图片加载完成前的占位图
|
||||
* @property {Boolean} selectable 是否开启长按复制
|
||||
* @property {Object} tagStyle 标签的默认样式
|
||||
* @property {Boolean} showWithAnimation 是否使用渐显动画
|
||||
* @property {Boolean} useAnchor 是否使用锚点
|
||||
* @property {Boolean} useCache 是否缓存解析结果
|
||||
* @event {Function} parse 解析完成事件
|
||||
* @event {Function} load dom 加载完成事件
|
||||
* @event {Function} ready 所有图片加载完毕事件
|
||||
* @event {Function} error 错误事件
|
||||
* @event {Function} imgtap 图片点击事件
|
||||
* @event {Function} linkpress 链接点击事件
|
||||
* @author JinYufeng
|
||||
* @version 20200615
|
||||
* @listens MIT
|
||||
*/
|
||||
export default {
|
||||
name: 'parser',
|
||||
data() {
|
||||
return {
|
||||
// #ifdef H5 || MP-360
|
||||
uid: this._uid,
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
height: 1,
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
showAm: '',
|
||||
// #endif
|
||||
nodes: []
|
||||
}
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
html: String,
|
||||
autopause: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoscroll: Boolean,
|
||||
autosetTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
compress: Number,
|
||||
loadingImg: String,
|
||||
useCache: Boolean,
|
||||
// #endif
|
||||
domain: String,
|
||||
lazyLoad: Boolean,
|
||||
selectable: Boolean,
|
||||
tagStyle: Object,
|
||||
showWithAnimation: Boolean,
|
||||
useAnchor: Boolean
|
||||
},
|
||||
watch: {
|
||||
html(html) {
|
||||
this.setContent(html);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 图片数组
|
||||
this.imgList = [];
|
||||
this.imgList.each = function(f) {
|
||||
for (var i = 0, len = this.length; i < len; i++)
|
||||
this.setItem(i, f(this[i], i, this));
|
||||
}
|
||||
this.imgList.setItem = function(i, src) {
|
||||
if (i == void 0 || !src) return;
|
||||
// #ifndef MP-ALIPAY || APP-PLUS
|
||||
// 去重
|
||||
if (src.indexOf('http') == 0 && this.includes(src)) {
|
||||
var newSrc = src.split('://')[0];
|
||||
for (var j = newSrc.length, c; c = src[j]; j++) {
|
||||
if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
|
||||
newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
|
||||
}
|
||||
newSrc += src.substr(j);
|
||||
return this[i] = newSrc;
|
||||
}
|
||||
// #endif
|
||||
this[i] = src;
|
||||
// 暂存 data src
|
||||
if (src.includes('data:image')) {
|
||||
var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
|
||||
if (!info) return;
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
|
||||
fs && fs.writeFile({
|
||||
filePath,
|
||||
data: info[3],
|
||||
encoding: info[2],
|
||||
success: () => this[i] = filePath
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
|
||||
var bitmap = new plus.nativeObj.Bitmap();
|
||||
bitmap.loadBase64Data(src, () => {
|
||||
bitmap.save(filePath, {}, () => {
|
||||
bitmap.clear()
|
||||
this[i] = filePath;
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5 || MP-360
|
||||
this.document = document.getElementById('rtf' + this._uid);
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
if (dom) this.document = new dom(this);
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
this.document = this.$refs.web;
|
||||
setTimeout(() => {
|
||||
// #endif
|
||||
if (this.html) this.setContent(this.html);
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
}, 30)
|
||||
// #endif
|
||||
},
|
||||
beforeDestroy() {
|
||||
// #ifdef H5 || MP-360
|
||||
if (this._observer) this._observer.disconnect();
|
||||
// #endif
|
||||
this.imgList.each(src => {
|
||||
// #ifdef APP-PLUS
|
||||
if (src && src.includes('_doc')) {
|
||||
plus.io.resolveLocalFileSystemURL(src, entry => {
|
||||
entry.remove();
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
if (src && src.includes(uni.env.USER_DATA_PATH))
|
||||
fs && fs.unlink({
|
||||
filePath: src
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
clearInterval(this._timer);
|
||||
},
|
||||
methods: {
|
||||
// #ifdef H5 || APP-PLUS-NVUE || MP-360
|
||||
_handleHtml(html, append) {
|
||||
if (!append) {
|
||||
// 处理 tag-style 和 userAgentStyles
|
||||
var style = '<style>@keyframes _show{0%{opacity:0}100%{opacity:1}}img{max-width:100%}';
|
||||
for (var item in cfg.userAgentStyles)
|
||||
style += `${item}{${cfg.userAgentStyles[item]}}`;
|
||||
for (item in this.tagStyle)
|
||||
style += `${item}{${this.tagStyle[item]}}`;
|
||||
style += '</style>';
|
||||
html = style + html;
|
||||
}
|
||||
// 处理 rpx
|
||||
if (html.includes('rpx'))
|
||||
html = html.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * 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 b=document.createElement('div');b.innerHTML='" + html.replace(/'/g, "\\'") +
|
||||
"';document.getElementById('parser').appendChild(b)");
|
||||
else {
|
||||
html =
|
||||
'<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><base href="' +
|
||||
this.domain + '"><div id="parser"' + (this.selectable ? '>' : ' style="user-select:none">') + this._handleHtml(html).replace(/\n/g, '\\n') +
|
||||
'</div><script>"use strict";function e(e){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[e]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}' +
|
||||
(this.showWithAnimation ? 'document.body.style.animation="_show .5s",' : '') +
|
||||
'setTimeout(function(){e({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight+16})},50);\x3c/script>';
|
||||
this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
|
||||
}
|
||||
this.$refs.web.evalJs(
|
||||
'var t=document.getElementsByTagName("title");t.length&&e({action:"getTitle",title:t[0].innerText});for(var o,n=document.getElementsByTagName("style"),r=0;o=n[r++];)o.innerHTML=o.innerHTML.replace(/body/g,"#parser");for(var i,a=document.getElementsByTagName("img"),s=[],c=0,l=0;i=a[c];c++)i.onerror=function(){' +
|
||||
(cfg.errorImg ? 'this.src="' + cfg.errorImg + '",' : '') +
|
||||
'e({action:"error",source:"img",target:this})},i.hasAttribute("ignore")||"A"==i.parentElement.nodeName||(i.i=l++,s.push(i.src),i.onclick=function(){e({action:"preview",img:{i:this.i,src:this.src}})});e({action:"getImgList",imgList:s});for(var d,u=document.getElementsByTagName("a"),g=0;d=u[g];g++)d.onclick=function(){var t,o=this.getAttribute("href");if("#"==o[0]){var n=document.getElementById(o.substr(1));n&&(t=n.offsetTop)}return e({action:"linkpress",href:o,offset:t}),!1};for(var m,f=document.getElementsByTagName("video"),h=0;m=f[h];h++)m.style.maxWidth="100%",m.onerror=function(){e({action:"error",source:"video",target:this})}' +
|
||||
(this.autopause ? ',m.onplay=function(){for(var e,t=0;e=f[t];t++)e!=this&&e.pause()}' : '') +
|
||||
';for(var v,y=document.getElementsByTagName("audio"),_=0;v=y[_];_++)v.onerror=function(){e({action:"error",source:"audio",target:this})};' +
|
||||
(this.autoscroll ? 'for(var p,w=document.getElementsByTagName("table"),T=0;p=w[T];T++){var E=document.createElement("div");E.style.overflow="scroll",p.parentNode.replaceChild(E,p),E.appendChild(p)}' : '') +
|
||||
'(function(){return new Promise(function(e){var t=document.getElementById("parser"),o=t.scrollHeight,n=setInterval(function(){o==t.scrollHeight?(clearInterval(n),e(o)):o=t.scrollHeight},500)})})().then(function(t){e({action:"ready",height:t+16})})'
|
||||
)
|
||||
this.nodes = [1];
|
||||
// #endif
|
||||
// #ifdef H5 || MP-360
|
||||
if (!html) {
|
||||
if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
|
||||
return;
|
||||
}
|
||||
var div = document.createElement('div');
|
||||
if (!append) {
|
||||
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
|
||||
this.rtf = div;
|
||||
} else {
|
||||
if (!this.rtf) this.rtf = div;
|
||||
else this.rtf.appendChild(div);
|
||||
}
|
||||
div.innerHTML = this._handleHtml(html, append);
|
||||
for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
|
||||
style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
|
||||
style.setAttribute('scoped', 'true');
|
||||
}
|
||||
// 懒加载
|
||||
if (!this._observer && this.lazyLoad && IntersectionObserver) {
|
||||
this._observer = new IntersectionObserver(changes => {
|
||||
for (let item, i = 0; item = changes[i++];) {
|
||||
if (item.isIntersecting) {
|
||||
item.target.src = item.target.getAttribute('data-src');
|
||||
item.target.removeAttribute('data-src');
|
||||
this._observer.unobserve(item.target);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
rootMargin: '500px 0px 500px 0px'
|
||||
})
|
||||
}
|
||||
var _ts = this;
|
||||
// 获取标题
|
||||
var title = this.rtf.getElementsByTagName('title');
|
||||
if (title.length && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: title[0].innerText
|
||||
})
|
||||
// 图片处理
|
||||
this.imgList.length = 0;
|
||||
var imgs = this.rtf.getElementsByTagName('img');
|
||||
for (let i = 0, j = 0, img; img = imgs[i]; i++) {
|
||||
var src = img.getAttribute('src');
|
||||
if (this.domain && src) {
|
||||
if (src[0] == '/') {
|
||||
if (src[1] == '/')
|
||||
img.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
|
||||
else img.src = this.domain + src;
|
||||
} else if (!src.includes('://')) img.src = this.domain + '/' + src;
|
||||
}
|
||||
if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
|
||||
img.i = j++;
|
||||
_ts.imgList.push(img.src || img.getAttribute('data-src'));
|
||||
img.onclick = function() {
|
||||
var preview = true;
|
||||
this.ignore = () => preview = false;
|
||||
_ts.$emit('imgtap', this);
|
||||
if (preview) {
|
||||
uni.previewImage({
|
||||
current: this.i,
|
||||
urls: _ts.imgList
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
img.onerror = function() {
|
||||
if (cfg.errorImg)
|
||||
_ts.imgList[this.i] = this.src = cfg.errorImg;
|
||||
_ts.$emit('error', {
|
||||
source: 'img',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
|
||||
img.setAttribute('data-src', img.src);
|
||||
img.removeAttribute('src');
|
||||
this._observer.observe(img);
|
||||
}
|
||||
}
|
||||
// 链接处理
|
||||
var links = this.rtf.getElementsByTagName('a');
|
||||
for (var link of links) {
|
||||
link.onclick = function() {
|
||||
var jump = true,
|
||||
href = this.getAttribute('href');
|
||||
_ts.$emit('linkpress', {
|
||||
href,
|
||||
ignore: () => jump = false
|
||||
});
|
||||
if (jump && href) {
|
||||
if (href[0] == '#') {
|
||||
if (_ts.useAnchor) {
|
||||
_ts.navigateTo({
|
||||
id: href.substr(1)
|
||||
})
|
||||
}
|
||||
} else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
|
||||
return true;
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 视频处理
|
||||
var videos = this.rtf.getElementsByTagName('video');
|
||||
_ts.videoContexts = videos;
|
||||
for (let video, i = 0; video = videos[i++];) {
|
||||
video.style.maxWidth = '100%';
|
||||
video.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'video',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
video.onplay = function() {
|
||||
if (_ts.autopause)
|
||||
for (let item, i = 0; item = _ts.videoContexts[i++];)
|
||||
if (item != this) item.pause();
|
||||
}
|
||||
}
|
||||
// 音频处理
|
||||
var audios = this.rtf.getElementsByTagName('audio');
|
||||
for (var audio of audios)
|
||||
audio.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'audio',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
// 表格处理
|
||||
if (this.autoscroll) {
|
||||
var tables = this.rtf.getElementsByTagName('table');
|
||||
for (var table of tables) {
|
||||
let div = document.createElement('div');
|
||||
div.style.overflow = 'scroll';
|
||||
table.parentNode.replaceChild(div, table);
|
||||
div.appendChild(table);
|
||||
}
|
||||
}
|
||||
if (!append) this.document.appendChild(this.rtf);
|
||||
this.$nextTick(() => {
|
||||
this.nodes = [1];
|
||||
this.$emit('load');
|
||||
});
|
||||
setTimeout(() => this.showAm = '', 500);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
// #ifndef H5 || MP-360
|
||||
var nodes;
|
||||
if (!html) return this.nodes = [];
|
||||
var parser = new Parser(html, this);
|
||||
// 缓存读取
|
||||
if (this.useCache) {
|
||||
var hashVal = hash(html);
|
||||
if (cache[hashVal])
|
||||
nodes = cache[hashVal];
|
||||
else {
|
||||
nodes = parser.parse();
|
||||
cache[hashVal] = nodes;
|
||||
}
|
||||
} else nodes = parser.parse();
|
||||
this.$emit('parse', nodes);
|
||||
if (append) this.nodes = this.nodes.concat(nodes);
|
||||
else this.nodes = nodes;
|
||||
if (nodes.length && nodes.title && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: nodes.title
|
||||
})
|
||||
if (this.imgList) this.imgList.length = 0;
|
||||
this.videoContexts = [];
|
||||
this.$nextTick(() => {
|
||||
(function f(cs) {
|
||||
for (var i = cs.length; i--;) {
|
||||
if (cs[i].top) {
|
||||
cs[i].controls = [];
|
||||
cs[i].init();
|
||||
f(cs[i].$children);
|
||||
}
|
||||
}
|
||||
})(this.$children)
|
||||
this.$emit('load');
|
||||
})
|
||||
// #endif
|
||||
var height;
|
||||
clearInterval(this._timer);
|
||||
this._timer = setInterval(() => {
|
||||
// #ifdef H5 || MP-360
|
||||
this.rect = this.rtf.getBoundingClientRect();
|
||||
// #endif
|
||||
// #ifndef H5 || MP-360
|
||||
uni.createSelectorQuery().in(this)
|
||||
.select('#_top').boundingClientRect().exec(res => {
|
||||
if (!res) return;
|
||||
this.rect = res[0];
|
||||
// #endif
|
||||
if (this.rect.height == height) {
|
||||
this.$emit('ready', this.rect)
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
height = this.rect.height;
|
||||
// #ifndef H5 || MP-360
|
||||
});
|
||||
// #endif
|
||||
}, 350);
|
||||
if (this.showWithAnimation && !append) this.showAm = 'animation:_show .5s';
|
||||
// #endif
|
||||
},
|
||||
getText(ns = this.nodes) {
|
||||
var txt = '';
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
txt = this._text;
|
||||
// #endif
|
||||
// #ifdef H5 || MP-360
|
||||
txt = this.rtf.innerText;
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
for (var i = 0, n; n = ns[i++];) {
|
||||
if (n.type == 'text') txt += n.text.replace(/ /g, '\u00A0').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/&/g, '&');
|
||||
else if (n.type == 'br') txt += '\n';
|
||||
else {
|
||||
// 块级标签前后加换行
|
||||
var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
|
||||
'0' && n.name[1] < '7');
|
||||
if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
if (n.children) txt += this.getText(n.children);
|
||||
if (block && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
else if (n.name == 'td' || n.name == 'th') txt += '\t';
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
return txt;
|
||||
},
|
||||
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 || MP-360
|
||||
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 || MP-360
|
||||
var d = ' ';
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
||||
d = '>>>';
|
||||
// #endif
|
||||
uni.createSelectorQuery().in(this).select('#_top' + (obj.id ? d + '#' + obj.id + ',#_top' + d + '.' + obj.id : '')).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);
|
||||
// #ifdef MP-ALIPAY
|
||||
obj.duration = 300;
|
||||
my.
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
uni.
|
||||
// #endif
|
||||
pageScrollTo(obj);
|
||||
})
|
||||
// #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') {
|
||||
if (data.source == 'img' && cfg.errorImg)
|
||||
this.imgList.setItem(data.target.i, cfg.errorImg);
|
||||
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>
|
||||
@@ -0,0 +1,97 @@
|
||||
const cfg = require('./config.js'),
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
|
||||
function CssHandler(tagStyle) {
|
||||
var styles = Object.assign(Object.create(null), cfg.userAgentStyles);
|
||||
for (var item in tagStyle)
|
||||
styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
|
||||
this.styles = styles;
|
||||
}
|
||||
CssHandler.prototype.getStyle = function(data) {
|
||||
this.styles = new parser(data, this.styles).parse();
|
||||
}
|
||||
CssHandler.prototype.match = function(name, attrs) {
|
||||
var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';
|
||||
if (attrs.class) {
|
||||
var items = attrs.class.split(' ');
|
||||
for (var i = 0, item; item = items[i]; i++)
|
||||
if (tmp = this.styles['.' + item])
|
||||
matched += tmp + ';';
|
||||
}
|
||||
if (tmp = this.styles['#' + attrs.id])
|
||||
matched += tmp + ';';
|
||||
return matched;
|
||||
}
|
||||
module.exports = CssHandler;
|
||||
|
||||
function parser(data, init) {
|
||||
this.data = data;
|
||||
this.floor = 0;
|
||||
this.i = 0;
|
||||
this.list = [];
|
||||
this.res = init;
|
||||
this.state = this.Space;
|
||||
}
|
||||
parser.prototype.parse = function() {
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
return this.res;
|
||||
}
|
||||
parser.prototype.section = function() {
|
||||
return this.data.substring(this.start, this.i);
|
||||
}
|
||||
// 状态机
|
||||
parser.prototype.Space = function(c) {
|
||||
if (c == '.' || c == '#' || isLetter(c)) {
|
||||
this.start = this.i;
|
||||
this.state = this.Name;
|
||||
} else if (c == '/' && this.data[this.i + 1] == '*')
|
||||
this.Comment();
|
||||
else if (!cfg.blankChar[c] && c != ';')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.Comment = function() {
|
||||
this.i = this.data.indexOf('*/', this.i) + 1;
|
||||
if (!this.i) this.i = this.data.length;
|
||||
this.state = this.Space;
|
||||
}
|
||||
parser.prototype.Ignore = function(c) {
|
||||
if (c == '{') this.floor++;
|
||||
else if (c == '}' && !--this.floor) this.state = this.Space;
|
||||
}
|
||||
parser.prototype.Name = function(c) {
|
||||
if (cfg.blankChar[c]) {
|
||||
this.list.push(this.section());
|
||||
this.state = this.NameSpace;
|
||||
} else if (c == '{') {
|
||||
this.list.push(this.section());
|
||||
this.Content();
|
||||
} else if (c == ',') {
|
||||
this.list.push(this.section());
|
||||
this.Comma();
|
||||
} else if (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.NameSpace = function(c) {
|
||||
if (c == '{') this.Content();
|
||||
else if (c == ',') this.Comma();
|
||||
else if (!cfg.blankChar[c]) this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.Comma = function() {
|
||||
while (cfg.blankChar[this.data[++this.i]]);
|
||||
if (this.data[this.i] == '{') this.Content();
|
||||
else {
|
||||
this.start = this.i--;
|
||||
this.state = this.Name;
|
||||
}
|
||||
}
|
||||
parser.prototype.Content = function() {
|
||||
this.start = ++this.i;
|
||||
if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;
|
||||
var content = this.section();
|
||||
for (var i = 0, item; item = this.list[i++];)
|
||||
if (this.res[item]) this.res[item] += ';' + content;
|
||||
else this.res[item] = content;
|
||||
this.list = [];
|
||||
this.state = this.Space;
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
/**
|
||||
* html 解析器
|
||||
* @tutorial https://github.com/jin-yufeng/Parser
|
||||
* @version 20200615
|
||||
* @author JinYufeng
|
||||
* @listens MIT
|
||||
*/
|
||||
const cfg = require('./config.js'),
|
||||
blankChar = cfg.blankChar,
|
||||
CssHandler = require('./CssHandler.js'),
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth;
|
||||
var emoji;
|
||||
|
||||
function MpHtmlParser(data, options = {}) {
|
||||
this.attrs = {};
|
||||
this.CssHandler = new CssHandler(options.tagStyle, windowWidth);
|
||||
this.data = data;
|
||||
this.domain = options.domain;
|
||||
this.DOM = [];
|
||||
this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
|
||||
options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http';
|
||||
this.options = options;
|
||||
this.state = this.Text;
|
||||
this.STACK = [];
|
||||
// 工具函数
|
||||
this.bubble = () => {
|
||||
for (var i = this.STACK.length, item; item = this.STACK[--i];) {
|
||||
if (cfg.richOnlyTags[item.name]) {
|
||||
if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1;
|
||||
return false;
|
||||
}
|
||||
item.c = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.decode = (val, amp) => {
|
||||
var i = -1,
|
||||
j, en;
|
||||
while (1) {
|
||||
if ((i = val.indexOf('&', i + 1)) == -1) break;
|
||||
if ((j = val.indexOf(';', i + 2)) == -1) break;
|
||||
if (val[i + 1] == '#') {
|
||||
en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j));
|
||||
if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1);
|
||||
} else {
|
||||
en = val.substring(i + 1, j);
|
||||
if (cfg.entities[en] || en == amp)
|
||||
val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
this.getUrl = url => {
|
||||
if (url[0] == '/') {
|
||||
if (url[1] == '/') url = this.options.prot + ':' + url;
|
||||
else if (this.domain) url = this.domain + url;
|
||||
} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
|
||||
url = this.domain + '/' + url;
|
||||
return url;
|
||||
}
|
||||
this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
|
||||
this.section = () => this.data.substring(this.start, this.i);
|
||||
this.parent = () => this.STACK[this.STACK.length - 1];
|
||||
this.siblings = () => this.STACK.length ? this.parent().children : this.DOM;
|
||||
}
|
||||
MpHtmlParser.prototype.parse = function() {
|
||||
if (emoji) this.data = emoji.parseEmoji(this.data);
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
if (this.state == this.Text) this.setText();
|
||||
while (this.STACK.length) this.popNode(this.STACK.pop());
|
||||
return this.DOM;
|
||||
}
|
||||
// 设置属性
|
||||
MpHtmlParser.prototype.setAttr = function() {
|
||||
var name = this.attrName.toLowerCase(),
|
||||
val = this.attrVal;
|
||||
if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
|
||||
else if (val) {
|
||||
if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp'));
|
||||
else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp');
|
||||
else if (name.substr(0, 5) != 'data-') this.attrs[name] = val;
|
||||
}
|
||||
this.attrVal = '';
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
}
|
||||
// 设置文本节点
|
||||
MpHtmlParser.prototype.setText = function() {
|
||||
var back, text = this.section();
|
||||
if (!text) return;
|
||||
text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
|
||||
if (back) {
|
||||
this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
|
||||
let j = this.start + text.length;
|
||||
for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
|
||||
return;
|
||||
}
|
||||
if (!this.pre) {
|
||||
// 合并空白符
|
||||
var tmp = [];
|
||||
for (let i = text.length, c; c = text[--i];)
|
||||
if (!blankChar[c] || (!blankChar[tmp[0]] && (c = ' '))) tmp.unshift(c);
|
||||
text = tmp.join('');
|
||||
}
|
||||
this.siblings().push({
|
||||
type: 'text',
|
||||
text: this.decode(text)
|
||||
});
|
||||
}
|
||||
// 设置元素节点
|
||||
MpHtmlParser.prototype.setNode = function() {
|
||||
var node = {
|
||||
name: this.tagName.toLowerCase(),
|
||||
attrs: this.attrs
|
||||
},
|
||||
close = cfg.selfClosingTags[node.name];
|
||||
this.attrs = {};
|
||||
if (!cfg.ignoreTags[node.name]) {
|
||||
// 处理属性
|
||||
var attrs = node.attrs,
|
||||
style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
|
||||
styleObj = {};
|
||||
if (attrs.id) {
|
||||
if (this.options.compress & 1) attrs.id = void 0;
|
||||
else if (this.options.useAnchor) this.bubble();
|
||||
}
|
||||
if ((this.options.compress & 2) && attrs.class) attrs.class = void 0;
|
||||
switch (node.name) {
|
||||
case 'a':
|
||||
case 'ad': // #ifdef APP-PLUS
|
||||
case 'iframe':
|
||||
// #endif
|
||||
this.bubble();
|
||||
break;
|
||||
case 'font':
|
||||
if (attrs.color) {
|
||||
styleObj['color'] = attrs.color;
|
||||
attrs.color = void 0;
|
||||
}
|
||||
if (attrs.face) {
|
||||
styleObj['font-family'] = attrs.face;
|
||||
attrs.face = void 0;
|
||||
}
|
||||
if (attrs.size) {
|
||||
var size = parseInt(attrs.size);
|
||||
if (size < 1) size = 1;
|
||||
else if (size > 7) size = 7;
|
||||
var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
|
||||
styleObj['font-size'] = map[size - 1];
|
||||
attrs.size = void 0;
|
||||
}
|
||||
break;
|
||||
case 'embed':
|
||||
// #ifndef APP-PLUS
|
||||
var src = node.attrs.src || '',
|
||||
type = node.attrs.type || '';
|
||||
if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8'))
|
||||
node.name = 'video';
|
||||
else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes(
|
||||
'.aac'))
|
||||
node.name = 'audio';
|
||||
else break;
|
||||
if (node.attrs.autostart)
|
||||
node.attrs.autoplay = 'T';
|
||||
node.attrs.controls = 'T';
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
this.bubble();
|
||||
break;
|
||||
// #endif
|
||||
case 'video':
|
||||
case 'audio':
|
||||
if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
|
||||
else this[`${node.name}Num`]++;
|
||||
if (node.name == 'video') {
|
||||
if (this.videoNum > 3)
|
||||
node.lazyLoad = 1;
|
||||
if (attrs.width) {
|
||||
styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px');
|
||||
attrs.width = void 0;
|
||||
}
|
||||
if (attrs.height) {
|
||||
styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px');
|
||||
attrs.height = void 0;
|
||||
}
|
||||
}
|
||||
attrs.source = [];
|
||||
if (attrs.src) {
|
||||
attrs.source.push(attrs.src);
|
||||
attrs.src = void 0;
|
||||
}
|
||||
this.bubble();
|
||||
break;
|
||||
case 'td':
|
||||
case 'th':
|
||||
if (attrs.colspan || attrs.rowspan)
|
||||
for (var k = this.STACK.length, item; item = this.STACK[--k];)
|
||||
if (item.name == 'table') {
|
||||
item.c = void 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attrs.align) {
|
||||
styleObj['text-align'] = attrs.align;
|
||||
attrs.align = void 0;
|
||||
}
|
||||
// 压缩 style
|
||||
var styles = style.split(';');
|
||||
style = '';
|
||||
for (var i = 0, len = styles.length; i < len; i++) {
|
||||
var info = styles[i].split(':');
|
||||
if (info.length < 2) continue;
|
||||
let key = info[0].trim().toLowerCase(),
|
||||
value = info.slice(1).join(':').trim();
|
||||
if (value.includes('-webkit') || value.includes('-moz') || value.includes('-ms') || value.includes('-o') || value.includes(
|
||||
'safe'))
|
||||
style += `;${key}:${value}`;
|
||||
else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
|
||||
styleObj[key] = value;
|
||||
}
|
||||
if (node.name == 'img') {
|
||||
if (attrs.src && !attrs.ignore) {
|
||||
if (this.bubble())
|
||||
attrs.i = (this.imgNum++).toString();
|
||||
else attrs.ignore = 'T';
|
||||
}
|
||||
if (attrs.ignore) {
|
||||
style += ';-webkit-touch-callout:none';
|
||||
styleObj['max-width'] = '100%';
|
||||
}
|
||||
var width;
|
||||
if (styleObj.width) width = styleObj.width;
|
||||
else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : attrs.width + 'px';
|
||||
if (width) {
|
||||
styleObj.width = width;
|
||||
attrs.width = '100%';
|
||||
if (parseInt(width) > windowWidth) {
|
||||
styleObj.height = '';
|
||||
if (attrs.height) attrs.height = void 0;
|
||||
}
|
||||
}
|
||||
if (styleObj.height) {
|
||||
attrs.height = styleObj.height;
|
||||
styleObj.height = '';
|
||||
} else if (attrs.height && !attrs.height.includes('%'))
|
||||
attrs.height += 'px';
|
||||
}
|
||||
for (var key in styleObj) {
|
||||
var value = styleObj[key];
|
||||
if (!value) continue;
|
||||
if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
|
||||
// 填充链接
|
||||
if (value.includes('url')) {
|
||||
var j = value.indexOf('(');
|
||||
if (j++ != -1) {
|
||||
while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
|
||||
value = value.substr(0, j) + this.getUrl(value.substr(j));
|
||||
}
|
||||
}
|
||||
// 转换 rpx
|
||||
else if (value.includes('rpx'))
|
||||
value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px');
|
||||
else if (key == 'white-space' && value.includes('pre'))
|
||||
this.pre = node.pre = true;
|
||||
style += `;${key}:${value}`;
|
||||
}
|
||||
style = style.substr(1);
|
||||
if (style) attrs.style = style;
|
||||
if (!close) {
|
||||
node.children = [];
|
||||
if (node.name == 'pre' && cfg.highlight) {
|
||||
this.remove(node);
|
||||
this.pre = node.pre = true;
|
||||
}
|
||||
this.siblings().push(node);
|
||||
this.STACK.push(node);
|
||||
} else if (!cfg.filter || cfg.filter(node, this) != false)
|
||||
this.siblings().push(node);
|
||||
} else {
|
||||
if (!close) this.remove(node);
|
||||
else if (node.name == 'source') {
|
||||
var parent = this.parent();
|
||||
if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src)
|
||||
parent.attrs.source.push(node.attrs.src);
|
||||
} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
|
||||
}
|
||||
if (this.data[this.i] == '/') this.i++;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
// 移除标签
|
||||
MpHtmlParser.prototype.remove = function(node) {
|
||||
var name = node.name,
|
||||
j = this.i;
|
||||
// 处理 svg
|
||||
var handleSvg = () => {
|
||||
var src = this.data.substring(j, this.i + 1);
|
||||
if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src;
|
||||
var i = j;
|
||||
while (this.data[j] != '<') j--;
|
||||
src = this.data.substring(j, i) + src;
|
||||
var parent = this.parent();
|
||||
if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
|
||||
parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
|
||||
this.siblings().push({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
|
||||
style: (/vertical[^;]+/.exec(node.attrs.style) || []).shift(),
|
||||
ignore: 'T'
|
||||
}
|
||||
})
|
||||
}
|
||||
if (node.name == 'svg' && this.data[j] == '/') return handleSvg(this.i++);
|
||||
while (1) {
|
||||
if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
|
||||
if (name == 'pre' || name == 'svg') this.i = j;
|
||||
else this.i = this.data.length;
|
||||
return;
|
||||
}
|
||||
this.start = (this.i += 2);
|
||||
while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
|
||||
if (this.section().toLowerCase() == name) {
|
||||
// 代码块高亮
|
||||
if (name == 'pre') {
|
||||
this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data
|
||||
.substr(this.i - 5);
|
||||
return this.i = j;
|
||||
} else if (name == 'style')
|
||||
this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
|
||||
else if (name == 'title')
|
||||
this.DOM.title = this.data.substring(j + 1, this.i - 7);
|
||||
if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
|
||||
if (name == 'svg') handleSvg();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 节点出栈处理
|
||||
MpHtmlParser.prototype.popNode = function(node) {
|
||||
// 空白符处理
|
||||
if (node.pre) {
|
||||
node.pre = this.pre = void 0;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].pre)
|
||||
this.pre = true;
|
||||
}
|
||||
var siblings = this.siblings(),
|
||||
len = siblings.length,
|
||||
childs = node.children;
|
||||
if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
|
||||
return siblings.pop();
|
||||
var attrs = node.attrs;
|
||||
// 替换一些标签名
|
||||
if (cfg.blockTags[node.name]) node.name = 'div';
|
||||
else if (!cfg.trustTags[node.name]) node.name = 'span';
|
||||
// 去除块标签前后空串
|
||||
if (node.name == 'div' || node.name == 'p' || node.name[0] == 't') {
|
||||
if (len > 1 && siblings[len - 2].text == ' ')
|
||||
siblings.splice(--len - 1, 1);
|
||||
if (childs.length && childs[childs.length - 1].text == ' ')
|
||||
childs.pop();
|
||||
}
|
||||
// 处理列表
|
||||
if (node.c && (node.name == 'ul' || node.name == 'ol')) {
|
||||
if ((node.attrs.style || '').includes('list-style:none')) {
|
||||
for (let i = 0, child; child = childs[i++];)
|
||||
if (child.name == 'li')
|
||||
child.name = 'div';
|
||||
} else if (node.name == 'ul') {
|
||||
var floor = 1;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == 'ul') floor++;
|
||||
if (floor != 1)
|
||||
for (let i = childs.length; i--;)
|
||||
childs[i].floor = floor;
|
||||
} else {
|
||||
for (let i = 0, num = 1, child; child = childs[i++];)
|
||||
if (child.name == 'li') {
|
||||
child.type = 'ol';
|
||||
child.num = ((num, type) => {
|
||||
if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
|
||||
if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
|
||||
if (type == 'i' || type == 'I') {
|
||||
num = (num - 1) % 99 + 1;
|
||||
var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
|
||||
ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
|
||||
res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
|
||||
if (type == 'i') return res.toLowerCase();
|
||||
return res;
|
||||
}
|
||||
return num;
|
||||
})(num++, attrs.type) + '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理表格的边框
|
||||
if (node.name == 'table') {
|
||||
var padding = attrs.cellpadding,
|
||||
spacing = attrs.cellspacing,
|
||||
border = attrs.border;
|
||||
if (node.c) {
|
||||
this.bubble();
|
||||
attrs.style = (attrs.style || '') + ';display:table';
|
||||
if (!padding) padding = 2;
|
||||
if (!spacing) spacing = 2;
|
||||
}
|
||||
if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
|
||||
if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
|
||||
if (border || padding || node.c)
|
||||
(function f(ns) {
|
||||
for (var i = 0, n; n = ns[i]; i++) {
|
||||
if (n.type == 'text') continue;
|
||||
var style = n.attrs.style || '';
|
||||
if (node.c && n.name[0] == 't') {
|
||||
n.c = 1;
|
||||
style += ';display:table-' + (n.name == 'th' || n.name == 'td' ? 'cell' : (n.name == 'tr' ? 'row' : 'row-group'));
|
||||
}
|
||||
if (n.name == 'th' || n.name == 'td') {
|
||||
if (border) style = `border:${border}px solid gray;${style}`;
|
||||
if (padding) style = `padding:${padding}px;${style}`;
|
||||
} else f(n.children || []);
|
||||
if (style) n.attrs.style = style;
|
||||
}
|
||||
})(childs)
|
||||
if (this.options.autoscroll) {
|
||||
var table = Object.assign({}, node);
|
||||
node.name = 'div';
|
||||
node.attrs = {
|
||||
style: 'overflow:scroll'
|
||||
}
|
||||
node.children = [table];
|
||||
}
|
||||
}
|
||||
this.CssHandler.pop && this.CssHandler.pop(node);
|
||||
// 自动压缩
|
||||
if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div')
|
||||
siblings[len - 1] = childs[0];
|
||||
}
|
||||
// 状态机
|
||||
MpHtmlParser.prototype.Text = function(c) {
|
||||
if (c == '<') {
|
||||
var next = this.data[this.i + 1],
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
if (isLetter(next)) {
|
||||
this.setText();
|
||||
this.start = this.i + 1;
|
||||
this.state = this.TagName;
|
||||
} else if (next == '/') {
|
||||
this.setText();
|
||||
if (isLetter(this.data[++this.i + 1])) {
|
||||
this.start = this.i + 1;
|
||||
this.state = this.EndTag;
|
||||
} else this.Comment();
|
||||
} else if (next == '!') {
|
||||
this.setText();
|
||||
this.Comment();
|
||||
}
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.Comment = function() {
|
||||
var key;
|
||||
if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
|
||||
else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
|
||||
else key = '>';
|
||||
if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
|
||||
else this.i += key.length - 1;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
MpHtmlParser.prototype.TagName = function(c) {
|
||||
if (blankChar[c]) {
|
||||
this.tagName = this.section();
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
} else if (this.isClose()) {
|
||||
this.tagName = this.section();
|
||||
this.setNode();
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.AttrName = function(c) {
|
||||
if (c == '=' || blankChar[c] || this.isClose()) {
|
||||
this.attrName = this.section();
|
||||
if (blankChar[c])
|
||||
while (blankChar[this.data[++this.i]]);
|
||||
if (this.data[this.i] == '=') {
|
||||
while (blankChar[this.data[++this.i]]);
|
||||
this.start = this.i--;
|
||||
this.state = this.AttrValue;
|
||||
} else this.setAttr();
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.AttrValue = function(c) {
|
||||
if (c == '"' || c == "'") {
|
||||
this.start++;
|
||||
if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
|
||||
this.attrVal = this.section();
|
||||
this.i++;
|
||||
} else {
|
||||
for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
|
||||
this.attrVal = this.section();
|
||||
}
|
||||
this.setAttr();
|
||||
}
|
||||
MpHtmlParser.prototype.EndTag = function(c) {
|
||||
if (blankChar[c] || c == '>' || c == '/') {
|
||||
var name = this.section().toLowerCase();
|
||||
for (var i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == name) break;
|
||||
if (i != -1) {
|
||||
var node;
|
||||
while ((node = this.STACK.pop()).name != name) this.popNode(node);
|
||||
this.popNode(node);
|
||||
} else if (name == 'p' || name == 'br')
|
||||
this.siblings().push({
|
||||
name,
|
||||
attrs: {}
|
||||
});
|
||||
this.i = this.data.indexOf('>', this.i);
|
||||
this.start = this.i + 1;
|
||||
if (this.i == -1) this.i = this.data.length;
|
||||
else this.state = this.Text;
|
||||
}
|
||||
}
|
||||
module.exports = MpHtmlParser;
|
||||
@@ -0,0 +1,93 @@
|
||||
/* 配置文件 */
|
||||
// #ifdef MP-WEIXIN
|
||||
const canIUse = wx.canIUse('editor'); // 高基础库标识,用于兼容
|
||||
// #endif
|
||||
module.exports = {
|
||||
// 出错占位图
|
||||
errorImg: null,
|
||||
// 过滤器函数
|
||||
filter: null,
|
||||
// 代码高亮函数
|
||||
highlight: null,
|
||||
// 文本处理函数
|
||||
onText: null,
|
||||
// 实体编码列表
|
||||
entities: {
|
||||
quot: '"',
|
||||
apos: "'",
|
||||
semi: ';',
|
||||
nbsp: '\xA0',
|
||||
ensp: '\u2002',
|
||||
emsp: '\u2003',
|
||||
ndash: '–',
|
||||
mdash: '—',
|
||||
middot: '·',
|
||||
lsquo: '‘',
|
||||
rsquo: '’',
|
||||
ldquo: '“',
|
||||
rdquo: '”',
|
||||
bull: '•',
|
||||
hellip: '…'
|
||||
},
|
||||
blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
|
||||
boolAttrs: makeMap('allowfullscreen,autoplay,autostart,controls,ignore,loop,muted'),
|
||||
// 块级标签,将被转为 div
|
||||
blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,section' + (
|
||||
// #ifdef MP-WEIXIN
|
||||
canIUse ? '' :
|
||||
// #endif
|
||||
',pre')),
|
||||
// 将被移除的标签
|
||||
ignoreTags: makeMap(
|
||||
'area,base,canvas,frame,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'
|
||||
// #ifdef MP-WEIXIN
|
||||
+ (canIUse ? ',rp' : '')
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
+ ',iframe'
|
||||
// #endif
|
||||
),
|
||||
// 只能被 rich-text 显示的标签
|
||||
richOnlyTags: makeMap('a,colgroup,fieldset,legend,table'
|
||||
// #ifdef MP-WEIXIN
|
||||
+ (canIUse ? ',bdi,bdo,caption,rt,ruby' : '')
|
||||
// #endif
|
||||
),
|
||||
// 自闭合的标签
|
||||
selfClosingTags: makeMap(
|
||||
'area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'
|
||||
),
|
||||
// 信任的标签
|
||||
trustTags: makeMap(
|
||||
'a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'
|
||||
// #ifdef MP-WEIXIN
|
||||
+ (canIUse ? ',bdi,bdo,caption,pre,rt,ruby' : '')
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
+ ',embed,iframe'
|
||||
// #endif
|
||||
),
|
||||
// 默认的标签样式
|
||||
userAgentStyles: {
|
||||
address: 'font-style:italic',
|
||||
big: 'display:inline;font-size:1.2em',
|
||||
blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',
|
||||
caption: 'display:table-caption;text-align:center',
|
||||
center: 'text-align:center',
|
||||
cite: 'font-style:italic',
|
||||
dd: 'margin-left:40px',
|
||||
mark: 'background-color:yellow',
|
||||
pre: 'font-family:monospace;white-space:pre;overflow:scroll',
|
||||
s: 'text-decoration:line-through',
|
||||
small: 'display:inline;font-size:0.8em',
|
||||
u: 'text-decoration:underline'
|
||||
}
|
||||
}
|
||||
|
||||
function makeMap(str) {
|
||||
var map = Object.create(null),
|
||||
list = str.split(',');
|
||||
for (var i = list.length; i--;)
|
||||
map[list[i]] = true;
|
||||
return map;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
var inline = {
|
||||
abbr: 1,
|
||||
b: 1,
|
||||
big: 1,
|
||||
code: 1,
|
||||
del: 1,
|
||||
em: 1,
|
||||
i: 1,
|
||||
ins: 1,
|
||||
label: 1,
|
||||
q: 1,
|
||||
small: 1,
|
||||
span: 1,
|
||||
strong: 1
|
||||
}
|
||||
module.exports = {
|
||||
use: function(item) {
|
||||
return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
<template>
|
||||
<view class="interlayer">
|
||||
<block v-for="(n, i) in nodes" v-bind:key="i">
|
||||
<!--图片-->
|
||||
<view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap="imgtap">
|
||||
<rich-text v-if="controls[i]!=0" :nodes="[{attrs:{src:loading&&(controls[i]||0)<2?loading:(lazyLoad&&!controls[i]?placeholder:(controls[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
|
||||
<image class="_image" :src="lazyLoad&&!controls[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad"
|
||||
:show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg"
|
||||
@error="error" />
|
||||
</view>
|
||||
<!--文本-->
|
||||
<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
|
||||
<!--#ifndef MP-BAIDU-->
|
||||
<text v-else-if="n.name=='br'">\n</text>
|
||||
<!--#endif-->
|
||||
<!--视频-->
|
||||
<view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&controls[i]==undefined" :id="n.attrs.id" :class="'_video '+(n.attrs.class||'')"
|
||||
:style="n.attrs.style" :data-i="i" @tap="_loadVideo" />
|
||||
<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||controls[i]==0"
|
||||
:controls="!n.attrs.autoplay||n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[controls[i]||0]"
|
||||
:unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" />
|
||||
<!--音频-->
|
||||
<audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author"
|
||||
:autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
|
||||
:src="n.attrs.source[controls[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio"
|
||||
@error.native="error" @play.native="play" />
|
||||
<!--链接-->
|
||||
<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
|
||||
:data-attrs="n.attrs" @tap="linkpress">
|
||||
<trees class="_span" :nodes="n.children" />
|
||||
</view>
|
||||
<!--广告-->
|
||||
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
|
||||
<!--列表-->
|
||||
<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex'">
|
||||
<view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
|
||||
<view v-else class="_ul-bef">
|
||||
<view v-if="n.floor%3==0" class="_ul-p1">█</view>
|
||||
<view v-else-if="n.floor%3==2" class="_ul-p2" />
|
||||
<view v-else class="_ul-p1" style="border-radius:50%">█</view>
|
||||
</view>
|
||||
<!--#ifdef MP-ALIPAY-->
|
||||
<view class="_li">
|
||||
<trees :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
|
||||
</view>
|
||||
<!--#endif-->
|
||||
<!--#ifndef MP-ALIPAY-->
|
||||
<trees class="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
|
||||
<!--#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, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
|
||||
<view v-for="(tr, p) in tbody.children" v-bind:key="p" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
|
||||
<trees v-if="tr.name=='td'" :nodes="tr.children" />
|
||||
<block v-else>
|
||||
<!--#ifdef MP-ALIPAY-->
|
||||
<view v-for="(td, q) in tr.children" v-bind:key="q" :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, q) in tr.children" v-bind:key="q" :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 || APP-PLUS-->
|
||||
<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
|
||||
<!--#endif-->
|
||||
<!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
|
||||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" />
|
||||
<!--#endif-->
|
||||
<!--#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" :loading="loading" />
|
||||
</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" :loading="loading" />
|
||||
<!--#endif-->
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script module="handler" lang="wxs" src="./handler.wxs"></script>
|
||||
<script>
|
||||
global.Parser = {};
|
||||
import trees from './trees'
|
||||
const errorImg = require('../libs/config.js').errorImg;
|
||||
export default {
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
name: 'trees',
|
||||
data() {
|
||||
return {
|
||||
controls: [],
|
||||
placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
|
||||
errorImg,
|
||||
loadVideo:
|
||||
// #ifdef APP-PLUS
|
||||
false
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
true
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
props: {
|
||||
nodes: Array,
|
||||
lazyLoad: Boolean,
|
||||
loading: String
|
||||
},
|
||||
mounted() {
|
||||
for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent);
|
||||
this.init();
|
||||
},
|
||||
// #ifdef APP-PLUS
|
||||
beforeDestroy() {
|
||||
this.observer && this.observer.disconnect();
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
init() {
|
||||
for (var i = this.nodes.length, n; n = this.nodes[--i];) {
|
||||
if (n.name == 'img') {
|
||||
this.top.imgList.setItem(n.attrs.i, n.attrs.src);
|
||||
// #ifdef APP-PLUS
|
||||
if (this.lazyLoad && !this.observer) {
|
||||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
|
||||
top: 500,
|
||||
bottom: 500
|
||||
});
|
||||
this.$nextTick(() => {
|
||||
this.observer.observe('._img', res => {
|
||||
if (res.intersectionRatio) {
|
||||
for (var j = this.nodes.length; j--;)
|
||||
if (this.nodes[j].name == 'img')
|
||||
this.$set(this.controls, j, 1);
|
||||
this.observer.disconnect();
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
} else if (n.name == 'video' || n.name == 'audio') {
|
||||
var ctx;
|
||||
if (n.name == 'video') {
|
||||
ctx = uni.createVideoContext(n.attrs.id
|
||||
// #ifndef MP-BAIDU
|
||||
, this
|
||||
// #endif
|
||||
);
|
||||
} else if (this.$refs[n.attrs.id])
|
||||
ctx = this.$refs[n.attrs.id][0];
|
||||
if (ctx) {
|
||||
ctx.id = n.attrs.id;
|
||||
this.top.videoContexts.push(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
// APP 上避免 video 错位需要延时渲染
|
||||
setTimeout(() => {
|
||||
this.loadVideo = true;
|
||||
}, 1000)
|
||||
// #endif
|
||||
},
|
||||
play(e) {
|
||||
var contexts = this.top.videoContexts;
|
||||
if (contexts.length > 1 && this.top.autopause)
|
||||
for (var i = contexts.length; i--;)
|
||||
if (contexts[i].id != e.currentTarget.dataset.id)
|
||||
contexts[i].pause();
|
||||
},
|
||||
imgtap(e) {
|
||||
var attrs = e.currentTarget.dataset.attrs;
|
||||
if (!attrs.ignore) {
|
||||
var preview = true,
|
||||
data = {
|
||||
id: e.target.id,
|
||||
src: attrs.src,
|
||||
ignore: () => preview = false
|
||||
};
|
||||
global.Parser.onImgtap && global.Parser.onImgtap(data);
|
||||
this.top.$emit('imgtap', data);
|
||||
if (preview) {
|
||||
var urls = this.top.imgList,
|
||||
current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
|
||||
uni.previewImage({
|
||||
current,
|
||||
urls
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
loadImg(e) {
|
||||
var i = e.currentTarget.dataset.i;
|
||||
if (this.lazyLoad && !this.controls[i]) {
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
this.$set(this.controls, i, 0);
|
||||
this.$nextTick(function() {
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
this.$set(this.controls, i, 1);
|
||||
// #endif
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
})
|
||||
// #endif
|
||||
} else if (this.loading && this.controls[i] != 2) {
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
this.$set(this.controls, i, 0);
|
||||
this.$nextTick(function() {
|
||||
// #endif
|
||||
this.$set(this.controls, i, 2);
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
linkpress(e) {
|
||||
var jump = true,
|
||||
attrs = e.currentTarget.dataset.attrs;
|
||||
attrs.ignore = () => jump = false;
|
||||
global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
|
||||
this.top.$emit('linkpress', attrs);
|
||||
if (jump) {
|
||||
// #ifdef MP
|
||||
if (attrs['app-id']) {
|
||||
return uni.navigateToMiniProgram({
|
||||
appId: attrs['app-id'],
|
||||
path: attrs.path
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
if (attrs.href) {
|
||||
if (attrs.href[0] == '#') {
|
||||
if (this.top.useAnchor)
|
||||
this.top.navigateTo({
|
||||
id: attrs.href.substring(1)
|
||||
})
|
||||
} else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openWeb(attrs.href);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
uni.setClipboardData({
|
||||
data: attrs.href,
|
||||
success: () =>
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
} else
|
||||
uni.navigateTo({
|
||||
url: attrs.href,
|
||||
fail() {
|
||||
uni.switchTab({
|
||||
url: attrs.href,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
error(e) {
|
||||
var target = e.currentTarget,
|
||||
source = target.dataset.source,
|
||||
i = target.dataset.i;
|
||||
if (source == 'video' || source == 'audio') {
|
||||
// 加载其他 source
|
||||
var index = this.controls[i] ? this.controls[i].i + 1 : 1;
|
||||
if (index < this.nodes[i].attrs.source.length)
|
||||
this.$set(this.controls, i, index);
|
||||
if (e.detail.__args__)
|
||||
e.detail = e.detail.__args__[0];
|
||||
} else if (errorImg && source == 'img') {
|
||||
this.top.imgList.setItem(target.dataset.index, errorImg);
|
||||
this.$set(this.controls, i, 3);
|
||||
}
|
||||
this.top && this.top.$emit('error', {
|
||||
source,
|
||||
target,
|
||||
errMsg: e.detail.errMsg
|
||||
});
|
||||
},
|
||||
_loadVideo(e) {
|
||||
this.$set(this.controls, e.target.dataset.i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 在这里引入自定义样式 */
|
||||
|
||||
/* 链接和图片效果 */
|
||||
._a {
|
||||
display: inline;
|
||||
padding: 1.5px 0 1.5px 0;
|
||||
color: #366092;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
._hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
._img {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
:host {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP || QUICKAPP-WEBVIEW */
|
||||
.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 || QUICKAPP-WEBVIEW*/
|
||||
._abbr,
|
||||
._b,
|
||||
._code,
|
||||
._del,
|
||||
._em,
|
||||
._i,
|
||||
._ins,
|
||||
._label,
|
||||
._q,
|
||||
._span,
|
||||
._strong,
|
||||
._sub,
|
||||
._sup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-WEIXIN || MP-QQ */
|
||||
.__bdo,
|
||||
.__bdi,
|
||||
.__ruby,
|
||||
.__rt {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
._video {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
._video::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -15px 0 0 -15px;
|
||||
content: '';
|
||||
border-color: transparent transparent transparent white;
|
||||
border-style: solid;
|
||||
border-width: 15px 0 15px 30px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="relative" :style="{height: `calc(${statusBar}px + ${height}px)`}">
|
||||
<view class="width-100b fixed1" :style="{height: `calc(${statusBar}px + ${height}px)`,background:background}">
|
||||
<view class="width-100b flex align-center fixed1 justify-center text-center" :style="{paddingTop: statusBar + 'px', height: `calc(${statusBar}px + 55px)`}">
|
||||
<text class="midetitle bold-font" :style="{color: color}">{{ title }}</text>
|
||||
<i @click="goBack" v-if="black" class="cuIcon-back font-30" style="position: absolute;left: 30rpx;" :style="{color: blackColor}"></i>
|
||||
<image @tap="Jump" v-if="isRight" style="position: absolute;right: 30rpx;" :style="'width:'+rightWidth +'rpx;height:' + rightHeight + 'rpx'"
|
||||
:src="righturl" />
|
||||
<text @tap='Jump' v-if="isRightext" class="font-30 bold-font" :style="{color: color}" style="position: absolute;right: 30rpx;">{{ rightText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
//标题
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '55'
|
||||
},
|
||||
// 标题字体颜色
|
||||
// color: {
|
||||
// type: String,
|
||||
// default: '#17181A'
|
||||
// },
|
||||
// 返回图标颜色
|
||||
// blackColor: {
|
||||
// type: String,
|
||||
// default: '#000'
|
||||
// },
|
||||
// 右侧文字颜色
|
||||
rightextColor: {
|
||||
type: String,
|
||||
default: '#17181A'
|
||||
},
|
||||
// 是否有右侧图片
|
||||
isRight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否有右侧文字
|
||||
isRightext: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右侧文字内容
|
||||
rightText: {
|
||||
type: String,
|
||||
default: '设置'
|
||||
},
|
||||
// 右侧图片路径
|
||||
righturl: {
|
||||
type: String,
|
||||
default: '/static/logo.png'
|
||||
},
|
||||
// 宽度
|
||||
rightWidth: {
|
||||
type: String,
|
||||
default: '38'
|
||||
},
|
||||
// 高度
|
||||
rightHeight: {
|
||||
type: String,
|
||||
default: '38'
|
||||
},
|
||||
// 导航背景色
|
||||
// background: {
|
||||
// type: String,
|
||||
// default: '#FFFFFF'
|
||||
// },
|
||||
isGoback: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
black: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusBar: uni.getSystemInfoSync().statusBarHeight,
|
||||
background:'',
|
||||
color:'',
|
||||
blackColor:'',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.background = this.$store.state.lightTheme.bg
|
||||
this.color = this.$store.state.lightTheme.text
|
||||
this.blackColor = this.$store.state.lightTheme.text
|
||||
// this.rightextColor = this.$store.state.lightTheme.text
|
||||
},
|
||||
methods: {
|
||||
Jump() {
|
||||
this.$emit('Jump')
|
||||
},
|
||||
goBack() {
|
||||
if (!this.isGoback) {
|
||||
this.$emit('isNoback')
|
||||
// uni.redirectTo({
|
||||
// url: '/pages/index/index'
|
||||
// })
|
||||
} else {
|
||||
this.router.goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fixed1 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 990;
|
||||
}
|
||||
.relative{
|
||||
position: relative;
|
||||
}
|
||||
.font-32{
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.midetitle{
|
||||
font-size: 16px;
|
||||
/* font-weight: 700; */
|
||||
}
|
||||
.font-40{
|
||||
font-size: 40rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
let darkTheme = {
|
||||
theme: "dark",
|
||||
text: "#FFFFFF",
|
||||
bg: "#020202",
|
||||
fu_bg:'#1A1A1A',
|
||||
fu_text:'#B7B7B7',
|
||||
selectcolor:'color-white',
|
||||
selectbg:'bj-white',
|
||||
color:'color-B7B7B7',
|
||||
selectedIconPath0:'/static/tabs/home.png',
|
||||
selectedIconPath1:'/static/tabs/heyue.png',
|
||||
selectedIconPath2:'/static/tabs/jiaoyi.png',
|
||||
selectedIconPath3:'/static/tabs/lianghua.png',
|
||||
selectedIconPath4:'/static/tabs/zichan.png',
|
||||
selectedColor:'#FFFFFF',
|
||||
backgroundColor:'#020202',
|
||||
borderStyle:'white',
|
||||
imgs:'/static/imgs/black',
|
||||
assetbg:'#1A1A1A',
|
||||
}
|
||||
|
||||
let lightTheme = {
|
||||
theme: "light",
|
||||
text: "#020202",
|
||||
bg: "#FFFFFF",
|
||||
fu_bg:'#F2F2F2',
|
||||
fu_text:'#909090',
|
||||
selectcolor:'color-020202',
|
||||
selectbg:'bj-020202',
|
||||
color:'color-909090',
|
||||
selectedIconPath0:'/static/lighttabs/home_1.png',
|
||||
selectedIconPath1:'/static/lighttabs/hangqing_1.png',
|
||||
selectedIconPath2:'/static/lighttabs/heyue_1.png',
|
||||
selectedIconPath3:'/static/lighttabs/jiaoyi_1.png',
|
||||
selectedIconPath4:'/static/lighttabs/zichan_1.png',
|
||||
selectedColor:'#020202',
|
||||
backgroundColor:'#FFFFFF',
|
||||
borderStyle:'black',
|
||||
imgs:'/static/imgs/light',
|
||||
assetbg:'#020202',
|
||||
}
|
||||
|
||||
export {
|
||||
darkTheme,
|
||||
lightTheme
|
||||
}
|
||||
Reference in New Issue
Block a user