2025.03.01~2025.03.03 工作代码提交

解决富文本在app端异常的问题
添加消息聊天页去举报的功能
账单调整
种子明细
This commit is contained in:
sx 2025-03-04 09:03:08 +08:00
parent b8527dc284
commit e5384d3995
27 changed files with 2477 additions and 627 deletions

View File

@ -299,12 +299,12 @@ const mine = {
},
/**
* 账号解冻
* 我的账单列表
* @param {Object} param
*/
getWalletBillList(param) {
return util.request({
url: '/user/walletTransaction/list',
url: '/user/walletTransaction/app/list',
data: param.data,
query: param.query,
method: 'GET',

View File

@ -0,0 +1,352 @@
/*
* HTML5 Parser By Sam Blowes
*
* Designed for HTML5 documents
*
* Original code by John Resig (ejohn.org)
* http://ejohn.org/blog/pure-javascript-html-parser/
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* ----------------------------------------------------------------------------
* License
* ----------------------------------------------------------------------------
*
* This code is triple licensed using Apache Software License 2.0,
* Mozilla Public License or GNU Public License
*
* ////////////////////////////////////////////////////////////////////////////
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* ////////////////////////////////////////////////////////////////////////////
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Simple HTML Parser.
*
* The Initial Developer of the Original Code is Erik Arvidsson.
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
* Reserved.
*
* ////////////////////////////////////////////////////////////////////////////
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ----------------------------------------------------------------------------
* Usage
* ----------------------------------------------------------------------------
*
* // Use like so:
* HTMLParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* // or to get an XML string:
* HTMLtoXML(htmlString);
*
* // or to get an XML DOM Document
* HTMLtoDOM(htmlString);
*
* // or to inject into an existing document/DOM node
* HTMLtoDOM(htmlString, document);
* HTMLtoDOM(htmlString, document.body);
*
*/
// Regular Expressions for parsing tags and attributes
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
// fixed by xxx 将 ins 标签从块级名单中移除
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
var special = makeMap('script,style');
function HTMLParser(html, handler) {
var index;
var chars;
var match;
var stack = [];
var last = html;
stack.last = function () {
return this[this.length - 1];
};
while (html) {
chars = true; // Make sure we're not in a script or style element
if (!stack.last() || !special[stack.last()]) {
// Comment
if (html.indexOf('<!--') == 0) {
index = html.indexOf('-->');
if (index >= 0) {
if (handler.comment) {
handler.comment(html.substring(4, index));
}
html = html.substring(index + 3);
chars = false;
} // end tag
} else if (html.indexOf('</') == 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
} // start tag
} else if (html.indexOf('<') == 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf('<');
var text = index < 0 ? html : html.substring(0, index);
html = index < 0 ? '' : html.substring(index);
if (handler.chars) {
handler.chars(text);
}
}
} else {
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
if (handler.chars) {
handler.chars(text);
}
return '';
});
parseEndTag('', stack.last());
}
if (html == last) {
throw 'Parse Error: ' + html;
}
last = html;
} // Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag('', stack.last());
}
}
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag('', tagName);
}
unary = empty[tagName] || !!unary;
if (!unary) {
stack.push(tagName);
}
if (handler.start) {
var attrs = [];
rest.replace(attr, function (match, name) {
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
if (!tagName) {
var pos = 0;
} // Find the closest opened tag of the same type
else {
for (var pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) {
break;
}
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--) {
if (handler.end) {
handler.end(stack[i]);
}
} // Remove the open elements from the stack
stack.length = pos;
}
}
}
function makeMap(str) {
var obj = {};
var items = str.split(',');
for (var i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
function removeDOCTYPE(html) {
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
}
function parseAttrs(attrs) {
return attrs.reduce(function (pre, attr) {
var value = attr.value;
var name = attr.name;
if (pre[name]) {
pre[name] = pre[name] + " " + value;
} else {
pre[name] = value;
}
return pre;
}, {});
}
function parseHtml(html) {
html = removeDOCTYPE(html);
var stacks = [];
var results = {
node: 'root',
children: []
};
HTMLParser(html, {
start: function start(tag, attrs, unary) {
var node = {
name: tag
};
if (attrs.length !== 0) {
node.attrs = parseAttrs(attrs);
}
if (unary) {
var parent = stacks[0] || results;
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
} else {
stacks.unshift(node);
}
},
end: function end(tag) {
var node = stacks.shift();
if (node.name !== tag) console.error('invalid state: mismatch end tag');
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
chars: function chars(text) {
var node = {
type: 'text',
text: text
};
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
comment: function comment(text) {
var node = {
node: 'comment',
text: text
};
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
});
return results.children;
}
export default parseHtml;

View File

@ -616,10 +616,17 @@ const util = {
mode: 'img',
success(res) {
if (res.code === 200) {
uni.getImageInfo({
src: item.path,
success: imageInfo => {
const result = res.data.url;
obj.success && obj.success({
value: result,
width: imageInfo.width,
height: imageInfo.height,
});
},
})
return;
}
util.alert(rs.msg);

View File

@ -35,10 +35,14 @@
})
//
const colorKey = ref('forecolor')
//
const editorWidth = ref(0)
onMounted(() => {
//
onEditorReady()
//
getEditorInfo()
})
//
@ -48,6 +52,14 @@
}).exec()
}
//
function getEditorInfo() {
const query = uni.createSelectorQuery().in(proxy);
query.select("#editor").boundingClientRect((data) => {
editorWidth.value = data.width
}).exec();
}
//
function init(html) {
editorCtx.value.setContents({
@ -55,15 +67,6 @@
})
}
//
function getEditorContents() {
editorCtx.value.getContents({
success: rs => {
return rs
}
})
}
//
function undo() {
editorCtx.value.undo()
@ -118,6 +121,8 @@
editorCtx.value.insertImage({
src: rs.value,
alt: '图像',
width: Math.min(rs.width, editorWidth.value),
extClass: 'editorImg',
})
}
})
@ -186,7 +191,6 @@
defineExpose({
init,
getEditorContents,
})
</script>
@ -196,7 +200,7 @@
<!-- 加粗 -->
<view class="item" data-name="bold">加粗</view>
<!-- 倾斜 -->
<view class="item" data-name="italic">倾斜</view>
<!-- <view class="item" data-name="italic">倾斜</view> -->
<!-- 下划线 -->
<view class="item" data-name="underline">下划线</view>
<!-- 删除线 -->
@ -208,7 +212,7 @@
<!-- 右对齐 -->
<view class="item" data-name="align" data-value="right">右对齐</view>
<!-- 两端对齐 -->
<view class="item" data-name="align" data-value="justify">两端对齐</view>
<!-- <view class="item" data-name="align" data-value="justify">两端对齐</view> -->
<!-- 清除格式 -->
<view class="item" @tap="handleRemoveFormat">清除所有格式</view>
<!-- 字体颜色 -->
@ -232,9 +236,9 @@
<!-- 重做 -->
<view class="item" @tap="redo">重做</view>
<!-- 增加缩进 -->
<view class="item" data-name="indent" data-value="+1">增加缩进</view>
<!-- <view class="item" data-name="indent" data-value="+1">增加缩进</view> -->
<!-- 减少缩进 -->
<view class="item" data-name="indent" data-value="-1">减少缩进</view>
<!-- <view class="item" data-name="indent" data-value="-1">减少缩进</view> -->
<!-- 分割线 -->
<view class="item" @tap="handleInsertDivider">分割线</view>
<!-- 插入图片 -->
@ -242,9 +246,9 @@
<!-- 大标题 -->
<view class="item" @click.stop="handleHeader">插入标题</view>
<!-- 下标 -->
<view class="item" data-name="script" data-value="sub">下标</view>
<!-- <view class="item" data-name="script" data-value="sub">下标</view> -->
<!-- 上标 -->
<view class="item" data-name="script" data-value="super">上标</view>
<!-- <view class="item" data-name="script" data-value="super">上标</view> -->
<!-- 清空 -->
<view class="item" @tap="handleClear">清空内容</view>
</view>

