绿满眶商城微信小程序-uniapp
85f3a086a37a09f3bff8aa1ddc63f1a6d11c9dc1..f621aacc5ed02fab11c9c1f35c69a36babb5411f
2025-08-25 zxl
购物车点击事件,分享视频封面问题
f621aa 对比 | 目录
2025-08-13 zxl
购物车点击事件,分享视频封面问题
9e4ae3 对比 | 目录
6个文件已修改
2个文件已添加
574 ■■■■■ 已修改文件
api/popup.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api/video.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/api.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/ActivityPopup/ActivityPopup.vue 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/commodity-square/commoditySquare.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/tabbar/index/home.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
store/index.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api/popup.js
New file
@@ -0,0 +1,20 @@
import { http, Method } from "@/utils/request.js";
import api from "@/config/api.js";
 export function setPopupRedisTime() {
  return http.request({
    url: "/lmk/popup/setPopupRedisTime",
    method: Method.GET,
    needToken: true,
  });
}
 export function getPopupAcitivty() {
  return http.request({
    url: "/lmk/popup/getPopupActivity",
    method: Method.GET,
    needToken: true,
  });
}
api/video.js
@@ -155,6 +155,17 @@
}
/**
 *
 */
export function removeByIdVideoComment(params){
    return http.request({
        url:"/lmk/video-comment/" + params,
        method: Method.DELETE,
        needToken: true,
    })
}
/**
 * 发布视频评论
 * 
 * @param params
config/api.js
@@ -9,18 +9,18 @@
  // buyer: "http://192.168.0.15:8888",
  // store: "http://192.168.0.15:8889",
  // im: "http://127.0.0.1:8885",
  // common: "http://127.0.0.1:8890",
  // buyer: "http://127.0.0.1:8888",
  // store: "http://127.0.0.1:8889",
  im: "http://127.0.0.1:8885",
  common: "http://127.0.0.1:8890",
  buyer: "http://127.0.0.1:8888",
  store: "http://127.0.0.1:8889",
  // common: "http://192.168.0.113:8890",
  // buyer: "http://192.168.0.113:8888",
  // im: "http://192.168.0.113:8885",
  im: "https://myk.9village.cn",
  common: "https://myk.9village.cn",
  buyer: "https://myk.9village.cn",
  store: "https://myk.9village.cn",
  // im: "https://myk.9village.cn",
  // common: "https://myk.9village.cn",
  // buyer: "https://myk.9village.cn",
  // store: "https://myk.9village.cn",
  // im: "https://www.meiyikuang.com/mykapi",
  // common: "https://www.meiyikuang.com/mykapi",
  // buyer: "https://www.meiyikuang.com/mykapi",
pages.json
@@ -28,6 +28,13 @@
        "navigationStyle": "custom"
        // 隐藏顶部导航栏
      }
    },
    {
        "path" : "pages/ActivityPopup/ActivityPopup",
        "style" :
        {
            "navigationBarTitleText" : ""
        }
    }
    // {
    //     "path": "pages/tabbar/home/index",
pages/ActivityPopup/ActivityPopup.vue
New file
@@ -0,0 +1,354 @@
<template>
  <view class="activity-popup" v-if="show" @click="onMaskClick">
    <!-- 遮罩层 -->
    <view class="popup-mask" :class="{ 'mask-enter': show, 'mask-leave': !show }"></view>
    <!-- 弹窗内容 -->
    <view class="popup-content"
          :class="{ 'content-enter': show, 'content-leave': !show }"
          @click.stop>
      <!-- 关闭按钮 -->
      <view class="close-btn" @click="onClose">
        <uni-icons type="close" size="24" color="#666"></uni-icons>
      </view>
      <!-- 活动图片 -->
      <view class="activity-img">
        <image :src="activityImage" mode="widthFix" class="popup-img"></image>
      </view>
      <!-- 活动信息 -->
      <view class="activity-info">
        <h3 class="activity-title">{{ activityTitle }}</h3>
        <p class="activity-desc">{{ activityDesc }}</p>
        <!-- 倒计时(如果需要) -->
        <view class="countdown" v-if="showCountdown">
          <text class="countdown-text">活动剩余时间:</text>
          <view class="countdown-time">
            <text class="time-box">{{ days }}</text>
            <text class="time-sep">:</text>
            <text class="time-box">{{ hours }}</text>
            <text class="time-sep">:</text>
            <text class="time-box">{{ minutes }}</text>
            <text class="time-sep">:</text>
            <text class="time-box">{{ seconds }}</text>
          </view>
        </view>
        <!-- 参与按钮 -->
        <button class="join-btn" @click="onJoinActivity">
          {{ joinButtonText }}
        </button>
      </view>
    </view>
  </view>
</template>
<script>
import {getPopupAcitivty} from '@/api/popup.js'
export default {
  name: 'ActivityPopup',
  props: {
    // 控制弹窗显示/隐藏
    show: {
      type: Boolean,
      default: false
    },
    // 活动图片URL
    activityImage: {
      type: String,
      default: ''
    },
    // 活动标题
    activityTitle: {
      type: String,
      default: '限时优惠活动'
    },
    // 活动描述
    activityDesc: {
      type: String,
      default: '参与本次活动,即可获得超值大奖,机会难得,不要错过!'
    },
    // 参与按钮文本
    joinButtonText: {
      type: String,
      default: '立即参与'
    },
    // 是否显示倒计时
    showCountdown: {
      type: Boolean,
      default: true
    },
    // 倒计时结束时间(时间戳)
    endTime: {
      type: Number,
      default: () => {
        // 默认7天后结束
        return Date.now() + 7 * 24 * 60 * 60 * 1000;
      }
    }
  },
  data() {
    return {
      days: '00',
      hours: '00',
      minutes: '00',
      seconds: '00',
      countdownTimer: null
    };
  },
  watch: {
    show(newVal) {
        console.log("弹窗监听变化",newVal)
      if (newVal && this.showCountdown) {
        this.startCountdown();
      } else if (!newVal && this.countdownTimer) {
        clearInterval(this.countdownTimer);
        this.countdownTimer = null;
      }
    }
  },
  mounted() {
      console.log('组件已挂载,此时可以访问 props 和 DOM');
      console.log('当前 show 状态:', this.show); // 可以打印 props 中的 show
    },
  methods: {
      onpan(){
      },
    // 开始倒计时
    startCountdown() {
      this.updateCountdown();
      if (this.countdownTimer) {
        clearInterval(this.countdownTimer);
      }
      this.countdownTimer = setInterval(() => {
        this.updateCountdown();
      }, 1000);
    },
    // 更新倒计时
    updateCountdown() {
      const now = Date.now();
      const diff = this.endTime - now;
      if (diff <= 0) {
        this.days = '00';
        this.hours = '00';
        this.minutes = '00';
        this.seconds = '00';
        clearInterval(this.countdownTimer);
        this.countdownTimer = null;
        return;
      }
      // 计算天、时、分、秒
      const days = Math.floor(diff / (1000 * 60 * 60 * 24));
      const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
      const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
      const seconds = Math.floor((diff % (1000 * 60)) / 1000);
      // 格式化数字为两位数
      this.days = days.toString().padStart(2, '0');
      this.hours = hours.toString().padStart(2, '0');
      this.minutes = minutes.toString().padStart(2, '0');
      this.seconds = seconds.toString().padStart(2, '0');
    },
    // 关闭弹窗
    onClose() {
      this.$emit('close');
    },
    // 点击遮罩层关闭
    onMaskClick() {
      this.$emit('close');
    },
    // 点击参与活动
    onJoinActivity() {
      this.$emit('join');
      // 可以在这里添加参与活动后的逻辑,比如关闭弹窗
      // this.onClose();
    }
  },
  beforeDestroy() {
    if (this.countdownTimer) {
      clearInterval(this.countdownTimer);
    }
  }
};
</script>
<style scoped>
.activity-popup {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20rpx;
}
/* 遮罩层样式 */
.popup-mask {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
}
.mask-enter {
  opacity: 1;
}
.mask-leave {
  opacity: 0;
}
/* 弹窗内容样式 */
.popup-content {
  width: 100%;
  max-width: 600rpx;
  background-color: #ffffff;
  border-radius: 24rpx;
  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.2);
  overflow: hidden;
  transform: translateY(50rpx) scale(0.9);
  opacity: 0;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.content-enter {
  transform: translateY(0) scale(1);
  opacity: 1;
}
.content-leave {
  transform: translateY(50rpx) scale(0.9);
  opacity: 0;
}
/* 关闭按钮 */
.close-btn {
  position: absolute;
  top: 20rpx;
  right: 20rpx;
  width: 50rpx;
  height: 50rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  z-index: 10;
}
/* 活动图片 */
.activity-img {
    position: relative;
    width: 100%; /* 容器宽度铺满弹窗内容区 */
    /* 可选:添加背景色,避免图片加载前显示空白 */
    background-color: #f5f5f5;
    border-top-left-radius: 24rpx; /* 与图片圆角一致,避免容器露白 */
    border-top-right-radius: 24rpx;
}
.popup-img {
    width: 100%;
    /* 核心:用 max-height 限制最大高度,不限制 min-height(保留比例) */
    max-height: 300rpx; /* 图片最大高度(超过则按比例缩小,保证完整显示) */
}
/* 活动信息 */
.activity-info {
  padding: 30rpx;
  text-align: center;
}
.activity-title {
  font-size: 32rpx;
  font-weight: bold;
  color: #333333;
  margin-bottom: 20rpx;
  line-height: 1.3;
}
.activity-desc {
  font-size: 26rpx;
  color: #666666;
  margin-bottom: 30rpx;
  line-height: 1.5;
}
/* 倒计时样式 */
.countdown {
  margin-bottom: 30rpx;
  text-align: center;
}
.countdown-text {
  font-size: 24rpx;
  color: #ff6b3b;
  margin-right: 10rpx;
}
.countdown-time {
  display: inline-flex;
  align-items: center;
}
.time-box {
  display: inline-block;
  width: 50rpx;
  height: 50rpx;
  line-height: 50rpx;
  background-color: #ff6b3b;
  color: #ffffff;
  font-size: 26rpx;
  font-weight: bold;
  border-radius: 8rpx;
  text-align: center;
}
.time-sep {
  margin: 0 10rpx;
  color: #ff6b3b;
  font-size: 28rpx;
  font-weight: bold;
}
/* 参与按钮 */
.join-btn {
  width: 100%;
  height: 80rpx;
  line-height: 80rpx;
  background-color: #ff6b3b;
  color: #ffffff;
  font-size: 30rpx;
  border-radius: 40rpx;
  border: none;
  box-shadow: 0 5rpx 15rpx rgba(255, 107, 59, 0.4);
  transition: all 0.2s ease;
}
.join-btn::after {
  border: none;
}
.join-btn:active {
  background-color: #e55a2a;
  transform: scale(0.98);
}
</style>
pages/commodity-square/commoditySquare.vue
@@ -77,7 +77,7 @@
                <view class="settlement">去购物车结算</view>
            </view> -->
        </view>
        <view class="squareFotter" style="width: 750rpx; display: flex;align-items: center;
        <view  @click="gotoCardList()" class="squareFotter" style="width: 750rpx; display: flex;align-items: center;
            justify-content: space-between;padding: 0 32rpx;box-sizing: border-box;">
            <view style="display: flex;align-items: center;justify-content: center;">
                <view class="icon" style="position: relative;">
