記事ディレクトリ
インターフェース効果
【イラスト】
- インターフェース内の製品の写真はXianyuからのものですが、侵害がある場合は、削除するために私達に連絡してください。
【製品詳細】
【コメント】
インターフェースの実装
ツール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();
},
英語自動折り返し表示
.content {
margin: 5px;
// 解决英文字符串、数字不换行的问题
word-break: break-all;
word-wrap: break-word;
}
後部
集める
コントローラ
商品データのクエリを容易にするために、データベース設計時に商品テーブルに回収番号の冗長フィールドを追加したため、商品の回収またはキャンセルのたびに商品テーブルの回収番号を更新する必要があります。
/**
* 收藏商品
*/
@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){
// 需要将商品的收藏量+1
productService.starNumPlusOne(productId);
}
return AjaxResult.success();
}
サービス
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 {
@Override
public boolean addStar(Star star) {
return baseMapper.addStar(star);
}
}
マッパー
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 DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `star`
WHERE `user_id` = #{star.productId} AND `product_id` = #{star.productId} limit 1
);
</insert>
コメント
コントローラ
/**
* 获取商品对应的所有评论
*
* @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);
}
サービス
ここでのツリー構造には 2 つのデータ層しかないことに注意してください (製品レビュー用の 1 層とすべてのコメント用の 1 層)。これは、ミニ プログラムでデータの層が多すぎると表示するのが不便であるためです。そうしないと、幅が狭くなります。非常に大きいです。完全なレビューを表示するには、ユーザーは繰り返しスワイプする必要があります
@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);
}
}
}
マッパー
この SQL は非常に複雑で、コメント所有者のニックネーム、アバター、コメントの「いいね!」の数を一度に調べることができ、また、再帰クエリを使用してコメントのサブコメントを継続的にクエリします。この SQL の効率は現時点では保証できませんが、関数を実装しただけなので、後でパフォーマンスが不十分な場合は最適化する方法を見つけます。
<select id="listCommentVoOfProduct" resultType="com.ruoyi.common.core.domain.vo.CommentVo">
SELECT
ct.id,
ct.user_id,
ct.item_id,
ct.type,
ct.content,
ct.create_time,
u.nick_name AS userNickName,
u.avatar AS userAvatar,
CASE
WHEN cl.user_id IS NULL THEN
0 ELSE 1
END AS isLike,
ct.LEVEL,
COALESCE ( likeNum, 0 ) AS likeNum
FROM
(
WITH RECURSIVE comment_tree AS (
SELECT
id,
user_id,
item_id,
type,
content,
create_time,
0 AS LEVEL
FROM
COMMENT
WHERE
item_id = #{productId} and type=0
UNION ALL
SELECT
c.id,
c.user_id,
c.item_id,
c.type,
c.content,
c.create_time,
ct.LEVEL + 1 AS LEVEL
FROM
COMMENT c
INNER JOIN comment_tree ct ON c.item_id = ct.id
WHERE
c.type = 1
) SELECT
*
FROM
comment_tree
) ct
LEFT JOIN ( SELECT comment_id, COUNT(*) AS likeNum FROM comment_like WHERE is_deleted = 0 GROUP BY comment_id ) pc ON ct.id = pc.comment_id
LEFT JOIN sys_user AS u ON ct.user_id = u.user_id
LEFT JOIN comment_like cl ON ct.id = cl.comment_id
AND cl.user_id = #{userId} and cl.is_deleted =0
</select>
商品
コントローラ
/**
* 获取商品详细信息
*/
@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) {
// 需要将商品的阅读量+1
productService.readNumPlusOne(id);
}
return success(productService.getById(id));
}
読む
サービス
<insert id="addRead">
INSERT INTO `product_read` (`user_id`, `product_id`)
SELECT #{productRead.userId},#{productRead.productId} FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `product_read`
WHERE `user_id` = #{productRead.userId} AND `product_id` = #{productRead.productId} limit 1
);
</insert>
同じプロジェクトに関する他の記事
このプロジェクトのその他の記事は、 【簡単ミニプログラムプロジェクト】プロジェクト紹介、ミニプログラムページ表示、連載記事集をご覧ください。