新增:文章详情页面添加弹幕功能;

修改:修复友链列表单分组显示问题;
优化;对恋爱日记清单列表进行优化
This commit is contained in:
小莫唐尼 2022-12-12 23:03:48 +08:00
parent f12262f4df
commit 9573b303e2
13 changed files with 250 additions and 93 deletions

View File

@ -65,14 +65,14 @@ export default {
line-height: 24px;
margin-bottom: 18px;
position: relative;
font-size: 1.2em;
font-size: 1.12em;
`,
h5: `
color: var(--main);
line-height: 24px;
margin-bottom: 14px;
position: relative;
font-size: 16px;
font-size: 1.1em;
`,
h6: `
color: #303133;
@ -90,7 +90,7 @@ export default {
strong: 'font-weight: 700;color: rgb(248, 57, 41);',
video: 'width: 100%',
},
containStyle: 'font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;padding:12px;font-size: 16px;color: #606266;word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 6px;background-color:#FFFFFF;',
containStyle: 'font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;padding:12px;font-size: 14px;color: #606266;word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 6px;background-color:#FFFFFF;',
loadingGif: HaloConfig.loadingGifUrl,
emptyGif: HaloConfig.loadingEmptyUrl,

View File

@ -1,15 +1,11 @@
<template>
<view style="overflow: hidden;position: fixed;width: 100%;height: 100%;pointer-events: none; top: 0;">
<view class="danmu-li" v-for="(item, index) in listData" :class="item.type" :style="item.style" :key="index">
<view class="barrage-wrap" :class="[type]">
<view class="danmu-li" v-for="(item, index) in listData" :class="[type, item.type]" :style="[item.style]" :key="index">
<view class="danmu-inner">
<view class="user-box">
<view class="user-img">
<view class="img-box">
<image :src="item.avatar || 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=317894666,3379114684&fm=26&gp=0.jpg'"></image>
</view>
</view>
<view class="user-text cl1">{{ item.nickName }}</view>
<view class="user-status cl1">{{ item.text }}</view>
<view class="user-img"><image class="avatar" :src="item.item.avatar"></image></view>
<view class="user-text cl1">{{ item.item.author }}</view>
<view class="user-status cl1">{{ item.item.content }}</view>
</view>
</view>
</view>
@ -23,6 +19,12 @@ export default {
type: String,
default: 'rightToLeft'
},
list: {
type: Array,
default() {
return [];
}
},
minTime: {
type: Number,
default: 4
@ -43,34 +45,35 @@ export default {
//
type: Number,
default: 40
},
noStacked: {
//()
type: Array,
default() {
return [];
}
}
},
data() {
return {
closeTimer: null,
listData: []
};
},
mounted() {
this.hrackNum = Math.floor((this.maxTop - this.minTop) / this.hrackH);
//leftBottom 使
if (this.type === 'leftBottom') {
this.hrackNum = Math.floor(this.maxTop / this.hrackH);
}
},
methods: {
add(obj) {
console.log('加载弹幕中...');
this.isShow = true;
this.showFlag = true;
let data = {
item: obj.item,
...obj,
id: Date.parse(new Date()),
time: Math.ceil(Math.floor(Math.random() * (this.maxTime - this.minTime + 1) + this.minTime)),
type: this.type
};
if (this.type === 'leftBottom') {
console.log('leftBottom加载弹幕中...');
let objData = {
item: data.item,
item: data,
type: 'leftBottomEnter',
style: {
transition: `all 0.5s`,
@ -88,7 +91,7 @@ export default {
} else if (this.listData[i].status === 'reset') {
//
this.listData[i].style.transition = 'none';
this.listData[i].style.bottom = 0;
this.listData[i].style.bottom = '6px';
this.listData[i].status = 'reuse';
} else if (this.listData[i].status === 'recycle') {
//
@ -105,8 +108,16 @@ export default {
if (listLen < hrackNum + 2) {
this.listData.push(objData);
}
} else if (this.type === 'rightToLeft' || this.type === 'leftToRight') {
let objData = this.horStacked(data);
} else if (this.type === 'rightToLeft') {
let objData = {
item: data.item,
type: 'rightToLeft',
style: {
animationDuration: `${data.time}s`,
top: `${Math.ceil(Math.random() * (this.maxTop - this.minTop + 1) + this.minTop)}px`
},
delTime: Date.parse(new Date()) + data.time * 1200
};
for (let i in this.listData) {
if (this.listData[i].delTime <= Date.parse(new Date())) {
this.repaint(i, objData.type);
@ -118,16 +129,40 @@ export default {
this.listData.push(objData);
}
},
horStacked(data) {
return {
item: data.item,
type: this.type,
style: {
animationDuration: `${data.time}s`,
top: `${Math.ceil(Math.random() * (this.maxTop - this.minTop + 1) + this.minTop)}px`
async remove(options = {}) {
options = Object.assign(
{},
{
duration: 5000, //
speed: 1000 //
},
delTime: Date.parse(new Date()) + data.time * 1200
options
);
const _fnHandleRemove = item => {
return new Promise(resolve => {
setTimeout(() => {
item['type'] = 'leftBottomExitLeft';
// this.$forceUpdate();
resolve();
}, options.speed);
});
};
const _fnHandleLoop = item => {
return new Promise(resolve => {
setTimeout(async () => {
for (var i = 0; i < this.listData.length; i++) {
await _fnHandleRemove(this.listData[i]);
}
resolve();
}, options.duration);
});
};
await _fnHandleLoop();
setTimeout(() => {
this.listData = [];
}, 100);
},
repaint(index, type) {
setTimeout(() => {
@ -137,8 +172,8 @@ export default {
}
};
</script>
<style></style>
<style lang="scss">
<style lang="scss" scoped>
@keyframes leftBottomEnter {
0% {
transform: translateY(100%);
@ -163,6 +198,17 @@ export default {
}
}
@keyframes leftBottomExitLeft {
0% {
transform: translateX(0%);
opacity: 1;
}
100% {
transform: translateX(-200%);
opacity: 0;
}
}
@keyframes leftToRight {
0% {
transform: translateX(-100%);
@ -182,13 +228,32 @@ export default {
transform: translateX(-100%);
}
}
.barrage-wrap {
position: fixed;
width: 100%;
height: 100%;
pointer-events: none;
top: 0;
z-index: 99;
transition: all 1s ease-in-out;
&.leftBottom {
top: initial;
bottom: 130rpx;
}
}
.danmu-li {
position: absolute;
width: 100%;
transform: translateX(100%);
animation-timing-function: linear;
transition: transform 0.5s ease-in-out;
&.is-hide {
transform: translateX(-100%) !important;
}
&.leftBottom {
left: 24rpx;
}
&.leftBottomEnter {
animation-name: leftBottomEnter;
}
@ -196,53 +261,59 @@ export default {
animation-name: leftBottomExit;
animation-fill-mode: forwards;
}
&.leftBottomExitLeft {
animation-name: leftBottomExitLeft;
animation-fill-mode: forwards;
}
&.rightToLeft {
animation-name: rightToLeft;
}
&.leftToRight {
animation-name: leftToRight;
animation-name: rightToLeft;
}
.danmu-inner {
display: inline-block;
font-size: 24rpx;
.user-box {
display: flex;
padding: 3rpx 40rpx 3rpx 10rpx;
padding: 6rpx 40rpx 6rpx 10rpx;
background: rgba(0, 0, 0, 0.3);
border-radius: 32rpx;
align-items: center;
.user-img {
.img-box {
display: flex;
image {
width: 58rpx;
height: 58rpx;
background: rgba(55, 55, 55, 1);
border-radius: 50%;
}
display: flex;
align-items: center;
justify-content: center;
.avatar {
width: 42rpx;
height: 42rpx;
background: rgba(55, 55, 55, 1);
border-radius: 50%;
}
}
.user-status {
margin-left: 10rpx;
white-space: nowrap;
font-size: 28rpx;
font-weight: 400;
color: rgba(255, 255, 255, 1);
max-width: 320rpx;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.user-text {
max-width: 100rpx;
margin-left: 10rpx;
// white-space: nowrap;
font-size: 28rpx;
font-weight: 400;
width: 80rpx;
color: rgba(255, 255, 255, 1);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}

View File

@ -143,6 +143,7 @@ export default {
this.result = res.data;
this.dataList = res.data.content;
this.loading = 'success';
this.$emit('on-loaded', this.dataList);
} else {
this.loading = 'error';
}

View File

@ -1,15 +0,0 @@
#如何使用
###js
```javascript
import lffBarrage from '@/components/lff-barrage/lff-barrage.vue'
components:{lffBarrage},
methods:{
colrdo(){ //插入一条弹幕
this.$refs.lffBarrage.add({item:'你好呀小伙子'});
}
}
```
###HTML
```html
<lff-barrage ref="lffBarrage"></lff-barrage>
```

View File

@ -20,12 +20,11 @@ export default {
}
},
data() {
return { _scrollTop: 0 };
return { timer: null, _scrollTop: 0 };
},
watch: {
scrollTop(val) {
this._scrollTop = val;
console.log('监听1', val);
this.$forceUpdate();
}
},
@ -35,11 +34,18 @@ export default {
methods: {
fnScroll() {
throttle(() => {
this.$emit('on-status', false);
const isTop = this._scrollTop >= 180;
this._scrollTop = isTop ? 0 : 999999;
this._scrollTop = isTop ? 0 : 999999999999;
uni.pageScrollTo({
duration: 500,
scrollTop: this._scrollTop
scrollTop: this._scrollTop,
success: () => {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.$emit('on-status', true);
}, 500);
}
});
this.$emit('update:scrollTop', this._scrollTop);
});

View File

@ -15,10 +15,10 @@ export default {
girl: {
name: '未知女主',
avatar: 'https://b.925i.cn/uni_halo_love/love_girl.png',
birthday: '2022-07-25', // 生日
birthday: '2022-04-19', // 生日
},
timeTitle: '这是我们一起走过的',
loveStartDate: '2022-05-20 13:14:20', // 恋爱开始时间
loveStartDate: '2022-04-10 13:14:20', // 恋爱开始时间
nav: [{
key: 'journey',

View File

@ -2,7 +2,7 @@
<view class="app-page pb-24">
<!-- 博主信息 -->
<view class="blogger-info" :style="[calcProfileStyle]">
<image class="avatar" :src="$utils.checkAvatarUrl(bloggerInfo.avatar)" mode="aspectFill" @click="miniProfileCard.show = true"></image>
<image class="avatar" :src="$utils.checkAvatarUrl(bloggerInfo.avatar)" mode="aspectFill"></image>
<view class="profile">
<view class="author mt-24 text-size-g text-weight-b">{{ bloggerInfo.nickname }}</view>
<view class="desc mt-24 text-size-m">{{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }}</view>

View File

@ -13,16 +13,34 @@
<!-- 如果只有一个分组使用列表的形式 result.length == 1 -->
<view v-else-if="result.length == 1" class="flex flex-col pb-24">
<block v-for="(item, index) in result[0].children" :key="index">
<block v-for="(link, index) in result[0].children" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<view class="link-card one round-a-6 flex ma-24 mb-0 pa-24" @click="fnOnLinkEvent(item)">
<cache-image class="logo shadow-2" radius="12rpx" width="80rpx" height="80rpx" :url="item.logo" :fileMd5="item.logo" mode="aspectFill"></cache-image>
<!-- 色彩版本 -->
<view
v-if="!globalAppSettings.links.useSimple"
class="info flex pt-24 pb-24 pl-12 pr-12 border-b-1"
:class="{ 'border-b-1': index != team.children.length - 1 }"
@click="fnOnLinkEvent(link)"
>
<view class="link-logo"><cache-image class="link-logo_img" radius="12rpx" :url="link.logo" :fileMd5="link.logo" mode="aspectFill"></cache-image></view>
<view class="flex flex-col pl-30 info-detail">
<view class="link-card_name text-size-l text-weight-b text-red">{{ link.name }}</view>
<view class="poup-tag ml--10 mt-6">
<tm-tags color="bg-gradient-amber-accent" :shadow="0" size="s" model="fill">ID{{ link.id }}</tm-tags>
<tm-tags color=" bg-gradient-light-blue-lighten" :shadow="0" size="s" model="fill">{{ link.team || '暂未分组' }}</tm-tags>
</view>
<view class="link-card_desc text-overflow text-size-s mt-4">博客简介{{ link.description || '这个博主很懒,没写简介~' }}</view>
</view>
</view>
<!-- 简洁版本 -->
<view v-else class="link-card flex ml-24 mr-24 pt-24 pb-24" @click="fnOnLinkEvent(link)">
<image class="logo shadow-6" :src="link.logo" mode="aspectFill"></image>
<view class="info pl-24">
<view class="name text-size-g">{{ item.name }}</view>
<view class="desc mt-12 text-size-s text-grey-darken-1">{{ item.description }}</view>
<view class="link mt-12 text-size-m text-grey-darken-1">
<view class="name text-size-g">{{ link.name }}</view>
<view class="desc mt-12 text-size-s text-grey-darken-1">{{ link.description }}</view>
<view v-if="false" class="link mt-12 text-size-m text-grey-darken-1">
<text class="iconfont icon-link mr-6 text-size-s"></text>
{{ item.url }}
{{ link.url }}
</view>
</view>
</view>
@ -51,7 +69,7 @@
<view class="link-card_name text-size-l text-weight-b text-red">{{ link.name }}</view>
<view class="poup-tag ml--10 mt-6">
<tm-tags color="bg-gradient-amber-accent" :shadow="0" size="s" model="fill">ID{{ link.id }}</tm-tags>
<tm-tags color=" bg-gradient-light-blue-lighten" :shadow="0" size="s" model="fill">{{ link.team }}</tm-tags>
<tm-tags color=" bg-gradient-light-blue-lighten" :shadow="0" size="s" model="fill">{{ link.team || '暂未分组' }}</tm-tags>
</view>
<view class="link-card_desc text-overflow text-size-s mt-4">博客简介{{ link.description || '这个博主很懒,没写简介~' }}</view>
</view>
@ -86,7 +104,7 @@
<view class="poup-name text-size-lg text-weight-b">{{ detail.data.name }}</view>
<view class="poup-tag ml--10">
<tm-tags color="bg-gradient-amber-accent" size="n" model="fill">ID{{ detail.data.id }}</tm-tags>
<tm-tags color="bg-gradient-light-blue-lighten" size="n" model="fill">{{ detail.data.team }}</tm-tags>
<tm-tags color="bg-gradient-light-blue-lighten" size="n" model="fill">{{ detail.data.team || '暂未分组' }}</tm-tags>
</view>
<view class="poup-link text-size-m" @click="fnCopyLink(detail.data)">
<text class="text-orange">{{ detail.data.url }}</text>

View File

@ -105,10 +105,18 @@
<!-- 评论展示区域 -->
<view class="comment-wrap bg-white mt-24 pa-24 round-4">
<commentList :disallowComment="result.disallowComment" :postId="result.id" :post="result" @on-comment-detail="fnOnShowCommentDetail"></commentList>
<commentList
:disallowComment="result.disallowComment"
:postId="result.id"
:post="result"
@on-comment-detail="fnOnShowCommentDetail"
@on-loaded="fnOnCommentLoaded"
></commentList>
</view>
</view>
<!-- 弹幕效果 -->
<barrage ref="barrage" :maxTop="240" type="leftBottom"></barrage>
<!-- 返回顶部 -->
<tm-flotbutton :offset="[16, 80]" icon="icon-angle-up" color="bg-gradient-light-blue-accent" @click="fnToTopPage()"></tm-flotbutton>
<tm-flotbutton :actions="btnOption.actions" actions-pos="left" :show-text="true" color="bg-gradient-orange-accent" @change="fnOnFlotButtonChange"></tm-flotbutton>
@ -190,6 +198,7 @@ import commentList from '@/components/comment-list/comment-list.vue';
import commentItem from '@/components/comment-item/comment-item.vue';
import rCanvas from '@/components/r-canvas/r-canvas.vue';
import barrage from '@/components/barrage/barrage.vue';
export default {
components: {
tmSkeleton,
@ -200,7 +209,8 @@ export default {
mpHtml,
commentList,
commentItem,
rCanvas
rCanvas,
barrage
},
data() {
return {
@ -629,6 +639,48 @@ export default {
uni.navigateTo({
url: `/pagesA/tag-detail/tag-detail?id=${tag.id}&slug=${tag.slug}&name=${tag.name}`
});
},
async fnOnCommentLoaded(data) {
const _list = [];
const _handleData = list => {
return new Promise(resolve => {
if (list.length == 0) {
resolve();
} else {
list.forEach(item => {
_list.push(item);
if (item.children && item.children.length != 0) {
_handleData(item.children);
}
resolve();
});
}
});
};
await _handleData(data);
if (this.globalAppSettings.barrage.use) {
this.$nextTick(() => {
_handleAddBarrage();
});
}
const _handleRemove = () => {
this.$refs['barrage'].remove({
duration: 5000, //
speed: 1000 //
});
};
let index = 0;
const _handleAddBarrage = () => {
setTimeout(() => {
this.$refs['barrage'].add(_list[index]);
index += 1;
if (index < _list.length - 1) {
_handleAddBarrage();
} else {
_handleRemove();
}
}, 1000);
};
}
}
};

View File

@ -303,7 +303,8 @@ export default {
&-desc {
margin-top: 24rpx;
line-height: 1.6;
font-size: 28rpx;
font-size: 30rpx;
color: rgba(26, 26, 26, 0.9);
&.is-empty {
display: flex;
align-items: center;

View File

@ -1,6 +1,6 @@
<template>
<view class="app-page">
<view class="love-card">
<view class="app-page" @touchstart="fnOnTouchstart" @touchend="fnOnTouchend" @touchcancel="fnOnTouchend">
<view class="love-card" :class="{ ani: isDoAni }">
<view class="head">
<image class="avatar" :src="loveConfig.boy.avatar" mode="scaleToFill"></image>
<view class="love-days">
@ -31,7 +31,7 @@
</view>
<view v-else class="list">
<block v-for="(item, index) in list" :key="index">
<view class="card" :style="{ '--delay': calcCardDelay(index) }">
<view class="card" :class="{ ani: isDoAni }" :style="{ '--delay': calcCardDelay(index) }">
<view class="head">
<view class="status">
<view v-if="!item.finish" class="text">进行中</view>
@ -70,7 +70,7 @@
</view>
</block>
</view>
<scroll-btn :scrollTop.sync="scrollTop"></scroll-btn>
<scroll-btn :scrollTop.sync="scrollTop" @on-status="fnOnScrollStatus"></scroll-btn>
</view>
</template>
@ -81,6 +81,7 @@ export default {
components: { ScrollBtn },
data() {
return {
isDoAni: true,
scrollTop: 0,
loveConfig: LoveConfig,
list: []
@ -142,6 +143,15 @@ export default {
fnOnItemOpen(item) {
item.open = !item.open;
this.$forceUpdate();
},
fnOnScrollStatus(isEnd) {
this.isDoAni = isEnd;
},
fnOnTouchstart() {
this.isDoAni = false;
},
fnOnTouchend() {
this.isDoAni = true;
}
}
};
@ -188,7 +198,9 @@ export default {
background-color: rgba(255, 199, 184, 0.9);
margin-bottom: 52rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.1);
animation: loveCardAni 3s ease-in-out infinite;
&.ani {
animation: loveCardAni 3s ease-in-out infinite;
}
.head {
display: flex;
@ -302,8 +314,10 @@ export default {
background-color: #ffffff;
margin-bottom: 36rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.05);
animation: cardAni 3s ease-in-out infinite;
animation-delay: var(--delay);
&.ani {
animation: cardAni 3s ease-in-out infinite;
}
.head {
width: 100%;
display: flex;

View File

@ -78,6 +78,10 @@
<text class="text-size-m ">友链简洁模式</text>
<tm-switch v-model="appSettings.links.useSimple" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m">启用评论弹幕</text>
<tm-switch v-model="appSettings.barrage.use" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m">是否圆形头像</text>
<tm-switch v-model="appSettings.isAvatarRadius" color="light-blue" :text="['是', '否']"></tm-switch>

View File

@ -35,6 +35,11 @@ export const _DefaultAppSettings = {
timeout: 3, // 屏蔽广告时长,时间到后自动恢复展示(单位小时)
disabled: false, // 是否屏蔽广告(看广告可以关闭应用内设置的广告)
},
// 评论弹幕(文章详情)
barrage: {
use: true, // 是否启用
type: 'leftBottom' // 弹幕类型
},
gallery: {
// 是否使用瀑布流
useWaterfull: true