View File

@ -0,0 +1,271 @@
/**
* html2Json 改造来自: https://github.com/Jxck/html2json
*
*
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
import wxDiscode from './wxDiscode';
import HTMLParser from './htmlparser';
function makeMap(str) {
const obj = {};
const items = str.split(',');
for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
return obj;
}
// Block Elements - HTML 5
const block = makeMap(
'br,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'
);
// Inline Elements - HTML 5
const inline = makeMap(
'a,abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'
);
// Elements that you can, intentionally, leave open
// (and which close themselves)
const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
function removeDOCTYPE(html) {
const isDocument = /<body.*>([^]*)<\/body>/.test(html);
return isDocument ? RegExp.$1 : html;
}
function trimHtml(html) {
return html
.replace(/<!--.*?-->/gi, '')
.replace(/\/\*.*?\*\//gi, '')
.replace(/[ ]+</gi, '<')
.replace(/<script[^]*<\/script>/gi, '')
.replace(/<style[^]*<\/style>/gi, '');
}
function getScreenInfo() {
const screen = {};
wx.getSystemInfo({
success: (res) => {
screen.width = res.windowWidth;
screen.height = res.windowHeight;
},
});
return screen;
}
function html2json(html, customHandler, imageProp, host) {
// 处理字符串
html = removeDOCTYPE(html);
html = trimHtml(html);
html = wxDiscode.strDiscode(html);
// 生成node节点
const bufArray = [];
const results = {
nodes: [],
imageUrls: [],
};
const screen = getScreenInfo();
function Node(tag) {
this.node = 'element';
this.tag = tag;
this.$screen = screen;
}
HTMLParser(html, {
start(tag, attrs, unary) {
// node for this element
const node = new Node(tag);
if (bufArray.length !== 0) {
const parent = bufArray[0];
if (parent.nodes === undefined) {
parent.nodes = [];
}
}
if (block[tag]) {
node.tagType = 'block';
} else if (inline[tag]) {
node.tagType = 'inline';
} else if (closeSelf[tag]) {
node.tagType = 'closeSelf';
}
node.attr = attrs.reduce((pre, attr) => {
const {
name
} = attr;
let {
value
} = attr;
if (name === 'class') {
node.classStr = value;
}
// has multi attibutes
// make it array of attribute
if (name === 'style') {
node.styleStr = value;
}
if (value.match(/ /)) {
value = value.split(' ');
}
// if attr already exists
// merge it
if (pre[name]) {
if (Array.isArray(pre[name])) {
// already array, push to last
pre[name].push(value);
} else {
// single value, make it array
pre[name] = [pre[name], value];
}
} else {
// not exist, put it
pre[name] = value;
}
return pre;
}, {});
// 优化样式相关属性
if (node.classStr) {
node.classStr += ` ${node.tag}`;
} else {
node.classStr = node.tag;
}
if (node.tagType === 'inline') {
node.classStr += ' inline';
}
// 对img添加额外数据
if (node.tag === 'img') {
let imgUrl = node.attr.src;
imgUrl = wxDiscode.urlToHttpUrl(imgUrl, imageProp.domain);
Object.assign(node.attr, imageProp, {
src: imgUrl || '',
});
if (imgUrl) {
results.imageUrls.push(imgUrl);
}
}
// 处理a标签属性
if (node.tag === 'a') {
node.attr.href = node.attr.href || '';
}
// 处理font标签样式属性
if (node.tag === 'font') {
const fontSize = [
'x-small',
'small',
'medium',
'large',
'x-large',
'xx-large',
'-webkit-xxx-large',
];
const styleAttrs = {
color: 'color',
face: 'font-family',
size: 'font-size',
};
if (!node.styleStr) node.styleStr = '';
Object.keys(styleAttrs).forEach((key) => {
if (node.attr[key]) {
const value = key === 'size' ? fontSize[node.attr[key] - 1] : node.attr[key];
node.styleStr += `${styleAttrs[key]}: ${value};`;
}
});
}
// 临时记录source资源
if (node.tag === 'source') {
results.source = node.attr.src;
}
if (customHandler.start) {
customHandler.start(node, results);
}
if (unary) {
// if this tag doesn't have end tag
// like <img src="hoge.png"/>
// add to parents
const parent = bufArray[0] || results;
if (parent.nodes === undefined) {
parent.nodes = [];
}
parent.nodes.push(node);
} else {
bufArray.unshift(node);
}
},
end(tag) {
// merge into parent tag
const node = bufArray.shift();
if (node.tag !== tag) {
console.error('invalid state: mismatch end tag');
}
// 当有缓存source资源时于于video补上src资源
if (node.tag === 'video' && results.source) {
node.attr.src = results.source;
delete results.source;
}
if (customHandler.end) {
customHandler.end(node, results);
}
if (bufArray.length === 0) {
results.nodes.push(node);
} else {
const parent = bufArray[0];
if (!parent.nodes) {
parent.nodes = [];
}
parent.nodes.push(node);
}
},
chars(text, results) {
if (!text.trim()) return;
const node = {
node: 'text',
text,
};
if(results && results.styleStr) node.styleStr = results.styleStr
if (customHandler.chars) {
customHandler.chars(node, results);
}
if (bufArray.length === 0) {
results.nodes.push(node);
} else {
const parent = bufArray[0];
if (parent.nodes === undefined) {
parent.nodes = [];
}
parent.nodes.push(node);
}
},
});
return results;
}
export default html2json;

View File

@ -0,0 +1,156 @@
/**
*
* htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
*
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
// Regular Expressions for parsing tags and attributes
const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z0-9_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
const attr = /([a-zA-Z0-9_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
function makeMap(str) {
const obj = {};
const items = str.split(',');
for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
return obj;
}
// Empty Elements - HTML 5
const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
// Block Elements - HTML 5
const block = makeMap('address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
// Inline Elements - HTML 5
const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
// Elements that you can, intentionally, leave open
// (and which close themselves)
const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
// Attributes that have their values filled in disabled="disabled"
const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
function HTMLParser(html, handler) {
let index;
let chars;
let match;
let last = html;
const stack = [];
stack.last = () => stack[stack.length - 1];
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
let pos;
if (!tagName) {
pos = 0;
} else {
// Find the closest opened tag of the same type
tagName = tagName.toLowerCase();
for (pos = stack.length - 1; pos >= 0; pos -= 1) {
if (stack[pos] === tagName) break;
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (let i = stack.length - 1; i >= pos; i -= 1) {
if (handler.end) handler.end(stack[i]);
}
// Remove the open elements from the stack
stack.length = pos;
}
}
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag('', stack.last());
}
}
if (closeSelf[tagName] && stack.last() === tagName) {
parseEndTag('', tagName);
}
unary = empty[tagName] || !!unary;
if (!unary) stack.push(tagName);
if (handler.start) {
const attrs = [];
rest.replace(attr, function genAttr(matches, name) {
const value = arguments[2] || arguments[3] || arguments[4] || (fillAttrs[name] ? name : '');
attrs.push({
name,
value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\"'), // "
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
while (html) {
chars = true;
if (html.indexOf('</') === 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
}
// start tag
} else if (html.indexOf('<') === 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf('<');
let text = '';
while (index === 0) {
text += '<';
html = html.substring(1);
index = html.indexOf('<');
}
text += index < 0 ? html : html.substring(0, index);
html = index < 0 ? '' : html.substring(index);
if (handler.chars) handler.chars(text);
}
if (html === last) throw new Error(`Parse Error: ${html}`);
last = html;
}
// Clean up any remaining tags
parseEndTag();
}
export default HTMLParser;

View File

@ -0,0 +1,195 @@
// HTML 支持的数学符号
function strNumDiscode(str) {
str = str.replace(/&forall;/g, '∀');
str = str.replace(/&part;/g, '∂');
str = str.replace(/&exist;/g, '∃');
str = str.replace(/&empty;/g, '∅');
str = str.replace(/&nabla;/g, '∇');
str = str.replace(/&isin;/g, '∈');
str = str.replace(/&notin;/g, '∉');
str = str.replace(/&ni;/g, '∋');
str = str.replace(/&prod;/g, '∏');
str = str.replace(/&sum;/g, '∑');
str = str.replace(/&minus;/g, '');
str = str.replace(/&lowast;/g, '');
str = str.replace(/&radic;/g, '√');
str = str.replace(/&prop;/g, '∝');
str = str.replace(/&infin;/g, '∞');
str = str.replace(/&ang;/g, '∠');
str = str.replace(/&and;/g, '∧');
str = str.replace(/&or;/g, '');
str = str.replace(/&cap;/g, '∩');
str = str.replace(/&cup;/g, '');
str = str.replace(/&int;/g, '∫');
str = str.replace(/&there4;/g, '∴');
str = str.replace(/&sim;/g, '');
str = str.replace(/&cong;/g, '≅');
str = str.replace(/&asymp;/g, '≈');
str = str.replace(/&ne;/g, '≠');
str = str.replace(/&le;/g, '≤');
str = str.replace(/&ge;/g, '≥');
str = str.replace(/&sub;/g, '⊂');
str = str.replace(/&sup;/g, '⊃');
str = str.replace(/&nsub;/g, '⊄');
str = str.replace(/&sube;/g, '⊆');
str = str.replace(/&supe;/g, '⊇');
str = str.replace(/&oplus;/g, '⊕');
str = str.replace(/&otimes;/g, '⊗');
str = str.replace(/&perp;/g, '⊥');
str = str.replace(/&sdot;/g, '⋅');
return str;
}
// HTML 支持的希腊字母
function strGreeceDiscode(str) {
str = str.replace(/&Alpha;/g, 'Α');
str = str.replace(/&Beta;/g, 'Β');
str = str.replace(/&Gamma;/g, 'Γ');
str = str.replace(/&Delta;/g, 'Δ');
str = str.replace(/&Epsilon;/g, 'Ε');
str = str.replace(/&Zeta;/g, 'Ζ');
str = str.replace(/&Eta;/g, 'Η');
str = str.replace(/&Theta;/g, 'Θ');
str = str.replace(/&Iota;/g, 'Ι');
str = str.replace(/&Kappa;/g, 'Κ');
str = str.replace(/&Lambda;/g, 'Λ');
str = str.replace(/&Mu;/g, 'Μ');
str = str.replace(/&Nu;/g, 'Ν');
str = str.replace(/&Xi;/g, 'Ν');
str = str.replace(/&Omicron;/g, 'Ο');
str = str.replace(/&Pi;/g, 'Π');
str = str.replace(/&Rho;/g, 'Ρ');
str = str.replace(/&Sigma;/g, 'Σ');
str = str.replace(/&Tau;/g, 'Τ');
str = str.replace(/&Upsilon;/g, 'Υ');
str = str.replace(/&Phi;/g, 'Φ');
str = str.replace(/&Chi;/g, 'Χ');
str = str.replace(/&Psi;/g, 'Ψ');
str = str.replace(/&Omega;/g, 'Ω');
str = str.replace(/&alpha;/g, 'α');
str = str.replace(/&beta;/g, 'β');
str = str.replace(/&gamma;/g, 'γ');
str = str.replace(/&delta;/g, 'δ');
str = str.replace(/&epsilon;/g, 'ε');
str = str.replace(/&zeta;/g, 'ζ');
str = str.replace(/&eta;/g, 'η');
str = str.replace(/&theta;/g, 'θ');
str = str.replace(/&iota;/g, 'ι');
str = str.replace(/&kappa;/g, 'κ');
str = str.replace(/&lambda;/g, 'λ');
str = str.replace(/&mu;/g, 'μ');
str = str.replace(/&nu;/g, 'ν');
str = str.replace(/&xi;/g, 'ξ');
str = str.replace(/&omicron;/g, 'ο');
str = str.replace(/&pi;/g, 'π');
str = str.replace(/&rho;/g, 'ρ');
str = str.replace(/&sigmaf;/g, 'ς');
str = str.replace(/&sigma;/g, 'σ');
str = str.replace(/&tau;/g, 'τ');
str = str.replace(/&upsilon;/g, 'υ');
str = str.replace(/&phi;/g, 'φ');
str = str.replace(/&chi;/g, 'χ');
str = str.replace(/&psi;/g, 'ψ');
str = str.replace(/&omega;/g, 'ω');
str = str.replace(/&thetasym;/g, 'ϑ');
str = str.replace(/&upsih;/g, 'ϒ');
str = str.replace(/&piv;/g, 'ϖ');
str = str.replace(/&middot;/g, '·');
return str;
}
function strcharacterDiscode(str) {
// 加入常用解析
str = str.replace(/&nbsp;/g, ' ');
str = str.replace(/&ensp;/g, '');
str = str.replace(/&emsp;/g, ' ');
str = str.replace(/&quot;/g, "'");
str = str.replace(/&amp;/g, '&');
str = str.replace(/&lt;/g, '<');
str = str.replace(/&gt;/g, '>');
str = str.replace(/&#8226;/g, '•');
return str;
}
// HTML 支持的其他实体
function strOtherDiscode(str) {
str = str.replace(/&OElig;/g, 'Œ');
str = str.replace(/&oelig;/g, 'œ');
str = str.replace(/&Scaron;/g, 'Š');
str = str.replace(/&scaron;/g, 'š');
str = str.replace(/&Yuml;/g, 'Ÿ');
str = str.replace(/&fnof;/g, 'ƒ');
str = str.replace(/&circ;/g, 'ˆ');
str = str.replace(/&tilde;/g, '˜');
str = str.replace(/&ensp;/g, '');
str = str.replace(/&emsp;/g, '');
str = str.replace(/&thinsp;/g, '');
str = str.replace(/&zwnj;/g, '');
str = str.replace(/&zwj;/g, '');
str = str.replace(/&lrm;/g, '');
str = str.replace(/&rlm;/g, '');
str = str.replace(/&ndash;/g, '');
str = str.replace(/&mdash;/g, '—');
str = str.replace(/&lsquo;/g, '');
str = str.replace(/&rsquo;/g, '');
str = str.replace(/&sbquo;/g, '');
str = str.replace(/&ldquo;/g, '“');
str = str.replace(/&rdquo;/g, '”');
str = str.replace(/&bdquo;/g, '„');
str = str.replace(/&dagger;/g, '†');
str = str.replace(/&Dagger;/g, '‡');
str = str.replace(/&bull;/g, '•');
str = str.replace(/&hellip;/g, '…');
str = str.replace(/&permil;/g, '‰');
str = str.replace(/&prime;/g, '');
str = str.replace(/&Prime;/g, '″');
str = str.replace(/&lsaquo;/g, '');
str = str.replace(/&rsaquo;/g, '');
str = str.replace(/&oline;/g, '‾');
str = str.replace(/&euro;/g, '€');
str = str.replace(/&trade;/g, '™');
str = str.replace(/&larr;/g, '←');
str = str.replace(/&uarr;/g, '↑');
str = str.replace(/&rarr;/g, '→');
str = str.replace(/&darr;/g, '↓');
str = str.replace(/&harr;/g, '↔');
str = str.replace(/&crarr;/g, '↵');
str = str.replace(/&lceil;/g, '⌈');
str = str.replace(/&rceil;/g, '⌉');
str = str.replace(/&lfloor;/g, '⌊');
str = str.replace(/&rfloor;/g, '⌋');
str = str.replace(/&loz;/g, '◊');
str = str.replace(/&spades;/g, '♠');
str = str.replace(/&clubs;/g, '♣');
str = str.replace(/&hearts;/g, '♥');
str = str.replace(/&diams;/g, '♦');
str = str.replace(/&#39;/g, "'");
return str;
}
function strDiscode(str) {
str = strNumDiscode(str);
str = strGreeceDiscode(str);
str = strcharacterDiscode(str);
str = strOtherDiscode(str);
return str;
}
function urlToHttpUrl(url, domain) {
if (/^\/\//.test(url)) {
return `https:${url}`;
} else if (/^\//.test(url)) {
return `https://${domain}${url}`;
}
return url;
}
export default {
strDiscode,
urlToHttpUrl,
};

View File

@ -0,0 +1,118 @@
<template>
<!--基础元素-->
<view class="parse" :class="className" v-if="!loading">
<template v-for="(node,index) of nodes" :key="index">
<parseTemplate :node="node" />
</template>
</view>
</template>
<script>
import HtmlToJson from './libs/html2json';
import parseTemplate from './parseTemplate.vue';
export default {
name: 'wxParse',
components: {
parseTemplate,
},
props: {
loading: {
type: Boolean,
default: false,
},
className: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
noData: {
type: String,
default: '<div style="color: red;">数据不能为空</div>',
},
startHandler: {
type: Function,
default () {
return (node) => {
node.attr.class = null;
node.attr.style = null;
};
},
},
endHandler: {
type: Function,
default: null,
},
charsHandler: {
type: Function,
default: null,
},
imageProp: {
type: Object,
default () {
return {
mode: 'aspectFit',
padding: 0,
lazyLoad: false,
domain: '',
};
},
},
},
emits: ['navigate', 'preview'],
data() {
return {
imageUrls: [],
};
},
computed: {
nodes() {
const {
content,
noData,
imageProp,
startHandler,
endHandler,
charsHandler,
} = this;
const parseData = content || noData;
const customHandler = {
start: startHandler,
end: endHandler,
chars: charsHandler,
};
const results = HtmlToJson(parseData, customHandler, imageProp, this);
this.imageUrls = results.imageUrls;
console.log('result', results, customHandler)
return results.nodes;
},
},
methods: {
navigate(href, $event) {
this.$emit('navigate', href, $event);
},
preview(src, $event) {
if (!this.imageUrls.length) return;
wx.previewImage({
current: src,
urls: this.imageUrls,
});
this.$emit('preview', src, $event);
},
removeImageUrl(src) {
const {
imageUrls
} = this;
imageUrls.splice(imageUrls.indexOf(src), 1);
},
},
};
</script>
<style lang="scss">
// @import url("@/components/parse/style.scss");
</style>

View File

@ -0,0 +1,225 @@
<template>
<view class="view">
<!--判断是否是标签节点-->
<template v-if="node.node == 'element'">
<template v-if="node.tag == 'button'">
<button type="default" size="mini">
<template v-for="(node, index) of node.nodes" :key="index">
<parseTemplate :node="node" />
</template>
</button>
</template>
<!--li类型-->
<template v-else-if="node.tag == 'li'">
<view :class="node.classStr" :style="node.styleStr">
<template v-for="(item, index) of node.nodes" :key="index">
<template v-if="parentNode && parentNode.tag == 'ol'">
<parseTemplate :node="item" :parentNode="node" :textBefore="parentIndex + 1 + '. '" />
</template>
<template v-if="parentNode && parentNode.tag == 'ul'">
<parseTemplate :node="item" :parentNode="node" textBefore="·" />
</template>
</template>
</view>
</template>
<!--video类型-->
<template v-else-if="node.tag == 'video'">
<!-- <wx-parse-video :node="node" /> -->
</template>
<!--audio类型-->
<template v-else-if="node.tag == 'audio'">
<!-- <wx-parse-audio :node="node" /> -->
</template>
<!--img类型-->
<template v-else-if="node.tag == 'img'">
<wx-parse-img :node="node" />
</template>
<!--a类型-->
<template v-else-if="node.tag == 'a'">
<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
<template v-for="(item, index) of node.nodes" :key="index">
<parseTemplate :node="item" />
</template>
</view>
</template>
<!--table类型-->
<template v-else-if="node.tag == 'table'">
<view :class="node.classStr" class="table" :style="node.styleStr">
<template v-for="(item, index) of node.nodes" :key="index">
<parseTemplate :node="item" />
</template>
</view>
</template>
<!--br类型-->
<template v-else-if="node.tag == 'br'">
<text> </text>
</template>
<!--hr类型-->
<template v-else-if="node.tag == 'hr'">
<view class="hr"></view>
</template>
<!--其他标签-->
<template v-else>
<view :class="node.classStr" :style="node.styleStr">
<!-- 用来控制是否往下嵌套 -->
<template v-if="node.nodes">
<template v-for="(item, index) of node.nodes" :key="index">
<parseTemplate :node="item" :parentNode="node" :parentIndex="index" />
</template>
</template>
</view>
</template>
</template>
<!--判断是否是文本节点-->
<template v-else-if="node.node == 'text'">
<!-- 用来处理父级居中样式的问题 -->
<template v-if="parentNode && parentNode.styleStr">
<text class="text" :class="node.classStr"
:style="parentNode.styleStr">{{textBefore}}{{node.text}}</text>
</template>
<!-- 用来处理纯文本样式 -->
<template v-else>
<text class="text" :class="node.classStr">{{textBefore}}{{node.text}}</text>
</template>
</template>
</view>
</template>
<script>
import wxParseImg from './wxParseImg';
// import wxParseVideo from './wxParseVideo';
// import wxParseAudio from './wxParseAudio';
export default {
name: 'parseTemplate',
props: {
//
node: {},
//
parentNode: {},
//
parentIndex: {},
//
textBefore: {},
},
components: {
wxParseImg,
// wxParseVideo,
// wxParseAudio,
},
methods: {
wxParseATap(e) {
const {
href
} = e.currentTarget.dataset; // TODO currentTargetdataset
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== 'function') { // TODO
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>
<style lang="scss">
$fontSize: 34rpx;
//
.parse {
.text {
font-size: $fontSize;
}
.h1 .text {
font-size: 2 * $fontSize;
}
.h2 .text {
font-size: 1.5 * $fontSize;
}
.h3 .text {
font-size: 1.17 * $fontSize;
}
.h4 .text {
font-size: 1 * $fontSize;
}
.h5 .text {
font-size: 0.83 * $fontSize;
}
.h6 .text {
font-size: 0.67 * $fontSize;
}
.h1 .text,
.h2 .text,
.h3 .text,
.h4 .text,
.h5 .text,
.h6 .text,
.b,
.strong {
font-weight: bold;
}
.u .text {
text-decoration: underline,
}
.s .text {
text-decoration: line-through,
}
.i,
.cite,
.em,
.var,
.address {
font-style: italic;
}
.big {
font-size: 33rpx;
}
.small,
.sub,
.sup {
font-size: 23rpx;
}
/* #ifndef APP-NVUE */
.sub {
vertical-align: sub;
}
.sup {
vertical-align: super;
}
/* #endif */
.a {
color: deepskyblue;
}
.hr {
border-top: 2rpx solid #999;
margin: 5rpx;
}
}
</style>

