init
This commit is contained in:
BIN
components/.DS_Store
vendored
Normal file
BIN
components/.DS_Store
vendored
Normal file
Binary file not shown.
9
components/api.js
Normal file
9
components/api.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const API = 'https://www.frbkw.com';
|
||||
// export const API = 'http://wp:7888';
|
||||
export const Getfriend = API + '/wp-content/plugins/fengrui-xingshu/fengrui-friend.php';
|
||||
export const Getwatch = API + '/wp-content/plugins/fengrui-xingshu/fengrui-watch.php';
|
||||
export const Getdstrict = API + '/wp-content/plugins/fengrui-xingshu/fengrui-dstrict.php';
|
||||
|
||||
|
||||
// 本程序SVG来自
|
||||
// <a href="https://storyset.com/coronavirus">Coronavirus illustrations by Storyset</a>
|
||||
194
components/gb-choujiang/gb-choujiang.vue
Normal file
194
components/gb-choujiang/gb-choujiang.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<view style="background-color:#1015F2; height: 1400rpx;">
|
||||
<!-- 背景图 -->
|
||||
<view class="wh-w750" style=" position: relative;">
|
||||
<image :src="nImg" mode="aspectFill"
|
||||
style="height: 1338rpx; width: 750rpx;"></image>
|
||||
</view>
|
||||
<!-- 转盘 -->
|
||||
<view class="luck-list"
|
||||
style="position: absolute;top: 415rpx;right: 0;left: 72rpx; width: 600rpx;">
|
||||
<view v-for="(item,index) in AwardList" :key="index">
|
||||
<view class="cell-item" :class="index==4||index===sel?'y-b':'w-b'"
|
||||
@tap="begin(index)">
|
||||
<view class="cell-content"
|
||||
:class="index==4||index===sel?'y':'w'">
|
||||
<image :src="item.image" mode="widthFix" style="width: 60rpx; height: 60rpx;" v-if="item.image"></image>
|
||||
<view style="margin-top: 5rpx;">
|
||||
<text style="font-size: 21rpx; font-weight: bold;">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 背景图
|
||||
nImg: String,
|
||||
// 列表
|
||||
AwardList:{
|
||||
type:Array,
|
||||
default:[]
|
||||
},
|
||||
// 中奖索引
|
||||
sjNum:{
|
||||
type:Number,
|
||||
default:0
|
||||
},
|
||||
// 判断免费抽奖,广告抽奖
|
||||
lotteryType:{
|
||||
type:Number,
|
||||
default:0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sel: '',
|
||||
FastNums: 0,
|
||||
SlowNums: 0,
|
||||
time: 1000,
|
||||
LoopStatus: true,
|
||||
noClick: true
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
// 中奖后的逻辑
|
||||
updateMoney(sjNum){
|
||||
this.$emit('updateMoney',sjNum)
|
||||
},
|
||||
// 开始抽奖
|
||||
begin(index) {
|
||||
if(this.lotteryType == 0){
|
||||
this.nowBegin(index)
|
||||
}else if(this.lotteryType == 1){
|
||||
this.$emit('lookAd',index)
|
||||
}else if(this.lotteryType == 2){
|
||||
this.$emit('noChance')
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: '出错啦 !',
|
||||
icon: "none",
|
||||
position: 'bottom'
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
nowBegin(index){
|
||||
if (index == 4) {
|
||||
if (!this.noClick) {
|
||||
uni.showToast({
|
||||
title: '请勿频繁点击',
|
||||
icon: "none",
|
||||
position: 'bottom'
|
||||
});
|
||||
return
|
||||
};
|
||||
this.noClick = false;
|
||||
this.FastNums = 0
|
||||
this.SlowNums = 0
|
||||
this.time = 200
|
||||
this.LoopStatus = true
|
||||
this.loop()
|
||||
}
|
||||
},
|
||||
// 抽奖过程 控制
|
||||
loop() {
|
||||
let sjNum = this.sjNum
|
||||
//console.log(sjNum)
|
||||
if (!this.sel || this.sel < 9) {
|
||||
if (this.sel == 3) {
|
||||
this.sel = 0
|
||||
} else if (this.sel === '') {
|
||||
this.sel = 0
|
||||
} else if (this.sel == 2) {
|
||||
this.sel = 5
|
||||
} else if (this.sel == 5) {
|
||||
this.sel = 8
|
||||
} else if (this.sel == 8) {
|
||||
this.sel = 7
|
||||
} else if (this.sel == 7) {
|
||||
this.sel = 6
|
||||
} else if (this.sel == 6) {
|
||||
this.sel = 3
|
||||
} else {
|
||||
this.sel++
|
||||
}
|
||||
this.FastNums++
|
||||
if (this.FastNums == 4) {
|
||||
this.FastNums = 0
|
||||
this.time = 50
|
||||
this.SlowNums++
|
||||
}
|
||||
if (this.SlowNums == 8) {
|
||||
this.SlowNums = 0
|
||||
this.time = 500
|
||||
this.FastNums = 5
|
||||
}
|
||||
if (this.FastNums > 5) {
|
||||
if (this.sel == sjNum) {
|
||||
this.noClick = true;
|
||||
this.LoopStatus = false
|
||||
// 成功的逻辑
|
||||
this.updateMoney(sjNum)
|
||||
}
|
||||
}
|
||||
if (this.LoopStatus) {
|
||||
setTimeout(() => {
|
||||
this.loop()
|
||||
}, this.time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 抽奖 */
|
||||
.luck-list{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.cell-item {
|
||||
width: 150rpx;
|
||||
height: 158rpx;
|
||||
border-radius: 30rpx;
|
||||
margin: 5rpx;
|
||||
}
|
||||
|
||||
.cell-content {
|
||||
width: 150rpx;
|
||||
height: 138rpx;
|
||||
border-radius: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.w {
|
||||
background-color: #fdf2ee;
|
||||
}
|
||||
|
||||
.w-b {
|
||||
background-color: #f8d0c3;
|
||||
}
|
||||
.y {
|
||||
background-color: #fee610;
|
||||
}
|
||||
|
||||
.y-b {
|
||||
background-color: #EFCD22;
|
||||
}
|
||||
</style>
|
||||
128
components/lun-bo-tu/lun-bo-tu.vue
Normal file
128
components/lun-bo-tu/lun-bo-tu.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<view class="shape">
|
||||
<swiper class="image-container example-body-item uni-swiper-wrapper" previous-margin="43rpx" next-margin="43rpx" circular @change="swiperChange" :indicator-dots="false" interval="2500" autoplay>
|
||||
<swiper-item :class="currentIndex == index ? 'swiper-item' : 'swiper-item-side'" v-for="(item, index) in imgList" :key="item[urlKey]">
|
||||
<image @click="clickImg(item)" :class="currentIndex == index ? 'item-img' : 'item-img-side'" :src="item.image" lazy-load :style="dontFirstAnimation ? 'animation: none;' : ''" mode="aspectFill"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
imgList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
urlKey: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0,
|
||||
dontFirstAnimation: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
swiperChange(e) {
|
||||
this.dontFirstAnimation = false
|
||||
this.currentIndex = e.detail.current
|
||||
},
|
||||
clickImg(item) {
|
||||
this.$emit('selected', item, this.currentIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
|
||||
.example-body-item {
|
||||
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.uni-swiper-wrapper {
|
||||
|
||||
// /deep/ 深度选择器,使用后才可选 uni-swiper-dots 和 uni-swiper-dot
|
||||
|
||||
/deep/ .uni-swiper-dots {
|
||||
top: 310rpx;
|
||||
}
|
||||
|
||||
/deep/ .uni-swiper-dot {
|
||||
width: 25upx !important;
|
||||
height: 9upx !important;
|
||||
border-radius: 4rpx;
|
||||
background: #C0C0C0 !important;
|
||||
}
|
||||
|
||||
/deep/ .uni-swiper-dot-active {
|
||||
width: 40upx !important;
|
||||
height: 8upx !important;
|
||||
background: #42E2CD !important;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.image-container {
|
||||
height: 360rpx;
|
||||
}
|
||||
|
||||
.item-img {
|
||||
width: 650rpx;
|
||||
height: 360rpx;
|
||||
border-radius: 14rpx;
|
||||
animation: to-big .3s;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
width: 650rpx;
|
||||
height: 360rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.item-img-side {
|
||||
width: 650rpx;
|
||||
height: 320rpx;
|
||||
border-radius: 14rpx;
|
||||
animation: to-mini .5s;
|
||||
}
|
||||
|
||||
.swiper-item-side {
|
||||
width: 650rpx;
|
||||
height: 320rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@keyframes to-mini
|
||||
{
|
||||
from {
|
||||
height: 360rpx;
|
||||
}
|
||||
to {
|
||||
height: 320rpx;
|
||||
}
|
||||
}
|
||||
@keyframes to-big
|
||||
{
|
||||
from {
|
||||
height: 320rpx;
|
||||
}
|
||||
to {
|
||||
height: 360rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
components/mp-html/.DS_Store
vendored
Normal file
BIN
components/mp-html/.DS_Store
vendored
Normal file
Binary file not shown.
262
components/mp-html/audio/audio.vue
Normal file
262
components/mp-html/audio/audio.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<view v-if="controls" class="_contain">
|
||||
<!-- 海报和按钮 -->
|
||||
<view class="_poster" :style="'background-image:url('+poster+')'">
|
||||
<view class="_button" @tap="_buttonTap">
|
||||
<view :class="playing?'_pause':'_play'" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 曲名和作者 -->
|
||||
<view class="_title">
|
||||
<view class="_name">{{name||'未知音频'}}</view>
|
||||
<view class="_author">{{author||'未知作者'}}</view>
|
||||
</view>
|
||||
<!-- 进度条 -->
|
||||
<slider class="_slider" activeColor="#585959" block-size="12" handle-size="12" :disabled="error" :value="value" @changing="_seeking" @change="_seeked" />
|
||||
<!--播放时间-->
|
||||
<view class="_time">{{time||'00:00'}}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @fileoverview audio 组件
|
||||
*/
|
||||
import context from './context'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
error: false,
|
||||
playing: false,
|
||||
time: '00:00',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
aid: String,
|
||||
name: String, // 音乐名
|
||||
author: String, // 作者
|
||||
poster: String, // 海报图片地址
|
||||
autoplay: [Boolean, String], // 是否自动播放
|
||||
controls: [Boolean, String], // 是否显示控件
|
||||
loop: [Boolean, String], // 是否循环播放
|
||||
src: String // 源地址
|
||||
},
|
||||
watch: {
|
||||
src (src) {
|
||||
this.setSrc(src)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this._ctx = uni.createInnerAudioContext()
|
||||
this._ctx.onError((err) => {
|
||||
this.error = true
|
||||
this.$emit('error', err)
|
||||
})
|
||||
this._ctx.onTimeUpdate(() => {
|
||||
const time = this._ctx.currentTime
|
||||
const min = parseInt(time / 60)
|
||||
const sec = Math.ceil(time % 60)
|
||||
this.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
|
||||
if (!this.lastTime) {
|
||||
this.value = time / this._ctx.duration * 100 // 不在拖动状态下
|
||||
}
|
||||
})
|
||||
this._ctx.onEnded(() => {
|
||||
if (!this.loop) {
|
||||
this.playing = false
|
||||
}
|
||||
})
|
||||
context.set(this.aid, this)
|
||||
this.setSrc(this.src)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this._ctx.destroy()
|
||||
context.remove(this.properties.audioId)
|
||||
},
|
||||
onPageShow () {
|
||||
if (this.playing && this._ctx.paused) {
|
||||
this._ctx.play()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 设置源
|
||||
setSrc (src) {
|
||||
this._ctx.autoplay = this.autoplay
|
||||
this._ctx.loop = this.loop
|
||||
this._ctx.src = src
|
||||
if (this.autoplay && !this.playing) {
|
||||
this.playing = true
|
||||
}
|
||||
},
|
||||
// 播放
|
||||
play () {
|
||||
this._ctx.play()
|
||||
this.playing = true
|
||||
this.$emit('play', {
|
||||
target: {
|
||||
id: this.aid
|
||||
}
|
||||
})
|
||||
},
|
||||
// 暂停
|
||||
pause () {
|
||||
this._ctx.pause()
|
||||
this.playing = false
|
||||
this.$emit('pause')
|
||||
},
|
||||
// 移动进度条
|
||||
seek (sec) {
|
||||
this._ctx.seek(sec)
|
||||
},
|
||||
// 内部方法
|
||||
_buttonTap () {
|
||||
if (this.playing) this.pause()
|
||||
else this.play()
|
||||
},
|
||||
_seeking (e) {
|
||||
// 避免过于频繁 setData
|
||||
if (e.timeStamp - this.lastTime < 200) return
|
||||
const time = Math.round(e.detail.value / 100 * this._ctx.duration)
|
||||
const min = parseInt(time / 60)
|
||||
const sec = time % 60
|
||||
this.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
|
||||
this.lastTime = e.timeStamp
|
||||
},
|
||||
_seeked (e) {
|
||||
this.seek(e.detail.value / 100 * this._ctx.duration)
|
||||
this.lastTime = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 顶层容器 */
|
||||
._contain {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 290px;
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 播放、暂停按钮 */
|
||||
._button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
background-color: rgb(0, 0, 0, 0.2);
|
||||
border: 1px solid white;
|
||||
border-radius: 50%;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
._play {
|
||||
margin-left: 2px;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 8px solid white;
|
||||
}
|
||||
|
||||
._pause {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* 海报 */
|
||||
._poster {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background-color: #e6e6e6;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
/* 标题栏 */
|
||||
._title {
|
||||
flex: 1;
|
||||
margin: 4px 0 0 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
._author {
|
||||
width: 45px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
._name {
|
||||
width: 140px;
|
||||
font-size: 15px;
|
||||
line-height: 39px;
|
||||
}
|
||||
|
||||
._author,
|
||||
._name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
._slider {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 8px;
|
||||
width: 140px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 播放时间 */
|
||||
._time {
|
||||
margin: 7px 14px 0 0;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 响应式布局,大屏幕用更大的尺寸 */
|
||||
@media (min-width: 400px) {
|
||||
._contain {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
._button {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
._poster {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
._author {
|
||||
width: 60px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
._name {
|
||||
width: 180px;
|
||||
font-size: 19px;
|
||||
line-height: 55px;
|
||||
}
|
||||
|
||||
._slider {
|
||||
right: 20px;
|
||||
bottom: 10px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
._time {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
7
components/mp-html/audio/context.js
Normal file
7
components/mp-html/audio/context.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const ctx = {}
|
||||
|
||||
export default {
|
||||
get: id => ctx[id],
|
||||
set: (id, vm) => { ctx[id] = vm },
|
||||
remove: id => { ctx[id] = undefined }
|
||||
}
|
||||
34
components/mp-html/audio/index.js
Normal file
34
components/mp-html/audio/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @fileoverview audio 插件
|
||||
*/
|
||||
import context from './context'
|
||||
let index = 0
|
||||
|
||||
function Audio (vm) {
|
||||
this.vm = vm
|
||||
}
|
||||
|
||||
Audio.prototype.onUpdate = function () {
|
||||
this.audios = []
|
||||
}
|
||||
|
||||
Audio.prototype.onParse = function (node) {
|
||||
if (node.name === 'audio') {
|
||||
if (!node.attrs.id) {
|
||||
node.attrs.id = 'a' + index++
|
||||
}
|
||||
this.audios.push(node.attrs.id)
|
||||
}
|
||||
}
|
||||
|
||||
Audio.prototype.onLoad = function () {
|
||||
setTimeout(() => {
|
||||
for (let i = 0; i < this.audios.length; i++) {
|
||||
const ctx = context.get(this.audios[i])
|
||||
ctx.id = this.audios[i]
|
||||
this.vm._videos.push(ctx)
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
export default Audio
|
||||
203
components/mp-html/emoji/index.js
Normal file
203
components/mp-html/emoji/index.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @fileoverview emoji 插件
|
||||
*/
|
||||
const reg = /\[(\S+?)\]/g
|
||||
const data = {
|
||||
笑脸: '😄',
|
||||
生病: '😷',
|
||||
破涕为笑: '😂',
|
||||
吐舌: '😝',
|
||||
脸红: '😳',
|
||||
恐惧: '😱',
|
||||
失望: '😔',
|
||||
无语: '😒',
|
||||
眨眼: '😉',
|
||||
酷: '😎',
|
||||
哭: '😭',
|
||||
痴迷: '😍',
|
||||
吻: '😘',
|
||||
思考: '🤔',
|
||||
困惑: '😕',
|
||||
颠倒: '🙃',
|
||||
钱: '🤑',
|
||||
惊讶: '😲',
|
||||
白眼: '🙄',
|
||||
叹气: '😤',
|
||||
睡觉: '😴',
|
||||
书呆子: '🤓',
|
||||
愤怒: '😡',
|
||||
面无表情: '😑',
|
||||
张嘴: '😮',
|
||||
量体温: '🤒',
|
||||
呕吐: '🤮',
|
||||
光环: '😇',
|
||||
幽灵: '👻',
|
||||
外星人: '👽',
|
||||
机器人: '🤖',
|
||||
捂眼镜: '🙈',
|
||||
捂耳朵: '🙉',
|
||||
捂嘴: '🙊',
|
||||
婴儿: '👶',
|
||||
男孩: '👦',
|
||||
女孩: '👧',
|
||||
男人: '👨',
|
||||
女人: '👩',
|
||||
老人: '👴',
|
||||
老妇人: '👵',
|
||||
警察: '👮',
|
||||
王子: '🤴',
|
||||
公主: '🤴',
|
||||
举手: '🙋',
|
||||
跑步: '🏃',
|
||||
家庭: '👪',
|
||||
眼睛: '👀',
|
||||
鼻子: '👃',
|
||||
耳朵: '👂',
|
||||
舌头: '👅',
|
||||
嘴: '👄',
|
||||
心: '❤️',
|
||||
心碎: '💔',
|
||||
雪人: '☃️',
|
||||
情书: '💌',
|
||||
大便: '💩',
|
||||
闹钟: '⏰',
|
||||
眼镜: '👓',
|
||||
雨伞: '☂️',
|
||||
音乐: '🎵',
|
||||
话筒: '🎤',
|
||||
游戏机: '🎮',
|
||||
喇叭: '📢',
|
||||
耳机: '🎧',
|
||||
礼物: '🎁',
|
||||
电话: '📞',
|
||||
电脑: '💻',
|
||||
打印机: '🖨️',
|
||||
手电筒: '🔦',
|
||||
灯泡: '💡',
|
||||
书本: '📖',
|
||||
信封: '✉️',
|
||||
药丸: '💊',
|
||||
口红: '💄',
|
||||
手机: '📱',
|
||||
相机: '📷',
|
||||
电视: '📺',
|
||||
中: '🀄',
|
||||
垃圾桶: '🚮',
|
||||
厕所: '🚾',
|
||||
感叹号: '❗',
|
||||
禁: '🈲',
|
||||
可: '🉑',
|
||||
彩虹: '🌈',
|
||||
旋风: '🌀',
|
||||
雷电: '⚡',
|
||||
雪花: '❄️',
|
||||
星星: '⭐',
|
||||
水滴: '💧',
|
||||
玫瑰: '🌹',
|
||||
加油: '💪',
|
||||
左: '👈',
|
||||
右: '👉',
|
||||
上: '👆',
|
||||
下: '👇',
|
||||
手掌: '🖐️',
|
||||
好的: '👌',
|
||||
好: '👍',
|
||||
差: '👎',
|
||||
胜利: '✌',
|
||||
拳头: '👊',
|
||||
挥手: '👋',
|
||||
鼓掌: '👏',
|
||||
猴子: '🐒',
|
||||
狗: '🐶',
|
||||
狼: '🐺',
|
||||
猫: '🐱',
|
||||
老虎: '🐯',
|
||||
马: '🐎',
|
||||
独角兽: '🦄',
|
||||
斑马: '🦓',
|
||||
鹿: '🦌',
|
||||
牛: '🐮',
|
||||
猪: '🐷',
|
||||
羊: '🐏',
|
||||
长颈鹿: '🦒',
|
||||
大象: '🐘',
|
||||
老鼠: '🐭',
|
||||
蝙蝠: '🦇',
|
||||
刺猬: '🦔',
|
||||
熊猫: '🐼',
|
||||
鸽子: '🕊️',
|
||||
鸭子: '🦆',
|
||||
兔子: '🐇',
|
||||
老鹰: '🦅',
|
||||
青蛙: '🐸',
|
||||
蛇: '🐍',
|
||||
龙: '🐉',
|
||||
鲸鱼: '🐳',
|
||||
海豚: '🐬',
|
||||
足球: '⚽',
|
||||
棒球: '⚾',
|
||||
篮球: '🏀',
|
||||
排球: '🏐',
|
||||
橄榄球: '🏉',
|
||||
网球: '🎾',
|
||||
骰子: '🎲',
|
||||
鸡腿: '🍗',
|
||||
蛋糕: '🎂',
|
||||
啤酒: '🍺',
|
||||
饺子: '🥟',
|
||||
汉堡: '🍔',
|
||||
薯条: '🍟',
|
||||
意大利面: '🍝',
|
||||
干杯: '🥂',
|
||||
筷子: '🥢',
|
||||
糖果: '🍬',
|
||||
奶瓶: '🍼',
|
||||
爆米花: '🍿',
|
||||
邮局: '🏤',
|
||||
医院: '🏥',
|
||||
银行: '🏦',
|
||||
酒店: '🏨',
|
||||
学校: '🏫',
|
||||
城堡: '🏰',
|
||||
火车: '🚂',
|
||||
高铁: '🚄',
|
||||
地铁: '🚇',
|
||||
公交: '🚌',
|
||||
救护车: '🚑',
|
||||
消防车: '🚒',
|
||||
警车: '🚓',
|
||||
出租车: '🚕',
|
||||
汽车: '🚗',
|
||||
货车: '🚛',
|
||||
自行车: '🚲',
|
||||
摩托: '🛵',
|
||||
红绿灯: '🚥',
|
||||
帆船: '⛵',
|
||||
游轮: '🛳️',
|
||||
轮船: '⛴️',
|
||||
飞机: '✈️',
|
||||
直升机: '🚁',
|
||||
缆车: '🚠',
|
||||
警告: '⚠️',
|
||||
禁止: '⛔'
|
||||
}
|
||||
|
||||
function Emoji () {
|
||||
|
||||
}
|
||||
|
||||
Emoji.prototype.onUpdate = function (content) {
|
||||
return content.replace(reg, ($, $1) => {
|
||||
if (data[$1]) return data[$1]
|
||||
return $
|
||||
})
|
||||
}
|
||||
|
||||
Emoji.prototype.onGetContent = function (content) {
|
||||
for (const item in data) {
|
||||
content = content.replace(new RegExp(data[item], 'g'), '[' + item + ']')
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
export default Emoji
|
||||
5
components/mp-html/highlight/config.js
Normal file
5
components/mp-html/highlight/config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
copyByLongPress: true, // 是否需要长按代码块时显示复制代码内容菜单
|
||||
showLanguageName: true, // 是否在代码块右上角显示语言的名称
|
||||
showLineNumber: true // 是否显示行号
|
||||
}
|
||||
90
components/mp-html/highlight/index.js
Normal file
90
components/mp-html/highlight/index.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @fileoverview highlight 插件
|
||||
* Include prismjs (https://prismjs.com)
|
||||
*/
|
||||
import prism from './prism.min'
|
||||
import config from './config'
|
||||
import Parser from '../parser'
|
||||
|
||||
function Highlight (vm) {
|
||||
this.vm = vm
|
||||
}
|
||||
|
||||
Highlight.prototype.onParse = function (node, vm) {
|
||||
if (node.name === 'pre') {
|
||||
let i
|
||||
for (i = node.children.length; i--;) {
|
||||
if (node.children[i].name === 'code') break
|
||||
}
|
||||
if (i === -1) return
|
||||
const code = node.children[i]
|
||||
let className = code.attrs.class || ''
|
||||
i = className.indexOf('language-')
|
||||
if (i === -1) {
|
||||
className = node.attrs.class || ''
|
||||
i = className.indexOf('language-')
|
||||
}
|
||||
if (i === -1) {
|
||||
className = 'language-text'
|
||||
i = className.indexOf('language-')
|
||||
}
|
||||
i += 9
|
||||
let j
|
||||
for (j = i; j < className.length; j++) {
|
||||
if (className[j] === ' ') break
|
||||
}
|
||||
const lang = className.substring(i, j)
|
||||
if (code.children.length) {
|
||||
const text = this.vm.getText(code.children).replace(/&/g, '&')
|
||||
if (!text) return
|
||||
if (node.c) {
|
||||
node.c = undefined
|
||||
}
|
||||
if (prism.languages[lang]) {
|
||||
code.children = (new Parser(this.vm).parse(
|
||||
// 加一层 pre 保留空白符
|
||||
'<pre>' + prism.highlight(text, prism.languages[lang], lang).replace(/token /g, 'hl-') + '</pre>'))[0].children
|
||||
}
|
||||
node.attrs.class = 'hl-pre'
|
||||
code.attrs.class = 'hl-code'
|
||||
if (config.showLanguageName) {
|
||||
node.children.push({
|
||||
name: 'div',
|
||||
attrs: {
|
||||
class: 'hl-language',
|
||||
style: 'user-select:none'
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: lang
|
||||
}]
|
||||
})
|
||||
}
|
||||
if (config.copyByLongPress) {
|
||||
node.attrs.style += (node.attrs.style || '') + ';user-select:none'
|
||||
node.attrs['data-content'] = text
|
||||
vm.expose()
|
||||
}
|
||||
if (config.showLineNumber) {
|
||||
const line = text.split('\n').length; const children = []
|
||||
for (let k = line; k--;) {
|
||||
children.push({
|
||||
name: 'span',
|
||||
attrs: {
|
||||
class: 'span'
|
||||
}
|
||||
})
|
||||
}
|
||||
node.children.push({
|
||||
name: 'span',
|
||||
attrs: {
|
||||
class: 'line-numbers-rows'
|
||||
},
|
||||
children
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Highlight
|
||||
16
components/mp-html/highlight/prism.min.js
vendored
Normal file
16
components/mp-html/highlight/prism.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
138
components/mp-html/img-cache/index.js
Normal file
138
components/mp-html/img-cache/index.js
Normal file
@@ -0,0 +1,138 @@
|
||||
const data = {
|
||||
name: 'imgcache',
|
||||
prefix: 'imgcache_'
|
||||
}
|
||||
function ImgCache (vm) {
|
||||
this.vm = vm // 保存实例在其他周期使用
|
||||
this.i = 0 // 用于标记第几张图
|
||||
vm.imgCache = {
|
||||
get list () {
|
||||
return uni
|
||||
.getStorageInfoSync()
|
||||
.keys.filter((key) => key.startsWith(data.prefix))
|
||||
.map((key) => key.split(data.prefix)[1])
|
||||
},
|
||||
get (url) {
|
||||
return uni.getStorageSync(data.prefix + url)
|
||||
},
|
||||
delete (url) {
|
||||
const path = uni.getStorageSync(data.prefix + url)
|
||||
if (!path) return false
|
||||
plus.io.resolveLocalFileSystemURL(path, (entry) => {
|
||||
entry.remove()
|
||||
})
|
||||
uni.removeStorageSync(data.prefix + url)
|
||||
return true
|
||||
},
|
||||
async add (url) {
|
||||
const filename = await download(url)
|
||||
if (filename) {
|
||||
uni.setStorageSync(data.prefix + url, filename)
|
||||
return 'file://' + plus.io.convertLocalFileSystemURL(filename)
|
||||
}
|
||||
return null
|
||||
},
|
||||
clear () {
|
||||
uni
|
||||
.getStorageInfoSync()
|
||||
.keys.filter((key) => key.startsWith(data.prefix))
|
||||
.forEach((key) => {
|
||||
uni.removeStorageSync(key)
|
||||
})
|
||||
|
||||
plus.io.resolveLocalFileSystemURL(`_doc/${data.name}/`, (entry) => {
|
||||
entry.removeRecursively(
|
||||
(entry) => {
|
||||
console.log(`${data.name}缓存删除成功`, entry)
|
||||
},
|
||||
(e) => {
|
||||
console.log(`${data.name}缓存删除失败`, e)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
ImgCache.prototype.onParse = function (node, parser) {
|
||||
// 启用本插件 && 解析图片标签 && 拥有src属性 && 是网络图片
|
||||
if (
|
||||
this.vm.ImgCache &&
|
||||
node.name === 'img' &&
|
||||
node.attrs.src &&
|
||||
/^https?:\/\//.test(node.attrs.src)
|
||||
) {
|
||||
const src = node.attrs.src
|
||||
node.attrs.src = ''
|
||||
node.attrs.i = this.vm.imgList.length + this.i++
|
||||
parser.expose()
|
||||
|
||||
async function getUrl (path) {
|
||||
if (await resolveFile(path)) return path
|
||||
const filename = await download(src)
|
||||
filename && uni.setStorageSync(data.prefix + src, filename)
|
||||
return filename
|
||||
}
|
||||
|
||||
uni.getStorage({
|
||||
key: data.prefix + src,
|
||||
success: async (res) => {
|
||||
const path = await getUrl(res.data)
|
||||
const url = path
|
||||
? 'file://' + plus.io.convertLocalFileSystemURL(path)
|
||||
: src
|
||||
node.attrs.src = url
|
||||
this.vm.imgList[node.attrs.i] = path || src
|
||||
},
|
||||
fail: async () => {
|
||||
const path = await getUrl()
|
||||
const url = path
|
||||
? 'file://' + plus.io.convertLocalFileSystemURL(path)
|
||||
: src
|
||||
node.attrs.src = url
|
||||
this.vm.imgList[node.attrs.i] = path || src
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const taskQueue = new Set()
|
||||
|
||||
function download (url) {
|
||||
return new Promise((resolve) => {
|
||||
if (taskQueue.has(url)) return
|
||||
taskQueue.add(url)
|
||||
const suffix = /.+\.(jpg|jpeg|png|bmp|gif|webp)/.exec(url)
|
||||
const name = `${makeid(8)}_${Date.now()}${suffix ? '.' + suffix[1] : ''}`
|
||||
const task = plus.downloader.createDownload(
|
||||
url,
|
||||
{ filename: `_doc/${data.name}/${name}` },
|
||||
(download, status) => {
|
||||
taskQueue.delete(url)
|
||||
resolve(status === 200 ? download.filename : null)
|
||||
}
|
||||
)
|
||||
task.start()
|
||||
})
|
||||
}
|
||||
|
||||
// 判断文件存在
|
||||
function resolveFile (url) {
|
||||
return new Promise((resolve) => {
|
||||
plus.io.resolveLocalFileSystemURL(url, resolve, () => resolve(null))
|
||||
})
|
||||
}
|
||||
|
||||
// 生成uuid
|
||||
function makeid (length) {
|
||||
let result = ''
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
// #endif
|
||||
|
||||
export default ImgCache
|
||||
34
components/mp-html/markdown/index.js
Normal file
34
components/mp-html/markdown/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @fileoverview markdown 插件
|
||||
* Include marked (https://github.com/markedjs/marked)
|
||||
* Include github-markdown-css (https://github.com/sindresorhus/github-markdown-css)
|
||||
*/
|
||||
import marked from './marked.min'
|
||||
let index = 0
|
||||
|
||||
function Markdown (vm) {
|
||||
this.vm = vm
|
||||
vm._ids = {}
|
||||
}
|
||||
|
||||
Markdown.prototype.onUpdate = function (content) {
|
||||
if (this.vm.markdown) {
|
||||
return marked(content)
|
||||
}
|
||||
}
|
||||
|
||||
Markdown.prototype.onParse = function (node, vm) {
|
||||
if (vm.options.markdown) {
|
||||
// 中文 id 需要转换,否则无法跳转
|
||||
if (vm.options.useAnchor && node.attrs && /[\u4e00-\u9fa5]/.test(node.attrs.id)) {
|
||||
const id = 't' + index++
|
||||
this.vm._ids[node.attrs.id] = id
|
||||
node.attrs.id = id
|
||||
}
|
||||
if (node.name === 'p' || node.name === 'table' || node.name === 'tr' || node.name === 'th' || node.name === 'td' || node.name === 'blockquote' || node.name === 'pre' || node.name === 'code') {
|
||||
node.attrs.class = `md-${node.name} ${node.attrs.class || ''}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Markdown
|
||||
6
components/mp-html/markdown/marked.min.js
vendored
Normal file
6
components/mp-html/markdown/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
471
components/mp-html/mp-html.vue
Normal file
471
components/mp-html/mp-html.vue
Normal file
@@ -0,0 +1,471 @@
|
||||
<template>
|
||||
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
|
||||
<slot v-if="!nodes[0]" />
|
||||
<!-- #ifndef APP-PLUS-NVUE -->
|
||||
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS-NVUE -->
|
||||
<web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* mp-html v2.3.1
|
||||
* @description 富文本组件
|
||||
* @tutorial https://github.com/jin-yufeng/mp-html
|
||||
* @property {String} container-style 容器的样式
|
||||
* @property {String} content 用于渲染的 html 字符串
|
||||
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
|
||||
* @property {String} domain 主域名,用于拼接链接
|
||||
* @property {String} error-img 图片出错时的占位图链接
|
||||
* @property {Boolean} lazy-load 是否开启图片懒加载
|
||||
* @property {string} loading-img 图片加载过程中的占位图链接
|
||||
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
|
||||
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
|
||||
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
|
||||
* @property {Boolean | String} selectable 是否开启长按复制
|
||||
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
|
||||
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
|
||||
* @property {Object} tag-style 标签的默认样式
|
||||
* @property {Boolean | Number} use-anchor 是否使用锚点链接
|
||||
* @event {Function} load dom 结构加载完毕时触发
|
||||
* @event {Function} ready 所有图片加载完毕时触发
|
||||
* @event {Function} imgtap 图片被点击时触发
|
||||
* @event {Function} linktap 链接被点击时触发
|
||||
* @event {Function} play 音视频播放时触发
|
||||
* @event {Function} error 媒体加载出错时触发
|
||||
*/
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
import node from './node/node'
|
||||
// #endif
|
||||
import Parser from './parser'
|
||||
import markdown from './markdown/index.js'
|
||||
import audio from './audio/index.js'
|
||||
import emoji from './emoji/index.js'
|
||||
import highlight from './highlight/index.js'
|
||||
import style from './style/index.js'
|
||||
import imgCache from './img-cache/index.js'
|
||||
const plugins=[markdown,audio,emoji,highlight,style,imgCache,]
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
// #endif
|
||||
export default {
|
||||
name: 'mp-html',
|
||||
data () {
|
||||
return {
|
||||
nodes: [],
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
height: 3
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
props: {
|
||||
ImgCache: Boolean,
|
||||
markdown: Boolean,
|
||||
containerStyle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
copyLink: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
domain: String,
|
||||
errorImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
lazyLoad: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
loadingImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
pauseVideo: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
previewImg: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
scrollTable: [Boolean, String],
|
||||
selectable: [Boolean, String],
|
||||
setTitle: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
showImgMenu: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
tagStyle: Object,
|
||||
useAnchor: [Boolean, Number]
|
||||
},
|
||||
// #ifdef VUE3
|
||||
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
components: {
|
||||
node
|
||||
},
|
||||
// #endif
|
||||
watch: {
|
||||
content (content) {
|
||||
this.setContent(content)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.plugins = []
|
||||
for (let i = plugins.length; i--;) {
|
||||
this.plugins.push(new plugins[i](this))
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.content && !this.nodes.length) {
|
||||
this.setContent(this.content)
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this._hook('onDetached')
|
||||
clearInterval(this._timer)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 将锚点跳转的范围限定在一个 scroll-view 内
|
||||
* @param {Object} page scroll-view 所在页面的示例
|
||||
* @param {String} selector scroll-view 的选择器
|
||||
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
|
||||
*/
|
||||
in (page, selector, scrollTop) {
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
if (page && selector && scrollTop) {
|
||||
this._in = {
|
||||
page,
|
||||
selector,
|
||||
scrollTop
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 锚点跳转
|
||||
* @param {String} id 要跳转的锚点 id
|
||||
* @param {Number} offset 跳转位置的偏移量
|
||||
* @returns {Promise}
|
||||
*/
|
||||
navigateTo (id, offset) {
|
||||
id = this._ids[decodeURI(id)] || id
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.useAnchor) {
|
||||
reject(Error('Anchor is disabled'))
|
||||
return
|
||||
}
|
||||
offset = offset || parseInt(this.useAnchor) || 0
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!id) {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset
|
||||
})
|
||||
resolve()
|
||||
} else {
|
||||
this._navigateTo = {
|
||||
resolve,
|
||||
reject,
|
||||
offset
|
||||
}
|
||||
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
|
||||
}
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
let deep = ' '
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
||||
deep = '>>>'
|
||||
// #endif
|
||||
const selector = uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this._in ? this._in.page : this)
|
||||
// #endif
|
||||
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
|
||||
if (this._in) {
|
||||
selector.select(this._in.selector).scrollOffset()
|
||||
.select(this._in.selector).boundingClientRect()
|
||||
} else {
|
||||
// 获取 scroll-view 的位置和滚动距离
|
||||
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
|
||||
}
|
||||
selector.exec(res => {
|
||||
if (!res[0]) {
|
||||
reject(Error('Label not found'))
|
||||
return
|
||||
}
|
||||
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
|
||||
if (this._in) {
|
||||
// scroll-view 跳转
|
||||
this._in.page[this._in.scrollTop] = scrollTop
|
||||
} else {
|
||||
// 页面跳转
|
||||
uni.pageScrollTo({
|
||||
scrollTop,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取文本内容
|
||||
* @return {String}
|
||||
*/
|
||||
getText (nodes) {
|
||||
let text = '';
|
||||
(function traversal (nodes) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
if (node.type === 'text') {
|
||||
text += node.text.replace(/&/g, '&')
|
||||
} else if (node.name === 'br') {
|
||||
text += '\n'
|
||||
} else {
|
||||
// 块级标签前后加换行
|
||||
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
|
||||
if (isBlock && text && text[text.length - 1] !== '\n') {
|
||||
text += '\n'
|
||||
}
|
||||
// 递归获取子节点的文本
|
||||
if (node.children) {
|
||||
traversal(node.children)
|
||||
}
|
||||
if (isBlock && text[text.length - 1] !== '\n') {
|
||||
text += '\n'
|
||||
} else if (node.name === 'td' || node.name === 'th') {
|
||||
text += '\t'
|
||||
}
|
||||
}
|
||||
}
|
||||
})(nodes || this.nodes)
|
||||
return text
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取内容大小和位置
|
||||
* @return {Promise}
|
||||
*/
|
||||
getRect () {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 暂停播放媒体
|
||||
*/
|
||||
pauseMedia () {
|
||||
for (let i = (this._videos || []).length; i--;) {
|
||||
this._videos[i].pause()
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
let page = this.$parent
|
||||
while (!page.$scope) page = page.$parent
|
||||
page.$scope.$getAppWebview().evalJS(command)
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
this.$refs.web.evalJs(command)
|
||||
// #endif
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置内容
|
||||
* @param {String} content html 内容
|
||||
* @param {Boolean} append 是否在尾部追加
|
||||
*/
|
||||
setContent (content, append) {
|
||||
if (!append || !this.imgList) {
|
||||
this.imgList = []
|
||||
}
|
||||
const nodes = new Parser(this).parse(content)
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (this._ready) {
|
||||
this._set(nodes, append)
|
||||
}
|
||||
// #endif
|
||||
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
|
||||
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
this._videos = []
|
||||
this.$nextTick(() => {
|
||||
this._hook('onLoad')
|
||||
this.$emit('load')
|
||||
})
|
||||
|
||||
// 等待图片加载完毕
|
||||
let height
|
||||
clearInterval(this._timer)
|
||||
this._timer = setInterval(() => {
|
||||
this.getRect().then(rect => {
|
||||
// 350ms 总高度无变化就触发 ready 事件
|
||||
if (rect.height === height) {
|
||||
this.$emit('ready', rect)
|
||||
clearInterval(this._timer)
|
||||
}
|
||||
height = rect.height
|
||||
}).catch(() => { })
|
||||
}, 350)
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 调用插件钩子函数
|
||||
*/
|
||||
_hook (name) {
|
||||
for (let i = plugins.length; i--;) {
|
||||
if (this.plugins[i][name]) {
|
||||
this.plugins[i][name]()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
/**
|
||||
* @description 设置内容
|
||||
*/
|
||||
_set (nodes, append) {
|
||||
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 接收到 web-view 消息
|
||||
*/
|
||||
_onMessage (e) {
|
||||
const message = e.detail.data[0]
|
||||
switch (message.action) {
|
||||
// web-view 初始化完毕
|
||||
case 'onJSBridgeReady':
|
||||
this._ready = true
|
||||
if (this.nodes) {
|
||||
this._set(this.nodes)
|
||||
}
|
||||
break
|
||||
// 内容 dom 加载完毕
|
||||
case 'onLoad':
|
||||
this.height = message.height
|
||||
this._hook('onLoad')
|
||||
this.$emit('load')
|
||||
break
|
||||
// 所有图片加载完毕
|
||||
case 'onReady':
|
||||
this.getRect().then(res => {
|
||||
this.$emit('ready', res)
|
||||
}).catch(() => { })
|
||||
break
|
||||
// 总高度发生变化
|
||||
case 'onHeightChange':
|
||||
this.height = message.height
|
||||
break
|
||||
// 图片点击
|
||||
case 'onImgTap':
|
||||
this.$emit('imgtap', message.attrs)
|
||||
if (this.previewImg) {
|
||||
uni.previewImage({
|
||||
current: parseInt(message.attrs.i),
|
||||
urls: this.imgList
|
||||
})
|
||||
}
|
||||
break
|
||||
// 链接点击
|
||||
case 'onLinkTap': {
|
||||
const href = message.attrs.href
|
||||
this.$emit('linktap', message.attrs)
|
||||
if (href) {
|
||||
// 锚点跳转
|
||||
if (href[0] === '#') {
|
||||
if (this.useAnchor) {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset: message.offset
|
||||
})
|
||||
}
|
||||
} else if (href.includes('://')) {
|
||||
// 打开外链
|
||||
if (this.copyLink) {
|
||||
plus.runtime.openWeb(href)
|
||||
}
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: href,
|
||||
fail () {
|
||||
uni.switchTab({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'onPlay':
|
||||
this.$emit('play')
|
||||
break
|
||||
// 获取到锚点的偏移量
|
||||
case 'getOffset':
|
||||
if (typeof message.offset === 'number') {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset: message.offset + this._navigateTo.offset
|
||||
})
|
||||
this._navigateTo.resolve()
|
||||
} else {
|
||||
this._navigateTo.reject(Error('Label not found'))
|
||||
}
|
||||
break
|
||||
// 点击
|
||||
case 'onClick':
|
||||
this.$emit('tap')
|
||||
this.$emit('click')
|
||||
break
|
||||
// 出错
|
||||
case 'onError':
|
||||
this.$emit('error', {
|
||||
source: message.source,
|
||||
attrs: message.attrs
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* #ifndef APP-PLUS-NVUE */
|
||||
/* 根节点样式 */
|
||||
._root {
|
||||
padding: 1px 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* 长按复制 */
|
||||
._select {
|
||||
user-select: text;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
809
components/mp-html/node/node.vue
Normal file
809
components/mp-html/node/node.vue
Normal file
@@ -0,0 +1,809 @@
|
||||
<template>
|
||||
<view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
|
||||
<block v-for="(n, i) in childs" v-bind:key="i">
|
||||
<!-- 图片 -->
|
||||
<!-- 占位图 -->
|
||||
<image v-if="n.name==='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style"
|
||||
:src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
|
||||
<!-- 显示图片 -->
|
||||
<!-- #ifdef H5 || (APP-PLUS && VUE2) -->
|
||||
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class"
|
||||
:style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style"
|
||||
:src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError"
|
||||
@tap.stop="imgTap" @longpress="imgLongTap" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || APP-PLUS -->
|
||||
<image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class"
|
||||
:style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style"
|
||||
:src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp"
|
||||
:show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore"
|
||||
:data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS && VUE3 -->
|
||||
<image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class"
|
||||
:style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style"
|
||||
:src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')"
|
||||
:data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
|
||||
<!-- #endif -->
|
||||
<!-- 文本 -->
|
||||
<!-- #ifndef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
|
||||
<text v-else-if="n.text" :user-select="opts[4]" decode>{{n.text}}</text>
|
||||
<!-- #endif -->
|
||||
<text v-else-if="n.name==='br'">\n</text>
|
||||
<!-- 链接 -->
|
||||
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class"
|
||||
hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
|
||||
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
|
||||
</view>
|
||||
<!-- 视频 -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style"
|
||||
v-html="n.html" @vplay.stop="play" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style"
|
||||
:autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted"
|
||||
:object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i"
|
||||
@play="play" @error="mediaError" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen"
|
||||
:frameborder="n.attrs.frameborder" :src="n.attrs.src" />
|
||||
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
|
||||
<!-- 音频 -->
|
||||
|
||||
<!-- #endif -->
|
||||
<view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id"
|
||||
:class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
|
||||
<node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
|
||||
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x"
|
||||
:class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
|
||||
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
|
||||
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
|
||||
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class"
|
||||
:style="tr.attrs.style">
|
||||
<node :childs="tr.children" :opts="opts" />
|
||||
</view>
|
||||
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
|
||||
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class"
|
||||
:style="td.attrs.style">
|
||||
<node :childs="td.children" :opts="opts" />
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<my-audio v-else-if="n.name=='audio'" :class="n.attrs.class" :style="n.attrs.style" :aid="n.attrs.id"
|
||||
:author="n.attrs.author" :controls="n.attrs.controls" :autoplay="n.attrs.autoplay" :loop="n.attrs.loop"
|
||||
:name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" data-source="audio"
|
||||
@play="play" @error="mediaError" />
|
||||
<rich-text v-else-if="n.attrs['data-content']" :nodes="[n]" :data-content="n.attrs['data-content']"
|
||||
:data-lang="n.attrs['data-lang']" @longpress="copyCode" />
|
||||
<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" />
|
||||
<!-- 富文本 -->
|
||||
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
|
||||
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f"
|
||||
:user-select="opts[4]" :nodes="[n]" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
|
||||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false"
|
||||
:selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
|
||||
<!-- #endif -->
|
||||
<!-- 继续递归 -->
|
||||
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class"
|
||||
:style="n.f+';'+n.attrs.style">
|
||||
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs"
|
||||
:childs="n2.children" :opts="opts" />
|
||||
</view>
|
||||
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script module="handler" lang="wxs">
|
||||
// 行内标签列表
|
||||
var inlineTags = {
|
||||
abbr: true,
|
||||
b: true,
|
||||
big: true,
|
||||
code: true,
|
||||
del: true,
|
||||
em: true,
|
||||
i: true,
|
||||
ins: true,
|
||||
label: true,
|
||||
q: true,
|
||||
small: true,
|
||||
span: true,
|
||||
strong: true,
|
||||
sub: true,
|
||||
sup: true
|
||||
}
|
||||
/**
|
||||
* @description 判断是否为行内标签
|
||||
*/
|
||||
module.exports = {
|
||||
isInline: function(tagName, style) {
|
||||
return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
import myAudio from '../audio/audio'
|
||||
|
||||
import node from './node'
|
||||
export default {
|
||||
name: 'node',
|
||||
options: {
|
||||
// #ifdef MP-WEIXIN
|
||||
virtualHost: true,
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
addGlobalClass: false
|
||||
// #endif
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ctrl: {}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
attrs: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
childs: Array,
|
||||
opts: Array
|
||||
},
|
||||
components: {
|
||||
myAudio,
|
||||
|
||||
// #ifndef H5 && VUE3
|
||||
node
|
||||
// #endif
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root
|
||||
.$parent);
|
||||
})
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (this.opts[0]) {
|
||||
let i
|
||||
for (i = this.childs.length; i--;) {
|
||||
if (this.childs[i].name === 'img') break
|
||||
}
|
||||
if (i !== -1) {
|
||||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
|
||||
top: 500,
|
||||
bottom: 500
|
||||
})
|
||||
this.observer.observe('._img', res => {
|
||||
if (res.intersectionRatio) {
|
||||
this.$set(this.ctrl, 'load', 1)
|
||||
this.observer.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
beforeDestroy() {
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (this.observer) {
|
||||
this.observer.disconnect()
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
copyCode(e) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['复制代码'],
|
||||
success: () =>
|
||||
uni.setClipboardData({
|
||||
data: e.currentTarget.dataset.content
|
||||
})
|
||||
})
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
toJSON() {},
|
||||
// #endif
|
||||
/**
|
||||
* @description 播放视频事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
play(e) {
|
||||
this.root.$emit('play')
|
||||
// #ifndef APP-PLUS
|
||||
if (this.root.pauseVideo) {
|
||||
let flag = false
|
||||
const id = e.target.id
|
||||
for (let i = this.root._videos.length; i--;) {
|
||||
if (this.root._videos[i].id === id) {
|
||||
flag = true
|
||||
} else {
|
||||
this.root._videos[i].pause() // 自动暂停其他视频
|
||||
}
|
||||
}
|
||||
// 将自己加入列表
|
||||
if (!flag) {
|
||||
const ctx = uni.createVideoContext(id
|
||||
// #ifndef MP-BAIDU
|
||||
, this
|
||||
// #endif
|
||||
)
|
||||
ctx.id = id
|
||||
this.root._videos.push(ctx)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片点击事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
imgTap(e) {
|
||||
const node = this.childs[e.currentTarget.dataset.i]
|
||||
if (node.a) {
|
||||
this.linkTap(node.a)
|
||||
return
|
||||
}
|
||||
if (node.attrs.ignore) return
|
||||
// #ifdef H5 || APP-PLUS
|
||||
node.attrs.src = node.attrs.src || node.attrs['data-src']
|
||||
// #endif
|
||||
this.root.$emit('imgtap', node.attrs)
|
||||
// 自动预览图片
|
||||
if (this.root.previewImg) {
|
||||
uni.previewImage({
|
||||
// #ifdef MP-WEIXIN
|
||||
showmenu: this.root.showImgMenu,
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
enablesavephoto: this.root.showImgMenu,
|
||||
enableShowPhotoDownload: this.root.showImgMenu,
|
||||
// #endif
|
||||
current: parseInt(node.attrs.i),
|
||||
urls: this.root.imgList
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片长按
|
||||
*/
|
||||
imgLongTap(e) {
|
||||
// #ifdef APP-PLUS
|
||||
const attrs = this.childs[e.currentTarget.dataset.i].attrs
|
||||
if (this.opts[3] && !attrs.ignore) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['保存图片'],
|
||||
success: () => {
|
||||
const save = path => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: path,
|
||||
success() {
|
||||
uni.showToast({
|
||||
title: '保存成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (this.root.imgList[attrs.i].startsWith('http')) {
|
||||
uni.downloadFile({
|
||||
url: this.root.imgList[attrs.i],
|
||||
success: res => save(res.tempFilePath)
|
||||
})
|
||||
} else {
|
||||
save(this.root.imgList[attrs.i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片加载完成事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
imgLoad(e) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
/* #ifndef H5 || (APP-PLUS && VUE2) */
|
||||
if (!this.childs[i].w) {
|
||||
// 设置原宽度
|
||||
this.$set(this.ctrl, i, e.detail.width)
|
||||
} else /* #endif */
|
||||
if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
|
||||
// 加载完毕,取消加载中占位图
|
||||
this.$set(this.ctrl, i, 1)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 链接点击事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
linkTap(e) {
|
||||
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
|
||||
const attrs = node.attrs || e
|
||||
const href = attrs.href
|
||||
this.root.$emit('linktap', Object.assign({
|
||||
innerText: this.root.getText(node.children || []) // 链接内的文本内容
|
||||
}, attrs))
|
||||
if (href) {
|
||||
if (href[0] === '#') {
|
||||
// 跳转锚点
|
||||
this.root.navigateTo(href.substring(1)).catch(() => {})
|
||||
} else if (href.split('?')[0].includes('://')) {
|
||||
// 复制外部链接
|
||||
if (this.root.copyLink) {
|
||||
// #ifdef H5
|
||||
window.open(href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.setClipboardData({
|
||||
data: href,
|
||||
success: () =>
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openWeb(href)
|
||||
// #endif
|
||||
}
|
||||
} else {
|
||||
// 跳转页面
|
||||
uni.navigateTo({
|
||||
url: href,
|
||||
fail() {
|
||||
uni.switchTab({
|
||||
url: href,
|
||||
fail() {}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 错误事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
mediaError(e) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
const node = this.childs[i]
|
||||
// 加载其他源
|
||||
if (node.name === 'video' || node.name === 'audio') {
|
||||
let index = (this.ctrl[i] || 0) + 1
|
||||
if (index > node.src.length) {
|
||||
index = 0
|
||||
}
|
||||
if (index < node.src.length) {
|
||||
this.$set(this.ctrl, i, index)
|
||||
return
|
||||
}
|
||||
} else if (node.name === 'img') {
|
||||
// #ifdef H5 && VUE3
|
||||
if (this.opts[0] && !this.ctrl.load) return
|
||||
// #endif
|
||||
// 显示错误占位图
|
||||
if (this.opts[2]) {
|
||||
this.$set(this.ctrl, i, -1)
|
||||
}
|
||||
}
|
||||
if (this.root) {
|
||||
this.root.$emit('error', {
|
||||
source: node.name,
|
||||
attrs: node.attrs,
|
||||
// #ifndef H5 && VUE3
|
||||
errMsg: e.detail.errMsg
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/deep/ .hl-code,
|
||||
/deep/ .hl-pre {
|
||||
color: #ccc;
|
||||
background: 0 0;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none
|
||||
}
|
||||
|
||||
/deep/ .hl-pre {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
/deep/ .hl-pre {
|
||||
background: #2d2d2d
|
||||
}
|
||||
|
||||
/deep/ .hl-block-comment,
|
||||
/deep/ .hl-cdata,
|
||||
/deep/ .hl-comment,
|
||||
/deep/ .hl-doctype,
|
||||
/deep/ .hl-prolog {
|
||||
color: #999
|
||||
}
|
||||
|
||||
/deep/ .hl-punctuation {
|
||||
color: #ccc
|
||||
}
|
||||
|
||||
/deep/ .hl-attr-name,
|
||||
/deep/ .hl-deleted,
|
||||
/deep/ .hl-namespace,
|
||||
/deep/ .hl-tag {
|
||||
color: #e2777a
|
||||
}
|
||||
|
||||
/deep/ .hl-function-name {
|
||||
color: #6196cc
|
||||
}
|
||||
|
||||
/deep/ .hl-boolean,
|
||||
/deep/ .hl-function,
|
||||
/deep/ .hl-number {
|
||||
color: #f08d49
|
||||
}
|
||||
|
||||
/deep/ .hl-class-name,
|
||||
/deep/ .hl-constant,
|
||||
/deep/ .hl-property,
|
||||
/deep/ .hl-symbol {
|
||||
color: #f8c555
|
||||
}
|
||||
|
||||
/deep/ .hl-atrule,
|
||||
/deep/ .hl-builtin,
|
||||
/deep/ .hl-important,
|
||||
/deep/ .hl-keyword,
|
||||
/deep/ .hl-selector {
|
||||
color: #cc99cd
|
||||
}
|
||||
|
||||
/deep/ .hl-attr-value,
|
||||
/deep/ .hl-char,
|
||||
/deep/ .hl-regex,
|
||||
/deep/ .hl-string,
|
||||
/deep/ .hl-variable {
|
||||
color: #7ec699
|
||||
}
|
||||
|
||||
/deep/ .hl-entity,
|
||||
/deep/ .hl-operator,
|
||||
/deep/ .hl-url {
|
||||
color: #67cdcc
|
||||
}
|
||||
|
||||
/deep/ .hl-bold,
|
||||
/deep/ .hl-important {
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
/deep/ .hl-italic {
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
/deep/ .hl-entity {
|
||||
cursor: help
|
||||
}
|
||||
|
||||
/deep/ .hl-inserted {
|
||||
color: green
|
||||
}
|
||||
|
||||
/deep/ .md-p {
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
}
|
||||
|
||||
/deep/ .md-table,
|
||||
/deep/ .md-blockquote {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/deep/ .md-table {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/deep/ .md-tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #c6cbd1;
|
||||
}
|
||||
|
||||
/deep/ .md-table .md-tr:nth-child(2n) {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
/deep/ .md-th,
|
||||
/deep/ .md-td {
|
||||
padding: 6px 13px !important;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
/deep/ .md-th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/deep/ .md-blockquote {
|
||||
padding: 0 1em;
|
||||
color: #6a737d;
|
||||
border-left: 0.25em solid #dfe2e5;
|
||||
}
|
||||
|
||||
/deep/ .md-code {
|
||||
padding: 0.2em 0.4em;
|
||||
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
|
||||
font-size: 85%;
|
||||
background-color: rgba(27, 31, 35, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/deep/ .md-pre .md-code {
|
||||
padding: 0;
|
||||
font-size: 100%;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/deep/ .hl-pre {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/deep/ .hl-code {
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/deep/ .hl-language {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
text-align: right;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
/deep/ .hl-pre {
|
||||
padding-top: 1.5em;
|
||||
}
|
||||
|
||||
/deep/ .hl-pre {
|
||||
font-size: 14px;
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
}
|
||||
|
||||
/deep/ .line-numbers-rows {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 1.5em;
|
||||
font-size: 100%;
|
||||
left: 0;
|
||||
width: 3em;
|
||||
/* works for line-numbers below 1000 lines */
|
||||
letter-spacing: -1px;
|
||||
border-right: 1px solid #999;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/deep/ .line-numbers-rows .span {
|
||||
display: block;
|
||||
counter-increment: linenumber;
|
||||
}
|
||||
|
||||
/deep/ .line-numbers-rows .span:before {
|
||||
content: counter(linenumber);
|
||||
color: #999;
|
||||
display: block;
|
||||
padding-right: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* a 标签默认效果 */
|
||||
._a {
|
||||
padding: 1.5px 0 1.5px 0;
|
||||
color: #366092;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* a 标签点击态效果 */
|
||||
._hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 图片默认效果 */
|
||||
._img {
|
||||
max-width: 100%;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
/* 内部样式 */
|
||||
|
||||
._block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
._b,
|
||||
._strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
._del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
._em,
|
||||
._i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
._h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
._h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
._h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
._h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
|
||||
._h6 {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
|
||||
._h1,
|
||||
._h2,
|
||||
._h3,
|
||||
._h4,
|
||||
._h5,
|
||||
._h6 {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._image {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
._ins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._li {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
._ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
._ol,
|
||||
._ul {
|
||||
display: block;
|
||||
padding-left: 40px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
._q::before {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._q::after {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._sub {
|
||||
font-size: smaller;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
._sup {
|
||||
font-size: smaller;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
._thead,
|
||||
._tbody,
|
||||
._tfoot {
|
||||
display: table-row-group;
|
||||
}
|
||||
|
||||
._tr {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
._td,
|
||||
._th {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
._th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
._ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
._ul ._ul {
|
||||
margin: 0;
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
._ul ._ul ._ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
._abbr,
|
||||
._b,
|
||||
._code,
|
||||
._del,
|
||||
._em,
|
||||
._i,
|
||||
._ins,
|
||||
._label,
|
||||
._q,
|
||||
._span,
|
||||
._strong,
|
||||
._sub,
|
||||
._sup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #ifdef APP-PLUS */
|
||||
._video {
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
1280
components/mp-html/parser.js
Normal file
1280
components/mp-html/parser.js
Normal file
File diff suppressed because it is too large
Load Diff
129
components/mp-html/style/index.js
Normal file
129
components/mp-html/style/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* @fileoverview style 插件
|
||||
*/
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
import Parser from './parser'
|
||||
// #endif
|
||||
|
||||
function Style () {
|
||||
this.styles = []
|
||||
}
|
||||
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
Style.prototype.onParse = function (node, vm) {
|
||||
// 获取样式
|
||||
if (node.name === 'style' && node.children.length && node.children[0].type === 'text') {
|
||||
this.styles = this.styles.concat(new Parser().parse(node.children[0].text))
|
||||
} else if (node.name) {
|
||||
// 匹配样式(对非文本标签)
|
||||
// 存储不同优先级的样式 name < class < id < 后代
|
||||
let matched = ['', '', '', '']
|
||||
for (let i = 0, len = this.styles.length; i < len; i++) {
|
||||
const item = this.styles[i]
|
||||
let res = match(node, item.key || item.list[item.list.length - 1])
|
||||
let j
|
||||
if (res) {
|
||||
// 后代选择器
|
||||
if (!item.key) {
|
||||
j = item.list.length - 2
|
||||
for (let k = vm.stack.length; j >= 0 && k--;) {
|
||||
// 子选择器
|
||||
if (item.list[j] === '>') {
|
||||
// 错误情况
|
||||
if (j < 1 || j > item.list.length - 2) break
|
||||
if (match(vm.stack[k], item.list[j - 1])) {
|
||||
j -= 2
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
} else if (match(vm.stack[k], item.list[j])) {
|
||||
j--
|
||||
}
|
||||
}
|
||||
res = 4
|
||||
}
|
||||
if (item.key || j < 0) {
|
||||
// 添加伪类
|
||||
if (item.pseudo && node.children) {
|
||||
let text
|
||||
item.style = item.style.replace(/content:([^;]+)/, (_, $1) => {
|
||||
text = $1.replace(/['"]/g, '')
|
||||
// 处理 attr 函数
|
||||
.replace(/attr\((.+?)\)/, (_, $1) => node.attrs[$1.trim()] || '')
|
||||
// 编码 \xxx
|
||||
.replace(/\\(\w{4})/, (_, $1) => String.fromCharCode(parseInt($1, 16)))
|
||||
return ''
|
||||
})
|
||||
const pseudo = {
|
||||
name: 'span',
|
||||
attrs: {
|
||||
style: item.style
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text
|
||||
}]
|
||||
}
|
||||
if (item.pseudo === 'before') {
|
||||
node.children.unshift(pseudo)
|
||||
} else {
|
||||
node.children.push(pseudo)
|
||||
}
|
||||
} else {
|
||||
matched[res - 1] += item.style + (item.style[item.style.length - 1] === ';' ? '' : ';')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
matched = matched.join('')
|
||||
if (matched.length > 2) {
|
||||
node.attrs.style = matched + (node.attrs.style || '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 匹配样式
|
||||
* @param {object} node 要匹配的标签
|
||||
* @param {string|string[]} keys 选择器
|
||||
* @returns {number} 0:不匹配;1:name 匹配;2:class 匹配;3:id 匹配
|
||||
*/
|
||||
function match (node, keys) {
|
||||
function matchItem (key) {
|
||||
if (key[0] === '#') {
|
||||
// 匹配 id
|
||||
if (node.attrs.id && node.attrs.id.trim() === key.substr(1)) return 3
|
||||
} else if (key[0] === '.') {
|
||||
// 匹配 class
|
||||
key = key.substr(1)
|
||||
const selectors = (node.attrs.class || '').split(' ')
|
||||
for (let i = 0; i < selectors.length; i++) {
|
||||
if (selectors[i].trim() === key) return 2
|
||||
}
|
||||
} else if (node.name === key) {
|
||||
// 匹配 name
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 多选择器交集
|
||||
if (keys instanceof Array) {
|
||||
let res = 0
|
||||
for (let j = 0; j < keys.length; j++) {
|
||||
const tmp = matchItem(keys[j])
|
||||
// 任意一个不匹配就失败
|
||||
if (!tmp) return 0
|
||||
// 优先级最大的一个作为最终优先级
|
||||
if (tmp > res) {
|
||||
res = tmp
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
return matchItem(keys)
|
||||
}
|
||||
// #endif
|
||||
|
||||
export default Style
|
||||
175
components/mp-html/style/parser.js
Normal file
175
components/mp-html/style/parser.js
Normal file
@@ -0,0 +1,175 @@
|
||||
const blank = {
|
||||
' ': true,
|
||||
'\n': true,
|
||||
'\t': true,
|
||||
'\r': true,
|
||||
'\f': true
|
||||
}
|
||||
|
||||
function Parser () {
|
||||
this.styles = []
|
||||
this.selectors = []
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析 css 字符串
|
||||
* @param {string} content css 内容
|
||||
*/
|
||||
Parser.prototype.parse = function (content) {
|
||||
new Lexer(this).parse(content)
|
||||
return this.styles
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析到一个选择器
|
||||
* @param {string} name 名称
|
||||
*/
|
||||
Parser.prototype.onSelector = function (name) {
|
||||
// 不支持的选择器
|
||||
if (name.includes('[') || name.includes('*') || name.includes('@')) return
|
||||
const selector = {}
|
||||
// 伪类
|
||||
if (name.includes(':')) {
|
||||
const info = name.split(':')
|
||||
const pseudo = info.pop()
|
||||
if (pseudo === 'before' || pseudo === 'after') {
|
||||
selector.pseudo = pseudo
|
||||
name = info[0]
|
||||
} else return
|
||||
}
|
||||
|
||||
// 分割交集选择器
|
||||
function splitItem (str) {
|
||||
const arr = []
|
||||
let i, start
|
||||
for (i = 1, start = 0; i < str.length; i++) {
|
||||
if (str[i] === '.' || str[i] === '#') {
|
||||
arr.push(str.substring(start, i))
|
||||
start = i
|
||||
}
|
||||
}
|
||||
if (!arr.length) {
|
||||
return str
|
||||
} else {
|
||||
arr.push(str.substring(start, i))
|
||||
return arr
|
||||
}
|
||||
}
|
||||
|
||||
// 后代选择器
|
||||
if (name.includes(' ')) {
|
||||
selector.list = []
|
||||
const list = name.split(' ')
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].length) {
|
||||
// 拆分子选择器
|
||||
const arr = list[i].split('>')
|
||||
for (let j = 0; j < arr.length; j++) {
|
||||
selector.list.push(splitItem(arr[j]))
|
||||
if (j < arr.length - 1) {
|
||||
selector.list.push('>')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selector.key = splitItem(name)
|
||||
}
|
||||
|
||||
this.selectors.push(selector)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析到选择器内容
|
||||
* @param {string} content 内容
|
||||
*/
|
||||
Parser.prototype.onContent = function (content) {
|
||||
// 并集选择器
|
||||
for (let i = 0; i < this.selectors.length; i++) {
|
||||
this.selectors[i].style = content
|
||||
}
|
||||
this.styles = this.styles.concat(this.selectors)
|
||||
this.selectors = []
|
||||
}
|
||||
|
||||
/**
|
||||
* @description css 词法分析器
|
||||
* @param {object} handler 高层处理器
|
||||
*/
|
||||
function Lexer (handler) {
|
||||
this.selector = ''
|
||||
this.style = ''
|
||||
this.handler = handler
|
||||
}
|
||||
|
||||
Lexer.prototype.parse = function (content) {
|
||||
this.i = 0
|
||||
this.content = content
|
||||
this.state = this.blank
|
||||
for (let len = content.length; this.i < len; this.i++) {
|
||||
this.state(content[this.i])
|
||||
}
|
||||
}
|
||||
|
||||
Lexer.prototype.comment = function () {
|
||||
this.i = this.content.indexOf('*/', this.i) + 1
|
||||
if (!this.i) {
|
||||
this.i = this.content.length
|
||||
}
|
||||
}
|
||||
|
||||
Lexer.prototype.blank = function (c) {
|
||||
if (!blank[c]) {
|
||||
if (c === '/' && this.content[this.i + 1] === '*') {
|
||||
this.comment()
|
||||
return
|
||||
}
|
||||
this.selector += c
|
||||
this.state = this.name
|
||||
}
|
||||
}
|
||||
|
||||
Lexer.prototype.name = function (c) {
|
||||
if (c === '/' && this.content[this.i + 1] === '*') {
|
||||
this.comment()
|
||||
return
|
||||
}
|
||||
if (c === '{' || c === ',' || c === ';') {
|
||||
this.handler.onSelector(this.selector.trimEnd())
|
||||
this.selector = ''
|
||||
if (c !== '{') {
|
||||
while (blank[this.content[++this.i]]);
|
||||
}
|
||||
if (this.content[this.i] === '{') {
|
||||
this.floor = 1
|
||||
this.state = this.val
|
||||
} else {
|
||||
this.selector += this.content[this.i]
|
||||
}
|
||||
} else if (blank[c]) {
|
||||
this.selector += ' '
|
||||
} else {
|
||||
this.selector += c
|
||||
}
|
||||
}
|
||||
|
||||
Lexer.prototype.val = function (c) {
|
||||
if (c === '/' && this.content[this.i + 1] === '*') {
|
||||
this.comment()
|
||||
return
|
||||
}
|
||||
if (c === '{') {
|
||||
this.floor++
|
||||
} else if (c === '}') {
|
||||
this.floor--
|
||||
if (!this.floor) {
|
||||
this.handler.onContent(this.style)
|
||||
this.style = ''
|
||||
this.state = this.blank
|
||||
return
|
||||
}
|
||||
}
|
||||
this.style += c
|
||||
}
|
||||
|
||||
export default Parser
|
||||
167
components/uni-section/uni-section.vue
Normal file
167
components/uni-section/uni-section.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<view class="uni-section">
|
||||
<view class="uni-section-header" @click="onClick">
|
||||
<view class="uni-section-header__decoration" v-if="type" :class="type" />
|
||||
<slot v-else name="decoration"></slot>
|
||||
|
||||
<view class="uni-section-header__content">
|
||||
<text :style="{'font-size':titleFontSize,'color':titleColor}" class="uni-section__content-title" :class="{'distraction':!subTitle}">{{ title }}</text>
|
||||
<text v-if="subTitle" :style="{'font-size':subTitleFontSize,'color':subTitleColor}" class="uni-section-header__content-sub">{{ subTitle }}</text>
|
||||
</view>
|
||||
|
||||
<view class="uni-section-header__slot-right">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="uni-section-content" :style="{padding: _padding}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
/**
|
||||
* Section 标题栏
|
||||
* @description 标题栏
|
||||
* @property {String} type = [line|circle|square] 标题装饰类型
|
||||
* @value line 竖线
|
||||
* @value circle 圆形
|
||||
* @value square 正方形
|
||||
* @property {String} title 主标题
|
||||
* @property {String} titleFontSize 主标题字体大小
|
||||
* @property {String} titleColor 主标题字体颜色
|
||||
* @property {String} subTitle 副标题
|
||||
* @property {String} subTitleFontSize 副标题字体大小
|
||||
* @property {String} subTitleColor 副标题字体颜色
|
||||
* @property {String} padding 默认插槽 padding
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniSection',
|
||||
emits:['click'],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
titleFontSize: {
|
||||
type: String,
|
||||
default: '14px'
|
||||
},
|
||||
titleColor:{
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
subTitleFontSize: {
|
||||
type: String,
|
||||
default: '12px'
|
||||
},
|
||||
subTitleColor: {
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
padding: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
_padding(){
|
||||
if(typeof this.padding === 'string'){
|
||||
return this.padding
|
||||
}
|
||||
|
||||
return this.padding?'10px':''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
title(newVal) {
|
||||
if (uni.report && newVal !== '') {
|
||||
uni.report('title', newVal)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
$uni-primary: #2979ff !default;
|
||||
|
||||
.uni-section {
|
||||
background-color: #fff;
|
||||
.uni-section-header {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 12px 10px;
|
||||
font-weight: normal;
|
||||
|
||||
&__decoration{
|
||||
margin-right: 6px;
|
||||
background-color: $uni-primary;
|
||||
&.line {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&.circle {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top-right-radius: 50px;
|
||||
border-top-left-radius: 50px;
|
||||
border-bottom-left-radius: 50px;
|
||||
border-bottom-right-radius: 50px;
|
||||
}
|
||||
|
||||
&.square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
color: #333;
|
||||
|
||||
.distraction {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
&-sub {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&__slot-right{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-section-content{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user