Vue3+Pinia+Koa+Three.js 全栈电商项目总结复盘

前言

前几天一个朋友去义乌旅游,带回来很多小商品,就是一整个物美价廉,但是为什么线下购物和网购有的时候差别这么大(网购经常要退换货啊😭😭😭),为此我萌生了一个想法,3D是不是就可以实现在线看商品的细节了,退换货这么麻烦是不是可以省省了😏

一、项目概述

这个项目是对义务购app的一个模仿,相对于其官方app,我新增的亮点如下:

  • 商品排列布局使用瀑布流布局

  • 实现3D看商品功能

  • 实现3D看义乌商贸城
    同时,基础功能如下:

  • 使用 MySQL 实现登录注册的功能

  • 使用 MySQL 实现商品搜索功能

  • 使用 MySQL 实现对用户的购物车及收货地址的增删改查功能

  • 技术栈:Vue3 + Pinia + Three.js + Koa

二、项目展示

  1. 首页
    在这里插入图片描述

  2. 商品展示
    在这里插入图片描述

  3. 圈子
    在这里插入图片描述

  4. 商品搜索
    在这里插入图片描述

  5. 购物车+地址管理
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/917ac781c6b645b2a0e92c7ac029e6c3.png

三、项目思路

  1. 登录采用 sessionStorage 做数据持久化,保存当前账号的登录状态,在登录的时候向后端发起接口请求,将当前账号的数据返回给前端。
  2. 商品搜索历史采用 localStorage 做数据持久化,保存当前账号的搜索历史,在搜索的时候向后端发起接口请求,将当前账号的数据返回给前端。
  3. 对于官方的商品展示以及商城的内部,增加一个3D 预览模块。
  4. 将不同的页面封装成一个组件,然后通过 Vue-router 对路由进行集中管理,实现不同商品页面展示不同商品。
  5. 借助 Pinia 保存横向导航栏(商品种类)的 id ,购物车数量角标,glb文件的路径。

四、项目主体结构

客户端目录结构:

  • client
    • public //商品3D模型
      • draco
      • model
    • src
      • api //自己封装的axios用于响应拦截
      • assets //图片及基本css的初始化
      • components //组件
      • router //路由配置
      • store //仓库
      • views //页面

服务端目录结构:

  • server
    • config //mysql配置文件
    • controllers //控制器
    • data //商品数据
    • routes //路由客户端目录结构:
  • client
    • public //商品3D模型
      • draco
      • model
    • src
      • api //自己封装的axios用于响应拦截
      • assets //图片及基本css的初始化
      • components //组件
      • router //路由配置
      • store //仓库
      • views //页面

服务端目录结构:

  • server
    • config //mysql配置文件
    • controllers //控制器
    • data //商品数据
    • routes //路由客户端目录结构:
  • client
    • public //商品3D模型
      • draco
      • model
    • src
      • api //自己封装的axios用于响应拦截
      • assets //图片及基本css的初始化
      • components //组件
      • router //路由配置
      • store //仓库
      • views //页面

服务端目录结构:

  • server
    • config //mysql配置文件
    • controllers //控制器
    • data //商品数据
    • routes //路由

五、前端实现

  • UI组件库:Vant
  • 移动端适配:lib-flexible
  • CSS预处理器:less
  • 滚动:BetterScroll

1. 组件

众所周知,组件可以省去很多代码的编写,这个项目中我将头部导航栏,底部导航栏,商品瀑布流布局做成组件便于引用。这里我主要介绍下头部导航栏及商品瀑布流布局的实现。

(1) 头部导航栏(对不同类别的商品的展示)
实现过程:后端数据中每个类别的商品数据都包含id这个字段,我将导航栏的每个类别的id和后端给的id对应起来,并将这个id存储在pinia仓库中,这样只要在页面用watch监听仓库id的变化去向后端请求相应类别的数据即可。
在这里插入图片描述
(2) 商品瀑布流布局(提供更好的用户体验)
实现过程:利用flex布局,它可以实现两栏以上的瀑布流布局,我这里是两栏瀑布流布局,故将父容器设置为弹性容器,子容器为两个弹性容器,将这两个子容器的排列方向设置为垂直排列,并用flex:1;两列平分区域占满整个视窗。
在这里插入图片描述

2. 仓库

仓库的出现让我们可以在不同的页面进行数据共享,简直不要太爽,再也不用担心跨组件通信了!

这里简单介绍一下购物车角标的实现:
因为添加或删除商品,购物车角标将立即更新,不管是在主页还是购物车页面还是商品详情页面,角标都得实时更新它的数值,我们将变量值、更新角标重新获取购物车数据的方法定义在仓库中,这样在页面就可以直接引入并使用就好啦~