View File

@ -0,0 +1,221 @@
.parse {
width: 100%;
font-family: Helvetica, sans-serif;
font-size: 30upx;
color: #666;
line-height: 1.8;
}
.parse view {
word-break: hyphenate;
}
.parse .inline {
display: inline;
margin: 0;
padding: 0;
}
.parse .div {
margin: 0;
padding: 0;
}
.parse .h1 .text {
font-size: 2em;
margin: 0.67em 0;
}
.parse .h2 .text {
font-size: 1.5em;
margin: 0.83em 0;
}
.parse .h3 .text {
font-size: 1.17em;
margin: 1em 0;
}
.parse .h4 .text {
margin: 1.33em 0;
}
.parse .h5 .text {
font-size: 0.83em;
margin: 1.67em 0;
}
.parse .h6 .text {
font-size: 0.67em;
margin: 2.33em 0;
}
.parse .h1 .text,
.parse .h2 .text,
.parse .h3 .text,
.parse .h4 .text,
.parse .h5 .text,
.parse .h6 .text,
.parse .b,
.parse .strong {
font-weight: bolder;
}
.parse .p {
margin: 1em 0;
}
.parse .i,
.parse .cite,
.parse .em,
.parse .var,
.parse .address {
font-style: italic;
}
.parse .pre,
.parse .tt,
.parse .code,
.parse .kbd,
.parse .samp {
font-family: monospace;
}
.parse .pre {
overflow: auto;
background: #f5f5f5;
padding: 16upx;
white-space: pre;
margin: 1em 0upx;
}
.parse .code {
display: inline;
background: #f5f5f5;
}
.parse .big {
font-size: 1.17em;
}
.parse .small,
.parse .sub,
.parse .sup {
font-size: 0.83em;
}
.parse .sub {
vertical-align: sub;
}
.parse .sup {
vertical-align: super;
}
.parse .s,
.parse .strike,
.parse .del {
text-decoration: line-through;
}
.parse .strong,
.parse .s {
display: inline;
}
.parse .a {
color: deepskyblue;
}
.parse .video {
text-align: center;
margin: 22upx 0;
}
.parse .video-video {
width: 100%;
}
.parse .img {
/* display: inline-block;
width: 0;
height: 0;
max-width: 100%;
overflow: hidden; */
}
.parse .blockquote {
margin: 10upx 0;
padding: 22upx 0 22upx 22upx;
font-family: Courier, Calibri, "宋体";
background: #f5f5f5;
border-left: 6upx solid #dbdbdb;
}
.parse .blockquote .p {
margin: 0;
}
.parse .ul, .parse .ol {
display: block;
margin: 1em 0;
padding-left: 33upx;
}
.parse .ol {
list-style-type: disc;
}
.parse .ol {
list-style-type: decimal;
}
.parse .ol>weixin-parse-template,.parse .ul>weixin-parse-template {
display: list-item;
align-items: baseline;
text-align: match-parent;
}
.parse .ol>.li,.parse .ul>.li {
display: list-item;
align-items: baseline;
text-align: match-parent;
}
.parse .ul .ul, .parse .ol .ul {
list-style-type: circle;
}
.parse .ol .ol .ul, .parse .ol .ul .ul, .parse .ul .ol .ul, .parse .ul .ul .ul {
list-style-type: square;
}
.parse .u {
text-decoration: underline;
}
.parse .hide {
display: none;
}
.parse .del {
display: inline;
}
.parse .figure {
overflow: hidden;
}
.parse .table {
width: 100%;
}
.parse .thead, .parse .tfoot, .parse .tr {
display: flex;
flex-direction: row;
}
.parse .tr {
width:100%;
display: flex;
border-right: 2upx solid #e0e0e0;
border-bottom: 2upx solid #e0e0e0;
}
.parse .th,
.parse .td {
display: flex;
width: 1276upx;
overflow: auto;
flex: 1;
padding: 11upx;
border-left: 2upx solid #e0e0e0;
}
.parse .td:last {
border-top: 2upx solid #e0e0e0;
}
.parse .th {
background: #f0f0f0;
border-top: 2upx solid #e0e0e0;
}

