添加注释

master
于肖磊 2025-12-05 17:31:05 +08:00
parent e4477da8e6
commit 6d7b17576f
7 changed files with 551 additions and 35 deletions

View File

@ -48,9 +48,20 @@
const animation = uni.requireNativePlugin('animation');
// #endif
/**
* 预定义的颜色数组用于点赞动画元素的颜色随机选择
*/
const COLORS = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57', '#ff9ff3', '#ff5252', '#66bb6a'];
/**
* 预定义的图标数组用于点赞动画元素的图标随机选择
*/
const ICONS = [];
/**
* 点赞动画效果组件
* 实现类似直播中点赞的效果包括飘心动画和连击数字提示
*/
export default {
name: 'SimpleLikeEffect',
props: {
@ -67,31 +78,80 @@
},
data() {
return {
/**
* 存储所有点赞动画元素的数组
*/
like_list: [],
//
/**
* 连续点赞计数
*/
like_count: 0,
/**
* 是否显示点赞计数
*/
show_like_count: false,
/**
* 点赞计数显示位置
*/
like_count_position: { x: 0, y: 0 },
/**
* 点赞计数文字颜色
*/
like_count_color: '#ff6b6b',
/**
* 点赞计数透明度
*/
like_count_opacity: 1,
/**
* 点赞计数缩放比例
*/
like_count_scale: 1,
/**
* 上次点赞的时间戳
*/
last_like_time: 0,
/**
* 点赞计数隐藏定时器
*/
like_count_timer: null,
/**
* 重置计数的定时器
*/
reset_timer: null, //
/**
* 上次点击时间用于防抖处理
*/
last_click_time: 0 //
}
},
computed: {
//
/**
* 可用的图标集合合并了默认图标和外部传入的自定义图标
* @returns {Array} 图标数组
*/
available_icons() {
return [...ICONS, ...this.propCustomIcons];
},
//
/**
* 可用的图片集合来自外部传入的自定义图片
* @returns {Array} 图片数组
*/
available_images() {
return this.propCustomImages;
}
},
methods: {
/**
* 添加点赞动画元素
* @param {Object} event - 点击事件对象用于获取点击坐标
* @param {Object} options - 可选配置参数
* @param {String} options.image_src - 自定义图片路径
* @param {String} options.icon - 自定义图标字符
* @param {String} options.color - 自定义颜色
*/
add_like(event, options = {}) {
//
const now = Date.now();
@ -183,11 +243,21 @@
this.handle_like_count(x, y, color);
},
/**
* 移除指定的点赞元素
* @param {Number|String} id - 要移除的点赞元素ID
*/
remove_like(id) {
this.like_list = this.like_list.filter(item => item.id !== id);
},
//
/**
* 处理连续点赞数量提示
* 当用户连续点赞时显示连击数
* @param {Number} x - 点击位置的x坐标
* @param {Number} y - 点击位置的y坐标
* @param {String} color - 数字提示颜色
*/
handle_like_count(x, y, color) {
const current_time = Date.now();
@ -248,7 +318,9 @@
}, 1200);
},
//
/**
* 隐藏点赞数量提示
*/
hide_like_count() {
// #ifdef APP-NVUE
const ref = this.$refs['likeCount'];
@ -274,7 +346,10 @@
// #endif
},
//
/**
* 执行单个点赞元素的动画效果
* @param {Number|String} id - 点赞元素ID
*/
animate_like_item(id) {
// #ifdef APP-NVUE
const ref = this.$refs['likeItem' + id];
@ -323,7 +398,9 @@
// #endif
},
//
/**
* 执行点赞数量提示的动画效果
*/
animate_like_count() {
// #ifdef APP-NVUE
const ref = this.$refs['likeCount'];
@ -347,6 +424,11 @@
}
// #endif
},
/**
* 更加健壮的数组检查方法
* @param {*} data - 待检测的数据
* @returns {Boolean} 是否为数组
*/
//
is_array(data) {
return Array.isArray(data) || data instanceof Array || (data && typeof data === 'object' && data.length !== undefined);

View File

@ -1,6 +1,7 @@
<template>
<view v-if="!is_loading" class="flex-row align-c jc-c pa-20 box-border-box oh bg-f9" :style="good_style">
<block v-if="good_list.length > 0">
<!-- 固定在顶部的商品操作栏 -->
<view class="goods-header-fixed" :style="'width:' + windowWidth + 'px;'">
<view class="flex-row align-c jc-e pa-10">
<view class="flex-col" @tap="goods_order">
@ -23,6 +24,7 @@
<scroll-view scroll-y class="scroll-type" :style="good_style + (propIsGoodsPopup ? 'padding-bottom: 20rpx;' : '' )" :show-scrollbar="false">
<view v-for="(item, index) in good_list" :key="item.id">
<!-- #endif -->
<!-- 商品项 -->
<view :class="'goods-item flex-row align-c box-border-box' + (item.id == explanation_id ? ' bg-red-light' : '')" :style="'width:' + (windowWidth - 24) + 'px;'" :data-index="index" :data-value="item.checkbox" :data-url="item.goods_url" @tap="goods_detail">
<view class="flex-1">
<view class="flex-row align-c">
@ -33,8 +35,9 @@
<!-- #ifdef APP-NVUE -->
<image :class="propIsGoodsPopup ? 'goods-item-popup-image' : 'goods-item-image'" :src="item.images" mode="aspectFit"></image>
<!-- #endif -->
<!-- 商品序号标识 -->
<text class="image-top-index">{{ index + 1 }}</text>
<!-- 音乐进度条 -->
<!-- 正在讲解中的商品音乐进度条 -->
<view v-if="item.id == explanation_id" class="music-progress-container flex-row align-c jc-c" :style="propIsGoodsPopup ? 'width: 200rpx;' : 'width: 120rpx;'">
<!-- #ifndef APP-NVUE -->
<view class="music-progress-bars mr-5">
@ -53,6 +56,7 @@
<text class="size-12 cr-f">讲解中</text>
</view>
</view>
<!-- 商品信息区域 -->
<view class="ml-10 flex-1 flex-col jc-sb goods-item-popup-content">
<text class="goods-item-title text-line-2">{{ item.title }}</text>
<view class="flex-1 mt-10">
@ -68,6 +72,7 @@
<text class="mr-5 size-14 cr-9">库存</text>
<text class="goods-item-inventory size-14 cr-9">{{ item.inventory }}</text>
</view>
<!-- 购买按钮 -->
<button type="primary" class="btn-block cr-main bg-main mr-0 ml-0 flex-row align-c jc-c pa-5" style="width: 100rpx;height:48rpx;border-radius: 10rpx" :data-id="item.id" :data-url="item.goods_url" @tap="goods_detail"><text class="size-14 cr-f">购买</text></button>
</view>
</view>
@ -89,6 +94,7 @@
<!-- #endif -->
</block>
<block v-else>
<!-- 无商品时的提示 -->
<view class="flex-1 flex-col align-c">
<text class="tip-title">暂无商品</text>
</view>
@ -101,6 +107,10 @@ import componentIcon from "@/pages/plugins/live/pull/components/icon/icon.vue";
import componentBottomLine from '@/components/bottom-line/bottom-line.vue';
const app = getApp();
/**
* 商品展示组件
* 用于直播间商品列表展示支持普通页面和弹窗两种模式
*/
export default {
name: 'Goods',
components: {
@ -108,11 +118,17 @@ export default {
componentBottomLine
},
props: {
// isGoodsPopup isGoodsPopupfalse
/**
* 是否是商品弹出框
* 弹出框一般是用在直播页显示的商品列表直播开始前isGoodsPopup是false显示的是一整个页面
*/
propIsGoodsPopup: {
type: Boolean,
default: true
},
/**
* 直播间ID
*/
propLiveRoomId: {
type: Number,
default: 0
@ -153,6 +169,10 @@ export default {
},
computed: {
//#region
/**
* 商品区域样式
* 根据是否为弹出框模式计算不同的宽高
*/
good_style() {
//
if (!this.propIsGoodsPopup) {
@ -172,7 +192,10 @@ export default {
watch: {
//#region
// #ifdef APP-NVUE
// ID
/**
* 监听正在讲解的商品ID变化
* 当商品讲解状态发生变化时控制音频动画的播放和停止
*/
explanation_id: {
handler(newVal, oldVal) {
//
@ -216,6 +239,10 @@ export default {
},
methods: {
//#region
/**
* 初始化商品列表
* 请求服务器获取商品数据并更新界面
*/
init() {
if (!this.propIsGoodsPopup) {
this.is_loading = true;
@ -266,7 +293,11 @@ export default {
//#endregion
//#region
//
/**
* 控制商品讲解状态
* 点击商品时切换其讲解状态已讲解则取消讲解未讲解则设为讲解中
* @param {Object} e 事件对象
*/
explanation(e) {
//
const id = e.currentTarget.dataset.id;
@ -280,7 +311,12 @@ export default {
//#region
// #ifdef APP-NVUE
// vueCSS
/**
* 启动第一个音频条动画
* 实现1.1秒周期的高度变化动画效果
* @param {Object} barRef 条形元素引用
* @param {Number} delay 动画延迟时间(ms)
*/
start_nvue_height_animation(barRef, delay) {
if (!barRef) return;
@ -362,7 +398,12 @@ export default {
this.animationTimers.push(timer);
},
// 1.4s
/**
* 启动第二个音频条动画
* 实现1.4秒周期的高度变化动画效果
* @param {Object} barRef 条形元素引用
* @param {Number} delay 动画延迟时间(ms)
*/
start_nvue_height_animation2(barRef, delay) {
if (!barRef) return;
@ -415,7 +456,12 @@ export default {
this.animationTimers.push(timer);
},
// 0.9s
/**
* 启动第三个音频条动画
* 实现0.9秒周期的高度变化动画效果
* @param {Object} barRef 条形元素引用
* @param {Number} delay 动画延迟时间(ms)
*/
start_nvue_height_animation3(barRef, delay) {
if (!barRef) return;
@ -462,6 +508,10 @@ export default {
this.animationTimers.push(timer);
},
/**
* 停止所有NVUE动画
* 清除动画定时器并重置所有音频条到初始状态
*/
stop_nvue_animation() {
//
this.animationTimers.forEach(timer => {
@ -488,21 +538,40 @@ export default {
},
// #endif
//#endregion
//
/**
* 查看商品详情
* 点击商品图片或购买按钮时跳转到商品详情页
* @param {Object} e 事件对象
*/
goods_detail(e) {
const url = e.currentTarget.dataset.url + '&live_room_id=1';
app.globalData.url_open(url);
},
//
/**
* 查看订单
* 点击订单图标时跳转到用户订单页面
* @param {Object} e 事件对象
*/
goods_order (e) {
console.log('查看订单');
app.globalData.url_open('/pages/user-order/user-order');
},
//
/**
* 查看购物车
* 点击购物车图标时跳转到购物车页面
* @param {Object} e 事件对象
*/
goods_cart (e) {
console.log('查看购物车');
app.globalData.url_open('/pages/cart-page/cart-page');
},
/**
* 返回上一页
*/
back() {
app.globalData.page_back_prev_event();
}
@ -711,5 +780,4 @@ export default {
}
}
// #endif
</style>
</style>

View File

@ -1,10 +1,25 @@
<!-- eslint-disable -->
<template>
<!-- 视频播放器容器 -->
<view class="player-wrapper" :id="videoWrapperId" :randomNum="randomNum" :change:randomNum="hlsVideoPlayer.randomNumChange" :viewportProps="viewportProps" :change:viewportProps="hlsVideoPlayer.viewportChange" :videoSrc="videoSrc" :change:videoSrc="hlsVideoPlayer.initVideoPlayer" :command="eventCommand" :change:command="hlsVideoPlayer.triggerCommand" :func="renderFunc" :change:func="hlsVideoPlayer.triggerFunc" />
</template>
<script>
/**
* H5 HLS视频播放组件
* 支持HLS流媒体播放和普通视频格式播放兼容多种浏览器和移动端设备
*/
export default {
/**
* 组件属性
* @property {String} propSrc - 视频源地址
* @property {Boolean} propAutoplay - 是否自动播放
* @property {Boolean} propControls - 是否显示控件
* @property {String} propObjectFit - 视频填充方式
* @property {Boolean} propMuted - 是否静音
* @property {Number} propPlaybackRate - 播放速率
* @property {String} propPoster - 视频封面图
*/
props: {
propSrc: {
type: String,
@ -35,6 +50,16 @@
default: ''
}
},
/**
* 组件内部数据
* @property {Number} randomNum - 随机数用于生成唯一ID
* @property {String} videoSrc - 视频源地址
* @property {Object} eventCommand - 视频原生事件指令
* @property {Object} renderFunc - renderjs层函数执行指令
* @property {Number} currentTime - 当前播放时间
* @property {Number} duration - 视频总时长
* @property {Boolean} playing - 是否正在播放
*/
data() {
return {
randomNum: Math.floor(Math.random() * 100000000),
@ -52,9 +77,16 @@
playing: false
}
},
/**
* 数据监听器
*/
watch: {
//
propSrc: {
/**
* 处理视频源地址变更
* @param {String} val - 新的视频源地址
*/
handler(val) {
if (!val) return
setTimeout(() => {
@ -64,11 +96,22 @@
immediate: true
}
},
/**
* 计算属性
*/
computed: {
/**
* 视频容器ID
* @returns {String} 唯一的视频容器ID
*/
videoWrapperId() {
return `video-wrapper-${this.randomNum}`
},
// renderjs
/**
* 视口属性集合
* @returns {Object} 包含所有视口相关配置的对象
*/
viewportProps() {
return {
autoplay: this.propAutoplay,
@ -80,28 +123,54 @@
}
}
},
//
/**
* 组件方法
*/
methods: {
/**
* 向父组件发送事件
* @param {Object} options - 事件选项
* @param {String} options.event - 事件名称
* @param {*} [options.data] - 事件数据
*/
//
eventEmit({ event, data }) {
this.$emit(event, data)
},
/**
* 更新视图层数据
* @param {Object} options - 数据选项
* @param {String} options.key - 数据键名
* @param {*} options.value - 数据值
*/
// viewdata
setViewData({ key, value }) {
key && this.$set(this, key, value)
},
/**
* 重置事件指令
*/
//
resetEventCommand() {
this.eventCommand = null
},
/**
* 发送播放指令
*/
//
play() {
this.eventCommand = 'play'
},
/**
* 发送暂停指令
*/
//
pause() {
this.eventCommand = 'pause'
},
/**
* 重置自定义函数指令
*/
//
resetFunc() {
this.renderFunc = {
@ -109,6 +178,10 @@
params: null
}
},
/**
* 移除视频
* @param {*} params - 参数
*/
// -
remove(params) {
this.renderFunc = {
@ -116,6 +189,10 @@
params
}
},
/**
* 全屏操作
* @param {*} params - 参数
*/
// -
fullScreen(params) {
this.renderFunc = {
@ -123,6 +200,10 @@
params
}
},
/**
* 跳转到指定时间点
* @param {Number} time - 时间点()
*/
//
toSeek(time) {
this.renderFunc = {
@ -135,9 +216,21 @@
</script>
<script module="hlsVideoPlayer" lang="renderjs">
/**
* RenderJS模块 - HLS视频播放器核心逻辑
* 在RenderJS环境中运行负责实际的视频播放控制
*/
import hlsjs from '@/node_modules/hls.js/dist/hls.min.js'
const PLAYER_ID = 'HLS_VIDEO_PLAYER'
export default {
/**
* RenderJS数据
* @property {String} num - 随机数标识符
* @property {HTMLElement} videoEl - 视频元素引用
* @property {Object} hlsPlayer - HLS播放器实例
* @property {Object} renderProps - 渲染属性
* @property {Boolean} autoplayRejected - 自动播放是否被拒绝
*/
data() {
return {
num: '',
@ -147,19 +240,41 @@
autoplayRejected: false //
}
},
/**
* RenderJS计算属性
*/
computed: {
/**
* 播放器ID
* @returns {String} 唯一的播放器ID
*/
playerId() {
return `${PLAYER_ID}_${this.num}`
},
/**
* 容器ID
* @returns {String} 视频容器ID
*/
wrapperId() {
return `video-wrapper-${this.num}`
}
},
/**
* RenderJS方法
*/
methods: {
/**
* 判断是否为苹果设备
* @returns {Boolean} 是否为苹果设备(iPhone或iPad)
*/
isApple() {
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('iphone') !== -1 || ua.indexOf('ipad') !== -1
},
/**
* 初始化视频播放器
* @param {String} src - 视频源地址
*/
async initVideoPlayer(src) {
await this.$nextTick()
if (!src) return
@ -217,6 +332,11 @@
this.attemptAutoPlay(videoEl, muted);
}
},
/**
* 尝试自动播放视频
* @param {HTMLVideoElement} videoElement - 视频元素
* @param {Boolean} isMuted - 是否静音
*/
//
attemptAutoPlay(videoElement, isMuted) {
const playPromise = videoElement.play();
@ -242,6 +362,10 @@
});
}
},
/**
* 初始化HLS播放器
* @param {String} src - HLS流地址
*/
//
initHlsPlayer(src) {
if (hlsjs.isSupported()) {
@ -277,6 +401,10 @@
this.videoEl.src = src
}
},
/**
* 监听视频相关事件
* 注册各种视频事件监听器包括播放暂停结束等
*/
//
listenVideoEvent() {
//
@ -405,6 +533,10 @@
document.addEventListener('fullscreenchange', fullscreenchangeHandler)
}
},
/**
* 销毁播放器实例
* 清理HLS播放器和视频元素释放资源
*/
//
destroyPlayer() {
if (this.hlsPlayer) {
@ -422,6 +554,10 @@
//
this.autoplayRejected = false;
},
/**
* 触发指令事件
* @param {String} eventType - 事件类型(play/pause等)
*/
triggerCommand(eventType) {
if (eventType) {
this.$ownerInstance.callMethod('resetEventCommand')
@ -434,6 +570,12 @@
}
}
},
/**
* 触发函数调用
* @param {Object} func - 函数对象
* @param {String} func.name - 函数名
* @param {*} func.params - 函数参数
*/
triggerFunc(func) {
const { name, params } = func || {}
if (name) {
@ -443,6 +585,9 @@
this.$ownerInstance.callMethod('resetFunc')
}
},
/**
* 移除视频处理器
*/
removeHandler() {
this.destroyPlayer()
this.$ownerInstance.callMethod('setViewData', {
@ -450,6 +595,10 @@
value: ''
})
},
/**
* 全屏处理器
* @param {*} params - 参数
*/
fullScreenHandler() {
if (this.videoEl) {
if (this.isApple()) {
@ -463,11 +612,19 @@
}
}
},
/**
* 跳转时间点处理器
* @param {Number} time - 时间点()
*/
toSeekHandler(time) {
if (this.videoEl) {
this.videoEl.currentTime = time
}
},
/**
* 视口变更处理
* @param {Object} props - 视口属性
*/
viewportChange(props) {
this.renderProps = props
const { autoplay, muted, controls, playbackRate } = props
@ -483,10 +640,18 @@
}
}
},
/**
* 随机数变更处理
* @param {Number} val - 随机数
*/
randomNumChange(val) {
this.num = val
}
},
/**
* 组件销毁前钩子
* 清理播放器资源
*/
//
beforeDestroy() {
this.destroyPlayer()

View File

@ -1,19 +1,24 @@
<template>
<view class="like-button">
<!-- #ifdef APP-NVUE -->
<!-- 在NVUE环境下使用list容器显示动画元素 -->
<list class="animate-wrap">
<!-- 循环渲染点赞动画元素 -->
<cell class="a-img" v-for="(item,index) in viewList" :key="item.elId" :ref="item.elId" :style="{ 'right': propSite.x || propSite[0] + 'rpx', 'bottom': propSite.y || propSite[1] + 'rpx'}">
<image :style="{ 'width': propImgWidth + 'rpx', 'height': propImgHeight + 'rpx'}" mode="widthFix" :src="item.src" :animation="item.animation"></image>
</cell>
</list>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<!-- 在非NVUE环境下使用view容器显示动画元素 -->
<view class="animate-wrap">
<!-- 循环渲染点赞动画元素 -->
<view class="a-img" v-for="(item,index) in viewList" :key="item.elId" :ref="item.elId" :style="{ 'right': propSite.x || propSite[0] + 'rpx', 'bottom': propSite.y || propSite[1] + 'rpx'}">
<image :style="{'width': propImgWidth + 'rpx', 'height': propImgHeight + 'rpx'}" mode="widthFix" :src="item.src" :animation="item.animation"></image>
</view>
</view>
<!-- #endif -->
<!-- 点赞按钮区域 -->
<view class="on-button">
<view ref="likeBtn" id="like-btn" class="el-like-btn" @tap="handleClick">
<slot></slot>
@ -24,46 +29,101 @@
</template>
<script>
/**
* 点赞动画按钮组件
* 实现点击按钮后产生飞出图标动画的效果常用于直播场景中的点赞功能
*/
export default {
props: {
/**
* 显示图标路径数组
* @type {Array}
* @default []
*/
propShowImgs: { //
type: Array,
default: () => {
return []
}
},
/**
* 动画持续时间毫秒
* @type {Number}
* @default 5000
*/
propDuration: { //
type: Number,
default: 5000
},
/**
* X轴方向随机幅度范围
* @type {Number}
* @default 50
*/
propRange: { // x
type: Number,
default: 50
},
/**
* 图标飞行最大高度
* @type {Number}
* @default 200
*/
propHigh: { //
type: Number,
default: 200
},
/**
* 组件宽度
* @type {Number|String}
* @default 52
*/
propWidth: { //
type: Number || String,
default: 52
},
/**
* 组件高度
* @type {Number|String}
* @default 52
*/
propHeight: { //
type: Number || String,
default: 52
},
/**
* 图标宽度
* @type {Number|String}
* @default 52
*/
propImgWidth: { //
type: Number || String,
default: 52
},
/**
* 图标高度
* @type {Number|String}
* @default 52
*/
propImgHeight: { //
type: Number || String,
default: 52
},
/**
* 点击节流时间毫秒防止过快点击
* @type {Number}
* @default 200
*/
propThrottle: { // ms
type: Number,
default: 200
},
/**
* 图标出现位置坐标
* 可以是[x, y]数组或{x, y}对象格式
* @type {Array|Object}
* @default [122, 95]
*/
propSite: { // x y [x<Number>, y<Number>]
type: Array || Object,
default: () => {
@ -73,10 +133,20 @@
}
}
},
/**
* 是否启用缩放效果可以设置为数字指定缩放倍数
* @type {Number|Boolean}
* @default false
*/
propLarge: { //
type: [Number, Boolean],
default: false
},
/**
* 是否单独移除动画元素true时逐个删除false时统一删除
* @type {Boolean}
* @default true
*/
propAlone: {
type: Boolean,
default: true
@ -84,17 +154,21 @@
},
data() {
return {
viewList: [], //
elId: 0, // id
oldTime: 0, //
timer: null, //
waitDeleteIndex: 0
viewList: [], //
elId: 0, // ID
oldTime: 0, //
timer: null, //
waitDeleteIndex: 0 //
}
},
watch: {},
methods: {
/**
* 处理点击事件创建并播放点赞动画
* @param {Event} e 点击事件对象
*/
handleClick(e) {
//
//
let interval = e.timeStamp - this.oldTime
if (interval < this.propThrottle) return null;
this.oldTime = e.timeStamp
@ -103,7 +177,9 @@
// #ifdef APP-NVUE
animation = weex.requireModule('animation')
// #endif
//
let randomImg = Math.floor(Math.random() * this.propShowImgs.length)
//
let _item = {
elId: 'el_likeicon_' + this.elId, // ref
src: this.propShowImgs[randomImg], //
@ -111,22 +187,24 @@
x: Math.ceil(Math.random() * this.propRange), //
q: Math.floor(Math.random() * 2), //
}
//
//
let _abs = ['-', '']
let _dirX = Number(_abs[_item.q] + _item.x) //
let _dirY = this.propHigh - Math.random() * 10
// DOM
//
this.elId++
this.viewList.push(_item)
// #ifndef APP-NVUE
// NVUE
_item.animation = uni.createAnimation({
duration: this.propDuration,
timingFunction: 'ease-out',
})
//
setTimeout(() => {
//
this.$emit('finished')
//
// propAlone
if (this.propAlone) {
this.waitDeleteIndex++
this.onThrottle(this.deleteView, this.propDuration)
@ -144,13 +222,16 @@
//
setTimeout(() => {
this.$nextTick(() => {
//
let _n = 1
if (this.propLarge) _n = typeof(this.propLarge) === 'number' ? this.propLarge : 2;
// #ifndef APP-NVUE
// NVUE
_item.animation.translateY(-_dirY).translateX(_dirX).scale(_n, _n).opacity(0).step()
_item.animation = _item.animation.export()
// #endif
// #ifdef APP-NVUE
// NVUE
let el = this.$refs[_item.elId][0];
clearTimeout(this.timer)
_item.animation.transition(el, {
@ -166,7 +247,7 @@
console.log('animation finished.')
//
this.$emit('finished')
//
// propAlone
if (this.propAlone) {
this.waitDeleteIndex++
this.onThrottle(this.deleteView, this.propDuration)
@ -185,10 +266,18 @@
//
this.$emit('handleClick', this.elId)
},
/**
* 删除已完成动画的视图元素
*/
deleteView() {
this.viewList.splice(0, this.waitDeleteIndex)
this.waitDeleteIndex = 0
},
/**
* 节流函数控制函数执行频率
* @param {Function} fn 需要节流的函数
* @param {Number} delay 延迟时间毫秒
*/
onThrottle(fn, delay) {
let verifi = true
return (function() {
@ -205,6 +294,7 @@
</script>
<style lang="scss" scoped>
/* 动画元素固定定位样式 */
.a-img {
position: fixed;
}

View File

@ -107,7 +107,20 @@
},
methods: {
//
/**
* 初始化分享配置
* @param {Object} config - 配置参数
* @param {String} config.type - 分享类型
* @param {Number} config.is_goods_poster - 是否为商品海报分享 (0|1)
* @param {Number} config.goods_id - 商品ID
* @param {String} config.url - 分享链接
* @param {String} config.images - 分享图片
* @param {String} config.title - 分享标题
* @param {String} config.summary - 分享摘要
* @param {Object} config.share_info - 分享信息
* @param {Boolean} config.status - 是否显示分享弹窗
* @returns {Boolean} 初始化结果
*/
init(config = {}) {
if (!app.globalData.is_single_page_check()) {
return false;
@ -168,12 +181,16 @@
// #endif
},
//
/**
* 关闭分享弹窗
*/
popup_close_event(e) {
this.$refs.popupShareRef.close();
},
// url
/**
* 复制链接分享
*/
share_url_copy_event() {
var url = app.globalData.get_page_url();
//
@ -187,7 +204,9 @@
app.globalData.text_copy_event(url);
},
//
/**
* 基础分享事件支付宝小程序
*/
share_base_event() {
this.$refs.popupShareRef.close();
uni.pageScrollTo({
@ -201,7 +220,9 @@
});
},
//
/**
* 商品海报分享
*/
poster_event() {
var user = app.globalData.get_user_info(this, 'poster_event');
if (user != false) {
@ -235,7 +256,13 @@
}
},
// app
/**
* APP端分享事件
* @param {Object} e - 事件对象
* @param {Object} e.currentTarget.dataset - 事件数据集
* @param {String} e.currentTarget.dataset.provider - 分享提供商weixinqq
* @param {String} e.currentTarget.dataset.scene - 分享场景WXSceneSessionWXSceneTimeline等
*/
share_app_event(e) {
//
var provider = e.currentTarget.dataset.provider;

View File

@ -13,11 +13,22 @@
<script>
import H5HlsVideo from '@/pages/plugins/live/pull/components/h5-hls-video/h5-hls-video.vue';
import { isEmpty } from '@/common/js/common/common.js';
/**
* 直播拉流视频组件
* 支持H5小程序和APP三种平台的视频播放
* 根据不同平台使用不同的播放器组件实现视频播放功能
*/
export default {
components: {
H5HlsVideo
},
props: {
/**
* 视频源地址
* @type {String}
* @default 'http://live-pull-all.shopxo.vip/68f764013572f9240ca7ce6c/shopxo122.m3u8'
*/
propSrc:{
type: String,
default: 'http://live-pull-all.shopxo.vip/68f764013572f9240ca7ce6c/shopxo122.m3u8'
@ -31,14 +42,25 @@
}
},
created() {
//
const data = uni.getWindowInfo();
this.windowWidth = data.windowWidth;
this.windowHeight = data.windowHeight;
},
methods: {
/**
* 直播播放器状态变化处理函数小程序平台
* @param {Object} e - 状态变化事件对象
*/
statechange(e) {
console.log(e.detail.code);
},
/**
* 播放错误处理函数
* 根据不同平台处理播放错误并在适当条件下触发ended事件
* @param {Object} e - 错误事件对象
*/
error(e) {
// #ifdef H5
// ,
@ -53,11 +75,22 @@
// #endif
console.log(e, 'error');
},
/**
* 视频播放结束处理函数
* 当视频播放完成时触发ended事件通知父组件
*/
// video app使
ended() {
this.$emit('ended');
},
//#ifdef H5
/**
* H5平台自动播放成功处理函数
* 当静音自动播放成功时触发mutedAutoPlaySuccess事件
* @param {Boolean} e - 是否自动播放成功
*/
// 访
// , ,
auto_play_success(e) {
@ -66,6 +99,12 @@
this.$emit('mutedAutoPlaySuccess');
}
},
/**
* H5平台自动播放失败处理函数
* 当自动播放失败时设置为静音状态重新尝试播放
* @param {Boolean} e - 是否自动播放失败
*/
// ,
auto_play_error(e) {
//
@ -73,6 +112,11 @@
this.muted = true;
}
},
/**
* H5平台静音状态切换函数
* 用户点击后取消静音状态
*/
//
muted_tap() {
this.muted = false;

View File

@ -1,6 +1,13 @@
/**
* 直播间混入组件
* 提供直播间相关的基本功能和数据管理
*/
const app = getApp();
import { isEmpty } from '@/common/js/common/common.js';
export default {
/**
* 组件内部数据
*/
data() {
return {
windowWidth: 0,
@ -20,6 +27,11 @@ export default {
lastLikeTime: 0 // 记录上次点赞时间,用于防抖
}
},
/**
* 页面加载时执行
* @param {Object} params 页面参数
*/
onLoad(params) {
// 调用公共事件方法
app.globalData.page_event_onload_handle(params);
@ -27,6 +39,9 @@ export default {
this.params = app.globalData.launch_params_handle(params);
},
/**
* 页面显示时执行
*/
onShow() {
// 调用公共事件方法
app.globalData.page_event_onshow_handle();
@ -41,10 +56,19 @@ export default {
// #endif
this.init();
},
/**
* 组件挂载完成后执行
*/
mounted() {
this.init();
},
methods: {
/**
* 初始化直播间数据
* 请求服务器获取直播间详情信息
*/
init() {
uni.showLoading({
title: '直播数据加载中...',
@ -88,13 +112,25 @@ export default {
}
});
},
/**
* 标记直播结束
*/
ended() {
this.is_live_ended = true;
},
/**
* 返回上一页
*/
live_back() {
app.globalData.page_back_prev_event();
},
// 处理鼠标双击事件
/**
* 处理鼠标双击事件
* @param {Event} event 鼠标事件对象
*/
handle_double_click(event) {
if (event.target.dataset.ignore) {
return;
@ -116,7 +152,11 @@ export default {
}
},
// 处理触屏双击事件
/**
* 处理触屏双击事件
* 检测用户在屏幕上的双击操作以触发点赞效果
* @param {Event} event 触摸事件对象
*/
handle_touch_end(event) {
if (event.target.dataset.ignore) {
return;