import {defineStore} from 'pinia'
import axios from 'axios'const useCartStore=defineStore('cart',{state:()=>{  return{badge:0   //响应式数据badge}},actions:{async changeBadge(){const res =await axios.post('/cartList', {  //获取购物车数据username: JSON.parse(sessionStorage.getItem('userInfo')).username})this.badge=res.data.length}}
})export default useCartStore

3. 搜索模块

实现过程:利用localStorage对搜索的词进行数据持久化,这样就能方便的从localStorage中拿到搜索的历史词段,并将其传给后端使用mysql检索相应的数据,并可以对其进行删除(也就是清除历史记录)

后面发现的一个小优化:逛淘宝发现我啥都不输入点击搜索可以搜索默认的字段,那还不简单?这只需要发一次接口请求将默认字段传给后端即可啦~

4. 3D商品预览

使用 Three.js 将引入的商品模型放入页面中,项目中模型来源于此:sketchfab.com[1]

由于模型的展示是通过点击商品图片后,以 遮罩层 + 动画 的形式呈现出来,不同商品展示不同模型,我们将其做成一个组件便于引用。部分代码如下:

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { ref, onMounted } from 'vue';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { useRoute } from 'vue-router';const route = useRoute()
const { id } = route.paramsconst canvasDom = ref(null)//场景
const scene = new THREE.Scene()
//渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true, setAlpha: true })  //setAlpha让其可设置透明度
renderer.setSize(window.innerWidth, window.innerHeight)
//镜头
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(10, 10, 10)
camera.lookAt(0, 0, 0)
const controls = new OrbitControls(camera, renderer.domElement)// 渲染函数
const render = () => {renderer.render(scene, camera)controls.update()requestAnimationFrame(render)
}onMounted(() => {//渲染canvasDom.value.appendChild(renderer.domElement)// 设置背景颜色并启用透明度renderer.setClearColor(0x000000, 0.2);render()//网格地面const gridHelper = new THREE.GridHelper(80)gridHelper.material.transparent = truegridHelper.material.opacity = 0scene.add(gridHelper)//加载gltf模型const loader = new GLTFLoader()const dracoLoader = new DRACOLoader()dracoLoader.setDecoderPath('../../public/draco/gltf/')loader.setDRACOLoader(dracoLoader)loader.load(`../../public/model/${id}.glb`, (gltf) => {  //传id让其点击不同商品展示不同模型 id对应商品的id// console.log(gltf.scene);const bmw = gltf.scenebmw.scale.set(0.2, 0.2, 0.2); //模型缩放scene.add(bmw) //将整个模型组添加到场景中})});//洒满灯光
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(0, 0, 10)
scene.add(light)
const light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 0, -10);
scene.add(light2);
const light3 = new THREE.DirectionalLight(0xffffff, 1);
light3.position.set(10, 0, 0);
scene.add(light3);
const light4 = new THREE.DirectionalLight(0xffffff, 1);
light4.position.set(-10, 0, 0);
scene.add(light4);
const light5 = new THREE.DirectionalLight(0xffffff, 1);
light5.position.set(0, 10, 0);
scene.add(light5);
const light6 = new THREE.DirectionalLight(0xffffff, 0.3);
light6.position.set(5, 10, 0);
scene.add(light6);
const light7 = new THREE.DirectionalLight(0xffffff, 0.3);
light7.position.set(0, 10, 5);
scene.add(light7);
const light8 = new THREE.DirectionalLight(0xffffff, 0.3);
light8.position.set(0, 10, -5);
scene.add(light8);
const light9 = new THREE.DirectionalLight(0xffffff, 0.3);
light9.position.set(-5, 10, 0);
scene.add(light9);

5. 商贸城3D预览

实现过程与3D商品预览类似,我们只需用这段代码将全景图作为场景的背景图即可(全景图的资源路径存在仓库中):

cubeTextureLoader.load(store.loadUrl, (texture) => {const crt = new THREE.WebGLCubeRenderTarget(texture.image.height)crt.fromEquirectangularTexture(renderer, texture)  //把全景图转换为纹理格式scene.background = crt.texture})

六、后端实现

使用 Koa 框架搭建后端开发环境,后端分为四块:

  • 配置文件:对mysql的配置
  • 路由:定义接口请求路径及响应体
  • 控制器:当接口被请求时,需要向前端响应的操作,即数据库的增删改查
  • 数据:后端提供给前端的数据

数据库中创建了三个表:

  • users表:存储用户账号密码
  • cart表: 存储用户的购物车信息
  • address表: 存储用户的地址信息

这里以登录注册模块为例,路由代码如下:

const router = require('koa-router')()
//引入抛出的对象里的方法
const userService = require('../controllers/mySqlController.js')router.prefix('/users')//登录接口
router.post('/login', async (ctx, next) => {console.log(ctx.request.body);const { username, password } = ctx.request.body//去读取数据库中的users表,判断读取到的值和前端传过来的值是否匹配try {const result = await userService.userLogin(username, password)console.log(result);if (result.length) {let data = {id: result[0].id,username: result[0].username}ctx.body = {code: '80000',data: data,msg: '登陆成功'}} else {ctx.body = {code: '80004',data: 'error',msg: '账号或密码错误'}}} catch (error) {ctx.body = {code: '80002',data: error,msg: '服务器异常'}}
})//注册接口
router.post('/register', async (ctx, next) => {const { username, password } = ctx.request.body//判断账号或密码是否为空if (!username || !password) {ctx.body = {code: '80001',msg: '账号或密码不能为空'}return}//判断该账号是否在数据库中存在try {let findres = await userService.userfind(username)if (findres.length) {  //如找到数据则向前端报错ctx.body = {code: '80003',data: 'error',msg: '用户名已存在!'}} else {  //如没找到则注册成功,往数据库添加这条数据await userService.userRegister([username, password]).then(res => {// console.log(res);if (res.affectedRows !== 0) {ctx.body = {code: '80000',data: 'success',msg: '注册成功!'}} else {ctx.body = {code: '80004',data: 'error',msg: '注册失败!'}}})}} catch (error) {ctx.body = {code: '80002',data: error,msg: '服务器异常'}}
})
module.exports = router

七、总结

这个项目让我对Vue3这个框架的使用更熟练了,整个过程中也遇到了很多bug及问题,以前我是一个很怕代码出bug的小白,一遇到问题就问别人哈哈哈,这个项目让我学会了怎样一步一步寻找错误并分析原因,现在自己能够解决大多数的bug,也回顾了很多基础的js知识,体验了一把理论联系实践了。不过,这个项目还是有需要改进完善的地方,后续再见!

注意

自己维护会有点累,暂时先到者,喜欢的同学可以一起合作把他做成一个完整项目哈~

github:https://gitee.com/chao-diangen/vue3-pinia-koa-three.js

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

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

相关文章

php_webshell免杀--从0改造你的AntSword

0x00 前言: 为什么会有改造蚁剑的想法,之前看到有做冰蝎的流量加密,来看到绕过waf,改造一些弱特征,通过流量转换,跳过密钥交互。 但是,冰蝎需要反编译去改造源码,再进行修复bug&am…

营销数字化|企业级 AIGC 工具的「iPhone 时刻」

2007 年,乔布斯发布了第一款 iPhone,从此彻底改变了手机行业的市场走向。iPhone 成功的背后,一个很重要的原因是:它让用户以更简单、更符合直觉的方式来使用手机。 如今,AIGC 工具也在等待它的「iPhone 时刻」&#xf…

C#_委托详解

委托是什么? 字面理解:例如A要建一栋别墅,找到B建筑施工队,请B来建筑别墅。 委托类型规定方法的签名(方法类型):返回值类型、参数类型、个数、顺序。 委托变量可以用来存储方法的引用&#x…

什么是响应式图片?如何在网页中实现响应式图片?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 响应式图片&#xff08;Responsive Images&#xff09;⭐ 实现响应式图片的方法1. 使用<img>标签的srcset属性2. 使用<picture>元素3. 使用CSS的max-width属性4. 使用响应式图片库 ⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&…

懵了,面试官问我Redis怎么测,我哪知道!

有些测试朋友来问我&#xff0c;redis要怎么测试&#xff1f;首先我们需要知道&#xff0c;redis是什么&#xff1f;它能做什么&#xff1f; redis是一个key-value类型的高速存储数据库。 redis常被用做&#xff1a;缓存、队列、发布订阅等。 所以&#xff0c;“redis要怎么测…

缺少代码签名证书会怎么样?

在当下恶意软件攻击频发的情形下&#xff0c;使用代码签名证书来保护代码安全已经成为每个软件开发商的基本认知。代码签名证书将保护软件代码的完整性&#xff0c;避免软件被非法篡改或植入恶意代码病毒&#xff0c;从而使得软件可以正常运行。那么如果软件缺少代码签名证书会…

mysql 8.0 窗口函数 之 序号函数 与 sql server 序号函数 一样

sql server 序号函数 序号函数 ROW_NUMBER() 顺序排序RANK() 并列排序&#xff0c;会跳过重复的序号&#xff0c;比如序号为1&#xff0c;1&#xff0c;3DENSE_RANK() 并列排序&#xff0c;不会跳过重复的序号&#xff0c;比如 序号为 1&#xff0c;1&#xff0c;2 语法结构…

ffmpeg,nginx,vlc把rtsp流转hls

ffmpeg:rtsp>hls流; nginx 托管hls流服务; vlc测试hls流服务; 参考了很多相关文档和资料,由于比较乱就不在一一引用介绍了&#xff0c;下面的是实操OK的例子&#xff1b; 1&#xff09;ffmpeg (ffmpeg-4.4.1-full_build)&#xff0c;要用full版本&#xff0c;否则会缺某些…

Python实现企业微信群告警

Python实现企业微信告警 1. 创建企业微信群机器人 1-1. 什么是企业微信群机器人&#xff1f; 企业微信群机器人是企业微信平台提供的一种功能&#xff0c;可以通过Webhook方式将消息发送到指定的企业微信群中。它可以用于自动化发送通知、告警等信息&#xff0c;实现监控和信…

飞天使-k8s基础组件分析-持久化存储

文章目录 emptyDirhostpathpv和pvc介绍nfs作为静态pv案例nfs作为动态pv案例使用本地文件夹作为pv改变默认存储类及回收策略参考文档 emptyDir 重启文件还有&#xff0c;但是如果杀了进程&#xff0c;则会丢失文件 创建pod # kubectl apply –f redis.yaml校验pod是否处于运行&…

数据结构(7)

B树 B树中允许一个节点拥有多个key。设定参数M&#xff0c;构造B树 1.每个结点最多右M-1个key&#xff0c;并且以升序排列 2.每个结点最多右M个子结点 3.根节点至少右两个子结点 通过磁盘预读&#xff0c;将数据放到B树中&#xff0c;3层B树可容纳1024*1024*1024差不多10亿…

CentOS7.9安装Java11

文章目录 Java11版本介绍安装步骤查看并卸载已有版本安装Java11最新版本配置生效 openjdk介绍 Java11版本介绍 Java 11是Java编程语言的一个重要版本&#xff0c;于2018年9月发布Java 11在语言特性、性能优化和安全性方面都有一些显著的改进&#xff0c;为Java开发者提供了更多…

MySQL每日一练--销售管理系统

一&#xff0c;创建数据库SaleSys 二&#xff0c;在数据库SaleSys中创建3张表 品牌信息表&#xff08;brand&#xff09; BrandId --品牌编号&#xff0c;整型&#xff0c;自动增长&#xff0c;主键BrandName --品牌名称&#xff0c;字符型&#xff0c; 唯一约束 商品表…

若依Cloud集成Flowable6.7.2

项目简介 基于若依Cloud的Jove-Fast微服务项目&#xff0c;集成工作流flowable(接上篇文章) 若依Cloud集成积木报表 项目地址&#xff1a;https://gitee.com/wxjstudy/jove-fast 后端 新建模块 目录结构如下: 引入依赖 前提:引入依赖之前先配置好maven的setting.xml &…

ES 7.6 - APi基础操作篇

ES7.6-APi基础操作篇 前言相关知识索引相关创建索引查询索引查询所有索引删除索引关闭与打开索引关闭索引打开索引 冻结与解冻索引冻结索引解冻索引 映射相关创建映射查看映射新增字段映射 文档相关(CURD)新增文档根据ID查询修改文档全量覆盖根据ID选择性修改根据条件批量更新 …

iOS开发之查看静态库(.a/.framework)中包含的.o文件和函数符号(ar,nm命令)

.a/.framework其实是把编译生成的.o文件&#xff0c;打包成一个.a/.framework文件。a的意思是archive/归档的意思。 查看静态库.a文件包含的内容用下面的命令解压&#xff1a; ar x xxx.a 用ar命令打包静态库&#xff1a; 参数r是将后面的*.o或者*.a文件添加到目标文件中 参数…

Python Opencv实践 - 图像直方图自适应均衡化

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/cat.jpg", cv.IMREAD_GRAYSCALE) print(img.shape)#整幅图像做普通的直方图均衡化 img_hist_equalized cv.equalizeHist(img)#图像直方图自适应均衡化 #1. 创…

js中?.、??、??=的用法及使用场景

上面这个错误&#xff0c;相信前端开发工程师应该经常遇到吧&#xff0c;要么是自己考虑不全造成的&#xff0c;要么是后端开发人员丢失数据或者传输错误数据类型造成的。因此对数据访问时的非空判断就变成了一件很繁琐且重要的事情&#xff0c;下面就介绍ES6一些新的语法来方便…

[Stable Diffusion教程] 第一课 原理解析+配置需求+应用安装+基本步骤

第一课 原理解析配置需求应用安装基本步骤 本次内容记录来源于B站的一个视频 以下是自己安装过程中整理的问题及解决方法&#xff1a; 问题&#xff1a;stable-diffusion-webui启动No Python at ‘C:\xxx\xxx\python.exe‘ 解答&#xff1a;打开webui.bat 把 if not de…

ARM-汇编指令

一&#xff0c;map.lds文件 链接脚本文件 作用&#xff1a;给编译器进行使用&#xff0c;告诉编译器各个段&#xff0c;如何进行分布 /*输出格式&#xff1a;32位可执行程序&#xff0c;小端对齐*/ OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm",…