Initial commit

This commit is contained in:
2025-07-04 16:18:58 +08:00
commit 2cf13f673d
770 changed files with 73394 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
export const areaCode = [
{ label: "中国", value: "86" },
{ label: "马来西亚", value: "60" },
{ label: "印度尼西亚", value: "62" },
{ label: "菲律宾", value: "63" },
{ label: "新加坡", value: "65" },
{ label: "泰国", value: "66" },
{ label: "文莱", value: "673" },
{ label: "日本", value: "81" },
{ label: "韩国", value: "82" },
{ label: "越南", value: "84" },
{ label: "朝鲜", value: "850" },
{ label: "香港(中国)", value: "852" },
{ label: "澳门(中国)", value: "853" },
{ label: "柬埔寨", value: "855" },
{ label: "老挝", value: "856" },
{ label: "台湾(中国)", value: "886" },
{ label: "孟加拉国", value: "880" },
{ label: "土耳其", value: "90" },
{ label: "印度", value: "91" },
{ label: "巴基斯坦", value: "92" },
{ label: "阿富汗", value: "93" },
{ label: "斯里兰卡", value: "94" },
{ label: "缅甸", value: "95" },
{ label: "马尔代夫", value: "960" },
{ label: "黎巴嫩", value: "961" },
{ label: "约旦", value: "962" },
{ label: "叙利亚", value: "963" },
{ label: "伊拉克", value: "964" },
{ label: "科威特", value: "965" },
{ label: "沙特阿拉伯", value: "966" },
{ label: "阿曼", value: "968" },
{ label: "以色列", value: "972" },
{ label: "巴林", value: "973" },
{ label: "卡塔尔", value: "974" },
{ label: "不丹", value: "975" },
{ label: "蒙古", value: "976" },
{ label: "尼泊尔", value: "977" },
{ label: "伊朗", value: "98" },
{ label: "塞浦路斯", value: "357" },
{ label: "巴勒斯坦", value: "970" },
{ label: "阿联酋", value: "971" },
{ label: "俄罗斯联邦", value: "7" },
{ label: "希腊", value: "30" },
{ label: "荷兰", value: "31" },
{ label: "比利时", value: "32" },
{ label: "法国", value: "33" },
{ label: "西班牙", value: "34" },
{ label: "直布罗陀", value: "350" },
{ label: "葡萄牙", value: "351" },
{ label: "卢森堡", value: "352" },
{ label: "爱尔兰", value: "353" },
{ label: "冰岛", value: "354" },
{ label: "阿尔巴尼亚", value: "355" },
{ label: "马耳他", value: "356" },
{ label: "安道尔", value: "376" },
{ label: "芬兰", value: "358" },
{ label: "保加利亚", value: "359" },
{ label: "匈牙利", value: "36" },
{ label: "德国", value: "49" },
{ label: "南斯拉夫", value: "381" },
{ label: "意大利", value: "39" },
{ label: "圣马力诺", value: "378" },
{ label: "梵蒂冈", value: "3906698" },
{ label: "罗马尼亚", value: "40" },
{ label: "瑞士", value: "41" },
{ label: "列支敦士登", value: "423" },
{ label: "奥地利", value: "43" },
{ label: "英国", value: "44" },
{ label: "丹麦", value: "45" },
{ label: "瑞典", value: "46" },
{ label: "挪威", value: "47" },
{ label: "波兰", value: "48" },
{ label: "捷克", value: "420" },
{ label: "斯洛伐克", value: "421" },
{ label: "摩纳哥", value: "377" },
{ label: "马其顿", value: "389" },
{ label: "科罗地亚", value: "385" },
{ label: "斯洛文尼亚", value: "386" },
{ label: "波斯尼亚和塞哥维那", value: "387" },
{ label: "亚美尼亚共和国", value: "374" },
{ label: "白俄罗斯共和国", value: "375" },
{ label: "格鲁吉亚共和国", value: "995" },
{ label: "哈萨克斯坦共和国", value: "7" },
{ label: "吉尔吉斯坦共和国", value: "996" },
{ label: "乌兹别克斯坦共和国", value: "998" },
{ label: "塔吉克斯坦共和国", value: "992" },
{ label: "土库曼斯坦共和国", value: "993" },
{ label: "乌克兰", value: "380" },
{ label: "立陶宛", value: "370" },
{ label: "拉脱维亚", value: "371" },
{ label: "爱沙尼亚", value: "372" },
{ label: "摩尔多瓦", value: "373" },
{ label: "埃及", value: "20" },
{ label: "摩洛哥", value: "212" },
{ label: "阿尔及利亚", value: "213" },
{ label: "突尼斯", value: "216" },
{ label: "利比亚", value: "218" },
{ label: "冈比亚", value: "220" },
{ label: "塞内加尔", value: "221" },
{ label: "毛里塔尼亚", value: "222" },
{ label: "马里", value: "223" },
{ label: "几内亚", value: "224" },
{ label: "科特迪瓦", value: "225" },
{ label: "布基拉法索", value: "226" },
{ label: "尼日尔", value: "227" },
{ label: "多哥", value: "228" },
{ label: "贝宁", value: "229" },
{ label: "毛里求斯", value: "230" },
{ label: "利比里亚", value: "231" },
{ label: "塞拉利昂", value: "232" },
{ label: "加纳", value: "233" },
{ label: "尼日利亚", value: "234" },
{ label: "乍得", value: "235" },
{ label: "中非", value: "236" },
{ label: "喀麦隆", value: "237" },
{ label: "佛得角", value: "238" },
{ label: "圣多美", value: "239" },
{ label: "普林西比", value: "239" },
];

View File

