提交 24ffce04 编写于 作者: DCloud_JSON's avatar DCloud_JSON

重构

上级 a1d97491
......@@ -93,7 +93,7 @@
/* #endif */
}
/* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */
view,
scroll-view,
text,
......@@ -105,10 +105,8 @@
box-sizing: border-box;
flex-direction: column;
}
scroll-view {
-webkit-overflow-scrolling: touch;
}
/* #endif */
/* #endif */
</style>
......@@ -18,7 +18,14 @@ module.exports = {
"/uni_modules/uni-news-favorite/pages/uni-news-favorite/list",
"/pages/ucenter/edit/uploadCutImageToUnicloud"
],
"login": [ "smsCode","univerify", "username", "weixin", "apple"] //默认就是短信验证码登陆
"login": [ "smsCode","uniVerify", "username", "weixin", "apple"],
/*
根据数组的第0项,决定登陆方式的第一优先级。
为完全列举到的或设备环境不支持的选项,将被隐藏。
快捷登陆按钮,在任意一页面都存在。
所以只有三种情况:
一键登录(uniVerify)、账号(username)、验证码登陆(其他值为第一项都为验证码登陆)
*/
},
"about": {
"appName": "base-app",
......
......@@ -8,8 +8,7 @@ export default function() {
initAppVersion();
// baseappConfig挂载到getApp().
getApp({allowDefault: true}).config = baseappConfig;
getApp({allowDefault: true}).config = baseappConfig;
//自定义路由拦截
const {
......@@ -41,8 +40,8 @@ export default function() {
}
//控制登陆优先级
if (url == '/pages/ucenter/login-page/index/index') {
//一键登录(univerify)、密码登陆(username)、快捷登录&验证码登陆(!univerify&password
if (login[0] == 'univerify') {
//一键登录(uniVerify)、账号(username)、验证码登陆(短信smsCode
if (login[0] == 'uniVerify') {
// console.log(e.url,url);
if (e.url == url) {
e.url += '?'
......@@ -67,7 +66,7 @@ export default function() {
fail(e) { // 失败回调拦截
console.log(e);
if (
e.errCode === 11 && uni.getSystemInfoSync().platform == "android" ||
e.errCode === 11 && uni.getSystemInfoSync().platform == "android" ||
e.errCode === 2 && uni.getSystemInfoSync().platform == "ios"
){
uni.showModal({
......
export default function(result){
uni.showToast({
title: '登陆成功',
icon: 'none'
});
console.log('登陆成功',result);
uni.setStorageSync('uni_id_uid', result.uid)
uni.setStorageSync('uni_id_token', result.token)
uni.setStorageSync('uni_id_token_expired', result.tokenExpired)
//delete result.userInfo.token
// this.setUserInfo(result.userInfo)
var delta = 0//判断需要返回几层
let pages = getCurrentPages();
// console.log(pages);
pages.forEach((page,index)=>{
// console.log(pages[pages.length-index-1].route.split('/')[2]);
pages[pages.length-index-1].route.split('/')
if(pages[pages.length-index-1].route.split('/')[2] == 'login-page'){
delta ++
}
})
// console.log('判断需要返回几层',delta);
uni.navigateBack({delta})
}
\ No newline at end of file
<template>
<view class="flex flex-row justify-center align-center flex-wrap auth-box">
<!-- <image :src="item.image" v-for="(item, index) in providerList" :key="item.value" @click="clickItem" class="auth-logo hidden"></image> -->
<image ref="logo" :src="item.image" v-for="(item, index) in providerList" :key="item.value" @click="clickItem"
class="auth-logo auth-logo-shadow hidden"></image>
<view ref="more" class="flex justify-center align-center auth-more" @click="startAnimation">
<text class="font-bolder font-50">···</text>
</view>
</view>
</template>
<script>
const animation = uni.requireNativePlugin('animation')
export default {
name: "auth-btn",
data() {
return {
providerList: [],
};
},
created() {
this.initProvider();
this.testInit();
},
methods: {
/**
* 测试方法
*/
testInit() {
const filters = ['apple', 'weixin', 'qq', 'sinaweibo'];
this.providerList = [];
filters.forEach(curProvider => {
this.providerList.push({
value: curProvider,
image: '../../static/login/img/' + curProvider + '.png'
});
})
},
/**
* 初始化第三方登录
*/
initProvider() {
return
const filters = ['apple', 'weixin', 'qq', 'sinaweibo'];
uni.getProvider({
service: 'oauth',
success: (res) => {
if (res.provider && res.provider.length) {
if (res.provider.indexOf('apple') !== -1) {
this.hasAppleLogin = true;
}
for (let i = 0; i < res.provider.length; i++) {
const curProvider = res.provider[i];
if (~filters.indexOf(curProvider)) {
this.providerList.push({
value: curProvider,
image: '/components/auth-btn/img/' + curProvider + '.png'
});
}
}
this.hasProvider = true;
}
},
fail: (err) => {
console.error('获取服务供应商失败:' + JSON.stringify(err));
}
});
},
clickItem(item) {
this.$emit('login', item)
},
// 开始动画
startAnimation() {
let more = this.$refs.more;
animation.transition(more, {
styles: {
opacity: '0'
},
duration: 100, //ms
timingFunction: 'linear',
delay: 0 //ms
}, () => {});
let logo = this.$refs.logo;
let logo_w = uni.upx2px(parseInt(logo[0].style.width));
let logo_m = logo_w/6;
let logo_l = logo.length;
let logo_c = (logo_l - 1)/2;
logo.forEach((item, index)=>{
let translateX = (logo_w + logo_m) * (index - logo_c);
animation.transition(item, {
styles: {
opacity: '1',
transform:`translateX(${translateX}px)`
},
duration: 200, //ms
timingFunction: 'linear',
delay: 100 //ms
}, () => {});
})
}
}
}
</script>
<style>
/* @import url("../../common/myStyle.css"); */
.auth-logo {
width: 84rpx;
height: 84rpx;
}
.hidden {
opacity: 0;
}
.auth-box {
position: relative;
height: 84rpx;
width: 750rpx;
}
.auth-logo-shadow {
position: absolute;
}
.auth-more {
position: absolute;
width: 84rpx;
height: 84rpx;
border-radius: 45rpx;
border-width: 2rpx;
border-color: #000000;
}
</style>
<template>
<uni-popup ref="actionSheet" type="bottom">
<view class="action-sheet-box">
<view class="auth-wrap auth-item" @click="clickItem(item)"
hover-class="hover" v-for="(item, index) in providerList" :key="index">
<image :src="item.image" class="login-logo"></image>
<text class="auth-text">{{providerName[item.value]}}</text>
</view>
<view class="cancel-line"></view>
<view class="auth-wrap cancel-item" @click="clickItem(item)">
<text class="auth-text">取消</text>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
name: "login-action-sheet",
data() {
return {
providerList: [],
providerName: {
apple: '苹果登录',
weixin: '微信登录',
qq: 'QQ登录',
sinaweibo: '微博登录'
}
};
},
created() {
this.initProvider();
this.testInit();
},
methods: {
/**
* 测试方法
*/
testInit() {
const filters = ['apple', 'weixin', 'qq', 'sinaweibo'];
this.providerList = [];
filters.forEach(curProvider => {
this.providerList.push({
value: curProvider,
image: '../../static/login/img/' + curProvider + '.png'
});
})
},
/**
* 初始化第三方登录
*/
initProvider() {
return
const filters = ['apple', 'weixin', 'qq', 'sinaweibo'];
uni.getProvider({
service: 'oauth',
success: (res) => {
if (res.provider && res.provider.length) {
if (res.provider.indexOf('apple') !== -1) {
this.hasAppleLogin = true;
}
for (let i = 0; i < res.provider.length; i++) {
const curProvider = res.provider[i];
if (~filters.indexOf(curProvider)) {
this.providerList.push({
value: curProvider,
image: '/components/auth-btn/img/' + curProvider + '.png'
});
}
}
this.hasProvider = true;
}
},
fail: (err) => {
console.error('获取服务供应商失败:' + JSON.stringify(err));
}
});
},
clickItem(item) {
this.$refs.actionSheet.close();
if (item) this.$emit('login', item);
},
open() {
this.$refs.actionSheet.open();
}
}
}
</script>
<style scoped>
.action-sheet-box {
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
width: 750rpx;
background-color: #fff;
}
.login-logo {
width: 42rpx;
height: 42rpx;
}
.auth-wrap{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
}
.auth-item {
border-bottom-width: 1px;
border-bottom-color: #F1F1F1;
height: 125rpx;
}
.cancel-item {
height: 125rpx;
}
.cancel-line {
width: 750rpx;
height: 10rpx;
background-color: #F1F1F1;
}
.auth-text{
padding: 0 10rpx;
font-size: 28rpx;
}
</style>
<template>
<view :class="[flex?'flex':'null']">
<slot></slot>
</view>
</template>
<script>
export default {
props: {
flex: {
default(){
return false
}
},
},
data() {
return {
}
},
watch: {
},
methods: {
},
}
</script>
<style lang="scss" scoped>
.null{
display: inline-flex;
width: auto;
}
.flex{
display: flex;
}
</style>
此差异已折叠。
<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>
<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: true
};
},
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>
<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>
\ No newline at end of file
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
/**
* 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;
/**
*
* 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;
// 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,
};
/**
* 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: #333;
line-height: 1.5;
font-size: 14px;
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;
}
\ No newline at end of file
<!--**
* forked from:https://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>
......@@ -6,9 +6,15 @@ import request from './js_sdk/request.js';
Vue.config.productionTip = false
Vue.prototype.request = request
openApp();
//openApp();
App.mpType = 'app'
import nullComponents from '@/components/null/null.vue'
Vue.component('cell',nullComponents);
Vue.component('refresh',nullComponents);
Vue.component('refreshBox',nullComponents);
const app = new Vue({
...App,
store
......
......@@ -107,11 +107,13 @@
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"appid" : "wx07597007230e3702",
"setting" : {
"urlCheck" : false
"urlCheck" : false,
"es6" : false
},
"usingComponents" : true
// "betterScopedSlots" : true
},
"mp-alipay" : {
"usingComponents" : true
......
{
"pages": [{
"pages": [
{
"path": "pages/list/list",
"style": {
//#ifndef MP
"navigationStyle": "custom",
//#endif
"enablePullDownRefresh": true
"style": {
// #ifdef APP-NVUE
"enablePullDownRefresh": false,
// #endif
// #ifdef H5
"navigationStyle":"custom",
// #endif
"app-plus":{
// #ifdef APP-PLUS
"titleNView":{ /*排除了H5*/
"searchInput":{
"placeholder":"请输入搜索的内容",
"backgroundColor":"#efefef",
"disabled":true,
"borderRadius":"30px"
}
}
// #endif
}
}
},
{
"path": "pages/test/test",
"style": {
"navigationBarTitleText": "测试页面",
"enablePullDownRefresh": false
}
},
{
......@@ -165,14 +187,7 @@
"navigationBarTitleText": ""
}
}, {
"path": "pages/test/test",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}, {
},{
"path": "pages/common/webview/webview",
"style": {
"navigationBarTitleText": "",
......@@ -199,7 +214,8 @@
"navigationBarTextStyle": "black",
"navigationBarTitleText": "base-app",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
"backgroundColor": "#F8F8F8",
"enablePullDownRefresh": false
},
"condition": {
"list": [{
......
......@@ -201,17 +201,6 @@
<style>
@charset "UTF-8";
/* 头条小程序组件内不能引入字体 */
/* #ifdef MP-TOUTIAO */
@font-face {
font-family: uniicons;
font-weight: normal;
font-style: normal;
src: url("~@/static/uni.ttf") format("truetype");
}
/* #endif */
/* #ifndef APP-NVUE */
page {
display: flex;
......
<template>
<!--
本页面模板教程:https://ext.dcloud.net.cn/plugin?id=2651
uni-list 文档:https://ext.dcloud.net.cn/plugin?id=24
uniCloud 文档:https://uniapp.dcloud.io/uniCloud/README
uni-clientDB 组件文档:https://uniapp.dcloud.net.cn/uniCloud/uni-clientdb-component
DB Schema 规范:https://uniapp.dcloud.net.cn/uniCloud/schema
-->
<view style="overflow: hidden;">
<view class="search-box">
<!-- #ifdef APP-PLUS -->
<status-bar class="status-bar"></status-bar>
<!-- #endif -->
<view class="search-container-bar">
<uni-search-bar ref="searchBar" style="flex:1;" radius="100" v-model="searchText" @click.native="searchClick" cancelButton="none" disabled />
</view>
</view>
<view class="list">
<!-- #ifdef APP-PLUS -->
<status-bar></status-bar>
<!-- #endif -->
<!-- 刷新页面后的顶部提示框 -->
<!-- 当前弹出内容没有实际逻辑 ,可根据当前业务修改弹出提示 -->
<view class="tips" :class="{ 'tips-ani': tipShow }">为您更新了10条内容</view>
<!-- 页面分类标题 -->
<uni-section style="margin:0;" v-if="searchText" :title="listTitle" type="line"></uni-section>
<unicloud-db ref="udb" v-slot:default="{data, loading, error, options}" :options="formData"
:collection="collection" :field="field" :foreignKey="foreignKey" :where="where" @load="load" @error="isLoading = false">
<text v-if="error" class="list-info">{{error.message}}</text>
<!-- 基于 uni-list 的页面布局 -->
<uni-list :class="{ 'uni-list--waterfall': options.waterfall }">
<!-- 通过 uni-list--waterfall 类决定页面布局方向 -->
<!-- to 属性携带参数跳转详情页面,当前只为参考 -->
<uni-list-item :border="!options.waterfall" :to="'./detail?id='+item._id+'&title='+item.title" class="uni-list-item--waterfall" title="自定义列表"
v-for="item in data" :key="item._id">
<!-- 通过header插槽定义列表左侧图片 -->
<template v-slot:header>
<view class="uni-thumb shop-picture" :class="{ 'shop-picture-column': options.waterfall }">
<image :src="item.avatar" mode="aspectFill"></image>
</view>
</template>
<!-- 通过body插槽定义布局 -->
<view slot="body" class="shop">
<view>
<view class="uni-title">
<text class="uni-ellipsis-2">{{ item.title }}</text>
</view>
</view>
<view>
<view class="uni-note ellipsis">
<text class="uni-ellipsis-1">{{ item.author[0].username }}</text>
<text>{{ item.comment_count }}评论</text>
<uni-dateformat :date="item.last_modify_date" format="yyyy-MM-dd" :threshold="[60000, 2592000000]"/>
</view>
</view>
</view>
</uni-list-item>
</uni-list>
<!-- 通过 loadMore 组件实现上拉加载效果,如需自定义显示内容,可参考:https://ext.dcloud.net.cn/plugin?id=29 -->
<uni-load-more v-if="!error && (loading || options.status === 'noMore') " :status="options.status" />
<uni-nodata v-if="data.length == 0" :isLoading="isLoading" @retry="refresh"></uni-nodata>
</unicloud-db>
</view>
</view>
</template>
<script>
import statusBar from '@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue';
export default {
components:{
statusBar
},
props:{
currentText:{
type:String,
default:''
},
canSearch:{
type:Boolean,
default:false
}
},
data() {
return {
searchText: this.currentText || '',
formData: {
waterfall: false, // 布局方向切换
status: 'loading', // 加载状态
},
where: '',
// 数据表名
collection: 'opendb-news-articles,uni-id-users',
// 查询字段,多个字段用 , 分割
foreignKey: '',
field: 'author{username, _id}, _id,avatar,title,excerpt,last_modify_date, comment_count, like_count',
tipShow: false ,// 是否显示顶部提示框
isLoading:true
};
},
/**
* 作为页面出现时的生命周期
*/
onShow(options) {
this.searchText = getApp().globalData.searchText;
},
/**
* 下拉刷新回调函数
*/
onPullDownRefresh() {
this.refresh();
},
onReachBottom() {
this.loadMore();
},
methods: {
/**
* 切换列表布局方向
*/
select() {
this.formData.waterfall = !this.formData.waterfall;
},
/**
* 下拉刷新回调函数
*/
refresh() {
this.tipShow = true
this.formData.status = 'more'
this.isLoading = true
this.$refs.udb.loadData({
clear: true
}, () => {
this.tipShow = false
this.isLoading = false
uni.stopPullDownRefresh()
})
},
/**
* 上拉加载回调函数
*/
loadMore() {
this.$refs.udb.loadMore()
},
load(data, ended) {
this.isLoading = false
if (ended) {
this.formData.status = 'noMore'
}
},
searchClick() {
uni.hideKeyboard();
if(this.canSearch){
uni.navigateTo({
url: '/pages/list/search/search',
animationType: 'fade-in'
});
}else {
uni.navigateBack();
}
}
},
computed: {
listTitle() {
if (this.searchText) return '搜索结果';
return '';
}
}
};
</script>
<style lang="scss" scoped>
@import '@/common/uni-ui.scss';
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #efeff4;
min-height: 100%;
height: auto;
}
.tips {
color: #67c23a;
font-size: 14px;
line-height: 40px;
text-align: center;
background-color: #f0f9eb;
height: 0;
opacity: 0;
transform: translateY(-100%);
transition: all 0.3s;
}
.tips-ani {
transform: translateY(0);
height: 40px;
opacity: 1;
}
.shop {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.shop-picture {
width: 110px;
height: 110px;
}
.shop-picture-column {
width: 100%;
height: 170px;
margin-bottom: 10px;
}
.ellipsis {
display: flex;
overflow: hidden;
}
.uni-ellipsis-1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.uni-ellipsis-2 {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.uni-note{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
}
// 默认加入 scoped ,所以外面加一层提升权重
.list {
margin-top: 52px;
.uni-list--waterfall {
/* #ifndef H5 || APP-VUE */
// 小程序 编译后会多一层标签,而其他平台没有,所以需要特殊处理一下
/deep/ .uni-list {
/* #endif */
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 5px;
box-sizing: border-box;
/* #ifdef H5 || APP-VUE */
// h5 和 app-vue 使用深度选择器,因为默认使用了 scoped ,所以样式会无法穿透
/deep/
/* #endif */
.uni-list-item--waterfall {
width: 50%;
box-sizing: border-box;
.uni-list-item__container {
padding: 5px;
flex-direction: column;
}
}
/* #ifndef H5 || APP-VUE */
}
/* #endif */
}
}
.search-icons {
padding: 16rpx;
}
.search-box{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
position: fixed;
left: 0;
right: 0;
z-index: 10;
background-color: #fff;
}
.search-container-bar {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
width: 750rpx;
}
/* #ifndef APP-NVUE */
/deep/
/* #endif */
.uni-searchbar__box {
border-width: 0;
}
/* #ifndef APP-NVUE */
/deep/
/* #endif */
.uni-input-placeholder {
font-size: 28rpx;
}
.list-info {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
flex: 1;
text-align: center;
font-size: 26rpx;
color: #808080;
margin-top: 20rpx;
}
</style>
......@@ -6,25 +6,25 @@
unicloud-db 组件文档:https://uniapp.dcloud.net.cn/uniCloud/unicloud-db-component
DB Schema 规范:https://uniapp.dcloud.net.cn/uniCloud/schema
-->
<view class="article">
<!-- #ifdef APP-PLUS -->
<uni-nav-bar :statusBar="true" :border="false"></uni-nav-bar>
<!-- #endif -->
<view class="article">
<!-- #ifdef APP-PLUS -->
<uni-nav-bar :statusBar="true" :border="false"></uni-nav-bar>
<!-- #endif -->
<view class="article-title">{{ title }}</view>
<unicloud-db v-slot:default="{data, loading, error, options}" :options="formData" :collection="collection"
:field="field" :getone="true" :where="where" :manual="true" ref="detail"
foreignKey="opendb-news-articles.author" @load="loadData">
foreignKey="opendb-news-articles.user_id" @load="loadData">
<template v-if="!loading && data">
<uni-list :border="false">
<uni-list-item thumbSize="lg" :thumb="data.image">
<!-- 通过body插槽定义作者信息内容 -->
<view slot="body" class="header-content">
<view class="uni-title">{{data.author && data.author[0].username}}</view>
<view class="uni-title">{{data.user_id && data.user_id[0].username}}</view>
</view>
<view slot="footer" class="footer">
<view class="uni-note">更新于
<uni-dateformat :date="data.last_modify_date" format="yyyy-MM-dd hh:mm"
:threshold="[60000, 2592000000]" />
<view slot="footer" class="footer">
<view class="uni-note">更新于
<uni-dateformat :date="data.last_modify_date" format="yyyy-MM-dd hh:mm"
:threshold="[60000, 2592000000]" />
</view>
<!-- <button @click="followClick" class="footer-button">关注</button> -->
</view>
......@@ -38,9 +38,8 @@
<text class="uni-ellipsis">{{data.excerpt}}</text>
</view>
</view>
<!-- 新闻详情:使用 uParse 解析富文本 -->
<view class="article-content">
<u-parse :content="data.content" :noData="options.noData"></u-parse>
<rich-text :nodes="data.content"></rich-text>
</view>
</template>
</unicloud-db>
......@@ -50,24 +49,22 @@
<script>
import baseappConfig from '@/baseapp.config.js';
import uniShare from 'uni_modules/uni-share/js_sdk/uni-share.js';
import uParse from '@/components/u-parse/parse.vue';
const db = uniCloud.database();
const newsFavoriteTable = db.collection('opendb-news-favorite')
import { mapGetters } from 'vuex';
const db = uniCloud.database();
const newsFavoriteTable = db.collection('opendb-news-favorite')
import {
mapGetters
} from 'vuex';
export default {
components: {
uParse
},
data() {
return {
return {
// 当前显示 _id
id: "",
title: 'title',
// 数据表名
collection: 'opendb-news-articles,uni-id-users',
// 查询字段,多个字段用 , 分割
field: 'author{username, _id},_id,avatar,excerpt,last_modify_date, comment_count, like_count,title,content',
field: 'user_id{username, _id},_id,avatar,excerpt,last_modify_date, comment_count, like_count,title,content',
formData: {
noData: '<p style="text-align:center;color:#666">详情加载中...</p>'
},
......@@ -78,13 +75,13 @@
//查询条件 ,更多详见 :https://uniapp.dcloud.net.cn/uniCloud/unicloud-db?id=jsquery
where() {
return `_id =="${this.id}"`
},
...mapGetters({
'userInfo':'user/info',
'hasLogin':'user/hasLogin'
},
...mapGetters({
'userInfo': 'user/info',
'hasLogin': 'user/hasLogin'
})
},
onLoad(event) {
onLoad(event) {
//获取真实新闻id,通常 id 来自上一个页面
if (event.id) {
this.id = event.id
......@@ -95,7 +92,7 @@
uni.setNavigationBarTitle({
title: event.title
})
}
}
},
onNavigationBarButtonTap(event) {
if (event.type == 'share') {
......@@ -113,49 +110,49 @@
})
}
},
methods: {
setFavorite(){
if(!this.has)return
newsFavoriteTable.where({
article_id:this.id,
user_id:this.userInfo._id
})
.get()
.then(res=>{
let value = {
article_id:this.id,
article_title:this.title,
user_id:this.userInfo._id,
update_date:Date.now()
}
if(res.result.data.length == 0){
return newsFavoriteTable.add(value)
} else {
return newsFavoriteTable.where({
article_id:this.id,
user_id:this.userInfo._id
})
.update(value)
}
})
.then(res=>{
// console.log(res);
})
.catch(err=>{
console.log(err);
})
methods: {
setFavorite() {
if (!this.has) return
newsFavoriteTable.where({
article_id: this.id,
user_id: this.userInfo._id
})
.get()
.then(res => {
let value = {
article_id: this.id,
article_title: this.title,
user_id: this.userInfo._id,
update_date: Date.now()
}
if (res.result.data.length == 0) {
return newsFavoriteTable.add(value)
} else {
return newsFavoriteTable.where({
article_id: this.id,
user_id: this.userInfo._id
})
.update(value)
}
})
.then(res => {
// console.log(res);
})
.catch(err => {
console.log(err);
})
},
loadData(data) {
loadData(data) {
//如果上一页未传递标题过来(如搜索直达详情),则从新闻详情中读取标题
if (this.title == '' && data[0].title) {
this.title = data[0].title
uni.setNavigationBarTitle({
title: data[0].title
});
}
});
}
this.setFavorite();
},
/**
......@@ -187,7 +184,7 @@
imageUrl: avatar
},
menus: [{
"img": "/static/sharemenu/wechatfriend.png",
"img": "/static/app-plus/sharemenu/wechatfriend.png",
"text": "微信好友",
"share": {
"provider": "weixin",
......@@ -195,7 +192,7 @@
}
},
{
"img": "/static/sharemenu/wechatmoments.png",
"img": "/static/app-plus/sharemenu/wechatmoments.png",
"text": "微信朋友圈",
"share": {
"provider": "weixin",
......@@ -203,41 +200,42 @@
}
},
{
"img": "/static/sharemenu/mp_weixin.png",
"img": "/static/app-plus/sharemenu/mp_weixin.png",
"text": "微信小程序",
"share": {
provider: "weixin",
scene: "WXSceneSession",
type: 5,
miniProgram: {
id:baseappConfig.mp.weixin.id,
path:`/pages/list/detail?id=${_id}&title=${title}`,
webUrl:baseappConfig.h5.url + `/#/pages/list/detail?id=${_id}&title=${title}`,
type:0
id: baseappConfig.mp.weixin.id,
path: `/pages/list/detail?id=${_id}&title=${title}`,
webUrl: baseappConfig.h5.url +
`/#/pages/list/detail?id=${_id}&title=${title}`,
type: 0
},
}
},
{
"img": "/static/sharemenu/weibo.png",
"img": "/static/app-plus/sharemenu/weibo.png",
"text": "微博",
"share": {
"provider": "sinaweibo"
}
},
{
"img": "/static/sharemenu/qq.png",
"img": "/static/app-plus/sharemenu/qq.png",
"text": "QQ",
"share": {
"provider": "qq"
}
},
{
"img": "/static/sharemenu/copyurl.png",
"img": "/static/app-plus/sharemenu/copyurl.png",
"text": "复制",
"share": "copyurl"
},
{
"img": "/static/sharemenu/more.png",
"img": "/static/app-plus/sharemenu/more.png",
"text": "更多",
"share": "shareSystem"
}
......@@ -251,7 +249,7 @@
}
</script>
<style scoped>
<style scoped>
.header-content {
flex: 1;
display: flex;
......
<template>
<view>
<view class="search-box">
<!-- #ifdef APP-PLUS -->
<uni-nav-bar class="status-bar"></uni-nav-bar>
<!-- #endif -->
<view class="search-container-bar">
<uni-search-bar ref="searchBar" style="flex:1;" radius="100" v-model="searchText"
@click.native="searchClick" cancelButton="none" disabled />
</view>
<!-- 搜索功能 小程序用绘制的且带原生导航,h5和app用原生自带绘制 -->
<!-- #ifndef APP-PLUS -->
<view class="uni-search-box">
<uni-search-bar class="uni-search-bar" v-model="keyword" ref="searchBar" radius="100"
@click.native="searchClick" cancelButton="none" disabled />
</view>
<view class="content">
<!-- #ifdef APP-PLUS -->
<!-- <uni-nav-bar class="status-bar"></uni-nav-bar> -->
<!-- #endif -->
<!-- 刷新页面后的顶部提示框 -->
<!-- 当前弹出内容没有实际逻辑 ,可根据当前业务修改弹出提示 -->
<view class="tips" :class="{ 'tips-ani': tipShow }">为您更新了10条内容</view>
<!-- 页面分类标题 -->
<!-- <uni-section style="margin:0;" v-if="searchText" :title="listTitle" type="line"></uni-section> -->
<unicloud-db ref="udb" v-slot:default="{data, loading, error, options}" :options="formData"
collection="opendb-news-articles,uni-id-users"
field="author{username, _id}, _id,avatar,title,excerpt,last_modify_date, comment_count, like_count"
:foreignKey="foreignKey" :where="where" @load="load" @error="isLoading = false">
<text v-if="error" class="list-info">{{error.message}}</text>
<!-- 基于 uni-list 的页面布局 -->
<uni-list :class="{ 'uni-list--waterfall': options.waterfall }">
<!-- 通过 uni-list--waterfall 类决定页面布局方向 -->
<!-- to 属性携带参数跳转详情页面,当前只为参考 -->
<uni-list-item :border="!options.waterfall" :to="'./detail?id='+item._id+'&title='+item.title"
class="uni-list-item--waterfall" title="自定义列表" v-for="item in data" :key="item._id">
<!-- 通过header插槽定义列表左侧图片 -->
<template v-slot:header>
<image class="avatar" :src="item.avatar" mode="aspectFill"></image>
</template>
<!-- 通过body插槽定义布局 -->
<view slot="body" class="main">
<text class="title">{{ item.title }}</text>
<view class="foot">
<text class="uni-ellipsis-1">{{ item.author[0].username }}</text>
<text>{{ item.comment_count }}评论</text>
<uni-dateformat :date="item.last_modify_date" format="yyyy-MM-dd"
:threshold="[60000, 2592000000]" />
</view>
<!-- #endif -->
<unicloud-db ref='udb' v-slot:default="{data,pagination,hasMore, loading, error, options}" @load="handleLoad"
:where="where" collection="opendb-news-articles,uni-id-users"
field="avatar,title,last_modify_date,user_id{username}">
<!-- 基于 uni-list 的页面布局 -->
<uni-list class="uni-list" :border="false" :style='{height:windowHeight+"px"}' :bounce="true"
:alwaysScrollableVertical="true">
<!-- #ifdef APP-NVUE -->
<refresh @refresh="refresh" @pullingdown="onpullingdown" :display="!loading ? 'show' : 'hide'">
<refreshBox :state="refreshState"></refreshBox>
</refresh>
<!-- #endif -->
<cell class="get-data-state" v-if="data.length===0&&pagination.current===1">
<!-- 刷新页面后的顶部提示框 当前弹出内容没有实际逻辑 ,可根据当前业务修改弹出提示 -->
<text class="refresh-tip"
:class="{ 'show-refresh-tip':showRefreshTip }">为您更新了{{data.length}}条内容</text>
<!-- 数据为空 当前页码为1,且正在加载中;这里为了演示,更加直观的表达内部逻辑。商用项目建议将这部分封装为组件,更好的让业务逻辑与功能分离-->
<template v-if="loading">
<image class="get-data-state-img" src="@/static/getDataState/loading.png" mode="widthFix">
</image>
<text class="get-data-state-text">加载中...</text>
</template>
<template v-else>
<image class="get-data-state-img" src="@/static/getDataState/nodata.png" mode="widthFix">
</image>
<text class="get-data-state-text">内容为空</text>
</template>
</cell>
<uni-list-item v-if="error">{{error.message}}</uni-list-item>
<uni-list-item v-else :to="'./detail?id='+item._id+'&title='+item.title"
v-for="(item,index) in testData(data)" :key="index">
<!-- 通过header插槽定义列表左侧图片 -->
<template v-slot:header>
<image class="avatar" :src="item.avatar" mode="aspectFill"></image>
</template>
<!-- 通过body插槽定义布局 -->
<view slot="body" class="main">
<text class="title">{{ item.title }}</text>
<view class="info">
<text class="author">{{item.user_id[0].username}}</text>
<uni-dateformat class="last_modify_date" :date="item.last_modify_date" format="yyyy-MM-dd"
:threshold="[60000, 2592000000]" />
</view>
</uni-list-item>
</uni-list>
<!-- 通过 loadMore 组件实现上拉加载效果,如需自定义显示内容,可参考:https://ext.dcloud.net.cn/plugin?id=29 -->
<uni-load-more v-if="!error && (loading || options.status === 'noMore') " :status="options.status" />
<uni-nodata v-if="data.length == 0" :isLoading="isLoading" @retry="refresh"></uni-nodata>
</unicloud-db>
</view>
</view>
</uni-list-item>
<!-- 存在下一页数据,且不在加载中 通过 loadMore 组件实现上拉加载效果,如需自定义显示内容,可参考:https://ext.dcloud.net.cn/plugin?id=29 -->
<cell v-if="hasMore">
<uni-load-more v-if="!error &&!loading&&hasMore" :status="options.status"></uni-load-more>
</cell>
<cell v-else>
<text class="noMore">- 没有更多数据了 -</text>
</cell>
</uni-list>
</unicloud-db>
</view>
</template>
<script>
var cdbRef, currentWebview;
export default {
data() {
return {
canSearch: true,
searchText: 'searchText',
formData: {
waterfall: false, // 布局方向切换
status: 'loading', // 加载状态
},
where: '',
// 数据表名
foreignKey: '',
tipShow: false, // 是否显示顶部提示框
isLoading: true
};
showRefreshTip: true,
where: "",
keyword: "",
refreshState: 0,
windowHeight: 0 //窗口的高
}
},
onLoad() {},
onShow(options) {
this.searchText = getApp().globalData.searchText;
onReady() {
this.windowHeight = uni.getSystemInfoSync().windowHeight
this.showRefreshTip = false
cdbRef = this.$refs.udb
console.log(cdbRef);
},
/**
* 下拉刷新回调函数
*/
onPullDownRefresh() {
this.refresh();
},
onReachBottom() {
this.loadMore();
onShow() {
try{
this.keyword = getApp().globalData.searchText
console.log(this.keyword);
getApp().globalData.searchText = ''
this.setInputText()
}catch(e){
console.log(e);
//TODO handle the exception
}
},
methods: {
load(e) {
console.log(e);
setInputText() {
// 设置 searchInput的 text
// #ifdef APP-PLUS
if (!currentWebview) {
let pages = getCurrentPages();
currentWebview = pages[pages.length - 1].$getAppWebview();
}
currentWebview.setTitleNViewSearchInputText(this.keyword)
// #endif
},
testData(data) {
var testData = []
for (let i = 0; i < 10; i++) {
testData.push(...data)
}
return testData
},
refresh(e) {
console.log(e);
this.tipShow = true
this.formData.status = 'more'
this.isLoading = true
this.$refs.udb.loadData({
handleLoad(data, ended, pagination) {
// `data` 当前查询结果 `ended` 是否有更多数据 `pagination` 分页信息 HBuilderX 3.1.5+ 支持
console.log(9527, data, ended, pagination);
//上拉加载成功
this.loadMoreEnd()
},
searchClick() {
uni.hideKeyboard();
uni.navigateTo({
url: '/pages/list/search/search',
animationType: 'fade-in',
events: {
acceptDataFromOpenedPage(e) {
console.log(e);
}
}
});
},
refresh() {
cdbRef.loadData({
clear: true
}, () => {
this.tipShow = false
this.isLoading = false
uni.stopPullDownRefresh()
this.showRefreshTip = true
setTimeout(() => {
this.showRefreshTip = false
}, 1000)
this.refreshEnd()
})
},
loadMore() {
cdbRef.loadMore({
clear: true
})
},
select() {
this.formData.waterfall = !this.formData.waterfall;
refreshEnd() {
// #ifdef APP-NVUE
this.refreshState = 0
// #endif
uni.stopPullDownRefresh()
},
loadMoreEnd() {
console.log('loadMoreEnd');
},
// #ifdef APP-NVUE
onpullingdown({
pullingDistance,
viewHeight
}) {
if (pullingDistance > viewHeight) {
this.refreshState = 1
}
},
// #endif
},
// #ifndef APP-NVUE
onPullDownRefresh() {
this.refresh()
console.log('refresh');
},
// #endif
onReachBottom() {
this.loadMore()
console.log('触底了');
},
onNavigationBarSearchInputClicked() {
this.searchClick()
},
onNavigationBarSearchInputChanged(e) {
console.log('变了', e);
if (e.text) {
this.where = 'title == ' + e.text
}
}
};
</script>
<style lang="scss" scoped>
.content {
flex: 1;
}
</script>
<style>
.avatar {
width: 200rpx;
height: 200rpx;
margin-right: 10rpx;
}
.tips {
.main {
justify-content: space-between;
}
.title {
width: 480rpx;
}
.info {
flex-direction: row;
justify-content: space-between;
}
.author,
.last_modify_date {
font-size: 28rpx;
color: #999999;
}
.refresh-tip {
color: #67c23a;
font-size: 14px;
line-height: 40px;
......@@ -130,22 +223,52 @@
height: 0;
opacity: 0;
transform: translateY(-100%);
//transition: all 0.3s;
transition: height 0.3s;
}
.main{
justify-content: space-between;
}
.title {
width: 480rpx;
}
.foot{
flex-direction: row;
justify-content: space-between;
.uni-search-box {
background-color: #FFFFFF;
position: sticky;
top: 0;
left: 0;
/* #ifndef APP-PLUS */
z-index:9;
/* #endif */
}
.tips-ani {
.show-refresh-tip {
transform: translateY(0);
height: 40px;
opacity: 1;
}
}
.get-data-state {
width: 750rpx;
align-items: center;
}
.get-data-state-img {
width: 500rpx;
}
.get-data-state-text {
width: 32rpx;
color: #999999;
line-height: 50rpx;
height: 50rpx;
width: 750rpx;
text-align: center;
}
.uni-list {
}
.noMore {
width: 750rpx;
text-align: center;
color: #6E6E6E;
font-size: 26rpx;
height: 55px;
line-height: 55px;
}
</style>
......@@ -296,8 +296,8 @@
},
loadList(text = '') {
getApp().globalData.searchText = text;
uni.navigateTo({
url:'/pages/list/news-list'
uni.switchTab({
url:'/pages/list/list'
})
},
backPage(){
......
<template>
<view>
<cdb-list v-slot="{data}" :reduce-height="0" collection="opendb-feedback" :page-size="3" class="j-list">
<cell v-for="(rows,index) in data" :key="index">
<view style="flex-direction: row;">
<view v-for="item in rows" :key="item._id" class="item">我是内容{{item}}-{{index}}</view>
</view>
</cell>
</cdb-list>
</view>
</template>
<script>
export default {
data() {
return {}
},
onLoad() {
},
methods: {}
}
</script>
<style>
.item {
height: 500px;
width: 750rpx;
background-color: #007AFF;
margin-bottom: 5px;
}
</style>
<template>
<view class="content">
<!-- 功能列表 -->
<uni-list :border="false" class="mt10" v-for="(sublist,index) in agreeList">
<uni-list-item :border="false" class="list-item" v-for="(item,i) in sublist" :key="i" :title="item.title"
:clickable="true" @click="itemClick(item)" :showSwitch="item.showSwitch" :switchChecked="item.isChecked"
:link="!item.showSwitch"
v-if="item.event!='changePwd'||hasLogin"
></uni-list-item>
</uni-list>
<!-- 退出按钮 -->
<!-- 功能列表 -->
<uni-list class="mt10">
<uni-list-item title="个人资料" to="/pages/ucenter/edit/edit" link="navigateTo"></uni-list-item>
<uni-list-item v-if="userInfo.phone" title="修改密码" :to="'/pages/ucenter/login-page/pwd-retrieve/pwd-retrieve?phoneNumber='+ userInfo.phone" link="navigateTo"></uni-list-item>
</uni-list>
<uni-list class="mt10">
<!-- #ifdef APP-PLUS -->
<!-- 检查push过程未结束不显示,push设置项 -->
<uni-list-item title="清理缓存" @click="clearTmp" link></uni-list-item>
<uni-list-item v-if="pushIsOn != 'wait'" @click.native="openSetting()" title="推送功能" showSwitch :switchChecked="pushIsOn"></uni-list-item>
<!-- #endif -->
<uni-list-item v-if="supportMode.includes('fingerPrint')" title="指纹解锁" @click="fingerPrint" link></uni-list-item>
<uni-list-item v-if="supportMode.includes('facial')" title="人脸解锁" @click="facial" link></uni-list-item>
</uni-list>
<!-- 退出/登陆 按钮 -->
<view class="bottom-back" @click="clickLogout">
<text class="bottom-back-text" v-if="hasLogin">退出登录</text>
<text class="bottom-back-text" v-else>登录</text>
......@@ -27,33 +34,9 @@
} from 'vuex';
export default {
data() {
return {
agreeList: [
[{
title: '个人资料',
event: 'toEdit'
},
{
title: '修改密码',
event: 'changePwd'
}
],
[
//#ifdef APP-PLUS
{
title: '推送功能',
name: 'push',
event: 'openSetting',
isChecked: false,
showSwitch: true
},
{
title: '清理缓存',
event: 'clearTmp'
},
//#endif
]
]
return {
supportMode:[],
pushIsOn:"wait"
}
},
computed: {
......@@ -63,10 +46,25 @@
})
},
onLoad() {
this.initSoterAuthentication();
// #ifdef APP-PLUS || MP-WEIXIN
uni.checkIsSupportSoterAuthentication({
success: (res) => {
console.log(res);
this.supportMode = res.supportMode
},
fail: (err) => {
reject(err);
}
})
// #endif
},
onShow() {
this.checkPush();
onShow() {
// 检查手机端获取推送是否开启
//#ifdef APP-PLUS
setTimeout(()=>{
this.pushIsOn = isOn();
},1)
//#endif
},
methods: {
...mapMutations({
......@@ -87,41 +85,14 @@
}
});
},
checkPush() {
// 手机端获取推送是否开启
//#ifdef APP-PLUS
let pushIsOn = isOn();
this.agreeList.forEach(item => {
item.name == 'push' ? (item.isChecked = pushIsOn) : '';
})
//#endif
},
/**
* 添加生物认证选项
*/
initSoterAuthentication() {
// #ifdef APP-PLUS || MP-WEIXIN
let checkAuthModeList = [{
title: '指纹解锁',
name: 'fingerPrint',
event: 'startSoterAuthentication'
}, {
title: '人脸解锁',
name: 'facial',
event: 'startSoterAuthentication'
}];
uni.checkIsSupportSoterAuthentication({
success: (res) => {
res.supportMode.forEach(item => {
this.agreeList.push([checkAuthModeList.find(mode => mode.name == item)]);
})
},
fail: (err) => {
reject(err);
}
})
// #endif
*/
fingerPrint(){
},
facial(){
},
/**
* 开始生物认证
......@@ -210,14 +181,6 @@
});
}
},
/**
* 每一项的点击事件
*/
itemClick(item) {
if (item.event) {
this[item.event](item);
}
},
clearTmp() {
uni.showLoading({
title: '清除中',
......@@ -264,7 +227,8 @@
/**
* 打开设置页面
*/
openSetting() {
openSetting() {
console.log('openSetting');
setting();
}
}
......@@ -326,8 +290,8 @@
background-color: #F9F9F9;
}
.list-item {
.content /deep/ .uni-list-item--disabled,.list-item {
height: 50px;
margin-bottom: 1px;
}
</style>
</style>
\ No newline at end of file
<template>
<view class="center">
<view class="userInfo" @click="toUserInfo">
<image class="logo-img" :src="userInfo.avatar||avatarUrl"></image>
<image class="logo-img" :src="userInfo.avatar||avatarUrl"></image>
<view class="logo-title">
<text class="uer-name">{{userInfo.nickname||userInfo.username||userInfo.mobile||'未登录'}}</text>
<text class="go-login-navigat-arrow navigat-arrow" v-if="!login">&#xe65e;</text>
<uni-icons class="icon" color="#FFFFFF" type="arrowright" v-if="!login" size="16"></uni-icons>
</view>
</view>
<uni-grid class="grid" :column="5" :showBorder="false" :square="true">
......@@ -204,23 +204,11 @@
<style>
/* #ifndef APP-PLUS-NVUE */
@font-face {
font-family: texticons;
font-weight: normal;
font-style: normal;
src: url('~@/static/text-icon.ttf') format('truetype');
}
page {
background-color: #f8f8f8;
}
/* #endif*/
/* 解决头条小程序字体图标不显示问题,因为头条运行时自动插入了span标签,且有全局字体 */
/* #ifdef MP-TOUTIAO */
text :not(view) {
font-family: texticons;
}
/* #endif */
.center {
flex: 1;
flex-direction: column;
......@@ -254,33 +242,15 @@
font-size: 38rpx;
color: #FFFFFF;
}
.go-login-navigat-arrow {
font-size: 38rpx;
color: #FFFFFF;
}
.navigat-arrow {
height: 90rpx;
width: 40rpx;
line-height: 90rpx;
font-size: 34rpx;
color: #FFFFFF;
text-align: right;
font-family: texticons;
}
.center-list {
margin-bottom: 30rpx;
background-color: #f9f9f9;
}
.center-list-cell {
width: 750rpx;
background-color: #007AFF;
height: 40rpx;
}
.grid {
background-color: #FFFFFF;
margin: 25rpx 0;
......
'use strict';
const uniID = require('uni-id')
const uniCaptcha = require('uni-captcha')
const db = uniCloud.database()
const dbCmd = db.command
let params,context,res;
class User {
async quickLogin(){
let {access_token,openid,type} = params
switch (type){
case 'weixin':
let userinfo_res = await uniCloud.httpclient.request('https://api.weixin.qq.com/sns/userinfo',
{
method: 'GET',
dataType:"json",
data:{ access_token,openid}
});
return userinfo_res.data//根据access_token,openid得到userinfo
//检查是否已经注册...
break;
case 'univerify':
return uniID.loginByUniverify({access_token,openid})
break;
case 'apple':
return await uniID.loginByApple(params)
break;
default:
return {"code":100,"msg":"暂不提供"+type+"登陆的云端接口演示"}
break;
}
}
async sendSmsCode(){
// 简单限制一下客户端调用频率
const ipLimit = await db.collection('uni-verify').where({
ip: context.CLIENTIP,
created_at: dbCmd.gt(Date.now() - 60000)
}).get()
if (ipLimit.data.length > 0) {
return {
code: 429,
msg: '请求过于频繁'
}
}
const templateId = '11753' // 替换为自己申请的模板id
if (!templateId) {
return {
code: 500,
msg: 'sendSmsCode需要传入自己的templateId,参考https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=sendsmscode'
}
}
const randomStr = '00000' + Math.floor(Math.random() * 1000000)
const code = randomStr.substring(randomStr.length - 6)
return await uniID.sendSmsCode({
mobile: params.mobile,
code,
type: params.type,
templateId
})
}
}
const userClass = new User();
exports.main = async (event, ctx) => {
[{params},context] = [event,ctx]
//1.判断需要token的action是否有token
/*let noCheckAction = ['register', 'loginByWeixin', 'checkToken','login', 'logout', 'sendSmsCode','loginBySms', 'inviteLogin', 'loginByUniverify','loginByApple', 'createCaptcha', 'verifyCaptcha','refreshCaptcha']
if(!noCheckAction.includes(event.action)) {
if (!event.uniIdToken) {
return {"code":403,"msg":"缺少token"}
}
let payload = {}
payload = await uniID.checkToken(event.uniIdToken)
if (payload.code && payload.code > 0) {
return payload
}
params.uid = payload.uid
}*/
try{
return await userClass[event.action]()||res;
}catch(err){
return {"code":404,"msg":err}
}
}
\ No newline at end of file
{
"name": "user",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"uni-captcha": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id": "file:../../../../uni-id/uniCloud/cloudfunctions/common/uni-id"
}
},
"../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha": {},
"../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center": {},
"../../../../uni-id/uniCloud/cloudfunctions/common/uni-id": {
"dependencies": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
},
"../../../../uni-id/uniCloud/cloudfunctions/common/uni-id/node_modules/uni-config-center": {
"resolved": "../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"link": true
},
"node_modules/uni-captcha": {
"resolved": "../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"link": true
},
"node_modules/uni-config-center": {
"resolved": "../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"link": true
},
"node_modules/uni-id": {
"resolved": "../../../../uni-id/uniCloud/cloudfunctions/common/uni-id",
"link": true
}
},
"dependencies": {
"uni-captcha": {
"version": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha"
},
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"uni-id": {
"version": "file:../../../../uni-id/uniCloud/cloudfunctions/common/uni-id",
"requires": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"dependencies": {
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
}
}
}
{
"name": "user",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"uni-captcha": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id": "file:../../../../uni-id/uniCloud/cloudfunctions/common/uni-id"
}
}
// 本文件中的json内容将在云函数【运行】时作为参数传给云函数。
// 配置教程参考:https://uniapp.dcloud.net.cn/uniCloud/quickstart?id=runparam
{
"action": "sendSmsCode",
"params": {
"phone":"17769516081"
}
}
// 在本文件中可配置云数据库初始化,数据格式见:https://uniapp.dcloud.io/uniCloud/cf-database?id=db_init
// 编写完毕后对本文件点右键,可按配置规则创建表和添加数据
{
"opendb-news-articles-detail": {
"opendb-news-articles": {
"data": [{
"article_status": 1,
"author_avatar": "https://pic.36krcnd.com/avatar/201804/11034847/2w2w8ix0d1x98939.jpg!120",
"author_name": "土土土槽",
"category_id": "223",
"comment_count": 1204,
"title": "阿里小程序IDE官方内嵌uni-app,为开发者提供多端开发服务",
"excerpt": "阿里小程序IDE官方内嵌uni-app,为开发者提供多端开发服务",
"content": "<p>随着微信、阿里、百度、头条、QQ纷纷推出小程序,开发者的开发维护成本持续上升,负担过重。这点已经成为共识,现在连小程序平台厂商也充分意识到了。</p>\n<p>阿里小程序团队,为了减轻开发者的负担,在官方的小程序开发者工具中整合了多端框架。</p>\n<p>经过阿里团队仔细评估,uni-app 在产品完成度、跨平台支持度、开发者社区、可持续发展等多方面优势明显,最终选定 uni-app内置于阿里小程序开发工具中,为开发者提供多端开发解决方案。</p>\n<p>经过之前1个月的公测,10月10日,阿里小程序正式发布0.70版开发者工具,通过 uni-app 实现多端开发,成为本次版本更新的亮点功能!</p>\n<p>如下图,在阿里小程序工具左侧主导航选择 uni-app,创建项目,即可开发。</p>\n<div class=\"aw-comment-upload-img-list active\"><img class=\"img-polaroid\" width=\"100%\" src=\"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e/b698232a-e608-4b2d-8019-8dc6bbf343a8.png\" /></div>\n<p><br />阿里小程序开发工具更新说明详见:https://docs.alipay.com/mini/ide/0.70-stable</p>\n<p>&nbsp;</p>\n<p>集成uni-app,这对于阿里团队而言,并不是一个容易做出的决定。毕竟 uni-app 是一个三方产品,要经过复杂的评审流程。</p>\n<p>这一方面突显出阿里团队以开发者需求为本的优秀价值观,另一方面也证明 uni-app的产品确实过硬。</p>\n<p>很多开发者都有多端需求,但又没有足够精力去了解、评估 uni-app,而处于观望态度。现在大家可以更放心的使用 uni-app 了,它没有让阿里失望,也不会让你失望。</p>\n<p>自从uni-app推出以来,DCloud也取得了高速的发展,目前拥有370万开发者,框架运行在4.6亿手机用户设备上,月活达到1.35亿(仅包括部分接入DCloud统计平台的数据)。并且数据仍在高速增长中,在市场占有率上处于遥遥领先的位置。</p>\n<p>本次阿里小程序工具集成 uni-app,会让 uni-app 继续快速爆发,取得更大的成功。</p>\n<p>后续DCloud还将深化与阿里的合作,在serverless等领域给开发者提供更多优质服务。</p>\n<p>使用多端框架开发各端应用,是多赢的模式。开发者减轻了负担,获得了更多新流量。而小程序平台厂商,也能保证自己平台上的各种应用可以被及时的更新。</p>\n<p>DCloud欢迎更多小程序平台厂商,与我们一起合作,为开发者、平台、用户的多赢而努力。</p>\n<p>进一步了解uni-app,详见:https://uniapp.dcloud.io</p>\n<p>欢迎扫码关注DCloud公众号,转发消息到朋友圈。<br /><img src=\"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e/27302ea5-e369-4e6c-89b1-431408497b3d.jpg\" width=\"80%\" /></p>",
"avatar": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-aliyun-gacrhzeynhss7c6d04/249516a0-3941-11eb-899d-733ae62bed2f.jpg",
"type": 0,
"user_id": "6082cc262c8abc0001683cc9",
"comment_count": 0,
"like_count": 0,
"comment_status": 0,
"content": "<p>编者按:本文来自微信公众号<a href=\"https://mp.weixin.qq.com/s/ahOP6VxdxWfxG70KBgh2JA\">“土土土槽”(ID:xtutux6)</a>,作者:林安,36氪经授权发布。</p><p>大家好,我是林安。今天想跟你们聊一聊自由职业者的五险一金问题。</p><p>因为这个是想做自由职业的朋友问我最多的问题,也是自由职业初期必须思考的问题,所以今天就展开给大家讲讲。</p><p>在成为自由职业者以前,我没有任何一次裸辞经历,基本都是找好下家才跳槽,所以你应该看得出来,我不是一名“冲动型”选手,做任何决定前一定会三思后行。</p><p>所以产生了辞职想法后,我着手准备的第一件事,就是“搞清楚社保和公积金怎么交”,“断交或不交会对我的生活产生哪些影响”。</p><p>这些也是想做自由职业的朋友问我最多的问题。下面我就结合自己的实际情况,分4部分给大家讲讲。</p><p><img style=\"max-width:100%\" src=\"https://img.36krcdn.com/20200410/v2_823646324f724d8b9dae3ae9d06a7d1a_img_000\" data-img-size-val=\"720,480\"/></p><h2 >&nbsp;一、自由职业后,还有必要交五险一金吗?</h2><p>其实五险一金中最重要的是“养老险”和“医疗险”。</p><p>一般社保交满15年后养老保险终身有效,养老金也是“多缴多得、长缴多得”;</p><p>社保交满25年后,医疗保险终身有效,看病时医保可以报销一部分费用。</p><p>关于“五险一金”交或不交,要根据不同人的不同情况来看。</p><p>如果你在户口所在地的城市工作,且在本地已有房,那么交不交、交多少完全看你需求。</p><p>如果你已经购买了性价比更高的商业保险,并且没有在本地再买房的打算,那你可以不交五险一金。</p><p>当然,如果你还是想买一份国民保险,图个安心,或者害怕以后办理一些事情被社保拖住(毕竟不同城市的政策也是经常变动),也可以自己在当地的社保局缴纳。</p><p>但如果你是外地户口且没有当地房产,将来又想在所在地买房落户,那我建议你最好继续缴纳五险,因为社保和大部分城市的落户买房政策挂钩,交不满年限,就无法买房落户。</p><p>而且五险中除了养老保险可以断交之外,其他一旦停交则不能补缴,需要重新开始再次缴纳。</p><p>我当初就是考虑到应该会在上海定居,才决定继续在上海缴纳五险的。</p><p>至于公积金,如果你近1年内没有买房需求,可以暂时不缴,等到你决定买房了,再开始重新缴纳也不迟。</p><p>停缴的公积金账户会暂时封存,等你重新交的时候,再去公积金中心解封就可以了。</p><p>但如果你没有在所在地定居的打算,比如很多朋友想年轻的时候在一线城市工作生活,等年纪大了或结婚成家了再退居二三线城市工作,这种情况其实可以不在当地缴纳社保,在户籍所在地缴纳或者给自己买一份商业保险也是OK的。</p><p><img style=\"max-width:100%\" src=\"https://img.36krcdn.com/20200410/v2_c46a9fc0cc444f118738fe09727de874_img_000\" data-img-size-val=\"720,480\"/></p><h2 >&nbsp;二、不交五险一金会有什么影响?</h2><p>公积金断交只对已经存在公积金贷款者或者准备申请公积金贷款者存在影响,其他则无直接影响。</p><p>如果你还有房贷要还却断交公积金,那么剩余的贷款就会按照商业利率来计算了。</p><p>社保断交的影响主要有2点:</p><h3 >1.影响医疗报销和报销上限</h3><p>很多城市社保只要断缴一个月,第二月就不能继续使用了。</p><p>而且有的城市政策是连续缴纳社保的时间越久,报销上限就越高。这个具体看不同城市的政策,你们可以自行上网查一下。</p><h3 >2.影响买房买车、积分落户、孩子上学</h3><p>部分热门城市要求社保交够一定年份,才可以买房买车。</p><p>还有的一线城市会把社保缴纳的额度和积分落户、孩子上学捆绑在一起,比如北上广深。</p><p>我所在的上海,就是连续缴纳5年社保才有资格买房买车,交满7年,才有资格申请落户。</p><p><img style=\"max-width:100%\" src=\"https://img.36krcdn.com/20200410/v2_6f15c618ffda491bba453443419de4b8_img_000\" data-img-size-val=\"720,521\"/></p><h2 >&nbsp;三、自由职业者如何缴纳五险一金?</h2><p>那么自由职业者应该如何缴纳五险一金呢?常见的方式有4种。</p><h3 >1.自己去社保局和公积金中心以灵活就业的方式缴纳</h3><p>这种只适合本地户籍的人,社保部分也只能缴纳养老保险和医疗保险。</p><p>你们可以根据自身的需求和具体状况,自由选择社保和公积金的缴费级别。</p><h3 >2.如果你是外地户籍,可以找第三方机构代缴</h3><p>通过这种方式缴纳的社保一般会像上班时一样把五险交齐,不同的是以前公司替员工缴纳的部分也要你自己承担。</p><p>现在网上类似的代理机构挺多的,支付宝、淘宝和小程序上有很多,一般每个月会收几十块的代理费。</p><p>记得一定要找正规、有名的机构,每次交完以后,也要登陆自己的账号查询是否有按时缴纳。</p><h3 >3.挂靠在朋友公司下缴纳</h3><p>如果你有朋友本身就是开公司的,可以把你的社保挂靠在他们公司下缴纳,这种比找机构省点钱,但是公司缴纳的部分金额也完全是你自己承担的。</p><h3 >4.开一家公司自行缴纳</h3><p>这种比较适合有稳定客户且业务量比较大的自由职业者,公司的用途除了可以帮你缴纳五险一金外,还可以给客户开正规发票。</p><p>但开一家公司又会面临很多税务上的问题,不太建议自由职业新人或收入不稳定的自由职业者开公司。具体原因我和自由税务师骆盈合开的课程里有详细讲到(传送门:<a href=\"http://mp.weixin.qq.com/s?__biz=MzA4MTYxNzMzNQ==&mid=2650502058&idx=1&sn=35885aa82b5555507197cf3b073daf1d&chksm=879d9b72b0ea12646260778c2b4720883703d9ecc29bc9363166c3a63ffcc2cee3064f8efe66&scene=21#wechat_redirect\">不懂一点税务知识,你“敢”成为自由职业者吗?</a>)。</p><p><img style=\"max-width:100%\" src=\"https://img.36krcdn.com/20200410/v2_68e061036408488fbbe2bb8b6295d703_img_000\" data-img-size-val=\"720,480\"/></p><p>以上,就是不同类型自由职业者交五险一金的方式了。我目前的情况是挂靠在朋友公司下缴纳五险,公积金暂时断交了,等到要买房的时候再重新开始交。</p><p>大家可以根据自己的实际情况选择交或不交,以及缴纳方式和数额。</p>",
"cover": "https://img.36krcdn.com/20200410/v2_d748f06964014bafbd0e9cd9371de6d1_img_000?x-oss-process=image/resize,m_mfit,w_432,h_288/crop,w_432,h_288,g_center",
"excerpt": "自由职业前,应该想清楚的问题。",
"is_essence": false,
"is_sticky": false,
"last_comment_user_id": "",
"last_modify_date": "2020-04-10 18:44:10",
"last_modify_ip": "",
"like_count": 4603,
"publish_date": "2020-04-10 18:39:00",
"publish_ip": "",
"title": "干货:自由职业者如何搞定五险一金?",
"user_id": 121321,
"user_name": "未来汽车日报",
"view_count": 32053
}],
"schema": {
"bsonType": "object",
"required": [
"user_id",
"title",
"content",
"article_status",
"view_count",
"like_count",
"is_sticky",
"is_essence",
"comment_status",
"comment_count",
"mode"
],
"permission": {
"read": true,
"create": "auth.uid != null",
"update": "doc.uid == auth.uid",
"delete": "doc.uid == auth.uid"
},
"properties": {
"_id": {
"description": "存储文档 ID(用户 ID),系统自动生成"
},
"user_id": {
"bsonType": "string",
"description": "文章作者ID, 参考`uni-id-users` 表"
},
"category_id": {
"bsonType": "string",
"description": "分类 id,参考`uni-news-categories`表"
},
"title": {
"bsonType": "string",
"description": "标题",
"label": "标题"
},
"content": {
"bsonType": "string",
"description": "文章内容",
"label": "文章内容"
},
"excerpt": {
"bsonType": "string",
"description": "文章摘录",
"label": "摘要"
},
"article_status": {
"bsonType": "int",
"minimum": 0,
"maximum": 1,
"description": "文章状态:0 草稿箱 1 已发布"
},
"view_count": {
"bsonType": "int",
"description": "阅读数量",
"permission": {
"write": false
}
},
"like_count": {
"bsonType": "int",
"description": "喜欢数、点赞数",
"permission": {
"write": false
}
},
"is_sticky": {
"bsonType": "bool",
"description": "是否置顶",
"permission": {
"write": false
}
},
"is_essence": {
"bsonType": "bool",
"description": "阅读加精",
"permission": {
"write": false
}
},
"comment_status": {
"bsonType": "int",
"minimum": 0,
"maximum": 1,
"description": "评论状态:0 关闭 1 开放"
},
"comment_count": {
"bsonType": "int",
"description": "评论数量",
"permission": {
"write": false
}
},
"last_comment_user_id": {
"bsonType": "string",
"description": "最后回复用户 id,参考`uni-id-users` 表"
},
"avatar": {
"bsonType": "string",
"description": "缩略图地址",
"label": "封面大图"
},
"publish_date": {
"bsonType": "timestamp",
"description": "发表时间",
"defaultValue": {
"$env": "now"
}
},
"publish_ip": {
"bsonType": "string",
"description": "发表时 IP 地址",
"forceDefaultValue": {
"$env": "clientIP"
}
},
"last_modify_date": {
"bsonType": "timestamp",
"description": "最后修改时间"
},
"last_modify_ip": {
"bsonType": "string",
"description": "最后修改时 IP 地址"
},
"mode": {
"bsonType": "number",
"description": "排版显示模式"
}
}
}
"article_status": 1,
"publish_date": 1616092287006,
"last_modify_date": 1616092303031,
"create_time": "2021-03-19T08:25:06.109Z"
}]
},
"opendb-app-versions":{
"data":[
{
"is_silently": false,
"is_mandatory": false,
"appid": "__UNI__03B096E",
"name": "base-app",
"title": "新增升级中心",
"contents": "新增升级中心",
"platform": [
"Android"
],
"version": "1.0.1",
"url": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3469aac7-a663-4c5d-8ee8-94275f8c09ab/3128d010-01c5-4121-a1d6-f3f919944a23.apk",
"stable_publish": false,
"type": "native_app",
"create_date": 1616771628150
}
]
"opendb-app-versions": {
"data": [{
"is_silently": false,
"is_mandatory": false,
"appid": "__UNI__03B096E",
"name": "base-app",
"title": "新增升级中心",
"contents": "新增升级中心",
"platform": [
"Android"
],
"version": "1.0.1",
"url": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3469aac7-a663-4c5d-8ee8-94275f8c09ab/3128d010-01c5-4121-a1d6-f3f919944a23.apk",
"stable_publish": false,
"type": "native_app",
"create_date": 1616771628150
}]
}
}
{
"bsonType": "object",
"permission": {
"create": false,
"delete": false,
"read": "doc.is_on_sale == true",
"update": false
},
"properties": {
"_id": {
"description": "存储文档 ID(商品 ID),系统自动生成"
},
"add_date": {
"bsonType": "timestamp",
"defaultValue": {
"$env": "now"
},
"description": "上架时间"
},
"category_id": {
"bsonType": "string",
"description": "分类 id,参考`opendb-mall-categories`表",
"foreignKey": "opendb-mall-categories._id"
},
"comment_count": {
"bsonType": "int",
"description": "累计评论数"
},
"goods_banner_imgs": {
"bsonType": "array",
"description": "商品详情页的banner图地址"
},
"goods_desc": {
"bsonType": "string",
"description": "商品详细描述",
"title": "详细描述",
"trim": "both"
},
"goods_sn": {
"bsonType": "string",
"description": "商品的唯一货号",
"title": "货号",
"trim": "both"
},
"goods_thumb": {
"bsonType": "string",
"description": "商品缩略图,用于在列表或搜索结果中预览显示",
"pattern": "^(http:\/\/|https:\/\/|\/|.\/|@\/)\\S",
"title": "缩略图地址",
"trim": "both"
},
"is_alone_sale": {
"bsonType": "bool",
"description": "是否能单独销售;如果不能单独销售,则只能作为某商品的配件或者赠品销售"
},
"is_best": {
"bsonType": "bool",
"description": "是否精品"
},
"is_hot": {
"bsonType": "bool",
"description": "是否热销"
},
"is_new": {
"bsonType": "bool",
"description": "是否新品",
"title": "是否新品"
},
"is_on_sale": {
"bsonType": "bool",
"description": "是否上架销售",
"title": "是否上架"
},
"is_real": {
"bsonType": "bool",
"description": "是否实物",
"title": "是否为实物"
},
"keywords": {
"bsonType": "string",
"description": "商品关键字,为搜索引擎收录使用",
"title": "关键字",
"trim": "both"
},
"last_modify_date": {
"bsonType": "timestamp",
"defaultValue": {
"$env": "now"
},
"description": "最后修改时间"
},
"month_sell_count": {
"bsonType": "int",
"description": "月销量"
},
"name": {
"bsonType": "string",
"description": "商品名称",
"title": "名称",
"trim": "both"
},
"remain_count": {
"bsonType": "int",
"description": "库存数量",
"title": "库存数量"
},
"seller_note": {
"bsonType": "string",
"description": "商家备注,仅商家可见",
"permission": {
"read": false
},
"trim": "both"
},
"total_sell_count": {
"bsonType": "int",
"description": "总销量"
}
},
"required": ["goods_sn", "name", "remain_count", "month_sell_count", "total_sell_count", "comment_count", "is_real",
"is_on_sale", "is_alone_sale", "is_best", "is_new", "is_hot"
]
}
{
"bsonType": "object",
"permission": {
"create": "auth.uid != null",
"delete": "doc.uid == auth.uid",
"read": true,
"update": "doc.uid == auth.uid"
},
"properties": {
"_id": {
"description": "存储文档 ID(用户 ID),系统自动生成"
},
"article_status": {
"bsonType": "int",
"description": "文章状态:0 草稿箱 1 已发布",
"maximum": 1,
"minimum": 0
},
"avatar": {
"bsonType": "string",
"description": "缩略图地址",
"label": "封面大图"
},
"category_id": {
"bsonType": "string",
"description": "分类 id,参考`uni-news-categories`表"
},
"comment_count": {
"bsonType": "int",
"description": "评论数量",
"permission": {
"write": false
}
},
"comment_status": {
"bsonType": "int",
"description": "评论状态:0 关闭 1 开放",
"maximum": 1,
"minimum": 0
},
"content": {
"bsonType": "string",
"description": "文章内容",
"label": "文章内容"
},
"excerpt": {
"bsonType": "string",
"description": "文章摘录",
"label": "摘要"
},
"is_essence": {
"bsonType": "bool",
"description": "阅读加精",
"permission": {
"write": false
}
},
"is_sticky": {
"bsonType": "bool",
"description": "是否置顶",
"permission": {
"write": false
}
},
"last_comment_user_id": {
"bsonType": "string",
"description": "最后回复用户 id,参考`uni-id-users` 表"
},
"last_modify_date": {
"bsonType": "timestamp",
"description": "最后修改时间"
},
"last_modify_ip": {
"bsonType": "string",
"description": "最后修改时 IP 地址"
},
"like_count": {
"bsonType": "int",
"description": "喜欢数、点赞数",
"permission": {
"write": false
}
},
"mode": {
"bsonType": "number",
"description": "排版显示模式"
},
"publish_date": {
"bsonType": "timestamp",
"defaultValue": {
"$env": "now"
},
"description": "发表时间"
},
"publish_ip": {
"bsonType": "string",
"description": "发表时 IP 地址",
"forceDefaultValue": {
"$env": "clientIP"
}
},
"title": {
"bsonType": "string",
"description": "标题",
"label": "标题"
},
"user_id": {
"bsonType": "string",
"description": "文章作者ID, 参考`uni-id-users` 表"
},
"view_count": {
"bsonType": "int",
"description": "阅读数量",
"permission": {
"write": false
}
}
},
"required": ["user_id", "title", "content", "article_status", "view_count", "like_count", "is_sticky", "is_essence",
"comment_status", "comment_count", "mode"
]
}
......@@ -19,16 +19,6 @@
"$env": "uid"
}
},
"author": {
"bsonType": "string",
"title": "作者信息",
"description": "冗余字段,用于保存联表查询结果,参考`uni-id-users`表",
"foreignKey": "uni-id-users._id",
"enum": {
"collection": "uni-id-users",
"field": "username, _id as text, _id as value"
}
},
"category_id": {
"bsonType": "string",
"title": "分类",
......
{
"bsonType": "object",
"permission": {
"read": "doc.is_reply == false",
"read": true, //"doc.is_reply == false",
"create": true,
"update": false,
"delete": false
......
......@@ -60,7 +60,6 @@
font-family: uniicons;
src: url('./uni.ttf') format('truetype');
}
/* #endif */
.uni-icons {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册