SpringBoot+Vue3 完成小红书项目

简介

该项目采用微服务架构,实现了前后端分离的系统设计。在前端,我们选择了 Vue3 配合 TypeScript 和 ElementUi 框架,以提升开发效率和用户体验。而在后端,则是运用 SpringBoot 和 Mybatis-plus 进行开发,保证了系统的高效和稳定。此外,我们引入了 ElasticSearch 作为全文检索服务,以加快搜索速度和提高检索效率。同时,通过 WebSocket 技术实现了实时聊天和消息推送功能,增强了用户的互动体验。

代码下载

https://url21.ctfile.com/f/15432821-1020751544-c0e7e4?p=8418
(访问密码: 8418)

项目目录

  • yanhuo-web 前段页面
  • yanhuo-auth 认证服务
  • yanhuo-common 公共模块,存放一些工具类或公用类
  • yanhuo-platform 烟火 app 主要功能模块
  • yanhuo-im 聊天模块
  • yanhuo-search 搜索模块
  • yanhuo-util 第三方服务模块,邮箱短信,oss 对象存储服务
  • yanhuo-xo 对象存放模块

源码讲解

Controller 源码示例

首先来看 AlbumController 中的代码,主要讲解八个容易误解的方面。

  1. @RestController:这是一个组合注解,它表示这个类是一个控制器,并且其中的所有方法都会返回数据而不是视图。

  2. @RequestMapping(“/album”):这个注解用于将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上。在这里,它将所有的请求映射到以/album 为前缀的 URL。

  3. @Autowired:这个注解用于自动注入 Spring 容器中的 bean。在这里,它注入了一个 AlbumService 的实例,这个实例提供了专辑的增删改查操作。

  4. getAlbumPageByUserId 方法:这个方法用于根据用户 ID 获取专辑列表,并支持分页。它接受三个参数:当前页(currentPage),分页数(pageSize)和用户 ID(userId)。它调用 albumService 的 getAlbumPageByUserId 方法来获取数据,并返回一个 Result<?>对象。

  5. saveAlbumByDTO 方法:这个方法用于保存专辑。它接受一个 AlbumDTO 对象作为参数,并使用 ValidatorUtils.validateEntity 方法进行数据校验。然后,它调用 albumService 的 saveAlbumByDTO 方法来保存专辑,并返回一个 Result<?>对象。

  6. getAlbumById 方法:这个方法用于根据专辑 ID 获取专辑。它接受一个 albumId 作为参数,并调用 albumService 的 getAlbumById 方法来获取数据,并返回一个 Result<?>对象。

  7. deleteAlbumById 方法:这个方法用于根据专辑 ID 删除专辑。它接受一个 albumId 作为参数,并调用 albumService 的 deleteAlbumById 方法来删除数据,并返回一个 Result<?>对象。

  8. updateAlbumByDTO 方法:这个方法用于更新专辑。它接受一个 AlbumDTO 对象作为参数,并使用 ValidatorUtils.validateEntity 方法进行数据校验。然后,它调用 albumService 的 updateAlbumByDTO 方法来更新专辑,并返回一个 Result<?>对象。

package com.yanhuo.platform.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yanhuo.common.result.Result;
import com.yanhuo.common.validator.ValidatorUtils;
import com.yanhuo.common.validator.group.AddGroup;
import com.yanhuo.common.validator.group.UpdateGroup;
import com.yanhuo.platform.service.AlbumService;
import com.yanhuo.xo.dto.AlbumDTO;
import com.yanhuo.xo.entity.Album;
import com.yanhuo.xo.vo.AlbumVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/album")
@RestController
public class AlbumController {

    @Autowired
    AlbumService albumService;

