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.params

const 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 = true
  gridHelper.material.opacity = 0
  scene.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.scene
    bmw.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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nginx详解 第三部分:Nginx高级配置(附配置实例)

Part 3 一、网页的状态页二、Nginx第三方模块2.1 echo 模块 三、变量3.1 内置变量3.1.1 常用内置变量3.1.2 举个例子 3.2 自定义变量 四、自定义访问日志 (优化)4.1 自定义访问日志的格式4.2 自定义json 格式日志 五、Nginx压缩功能(重要)六、HTTPS 功能…

集合框架-(Collection/Map)

1.单列集合 1.1基础概要 集合中存储的是对象的地址信息,想要输出对象的信息,需要在具体的类中重写toString()方法 Collection代表单列集合,每个元素数据只包含一个值 List集合:添加的元素可以是有序、可…

2023年最新 Github Pages 使用手册

参考:GitHub Pages 快速入门 1、什么是 Github Pages GitHub Pages 是一项静态站点托管服务,它直接从 GitHub 上的仓库获取 HTML、CSS 和 JavaScript 文件,(可选)通过构建过程运行文件,然后发布网站。 可…

tensordataset 和dataloader取值

测试1 from torch.utils.data import TensorDataset,DataLoader import numpy as np import torch a np.array([[1,2,3],[2,3,3],[1,1,2],[10,10,10],[100,200,200],[-1,-2,-3]]) print(a)X torch.FloatTensor(a) print(X)dataset TensorDataset(X,X)测试2 from torch.uti…

Java接收json参数

JSON 并不是唯一能够实现在互联网中传输数据的方式,除此之外还有一种 XML 格式。JSON 和 XML 能够执行许多相同的任务,那么我们为什么要使用 JSON,而不是 XML 呢? 之所以使用 JSON,最主要的原因是 JavaScript。众所周知…

vscode流程图插件使用

vscode流程图插件使用 1.在vscode中点击左下角设置然后选择扩展。 2.在扩展中搜索Draw.io Integration,安装上面第一个插件。 3.安装插件后在工程中创建一个后缀为drawio的文件并且双击打开即可绘制流程图

SpringBoot项目在启动后自动关闭

问题描述: 今天搭建了一个SpringBoot项目,但是在启动之后就自行关闭了,就像下面这样: 原因分析:在创建SpringBoot项目的时候,Web的依赖没有导入,默认以普通java项目运行导致的终止。 解决方案…

比较重合点的排斥能

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点,AB训练集各由5张二值化的图片组成,让A中有2个1,B中有1个1,有一个点重合,排列组合,统计迭代次数并排序。 得到数据 构造平均列A 构造平均列…

【GoLang】go入门:go语言执行过程分析 常见数据类型(基本数据类型)

1、go语言执行过程分析 【1】执行流程分析 通过 go build 进行编译 运行上一步生成的可执行文件 通过 go run 命令直接运行 【2】上述两种执行流程的区别 在编译时,编译器会将程序运行时依赖的库文件包含在可执行文件中,所以可执行文件会变大很多通过g…

Git入门

本文主要介绍Git的入门知识。首先讲述版本控制工具的一些背景, 然后介绍如何在你自己的系统上安装.配置和运行Git。学完本文,你将明白Git是怎么来的、为什么需要Git,并掌握使用Git的基础知识。 一、版本控制 什么是“版本控制”,为什么需要它?版本控制是…

Python OCR 使用easyocr库将图片中的文章提取出来

Python OCR 使用easyocr库将图片中的文章提取出来 初环境内容步骤一:安装easyocr库步骤二:导入必要的库步骤三:创建OCR阅读器对象步骤四:指定要识别的图片路径步骤五:执行OCR识别并提取文章内容步骤六:遍历…

Docker consul的容器服务注册与发现

前言一、服务注册与发现二、consul 介绍三、consul 部署3.1 consul服务器3.1.1 建立 Consul 服务3.1.2 查看集群信息3.1.3 通过 http api 获取集群信息 3.2 registrator服务器3.2.1 安装 Gliderlabs/Registrator3.2.2 测试服务发现功能是否正常3.2.3 验证 http 和 nginx 服务是…

Leetcode-每日一题【剑指 Offer 37. 序列化二叉树】

题目 请实现两个函数,分别用来序列化和反序列化二叉树。 你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 …

Vue2向Vue3过度Vue3状态管理工具Pinia

目录 1. 什么是Pinia2. 手动添加Pinia到Vue项目3. Pinia基础使用4. getters实现5. action异步实现6. storeToRefs工具函数7. Pinia的调试8. Pinia持久化插件 1. 什么是Pinia Pinia 是 Vue 的专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品 2. 手动添加Pinia到…

【Java】树结构SQL数据的如何去实现搜索

这里写自定义目录标题 需要实现的效果前端需要的json格式:一定是一个完整的树结构错误错误的返回格式错误的返回格式实现的效果 正确正确的返回格式正确的展示画面 后端逻辑分析代码总览 数据库表结构 需要实现的效果 前端需要的json格式:一定是一个完整…

【算法】倍增-ST表

一.倍增 倍增是一种常用的算法技巧,通常用于优化时间复杂度。它的核心思想是将原问题分解成若干个规模较小的子问题,通过对子问题的求解来得到原问题的解。具体来说,倍增算法通常采用二分思想,将问题规模不断缩小,直到…

C#获取DataTable的前N行数据然后按指定字段排序

获取DataTable的前N行数据然后按指定字段排序 可以使用以下三种代码: 第一种:使用Linq DataTable dtLast dataTable.AsEnumerable().Take(count).OrderBy(dataRow > Convert.ToInt32(dataRow["Sequence"])).CopyToDataTable(); 第二种…

【Terraform学习】使用 Terraform 从 EC2 实例访问 S3 存储桶(Terraform-AWS最佳实战学习)

使用 Terraform 从 EC2 实例访问 S3 存储桶 实验步骤 前提条件 安装 Terraform: 地址 下载仓库代码模版 本实验代码位于 task_ec2_s3connet 文件夹中。 变量文件 variables.tf 在上面的代码中,您将声明,aws_access_key,aws_…

stm32之9.中断优先级配置

主函数main.c #include <stm32f4xx.h> #include "led.h" #include "key.h"#define PAin(n) (*(volatile uint32_t *)(0x42000000 (GPIOA_BASE0x10-0x40000000)*32 (n)*4)) #define PEin(n) (*(volatile uint32_t *)(0x42000000 (GP…

OpenCV基础知识(8)— 图形检测

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。图形检测是计算机视觉的一项重要功能。通过图形检测可以分析图像中可能存在的形状&#xff0c;然后对这些形状进行描绘&#xff0c;例如搜索并绘制图像的边缘&#xff0c;定位图像的位置&#xff0c;判断图像中有没有直线、…