@@ -93,7 +93,7 @@
                <view style="margin-left: 10rpx;color: #e06c75;font-size: 48rpx;font-weight: bold;">
                    ¥{{priceInfo.price}}</view>
            </view>
            <view class="settlement" @click="gotoCardList()">去结算</view>
            <view class="settlement">去结算</view>
        </view>
    </view>
</template>
pages/tabbar/index/home.vue
@@ -202,11 +202,13 @@
          </view>
          <view v-else class="comment-item" v-for="(comment, index) in comments" :key="comment.id">
            <view style="display: flex;">
            <view style="display: flex;" >
                <image class="comment-avatar" :src="comment.userAvatar || '/static/default-avatar.png'"></image>
                <view class="comment-content">
                  <text class="nickname">{{comment.userNickname}} <text v-if="userId===comment.userId">(我)</text> </text>
                  <text class="content">{{comment.commentContent}}</text>
                    <view @click="replyClick(comment)">
                        <text class="nickname">{{comment.userNickname}} <text v-if="userId===comment.userId">(我)</text> </text>
                        <text class="content">{{comment.commentContent}}</text>
                    </view>
                  <view style="position: relative;">
                    <text class="time">{{formatTime(comment.createTime)}}</text>
                    <text @click="openReply(comment)" class="reply-btu time">回复</text>
