vr-shopxo-uniapp/pages/plugins/video/detail/detail.nvue

2114 lines
112 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view :class="theme_view" :style="width_height_style">
<view v-if="display_video_list && display_video_list.length > 0" class="content pr" :style="width_height_style">
<!-- 视频列表 -->
<swiper class="swiper-container" :key="'top-or-buttom-' + swiper_key" :style="width_height_style + swiperStyle" :duration="300" :vertical="true" :circular="false" :skip-hidden-item-layout="true" :current="current_index" easing-function="linear" @transition="on_transition" @change="handle_swiper_change">
<swiper-item v-for="(video_item, index) in display_video_list" :key="video_item.id" :style="width_height_style">
<view class="pr" @tap.stop="toggle_play_pause" :style="width_height_style" @touchstart.prevent="handle_swiper_touch_start" @touchmove.prevent="handle_swiper_touch_move" @touchend="handle_swiper_touch_end">
<view class="video-bg" :style="(!isEmpty(video_item.cover) ? 'background-image: url(' + video_item.cover + ')' : '') + width_height_style"></view>
<video class="video" :style="width_height_style + swiperStyle" :src="video_item.video_url" :poster="video_item.cover" :id="`video_${index}`" :loop="true" :show-fullscreen-btn="false" :show-center-play-btn="false" :show-play-btn="false" :controls="false" :show-mute-btn="true" object-fit="contain" @timeupdate="handle_time_update" @play="handle_play" @tap.stop="toggle_play_pause"></video>
<view v-if="paused && current_index == index" class="play-icon" :style="width_height_style + swiperStyle">
<view class="flex-1 pr flex-row align-c jc-c">
<view class="play-icon-bg"></view>
<view class="pa z-i">
<u-icon propName="bofang" propSize="120rpx" propColor="#4F3E35"></u-icon>
</view>
</view>
</view>
<template v-if="!show_comment_modal">
<!-- Right Action Bar -->
<view class="right-actions">
<view v-if="base_config_data && base_config_data.is_video_give_thumbs && base_config_data.is_video_give_thumbs == 1" class="action-item" :data-id="video_item.id" @tap.stop="handle_like">
<u-icon propName="givealike" propSize="60rpx" :propColor="video_item.is_give_thumbs == 0 ? '#fff' : '#F4B73F'"></u-icon>
<text class="action-text">{{ video_item.give_thumbs_count }}</text>
</view>
<view v-if="base_config_data && base_config_data.is_video_comments_show && base_config_data.is_video_comments_show == 1" class="action-item" :data-id="video_item.id" @tap.stop="handle_comment">
<u-icon propName="comment" propSize="60rpx" propColor="#fff"></u-icon>
<text class="action-text">{{ video_item.comments_count }}</text>
</view>
<view class="action-item" @tap.stop="handle_share">
<u-icon propName="share-solid" propSize="60rpx" propColor="#fff"></u-icon>
<text class="action-text">{{$t('common.share')}}</text>
</view>
</view>
<view v-if="!isEmpty(video_item.goods) && base_config_data && base_config_data.is_video_detail_show_goods && base_config_data.is_video_detail_show_goods == 1" class="product-card">
<view class="flex-col">
<view v-if="video_item.show_goods" class="flex-row align-c product-card-item" style="margin-bottom: 20rpx;" :data-id="video_item.id" @tap.stop="handle_product_card_item">
<view style="width: 100rpx;height:100rpx;">
<image :src="video_item.goods.images" mode="aspectFill" style="width: 100rpx;height:100rpx;"></image>
</view>
<view class="flex-1 flex-col align-sb jc-c" style="margin-left: 20rpx;">
<text class="product-name text-line-1" style="margin-bottom: 20rpx;">{{ video_item.goods.title }}</text>
<text class="product-price">¥{{ video_item.goods.price }}</text>
</view>
<view class="product-close" :data-id="video_item.id" @tap.stop="product_close_event">
<u-icon propName="close" propSize="50rpx" propColor="#999"></u-icon>
</view>
</view>
<view class="product-button" :data-id="video_item.id" @tap.stop="handle_product_button">
<view class="product-button-left flex-row align-c">
<u-icon propName="cart-have" propSize="30rpx" propColor="#F5C366"></u-icon>
<text class="size-14 cr-f" style="margin-left: 20rpx;">{{$t('common.buy')}} {{$t('common.goods')}}</text>
</view>
<u-icon propName="angle-right" propSize="30rpx" propColor="#fff"></u-icon>
</view>
</view>
</view>
<!-- Progress Bar -->
<view class="progress-bar-container" v-if="current_index == index" :style="'width: ' + (windowWidth - 20) + 'px'">
<slider class="flex-1 progress-slider" :value="current_video_progress" :max="current_video_duration" @change.stop="handle_slider_change" @changing="handle_slider_changing" @tap.stop="handle_slider_change" block-size="14" activeColor="#FFFFFF" backgroundColor="rgba(255, 255, 255, 0.4)" />
<text class="time-display">{{ format_time(current_video_progress) }} / {{ format_time(current_video_duration) }}</text>
</view>
</template>
</view>
</swiper-item>
</swiper>
<!-- 搜索框 -->
<view v-if="!show_comment_modal" class="header-top" :style="top_content_style + menu_button_info + ' width: ' + windowWidth + 'px;'">
<view id="search-height" class="flex-row align-c">
<!-- 支付宝小程序自带返回按钮,这里就不给返回按钮了,这里给留出一点空间就行 -->
<!-- #ifndef MP-ALIPAY -->
<view class="cp" @tap="handle_back">
<u-icon propName="arrow-left" propSize="36rpx" propColor="#333" class="mr-10"></u-icon>
</view>
<!-- #endif -->
<view class="flex-1" :style="header_padding_left">
<search-component propIsDisabled @disabledSearch="handle_search" :propsWidth="windowWidth" />
</view>
</view>
</view>
</view>
<template v-else>
<component-no-data :propStatus="data_list_loding_status" :propMsg="data_list_loding_msg"></component-no-data>
</template>
<!-- 评论弹窗 -->
<view v-if="show_comment_modal" class="comment-modal" :style="width_height_style">
<view class="comment-content bottom-line-exclude-bottom" @tap.stop :style="commentContentStyle">
<view class="comment-header" data-type="header" @tap.stop @touchstart.prevent="handle_comment_touch_start" @touchmove.prevent="handle_comment_touch_move" @touchend="handle_comment_touch_end">
<text class="comment-count">{{$t('common.comment')}}</text>
<view class="close-btn" @tap="close_comment_modal">✕</view>
</view>
<view class="flex-1 flex-row oh" :style="'width:' + windowWidth + 'px;'" data-type="scroll" @tap.stop @touchstart.prevent="handle_comment_touch_start" @touchmove.prevent="handle_comment_touch_move" @touchend="handle_comment_touch_end">
<!-- 评论内容区域 -->
<!-- <scroll-view class="flex-1 comment-list flex-row" scroll-y :scroll-top="comment_scroll_top" show-scrollbar="false" scroll-with-animation :scroll-with-touch="!is_dragging" @scrolltolower="handle_comment_to_lower_scroll" @scroll="handle_comment_scroll"> -->
<list class="comment-list comment-scroll" :show-scrollbar="false" :scrollable="!is_dragging" loadmoreoffset="100" @scroll="handle_comment_scroll" @loadmore="handle_comment_to_lower_scroll">
<template v-if="active_comments && active_comments.length > 0">
<cell v-for="(comment_item, index) in active_comments" :key="comment_item.id">
<view class="comment-item flex-col">
<commentInfoComponent :style="window_more_style" :propComment="comment_item" :propId="comment_item.id" :propDropDownVisible="active_dropdown_id == comment_item.id" @comment_reply="comment_reply" @comment_like="comment_like" @toggle_dropdown="handle_toggle_dropdown" @dropdown_item_click="handle_dropdown_item_click"></commentInfoComponent>
<!-- 子评论 -->
<view class="sub-comment flex-col jc-c mt-10">
<view v-if="comment_item.sub_comments && Array.isArray(comment_item.sub_comments) && comment_item.sub_comments.length > 0 && comment_item.show_sub_comment" style="margin-buttom: 20rpx;" class="sub-comment-list flex-col jc-c">
<view v-for="(sub_comment_item, sub_comment_index) in comment_item.sub_comments" :key="sub_comment_index" class="sub-comment-item flex-row align-s" style="margin-bottom: 20rpx;">
<commentInfoComponent :style="window_sub_more_style" :propComment="sub_comment_item" :propId="sub_comment_item.id" :propDropDownVisible="active_dropdown_id == sub_comment_item.id" @comment_reply="comment_reply" @comment_like="comment_like" @toggle_dropdown="handle_toggle_dropdown" @dropdown_item_click="handle_dropdown_item_click"></commentInfoComponent>
</view>
</view>
<template v-if="comment_item.comments_count > 0">
<template v-if="!comment_item.show_sub_comment">
<commentMoreComponent :style="window_more_style" :propId="comment_item.id" :propIsLevel="1" :propText="'—— '+ $t('common.expand') + (comment_item.comments_count ? comment_item.comments_count || 0 : 0) + $t('ask-comments.ask-comments.ymmd24')" @comment_more_event="open_sub_comment"></commentMoreComponent>
</template>
<template v-else>
<template v-if="comment_item.show_sub_comment_loading">
<loading-component :style="window_more_style"></loading-component>
</template>
<view v-else class="sub-comment-more flex-row align-c gap-10">
<template v-if="comment_item.page != null && comment_item.page < comment_item.page_total">
<commentMoreComponent :style="window_more_style" :propId="comment_item.id" :propIsLevel="2" :propText="$t('common.expand')" @comment_more_event="open_sub_comment"></commentMoreComponent>
</template>
<commentMoreComponent :style="window_more_style" :propId="comment_item.id" :propText="$t('common.retract')" propIconName="arrow-top" @comment_more_event="close_sub_comment"></commentMoreComponent>
</view>
</template>
</template>
</view>
</view>
</cell>
<template v-if="comment_item_loading">
<cell>
<view class="flex-row align-c jc-c" :style="window_more_style">
<loading-component></loading-component>
</view>
</cell>
</template>
<template v-else>
<cell>
<!-- 结尾 -->
<component-bottom-line :propStatus="goods_bottom_line_status"></component-bottom-line>
</cell>
</template>
</template>
<template v-else>
<cell>
<component-no-data :propMsg="$t('common.no_data')"></component-no-data>
</cell>
</template>
<!-- </view> -->
</list>
</view>
<view v-if="base_config_data && base_config_data.is_video_comments_add && base_config_data.is_video_comments_add == 1" class="comment-input-container">
<view class="comment-input-content flex-col jc-c">
<view v-if="!isEmpty(comments_reply_data)" class="comment-reply-content flex-row align-c jc-sb gap-10">
<text class="size-12 cr-f text-line-1">@{{ isEmpty(comments_reply_data.user) ? '' : comments_reply_data.user.user_name_view }}:{{ comments_reply_data.content }}</text>
<view data-type="image" @tap="comment_data_delete">
<u-icon propName="close-line" propSize="24rpx" propColor="#fff"></u-icon>
</view>
</view>
<view class="flex-row align-c wh-auto ht-auto pr-16 box-border-box" :style="window_more_style">
<input :value="comment_input_value" class="comment-input" style="margin-right: 20rpx;" type="text" confirm-type="send" :adjust-position="false" :placeholder="$t('video-detail.video-detail.98yyuf')" @focus="add_comment" @input="comment_input_event" @confirm="send_comment" />
<component-upload :propMaxNum="1" :propPathType="editor_path_type" propSlot propSingleCall propIsAllInfo @call-back="upload_images_event">
<u-icon propName="layout-module-single-images" propSize="32rpx" propColor="#999"></u-icon>
</component-upload>
</view>
<view v-if="form_images_list && form_images_list.length > 0" class="pr w h comment-input-img-container">
<view v-for="(item, index) in form_images_list" :key="index" class="comment-input-img pr">
<u-icon propName="close" propSize="10" propColor="#000" class="comment-input-img-close" :data-index="index" @tap="comment_input_img_close"></u-icon>
<image :src="item.url" :data-index="index" @tap="upload_show_event" mode="aspectFill" class="wh-auto ht-auto"></image>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 举报弹窗 -->
<u-popup ref="popupReportRef" propMode="bottom" propCloseType="text" :propWidth="'width:' + windowWidth + 'px;'" propTitleBorder class="pointer-events-auto" :propTitle="$t('video-detail.video-detail.rfsdfg')" :propCloseable="true" @close="popup_report_close_event" @callBack="submit_report">
<view class="report-content">
<!-- 主要内容区域 -->
<view class="report-body">
<!-- 第一层:举报原因选择 -->
<view v-if="report_type_list && report_type_list.length > 0" class="report-section">
<view class="report-label flex-row align-c">{{$t('video-detail.video-detail.rfsdfg')}}<text class="ml-10 report-required">*</text></view>
<view class="flex-row align-c flex-wrap">
<radio-group class="flex-row align-c flex-wrap" :style="window_more_style">
<view v-for="(mainItem, main_index) in report_type_list" :key="main_index" class="flex-row align-c" style="margin-right: 20rpx;" :data-index="main_index" @tap.stop="select_main_reason">
<view class="flex-row align-c">
<radio :value="main_index.toString()" :checked="current_main_index === main_index" style="transform:scale(0.7)" />
<text class="flex-row align-c">{{mainItem.name}}</text>
</view>
</view>
</radio-group>
</view>
</view>
<!-- 第二层:具体类型选择(当有主类别选中时显示) -->
<view class="report-section mt-20" v-if="current_main_index >= 0 && report_type_list[current_main_index] && report_type_list[current_main_index].data">
<view class="report-label flex-row align-c">{{$t('video-detail.video-detail.fsdf33')}}<text class="ml-10 report-required">*</text></view>
<view class="flex-row align-c flex-wrap">
<radio-group class="flex-row align-c flex-wrap" :style="window_more_style">
<view v-for="(subItem, sub_index) in report_type_list[current_main_index].data" :key="sub_index" class="flex-row align-c" style="margin-right: 20rpx;" :data-index="sub_index" @tap.stop="select_sub_reason">
<view class="flex-row align-c">
<radio :value="sub_index.toString()" :checked="current_sub_index === sub_index" style="transform:scale(0.7)" />
<view class="flex-row align-c">{{subItem}}</view>
</view>
</view>
</radio-group>
</view>
</view>
</view>
</view>
</u-popup>
<!-- 添加评论弹出框 -->
<view v-if="is_add_comment" class="keyboard-input br-top-shadow" :style="'width:'+ windowWidth +'px;bottom:' + listener_height + 'px;'">
<view class="comment-input-content flex-col jc-c">
<view v-if="!isEmpty(comments_reply_data)" class="comment-reply-content flex-row align-c jc-sb gap-10">
<text class="size-12 cr-f text-line-1">@{{ comments_reply_data.user.user_name_view }}:{{ comments_reply_data.content }}</text>
<view data-type="image" @tap="comment_data_delete">
<u-icon propName="close-line" propSize="24rpx" propColor="#fff"></u-icon>
</view>
</view>
<view class="flex-row align-c wh-auto ht-auto pr-16 box-border-box" :style="window_more_style">
<input :value="comment_input_value" :focus="is_add_comment" class="comment-input" style="margin-right: 20rpx;" type="text" confirm-type="done" :adjust-position="false" :auto-blur="true" :placeholder="input_placeholder" @input="comment_input_event" @blur="() => is_add_comment = false" @confirm="send_comment" />
<component-upload :propMaxNum="1" :propPathType="editor_path_type" propSlot propSingleCall propIsAllInfo @call-back="upload_images_event">
<u-icon propName="layout-module-single-images" propSize="48rpx" propColor="#999"></u-icon>
</component-upload>
</view>
<view v-if="form_images_list && form_images_list.length > 0" class="pr w h comment-input-img-container">
<view v-for="(item, index) in form_images_list" :key="index" class="comment-input-img pr">
<u-icon propName="close" propSize="10" propColor="#000" class="comment-input-img-close" :data-index="index" @tap="comment_input_img_close"></u-icon>
<image :src="item.url" :data-index="index" @tap="upload_show_event" mode="aspectFill" class="wh-auto ht-auto"></image>
</view>
</view>
</view>
</view>
<!-- 分享弹窗 -->
<u-share-popup ref="share" class="pointer-events-auto"></u-share-popup>
<!-- 公共 -->
<!-- <component-common ref="common"></component-common> -->
</view>
</template>
<script>
const app = getApp();
import { get_math, isEmpty, video_get_top_left_padding, showToast } from '@/common/js/common/common.js';
import commentInfoComponent from '@/pages/plugins/video/components/comment-info.vue';
import loadingComponent from '@/pages/plugins/video/components/loading.vue';
import commentMoreComponent from '@/pages/plugins/video/components/comment-more.vue';
import searchComponent from '@/pages/plugins/video/components/search.vue';
import componentNoData from '@/components/no-data/no-data';
import componentBottomLine from '@/components/bottom-line/bottom-line';
import componentPopup from '@/components/popup/popup';
import componentUpload from '@/components/upload/upload';
import componentCommon from '@/components/common/common';
// 多语言
//#ifdef APP-NVUE
import i18n from '@/locale/index.js';
// nvue页面在方法中使用时的处理
import { initVueI18n } from '@dcloudio/uni-i18n';
import indexNvue from '@/locale/index-nvue.js';
const { t } = initVueI18n(indexNvue)
//#endif
// 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO || H5
bar_height = 0;
// #endif
export default {
//#ifdef APP-NVUE
i18n,
//#endif
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
top_content_style: 'padding-top:' + bar_height + 'px;padding-bottom:10px;',
data_list_loding_status: 1,
data_list_loding_msg: '',
video_data_list: [],
display_video_list: [],
current_index: 0,
video_contexts: [], // 原生的video视频
create_video_contexts: [], // 使用uni.createVideoContext创建的视频上下文
paused: false,
current_video_progress: 0,
current_video_duration: 1,
is_seeking: false,
show_comment_modal: false,
active_comments: {},
comments_page: 1, // 当前评论页
comments_page_total: 5, // 评论总页数
goods_bottom_line_status: false, //评论页是否显示底部线
comment_item_loading: false,
comment_start_y: 0, // 评论开始拖拽位置
comment_current_y: 0, // 评论当前拖拽位置
move_distance: 0, // 评论拖拽距离
is_dragging: false, // 是否正在拖拽中
current_video_id: '', // 当前播放视频的 ID
is_slide_start: false,
swiper_key: get_math(),
comment_scroll_top: 0, // 评论滚动距离顶部的距离
comment_input_value: '',
propMaxNum: 1,
form_images_list: [],
share_info: {},
menu_button_info: '',
direction: 'direction',
base_config_data: {},
video_switch_debounce_timer: null, // 视频切换防抖定时器
video_cleanup_timer: null, // 视频清理定时器
comment_scroll_debounce_timer: null, // 评论滚动防抖定时器
comment_move_throttle_timer: null, // 评论拖拽节流定时器
// 添加下拉菜单状态管理
active_dropdown_id: null, // 当前显示下拉菜单的评论ID
params: {},
header_padding_left: '',
report_type_list: [], // 举报类型列表
popup_report_status: false, // 举报弹窗状态
current_main_index: 0, // 默认选中第一个举报原因
current_sub_index: 0, // 默认选中第一个具体类型
report_comment_id: '', // 举报的评论id
comment_value: '',
is_add_comment: false,
// 监听键盘高度变化事件
listener_height: 0,
comments_reply_data: {},
editor_path_type: 'video',
is_manual_pause: false, // 是否手动暂停
windowWidth: 0,
windowHeight: 0,
padding_width: 0,
sub_comment_padding_left: 0,
// 视频滚动
swiper_start_y: 0,
swiper_current_y: 0,
swiper_move_distance: 0,
swiper_move_throttle_timer: null,
};
},
components: {
commentInfoComponent,
commentMoreComponent,
searchComponent,
componentNoData,
componentBottomLine,
componentPopup,
loadingComponent,
componentUpload,
componentCommon
},
computed: {
// 视频列表高度
swiperStyle() {
return this.show_comment_modal ? (this.move_distance > 0 ? `height: ${Math.round(this.windowHeight * 0.3) + this.move_distance}px;` : `height: ${Math.round(this.windowHeight * 0.3)}px;`) : `height: ${this.windowHeight}px;`;
},
// 评论内容区域高度
commentContentStyle() {
const baseHeight = Math.round(this.windowHeight * 0.7);
return this.show_comment_modal && this.move_distance > 0
? `transform: translateY(3px); width:${this.windowWidth}px; height: ${baseHeight - 10 - this.move_distance}px;`
: `transform: translateY(0); width:${this.windowWidth}px; height: ${baseHeight + 10}px;`;
},
// 当前播放视频的索引
current_video_index() {
if (this.video_data_list && this.video_data_list.length > 0) {
return this.video_data_list.findIndex(item => item.id == this.current_video_id);
} else {
return -1;
}
},
close_circular() {
try {
if (this.video_data_list && this.video_data_list.length > 0) {
return this.video_data_list[0].id == this.current_video_id || this.video_data_list[this.video_data_list.length - 1].id == this.current_video_id;
} else {
return true
}
} catch {
console.log('close_circular');
}
},
width_height_style() {
return `width: ${ this.windowWidth }px;height: ${ this.windowHeight }px;`;
},
window_more_style() {
return `width: ${ this.windowWidth - this.padding_width }px;`
},
window_sub_more_style() {
return `width: ${ this.windowWidth - this.sub_comment_padding_left }px;`
}
},
onLoad(params) {
try {
// 调用公共事件方法
app.globalData.page_event_onload_handle(params);
// 设置参数
this.params = app.globalData.launch_params_handle(params);
} catch {
console.log('close_circular');
}
},
onShow() {
try {
const data = uni.getWindowInfo();
this.windowWidth = data.windowWidth > 800 ? 800 : data.windowWidth;
this.windowHeight = data.windowHeight;
// 调用公共事件方法
app.globalData.page_event_onshow_handle();
// 视频播放
if (!this.is_manual_pause && this.create_video_contexts && this.create_video_contexts[this.current_index]) {
this.video_play_event(this.create_video_contexts[this.current_index]);
}
// 公共onshow事件
if ((this.$refs.common || null) != null) {
this.$refs.common.on_show();
}
// 分享菜单处理
app.globalData.page_share_handle();
} catch {
console.log('close_circular');
}
},
onHide() {
// 清理定时器
if (this.video_switch_debounce_timer) {
clearTimeout(this.video_switch_debounce_timer);
}
if (this.video_cleanup_timer) {
clearTimeout(this.video_cleanup_timer);
}
// 清理所有视频资源
this.cleanup_all_videos();
},
mounted() {
try {
// 初始化
this.init();
// #ifdef H5
// 添加全局点击事件监听
document.addEventListener('click', this.handle_global_click);
// 添加触摸事件监听(移动端兼容)
document.addEventListener('touchstart', this.handle_global_click);
//#endif
// 创建监听事件
this.bind_keyboard_listener();
} catch {
console.log('close_circular');
}
},
beforeDestroy() {
// 清理定时器
if (this.video_switch_debounce_timer) {
clearTimeout(this.video_switch_debounce_timer);
}
if (this.video_cleanup_timer) {
clearTimeout(this.video_cleanup_timer);
}
if (this.comment_scroll_debounce_timer) {
clearTimeout(this.comment_scroll_debounce_timer);
}
if (this.comment_move_throttle_timer) {
clearTimeout(this.comment_move_throttle_timer);
}
if (this.video_move_throttle_timer) {
clearTimeout(this.video_move_throttle_timer);
}
// 清理所有视频资源
this.cleanup_all_videos();
// 移除键盘事件监听器
// #ifdef H5
if (typeof document !== 'undefined') {
document.removeEventListener('keydown', this.handle_keydown);
}
// 移除全局事件监听器
document.removeEventListener('click', this.handle_global_click);
document.removeEventListener('touchstart', this.handle_global_click);
// #endif
this.unbind_keyboard_listener();
},
methods: {
isEmpty,
init() {
try {
// 小程序下,获取小程序胶囊的宽度
let menu_button_info = `max-width:${ this.windowWidth }px;`;
// #ifndef MP-TOUTIAO
// #ifdef MP
// 判断是否有胶囊
const is_current_single_page = app.globalData.is_current_single_page();
// 如果有胶囊的时候,做处理
if (is_current_single_page == 0) {
const custom = uni.getMenuButtonBoundingClientRect();
menu_button_info = `max-width:calc(${ this.windowWidth } - ${custom.width + 10}px);`;
}
// #endif
// #endif
// 视频详情页,需要添加 padding-left
let padding_left = '';
// #ifdef MP-ALIPAY
padding_left = video_get_top_left_padding();
// #endif
this.padding_width = app.globalData.rpx_to_px(30);
this.sub_comment_padding_left = app.globalData.rpx_to_px(80);
this.header_padding_left = padding_left;
this.menu_button_info = menu_button_info;
this.current_video_id = isEmpty(this.current_video_id) ? this.params.id : this.current_video_id;
this.get_video_detail(this.current_video_id);
} catch (error) {
console.error('init error:', error);
}
},
/*
* 获取视频详情
* @param {*} id 视频 id
*/
get_video_detail(id) {
try {
// 获取数据
uni.request({
url: app.globalData.get_request_url("detail", "index", "video"),
method: 'POST',
data: {
id: id
},
dataType: 'json',
success: res => {
try {
const data = res.data;
if (data.code == 0) {
const new_data = data.data;
// 数据更新
this.data_list_loding_status = 3;
this.video_data_list = [new_data.data];
this.report_type_list = new_data.report_type_list;
this.base_config_data = new_data.base_config_data;
this.editor_path_type = new_data.editor_path_type;
this.get_last_or_next_data_list(this.params.id, 1, 1);
} else {
this.data_list_loding_status = 0;
this.data_tabs_loding_msg = data.msg;
}
} catch {
console.error('get_video_detail error:', error);
}
},
fail: (err) => {
this.data_list_loding_status = 2;
this.data_list_loding_msg = t('common_internet_error_tips');
}
});
} catch (error) {
console.error('get_video_detail error:', error);
}
},
/*
* 获取视频列表
* @param {*} id 视频 id
* @param {*} is_last 是否获取上一批数据
* @param {*} is_next 是否获取下一批数据
*/
get_last_or_next_data_list(id, is_last = 0, is_next = 0) {
try {
// 获取数据
uni.request({
url: app.globalData.get_request_url("lastnextdata", "index", "video"),
method: 'POST',
data: {
id: id,
is_last: is_last,
is_next: is_next,
},
dataType: 'json',
success: res => {
const data = res.data;
if (data.code == 0) {
const new_data = data.data;
// 第一次的数据
// let data_list = JSON.parse(JSON.stringify(this.video_data_list));
// 创建现有数据的 ID 映射表,用于快速去重
const existing_ids = new Map();
this.video_data_list.forEach(item => {
existing_ids.set(item.id, true);
});
if (is_last == 1 && is_next == 1) {
// 上一批数据 - 去重处理
if (new_data.last && new_data.last.length > 0) {
const unique_last = new_data.last.filter(item => !existing_ids.has(item.id));
if (unique_last.length > 0) {
this.video_data_list.unshift(...unique_last);
// 更新 ID 映射表
unique_last.forEach(item => existing_ids.set(item.id, true));
}
}
// 下一批数据 - 去重处理
if (new_data.next && new_data.next.length > 0) {
const unique_next = new_data.next.filter(item => !existing_ids.has(item.id));
if (unique_next.length > 0) {
this.video_data_list.push(...unique_next);
}
}
} else if (is_last == 1 && new_data.last && new_data.last.length > 0) { // 上一页数据 - 去重处理
const unique_last = new_data.last.filter(item => !existing_ids.has(item.id));
if (unique_last.length > 0) {
this.video_data_list.unshift(...unique_last);
}
} else if (is_next == 1 && new_data.next && new_data.next.length > 0) { // 下一页数据 - 去重处理
const unique_next = new_data.next.filter(item => !existing_ids.has(item.id));
if (unique_next.length > 0) {
this.video_data_list.push(...unique_next);
}
}
// 更新当前视频商品信息
const new_index = this.video_data_list.findIndex(item => item.id == this.params.id);
// 处理当前视频商品信息
this.video_data_list.forEach((item) => {
if (isEmpty(item.show_goods)) {
if (this.base_config_data && this.base_config_data.is_video_detail_show_goods_modal && this.base_config_data.is_video_detail_show_goods_modal == 1) {
item.show_goods = true;
} else {
item.show_goods = false;
}
}
});
// 更新所有视频信息
// this.video_data_list = this.video_data_list;
this.$set(this, 'video_data_list', this.video_data_list);
// 逻辑说明:当是最后一个视频且需要播放下一个时,根据数组长度和新索引计算新的当前索引
// - 数组长度 > 2 时:新索引是最后一个元素则返回 2是倒数第二个则返回 1否则返回 0
// - 数组长度 <= 2 时:返回 length - 1
this.current_index = is_last == 1 && is_next == 1 ? this.calculate_new_index(this.video_data_list.length, new_index) : this.current_index;
if (is_last == 1 && is_next == 1) {
setTimeout(() => {
// 更新显示数据数据信息
this.update_display_data();
setTimeout(() => {
if (this.display_video_list && this.display_video_list.length > 0) {
// 更新分享信息
this.update_share_info(this.display_video_list[this.current_index]);
console.log(this.display_video_list.length);
this.display_video_list.forEach((item, index) => {
// this.create_video_contexts[index] = uni.createVideoContext(`video_${index}`, this);
this.$set(this.create_video_contexts, index, uni.createVideoContext(`video_${index}`, this));
//#ifdef H5
if (document.getElementById(`video_${index}`) != null && this.video_contexts) {
// this.video_contexts[index] = document.getElementById(`video_${index}`).querySelector('video');
this.$set(this.video_contexts, index, document.getElementById(`video_${index}`).querySelector('video'));
}
//#endif
});
}
//#ifdef H5
if (this.video_contexts && this.video_contexts[this.current_index]) { // 当前播放的视频索引为 0
this.video_play_event(this.video_contexts[this.current_index], true);
}
//#endif
//#ifndef H5
if (this.create_video_contexts && this.create_video_contexts[this.current_index]) { // 当前播放的视频索引为 0
this.video_play_event(this.create_video_contexts[this.current_index], true);
}
//#endif
}, 200);
}, 100);
}
}
}
});
} catch (error) {
console.error('get_last_or_next_data_list error:', error);
}
},
/**
* 计算新的视频索引
* 当是最后一个视频且需要播放下一个时,根据数组长度和新索引计算新的当前索引
* @param {number} listLength - 视频列表长度
* @param {number} newIndex - 新视频在列表中的索引
* @returns {number} 计算后的新索引
*/
calculate_new_index(listLength, newIndex) {
try {
// 边界处理:空数组
if (listLength === 0) {
return 0;
}
// 数组长度 <= 2 时,返回最后一个索引
if (listLength <= 2) {
return listLength - 1;
}
// 数组长度 > 2 时,根据新索引位置返回对应值
// 新索引是最后一个元素 -> 返回 2
if (newIndex === listLength - 1) {
return 2;
}
// 新索引是倒数第二个元素 -> 返回 1
if (newIndex === listLength - 2) {
return 1;
}
// 其他情况 -> 返回 0
return 0;
} catch (error) {
console.error('calculate_new_index error:', error);
}
},
// 视频拖拽开始
handle_swiper_touch_start(e) {
try {
// 如果是滚动区域内滚动到顶部才可以拖拽,如果是头部拖拽的话,一直都可以
this.swiper_start_y = e?.touches[0]?.pageY || 0;
this.swiper_current_y = this.swiper_start_y;
this.swiper_move_distance = 0;
} catch (error) {
console.error('handle_swiper_touch_start error:', error);
}
},
// 视频拖拽中
handle_swiper_touch_move(e) {
try {
// 阻止默认行为,防止页面滚动干扰
if (e.preventDefault) {
e.preventDefault();
}
const current_y = e?.touches[0]?.pageY || 0;
const distance = current_y - this.swiper_start_y;
// 只有向下移动且距离超过阈值10px才开始拖拽避免误触和抖动
if (Math.abs(distance) > 10) {
this.swiper_current_y = current_y;
// 使用节流控制 move_distance 的更新频率,避免计算属性频繁触发导致抖动
if (this.swiper_move_throttle_timer) {
return;
}
this.move_distance = distance;
// 设置节流定时器16ms 约等于 60fps保证流畅度同时避免过度更新
this.swiper_move_throttle_timer = setTimeout(() => {
this.swiper_move_throttle_timer = null;
}, 80);
}
} catch(error) {
console.error('handle_swiper_touch_move error:', error);
}
},
// 视频拖拽结束
handle_swiper_touch_end(e) {
try {
const move_distance = this.swiper_current_y - this.swiper_start_y;
// 判断滑动方向:向下为正,向上为负
if (Math.abs(move_distance) > (this.windowHeight * 0.3)) {
// 只有滑动距离超过屏幕高度的 15% 才触发切换
if (move_distance > 0) {
// 向下滑动,切换到上一个视频
this.handle_swiper_change_by_direction('prev');
} else {
// 向上滑动,切换到下一个视频
this.handle_swiper_change_by_direction('next');
}
} else {
// 滑动距离不够,重置位置
this.swiper_move_distance = 0;
}
// 清理节流定时器
if (this.swiper_move_throttle_timer) {
clearTimeout(this.swiper_move_throttle_timer);
this.swiper_move_throttle_timer = null;
}
// 重置拖拽状态
this.swiper_start_y = 0;
this.swiper_current_y = 0;
} catch (error) {
console.error('handle_swiper_touch_end error:', error);
}
},
// 根据滑动方向切换视频
handle_swiper_change_by_direction(direction) {
try {
let new_index = this.current_index;
if (direction === 'prev') {
// 向下滑动,切换到上一个
if (this.current_video_index <= 0) {
app.globalData.showToast('已经是第一个视频了');
return;
} else {
new_index = this.current_index - 1 < 0 ? 2 : (this.current_index - 1);
}
} else if (direction === 'next') {
// 向上滑动,切换到下一个
if (this.current_video_index >= this.video_data_list.length - 1) {
app.globalData.showToast('已经是最后一个视频了');
return;
} else {
console.log('new_index', this.current_index);
new_index = this.current_index + 1 > 2 ? 0 : (this.current_index + 1);
}
}
console.log('new_index', new_index);
// 创建模拟的 event 对象
const mockEvent = {
detail: {
current: new_index
}
};
// 调用原有的处理逻辑
this.handle_swiper_change(mockEvent);
} catch (error) {
console.error('handle_swiper_change_by_direction error:', error);
}
},
// 视频滚动处理逻辑(带防抖)
handle_swiper_change(event) {
try {
const { current } = event.detail;
// 防抖处理,避免快速切换时的重复操作
if (this.video_switch_debounce_timer) {
clearTimeout(this.video_switch_debounce_timer);
}
this.video_switch_debounce_timer = setTimeout(() => {
this.process_swiper_change(current);
}, 100); // 100ms 防抖延迟
} catch (error) {
console.error('handle_swiper_change error:', error);
}
},
// 实际的 swiper 切换处理逻辑
process_swiper_change(current) {
try {
// 先暂停所有视频,确保不会有后台播放
this.pause_all_videos_except(current);
const id = this?.display_video_list[current]?.id || '';
// 更新状态
this.current_index = current;
this.paused = false;
this.is_manual_pause = false;
this.current_video_progress = 0;
this.current_video_duration = 1;
this.is_seeking = false;
this.current_video_id = id; // 更新当前播放视频的ID
//#ifdef H5
// 使用URLSearchParams处理当前查询参数
const url = new URL(location.href);
url.searchParams.set('id', id);
// 替换URL路径保持查询参数不变
const pathname = location.href?.split('?')[0] || '';
history.replaceState(null, '', pathname + url.search);
//#endif
const index = this.video_data_list.findIndex(item => item.id == id);
// 数据预加载逻辑
if (index < 2 && this.direction == 'prev') {
this.get_last_or_next_data_list(this.video_data_list[0].id, 1, 0);
} else if (index < this.video_data_list.length - 3 && this.direction == 'next') {
this.get_last_or_next_data_list(this.video_data_list[this.video_data_list.length - 1].id, 0, 1);
}
// 获取视频详细信息
this.get_video_data_detail(id);
// 边界处理逻辑
if (this.current_video_index == 0 && this.is_slide_start) {
const list = this.update_video_list([0, 1, 2]);
this.is_slide_start = false;
this.current_index = 0;
// this.swiper_key = get_math();
// 刷新数据
this.update_display_video_list(list);
} else if (this.current_video_index == this.video_data_list.length - 1) {
const list = this.update_video_list([-2, -1, 0]);
this.current_index = list.length - 1;
// this.swiper_key = get_math();
// 刷新数据
this.update_display_video_list(list);
} else {
this.is_slide_start = true;
// this.swiper_key = get_math();
this.update_display_data();
}
// 更新分享信息
this.update_share_info(this.display_video_list[current]);
this.swiper_key = get_math();
// 延迟播放当前视频确保DOM更新完成
setTimeout(() => {
this.play_current_video_safely(this.current_index);
}, 150);
} catch (error) {
console.error('process_swiper_change error:', error);
}
},
update_video_list(offsets) {
try {
let list = [];
for (let i = 0; i < offsets.length; i++) {
const targetIndex = this.current_video_index + offsets[i];
if (targetIndex >= 0 && targetIndex < this.video_data_list.length) {
list.push(this.get_video_by_index(targetIndex));
}
}
return list;
} catch (error) {
console.error('update_video_list error:', error);
}
},
// 批量暂停除指定索引外的所有视频
pause_all_videos_except(exceptIndex) {
try {
if (this.create_video_contexts && this.create_video_contexts.length > 0) {
// 暂停 uni.createVideoContext 创建的视频
this.create_video_contexts.forEach((context, index) => {
if (index !== exceptIndex && context) {
try {
context.pause();
} catch (error) {
console.warn(`暂停视频 ${index} 失败:`, error);
}
}
});
}
} catch (error) {
console.error('pause_all_videos_except error:', error);
}
},
// 安全播放当前视频
play_current_video_safely(index) {
try {
// 优先使用 uni.createVideoContext
if (this.create_video_contexts && this.create_video_contexts[index]) {
this.video_play_event(this.create_video_contexts[index]);
return;
}
} catch (error) {
console.error('play_current_video_safely error:', error);
}
},
// 切换播放暂停
toggle_play_pause(e) {
try {
const currentIndex = this.current_index;
// 检查视频上下文是否存在
const videoContext = this.create_video_contexts[currentIndex] || this.video_contexts[currentIndex];
if (!videoContext) {
console.warn(`当前索引 ${currentIndex} 无可用视频上下文`);
e.stopPropagation();
return;
}
this.paused = !this.paused;
this.is_manual_pause = !this.paused;
if (this.paused) {
// 暂停当前视频
try {
videoContext.pause();
} catch (error) {
e.stopPropagation();
console.warn('暂停视频失败:', error);
}
} else {
// 播放当前视频
this.video_play_event(videoContext);
}
e.stopPropagation();
} catch (error) {
console.error('toggle_play_pause error:', error);
}
},
// 播放数组更新
update_display_video_list(list) {
try {
// 不理解为什么这里时undefined的
// 根据 weex 的通用做法,需要同时改变“引用”和“长度”两个维度才能被识别数组更新。
// 1. 先清空原数组(触发长度变化)
if (this.display_video_list && this.display_video_list.length > 0) {
this.display_video_list.splice(0, this.display_video_list.length)
} else {
this.display_video_list = [];
}
// this.display_video_list.splice(0, this.display_video_list.length, ...list);
// this.$nextTick(() => {
// setTimeout(() => {
// 2. 再把新数据 push 进去(触发内容变化)
list.forEach(item => this.display_video_list.push(item))
// }, 0);
// });
} catch (e) {
console.log('update_display_video_list', e);
}
},
// 更新分享信息
update_share_info(data) {
try {
const info = {
title: data?.title || '',
desc: data?.desc || '',
path: '/pages/plugins/video/detail/detail',
query: 'id=' + this.current_video_id,
img: data.cover || ''
}
this.share_info = info;
// 分享菜单处理
app.globalData.page_share_handle(info);
// 更新页面标题
uni.setNavigationBarTitle({title: data.title});
} catch (error) {
console.error('update_share_info error:', error);
}
},
// 安全的视频播放事件处理
video_play_event(videoContext, is_first_play = false) {
try {
if (!videoContext) {
this.paused = true;
this.is_manual_pause = false;
return;
}
try {
if (is_first_play) {
//#ifdef H5
videoContext.play().catch((error) => {
this.paused = true;
this.is_manual_pause = false;
});
//#endif
//#ifndef H5
videoContext.play();
//#endif
} else {
videoContext.play();
}
} catch (error) {
console.error('视频播放异常:', error);
this.paused = true;
this.is_manual_pause = false;
}
} catch (error) {
console.error('video_play_event error:', error);
}
},
// 安全获取视频数据的方法,处理索引超限情况
get_video_by_index(index) {
try {
// 处理负数索引
if (index < 0) {
// 循环到数组末尾
const actualIndex = this.video_data_list.length + (index % this.video_data_list.length);
return this.video_data_list[actualIndex] || {};
}
// 处理超出数组长度的索引
if (index >= this.video_data_list.length) {
// 循环到数组开头
const actualIndex = index % this.video_data_list.length;
return this.video_data_list[actualIndex] || {};
}
// 正常情况直接返回
return this.video_data_list[index];
} catch (error) {
console.error('get_video_by_index error:', error);
}
},
/*
* 更新显示的视频数据
*/
update_display_data() {
try {
let list = [];
// 如果当前索引为0只显示当前元素和下一个元素
if (this.current_index == 0) {
if (this.current_video_index == 0) {
list = this.update_video_list([0, 1, 2]);
} else {
list = this.update_video_list([0, 1, -1]);
}
} else if (this.current_index == 1) { // 索引为1时为确保无限轮播正常需要改变数据插入顺序
list = this.update_video_list([-1, 0, 1]);
} else {
if (this.current_video_index == this.video_data_list.length - 1) {
list = this.update_video_list([-2, -1, 0]);
} else {
list = this.update_video_list([1, -1, 0]);
}
}
// 刷新数据
this.update_display_video_list(list);
} catch (error) {
console.error('update_display_data error:', error);
}
},
// 评论输入框事件
comment_input_event(e) {
try {
this.comment_input_value = e.detail.value;
} catch (error) {
console.error('comment_input_event error:', error);
}
},
// 图片上传回调
upload_images_event(res) {
try {
if((res || null) != null) {
// 存储上传图片内容
this.form_images_list.push({
url: res.url,
name: res.name,
size: res.size,
});
}
} catch (error) {
console.error('upload_images_event error:', error);
}
},
// 上传图片预览
upload_show_event(e) {
try {
const index = e?.currentTarget?.dataset?.index || 0;
uni.previewImage({
current: this?.form_images_list[index]?.url || '',
urls: this.form_images_list.map(item => item.url),
});
} catch (error) {
console.error('upload_show_event error:', error);
}
},
// 评论输入图片删除
comment_input_img_close(e) {
try {
const index = e?.currentTarget?.dataset?.index || 0;
var list = this.form_images_list;
list.splice(index, 1);
// 图片赋值
this.form_images_list = list;
} catch (error) {
console.error('comment_input_img_close error:', error);
}
},
// swiper-item 的位置发生改变时
on_transition(e) {
try {
const dy = e.detail.dy;
let status = 'direction';
if (dy > 0) {
status = 'next';
} else if (dy < 0) {
status = 'prev';
}
// 如果历史的是向下滑动,这次也是向下滑动,就不更新数据
if (this.direction != status) {
this.direction = status;
}
} catch (error) {
console.error('on_transition error:', error);
}
},
// 播放
handle_play() {
try {
this.paused = false;
this.is_manual_pause = false;
} catch (error) {
console.error('handle_play error:', error);
}
},
// 收藏
handle_like(e) {
try {
if (!app.globalData.is_single_page_check()) {
return false;
}
var user = app.globalData.get_user_info(this, 'handle_like', e);
if (user != false) {
// const id = e?.currentTarget?.dataset?.id || '';
this.set_givethumbs_num(this.current_video_id);
}
} catch (error) {
console.error('handle_like error:', error);
}
},
// 打开评论区
handle_comment(e) {
try {
e.stopPropagation();
// const id = this.current_video_id;
const old_data = this.video_data_list.find(item => item.id == this.current_video_id);
if (old_data && old_data.comments_list) {
// 初始化评论数据
const new_data = old_data.comments_list.map(item1 => ({
...item1,
show_sub_comment: false,
show_sub_comment_loading: false,
page: 0,
sub_comments: [],
}));
this.active_comments = new_data;
this.comments_page = 1;
this.comments_page_total = 5;
this.comment_item_loading = false;
this.show_comment_modal = true;
this.move_distance = 0;
this.is_dragging = false; // 重置拖拽状态
this.comment_start_y = 0; // 重置起始位置
this.comment_current_y = 0; // 重置当前位置
this.comment_scroll_top = 0 + Math.random(); // 滚动到最顶部
}
} catch (error) {
console.error('handle_comment error:', error);
}
},
// 关闭评论区
close_comment_modal(e) {
try {
this.active_dropdown_id = null;
this.show_comment_modal = false;
this.comment_scroll_top = 0 + Math.random(); // 关闭评论时滚动到最顶部
this.comments_reply_data = {}; // 清空回复评论数据
this.form_images_list = []; // 清空上传图片
this.comment_input_value = ''; // 清空输入框内容
this.move_distance = 0;
this.is_dragging = false; // 重置拖拽状态
this.comment_start_y = 0; // 重置起始位置
this.comment_current_y = 0; // 重置当前位置
// 清理节流定时器
if (this.comment_move_throttle_timer) {
clearTimeout(this.comment_move_throttle_timer);
this.comment_move_throttle_timer = null;
}
} catch (error) {
console.error('close_comment_modal error:', error);
}
},
// 评论滚动事件,记录滚动位置(带防抖)
handle_comment_scroll(e) {
try {
// 清除之前的防抖定时器
if (this.comment_scroll_debounce_timer) {
clearTimeout(this.comment_scroll_debounce_timer);
}
// 设置新的防抖定时器
this.comment_scroll_debounce_timer = setTimeout(() => {
this.comment_scroll_top = Math.abs(e.contentOffset.y);
}, 100); // 100ms防抖延迟
} catch (error) {
console.error('handle_comment_scroll error:', error);
}
},
// 评论滚动到底部事件
handle_comment_to_lower_scroll() {
try {
if (this.goods_bottom_line_status) {
return;
}
this.comment_item_loading = true;
// 获取数据
uni.request({
url: app.globalData.get_request_url("commentsreplylist", "index", "video"),
method: 'POST',
data: {
video_id: this.current_video_id,
page: this.comments_page + 1,
video_comments_id: 0
},
dataType: 'json',
success: res => {
const data = res.data;
if (data.code == 0) {
const new_data = data.data;
if (new_data.data.length > 0) {
// 初始化评论数据
const comment_data = new_data.data.map(item1 => ({
...item1,
show_sub_comment: false,
show_sub_comment_loading: false,
page: 0,
sub_comments: [],
}));
this.active_comments.push(...comment_data);
}
// 是否显示没有更多数据
if (new_data.page >= new_data.page_total) {
// 没有更多数据了
this.goods_bottom_line_status = true;
}
this.comments_page = new_data.page;
this.comments_page_total = new_data.page_total
}
},
complete: () => {
this.comment_item_loading = false;
}
});
} catch (error) {
console.error('handle_comment_to_lower_scroll error:', error);
}
},
// 评论拖拽开始
handle_comment_touch_start(e) {
try {
const type = e?.target?.dataset?.type || 'header';
// 如果是滚动区域内滚动到顶部才可以拖拽,如果是头部拖拽的话,一直都可以
if ((this.comment_scroll_top <= 5 && type == 'scroll') || type == 'header') {
this.comment_start_y = e?.touches[0]?.pageY || 0;
this.comment_current_y = this.comment_start_y;
this.move_distance = 0;
this.is_dragging = false; // 开始时不设置为拖拽状态,需要移动一定距离后才算拖拽
}
} catch (error) {
console.error('handle_comment_touch_start error:', error);
}
},
// 评论拖拽中
handle_comment_touch_move(e) {
try {
const type = e?.target?.dataset?.type || 'header';
// 如果是滚动区域内滚动到顶部才可以拖拽,如果是头部拖拽的话,一直都可以
if ((this.comment_scroll_top <= 5 && type == 'scroll') || type == 'header') {
// 阻止默认行为,防止页面滚动干扰
if (e.preventDefault) {
e.preventDefault();
}
const current_y = e?.touches[0]?.pageY || 0;
const distance = current_y - this.comment_start_y;
// 只有向下移动且距离超过阈值30px才开始拖拽避免误触和抖动
if (distance > 10) {
this.is_dragging = true;
this.comment_current_y = current_y;
// 使用节流控制 move_distance 的更新频率,避免计算属性频繁触发导致抖动
if (this.comment_move_throttle_timer) {
return;
}
this.move_distance = distance;
// 设置节流定时器16ms 约等于 60fps保证流畅度同时避免过度更新
this.comment_move_throttle_timer = setTimeout(() => {
this.comment_move_throttle_timer = null;
}, 80);
}
}
} catch(error) {
console.error('handle_comment_touch_move error:', error);
}
},
// 评论拖拽结束
handle_comment_touch_end(e) {
try {
const type = e?.target?.dataset?.type || 'header';
// 如果是滚动区域内滚动到顶部才可以拖拽,如果是头部拖拽的话,一直都可以
if ((this.comment_scroll_top <= 5 && type == 'scroll') || type == 'header') {
const move_distance = this.comment_current_y - this.comment_start_y;
// 如果拖拽距离足够大,关闭评论弹窗
if (move_distance > 150) {
this.close_comment_modal();
} else {
this.move_distance = 0;
}
this.is_dragging = false; // 拖拽结束,重置状态
// 清理节流定时器
if (this.comment_move_throttle_timer) {
clearTimeout(this.comment_move_throttle_timer);
this.comment_move_throttle_timer= null;
}
}
} catch (error) {
console.error('handle_comment_touch_end error:', error);
}
},
// 评论
send_comment() {
try {
let comment_text = this.comment_input_value;
if (!comment_text.trim()) return;
// video_id 视频id video_comments_id 父级评论id id 当前评论id
let new_video_comments_id = 0;
let reply_comments_id = 0
if (!isEmpty(this.comments_reply_data)) {
const { video_comments_id, id } = this.comments_reply_data;
new_video_comments_id = video_comments_id == 0 ? id : video_comments_id;
reply_comments_id = video_comments_id == 0 ? 0 : id;
}
uni.request({
url: app.globalData.get_request_url("comments", "index", "video"),
method: 'POST',
data: {
video_id: this.current_video_id,
video_comments_id: new_video_comments_id, // 如果父级评论id为0说明没有父级id所以取当前id
reply_comments_id: reply_comments_id, // 如果父级评论id为0说明没有父级id所以回复id为0
content: comment_text,
images: this.form_images_list.length > 0 ? (this.form_images_list[0]?.url || '') : '',
},
dataType: 'json',
success: res => {
const data = res.data;
if (data.code == 0) {
const new_data = data.data;
// 没有回复时的评论
if (new_video_comments_id == 0) {
this.active_comments.unshift({
...new_data,
show_sub_comment: false,
show_sub_comment_loading: false,
page: 0,
sub_comments: [],
})
this.video_data_list.forEach(item => {
if (item.id == this.current_video_id) {
item.comments_count++;
}
})
// this.video_data_list = this.video_data_list;
this.$set(this, 'video_data_list', this.video_data_list)
this.comment_scroll_top = 0 + Math.random(); // 添加主评论时滚动到最顶部
} else {
this.active_comments.forEach(item => {
if (item.id == new_video_comments_id) {
item.sub_comments.unshift(new_data);
item.comments_count++;
if (!item.show_sub_comment) {
item.show_sub_comment = true;
// 如果回复总数跟当前显示的数量对得上,就不显示展开. 如果之前没有页面时设置页数和分页都为1 否则保持之前的分页
item.page = item.comments_count == item.sub_comments.length ? (!isEmpty(item.page) && item.page > 0 ? item.page : 1) : 0;
// 如果之前没有数据时设置总页数为1
item.page_total = !isEmpty(item.page_total) ? item.page_total : 1;
}
}
})
}
// 清空输入框,更新数据内容
this.active_comments = this.active_comments;
this.form_images_list = [];
this.comment_input_value = '';
this.comments_reply_data = {};
} else {
if (app.globalData.is_login_check(res.data)) {
app.globalData.showToast(res.data.msg);
} else {
app.globalData.showToast(t('common_sub_error_retry_tips'));
}
}
}
});
} catch (error) {
console.error('send_comment error:', error);
}
},
// 展开子评论
open_sub_comment(id, is_level) {
try {
console.log(id);
const comment = this.active_comments.find(item => item.id == id);
if (comment) {
comment.show_sub_comment = true;
comment.show_sub_comment_loading = true;
// 如果是一级,并且有子评论数据,不需要调用接口,直接渲染评论信息
if (is_level == 1 && !isEmpty(comment.sub_comments)) {
comment.show_sub_comment_loading = false;
return;
}
uni.request({
url: app.globalData.get_request_url("commentsreplylist", "index", "video"),
method: 'POST',
data: {
video_id: this.current_video_id,
video_comments_id: id,
page: comment.page + 1,
},
dataType: 'json',
success: res => {
const data = res.data;
if (data.code == 0) {
const new_data = data.data;
if (comment.page == 0) {
comment.sub_comments = new_data.data;
} else if (new_data.data.length > 0) {
comment.sub_comments.push(...new_data.data);
}
comment.page = new_data.page;
comment.page_total = new_data.page_total
}
},
complete: () => {
comment.show_sub_comment_loading = false;
}
});
}
} catch (error) {
console.error('open_sub_comment error:', error);
}
},
// 收起子评论
close_sub_comment(id) {
try {
const comment = this.active_comments.find(item => item.id == id);
if (comment) {
comment.show_sub_comment = false;
}
} catch (error) {
console.error('close_sub_comment error:', error);
}
},
// 分享事件
handle_share() {
try {
if ((this.$refs.share || null) != null) {
this.$refs.share.init({
status: true,
share_info: this.share_info,
});
}
} catch (error) {
console.error('handle_share error:', error);
}
},
// 更新视频数据信息
get_video_data_detail(id) {
try {
uni.request({
url: app.globalData.get_request_url("data", "index", "video"),
method: 'POST',
data: {
id: id,
},
dataType: 'json',
success: res => {
const data = res.data;
if (data.code == 0) {
const new_data = data.data;
// 更新视频数据
const index = this.video_data_list.findIndex(item => item.id == id);
if (index !== -1) {
// 使用Object.assign更新原对象保持引用不变
Object.assign(this.video_data_list[index], {
...this.video_data_list[index],
...new_data.data
});
}
}
}
});
} catch (error) {
console.error('get_video_data_detail error:', error);
}
},
// 更新点赞数量
set_givethumbs_num(id, comments_id) {
try {
uni.request({
url: app.globalData.get_request_url("givethumbs", "index", "video"),
method: 'POST',
data: {
video_id: id,
...(isEmpty(comments_id) ? {} : {video_comments_id: comments_id}),
},
dataType: 'json',
success: res => {
const data = res.data;
if (data.code == 0) {
const new_data = data.data;
// 提取更新点赞状态的公共函数
const updateThumbsStatus = (target, new_data) => {
target.give_thumbs_count = new_data.count;
target.is_give_thumbs = new_data.is_active;
};
// 优化后的遍历逻辑
for (let i = 0; i < this.video_data_list.length; i++) {
const item = this.video_data_list[i];
if (item.id == id) {
if (!isEmpty(comments_id)) {
// 安全检查comments数组是否存在
if (this.active_comments && Array.isArray(this.active_comments)) {
for (let j = 0; j < this.active_comments.length; j++) {
const comment = this.active_comments[j];
if (comment.id == comments_id) {
updateThumbsStatus(comment, new_data);
return; // 找到后直接返回
} else {
// 安全检查sub_comments数组是否存在
if (comment.sub_comments && Array.isArray(comment.sub_comments)) {
for (let k = 0; k < comment.sub_comments.length; k++) {
const sub_comment = comment.sub_comments[k];
if (sub_comment.id == comments_id) {
updateThumbsStatus(sub_comment, new_data);
return; // 找到后直接返回
}
}
}
}
}
}
} else {
updateThumbsStatus(item, new_data);
}
break; // 处理完当前item后跳出外层循环
}
}
// this.video_data_list = this.video_data_list;
this.$set(this, 'video_data_list', this.video_data_list)
} else {
if (app.globalData.is_login_check(res.data)) {
app.globalData.showToast(res.data.msg);
} else {
app.globalData.showToast(t('common_sub_error_retry_tips'));
}
}
}
});
} catch (error) {
console.error('set_givethumbs_num error:', error);
}
},
// 主评论回复
comment_reply(comments) {
try {
this.active_dropdown_id = null;
if (!isEmpty(comments)) {
this.comments_reply_data = comments;
}
} catch (error) {
console.error('comment_reply error:', error);
}
},
// 删除回复评论数据
comment_data_delete() {
try {
this.comments_reply_data = {};
} catch (error) {
console.error('comment_data_delete error:', error);
}
},
// 播放进度变化时触发
handle_time_update(e) {
try {
if (this.is_seeking) return;
let duration = this.current_video_duration;
// #ifdef MP-ALIPAY
if (e.detail.videoDuration > 0) {
duration = e.detail.videoDuration;
}
// #endif
// #ifndef MP-ALIPAY
if (e.detail.duration > 0) {
duration = e.detail.duration;
}
// #endif
// this.$nextTick(() => {
// this.current_video_duration = duration;
// this.current_video_progress = e.detail.currentTime;
// })
this.$set(this, 'current_video_duration', duration);
this.$set(this, 'current_video_progress', e.detail.currentTime)
} catch (error) {
console.error('handle_time_update error:', error);
}
},
// 视频进度条拖动时触发事件
handle_slider_changing() {
try {
this.is_seeking = true;
e.stopPropagation();
} catch (error) {
console.error('handle_slider_changing error:', error);
}
},
// 评论点赞
comment_like(id) {
try {
this.active_dropdown_id = null;
this.set_givethumbs_num(this.current_video_id, id);
} catch (error) {
console.error('comment_like error:', error);
}
},
// 视频进度条拖动完成触发事件
handle_slider_change(e) {
try {
const seek_time = e.detail.value;
if (this.create_video_contexts && this.create_video_contexts[this.current_index]) {
this.create_video_contexts[this.current_index].seek(seek_time);
this.current_video_progress = seek_time;
}
setTimeout(() => {
this.is_seeking = false;
}, 100);
e.stopPropagation();
} catch (error) {
console.error('handle_slider_change error:', error);
}
},
// 视频进度条时间显示
format_time(seconds) {
try {
if (isNaN(seconds) || seconds < 0) {
return '00:00';
}
const min = Math.floor(seconds / 60);
const sec = Math.floor(seconds % 60);
return `${min < 10 ? '0' : ''}${min}:${sec < 10 ? '0' : ''}${sec}`;
} catch (error) {
console.error('format_time error:', error);
}
},
// 返回上一页
handle_back() {
try {
app.globalData.page_back_prev_event();
} catch (error) {
console.error('handle_back error:', error);
}
},
// 跳转搜索记录页面
handle_search() {
try {
// 跳转到搜索记录页面
app.globalData.url_open(`/pages/plugins/video/search-record/search-record`, false);
} catch (error) {
console.error('handle_search error:', error);
}
},
// 关闭推荐商品
product_close_event(e) {
try {
this.video_data_list.forEach((item) => {
if (item.id == this.current_video_id) {
item.show_goods = false;
}
});
// this.video_data_list = this.video_data_list;
this.$set(this, 'video_data_list', this.video_data_list);
e.stopPropagation();
} catch (error) {
console.error('product_close_event error:', error);
}
},
// 点击商品卡片触发事件
handle_product_card_item(e) {
try {
const id = e?.currentTarget?.dataset?.id || '';
const data = this.video_data_list.find(item => item.id == id);
if (!isEmpty(data) && !isEmpty(data.goods)) {
app.globalData.url_open(data.goods.goods_url);
}
e.stopPropagation();
} catch (error) {
console.error('handle_product_card_item error:', error);
}
},
// 点击购买商品按钮触发事件
handle_product_button(e) {
try {
const id = e.currentTarget.dataset.id;
this.video_data_list.forEach((item, index) => {
if (item.id == id) {
if (item.show_goods && !isEmpty(item.goods)) {
app.globalData.url_open(item.goods.goods_url);
} else {
item.show_goods = true;
}
}
});
// this.video_data_list = this.video_data_list;
this.$set(this, 'video_data_list', this.video_data_list);
e.stopPropagation();
} catch (error) {
console.error('handle_product_button error:', error);
}
},
// 清理所有视频资源
cleanup_all_videos() {
try {
// 暂停所有视频
this.pause_all_videos_except(-1);
} catch (error) {
console.error('清理视频资源时出错:', error);
}
},
// 处理下拉菜单切换
handle_toggle_dropdown(comment_id) {
try {
if (this.active_dropdown_id == comment_id) {
this.active_dropdown_id = null;
} else {
this.active_dropdown_id = comment_id;
}
console.log(this.active_dropdown_id);
} catch (error) {
console.error('handle_toggle_dropdown error:', error);
}
},
// 关闭下拉菜单
handle_dropdown_item_click(comment_id, obj) {
try {
if (this.active_dropdown_id == comment_id) {
this.active_dropdown_id = null;
}
// 处理不同操作
if (obj.type == 'delete') {
// 确认删除
uni.showModal({
title: t('common_warm_tips'),
content: t('common_delete_confirm_tips'),
success: (res) => {
if (res.confirm) {
// 调用删除接口
this.delete_comment(comment_id);
}
}
});
} else if (obj.type == 'report') {
// 举报评论
this.$refs.popupReportRef.open();
this.report_comment_id = comment_id;
}
} catch (error) {
console.error('handle_dropdown_item_click error:', error);
}
},
// 删除评论
delete_comment(comment_id) {
try {
uni.request({
url: app.globalData.get_request_url("delete", "index", "video"),
method: 'POST',
data: {
ids: comment_id,
},
dataType: 'json',
success: res => {
if (res.data.code == 0) {
// 删除评论数据
this.delete_comment_handle(comment_id);
// 显示删除成功提示
app.globalData.showToast(res.data.msg, 'success');
} else {
if (app.globalData.is_login_check(res.data)) {
app.globalData.showToast(res.data.msg);
} else {
app.globalData.showToast(t('common_sub_error_retry_tips'));
}
}
}
});
} catch (error) {
console.error('send_comment error:', error);
}
},
// 删除评论数据处理
delete_comment_handle(comment_id) {
try {
// 删除成功从active_comments中移除对应数据
if (this.active_comments && Array.isArray(this.active_comments)) {
// 创建新的数组来存储过滤后的结果
const filteredComments = [];
for (let i = 0; i < this.active_comments.length; i++) {
const comment = this.active_comments[i];
// 清空回复评论数据(仅当匹配时)
if (comment.id === this.comments_reply_data.id || (comment.sub_comments && Array.isArray(comment.sub_comments) && comment.sub_comments.some(subComment => subComment.id === this.comments_reply_data.id))) {
this.comments_reply_data = {};
}
// 如果是父级评论且 id 匹配,跳过整个评论(包括子评论)
if (comment.id === comment_id) {
continue;
}
// 处理子评论
if (comment.sub_comments && Array.isArray(comment.sub_comments)) {
// 查找并移除匹配的子评论
const targetSubCommentIndex = comment.sub_comments.findIndex(subComment => subComment.id === comment_id);
if (targetSubCommentIndex !== -1) {
// 移除目标子评论
comment.sub_comments.splice(targetSubCommentIndex, 1);
// 更新显示状态和评论数
if (comment.sub_comments.length === 0) {
comment.show_sub_comment = false;
}
comment.comments_count -= 1;
}
}
// 保留当前评论
filteredComments.push(comment);
}
// 删除之后更新评论数据
this.video_data_list.forEach(item => {
if (item.id == this.current_video_id) {
item.comments_count = filteredComments.length;
}
})
// 更新数据
this.active_comments = filteredComments;
// this.video_data_list = this.video_data_list;
this.$set(this, 'video_data_list', this.video_data_list)
}
} catch (error) {
console.error('delete_comment_handle error:', error);
}
},
// 处理全局点击事件
handle_global_click(e) {
try {
// 检查点击目标是否在下拉菜单相关元素内
const target = e.target || e.srcElement;
// 查找点击元素是否在comment-option或dropdown-menu内
let isInDropdown = false;
let currentElement = target;
while (currentElement && currentElement !== document) {
// 检查是否点击了下拉菜单触发器或菜单本身
if (currentElement.classList &&
(currentElement.classList.contains('comment-option') ||
currentElement.classList.contains('dropdown-menu') ||
currentElement.closest('.comment-option') ||
currentElement.closest('.dropdown-menu'))) {
isInDropdown = true;
break;
}
currentElement = currentElement.parentNode;
}
// 如果点击的不是下拉菜单相关元素,则关闭所有下拉菜单
if (!isInDropdown && this.active_dropdown_id !== null) {
this.active_dropdown_id = null;
}
} catch (error) {
console.error('handle_global_click error:', error);
}
},
// 关闭举报弹窗
popup_report_close_event() {
try {
// this.$refs.popupReportRef.close();
this.current_main_index = 0;
this.current_sub_index = 0;
} catch (error) {
console.error('popup_report_close_event error:', error);
}
},
// 直接选择主原因(用于一行显示的点击)
select_main_reason(e) {
try {
const index = e?.currentTarget?.dataset?.index || 0;
console.log(index);
const main_index = parseInt(index);
if (main_index !== this.current_main_index) {
this.current_main_index = main_index;
this.current_sub_index = 0; // 默认选中第一个子类型
}
} catch (error) {
console.error('select_main_reason error:', error);
}
},
// 直接选择子类型(用于一行显示的点击)
select_sub_reason(e) {
try {
const index = e?.currentTarget?.dataset?.index || 0;
const sub_index = parseInt(index);
if (sub_index !== this.current_sub_index) {
this.current_sub_index = sub_index;
}
} catch (error) {
console.error('select_sub_reason error:', error);
}
},
/*
* 提交举报
*/
submit_report() {
try {
// 获取选中的举报原因和具体类型
const main_reason = this.report_type_list[this.current_main_index] || {};
const sub_reason = main_reason?.data[this.current_sub_index] || '';
// 调用举报接口
uni.request({
url: app.globalData.get_request_url("report", "index", "video"),
method: 'POST',
data: {
id: this.report_comment_id,
reason: main_reason?.name || '',
type: sub_reason
},
dataType: 'json',
success: res => {
if (res.data.code == 0) {
// 显示删除成功提示
app.globalData.showToast(res.data.msg, 'success');
// 关闭弹窗
this.popup_report_close_event();
} else {
if (app.globalData.is_login_check(res.data)) {
app.globalData.showToast(res.data.msg);
} else {
app.globalData.showToast(t('common_sub_error_retry_tips'));
}
}
}
});
} catch (error) {
console.error('submit_report error:', error);
}
},
// 键盘显示时,切换输入框
add_comment() {
try {
//#ifndef H5
this.is_add_comment = true;
this.active_dropdown_id = null;
//#endif
} catch (error) {
console.error('add_comment error:', error);
}
},
/**
* 键盘高度变化监听处理
* @param {Object} res - 键盘高度变化事件对象
*/
listener(res) {
try {
if (res.height > 0) {
this.listener_height = res.height - 1;
} else {
this.listener_height = 0;
}
} catch (error) {
console.error('listener error:', error);
}
},
/**
* 绑定键盘高度变化监听事件
*/
bind_keyboard_listener() {
try {
uni.onKeyboardHeightChange(this.listener);
} catch (error) {
console.error('bind_keyboard_listener error:', error);
}
},
/**
* 解绑键盘高度变化监听事件
*/
unbind_keyboard_listener() {
try {
uni.offKeyboardHeightChange(this.listener);
} catch (error) {
console.error('unbind_keyboard_listener error:', error);
}
},
}
};
</script>
<style lang="scss" scoped>
@import './detail.css';
</style>