Files
huangjingfen/pro_v3.5.1_副本/view/uniapp/components/discoverVideo/index.vue
apple 434aa8c69d feat(fsgx): 完成全部24项开发任务 Phase1-7
Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods

Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口

Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则

Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)

Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口

Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单

Made-with: Cursor
2026-03-23 22:32:19 +08:00

733 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view>
<view class="fixed-lt pl-20 z-4000 h-80 flex-y-center"
:style="{top: sysHeight + 'px'}" v-if="fullVideo" @tap="backPage">
<text class="iconfont icon-ic_leftarrow fs-40 text--w111-fff"></text>
</view>
<view>
<swiper class="video-swiper"
vertical
:circular="false"
:autoplay="false"
:duration="300"
:current="swiperCurrent"
@change="videoChange">
<swiper-item v-for="(item, index) in swiperData" :key="index">
<view class="swiper-video-item h-full">
<view class="h-full relative" v-if="item.content_type == 2">
<video
:id="item.id + 'k' + index"
:muted="false"
:autoplay="index === swiperCurrent"
:loop="true"
:controls="false"
:http-cache="true"
:page-gesture="false"
:show-fullscreen-btn="false"
:show-loading="false"
:show-center-play-btn="false"
:enable-progress-gesture="false"
:src="item.video_url"
@ended="ended"
@click="tapVideoHover($event)"
class="w-full h-full"
></video>
<!-- #ifdef H5 -->
<view class="abs-center w-full h-full flex-center" @tap="playVideo" v-if="!isPlay && Auth.isWeixin()">
<image src="@/static/images/stop.png" mode="" class="w-160 h-160"></image>
</view>
<!-- #endif -->
</view>
<view class="flex-y-center h-full" v-else>
<swiper
class="w-full h-full"
circular
:autoplay="index === swiperCurrent"
:current="imgCurrent"
:interval="4000"
:duration="500"
@change="photoChange">
<swiper-item v-for="(photo, i) in item.slider_image" :key="i">
<view class="h-full flex-y-center">
<image :src="photo" mode="widthFix" class="w-full"></image>
</view>
</swiper-item>
</swiper>
</view>
</view>
</swiper-item>
</swiper>
</view>
<view class="right-action text--w111-fff flex-col flex-y-center z-800"
:class="{'action-box':showFooter,'full-action': fullVideo}">
<view class="relative">
<view class="w-88 h-88 rd-50-p111- avatar-box" v-if="currentData && currentData.author_image" >
<image :src="currentData.author_image" class="w-full h-full rd-50-p111-" @tap="toUser()"></image>
</view>
<view class="w-88 h-88 rd-50-p111- avatar-box" v-else></view>
<view class="follow-icon flex-center" v-if="!currentData.is_follow && !currentData.is_self"
@tap="followFun">
<text class="iconfont icon-ic_increase fs-20 text--w111-fff"></text>
</view>
</view>
<!-- 2.点赞 -->
<view class="mt-50 flex-col flex-y-center" @tap="contentLike">
<text class="iconfont fs-72 icon-ic_love_2 shadow" :class="{'like-heart': currentData.is_like}"></text>
<text class="fs-22 pt-10 text-center shadow">{{currentData.like_num > 0 ? currentData.like_num : '点赞'}}</text>
</view>
<!-- 3.评论 -->
<view class="mt-36 flex-col flex-y-center" @tap="toggleReplyDrawer" v-if="replyStatus">
<text class="iconfont icon-icon_comment fs-64 shadow"></text>
<text class="fs-22 pt-16 text-center shadow">{{currentData.comment_num > 0 ? currentData.comment_num : '评论'}}</text>
</view>
<!-- 3.分享 -->
<!-- #ifdef H5 || APP-PLUS -->
<view class="mt-36 flex-col flex-y-center" @tap="openShare">
<text class="iconfont icon-icon_transmit fs-66 shadow"></text>
<text class="fs-22 pt-10 shadow">分享</text>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button class="mt-36 flex-col flex-y-center bg-transparent text--w111-fff" open-type="share" hover-class="none" @tap="openShare">
<text class="iconfont icon-icon_transmit fs-66 shadow"></text>
<text class="fs-22 pt-10 shadow">分享</text>
</button>
<!-- #endif -->
<view class="mt-36 flex-col flex-y-center relative" v-if="showMore">
<text class="iconfont icon-ic_more fs-60 shadow" @tap="()=>{showBubble = !showBubble}"></text>
<view class="bubble_box">
<view class="bubble text--w111-333 flex-col justify-between" v-if="showBubble">
<view class="flex-y-center pl-16" @tap="editTo">
<text class="iconfont icon-ic_edit"></text>
<text class="fs-26 pl-18">编辑</text>
</view>
<view class="x-line"></view>
<view class="flex-y-center pl-16" @tap="delContent">
<text class="iconfont icon-ic_delete"></text>
<text class="fs-26 pl-18">删除</text>
</view>
</view>
</view>
</view>
<view class="mt-36 flex-col flex-y-center"
v-if="currentData && currentData.product && currentData.product.length" @tap="getProList">
<image src="@/static/img/discover_cart.png" class="w-66 h-66"></image>
</view>
<view v-if="showMore && !currentData.product.length" class="h-24"></view>
</view>
<view class="fixed-desc w-full text--w111-fff" :class="{'hide-tab':showFooter,'full-tab':fullVideo}">
<view class="box mb-32">
<view class="w-560 lh-42rpx fs-28 fw-500">@{{currentData.author || '用户已注销'}}</view>
<view class="w-560 content-box lh-42rpx fs-28 mt-20">
<BaseTextMore
:content="currentData.title + currentData.content"
fontColor="rgba(255,255,255,0.9)"
actionFontColor="#fff"
:font-size="28"
:rows="2"
expand-text="展开"
collapse-text="收起"></BaseTextMore>
</view>
<view class="w-560 content-box lh-42rpx fs-28 mt-10 text--w111-fff">
<text class="pr-10" v-for="(topic,i) in currentData.topic" :key="i"
@tap="authTo('/pages/discover/discoverTopic/index?id=' + topic.id + '&name=' + topic.name)">
#{{topic.name}}</text>
</view>
<scroll-view scroll-x="true" class="white-nowrap vertical-middle w-584 mt-24" show-scrollbar="false"
v-if="currentData && currentData.product && currentData.product.length">
<view class="inline-block mr-30" v-for="(pro,k) in currentData.product" :key="k">
<view class="w-444 bg--w111-fff rd-16rpx p-16 b-e flex-between-center"
@tap="goDetail(pro.id)">
<image :src="pro.image" class="w-104 h-104 rd-8rpx"></image>
<view class="flex-1 pl-14">
<view class="w-282 line1 fs-26 pb-20 text--w111-333">{{pro.store_name}}</view>
<view class="flex-between-center">
<baseMoney :money="pro.price" symbolSize="22" integerSize="32" decimalSize="24" color="#333" weight></baseMoney>
<view class="w-92 h-40 rd-20rpx bg-color flex-center text--w111-fff fs-22">购买</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="w-full flex-center pb-32 mt-24 relative z-80"
style="left: -20rpx;"
v-if="imgList.length && imgList.length > 1">
<block v-for="(_, index) in imgList" :key="index">
<view class="dot_item h-6 rd-2rpx" :style="{ 'background-color': imgCurrent >= index ? '#fff' : 'rgba(255,255,255,0.4)', width: dotWidth + 'rpx' }"></view>
</block>
</view>
<view v-else class="h-34"></view>
</view>
<base-drawer
mode="bottom"
:visible="showProDrawer"
zIndex="2000"
background-color="transparent"
mask
maskClosable
@close="closeDrawer">
<view class="w-full bg--w111-fff text--w111-333 rd-t-40rpx relative">
<view class="fs-30 pt-48 pb-44 pl-32">TA提到的宝贝</view>
<view class="close-btn flex-center" @tap="closeDrawer()">
<text class="iconfont icon-ic_close text--w111-666 fs-24"></text>
</view>
<view class="px-30">
<scroll-view scroll-y="true" style="max-height: 722rpx;">
<view class="flex-between-center pro-item"
v-for="(item,index) in productList" :key="index"
@tap="goDetail(item.id)">
<view class="flex-1 flex">
<image class="w-200 h-200 rd-16rpx" :src='item.image' mode="aspectFill"></image>
<view class="h-200 flex-1 flex-col justify-between pl-30">
<view>
<view class="h-68 lh-34rpx fs-30 line2">{{item.store_name}}</view>
<view class="w-full flex items-end flex-wrap mt-16">
<BaseTag
:text="label.label_name"
:color="label.color"
:background="label.bg_color"
:borderColor="label.border_color"
:circle="label.border_color ? true : false"
:imgSrc="label.icon"
v-for="(label, idx) in item.store_label" :key="idx"></BaseTag>
</view>
</view>
<view class="flex-between-center">
<view class="flex-y-center">
<baseMoney :money="item.price" symbolSize="26" integerSize="40" decimalSize="26" weight></baseMoney>
<view class="text--w111-999 fs-26 pl-12 text-line">{{item.ot_price}}</view>
</view>
<view class="w-136 h-52 rd-30rpx flex-center text--w111-fff fs-22 bg-color">立即购买</view>
</view>
</view>
</view>
</view>
<view v-if="!productList.length">
<emptyPage title="暂无商品,去看点别的吧~" ></emptyPage>
</view>
</scroll-view>
</view>
<view :class="{'hide-footer':!showFooter,'show-footer': showFooter}">
<view :class="{'h-100':showFooter}"></view>
</view>
</view>
</base-drawer>
<replyList
:visible="showReply"
:community_id="community_id"
:comment_num="comment_num"
:addReply="addReply"
:showFooter="showFooter"
@closeDrawer="closeDrawer"
@commentAdd="commentAdd"></replyList>
<tuiModal
:show="showDelete"
title="确认删除该内容"
:maskClosable="false"
@click="handleDelete"></tuiModal>
<view class="notice-modal" :style="{'top': sysHeight + 52 + 'px'}" v-if="fullVideo">
<view class="w-full h-full container text--w111-fff" v-if="currentData.is_verify == -2" >
<view class="flex-y-center fs-28 fw-500">
<text class="iconfont icon-icon_clock1 fs-30"></text>
<text class="pl-20">平台强制下架内容仅自己可见</text>
</view>
<view class="fs-22 pt-14 pl-48">发布的内容涉及政治敏感词汇请修改后重新发布</view>
</view>
<view class="w-full h-full container text--w111-fff" v-if="currentData.is_verify == -1" >
<view class="flex-y-center fs-28 fw-500">
<text class="iconfont icon-icon_clock1 fs-30"></text>
<text class="pl-20">审核未通过内容仅自己可见</text>
</view>
<view class="fs-22 pt-14 pl-48">{{currentData.refusal}}</view>
</view>
<view class="w-full h-full container text--w111-fff" v-if="currentData.is_verify == 0" >
<view class="flex-y-center fs-28 fw-500">
<text class="iconfont icon-icon_clock1 fs-30"></text>
<text class="pl-20">正在审核内容仅自己可见</text>
</view>
<view class="fs-22 pt-14 pl-48">发布的内容审核通过后将在展示首页展示</view>
</view>
</view>
</view>
</template>
<script>
let sysHeight = uni.getWindowInfo().statusBarHeight;
import { HTTP_REQUEST_URL } from '@/config/app';
import emptyPage from '@/components/emptyPage.vue';
import replyList from "@/components/discoverVideo/replyList.vue"
import tuiModal from "@/components/tui-modal/index.vue";
import BaseTextMore from "@/components/BaseTextMore.vue"
import { getProductslist } from "@/api/store.js";
import {
communityLikeApi,
communitySetInsterestApi,
communityDeleteApi,
communityShareApi,
communityBrowseApi,
} from "@/api/community.js"
import { mapGetters } from 'vuex';
import { toLogin } from "@/libs/login.js"
// #ifdef H5
import Auth from '@/libs/wechat.js';
// #endif
export default {
name: 'discoverSwiperVideo',
props: {
swiperData: {
type: Array,
default: () => []
},
showFooter:{
type: Boolean,
default: false
},
fullVideo:{
type: Boolean,
default: false
},
replyStatus:{
type: Number,
default: 1
},
addReply:{
type: Number,
default: 1
}
},
data() {
return {
sysHeight,
swiperCurrent: 0,
imgList: [],
imgCurrent: 0,
isPlay: false,
showProDrawer:false,
productList:[],
showBubble:false,
showReply:false,
community_id:'',
comment_num:'',
currentData:{
author:'',
author_image:'',
title:'',
content:'',
product:[],
slider_image:[],
is_follow:0,
is_self:0
},
showDelete:false,
isExpanded:false,
startY: 0,
moveY: 0,
threshold: 50, // 滑动阈值
// #ifdef H5
Auth,
// #endif
};
},
components:{
emptyPage,
replyList,
tuiModal,
BaseTextMore
},
watch:{
swiperData:{
handler(nVal){
this.currentData = nVal[this.swiperCurrent];
if(this.currentData && this.currentData.slider_image && this.currentData.slider_image.length){
this.imgList = this.currentData.slider_image;
}
//初始化记录第一个浏览的内容
if(this.currentData){
this.lookVideo();
}
},
immediate:true
},
},
computed: {
...mapGetters(['isLogin','uid']),
dotWidth() {
let windowWidth = uni.getWindowInfo().windowWidth;
return (windowWidth * 2 - (20 + (this.imgList.length) * 12)) / this.imgList.length;
},
showMore(){
if(this.currentData && this.currentData.relation_id == this.uid && this.isLogin){
return true
}else {
return false
}
}
},
methods: {
handleTouchStart(e) {
this.startY = e.touches[0].pageY;
},
handleTouchMove(e) {
this.moveY = e.touches[0].pageY;
},
handleTouchEnd() {
const diffY = this.moveY - this.startY;
if (diffY > this.threshold) {
if (this.swiperCurrent > 0) {
this.videoChange({detail: {current: this.swiperCurrent - 1}});
}
} else if (diffY < -this.threshold) {
this.videoChange({detail: {current: this.swiperCurrent + 1}});
}
},
backPage(){
let pages = getCurrentPages(); // 获取当前打开过的页面路由数,
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.switchTab({
url: '/pages/index/index'
});
}
},
playVideo(){
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).play();
this.isPlay = true;
},
tapVideoHover(e) {
if(this.isPlay){
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).pause();
this.isPlay = false;
}else{
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).play();
this.isPlay = true;
}
},
ended() {},
videoChange(event) {
console.log(event);
const newIndex = event.detail.current;
// 暂停当前视频
if (this.swiperCurrent !== newIndex) {
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).pause();
this.swiperCurrent = newIndex;
// 播放新视频
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).play();
this.currentData = this.swiperData[this.swiperCurrent];
this.showBubble = false;
this.$emit('onSwiper',this.swiperCurrent);
}
if(this.swiperData[this.swiperCurrent].slider_image.length){
this.imgList = this.swiperData[this.swiperCurrent].slider_image;
}else{
this.imgList = [];
}
this.imgCurrent = 0;
this.lookVideo();
},
lookVideo(){
if(this.isLogin){
communityBrowseApi(this.currentData.id).catch(err=>{
console.error(err);
})
}
},
photoChange(e) {
this.imgCurrent = e.detail.current;
},
goDetail(id){
let url = `/pages/goods_details/index?id=${id}`
this.$util.JumpPath(url);
},
editTo(){
uni.navigateTo({
url: '/pages/discover/discoverCreate/index?id=' + this.currentData.id
})
},
delContent(){
this.showDelete = true;
this.showBubble = false;
},
handleDelete(e){
let index = e.index;
let that = this;
if(index == 1){
communityDeleteApi(this.currentData.id).then(res=>{
this.showDelete = false;
return this.$util.Tips({
title: res.msg
},{
tab:4,
url:'/pages/discoverIndex/index'
});
}).catch(err => {
this.showDelete = false;
return this.$util.Tips({
title: err
});
});
}else{
this.showDelete = false;
}
},
getProList(){
let data = {
ids: this.currentData.product_id.toString()
};
getProductslist(data).then(res=>{
this.productList = res.data;
this.showProDrawer = true;
})
},
toUser(){
uni.navigateTo({
url: '/pages/discover/discoverUser/index?id=' + this.currentData.relation_id
})
},
authTo(url){
uni.navigateTo({
url: url
});
},
toggleReplyDrawer(){
this.community_id = this.currentData.id;
this.comment_num = this.currentData.comment_num;
if(this.isLogin){
this.showReply = true;
}else{
toLogin();
}
},
contentLike(){
if(!this.isLogin) {
toLogin();
}
let id = this.currentData.id;
let status = this.currentData.is_like == 1 ? 0 : 1;
let that = this;
communityLikeApi(id,{status}).then(res=>{
that.$emit('onLike',{index:that.swiperCurrent,status:status});
}).catch(err=>{
return this.$util.Tips({
title: err
});
})
},
closeDrawer(){
this.showReply = false;
this.showProDrawer = false;
},
followFun(){
if(!this.isLogin) {
toLogin();
}
communitySetInsterestApi(this.currentData.relation_id,{status:1}).then(res=>{
this.$emit('followChange',{index:this.swiperCurrent});
}).catch(err => {
return this.$util.Tips({
title: err
});
});
},
commentAdd(data){
if(data.type == 1){
this.comment_num++;
}else{
this.comment_num = this.comment_num - data.num;
}
},
openShare(){
let that = this;
if(that.isLogin){
communityShareApi(that.currentData.id).catch(err=>{
console.error(err);
})
// #ifdef H5
uni.setClipboardData({
data: `${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?id=${that.currentData.id}&spid=${that.uid}`,
success: () => {
uni.showToast({
title: '链接已复制'
})
}
})
// #endif
// #ifdef MP-WEIXIN
this.$emit('onShare',that.currentData);
// #endif
}else{
toLogin();
}
},
},
};
</script>
<style lang="scss" scoped>
.video-swiper {
height: 100vh;
}
.z-4000{
z-index: 4000;
}
.avatar-box{
border: 3rpx solid #FFFFFF;
}
.bg-transparent{
background-color: transparent;
}
.text-line{
text-decoration: line-through;
}
.hide-footer{
padding-bottom: 30rpx;
}
.show-footer{
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
}
.right-action {
position: fixed;
right: 20rpx;
/* #ifdef H5 */
bottom: 162rpx;
/* #endif */
/* #ifdef MP-WEIXIN */
bottom: 62rpx;
/* #endif */
width: 88rpx;
}
.action-box{
/* #ifdef MP-WEIXIN */
bottom: calc(162rpx + env(safe-area-inset-bottom));
/* #endif */
}
.full-action{
/* #ifdef H5 */
bottom: 64rpx !important;
/* #endif */
/* #ifdef MP-WEIXIN */
bottom: calc(64rpx + env(safe-area-inset-bottom)) !important;
/* #endif */
}
.shadow{
text-shadow: 0px 2rpx 4rpx rgba(0, 0, 0, 0.2);
}
.fixed-desc {
position: fixed;
left: 0;
padding-left: 20rpx;
z-index: 10;
/* #ifdef H5 */
bottom: 100rpx;
/* #endif */
/* #ifdef MP-WEIXIN */
bottom: 0;
/* #endif */
background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.3) 100%);
.box {
width: 584rpx;
}
.w-584 {
width: 584rpx;
}
}
.content-box{
max-height: 400rpx;
overflow-y: auto;
}
.hide-tab{
/* #ifdef MP-WEIXIN */
bottom: calc(100rpx + env(safe-area-inset-bottom));
/* #endif */
}
.full-tab{
/* #ifdef H5 */
bottom: 0 !important;
/* #endif */
/* #ifdef MP-WEIXIN */
bottom: env(safe-area-inset-bottom) !important;
/* #endif */
}
.dot_item ~ .dot_item {
margin-left: 12rpx;
}
.follow-icon{
position: absolute;
bottom: -10rpx;
left: 24rpx;
width: 42rpx;
height: 42rpx;
border-radius: 50%;
background-color: var(--view-theme);
}
.like-heart{
color: #e93323;
}
.pro-item ~ .pro-item{
margin-top: 42rpx;
}
.bubble_box{
position: absolute;
top: -64rpx;
left: -208rpx;
width: 184rpx;
z-index: 999;
.bubble{
width: 184rpx;
height: 198rpx;
background: #FFFFFF;
box-shadow: 0rpx 2rpx 15rpx 0rpx rgba(0,0,0,0.102);
border-radius: 16rpx;
padding: 30rpx 16rpx 30rpx 16rpx;
position: relative;
&:after{
content: '';
position: absolute;
right: -10px;
top: 74rpx;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid #fff;
}
}
.x-line{
border-bottom: 1rpx solid rgba(0, 0, 0, 0.10);
}
}
.notice-modal{
position: fixed;
left: 50%;
transform: translateX(-50%);
width: 690rpx;
background: rgba(51, 51, 51, 0.7);
border-radius: 10rpx;
.container{
padding: 26rpx 30rpx;
.icon-icon_clock1{
color: rgba(252, 131, 39, 1);
}
.icon-a-ic_tanhao1{
color: rgba(233, 51, 35, 1);
}
}
}
.close-btn{
position: absolute;
right: 28rpx;
top: 28rpx;
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background-color: #eee;
}
</style>