    /**
     * 根据用户id获取专辑
     * @param currentPage 当前页
     * @param pageSize 分页数
     * @param userId 用户id
     * @return 专辑数
     */
    @RequestMapping("getAlbumPageByUserId/{currentPage}/{pageSize}")
    public Result<?> getAlbumPageByUserId(@PathVariable long currentPage, @PathVariable long pageSize,String userId){
        Page<Album> page =  albumService.getAlbumPageByUserId(currentPage,pageSize,userId);
        return Result.ok(page);
    }

    /**
     * 保存专辑
     * @param albumDTO 专辑实体
     * @return success
     */
    @RequestMapping("saveAlbumByDTO")
    public Result<?> saveAlbumByDTO(@RequestBody AlbumDTO albumDTO) {
        ValidatorUtils.validateEntity(albumDTO, AddGroup.class);
        albumService.saveAlbumByDTO(albumDTO);
        return Result.ok();
    }


    /**
     * 根据专辑id获取专辑
     * @param albumId 专辑id
     * @return 专辑实体
     */
    @RequestMapping("getAlbumById")
    public Result<?> getAlbumById(String albumId) {
        AlbumVo albumVo = albumService.getAlbumById(albumId);
        return Result.ok(albumVo);
    }

    /**
     * 根据专辑id删除专辑
     * @param albumId 专辑id
     * @return success
     */
    @RequestMapping("deleteAlbumById")
    public Result<?> deleteAlbumById(String albumId) {
        albumService.deleteAlbumById(albumId);
        return Result.ok();
    }

    /**
     * 更新专辑
     * @param albumDTO 专辑实体
     * @return success
     */
    @RequestMapping("updateAlbumByDTO")
    public Result<?> updateAlbumByDTO(@RequestBody AlbumDTO albumDTO) {
        ValidatorUtils.validateEntity(albumDTO, UpdateGroup.class);
        albumService.updateAlbumByDTO(albumDTO);
        return Result.ok();
    }
}

Service 源码示例

下面代码是一个服务实现类,用于处理与笔记相关的业务逻辑。这个类使用了 MyBatis-Plus 框架来简化数据库操作,并使用了 Hutool 工具类库来处理 JSON 数据。下面是这段代码的详细解释:

  1. NoteServiceImpl 类继承了 ServiceImpl<NoteDao, Note>,这意味着它继承了 MyBatis-Plus 提供的基本 CRUD 操作。NoteDao 是一个接口,用于定义与笔记相关的数据库操作方法。Note 是一个实体类,用于映射数据库中的笔记表。

  2. 类中注入了多个服务,包括用户服务、标签服务、分类服务、ES 客户端、关注服务、点赞或收藏服务以及 OSS 客户端。这些服务将在后面的方法中被使用。

  3. getNoteById 方法用于根据笔记 ID 获取笔记详情。该方法首先通过 getById 方法从数据库中获取笔记对象,然后将浏览次数加 1 并更新到数据库。接下来,通过用户 ID 获取用户对象,并将用户名、头像和更新时间设置到 NoteVo 对象中。然后,检查当前用户是否关注了该笔记的作者,并设置 NoteVo 对象的 isFollow 属性。接着,检查当前用户是否点赞或收藏了该笔记,并设置 NoteVo 对象的 isLikeisCollection 属性。最后,通过标签笔记关系服务获取该笔记的所有标签,并设置到 NoteVo 对象中。

  4. saveNoteByDTO 方法用于保存新的笔记。该方法首先从当前用户上下文中获取用户 ID,然后将传入的 JSON 字符串转换为 NoteDTO 对象,并将其转换为 Note 对象。然后,将用户 ID 设置到 Note 对象中,并保存到数据库。接下来,更新用户的动态数量。然后,处理标签关系,将标签 ID 和笔记 ID 保存到标签笔记关系表中。然后,通过 OSS 客户端上传图片,并将图片 URL 保存到 Note 对象中。最后,将 Note 对象更新到数据库,并将笔记信息添加到 ES 搜索引擎中。

  5. deleteNoteByIdsupdateNoteByDTOgetHotPage 方法目前没有实现具体的业务逻辑。这段代码的主要功能是处理与笔记相关的业务逻辑,包括获取笔记详情、保存新的笔记、更新笔记等。代码中使用了 MyBatis-Plus 框架来简化数据库操作,并使用了 Hutool 工具类库来处理 JSON 数据。此外,代码中还使用了 ES 搜索引擎和 OSS 对象存储服务来处理笔记的搜索和图片存储。