View File

@ -0,0 +1,27 @@
<template>
<!--增加audio标签支持-->
<audio
:id="node.attr.id"
:class="node.classStr"
:style="node.styleStr"
:src="node.attr.src"
:loop="node.attr.loop"
:poster="node.attr.poster"
:name="node.attr.name"
:author="node.attr.author"
controls></audio>
</template>
<script>
export default {
name: 'wxParseAudio',
props: {
node: {
type: Object,
default() {
return {};
},
},
},
};
</script>

View File

@ -0,0 +1,105 @@
<template>
<!-- -->
<image :mode="node.attr.mode" :lazy-load="node.attr.lazyLoad" :style="newStyleStr || node.styleStr"
:class="node.classStr" :data-src="node.attr.src" :src="node.attr.src" @tap="wxParseImgTap"
@load="wxParseImgLoad" />
</template>
<script>
export default {
name: 'wxParseImg',
data() {
return {
newStyleStr: '',
preview: true,
};
},
props: {
node: {
type: Object,
default () {
return {};
},
},
},
methods: {
wxParseImgTap(e) {
if (!this.preview) return;
const {
src
} = e.currentTarget.dataset;
if (!src) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== 'function') { // TODO
parent = parent.$parent;
}
parent.preview(src, e);
},
//
wxParseImgLoad(e) {
console.log('wxParseImgLoad', e)
const {
src
} = e.currentTarget.dataset;
if (!src) return;
const {
width,
height
} = e.detail;
const recal = this.wxAutoImageCal(width, height);
const {
imageheight,
imageWidth
} = recal;
const {
padding,
mode
} = this.node.attr;
const {
styleStr
} = this.node;
console.log('node.classStr', this.node.classStr)
// const imageHeightStyle = mode === 'widthFix' ? '' : `height: ${imageheight}px;`;
// this.newStyleStr = `${styleStr}; ${imageHeightStyle}; width: ${imageWidth}px; padding: 0 ${+padding || 0}px;`;
// console.log('this.newStyleStr', this.newStyleStr, this.node)
},
//
wxAutoImageCal(originalWidth, originalHeight) {
//
const {
padding
} = this.node.attr;
const windowWidth = this.node.$screen.width - (2 * padding);
const results = {};
if (originalWidth < 60 || originalHeight < 60) {
const {
src
} = this.node.attr;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== 'function') {
parent = parent.$parent;
}
parent.removeImageUrl(src);
this.preview = false;
}
//
if (originalWidth > windowWidth) {
// widthwidth
results.imageWidth = windowWidth;
results.imageheight = windowWidth * (originalHeight / originalWidth);
} else {
//
results.imageWidth = originalWidth;
results.imageheight = originalHeight;
}
return results;
},
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,15 @@
<template>
<!--增加video标签支持并循环添加-->
<view :class="node.classStr" :style="node.styleStr">
<video :class="node.classStr" class="video-video" :src="node.attr.src"></video>
</view>
</template>
<script>
export default {
name: 'wxParseVideo',
props: {
node: {},
},
};
</script>

