【UniApp开发小程序】商品详情展示+评论、评论展示、评论点赞+商品收藏【后端基于若依管理系统开发】

文章目录

  • 界面效果
  • 界面实现
    • 工具js
    • 页面
      • 日期格式化
  • 后端
    • 收藏
      • Controller
      • Service
      • mapper
    • 评论
      • Controller
      • Service
      • Mapper
    • 商品
      • Controller
    • 阅读
      • Service

界面效果

【说明】

  • 界面中商品的图片来源于闲鱼,若侵权请联系删除

【商品详情】
在这里插入图片描述

【评论】
在这里插入图片描述

界面实现

工具js

该工具类的作用是,给定一个图片的url地址,计算出图片的高宽比,计算高宽比的作用是让图片可以按照正常比例显示

/*** 获取uuid*/
export default {/*** 获取高宽比 乘以 100%*/getAspectRatio(url) {uni.getImageInfo({src: url,success: function(res) {let aspectRatio = res.height * 100.0 / res.width;// console.log("aspectRatio:" + aspectRatio);return aspectRatio + "%";}});},
}
export default {/*** 日期格式化*/formatDateToString(date) {return new Date(date).toLocaleString();},
}

页面

<template><view class="container"><u-toast ref="uToast"></u-toast><view class="userItem"><view class="userProfile"><u--image :src="productVo.avatar" width="35" height="35" shape="circle"></u--image><view style="width: 10px;"></view><view><view class="nickname">{{productVo.nickname}}</view><view class="other">10分钟前来过 广东工业大学大学城校区</view></view></view><view class="follow" @click="follow" v-if="hadFollow==false"><view><u-icon name="plus" color="#ffffff" style="font-weight: bold;" size="15"></u-icon></view><view style="margin-left: 10rpx;font-size: 15px;">关 注</view></view><view class="followed" @click="cancelFollow" v-else><view style="font-size: 15px;color: #C2C2C2;">已 关 注</view></view></view><view class="productItem"><view class="top"><view class="price">¥<text class="number">{{productVo.price}}</text>/{{productVo.unit}}</view><view class="browseInformation">{{product.starNum}}人想要 | {{product.readNum}}个浏览</view></view><view class="productDetail">{{productVo.description}}</view><u--image :showLoading="true" v-for="(pic,index) in productVo.picList" :src="pic" width="100%":height="getAspectRatio(pic)" radius="10" mode="widthFix"></u--image></view><view class="commentView"><view style="color: #3D3D3D;">{{commentNum}}条评论</view><view v-for="(commentItem,index) in commentVoList"><view class="commentItem"><view style="display: flex;"><u--image :src="commentItem.userAvatar" width="30" height="30" shape="circle"></u--image><view style="width: 10px;"></view><view @click="clickShowBottomPopup(1, commentItem.id,commentItem.userNickName)"><view class="nickname">{{commentItem.userNickName}}</view><view class="content">{{commentItem.content}}</view><view class="dateAndPosition">{{formatDateToString(commentItem.createTime)}}</view></view></view><view style="display: inline-block;text-align: center;"><u-icon name="thumb-up" size="28" @click="likeComment(commentItem.id,commentItem)"v-if="commentItem.isLike==0"></u-icon><u-icon name="thumb-up-fill" color="#2B92FF" size="28"@click="cancelLikeComment(commentItem.id,commentItem)" v-else></u-icon><view style="font-size: 12px;color: #B9B9B9;">{{commentItem.likeNum}}</view></view></view><view class="sonCommentItem" v-for="(commentItem1,index1) in commentItem.children"><view style="display: flex;"><u--image :src="commentItem1.userAvatar" width="30" height="30" shape="circle"></u--image><view style="width: 10px;"></view><view @click="clickShowBottomPopup(1, commentItem1.id,commentItem1.userNickName)"><view class="nickname">{{commentItem1.userNickName}}</view><view class="content"><text style="font-size: 14px;">回复了<text style="color:#B9B9B9 ;">{{commentItem1.toUserNickName}}</text></text><text>{{ commentItem1.content }}</text></view><view class="dateAndPosition">{{formatDateToString(commentItem1.createTime)}}</view></view></view><view style="display: inline-block;text-align: center;"><u-icon name="thumb-up" size="28" @click="likeComment(commentItem1.id,commentItem1)"v-if="commentItem1.isLike==0"></u-icon><u-icon name="thumb-up-fill" color="#2B92FF" size="28"@click="cancelLikeComment(commentItem1.id, commentItem1)" v-else></u-icon><view style="font-size: 12px;color: #B9B9B9;">{{commentItem1.likeNum}}</view></view></view></view></view><view class="footer"><view><view class="item" @click="clickShowBottomPopup(0, productVo.id,)"><u-icon name="chat" size="28"></u-icon><view class="comment">评论</view></view><view class="item" @click="starProduct()" v-if="hadStar==false"><u-icon name="star" size="28"></u-icon><view class="comment">我想要</view></view><view class="item" @click="cancelStar()" v-if="hadStar==true"><u-icon name="star-fill" color="#2B92FF" size="28"></u-icon><view class="comment" style="color: #2B92FF">已收藏</view></view></view><view class="chat"><u-icon name="chat" color="#ffffff" size="18"></u-icon><view style="width: 5px;"></view>私 聊</view></view><!-- 底部弹出框:用于输入评论 --><!-- @close="this.showBottomPopup=false" 点击遮罩层关闭弹框  --><u-popup :show="showBottomPopup" mode="bottom" :round="10" @close="this.showBottomPopup=false"><view class="commentPopup"><u--textarea v-model="comment.content" :placeholder="commentPlaceHolder" autoHeight height="200"border="surround"></u--textarea><view class="commentButton" @click="commitComment()"><u-icon name="chat" color="#ffffff" size="18"></u-icon><view style="width: 5px;"></view>评 论</view></view></u-popup></view>
</template><script>import pictureApi from "@/utils/picture.js";import {addFollow,hadFollowSomeone,cancelFollowSomeone} from "@/api/market/follow.js";import {starProduct,cancelStar,hadStar} from "@/api/market/star.js";import {addComment,listCommentVoOfProduct} from "@/api/market/comment.js";import dateUtil from "@/utils/date.js";import {likeComment,cancelLikeComment} from "@/api/market/commentLike.js"import {getProduct} from "@/api/market/prodct.js"export default {data() {return {productVo: {},product: {},// 是否已经关注商品主人hadFollow: false,// 是否已经收藏商品hadStar: false,// 是否显示底部弹出框showBottomPopup: false,// 评论comment: {itemId: undefined,type: undefined,content: '',isTop: 0},// 存储商品对应的评论集合commentVoList: [],// 评论数量commentNum: undefined,commentPlaceHolder: "",}},methods: {/*** 获取高宽比 乘以 100%*/getAspectRatio(url) {// uni.getImageInfo({// 	src: url,// 	success: function(res) {// 		let aspectRatio = res.height * 100.0 / res.width;// 		// console.log("aspectRatio:" + aspectRatio);// 		return aspectRatio + "%";// 	}// });return pictureApi.getAspectRatio(url);},/*** 关注用户*/follow() {let data = {followedId: this.productVo.userId}addFollow(data).then(res => {this.hadFollow = true;this.$refs.uToast.show({type: 'success',message: "关注成功",duration: 300})}).catch(err => {this.$refs.uToast.show({type: 'error',message: err.msg,duration: 300})})},/*** 取消关注*/cancelFollow() {cancelFollowSomeone(this.productVo.userId).then(res => {this.hadFollow = false;this.$refs.uToast.show({type: 'success',message: "取消关注成功",duration: 300})})},/*** 查询是否已经关注了用户*/searchWhetherFollow() {hadFollowSomeone(this.productVo.userId).then(res => {// console.log("res:" + JSON.stringify(res));this.hadFollow = res.hadFollow;// console.log("this.hadFollow :" + this.hadFollow);})},/*** 收藏商品*/starProduct() {starProduct(this.productVo.id).then(res => {this.hadStar = true;this.getProduct();this.$refs.uToast.show({type: 'success',message: "收藏成功",duration: 300})})},/*** 取消收藏*/cancelStar() {cancelStar(this.productVo.id).then(res => {this.hadStar = false;this.getProduct();this.$refs.uToast.show({type: 'success',message: "取消收藏成功",duration: 300})})},/*** 点赞评论*/likeComment(commentId, comment) {// console.log("comment:" + JSON.stringify(comment))likeComment(commentId).then(res => {comment.isLike = 1;comment.likeNum += 1;this.$refs.uToast.show({type: 'success',message: "点赞成功",duration: 300})})},/*** 取消点赞评论*/cancelLikeComment(commentId, comment) {cancelLikeComment(commentId).then(res => {comment.isLike = 0;comment.likeNum -= 1;this.$refs.uToast.show({type: 'success',message: "取消点赞成功",duration: 300})})},/*** 查询是否已经关注了用户*/searchWhetherStar() {hadStar(this.productVo.id).then(res => {// console.log("res:" + JSON.stringify(res));this.hadStar = res.hadStar;// console.log("this.hadFollow :" + this.hadFollow);})},/*** 显示底部弹出框*/clickShowBottomPopup(type, itemId, username = undefined) {this.showBottomPopup = true;this.comment.type = type;this.comment.itemId = itemId;if (type == 0) {this.commentPlaceHolder = "想要了解更多信息,可以评论让商品主人看见哟";} else {this.commentPlaceHolder = "正在回复" + username + "";}},/*** 发表评论*/commitComment() {// console.log("发送评论,comment:" + JSON.stringify(this.comment))addComment(this.comment).then(res => {this.showBottomPopup = false;this.comment.content = '';this.listCommentVoOfProduct();this.$refs.uToast.show({type: 'success',message: "评论发送成功",duration: 300})})},/*** 获取商品对应的所有评论*/listCommentVoOfProduct() {listCommentVoOfProduct(this.productVo.id).then(res => {// console.log("listCommentVoOfProduct:" + JSON.stringify(res));this.commentVoList = res.tree;this.commentNum = res.commentNum;})},/*** 格式化日期* @param {Object} date*/formatDateToString(dateStr) {let date = new Date(dateStr);// 月份需要加一return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();},/*** 获取商品详细信息,同时增加阅读量*/getProduct() {getProduct(this.productVo.id).then(res => {console.log("product:" + JSON.stringify(res.data));this.product = res.data;})}},onLoad(e) {this.productVo = JSON.parse(decodeURIComponent(e.productVo));this.searchWhetherFollow();this.searchWhetherStar();this.listCommentVoOfProduct();this.getProduct();// console.log("productVo:" + JSON.stringify(productVo));}}
</script><style lang="scss">.container {// padding: 20rpx;background: #F7F7F7;.userItem {display: flex;align-items: center;justify-content: space-between;background: #ffffff;padding: 20rpx;.userProfile {display: flex;.nickname {color: #202020;font-weight: bold;font-size: 14px;}.other {color: #A6A4A5;font-size: 11px;}}.follow {display: flex;align-items: center;font-weight: bold;color: #ffffff;background: #2B92FF;border-radius: 20px;padding: 4px 8px;}.followed {background: #F6F6F6;border-radius: 20px;padding: 4px 8px;}}.productItem {background: #ffffff;padding: 20rpx;.top {display: flex;align-items: center;justify-content: space-between;.price {color: #F84442;font-weight: bold;.number {font-size: 30px;}}.browseInformation {color: #A6A4A5;font-size: 14px;}}.productDetail {margin-top: 20rpx;margin-bottom: 10rpx;color: #4C4C4C;font-size: 15px;line-height: 30px;font-weight: bold;}}.commentView {margin-top: 10px;// 用来预留展示 footer 的高度,不然footer会挡住评论margin-bottom: calc(60px + 10rpx);background: #ffffff;padding: 30rpx 30rpx;.nickname {font-size: 14px;color: #B9B9B9;}.content {margin: 5px;// 解决英文字符串、数字不换行的问题word-break: break-all;word-wrap: break-word;}.dateAndPosition {font-size: 11px;color: #B9B9B9;}.commentItem {display: flex;margin: 10px;justify-content: space-between;}.sonCommentItem {display: flex;margin: 10px 10px 10px 50px;justify-content: space-between;}}.footer {padding: 20rpx;position: fixed;// right: 20rpx;bottom: 0rpx;background: #ffffff;height: 60px;width: 710rpx;padding-top: 2px;display: flex;align-items: center;justify-content: space-between;.item {display: inline-block;text-align: center;margin-right: 10px;.comment {font-size: 10px;}}.chat {display: flex;align-items: center;background-color: #2B92FF;border-radius: 20px;padding: 7px;color: #ffffff;// margin-right: 20px;font-size: 12px;}}.commentPopup {display: flex;padding: 10px;min-height: 200rpx;.commentButton {background-color: #2B92FF;border-radius: 5px;padding: 7px;color: #ffffff;font-size: 12px;height: 20px;display: flex;align-items: center;}}}
</style>

日期格式化

有时候后端传递过来的日期格式直接在前端页面中展示不太美观或简洁,那就可以自己写一个日期格式化方法,将日期转化为我们需要的格式来显示

/*** 格式化日期* @param {Object} date*/
formatDateToString(dateStr) {let date = new Date(dateStr);// 月份需要加一return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
},

后端

收藏

Controller

为了便于商品数据的查询,我在数据库设计的时候给商品表增加了收藏数的冗余字段,因此每次收藏商品或者取消商品的收藏的同时,需要更新商品表的收藏数

在这里插入图片描述

/*** 收藏商品*/
@PreAuthorize("@ss.hasPermi('market:star:star')")
@GetMapping("/starProduct/{productId}")
public AjaxResult starProduct(@PathVariable("productId") Long productId) {Star star = new Star();star.setUserId(getLoginUser().getUserId());star.setProductId(productId);boolean isStar = starService.addStar(star);if (isStar){// 需要将商品的收藏量+1productService.starNumPlusOne(productId);}return AjaxResult.success();
}

Service

package com.shm.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.domain.entity.Star;
import com.shm.mapper.StarMapper;
import com.shm.service.IStarService;
import org.springframework.stereotype.Service;/**
* @author dam
* @description 针对表【collection(收藏表)】的数据库操作Service实现
* @createDate 2023-08-09 19:41:23
*/
@Service
public class IStarServiceImpl extends ServiceImpl<StarMapper, Star>implements IStarService {@Overridepublic boolean addStar(Star star) {return baseMapper.addStar(star);}
}

mapper

public interface StarMapper extends BaseMapper<Star> {boolean addStar(@Param("star") Star star);
}

将商品添加收藏的时候,需要先判断同样的收藏数据不存在于数据库中才执行插入操作,否则如果用户网络卡顿并多次发送收藏请求,数据库会出现冗余的脏数据

<insert id="addStar">INSERT INTO `star` (`user_id`, `product_id`)SELECT #{star.userId},#{star.productId} FROM DUALWHERE NOT EXISTS (SELECT 1 FROM `star`WHERE `user_id` = #{star.productId} AND `product_id` = #{star.productId} limit 1);
</insert>

评论

Controller

/*** 获取商品对应的所有评论** @param productId* @return*/
@PreAuthorize("@ss.hasPermi('market:comment:list')")
@GetMapping("/listCommentVoOfProduct/{productId}")
public AjaxResult listCommentVoOfProduct(@PathVariable("productId") Long productId) {// 查询出商品对应的所有评论数据List<CommentVo> commentVoList = commentService.listCommentVoOfProduct(productId, getLoginUser().getUserId());int commentNum = commentVoList.size();// 将评论数据封装成树形结构List<CommentVo> tree = commentService.buildTree(commentVoList);return AjaxResult.success().put("tree", tree).put("commentNum", commentNum);
}

Service

需要注意的是,这里的树形结构只有两层数据(针对商品的评论为一层,针对评论的所有评论为一层),因为小程序不方便显示太多层数据,否则宽度会非常大,用户需要反复滑动来查看完整的评论
在这里插入图片描述

在这里插入图片描述

@Override
public List<CommentVo> listCommentVoOfProduct(Long productId, Long userId) {return commentMapper.listCommentVoOfProduct(productId, userId);
}/*** 将评论数据封装成树形结构** @param commentVoList* @return*/
@Override
public List<CommentVo> buildTree(List<CommentVo> commentVoList) {// 将所有父级评论过滤出来List<CommentVo> fatherList = commentVoList.stream().filter((item) -> {return item.getType() == 0;}).collect(Collectors.toList());commentVoList.removeAll(fatherList);// 为所有父级评论寻找孩子for (CommentVo father : fatherList) {father.setChildren(new ArrayList<>());this.searchSon(father.getId(), father.getUserNickName(), father.getChildren(), commentVoList);}return fatherList;
}/*** 寻找孩子** @param fatherId* @param children* @param commentVoList*/
private void searchSon(Long fatherId, String fatherNickName, List<CommentVo> children, List<CommentVo> commentVoList) {for (CommentVo commentVo : commentVoList) {if (commentVo.getItemId().equals(fatherId)) {commentVo.setToUserNickName(fatherNickName);children.add(commentVo);this.searchSon(commentVo.getId(), commentVo.getUserNickName(), children, commentVoList);}}
}

Mapper

这段sql非常复杂,一次性将评论的主人昵称、头像、评论的点赞数量查出来了,同时还使用递归查询来不断查询出评论的子评论。我目前不能保证这段sql的效率,只是实现了功能,后面如果性能不足,我再想办法优化

<select id="listCommentVoOfProduct" resultType="com.ruoyi.common.core.domain.vo.CommentVo">SELECTct.id,ct.user_id,ct.item_id,ct.type,ct.content,ct.create_time,u.nick_name AS userNickName,u.avatar AS userAvatar,CASEWHEN cl.user_id IS NULL THEN0 ELSE 1END AS isLike,ct.LEVEL,COALESCE ( likeNum, 0 ) AS likeNumFROM(WITH RECURSIVE comment_tree AS (SELECTid,user_id,item_id,type,content,create_time,0 AS LEVELFROMCOMMENTWHEREitem_id = #{productId} and type=0UNION ALLSELECTc.id,c.user_id,c.item_id,c.type,c.content,c.create_time,ct.LEVEL + 1 AS LEVELFROMCOMMENT cINNER JOIN comment_tree ct ON c.item_id = ct.idWHEREc.type = 1) SELECT*FROMcomment_tree) ctLEFT JOIN ( SELECT comment_id, COUNT(*) AS likeNum FROM comment_like WHERE is_deleted = 0 GROUP BY comment_id ) pc ON ct.id = pc.comment_idLEFT JOIN sys_user AS u ON ct.user_id = u.user_idLEFT JOIN comment_like cl ON ct.id = cl.comment_idAND cl.user_id = #{userId} and cl.is_deleted =0</select>

商品

Controller

/*** 获取商品详细信息*/
@PreAuthorize("@ss.hasPermi('market:product:query')")
@GetMapping(value = "/{id}")
@Transactional // 同时处理多个表,添加事务
public AjaxResult getInfo(@PathVariable("id") Long id) {// 首先判断用户有没有阅读该商品boolean isAdd = productReadService.addRead(new ProductRead(getLoginUser().getUserId(), id));if (isAdd) {// 需要将商品的阅读量+1productService.readNumPlusOne(id);}return success(productService.getById(id));
}

阅读

Service

<insert id="addRead">INSERT INTO `product_read` (`user_id`, `product_id`)SELECT #{productRead.userId},#{productRead.productId} FROM DUALWHERE NOT EXISTS (SELECT 1 FROM `product_read`WHERE `user_id` = #{productRead.userId} AND `product_id` = #{productRead.productId} limit 1);</insert>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/76310.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

扩散模型实战(四):从零构建扩散模型

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 本文以MNIST数据集为例&#xff0c;从…

《HeadFirst设计模式(第二版)》第十一章代码——代理模式

代码文件目录&#xff1a; RMI&#xff1a; MyRemote package Chapter11_ProxyPattern.RMI;import java.rmi.Remote; import java.rmi.RemoteException;public interface MyRemote extends Remote {public String sayHello() throws RemoteException; }MyRemoteClient packa…

使用swoole实现实时消息推送给客户端

一. 测试服务端 //测试服务端public function testServer(){$server new Server(192.168.0.144, 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);$server->on(request, function ($request, $response) {$response->header(Content-Type, text/plain);$response->end("He…

宝塔部署Java+Vue前后端分离项目经验总结

前言 之前部署服务器都是在Linux环境下自己一点一点安装软件&#xff0c;听说用宝塔傻瓜式部署更快&#xff0c;这次浅浅尝试了一把。 确实简单&#xff01; 1、 买服务器 咋买服务器略&#xff0c;记得服务器装系统就装 Cent OS 7系列即可&#xff0c;我装的7.6。 2、创建…

SpringBoot案例 调用第三方接口传输数据

一、前言 最近再写调用三方接口传输数据的项目&#xff0c;这篇博客记录项目完成的过程&#xff0c;方便后续再碰到类似的项目可以快速上手 项目结构&#xff1a; 二、编码 这里主要介绍HttpClient发送POST请求工具类和定时器的使用&#xff0c;mvc三层架构编码不做探究 pom.x…

每日刷题(翻转+二分+BFS)

食用指南&#xff1a;本文为作者刷题中认为有必要记录的题目 ♈️今日夜电波&#xff1a;凄美地—郭顶 1:10 ━━━━━━️&#x1f49f;──────── 4:10 &#x1f504; ◀️ ⏸ ▶️ ☰…

MyBatis快速入门以及环境搭建和CRUD的实现

目录 前言 一、MyBatis简介 1.MyBatis是什么 2.MyBatis的特点 3.mybatis的作用 4.MyBatis的应用场景 5.MyBatis优缺点 二、相关概念 1.ORM概述 2.常见的ORM框架 3.什么是持久层框架 三、MyBatis的工作原理 1.框架交互 2.工作原理 ​编辑 四、MyBatis环境搭建 1…

Docker容器:docker镜像的创建及dockerfile

Docker容器&#xff1a;docker镜像的创建及dockerfile案例 一.docker镜像的三种创建方法 创建镜像有三种方法&#xff1a;基于现有镜像创建、基于本地模板创建及基于dockerfile创建 1.基于现有镜像创建 1.1 启动镜像 #首先启动一个镜像&#xff0c;在容器里做修改 docker …

记录win 7旗舰版 “VMware Alias Manager and Ticket Service‘(VGAuhService)启动失败。

记录win 7旗舰版 "VMware Alias Manager and Ticket Service’(VGAuhService)启动失败。 描述如图 https://learn.microsoft.com/zh-CN/cpp/windows/latest-supported-vc-redist?viewmsvc-140#visual-studio-2015-2017-2019-and-2022 安装对应版本的VC 库就可以解决问…

机器人的运动范围

声明 该系列文章仅仅展示个人的解题思路和分析过程&#xff0c;并非一定是优质题解&#xff0c;重要的是通过分析和解决问题能让我们逐渐熟练和成长&#xff0c;从新手到大佬离不开一个磨练的过程&#xff0c;加油&#xff01; 原题链接 机器人的运动范围https://leetcode.c…

Python 学习笔记——代码基础

目录 Python基础知识 变量 赋值 数据类型 print用法 print格式化输出 运算符 if-else 数据结构 元组 in运算符 列表 切片 [ : ] 追加 append() 插入 insert&#xff08;&#xff09; 删除 pop() 字典 循环 for循环 for循环应用——遍历 for循环应用——累加…

【BASH】回顾与知识点梳理(二十九)

【BASH】回顾与知识点梳理 二十九 二十九. 进程和工作管理29.1 什么是进程 (process)进程与程序 (process & program)子进程与父进程&#xff1a;fork and exec&#xff1a;进程呼叫的流程系统或网络服务&#xff1a;常驻在内存的进程 29.2 Linux 的多人多任务环境多人环境…