package com.yanhuo.platform.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yanhuo.common.auth.AuthContextHolder;
import com.yanhuo.common.exception.YanHuoException;
import com.yanhuo.common.result.Result;
import com.yanhuo.common.utils.ConvertUtils;
import com.yanhuo.platform.client.EsClient;
import com.yanhuo.platform.client.OssClient;
import com.yanhuo.platform.service.*;
import com.yanhuo.xo.dao.NoteDao;
import com.yanhuo.xo.dto.NoteDTO;
import com.yanhuo.xo.entity.*;
import com.yanhuo.xo.vo.NoteSearchVo;
import com.yanhuo.xo.vo.NoteVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class NoteServiceImpl extends ServiceImpl<NoteDao, Note> implements NoteService {
    @Autowired
    UserService userService;

    @Autowired
    TagNoteRelationService tagNoteRelationService;

    @Autowired
    TagService tagService;

    @Autowired
    CategoryService categoryService;

    @Autowired
    EsClient esClient;

    @Autowired
    FollowerService followerService;

    @Autowired
    LikeOrCollectionService likeOrCollectionService;

    @Autowired
    OssClient ossClient;


    @Override
    public NoteVo getNoteById(String noteId) {
        Note note = this.getById(noteId);
        note.setViewCount(note.getViewCount() + 1);
        User user = userService.getById(note.getUid());
        NoteVo noteVo = ConvertUtils.sourceToTarget(note, NoteVo.class);
        noteVo.setUsername(user.getUsername())
                .setAvatar(user.getAvatar())
                .setTime(note.getUpdateDate().getTime());

        boolean follow = followerService.isFollow(user.getId());
        noteVo.setIsFollow(follow);

        String currentUid = AuthContextHolder.getUserId();
        List<LikeOrCollection> likeOrCollectionList = likeOrCollectionService.list(new QueryWrapper<LikeOrCollection>().eq("like_or_collection_id", noteId).eq("uid", currentUid));
        if(!likeOrCollectionList.isEmpty()) {
            Set<Integer> types = likeOrCollectionList.stream().map(LikeOrCollection::getType).collect(Collectors.toSet());
            noteVo.setIsLike(types.contains(1));
            noteVo.setIsCollection(types.contains(3));
        }

        //得到标签
        List<TagNoteRelation> tagNoteRelationList = tagNoteRelationService.list(new QueryWrapper<TagNoteRelation>().eq("nid", noteId));
        List<String> tids = tagNoteRelationList.stream().map(TagNoteRelation::getTid).collect(Collectors.toList());

        if (!tids.isEmpty()) {
            List<Tag> tagList = tagService.listByIds(tids);
            noteVo.setTagList(tagList);
        }

        this.updateById(note);
        return noteVo;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public String saveNoteByDTO(String noteData, MultipartFile[] files) {
        String currentUid = AuthContextHolder.getUserId();
        NoteDTO noteDTO = JSONUtil.toBean(noteData, NoteDTO.class);
        Note note =ConvertUtils.sourceToTarget(noteDTO, Note.class);
        note.setUid(currentUid);
        boolean  save = this.save(note);
        if(!save){
            return null;
        }
        // TODO 需要往专辑中添加

        User user = userService.getById(currentUid);
        user.setTrendCount(user.getTrendCount() + 1);
        userService.updateById(user);

        List<String> tids = noteDTO.getTagList();
        List<TagNoteRelation> tagNoteRelationList = new ArrayList<>();

        String tags="";
        if(!tids.isEmpty()){
            for (String tid : tids) {
                TagNoteRelation tagNoteRelation = new TagNoteRelation();
                tagNoteRelation.setTid(tid);
                tagNoteRelation.setNid(note.getId());
                tagNoteRelationList.add(tagNoteRelation);
            }
            tagNoteRelationService.saveBatch(tagNoteRelationList);
            tags  = tagService.listByIds(tids).stream().map(Tag::getTitle).collect(Collectors.joining(","));
        }
        Category category = categoryService.getById(note.getCid());
        Category parentCategory = categoryService.getById(note.getCpid());

        List<String> dataList;
        try {
            Result<List<String>> result = ossClient.saveBatch(files, 1);
            dataList = result.getData();
        }catch (Exception e){
           throw new YanHuoException("添加图片失败");
        }
        String[] urlArr = dataList.toArray(new String[dataList.size()]);
        String urls = JSONUtil.toJsonStr(urlArr);
        note.setUrls(urls);
        note.setNoteCover(urlArr[0]);
        this.updateById(note);

        // 往es中添加数据
        NoteSearchVo noteSearchVo = ConvertUtils.sourceToTarget(note, NoteSearchVo.class);
        noteSearchVo.setUsername(user.getUsername())
                .setAvatar(user.getAvatar())
                .setLikeCount(0L)
                .setCategoryName(category.getTitle())
                .setCategoryParentName(parentCategory.getTitle())
                .setTags(tags)
                .setTime(note.getUpdateDate().getTime());
        esClient.addNote(noteSearchVo);
        return note.getId();
    }

    @Override
    public void deleteNoteByIds(List<String> noteIds) {

    }

    @Override
    public String updateNoteByDTO(NoteDTO noteDTO) {
        return null;
    }

    @Override
    public Page<NoteVo> getHotPage(long currentPage, long pageSize) {
        return null;
    }
}

Vue3 源码示例

这段代码是一个Vue3组件的模板和脚本部分,用于展示和加载关注者的动态(trends)。下面是对代码的详细解释:

模板部分(Template)
  1. <template> 标签内定义了组件的结构。
  2. 使用了Vue的v-infinite-scroll指令来实现无限滚动加载更多数据。
  3. 使用v-for指令循环渲染trendData数组中的每个动态项。
  4. 每个动态项包含用户头像、用户信息、动态内容、图片和交互按钮(点赞、评论等)。
  5. 使用v-ifv-else指令来条件渲染加载中的图片和已加载的图片。
  6. 事件绑定(@click)用于处理用户交互,如跳转到用户页面、点赞、查看动态等。
脚本部分(Script)
  1. 导入了必要的Vue组件和图标。
  2. 使用Vue 3的Composition API(<script setup>)定义组件的逻辑。
  3. 响应式变量,如currentPagepageSizetrendData等,用于控制分页和存储数据。
  4. handleLoad函数,用于处理图片加载完成的事件。
  5. toUser函数,用于导航到用户页面。
  6. getFollowTrends函数,用于获取关注者的动态数据。
  7. loadMoreData函数,用于加载更多动态数据。
  8. toMain函数,用于显示动态的详细内容。
  9. close函数,用于关闭动态详细内容的显示。
  10. refresh函数,用于刷新动态数据。
  11. like函数,用于处理点赞和取消点赞的操作。
    12.initData函数,用于初始化动态数据。
  12. 在组件挂载时调用initData函数。
样式部分(Style)
  1. 使用了Less预处理器编写样式。
  2. 定义了.container.trend-container.trend-item等类,用于设置组件的布局和样式。
  3. 使用了flex布局来排列用户头像、信息和图片。
  4. 使用了scoped属性,确保样式只应用于当前组件。
<template>
  <div class="container" v-infinite-scroll="loadMoreData">
    <ul class="trend-container">
      <li class="trend-item" v-for="(item, index) in trendData" :key="index">
        <a class="user-avatar">
          <img class="avatar-item" :src="item.avatar" @click="toUser(item.uid)" />
        </a>
        <div class="main">
          <div class="info">
            <div class="user-info">
              <a class>{{ item.username }}</a>
            </div>
            <div class="interaction-hint">
              <span>{{ item.time }}</span>
            </div>
            <div class="interaction-content" @click="toMain(item.nid)">
              {{ item.content }}
            </div>
            <div class="interaction-imgs" @click="toMain(item.nid)">
              <div class="details-box" v-for="(url, index) in item.imgUrls" :key="index">
                <el-image
                  v-if="!item.isLoading"
                  :src="url"
                  @load="handleLoad(item)"
                  style="height: 230px; width: 100%"
                >
                </el-image>
                <el-image
                  v-else
                  :src="url"
                  class="note-img animate__animated animate__fadeIn animate__delay-0.5s"
                  fit="cover"
                ></el-image>
              </div>
            </div>
            <div class="interaction-footer">
              <div class="icon-item">
                <i
                  class="iconfont icon-follow-fill"
                  style="width: 1em; height: 1em"
                  @click="like(item.nid, item.uid, index, -1)"
                  v-if="item.isLike"
                ></i>
                <i
                  class="iconfont icon-follow"
                  style="width: 1em; height: 1em"
                  @click="like(item.nid, item.uid, index, 1)"
                  v-else
                ></i
                ><span class="count">{{ item.likeCount }}</span>
              </div>
              <div class="icon-item">
                <ChatRound style="width: 0.9em; height: 0.9em" /><span class="count">{{
                  item.commentCount
                }}</span>
              </div>
              <div class="icon-item"><More style="width: 1em; height: 1em" /></div>
            </div>
          </div>
        </div>
      </li>
    </ul>
    <div class="feeds-loading">
      <Refresh style="width: 1.2em; height: 1.2em" color="rgba(51, 51, 51, 0.8)" />
    </div>
    <FloatingBtn @click-refresh="refresh"></FloatingBtn>
    <Main
      v-show="mainShow"
      :nid="nid"
      class="animate__animated animate__zoomIn animate__delay-0.5s"
      @click-main="close"
    ></Main>
  </div>
</template>
<script lang="ts" setup>
import { ChatRound, More, Refresh } from "@element-plus/icons-vue";
import { ref } from "vue";
import { getFollowTrendPage } from "@/api/follower";
import { formateTime } from "@/utils/util";
import FloatingBtn from "@/components/FloatingBtn.vue";
import Main from "@/pages/main/main.vue";
import type { LikeOrCollectionDTO } from "@/type/likeOrCollection";
import { likeOrCollectionByDTO } from "@/api/likeOrCollection";
import { useRouter } from "vue-router";

const router = useRouter();
const currentPage = ref(1);
const pageSize = ref(5);
const trendData = ref<Array<any>>([]);
const trendTotal = ref(0);
const topLoading = ref(false);
const mainShow = ref(false);
const nid = ref("");
const likeOrCollectionDTO = ref<LikeOrCollectionDTO>({
  likeOrCollectionId: "",
  publishUid: "",
  type: 0,
});

const handleLoad = (item: any) => {
  item.isLoading = true;
};

const toUser = (uid: string) => {
  router.push({ name: "user", state: { uid: uid } });
};

const getFollowTrends = () => {
  getFollowTrendPage(currentPage.value, pageSize.value).then((res) => {
    const { records, total } = res.data;
    console.log(records, total);
    records.forEach((item: any) => {
      item.time = formateTime(item.time);
      trendData.value.push(item);
    });
    trendTotal.value = total;
  });
};

const loadMoreData = () => {
  currentPage.value += 1;
  getFollowTrends();
};

const toMain = (noteId: string) => {
  nid.value = noteId;
  mainShow.value = true;
};

const close = (nid: string, isLike: boolean) => {
  const index = trendData.value.findIndex((item) => item.nid === nid);
  trendData.value[index].isLike = isLike;
  mainShow.value = false;
};

const refresh = () => {
  let scrollTop =
    window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
  const clientHeight =
    window.innerHeight ||
    Math.min(document.documentElement.clientHeight, document.body.clientHeight);
  if (scrollTop <= clientHeight * 2) {
    const timeTop = setInterval(() => {
      document.documentElement.scrollTop = document.body.scrollTop = scrollTop -= 100;
      if (scrollTop <= 0) {
        clearInterval(timeTop);
        topLoading.value = true;
        setTimeout(() => {
          currentPage.value = 1;
          trendData.value = [];
          getFollowTrends();
          topLoading.value = false;
        }, 500);
      }
    }, 10); //定时调用函数使其更顺滑
  } else {
    document.documentElement.scrollTop = 0;
    topLoading.value = true;
    setTimeout(() => {
      currentPage.value = 1;
      trendData.value = [];
      getFollowTrends();
      topLoading.value = false;
    }, 500);
  }
};

const like = (nid: string, uid: string, index: number, val: number) => {
  likeOrCollectionDTO.value.likeOrCollectionId = nid;
  likeOrCollectionDTO.value.publishUid = uid;
  likeOrCollectionDTO.value.type = 1;
  likeOrCollectionByDTO(likeOrCollectionDTO.value).then(() => {
    trendData.value[index].isLike = val == 1;
    trendData.value[index].likeCount += val;
  });
};

const initData = () => {
  getFollowTrends();
};

initData();
</script>

<style lang="less" scoped>
.container {
  flex: 1;
  padding: 0 24px;
  padding-top: 72px;
  width: 67%;
  height: 100vh;
  margin: 0 auto;

  .feeds-loading {
    margin: 3vh;
    text-align: center;
  }

  .trend-container {
    .trend-item {
      display: flex;
      flex-direction: row;
      padding-top: 24px;
      max-width: 100vw;

      .user-avatar {
        margin-right: 24px;
        flex-shrink: 0;

        .avatar-item {
          width: 48px;
          height: 48px;
          display: flex;
          align-items: center;
          justify-content: center;
          cursor: pointer;
          border-radius: 100%;
          border: 1px solid rgba(0, 0, 0, 0.08);
          object-fit: cover;
        }
      }

      .main {
        flex-grow: 1;
        flex-shrink: 1;
        display: flex;
        flex-direction: row;
        padding-bottom: 12px;
        border-bottom: 1px solid rgba(0, 0, 0, 0.08);

        .info {
          flex-grow: 1;
          flex-shrink: 1;

          .user-info {
            display: flex;
            flex-direction: row;
            align-items: center;
            font-size: 16px;
            font-weight: 600;
            margin-bottom: 4px;

            a {
              color: #333;
            }
          }
          .interaction-hint {
            font-size: 14px;
            color: rgba(51, 51, 51, 0.6);
            margin-bottom: 8px;
          }
          .interaction-content {
            display: flex;
            font-size: 14px;
            color: #333;
            margin-bottom: 12px;
            line-height: 140%;
            cursor: pointer;
          }

          .interaction-imgs {
            display: flex;
            .details-box {
              width: 25%;
              border-radius: 4px;
              margin: 8px 12px 0 0;
              cursor: pointer;

              .note-img {
                width: 100%;
                height: 230px;
                display: flex;
                border-radius: 4px;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                object-fit: cover;
              }
            }
          }

          .interaction-footer {
            margin: 8px 12px 0 0;
            padding: 0 12px;
            display: flex;
            justify-content: space-between;
            align-items: center;

            .icon-item {
              display: flex;
              justify-content: left;
              align-items: center;
              color: rgba(51, 51, 51, 0.929);
              .count {
                margin-left: 3px;
              }
            }
          }
        }
      }
    }
  }
}
</style>

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
小红书”——给你安利几个小程序的“坑”微信小程序写在前面  小程序发布至今已有一年多时间,很多人都已经在小程序道路上狂奔。最近我也开始了学习小程序,学了一段时间后,想看看这段时间的学习效果,于是边学习边开始了我的第一个小程序。相信很多人都用过小红书吧,我可是被它安利了很多好东西呢,所以想着就仿写一个小红书的微信小程序吧。下面我就给大家“安利”几个我在写的过程中的“坑”。  因为花的时间不多,功能有很多没有完善,页面写的不是很好看,请各位将就着看啦。╮(╯▽╰)╭   准备工作  1. 开发环境:WXML(HTML),WXSS(CSS),Javascript  2. 开发工具:vscode,微信开发者工具  3. 辅助工具:Markman:图标标注工具,可用于取色、测量。Easy-Mock:可以伪造假数据,在js中引用就好了。点这里可以查看我的项目数据。Markdown:在线编辑器GifCam:Gif录制工具 微信小程序开发文档Iconfont-阿里巴巴矢量图标库:各种需要的小图标都有哦遇到的几个问题1、首页导航栏左右滑动效果图:  这部分,是通过微信小程序的scroll-view组件来完成的。代码如下:<scroll-view class="navBar-box" scroll-x="true"  一些使用scroll-view的注意事项:请勿在 scroll-view 中使用 textarea、map、canvas、video 组件scroll-into-view 的优先级高于 scroll-top在滚动 scroll-view 时会阻止页面回弹,所以在 scroll-view 中滚动,是无法触发 onPullDownRefresh若要使用下拉刷新,请使用页面的滚动,而不是 scroll-view ,这样也能通过点击顶部状态栏回到页面顶部2、首页文章列表随着点击导航栏列表改变效果图:  这部分,是通过微信小程序的swiper组件来完成的。代码如下:                                                                                                    {{notes.title}}                              <!-- 作者信息 -->                                              {{notes.writer}}                                                {{notes.like}}                                    使用swiper组件,将所有文章列表包起来,每个swiper-item表示不同的列表模块。之前在导航栏各列表项绑定了不同
SpringBootVue3商城项目是一种基于Java后端框架SpringBoot和前端框架Vue3开发的电商项目。 首先,SpringBoot是一个快速开发的Java后端框架,它提供了简化配置和快速集成的特性。它能够快速搭建一个稳定可靠的后端服务,并且可以与各种第三方库和组件进行集成,例如数据库、缓存、消息队列等。在商城项目中,SpringBoot可以作为后端服务提供接口供前端调用,处理用户登录、注册、商品列表、购物车等功能。 而Vue3是一款流行的前端框架,它提供了组件化开发、数据响应式、虚拟DOM等特性,使前端开发更加高效和可维护。在商城项目中,Vue3可以负责展示商品列表、购物车、用户订单等页面,并与后端接口进行交互,实现后端数据的传输和展示。 在商城项目中,SpringBootVue3需要进行前后端的数据传输,可以使用RESTful API进行通信,通过HTTP协议传递JSON格式的数据。后端接收到前端的请求后,可以进行相应的业务逻辑处理,查询数据库、处理用户的购物请求等。前端则可以通过Ajax或者Axios等工具发起请求,并将后端返回的数据渲染到页面上。 此外,商城项目还可以使用一些其他的技术和工具,例如数据库可以选用MySQL、Redis等;前端可以使用ElementUI进行页面快速布局设计;图片上传可以使用七牛云等存储服务。通过使用这些技术和工具,可以快速搭建一个功能完善、用户友好的电商网站。 总之,SpringBootVue3商城项目是一种基于后端Java框架和前端JavaScript框架的电商网站开发方式,通过前后端的配合,实现商品展示、购物车管理、用户登录等功能。使用这种开发方式,可以大大提高项目的开发效率和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱技术的小胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值