@@ -0,0 +1,44 @@
<template>
<u-picker
:show="show"
:defaultIndex="defaultIndex"
:columns="columns"
keyName="label"
@cancel="cancel"
@confirm="confirm"
/>
</template>
<script>
import { areaCode } from "./areaCode";
export default {
data() {
return {
show: false,
defaultIndex: [0],
};
},
methods: {
init() {
this.show = true;
},
confirm({ value }) {
const item = value[0];
this.$emit("chooseArea", item.value);
this.show = false;
},
cancel() {
this.show = false;
},
},
computed: {
columns() {
const list = areaCode.map((i) => {
return { label: `${i.label} +${i.value}`, value: i.value };
});
return [list];
},
},
};
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,59 @@
<template>
<view class="selected_item">
<view class="left_info">
<my-avatar
:src="source.faceURL"
:desc="source.nickname || source.showName"
:isGroup="Boolean(source.groupID)"
size="42"
/>
<text>{{ source.nickname || source.groupName || source.showName }}</text>
</view>
<view>
<u-button @click="action" plain text="移除" type="primary" />
</view>
</view>
</template>
<script>
import MyAvatar from "@/components/MyAvatar/index.vue";
export default {
name: "",
components: {
MyAvatar,
},
props: {
source: Object,
},
data() {
return {};
},
methods: {
action() {
this.$emit("removeItem");
},
},
mounted() {
console.log(this.source);
},
};
</script>
<style lang="scss" scoped>
.selected_item {
@include btwBox();
padding: 20rpx 0;
.left_info {
@include vCenterBox();
.u-avatar {
margin-right: 24rpx;
}
}
.u-button {
height: 48rpx;
}
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<view class="member_checked_desc">
<view @click="showSelected = true" class="left_info">
<view class="select_num">
<text class="text">{{ `已选择(${choosedData.length})` }}</text>
<u-icon name="arrow-up" size="14" color="#007aff" />
</view>
<view class="select_list">{{ selectedStr }}</view>
</view>
<view class="">
<u-button
:loading="comfirmLoading"
@click="clickComfirm"
:disabled="choosedData.length === 0"
type="primary"
:text="
isRemove
? '移除'
: `确定(${choosedData.length}${
maxLength > 0 ? `/${maxLength}` : ``
})`
"
/>
<u-popup round="24" :show="showSelected" mode="bottom" @close="close">
<view class="selected_container">
<view class="top_desc">
<text>{{ `已选择(${choosedData.length})` }}</text>
<text @click="close" class="comfirm_text">确认</text>
</view>
<u-list class="selected_list">
<u-list-item
v-for="item in choosedData"
:key="item.userID || item.groupID"
>
<selected-member @removeItem="removeItem(item)" :source="item" />
</u-list-item>
</u-list>
</view>
</u-popup>
</view>
</view>
</template>
<script>
import SelectedMember from "./SelectedMember.vue";
export default {
name: "ChooseIndexFooter",
components: {
SelectedMember,
},
props: {
isRemove: Boolean,
choosedData: Array,
comfirmLoading: Boolean,
maxLength: Number,
},
data() {
return {
showSelected: false,
showConfirmModal: false,
};
},
computed: {
selectedStr() {
return this.choosedData
.map((item) => item.nickname || item.showName || item.groupName)
.join("、");
},
},
methods: {
close() {
this.showSelected = false;
},
removeItem(item) {
this.$emit("removeItem", item);
},
clickComfirm() {
this.$emit("confirm");
},
},
};
</script>
<style lang="scss" scoped>
.member_checked_desc {
@include btwBox();
background-color: #fff;
align-items: flex-start;
padding: 24rpx 44rpx 0;
height: 60px;
max-height: 60px;
box-shadow: 0px -1px 4px 1px rgba(0, 0, 0, 0.04);
.left_info {
@include colBox(false);
.select_num {
display: flex;
align-items: center;
color: $uni-color-primary;
}
.select_list {
font-size: 24rpx;
color: #8e9ab0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 50vw;
}
.text {
font-size: 30rpx;
margin-right: 24rpx;
}
}
.u-button {
background-color: #0089ff;
height: 40px;
margin: 0;
}
}
.selected_container {
padding: 44rpx;
.top_desc {
@include btwBox();
margin-bottom: 20rpx;
.comfirm_text {
color: $uni-color-primary;
margin-right: 24rpx;
}
}
.selected_list {
height: 60vh !important;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<u-index-list
@scrolltolower="scrolltolower"
class="user_list"
:style="{ height: height }"
:index-list="indexList"
>
<template v-for="(item, index) in itemArr">
<u-index-item :key="index">
<u-index-anchor
class="user_anchor"
:text="indexList[index]"
></u-index-anchor>
<user-item
@itemClick="itemClick"
@updateCheck="updateCheck"
:checked="checkedIDList.includes(cell.userID)"
:disabled="disabledIDList.includes(cell.userID)"
:checkVisible="showCheck"
v-for="cell in item"
:item="cell"
:key="cell.userID"
/>
</u-index-item>
</template>
</u-index-list>
</template>
<script>
import UserItem from "../UserItem/index.vue";
export default {
name: "ChooseIndexList",
components: {
UserItem,
},
props: {
height: {
type: String,
default: "0px",
},
indexList: {
type: Array,
default: () => [],
},
itemArr: {
type: Array,
default: () => [],
},
checkedIDList: {
type: Array,
default: () => [],
},
disabledIDList: {
type: Array,
default: () => [],
},
showCheck: {
type: Boolean,
default: false,
},
},
data() {
return {};
},
methods: {
itemClick(item) {
this.$emit("itemClick", item);
},
updateCheck(item) {
this.$emit("updateCheck", item);
},
scrolltolower() {
this.$emit("scrolltolower");
},
},
};
</script>
<style scoped lang="scss">
.user_list {
flex: 1;
/deep/uni-scroll-view {
max-height: 100% !important;
}
}
.user_anchor {
background-color: #f8f8f8 !important;
border: none !important;
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<u-navbar :title="title" placeholder class="custom_nav_bar">
<template slot="left">
<slot name="left">
<view class="u-nav-slot">
<img
@click="leftClick"
class="back_icon"
width="12"
height="20"
src="static/images/common_left_arrow.png"
alt=""
srcset=""
/>
</view>
</slot>
</template>
<template slot="center">
<slot name="center"></slot>
</template>
<template slot="right">
<slot name="more">
<view @click="rightClick" v-if="more" class="u-nav-slot">
<u-icon
class="more_dot"
name="more-dot-fill"
size="23"
color="#0C1C33"
></u-icon>
</view>
</slot>
</template>
</u-navbar>
</template>
<script>
export default {
name: "",
components: {},
props: {
title: {
type: String,
},
more: {
type: Boolean,
default: false,
},
route: {
type: Boolean,
default: true,
},
},
data() {
return {};
},
methods: {
leftClick() {
if (this.route) {
uni.navigateBack();
}
this.$emit("leftClick");
},
rightClick() {
this.$emit("rightClick");
},
},
};
</script>
<style lang="scss" scoped>
.custom_nav_bar {
/deep/ .u-navbar__content__left {
padding: 0;
}
/deep/ .u-navbar__content__right {
padding: 0;
}
.back_icon {
padding: 24rpx;
margin-left: 20rpx;
}
.more_dot {
padding: 24rpx;
margin-right: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<u-avatar
@longpress="longpress"
@click="click"
@onError="errorHandle"
:src="getAvatarUrl"
:text="avatarText"
bgColor="#0089FF"
:defaultUrl="getDdefaultUrl"
:shape="shape"
:size="size"
mode="aspectFill"
font-size="14"
>
</u-avatar>
</template>
<script>
import defaultGroupIcon from "static/images/contact_my_group.png";
import defaultNotifyIcon from "static/images/default_notify_icon.png";
export default {
name: "MyAvatar",
props: {
src: String,
shape: {
type: String,
default: "square",
},
size: {
type: String,
default: "40",
},
isGroup: {
type: Boolean,
default: false,
},
isNotify: {
type: Boolean,
default: false,
},
desc: String,
},
data() {
return {
avatarText: undefined,
};
},
computed: {
getAvatarUrl() {
if (this.src) {
return this.src;
}
if (this.isGroup) {
return defaultGroupIcon;
}
if (this.isNotify) {
return defaultNotifyIcon;
}
this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
return "";
},
getDdefaultUrl() {
return this.isGroup ? defaultGroupIcon : undefined;
},
},
methods: {
errorHandle() {
this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
},
redirectShow() {
if (this.avatarText) {
this.avatarText = undefined;
}
},
click() {
this.$emit("click");
},
longpress() {
this.$emit("longpress");
},
},
watch: {
src() {
this.redirectShow();
},
desc() {
this.redirectShow();
},
},
};
</script>
<style></style>

View File

@@ -0,0 +1,84 @@
<template>
<view
@click="onClick"
class="setting_item"
:class="{ setting_item_border: border }"
>
<text :style="{ color: danger ? '#FF381F' : '$uni-text-color' }">{{
title
}}</text>
<u-switch
:loading="loading"
@change="onChange"
:asyncChange="true"
v-if="is_switch"
size="20"
:value="switchValue"
/>
<view v-else class="setting_right">
<slot></slot>
<u-icon v-if="arrow" name="arrow-right" color="#999" size="18" />
</view>
</view>
</template>
<script>
export default {
name: "",
components: {},
props: {
title: String,
danger: {
type: Boolean,
default: false,
},
is_switch: {
type: Boolean,
default: false,
},
switchValue: {
type: Boolean,
default: false,
},
loading: {
type: Boolean,
default: false,
},
border: {
type: Boolean,
default: true,
},
arrow: {
type: Boolean,
default: true,
},
},
data() {
return {};
},
methods: {
onClick() {
this.$emit("click");
},
onChange(value) {
this.$emit("switch", value);
},
},
};
</script>
<style lang="scss" scoped>
.setting_item {
@include btwBox();
padding: 24rpx 36rpx;
color: $uni-text-color;
.setting_right {
@include vCenterBox();
}
&_border {
border-bottom: 1px solid rgba(153, 153, 153, 0.2);
}
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<view @click="clickItem" class="user_item">
<view
v-if="checkVisible"
class="check_wrap"
:class="{ check_wrap_active: checked, check_wrap_disabled: disabled }"
>
<u-icon v-show="checked" name="checkbox-mark" size="12" color="#fff" />
</view>
<my-avatar
:src="item.faceURL"
:desc="item.remark || item.nickname || item.showName"
:isGroup="item.groupName !== undefined || isGroupConversation"
size="42"
/>
<view class="user_item_details">
<text class="user_name">{{
item.remark || item.nickname || item.groupName || item.showName
}}</text>
<text v-if="item.roleLevel === 100" class="user_role">群主</text>
<text v-if="item.roleLevel === 60" class="user_role admin_role"
>管理员</text
>
<!-- <view class="bottom_line" /> -->
</view>
<slot name="action"></slot>
</view>
</template>
<script>
import MyAvatar from "@/components/MyAvatar/index.vue";
import { SessionType } from "openim-uniapp-polyfill";
export default {
name: "UserItem",
components: {
MyAvatar,
},
props: {
checkVisible: {
type: Boolean,
default: false,
},
checked: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
item: Object,
},
data() {
return {};
},
computed: {
isGroupConversation() {
return this.item.conversationType === SessionType.WorkingGroup;
},
},
methods: {
clickItem() {
if (!this.disabled) {
this.$emit(this.checkVisible ? "updateCheck" : "itemClick", this.item);
}
},
},
};
</script>
<style lang="scss" scoped>
.user_item {
@include vCenterBox();
padding: 24rpx 44rpx;
color: $uni-text-color;
position: relative;
.check_wrap {
@include centerBox();
box-sizing: border-box;
width: 40rpx;
min-width: 40rpx;
height: 40rpx;
min-height: 40rpx;
border: 2px solid #979797;
border-radius: 50%;
margin-top: 16rpx;
margin-right: 24rpx;
&_active {
background-color: #1d6bed;
border: none;
}
&_disabled {
background-color: #c8c9cc;
}
}
&_details {
@include vCenterBox();
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-left: 24rpx;
width: 100%;
position: relative;
.bottom_line {
height: 1px;
width: 100%;
background-color: #f0f0f0;
position: absolute;
bottom: -44rpx;
}
.user_name {
@include nomalEllipsis();
max-width: 60%;
color: $uni-text-color;
}
.user_role {
font-size: 34rpx;
// background-color: #f4da9a;
// color: #FF8C00;
padding: 8rpx 24rpx;
border-radius: 24rpx;
margin-left: 24rpx;
color: $u-tips-color;
}
.admin_role {
color: $u-tips-color;
// background-color: #A2C9F8;
// color: #2691ED;
}
}
}
.u-list-item:last-child {
.bottom_line {
height: 0;
}
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<!-- '<audio/>' 组件不再维护建议使用能力更强的 'uni.createInnerAudioContext' 接口 有时间再改-->
<!--增加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,95 @@
<template>
<image
:mode="node.attr.mode"
:lazy-load="node.attr.lazyLoad"
:class="node.classStr"
:style="newStyleStr || node.styleStr"
:data-src="node.attr.src"
:src="node.attr.src"
@tap="wxParseImgTap"
@load="wxParseImgLoad"
/>
</template>
<script>
export default {
name: "wxParseImg",
data() {
return {
newStyleStr: "",
preview: false,
};
},
inject: ["parseWidth"],
mounted() {},
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) {
const { src } = e.currentTarget.dataset;
if (!src) return;
let { width, height } = e.mp.detail;
const recal = this.wxAutoImageCal(width, height);
const { imageheight, imageWidth } = recal;
const { padding, mode } = this.node.attr; //删除padding
// const { mode } = this.node.attr;
const { styleStr } = this.node;
const imageHeightStyle =
mode === "widthFix" ? "" : `height: ${imageheight}px;`;
this.newStyleStr = `${styleStr}; ${imageHeightStyle}; width: ${imageWidth}px; padding: 0 ${+padding}px;`; //删除padding
// this.newStyleStr = `${styleStr}; ${imageHeightStyle}; width: ${imageWidth}px;`;
},
// 计算视觉优先的图片宽高
wxAutoImageCal(originalWidth, originalHeight) {
// 获取图片的原始长宽
const windowWidth = this.parseWidth.value;
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) {
// 在图片width大于手机屏幕width时候
results.imageWidth = windowWidth;
results.imageheight = windowWidth * (originalHeight / originalWidth);
} else {
// 否则展示原来的数据
results.imageWidth = originalWidth;
results.imageheight = originalHeight;
}
return results;
},
},
};
</script>

View File

@@ -0,0 +1,59 @@
<template>
<div class="tablebox">
<rich-text
:nodes="nodes"
:class="node.classStr"
:style="'user-select:' + parseSelect"
></rich-text>
</div>
</template>
<script>
export default {
name: "wxParseTable",
props: {
node: {
type: Object,
default() {
return {};
},
},
},
inject: ["parseSelect"],
data() {
return {
nodes: [],
};
},
mounted() {
this.nodes = this.loadNode([this.node]);
},
methods: {
loadNode(node) {
let obj = [];
for (let children of node) {
if (children.node == "element") {
let t = {
name: children.tag,
attrs: {
class: children.classStr,
// style: children.styleStr,
},
children: children.nodes ? this.loadNode(children.nodes) : [],
};
obj.push(t);
} else if (children.node == "text") {
obj.push({
type: "text",
text: children.text,
});
}
}
return obj;
},
},
};
</script>
<style>
@import url("../parse.css");
</style>

View File

@@ -0,0 +1,122 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
// #ifdef APP-PLUS | H5
import wxParseTemplate from "./wxParseTemplate0";
// #endif
// #ifdef MP
import wxParseTemplate from "./wxParseTemplate1";
// #endif
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
// #ifdef APP-PLUS | H5
name: "wxParseTemplate",
// #endif
// #ifdef MP
name: "wxParseTemplate0",
// #endif
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset; // TODO currentTarget才有dataset
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
// TODO 遍历获取父节点执行方法
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate2";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate1",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate11";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate10",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,121 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<rich-text
:nodes="node"
:class="node.classStr"
:style="'user-select:' + parseSelect"
></rich-text>
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<rich-text
:nodes="node"
:class="node.classStr"
:style="'user-select:' + parseSelect"
></rich-text>
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<rich-text
:nodes="node"
:class="node.classStr"
:style="'user-select:' + parseSelect"
></rich-text>
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img :node="node" v-else-if="node.tag == 'img'" />
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<rich-text
:nodes="node"
:class="node.classStr"
:style="'user-select:' + parseSelect"
></rich-text>
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate11",
props: {
node: {},
},
components: {
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate3";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate2",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate4";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate3",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate5";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate4",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate6";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate5",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate7";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate6",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate8";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate7",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate9";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate8",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

@@ -0,0 +1,111 @@
<template>
<!--判断是否是标签节点-->
<block v-if="node.node == 'element'">
<!--button类型-->
<button
v-if="node.tag == 'button'"
type="default"
size="mini"
:class="node.classStr"
:style="node.styleStr"
>
<wx-parse-template :node="node" />
</button>
<!--a类型-->
<view
v-else-if="node.tag == 'a'"
@click="wxParseATap(node.attr, $event)"
:class="node.classStr"
:data-href="node.attr.href"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--li类型-->
<view
v-else-if="node.tag == 'li'"
:class="node.classStr"
:style="node.styleStr"
>
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
<!--table类型-->
<wx-parse-table
v-else-if="node.tag == 'table'"
:class="node.classStr"
:style="node.styleStr"
:node="node"
/>
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'" />
<!-- #endif -->
<!--video类型-->
<wx-parse-video :node="node" v-else-if="node.tag == 'video'" />
<!--audio类型-->
<wx-parse-audio :node="node" v-else-if="node.tag == 'audio'" />
<!--img类型-->
<wx-parse-img
:node="node"
v-else-if="node.tag == 'img'"
:style="node.styleStr"
/>
<!--其他标签-->
<view v-else :class="node.classStr" :style="node.styleStr">
<block v-for="(node, index) of node.nodes" :key="index">
<wx-parse-template :node="node" />
</block>
</view>
</block>
<!--判断是否是文本节点-->
<block v-else-if="node.node == 'text'">{{ node.text }}</block>
</template>
<script>
import wxParseTemplate from "./wxParseTemplate10";
import wxParseImg from "./wxParseImg";
import wxParseVideo from "./wxParseVideo";
import wxParseAudio from "./wxParseAudio";
import wxParseTable from "./wxParseTable";
export default {
name: "wxParseTemplate9",
props: {
node: {},
},
components: {
wxParseTemplate,
wxParseImg,
wxParseVideo,
wxParseAudio,
wxParseTable,
},
methods: {
wxParseATap(attr, e) {
const { href } = e.currentTarget.dataset;
if (!href) return;
let parent = this.$parent;
while (!parent.preview || typeof parent.preview !== "function") {
parent = parent.$parent;
}
parent.navigate(href, e, attr);
},
},
};
</script>

View File

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

View File

@@ -0,0 +1,266 @@
/**
* 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) {
if (!text.trim()) return;
const node = {
node: "text",
text,
};
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,170 @@
/**
*
* 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,227 @@
// HTML 支持的数学符号
function strNumDiscode(str) {
str = str.replace(/&forall;|&#8704;|&#x2200;/g, "∀");
str = str.replace(/&part;|&#8706;|&#x2202;/g, "∂");
str = str.replace(/&exist;|&#8707;|&#x2203;/g, "∃");
str = str.replace(/&empty;|&#8709;|&#x2205;/g, "∅");
str = str.replace(/&nabla;|&#8711;|&#x2207;/g, "∇");
str = str.replace(/&isin;|&#8712;|&#x2208;/g, "∈");
str = str.replace(/&notin;|&#8713;|&#x2209;/g, "∉");
str = str.replace(/&ni;|&#8715;|&#x220b;/g, "∋");
str = str.replace(/&prod;|&#8719;|&#x220f;/g, "∏");
str = str.replace(/&sum;|&#8721;|&#x2211;/g, "∑");
str = str.replace(/&minus;|&#8722;|&#x2212;/g, "");
str = str.replace(/&lowast;|&#8727;|&#x2217;/g, "");
str = str.replace(/&radic;|&#8730;|&#x221a;/g, "√");
str = str.replace(/&prop;|&#8733;|&#x221d;/g, "∝");
str = str.replace(/&infin;|&#8734;|&#x221e;/g, "∞");
str = str.replace(/&ang;|&#8736;|&#x2220;/g, "∠");
str = str.replace(/&and;|&#8743;|&#x2227;/g, "∧");
str = str.replace(/&or;|&#8744;|&#x2228;/g, "");
str = str.replace(/&cap;|&#8745;|&#x2229;/g, "∩");
str = str.replace(/&cup;|&#8746;|&#x222a;/g, "");
str = str.replace(/&int;|&#8747;|&#x222b;/g, "∫");
str = str.replace(/&there4;|&#8756;|&#x2234;/g, "∴");
str = str.replace(/&sim;|&#8764;|&#x223c;/g, "");
str = str.replace(/&cong;|&#8773;|&#x2245;/g, "≅");
str = str.replace(/&asymp;|&#8776;|&#x2248;/g, "≈");
str = str.replace(/&ne;|&#8800;|&#x2260;/g, "≠");
str = str.replace(/&le;|&#8804;|&#x2264;/g, "≤");
str = str.replace(/&ge;|&#8805;|&#x2265;/g, "≥");
str = str.replace(/&sub;|&#8834;|&#x2282;/g, "⊂");
str = str.replace(/&sup;|&#8835;|&#x2283;/g, "⊃");
str = str.replace(/&nsub;|&#8836;|&#x2284;/g, "⊄");
str = str.replace(/&sube;|&#8838;|&#x2286;/g, "⊆");
str = str.replace(/&supe;|&#8839;|&#x2287;/g, "⊇");
str = str.replace(/&oplus;|&#8853;|&#x2295;/g, "⊕");
str = str.replace(/&otimes;|&#8855;|&#x2297;/g, "⊗");
str = str.replace(/&perp;|&#8869;|&#x22a5;/g, "⊥");
str = str.replace(/&sdot;|&#8901;|&#x22c5;/g, "⋅");
return str;
}
// HTML 支持的希腊字母
function strGreeceDiscode(str) {
str = str.replace(/&Alpha;|&#913;|&#x391;/g, "Α");
str = str.replace(/&Beta;|&#914;|&#x392;/g, "Β");
str = str.replace(/&Gamma;|&#915;|&#x393;/g, "Γ");
str = str.replace(/&Delta;|&#916;|&#x394;/g, "Δ");
str = str.replace(/&Epsilon;|&#917;|&#x395;/g, "Ε");
str = str.replace(/&Zeta;|&#918;|&#x396;/g, "Ζ");
str = str.replace(/&Eta;|&#919;|&#x397;/g, "Η");
str = str.replace(/&Theta;|&#920;|&#x398;/g, "Θ");
str = str.replace(/&Iota;|&#921;|&#x399;/g, "Ι");
str = str.replace(/&Kappa;|&#922;|&#x39a;/g, "Κ");
str = str.replace(/&Lambda;|&#923;|&#x39b;/g, "Λ");
str = str.replace(/&Mu;|&#924;|&#x39c;/g, "Μ");
str = str.replace(/&Nu;|&#925;|&#x39d;/g, "Ν");
str = str.replace(/&Xi;|&#925;|&#x39d;/g, "Ν");
str = str.replace(/&Omicron;|&#927;|&#x39f;/g, "Ο");
str = str.replace(/&Pi;|&#928;|&#x3a0;/g, "Π");
str = str.replace(/&Rho;|&#929;|&#x3a1;/g, "Ρ");
str = str.replace(/&Sigma;|&#931;|&#x3a3;/g, "Σ");
str = str.replace(/&Tau;|&#932;|&#x3a4;/g, "Τ");
str = str.replace(/&Upsilon;|&#933;|&#x3a5;/g, "Υ");
str = str.replace(/&Phi;|&#934;|&#x3a6;/g, "Φ");
str = str.replace(/&Chi;|&#935;|&#x3a7;/g, "Χ");
str = str.replace(/&Psi;|&#936;|&#x3a8;/g, "Ψ");
str = str.replace(/&Omega;|&#937;|&#x3a9;/g, "Ω");
str = str.replace(/&alpha;|&#945;|&#x3b1;/g, "α");
str = str.replace(/&beta;|&#946;|&#x3b2;/g, "β");
str = str.replace(/&gamma;|&#947;|&#x3b3;/g, "γ");
str = str.replace(/&delta;|&#948;|&#x3b4;/g, "δ");
str = str.replace(/&epsilon;|&#949;|&#x3b5;/g, "ε");
str = str.replace(/&zeta;|&#950;|&#x3b6;/g, "ζ");
str = str.replace(/&eta;|&#951;|&#x3b7;/g, "η");
str = str.replace(/&theta;|&#952;|&#x3b8;/g, "θ");
str = str.replace(/&iota;|&#953;|&#x3b9;/g, "ι");
str = str.replace(/&kappa;|&#954;|&#x3ba;/g, "κ");
str = str.replace(/&lambda;|&#955;|&#x3bb;/g, "λ");
str = str.replace(/&mu;|&#956;|&#x3bc;/g, "μ");
str = str.replace(/&nu;|&#957;|&#x3bd;/g, "ν");
str = str.replace(/&xi;|&#958;|&#x3be;/g, "ξ");
str = str.replace(/&omicron;|&#959;|&#x3bf;/g, "ο");
str = str.replace(/&pi;|&#960;|&#x3c0;/g, "π");
str = str.replace(/&rho;|&#961;|&#x3c1;/g, "ρ");
str = str.replace(/&sigmaf;|&#962;|&#x3c2;/g, "ς");
str = str.replace(/&sigma;|&#963;|&#x3c3;/g, "σ");
str = str.replace(/&tau;|&#964;|&#x3c4;/g, "τ");
str = str.replace(/&upsilon;|&#965;|&#x3c5;/g, "υ");
str = str.replace(/&phi;|&#966;|&#x3c6;/g, "φ");
str = str.replace(/&chi;|&#967;|&#x3c7;/g, "χ");
str = str.replace(/&psi;|&#968;|&#x3c8;/g, "ψ");
str = str.replace(/&omega;|&#969;|&#x3c9;/g, "ω");
str = str.replace(/&thetasym;|&#977;|&#x3d1;/g, "ϑ");
str = str.replace(/&upsih;|&#978;|&#x3d2;/g, "ϒ");
str = str.replace(/&piv;|&#982;|&#x3d6;/g, "ϖ");
str = str.replace(/&middot;|&#183;|&#xb7;/g, "·");
return str;
}
function strcharacterDiscode(str) {
// 加入常用解析
// str = str.replace(/&nbsp;|&#32;|&#x20;/g, "&nbsp;");
// str = str.replace(/&ensp;|&#8194;|&#x2002;/g, '&ensp;');
// str = str.replace(/&#12288;|&#x3000;/g, '<span class=\'spaceshow\'> </span>');
// str = str.replace(/&emsp;|&#8195;|&#x2003;/g, '&emsp;');
// str = str.replace(/&quot;|&#34;|&#x22;/g, "\"");
// str = str.replace(/&apos;|&#39;|&#x27;/g, "&apos;");
// str = str.replace(/&acute;|&#180;|&#xB4;/g, "´");
// str = str.replace(/&times;|&#215;|&#xD7;/g, "×");
// str = str.replace(/&divide;|&#247;|&#xF7;/g, "÷");
// str = str.replace(/&amp;|&#38;|&#x26;/g, '&amp;');
// str = str.replace(/&lt;|&#60;|&#x3c;/g, '&lt;');
// str = str.replace(/&gt;|&#62;|&#x3e;/g, '&gt;');
str = str.replace(/&nbsp;|&#32;|&#x20;/g, "<span class='spaceshow'> </span>");
str = str.replace(
/&ensp;|&#8194;|&#x2002;/g,
"<span class='spaceshow'></span>",
);
str = str.replace(/&#12288;|&#x3000;/g, "<span class='spaceshow'> </span>");
str = str.replace(
/&emsp;|&#8195;|&#x2003;/g,
"<span class='spaceshow'></span>",
);
str = str.replace(/&quot;|&#34;|&#x22;/g, '"');
str = str.replace(/&quot;|&#39;|&#x27;/g, "'");
str = str.replace(/&acute;|&#180;|&#xB4;/g, "´");
str = str.replace(/&times;|&#215;|&#xD7;/g, "×");
str = str.replace(/&divide;|&#247;|&#xF7;/g, "÷");
str = str.replace(/&amp;|&#38;|&#x26;/g, "&");
str = str.replace(/&lt;|&#60;|&#x3c;/g, "<");
str = str.replace(/&gt;|&#62;|&#x3e;/g, ">");
return str;
}
// HTML 支持的其他实体
function strOtherDiscode(str) {
str = str.replace(/&OElig;|&#338;|&#x152;/g, "Œ");
str = str.replace(/&oelig;|&#339;|&#x153;/g, "œ");
str = str.replace(/&Scaron;|&#352;|&#x160;/g, "Š");
str = str.replace(/&scaron;|&#353;|&#x161;/g, "š");
str = str.replace(/&Yuml;|&#376;|&#x178;/g, "Ÿ");
str = str.replace(/&fnof;|&#402;|&#x192;/g, "ƒ");
str = str.replace(/&circ;|&#710;|&#x2c6;/g, "ˆ");
str = str.replace(/&tilde;|&#732;|&#x2dc;/g, "˜");
str = str.replace(
/&thinsp;|$#8201;|&#x2009;/g,
"<span class='spaceshow'></span>",
);
str = str.replace(
/&zwnj;|&#8204;|&#x200C;/g,
"<span class='spaceshow'></span>",
);
str = str.replace(
/&zwj;|$#8205;|&#x200D;/g,
"<span class='spaceshow'></span>",
);
str = str.replace(
/&lrm;|$#8206;|&#x200E;/g,
"<span class='spaceshow'></span>",
);
str = str.replace(
/&rlm;|&#8207;|&#x200F;/g,
"<span class='spaceshow'></span>",
);
str = str.replace(/&ndash;|&#8211;|&#x2013;/g, "");
str = str.replace(/&mdash;|&#8212;|&#x2014;/g, "—");
str = str.replace(/&lsquo;|&#8216;|&#x2018;/g, "");
str = str.replace(/&rsquo;|&#8217;|&#x2019;/g, "");
str = str.replace(/&sbquo;|&#8218;|&#x201a;/g, "");
str = str.replace(/&ldquo;|&#8220;|&#x201c;/g, "“");
str = str.replace(/&rdquo;|&#8221;|&#x201d;/g, "”");
str = str.replace(/&bdquo;|&#8222;|&#x201e;/g, "„");
str = str.replace(/&dagger;|&#8224;|&#x2020;/g, "†");
str = str.replace(/&Dagger;|&#8225;|&#x2021;/g, "‡");
str = str.replace(/&bull;|&#8226;|&#x2022;/g, "•");
str = str.replace(/&hellip;|&#8230;|&#x2026;/g, "…");
str = str.replace(/&permil;|&#8240;|&#x2030;/g, "‰");
str = str.replace(/&prime;|&#8242;|&#x2032;/g, "");
str = str.replace(/&Prime;|&#8243;|&#x2033;/g, "″");
str = str.replace(/&lsaquo;|&#8249;|&#x2039;/g, "");
str = str.replace(/&rsaquo;|&#8250;|&#x203a;/g, "");
str = str.replace(/&oline;|&#8254;|&#x203e;/g, "‾");
str = str.replace(/&euro;|&#8364;|&#x20ac;/g, "€");
str = str.replace(/&trade;|&#8482;|&#x2122;/g, "™");
str = str.replace(/&larr;|&#8592;|&#x2190;/g, "←");
str = str.replace(/&uarr;|&#8593;|&#x2191;/g, "↑");
str = str.replace(/&rarr;|&#8594;|&#x2192;/g, "→");
str = str.replace(/&darr;|&#8595;|&#x2193;/g, "↓");
str = str.replace(/&harr;|&#8596;|&#x2194;/g, "↔");
str = str.replace(/&crarr;|&#8629;|&#x21b5;/g, "↵");
str = str.replace(/&lceil;|&#8968;|&#x2308;/g, "⌈");
str = str.replace(/&rceil;|&#8969;|&#x2309;/g, "⌉");
str = str.replace(/&lfloor;|&#8970;|&#x230a;/g, "⌊");
str = str.replace(/&rfloor;|&#8971;|&#x230b;/g, "⌋");
str = str.replace(/&loz;|&#9674;|&#x25ca;/g, "◊");
str = str.replace(/&spades;|&#9824;|&#x2660;/g, "♠");
str = str.replace(/&clubs;|&#9827;|&#x2663;/g, "♣");
str = str.replace(/&hearts;|&#9829;|&#x2665;/g, "♥");
str = str.replace(/&diams;|&#9830;|&#x2666;/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,271 @@
/**
* 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 '/static/wxParse.css';
*/
.wxParse {
user-select: none;
width: 100%;
font-family: Helvetica, "PingFangSC", "Microsoft Yahei", "微软雅黑", Arial,
sans-serif;
color: #0c1c33;
line-height: 1.5;
font-size: 1em;
text-align: justify; /* //左右两端对齐 */
}
.wxParse view,
.wxParse uni-view {
word-break: break-word;
}
.wxParse .p {
padding-bottom: 0.5em;
clear: both;
/* letter-spacing: 0;//字间距 */
}
.wxParse .inline {
display: inline;
margin: 0;
padding: 0;
}
.wxParse .div {
margin: 0;
padding: 0;
display: block;
}
.wxParse .h1 {
font-size: 2em;
line-height: 1.2em;
margin: 0.67em 0;
}
.wxParse .h2 {
font-size: 1.5em;
margin: 0.83em 0;
}
.wxParse .h3 {
font-size: 1.17em;
margin: 1em 0;
}
.wxParse .h4 {
margin: 1.33em 0;
}
.wxParse .h5 {
font-size: 0.83em;
margin: 1.67em 0;
}
.wxParse .h6 {
font-size: 0.83em;
margin: 1.67em 0;
}
.wxParse .h1,
.wxParse .h2,
.wxParse .h3,
.wxParse .h4,
.wxParse .h5,
.wxParse .h6,
.wxParse .b,
.wxParse .strong {
font-weight: bolder;
}
.wxParse .i,
.wxParse .cite,
.wxParse .em,
.wxParse .var,
.wxParse .address {
font-style: italic;
}
.wxParse .spaceshow {
white-space: pre;
}
.wxParse .pre,
.wxParse .tt,
.wxParse .code,
.wxParse .kbd,
.wxParse .samp {
font-family: monospace;
}
.wxParse .pre {
overflow: auto;
background: #f5f5f5;
padding: 16upx;
white-space: pre;
margin: 1em 0upx;
font-size: 24upx;
}
.wxParse .code {
overflow: auto;
padding: 16upx;
white-space: pre;
margin: 1em 0upx;
background: #f5f5f5;
font-size: 24upx;
}
.wxParse .big {
font-size: 1.17em;
}
.wxParse .small,
.wxParse .sub,
.wxParse .sup {
font-size: 0.83em;
}
.wxParse .sub {
vertical-align: sub;
}
.wxParse .sup {
vertical-align: super;
}
.wxParse .s,
.wxParse .strike,
.wxParse .del {
text-decoration: line-through;
}
.wxParse .strong,
.wxParse .text,
.wxParse .span,
.wxParse .s {
display: inline;
}
.wxParse .a {
color: deepskyblue;
}
.wxParse .video {
text-align: center;
margin: 22upx 0;
}
.wxParse .video-video {
width: 100%;
}
.wxParse .uni-image {
max-width: 100%;
}
.wxParse .img {
display: block;
max-width: 100%;
margin-bottom: 0em; /* //与p标签底部padding同时修改 */
overflow: hidden;
}
.wxParse .blockquote {
margin: 10upx 0;
padding: 22upx 0 22upx 22upx;
font-family: Courier, Calibri, "宋体";
background: #f5f5f5;
border-left: 6upx solid #dbdbdb;
}
.wxParse .blockquote .p {
margin: 0;
}
.wxParse .ul,
.wxParse .ol {
display: block;
margin: 1em 0;
padding-left: 2em;
}
.wxParse .ol {
list-style-type: disc;
}
.wxParse .ol {
list-style-type: decimal;
}
.wxParse .ol > weixin-parse-template,
.wxParse .ul > weixin-parse-template {
display: list-item;
align-items: baseline;
text-align: match-parent;
}
.wxParse .ol > .li,
.wxParse .ul > .li {
display: list-item;
align-items: baseline;
text-align: match-parent;
}
.wxParse .ul .ul,
.wxParse .ol .ul {
list-style-type: circle;
}
.wxParse .ol .ol .ul,
.wxParse .ol .ul .ul,
.wxParse .ul .ol .ul,
.wxParse .ul .ul .ul {
list-style-type: square;
}
.wxParse .u {
text-decoration: underline;
}
.wxParse .hide {
display: none;
}
.wxParse .del {
display: inline;
}
.wxParse .figure {
overflow: hidden;
}
.wxParse .tablebox {
overflow: auto;
background-color: #f5f5f5;
background: #f5f5f5;
font-size: 13px;
padding: 8px;
}
.wxParse .table .table,
.wxParse .table {
border-collapse: collapse;
box-sizing: border-box;
/* 内边框 */
/* width: 100%; */
overflow: auto;
white-space: pre;
}
.wxParse .tbody {
border-collapse: collapse;
box-sizing: border-box;
/* 内边框 */
border: 1px solid #dadada;
}
.wxParse .table .thead,
.wxParse .table .tfoot,
.wxParse .table .th {
border-collapse: collapse;
box-sizing: border-box;
background: #ececec;
font-weight: 40;
}
.wxParse .table .tr {
border-collapse: collapse;
box-sizing: border-box;
/* border: 2px solid #F0AD4E; */
overflow: auto;
}
.wxParse .table .th,
.wxParse .table .td {
border-collapse: collapse;
box-sizing: border-box;
border: 2upx solid #dadada;
overflow: auto;
}
.wxParse .audio,
.wxParse .uni-audio-default {
display: block;
}

View File

@@ -0,0 +1,226 @@
<!--**
* forked fromhttps://github.com/F-loat/mpvue-wxParse
*
* github地址: https://github.com/dcloudio/uParse
*
* for: uni-app框架下 富文本解析
*
* 优化 by gaoyia@qq.com https://github.com/gaoyia/parse
*/-->
<template>
<!--基础元素-->
<div class="wxParse" :class="className" :style="'user-select:' + userSelect">
<block v-for="(node, index) of nodes" :key="index" v-if="!loading">
<wxParseTemplate :node="node" />
</block>
</div>
</template>
<script>
import HtmlToJson from "./libs/html2json";
import wxParseTemplate from "./components/wxParseTemplate0";
export default {
name: "wxParse",
props: {
// user-select:none;
userSelect: {
type: String,
default: "text", //none |text| all | element
},
imgOptions: {
type: [Object, Boolean],
default: function () {
return {
loop: false,
indicator: "number",
longPressActions: false,
// longPressActions: {
// itemList: ['发送给朋友', '保存图片', '收藏'],
// success: function (res) {
// console.log('选中了第' + (res.tapIndex + 1) + '个按钮');
// },
// fail: function (res) {
// console.log(res.errMsg);
// }
// }
// }
};
},
},
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: "",
};
},
},
},
components: {
wxParseTemplate,
},
data() {
return {
nodes: {},
imageUrls: [],
wxParseWidth: {
value: 0,
},
};
},
computed: {},
mounted() {
this.setHtml();
},
methods: {
setHtml() {
this.getWidth().then((data) => {
this.wxParseWidth.value = data;
});
let {
content,
noData,
imageProp,
startHandler,
endHandler,
charsHandler,
} = this;
let parseData = content || noData;
let customHandler = {
start: startHandler,
end: endHandler,
chars: charsHandler,
};
let results = HtmlToJson(parseData, customHandler, imageProp, this);
this.imageUrls = results.imageUrls;
// this.nodes = results.nodes;
this.nodes = [];
results.nodes.forEach((item) => {
setTimeout(() => {
this.nodes.push(item);
}, 0);
});
},
getWidth() {
return new Promise((res, rej) => {
// #ifndef MP-ALIPAY || MP-BAIDU
uni
.createSelectorQuery()
.in(this)
.select(".wxParse")
.fields(
{
size: true,
scrollOffset: true,
},
(data) => {
res(data.width);
},
)
.exec();
// #endif
// #ifdef MP-BAIDU
const query = swan.createSelectorQuery();
query.select(".wxParse").boundingClientRect();
query.exec((obj) => {
const rect = obj[0];
if (rect) {
res(rect.width);
}
});
// #endif
// #ifdef MP-ALIPAY
my.createSelectorQuery()
.select(".wxParse")
.boundingClientRect()
.exec((ret) => {
res(ret[0].width);
});
// #endif
});
},
navigate(href, $event, attr) {
console.log(href, attr);
this.$emit("navigate", href, $event);
},
preview(src, $event) {
// if (!this.imageUrls.length || typeof this.imgOptions === 'boolean') {
// } else {
// uni.previewImage({
// current: src,
// urls: this.imageUrls,
// loop: this.imgOptions.loop,
// indicator: this.imgOptions.indicator,
// longPressActions: this.imgOptions.longPressActions
// });
// }
// this.$emit('preview', src, $event);
},
removeImageUrl(src) {
const { imageUrls } = this;
imageUrls.splice(imageUrls.indexOf(src), 1);
},
},
// 父组件中提供
provide() {
return {
parseWidth: this.wxParseWidth,
parseSelect: this.userSelect,
// 提示provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
};
},
watch: {
content() {
this.setHtml();
},
// content: {
// handler: function(newVal, oldVal) {
// if (newVal !== oldVal) {
//
// }
// },
// deep: true
// }
},
};
</script>