@@ -218,7 +220,7 @@
            <!-- 回复列表 -->
              <view class="reply-list" v-if="comment.replies && comment.replies.length > 0">
                <view class="reply-item" v-for="(reply, replyIndex) in comment.replies" :key="reply.id">
                  <view class="reply-content">
                  <view class="reply-content" @click="replyClick(reply)">
                    <view style="display: flex;">
                        <image class="comment-reply-avatar" :src="reply.replyUserAvatar || '/static/default-avatar.png'"></image>
                        <text class="nickname">{{reply.userNickname}}<text v-if="userId===comment.userId">(我)</text></text>
@@ -261,6 +263,16 @@
    <custom-tabbar bgColor="#333333" selected="index" selectedTextColor="#ffffff"></custom-tabbar>
    <ActivityPopup
          :show="activityPopup.show"
          :activityTitle="activityPopup.title"
          :activityDesc="activityPopup.desc"
          :activityImage="activityPopup.image"
          :endTime="activityPopup.endTime"
          @close="onClosePopup"
          @join="onJoinActivity"
        />
  </view>
</template>
@@ -274,8 +286,13 @@
    thubmsUpComment, 
    cancelThubmsUpComment,
    changeThumbsUp,
    getGoodsSimilarlyVideos
    getGoodsSimilarlyVideos,
  removeByIdVideoComment,
} from "@/api/video.js";
import ActivityPopup from '@/pages/ActivityPopup/ActivityPopup.vue'
import { mapState, mapMutations } from 'vuex'
import {setPopupRedisTime,getPopupAcitivty} from '@/api/popup.js'
import { changeCollect } from "@/api/collect.js";
import { saveShare, saveShareClickRecord } from "@/api/share.js";
import { silentLogin } from "@/api/connect.js";
@@ -283,13 +300,15 @@
import storage from "@/utils/storage.js";
import TopBar from "@/components/TopBar.vue";
import { nextTick } from "vue";
import {getVideoCover } from "@/api/common.js"
export default {
  components: {TopBar},
  components: {TopBar,ActivityPopup},
  computed: {
        hasPlayTime() {
          return this.sliderFormatTime(this.progress > 0 ? this.duration * this.progress / 100 : 0);
        }
        },
        // 错误:没有用 ... 展开,导致 activityPopup 是函数
        ...mapState(['activityPopup'])
  },
  data() {
    return {
@@ -382,6 +401,10 @@
    }
  },
  onShow() {
      this.openActivityPopup()
      if(!this.userId){
          this.getUserId()
      }
@@ -463,12 +486,104 @@
        shareUser: userInfo.id
    }
    saveShare(data)
      return {
          title: videoInfo.title,
          path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
      }
  },
    // getVideoCover(videoInfo.id).then(res =>{
    //     if(res.statusCode === 200){
    //         imageUrl = res.data.data
    //         console.log(imageUrl)
    //         return {
    //             title: videoInfo.title,
    //             path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
    //             imageUrl: imageUrl
    //         }
    //     }
    // })
    console.log(videoInfo)
    return {
        title: videoInfo.title,
        path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
        imageUrl: videoInfo.coverUrl
        }
    // 保存分享记录
    },
  methods: {
      async openActivityPopup() {
          await getPopupAcitivty().then(res =>{
              if(res.statusCode === 200){
                  let obj = res.data.data;
                  if(obj.enableStatus === 'ON'){
                      setPopupRedisTime().then(res =>{
                          if(res.statusCode === 200){
                              if(res.data.state){
                                  this.showActivityPopup({
                                  title: obj.activityName,
                                  desc: obj.activityDes,
                                  image: obj.activityCoverUrl,
                                  endTime:new Date(obj.endTime).getTime()
                                  })
                              }else{
                                  this.hideActivityPopup()
                              }
                          }
                      });
                  }
              }
          })
          },
      ...mapMutations(['showActivityPopup','hideActivityPopup']), // 引入Vuex的方法
       onClosePopup() {
            this.hideActivityPopup()
          },
      onJoinActivity() {
            // 处理参与活动逻辑
            console.log('全局:用户参与活动')
            this.hideActivityPopup()
          },
      replyClick(reply){
          if(this.userId === reply.userId){
              let that = this;
             uni.showModal({
                 title: '提示',
                 content: '你确定要删除吗',
                 success: function (res) {
                     if (res.confirm) {
                     console.log('确定');
                        //调用删除的逻辑
                         console.log(reply)
                        removeByIdVideoComment(reply.id).then(res =>{
                            const item = {
                                id:reply.videoId
                            }
                            that.commentQuery.pageNumber = 1;
                            //重新更新评论
                            that.showComments(item);
                        })
                     } else if (res.cancel) {
                     console.log('取消');
                     }
                 }
             });
          }
          console.log(reply)
      },
       // 截取视频当前帧
       captureVideoFrame(videoCtx) {
         return new Promise((resolve) => {
           videoCtx.requestFrame(() => {
             wx.canvasToTempFilePath({
               canvasId: 'shareCanvas',
               success: (res) => resolve(res),
               fail: () => resolve({ tempFilePath: '/assets/default-cover.jpg' })
             });
           });
         });
       },
      requestFullScreen(id,item){
          console.log(item)
         
@@ -813,6 +928,7 @@
                this.commentQuery.pageNumber++;
            })
        },
        // 显示评论弹窗
        async showComments(item) {
          this.commentForm.videoId = item.id;
store/index.js
@@ -15,8 +15,28 @@
    userInfo: storage.getUserInfo(),
    uuid: storage.getUuid(),
    token: "",
     // 活动弹窗状态
     activityPopup: {
          show: false,
          title: '',
          desc: '',
          image: '',
          endTime: 0
    }
  },
  mutations: {
      // 显示弹窗
          showActivityPopup(state, data) {
            state.activityPopup = {
              show: true,
              ...data // 合并传入的弹窗数据(标题、描述等)
            }
            console.log("Vuex 状态更新后:", state.activityPopup);
          },
          // 隐藏弹窗
          hideActivityPopup(state) {
            state.activityPopup.show = false
          },
    login(state, userInfo) {
      state.userInfo = userInfo || {};
      state.userName =