View File

@ -20,6 +20,8 @@
import api from '@/api/index.js'
//
import util from '@/common/js/util.js'
//
import parseRichText from '@/components/public/parse/parse.vue'
//
const props = defineProps({
@ -62,6 +64,8 @@
//
}
}
//
const editorCtx = ref(null)
//
const spaceIndex = ref(0)
//
@ -84,8 +88,14 @@
return result
})
//
const userinfo = computed(() => {
return uni.$store.state.userinfo
const userinfo = computed(() => uni.$store.state.userinfo)
//
const infoRichText = computed(() => {
let result = ''
const richText = props.detail.infoRichText || ''
if (richText) result = decodeURIComponent(escape(atob(richText)))
console.log('richText', result, decodeURIComponent(escape(atob(richText))))
return result
})
onMounted(() => {
@ -95,7 +105,6 @@
getRecentOrder()
})
//
function getRecentOrder() {
api.shop.recentOrder({
@ -368,7 +377,8 @@
<!-- 商品详情 -->
<view class="content mt30">
<rich-text :nodes="detail.infoRichText || ''" />
<!-- <rich-text :nodes="infoRichText" v-if="infoRichText" /> -->
<parseRichText :imageProp="{'mode': 'widthFix',}" :content="infoRichText" />
</view>
</view>
@ -482,4 +492,10 @@
}
}
}
//
.editorImg {
width: 700rpx;
height: 400rpx;
}
</style>

View File

@ -2,8 +2,8 @@
"name" : "九亿",
"appid" : "__UNI__08B31BC",
"description" : "",
"versionName" : "1.0.9",
"versionCode" : 1009,
"versionName" : "1.0.12",
"versionCode" : 1012,
"transformPx" : false,
/* 5+App */
"app-plus" : {

View File

@ -88,7 +88,8 @@
{
"path": "pages/news/chat/chat",
"style": {
"navigationBarTitleText": "问答页"
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
@ -771,6 +772,13 @@
"style": {
"navigationBarTitleText": "登录密码"
}
},
{
"path" : "pages/index/seedLog",
"style" :
{
"navigationBarTitleText" : "流量点明细"
}
}
],

View File

@ -224,7 +224,7 @@
}
//
function navigateToPage(path) {
function link(path) {
uni.navigateTo({
url: path
})
@ -261,7 +261,7 @@
<!-- 树苗 -->
<view class="sapling bgColor mtb30 ptb15 plr30">
<view class="df">
<view class="f1 fmid">
<view class="f1 fmid" @click="link('/pages/index/seedLog')">
<image class="wh110" src="/static/sapling.png" mode="aspectFit" />
</view>
<view class="f1 fmid">
@ -280,10 +280,9 @@
</view>
<view class="df fdc jcsa f1">
<view class="button btn colourful plr30" @click="navigateToPage('/pages/index/orchard')">置换</view>
<view class="button btn colourful plr30" @click="link('/pages/index/orchard')">置换</view>
<view class="button btn colourful plr30 fmid"
@click="navigateToPage('/pages/index/durainActivation')">
<view class="button btn colourful plr30 fmid" @click="link('/pages/index/durainActivation')">
<view class="">我的榴莲果树</view>
</view>
</view>
@ -308,14 +307,14 @@
<view class="key fmid c333 f24">互转</view>
<!-- <view class="value mt5 c333 f20">销毁30%</view> -->
</view>
<view class="item ver f1 mt30" @click="navigateToPage('/pages/index/trade')">
<view class="item ver f1 mt30" @click="link('/pages/index/trade')">
<view class="key fmid c333 f24">交易</view>
<!-- <view class="value mt5 c333 f20">求购 出售</view> -->
</view>
</view>
</view>
<view class="btn plus black mt60 mlr60" @click="navigateToPage('/pages/index/dataCenter/push')">置换流量</view>
<view class="btn plus black mt60 mlr60" @click="link('/pages/index/dataCenter/push')">置换流量</view>
</view>
<view class="fill" style="height: 60rpx;"></view>

View File

@ -1,8 +1,7 @@
<script setup>
/**
* 积分变动明细
* 榴莲果变动明细
*/
import {
ref,
reactive,

View File

@ -103,11 +103,7 @@ function getList() {
}).then(rs => {
if (rs.code == 200) {
if (list.pageNum == 1) list.data.length = []
list.data.push(...rs.rows.map(item => {
item.format_videoUrl = util.format_url(item.videoUrl, 'video')
item.format_imageUrl = util.format_url(item.imageUrl, 'img')
return item
}))
list.data.push(...rs.rows)
list.total = rs.total
return
}
@ -161,16 +157,8 @@ function handleSubmit() {
util.alert('详细描述不能为空')
return
}
if (data.particulars.length > 100) {
util.alert('详细描述自述超过100')
return
}
if (!data.videoPictureUrl) {
util.alert('请上传举报图片')
return
}
if (!data.videoId) {
util.alert('请选择举报视频')
if (data.particulars.length > 500) {
util.alert('详细描述自述超过500')
return
}
@ -222,7 +210,7 @@ function videoInfo(param) {
<textarea v-model="form.particulars" placeholder="请详细填写,以提高举报成功率。" />
</view>
<view class="hint mt10 tar f20">{{ form.particulars.length }}/100</view>
<view class="hint mt10 tar f20">{{ form.particulars.length }}/500</view>
</view>
<view class="line mtb50 uploadBox">

View File

@ -0,0 +1,121 @@
<script setup>
/**
* 种子变动明细
*/
import {
ref,
reactive,
computed,
} from 'vue'
import {
onLoad,
onReachBottom,
onPullDownRefresh
} from '@dcloudio/uni-app'
import {
useStore
} from 'vuex'
//
import apex from '/components/header/apex'
import api from '@/api/index.js';
//
import util from '@/common/js/util.js'
const store = useStore()
//
const list = reactive({
data: [],
pageNum: 1,
pageSize: 20,
total: 0,
})
// id
const id = ref('')
//
const userinfo = computed(() => {
let result = store.state.userinfo
return result
})
onLoad((option) => {
if (option.id) id.value = option.id
//
getList()
})
onPullDownRefresh(() => {
//
refreshList()
})
onReachBottom(() => {
//
getMoreList()
})
//
function refreshList() {
list.homePageSize = 1
getList()
}
//
function getMoreList() {
if (list.total <= list.data.length) return
list.pageNum++
getList()
}
//
function getList() {
api.mine.getWalletBillList({
query: {
pageSize: list.pageSize,
pageNum: list.pageNum,
type: 'seed',
}
}).then(rs => {
if (rs.code == 200) {
if (list.pageNum == 1) list.data.length = 0
list.data.push(...rs.rows)
list.total = rs.total
return
}
util.alert({
content: rs.msg,
showCancel: false,
})
}).finally(() => {
//
uni.stopPullDownRefresh()
})
}
</script>
<template>
<view class="app">
<view class="list">
<view class="li" v-for="(item,index) in list.data" :key="index">
<view class="item rows ptb30 plr20 bfff">
<view class="col oh f1">
<view class="c333 f36">{{item.reason}}</view>
<view class="mt20 c666 f28">{{item.createTime}}</view>
</view>
<view class="change fs0 c333 f36">
<text>{{item.amount}}</text>
</view>
</view>
</view>
<!-- 暂无更多 -->
<view class="nomore mtb50" v-if="!list.data[0]">暂无明细~</view>
</view>
<!-- 填充 -->
<view class="fill"></view>
</view>
</template>
<style lang="scss">
//
</style>

View File

@ -97,12 +97,10 @@
onLoad(() => {
getList()
//
util.getPurse()
})
onReady(() => {
// proxy.$refs.orderDetail.open()
proxy.$refs.orderDetail.open()
})
onPullDownRefresh(() => {
@ -139,6 +137,8 @@
//
function getList() {
uni.stopPullDownRefresh()
return
durianlApi.getOrderList({
query: {
type: tabIndex.value,
@ -239,7 +239,7 @@
//
fruitAmount: form.sellNum,
//
totalPrice:form.totalPrice,
totalPrice: form.totalPrice,
//
name: `${form.first}${form.name}`,
//

View File

@ -27,7 +27,7 @@
//
const typeList = reactive([{
name: '全部',
id: '',
id: 'balance,score,fruit',
},
{
name: '余额',
@ -37,10 +37,10 @@
name: '积分',
id: 'score',
},
{
name: '种子',
id: 'seed',
},
// {
// name: '',
// id: 'seed',
// },
{
name: '榴莲果',
id: 'fruit',
@ -100,6 +100,9 @@
content: rs.msg,
showCancel: false,
})
}).finally(() => {
//
uni.stopPullDownRefresh()
})
}

View File

@ -1,10 +1,10 @@
<script setup>
/**
/**
* 聊天页面
*/
//
import TencentCloudChat from '@tencentcloud/chat';
import {
//
import TencentCloudChat from '@tencentcloud/chat';
import {
ref,
reactive,
nextTick,
@ -13,37 +13,39 @@ import {
computed,
getCurrentInstance,
watch,
} from 'vue'
// api
import api from '@/api/index.js'
//
import util from '@/common/js/util.js'
import {
} from 'vue'
// api
import api from '@/api/index.js'
//
import util from '@/common/js/util.js'
import {
onLoad,
onReady,
onPageScroll,
onUnload
} from "@dcloudio/uni-app"
} from "@dcloudio/uni-app"
//
import newsTemplate from './components/news-temp'
//
import emoji from './emoji.vue'
//
import JyVoice from './jy-voice.vue'
//
import JyPlus from './jy-plus.vue'
//
import newsTemplate from './components/news-temp'
//
import emoji from './emoji.vue'
//
import JyVoice from './jy-voice.vue'
//
import JyPlus from './jy-plus.vue'
//
import apex from '@/components/header/apex.vue'
import {
import {
useStore
} from 'vuex'
const {
} from 'vuex'
const {
proxy
} = getCurrentInstance()
const store = useStore()
} = getCurrentInstance()
const store = useStore()
//
const msg = reactive({
//
const msg = reactive({
//
id: '',
// C2C GROUP
@ -52,41 +54,43 @@ const msg = reactive({
num: '',
//
isCustomer: false,
})
//
const content = ref('')
//
const loading = ref(false)
//
const userinfo = computed(() => {
})
//
const content = ref('')
//
const loading = ref(false)
//
const userinfo = computed(() => {
let result = store.state.userinfo
return result
})
//
const list = reactive({
})
//
const list = reactive({
//
limit: 20,
//
data: [],
//
total: 0,
})
//
const top = ref(0)
//
const toolHeight = ref(0)
//
const messageItem = ref({})
// voice input emoji plus
const toolStatus = ref('input')
// video
const videoUrl = ref('')
//
const videoContext = ref(null)
//
const redPacket = reactive({})
})
//
const pageTitle = ref('')
//
const top = ref(0)
//
const toolHeight = ref(0)
//
const messageItem = ref({})
// voice input emoji plus
const toolStatus = ref('input')
// video
const videoUrl = ref('')
//
const videoContext = ref(null)
//
const redPacket = reactive({})
onLoad(option => {
onLoad(option => {
//
let title = ''
//
@ -101,9 +105,7 @@ onLoad(option => {
title = `(${option.num})${option.name}`
}
//
if (title) uni.setNavigationBarTitle({
title,
})
if (title) pageTitle.value = title
//
if (option.isCustomer) msg.isCustomer = option.isCustomer
@ -121,30 +123,30 @@ onLoad(option => {
})
})
// #endif
})
})
onReady(() => {
onReady(() => {
uni.createSelectorQuery().in(proxy).select('#tool').boundingClientRect((rect) => {
toolHeight.value = rect.height
}).exec();
//
videoContext.value = uni.createVideoContext('video')
})
})
onPageScroll((ev) => {
onPageScroll((ev) => {
onContentScroll(ev)
})
})
onUnload(() => {
onUnload(() => {
// #ifdef APP
uni.offKeyboardHeightChange(() => { })
uni.offKeyboardHeightChange(() => {})
// #endif
videoContext.value.stop()
})
})
//
function addListener() {
let onMessageReceived = function (event) {
//
function addListener() {
let onMessageReceived = function(event) {
console.log('TencentCloudChat.EVENT.MESSAGE_RECEIVED', event)
setTimeout(() => {
//
@ -158,18 +160,18 @@ function addListener() {
// #ifdef APP
uni.$chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
// #endif
}
}
//
function onContentScroll(ev) {
//
function onContentScroll(ev) {
if (ev.scrollTop == 50) getMoreHistroy()
debounce(() => {
top.value = ev.detail.scrollTop
})
}
}
//
function handleSend() {
//
function handleSend() {
//
sendMsg({
query: {
@ -185,21 +187,21 @@ function handleSend() {
content.value = ''
}
})
}
}
/**
/**
* 加号菜单发送
* @param {Object} message 消息对象
*/
function handlePlusSend(message) {
function handlePlusSend(message) {
sendMsg(message)
}
}
/**
/**
* 发送消息
* @param {Object} param
*/
function sendMsg(param) {
function sendMsg(param) {
//
let request = api.news.sendUserMsg
//
@ -226,13 +228,13 @@ function sendMsg(param) {
}).catch((rs) => {
console.log('sendMsg error:', rs);
})
}
}
/**
/**
* 打开红包详情
* @param {Object} ev
*/
function handleRedPacket(ev) {
function handleRedPacket(ev) {
messageItem.value = ev
api.news.getRedPacketInfo({
query: {
@ -250,10 +252,10 @@ function handleRedPacket(ev) {
showCancel: false,
})
})
}
}
//
function handleOpenReadPacket() {
//
function handleOpenReadPacket() {
//
if (redPacket.redStatus == false) return
//
@ -271,7 +273,7 @@ function handleOpenReadPacket() {
sendType: {
'C2C': '1',
'GROUP': '2',
}[msg.type],
} [msg.type],
}
}).then(rs => {
if (rs.code == 200) {
@ -286,38 +288,38 @@ function handleOpenReadPacket() {
showCancel: false,
})
})
}
}
// emoji
function emojiTap(val) {
// emoji
function emojiTap(val) {
content.value = content.value + val
}
}
//
function handleTool(val) {
//
function handleTool(val) {
if (toolStatus.value === val) toolStatus.value = 'input'
else toolStatus.value = val
}
}
//
function getMoreHistroy() {
//
function getMoreHistroy() {
//
if (list.total <= list.data.length) return
getHistory({
msgId: list.data[0].id
})
}
}
/**
/**
* 获取历史记录
* @param {Object} param
*/
function getHistory(param = {}) {
function getHistory(param = {}) {
// sdk
// #ifdef APP
let isReady = uni.$chat.isReady();
if (!isReady && userinfo.value.id) {
setTimeout(function () {
setTimeout(function() {
getHistory()
}, 200);
return
@ -376,10 +378,10 @@ function getHistory(param = {}) {
}).finally(() => {
loading.value = false
})
}
}
//
function scrollToBottom() {
//
function scrollToBottom() {
uni.createSelectorQuery().in(proxy).select('#scroll-content').boundingClientRect((res) => {
top.value = res.height
@ -389,71 +391,103 @@ function scrollToBottom() {
})
// console.log('top.value', top.value)
}).exec();
}
}
//
function debounce(func, wait = 500) {
//
function debounce(func, wait = 500) {
let timeout = null;
return function (...args) {
return function(...args) {
clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(this, args)
}, wait);
}
}
}
//
function onFocus() {
//
function onFocus() {
handleTool('input')
}
}
//
function voiceSend(message) {
//
function voiceSend(message) {
console.log('handlePlusSend', message)
sendMsg(message)
}
}
//
const handleScroll = (e) => {
//
const handleScroll = (e) => {
if (e.detail.scrollTop === 0) {
getHistory()
}
}
}
//
const showGhost = ref(false)
//
const ghostBox = ref({
//
const showGhost = ref(false)
//
const ghostBox = ref({
height: '0px',
duration: '0.25s'
})
})
//
function keyboardheightchange(res) {
//
function keyboardheightchange(res) {
ghostBox.value = res.detail
nextTick(() => {
showGhost.value = res.detail.height > 0 ? true : false
})
}
}
/**
/**
* 看视频
* @param {Object} item 聊天消息对象
*/
function handleViewVideo(item) {
function handleViewVideo(item) {
videoUrl.value = item.payload.videoUrl
//
videoContext.value.requestFullScreen()
}
}
//
function onScreenChange(ev) {
console.log('onScreenChange', ev)
//
function onScreenChange(ev) {
if (!ev.fullScreen) videoContext.value.pause()
}
}
/**
* 更多
* @param {Object} ev
*/
function handleMore(ev) {
const config = [{
name: '举报',
callback: rs => {
uni.navigateTo({
url: util.setUrl('/pages/index/report', {
userId: msg.id,
})
})
}
}]
//
uni.showActionSheet({
itemList: config.map(node => node.name),
success: rs => {
config[rs.tapIndex].callback()
}
})
}
</script>
<template>
<apex :title="pageTitle">
<template #right>
<view>
<uni-icons type="more-filled" size="40rpx" @click="handleMore" />
</view>
</template>
</apex>
<view class="app">
<scroll-view class="scroll-view" scroll-y :scroll-with-animation="true" :scroll-top="top"
@scroll="onContentScroll" @scrolltoupper="getMoreHistroy">
@ -570,17 +604,17 @@ function onScreenChange(ev) {
<style lang="scss" scoped>
@import './index.scss';
@import './index.scss';
//
#video {
//
#video {
position: fixed;
top: 100%;
left: 0;
}
}
//
.red-bag {
//
.red-bag {
position: relative;
width: 528rpx;
height: 60vh;
@ -712,5 +746,5 @@ function onScreenChange(ev) {
}
}
}
}
}
</style>

View File

@ -57,6 +57,7 @@
//
if (detail.infoRichText) {
form.infoRichText = decodeURIComponent(escape(atob(detail.infoRichText)))
console.log('form.infoRichText', form.infoRichText)
//
proxy.$refs.editorAreaRef.init(form.infoRichText)
}

View File

@ -94,11 +94,6 @@
if (rs.code == 200) {
//
const result = rs.data
//
if (result.infoRichText) {
result.infoRichText = decodeURIComponent(escape(atob(result.infoRichText)))
result.infoRichText = util.imgReplace(result.infoRichText)
}
Object.assign(detail, {}, result)
return
}

View File

@ -355,16 +355,6 @@ call_type 通话类型 2为视频1是音频
子账号不能登录app 并且区分身份标识
文本
加粗 倾斜 下划线 删除线 字号加大 字号变小
左对齐 居中 右对齐 两端对齐
清除格式 重做 取消重做 清空内容
字体颜色 字体选背景
添加日期
无序列表 数字列表 点列表
缩进 取消缩进 分割线 标题 反向输入
倍速播放改成1.5