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

简介: 【易售小程序项目】商品详情展示+评论、评论展示、评论点赞+商品收藏【后端基于若依管理系统开发】

界面效果

【说明】

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

【商品详情】

【评论】

界面实现

工具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;
}

后端

收藏

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){
        // 需要将商品的收藏量+1
        productService.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 {
    @Override
    public 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 DUAL
    WHERE 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">
      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>

商品

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) {
        // 需要将商品的阅读量+1
        productService.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 DUAL
    WHERE NOT EXISTS (
            SELECT 1 FROM `product_read`
            WHERE `user_id` = #{productRead.userId} AND `product_id` = #{productRead.productId} limit 1
        );
</insert>
目录
相关文章
|
16天前
|
小程序 前端开发 API
小程序全栈开发中的多端适配与响应式布局
【4月更文挑战第12天】本文探讨了小程序全栈开发中的多端适配与响应式布局。多端适配涉及平台和设备适应,确保统一用户体验;响应式布局利用媒体查询和弹性布局维持不同设备的布局一致性。实践中,开发者可借助跨平台框架实现多平台开发,运用响应式布局技术适应不同设备。同时,注意兼容性、性能优化和用户体验,以提升小程序质量和用户体验。通过这些方法,开发者能更好地掌握小程序全栈开发。
|
16天前
|
小程序 前端开发 API
微信小程序全栈开发中的异常处理与日志记录
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的异常处理和日志记录,强调其对确保应用稳定性和用户体验的重要性。异常处理涵盖前端(网络、页面跳转、用户输入、逻辑异常)和后端(数据库、API、业务逻辑)方面;日志记录则关注关键操作和异常情况的追踪。实践中,前端可利用try-catch处理异常,后端借助日志框架记录异常,同时采用集中式日志管理工具提升分析效率。开发者应注意安全性、性能和团队协作,以优化异常处理与日志记录流程。
|
16天前
|
小程序 安全 数据安全/隐私保护
微信小程序全栈开发中的身份认证与授权机制
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的身份认证与授权机制。身份认证包括手机号验证、微信登录和第三方登录,而授权机制涉及角色权限控制、ACL和OAuth 2.0。实践中,开发者可利用微信登录获取用户信息,集成第三方登录,以及实施角色和ACL进行权限控制。注意点包括安全性、用户体验和合规性,以保障小程序的安全运行和良好体验。通过这些方法,开发者能有效掌握小程序全栈开发技术。
|
16天前
|
小程序 前端开发 安全
小程序全栈开发中的跨域问题及其解决方案
【4月更文挑战第12天】本文探讨了小程序全栈开发中的跨域问题及其解决方案。跨域问题源于浏览器安全策略,主要体现在前后端分离、第三方服务集成和数据共享上。为解决此问题,开发者可采用CORS、JSONP、代理服务器、数据交换格式和域名策略等方法。实践中需注意安全性、兼容性和性能。通过掌握这些解决方案,开发者能更好地处理小程序的跨域问题,提升用户体验。
|
16天前
|
小程序 前端开发 JavaScript
微信小程序全栈开发中的PWA技术应用
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中PWA技术的应用,PWA结合Web的开放性和原生应用的性能,提供离线访问、后台运行、桌面图标和原生体验。开发者可利用Service Worker实现离线访问,Worker处理后台运行,Web App Manifest添加桌面图标,CSS和JavaScript提升原生体验。实践中需注意兼容性、性能优化和用户体验。PWA技术能提升小程序的性能和用户体验,助力开发者打造优质小程序。
|
16天前
|
JavaScript 前端开发 小程序
微信小程序全栈开发之性能优化策略
【4月更文挑战第12天】本文探讨了微信小程序全栈开发的性能优化策略,包括前端的资源和渲染优化,如图片压缩、虚拟DOM、代码分割;后端的数据库和API优化,如索引创建、缓存使用、RESTful API设计;以及服务器的负载均衡和CDN加速。通过这些方法,开发者可提升小程序性能,优化用户体验,增强商业价值。
|
1月前
|
小程序 API
点餐小程序实战教程09-订单功能开发
点餐小程序实战教程09-订单功能开发
|
1月前
|
小程序 UED
人力资源小程序的设计与开发步骤
人力资源小程序的设计与开发步骤
21 1
|
16天前
|
小程序 前端开发 API
小程序全栈开发中的RESTful API设计
【4月更文挑战第12天】本文探讨了小程序全栈开发中的RESTful API设计,旨在帮助开发者理解和掌握相关技术。RESTful API基于REST架构风格,利用HTTP协议进行数据交互,遵循URI、客户端-服务器架构、无状态通信、标准HTTP方法和资源表述等原则。在小程序开发中,通过资源建模、设计API接口、定义资源表述及实现接口,实现前后端高效分离,提升开发效率和代码质量。小程序前端利用微信API与后端交互,确保数据流通。掌握这些实践将优化小程序全栈开发。
|
16天前
|
SQL 安全 小程序
探索微信小程序全栈开发的安全性问题
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的安全性问题,包括数据安全、接口安全、隐私保护和代码安全。为解决这些问题,建议采取数据加密、使用HTTPS协议、身份认证与授权、输入验证、安全审计及漏洞扫描以及安全培训等措施。通过这些方法,开发者可提升小程序安全性,保护用户隐私和数据。