<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          當(dāng)3D走進(jìn)義務(wù)購(Vue3+Pinia+Koa+Three.js 全棧項(xiàng)目)

          共 8972字,需瀏覽 18分鐘

           ·

          2023-07-19 17:36

          前言

          前幾天一個(gè)朋友去義烏旅游,帶回來很多小商品,就是一整個(gè)物美價(jià)廉,但是為什么線下購物和網(wǎng)購有的時(shí)候差別這么大(網(wǎng)購經(jīng)常要退換貨啊??????),為此我萌生了一個(gè)想法,3D是不是就可以實(shí)現(xiàn)在線看商品的細(xì)節(jié)了,退換貨這么麻煩是不是可以省省了??

          一、項(xiàng)目概述

          這個(gè)項(xiàng)目是對義務(wù)購app的一個(gè)模仿,相對于其官方app,我新增的亮點(diǎn)如下:

          • 商品排列布局使用瀑布流布局
          • 實(shí)現(xiàn)3D看商品功能
          • 實(shí)現(xiàn)3D看義烏商貿(mào)城

          同時(shí),基礎(chǔ)功能如下:

          • 使用 MySQL 實(shí)現(xiàn)登錄注冊的功能
          • 使用 MySQL 實(shí)現(xiàn)商品搜索功能
          • 使用 MySQL 實(shí)現(xiàn)對用戶的購物車收貨地址增刪改查功能

          技術(shù)棧Vue3 + Pinia + Three.js + Koa

          二、項(xiàng)目展示
          1. 首頁
          4509b770ac4c3d1e5c1cf2c03e111802.webp主頁.gif
          1. 商品展示
          f5cb1c8a1b748164f5ec62110734175b.webp商品展示.gif
          1. 圈子
          14f29a90be8b0a084a533bfb5b5b240f.webp圈子.gif
          1. 商品搜索
          1c67408dd600ced92297b39a3494c31f.webp搜索.gif
          1. 購物車+地址管理
          eb5b52c523e198b0b34025e0f68059f7.webp加購.gif 三、項(xiàng)目思路
          1. 登錄采用 sessionStorage 做數(shù)據(jù)持久化,保存當(dāng)前賬號的登錄狀態(tài),在登錄的時(shí)候向后端發(fā)起接口請求,將當(dāng)前賬號的數(shù)據(jù)返回給前端。
          2. 商品搜索歷史采用 localStorage 做數(shù)據(jù)持久化,保存當(dāng)前賬號的搜索歷史,在搜索的時(shí)候向后端發(fā)起接口請求,將當(dāng)前賬號的數(shù)據(jù)返回給前端。
          3. 對于官方的商品展示以及商城的內(nèi)部,增加一個(gè)3D 預(yù)覽模塊。
          4. 不同的頁面封裝成一個(gè)組件,然后通過 Vue-router 對路由進(jìn)行集中管理,實(shí)現(xiàn)不同商品頁面展示不同商品。
          5. 借助 Pinia 保存橫向?qū)Ш綑冢ㄉ唐贩N類)的 id ,購物車數(shù)量角標(biāo),glb文件的路徑。
          四、項(xiàng)目主體結(jié)構(gòu)

          markdown

          復(fù)制代碼

          |-- client // 客戶端目錄結(jié)構(gòu) |-- public //商品3D模型 |-- draco |-- model |-- src |-- api //自己封裝的axios用于響應(yīng)攔截 |-- assets //圖片及基本css的初始化 |-- components //組件 |-- router //路由配置 |-- store //倉庫 |-- views //頁面 |-- server // 服務(wù)端目錄結(jié)構(gòu) |-- config //mysql配置文件 |-- controllers //控制器 |-- data //商品數(shù)據(jù) |-- routes //路由

          五、前端實(shí)現(xiàn)
          • UI組件庫:Vant
          • 移動(dòng)端適配:lib-flexible
          • CSS預(yù)處理器:less
          • 滾動(dòng):BetterScroll

          1. 組件

          眾所周知,組件可以省去很多代碼的編寫,這個(gè)項(xiàng)目中我將頭部導(dǎo)航欄底部導(dǎo)航欄,商品瀑布流布局做成組件便于引用。這里我主要介紹下頭部導(dǎo)航欄及商品瀑布流布局的實(shí)現(xiàn)。

          (1) 頭部導(dǎo)航欄(對不同類別的商品的展示)
          實(shí)現(xiàn)過程:后端數(shù)據(jù)中每個(gè)類別的商品數(shù)據(jù)都包含id這個(gè)字段,我將導(dǎo)航欄的每個(gè)類別的id和后端給的id對應(yīng)起來,并將這個(gè)id存儲在pinia倉庫中,這樣只要在頁面用watch監(jiān)聽倉庫id的變化去向后端請求相應(yīng)類別的數(shù)據(jù)即可。

          12c281063560e0605bcad5bee29b2c9a.webpimage.png

          (2) 商品瀑布流布局(提供更好的用戶體驗(yàn))
          實(shí)現(xiàn)過程:利用flex布局,它可以實(shí)現(xiàn)兩欄以上的瀑布流布局,我這里是兩欄瀑布流布局,故將父容器設(shè)置為彈性容器,子容器為兩個(gè)彈性容器,將這兩個(gè)子容器的排列方向設(shè)置為垂直排列,并用flex:1;兩列平分區(qū)域占滿整個(gè)視窗。

          c5299fa6a0fc89c8069c766f86d3e00c.webpimage.png

          2. 倉庫

          倉庫的出現(xiàn)讓我們可以在不同的頁面進(jìn)行數(shù)據(jù)共享,簡直不要太爽,再也不用擔(dān)心跨組件通信了!

          這里簡單介紹一下購物車角標(biāo)的實(shí)現(xiàn):
          因?yàn)樘砑踊騽h除商品,購物車角標(biāo)將立即更新,不管是在主頁還是購物車頁面還是商品詳情頁面,角標(biāo)都得實(shí)時(shí)更新它的數(shù)值,我們將變量值、更新角標(biāo)重新獲取購物車數(shù)據(jù)的方法定義在倉庫中,這樣在頁面就可以直接引入并使用就好啦~

                
                import?{defineStore}?from?'pinia'
          import?axios?from?'axios'

          const?useCartStore=defineStore('cart',{
          ??state:()=>{??
          ????return{
          ??????badge:0???//響應(yīng)式數(shù)據(jù)badge
          ????}
          ??},
          ??actions:{
          ????async?changeBadge(){
          ??????const?res?=await?axios.post('/cartList',?{??//獲取購物車數(shù)據(jù)
          ????????username:?JSON.parse(sessionStorage.getItem('userInfo')).username
          ??????})
          ??????this.badge=res.data.length
          ????}
          ??}
          })

          export?default?useCartStore

          3. 搜索模塊

          實(shí)現(xiàn)過程:利用localStorage對搜索的詞進(jìn)行數(shù)據(jù)持久化,這樣就能方便的從localStorage中拿到搜索的歷史詞段,并將其傳給后端使用mysql檢索相應(yīng)的數(shù)據(jù),并可以對其進(jìn)行刪除(也就是清除歷史記錄)

          ??PS: 后面發(fā)現(xiàn)的一個(gè)小優(yōu)化: 逛淘寶發(fā)現(xiàn)我啥都不輸入點(diǎn)擊搜索可以搜索默認(rèn)的字段,那還不簡單?這只需要發(fā)一次接口請求將默認(rèn)字段傳給后端即可啦~

          4. 3D商品預(yù)覽

          使用?Three.js?將引入的商品模型放入頁面中,項(xiàng)目中模型來源于此:sketchfab.com[1]

          由于模型的展示是通過點(diǎn)擊商品圖片后,以 遮罩層 + 動(dòng)畫 的形式呈現(xiàn)出來,不同商品展示不同模型,我們將其做成一個(gè)組件便于引用。部分代碼如下:

                
                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讓其可設(shè)置透明度
          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)

          //?渲染函數(shù)
          const?render?=?()?=>?{
          ??renderer.render(scene,?camera)
          ??controls.update()
          ??requestAnimationFrame(render)
          }

          onMounted(()?=>?{
          ??//渲染
          ??canvasDom.value.appendChild(renderer.domElement)

          ??//?設(shè)置背景顏色并啟用透明度
          ??renderer.setClearColor(0x000000,?0.2);
          ??render()

          ??//網(wǎng)格地面
          ??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讓其點(diǎn)擊不同商品展示不同模型?id對應(yīng)商品的id
          ????//?console.log(gltf.scene);
          ????const?bmw?=?gltf.scene
          ????bmw.scale.set(0.2,?0.2,?0.2);?//模型縮放
          ????scene.add(bmw)?//將整個(gè)模型組添加到場景中
          ??})

          });

          //灑滿燈光
          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. 商貿(mào)城3D預(yù)覽

          實(shí)現(xiàn)過程與3D商品預(yù)覽類似,我們只需用這段代碼將全景圖作為場景的背景圖即可(全景圖的資源路徑存在倉庫中):

                
                cubeTextureLoader.load(store.loadUrl,?(texture)?=>?{
          ????const?crt?=?new?THREE.WebGLCubeRenderTarget(texture.image.height)
          ????crt.fromEquirectangularTexture(renderer,?texture)??//把全景圖轉(zhuǎn)換為紋理格式
          ????scene.background?=?crt.texture
          ??})

          六、后端實(shí)現(xiàn)

          使用 Koa 框架搭建后端開發(fā)環(huán)境,后端分為四塊

          • 配置文件:對mysql的配置
          • 路由:定義接口請求路徑及響應(yīng)體
          • 控制器:當(dāng)接口被請求時(shí),需要向前端響應(yīng)的操作,即數(shù)據(jù)庫的增刪改查
          • 數(shù)據(jù):后端提供給前端的數(shù)據(jù)

          數(shù)據(jù)庫中創(chuàng)建了三個(gè)表

          • 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
          ??//去讀取數(shù)據(jù)庫中的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:?'賬號或密碼錯(cuò)誤'
          ??????}
          ????}
          ??}?catch?(error)?{
          ????ctx.body?=?{
          ??????code:?'80002',
          ??????data:?error,
          ??????msg:?'服務(wù)器異常'
          ????}
          ??}
          })

          //注冊接口
          router.post('/register',?async?(ctx,?next)?=>?{
          ??const?{?username,?password?}?=?ctx.request.body
          ??//判斷賬號或密碼是否為空
          ??if?(!username?||?!password)?{
          ????ctx.body?=?{
          ??????code:?'80001',
          ??????msg:?'賬號或密碼不能為空'
          ????}
          ????return
          ??}
          ??//判斷該賬號是否在數(shù)據(jù)庫中存在
          ??try?{
          ????let?findres?=?await?userService.userfind(username)
          ????if?(findres.length)?{??//如找到數(shù)據(jù)則向前端報(bào)錯(cuò)
          ??????ctx.body?=?{
          ????????code:?'80003',
          ????????data:?'error',
          ????????msg:?'用戶名已存在!'
          ??????}
          ????}?else?{??//如沒找到則注冊成功,往數(shù)據(jù)庫添加這條數(shù)據(jù)
          ??????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:?'服務(wù)器異常'
          ????}
          ??}
          })
          module.exports?=?router

          七、總結(jié)

          這個(gè)項(xiàng)目讓我對Vue3這個(gè)框架的使用更熟練了,整個(gè)過程中也遇到了很多bug及問題,以前我是一個(gè)很怕代碼出bug的小白,一遇到問題就問別人哈哈哈,這個(gè)項(xiàng)目讓我學(xué)會了怎樣一步一步尋找錯(cuò)誤并分析原因,現(xiàn)在自己能夠解決大多數(shù)的bug,也回顧了很多基礎(chǔ)的js知識,體驗(yàn)了一把理論聯(lián)系實(shí)踐了。不過,這個(gè)項(xiàng)目還是有需要改進(jìn)完善的地方,后續(xù)再見!

          項(xiàng)目地址 [2]

          參考資料

          [1]

          https://link.juejin.cn/?target=https%3A%2F%2Fsketchfab.com

          [2]

          https://gitee.com/zt18507085081/Yiwu_Shopping

          關(guān)于本文
          作者:zt_ever https://juejin.cn/post/7251101434023624760

          最后


          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ? 回復(fù)「 交流 」,吹吹水、聊聊技術(shù)、吐吐槽! 回復(fù)「 閱讀 」,每日刷刷高質(zhì)量好文! 如果這篇文章對你有幫助,在看」是最大的支持!


                    
                          

          最后不要忘了點(diǎn)贊呦!5a972aa4c9aea1880eaebbe7fd2959b7.webp


          瀏覽 78
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久性爱视频 | 国理伦中文字幕 | 围产精品久久久久久 | 91人妻无码一区二区三区 | 久久久久久久久久久久精 |