Skip to content

13 智慧商城 项目初始化

项目资料

项目功能演示

打开演示地址 智慧商城,演示移动端面经内容,明确功能模块。

明确功能模块

55556c88-6b14-4cd3-8bb7-4647c0255895

项目收获

  • 完整电商购物业务流
  • 组件库 vant (全部&按需导入)
  • 移动端 vw 适配
  • request 请求方法封装
  • storage 存储模块封装
  • api 请求模块封装
  • 请求响应拦截器
  • 嵌套路由配置
  • 路由导航守卫
  • 路由跳转传参
  • vuex 分模块管理数据
  • 项目打包&优化

项目创建及初始化

这里使用 create-vue 创建项目,并非课程里的 vue-cli 创建项目。

创建项目

  1. 使用 create-vue 创建项目

    bash
     pnpm create vue@legacy
    .../Local/pnpm/store/v3/tmp/dlx-14152    |   +1 +
    .../Local/pnpm/store/v3/tmp/dlx-14152    | Progress: resolved 1, reused 1, downloaded 0, added 1, done
    
    Vue.js - The Progressive JavaScript Framework
    
     Project name: ... hm-shopping
     Add TypeScript? ... No / Yes
     Add JSX Support? ... No / Yes
     Add Vue Router for Single Page Application development? ... No / Yes
     Add Pinia for state management? ... No / Yes
     Add Vitest for Unit Testing? ... No / Yes
     Add Cypress for both Unit and End-to-End testing? ... No / Yes
     Add ESLint for code quality? ... No / Yes
     Add Prettier for code formatting? ... No / Yes
    
    Scaffolding project in C:\Users\Jaime\Desktop\code\hm-shopping...
    
    Done. Now run:
    
      cd hm-shopping
      pnpm install
      pnpm lint
      pnpm dev
    
     cd hm-shopping && pnpm i
     WARN  deprecated vue@2.7.16: Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.
     WARN  1 deprecated subdependencies found: sourcemap-codec@1.4.8
    Packages: +160
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    Progress: resolved 182, reused 158, downloaded 2, added 160, done
    
    dependencies:
    + vue 2.7.16 (3.4.15 is available) deprecated
    + vue-router 3.6.5 (4.2.5 is available)
    
    devDependencies:
    + @rushstack/eslint-patch 1.7.2
    + @vitejs/plugin-legacy 2.3.1 (5.3.0 is available)
    + @vitejs/plugin-vue2 1.1.2 (2.3.1 is available)
    + @vue/eslint-config-prettier 7.1.0 (9.0.0 is available)
    + eslint 8.56.0
    + eslint-plugin-vue 9.21.1
    + prettier 2.8.8 (3.2.4 is available)
    + terser 5.27.0
    + vite 3.2.8 (5.0.12 is available)
    
    Done in 3.2s
  2. 手动安装 vuex3

    bash
    pnpm add vuex@3
  3. 安装 less 预处理器

    bash
    pnpm add less less-loader -D

调整初始化目录结构

强烈建议大家严格按照老师的步骤进行调整,为了符合企业规范

为了更好的实现后面的操作,我们把整体的目录结构做一些调整。

  1. 删除初始化的一些默认文件

    • src/assets/*
    • src/components/*
    • src/views/*
  2. 修改没删除的文件

    • router/index.js:删除默认的路由配置
    • App.vue:删除 <router-view /> 路由出口以外的所有元素及样式
    • main.js:删除引入的 main.css 文件
  3. 新增需要的目录结构

    • src/api:存储接口模块 (发送 ajax 请求接口的模块)
    • src/utils:存储一些工具模块 (自己封装的方法)
  4. 此时的目录结构

    bash
     lsd --tree --icon-theme unicode --group-directories-first -I node_modules
    📂 .
    ├── 📂 public
       └── 📄 favicon.ico
    ├── 📂 src
       ├── 📂 api
       ├── 📂 assets
       ├── 📂 components
       ├── 📂 router
       └── 📄 index.js
       ├── 📂 store
       └── 📄 index.js
       ├── 📂 utils
       ├── 📂 views
       ├── 📄 App.vue
       └── 📄 main.js
    ├── 📄 eslint.config.js
    ├── 📄 index.html
    ├── 📄 package.json
    ├── 📄 pnpm-lock.yaml
    └── 📄 vite.config.js

代码示例

js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = []

const router = new VueRouter({
  mode: 'history',
  routes,
})

export default router
js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
export default new Vuex.Store({
  modules: {},
})
vue
<script setup>
</script>

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<style scoped>

</style>
js
import Vue from 'vue'

import App from './App.vue'
import router from './router'
import store from './store'

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

vant 组件库

常用的组件库

安装 Vant 组件库

快速上手 - Vant 3

bash
# Vue 3 项目,安装最新版 Vant
pnpm add vant

# Vue 2 项目,安装 Vant 2
pnpm add vant@latest-v2

引入 Vant 组件

全部引入和按需引入的区别

  • 全部引入使用简单,但是项目打包后的体积变大,进而影响用户访问网站的性能。
  • 按需引入组件的 CSS 样式,从而减少一部分代码体积,但使用起来会变得繁琐一些。
  • 如果业务对 CSS 的体积要求不是特别极致,推荐使用更简便的全部引入。

全部引入

  • main.js中全局引入 vant 组件库然后全局注册。

    • 全局注册后,你可以在 app 下的任意子组件中使用注册的 Vant 组件。
  • 子组件中使用 Vant 组件。

js
import Vue from 'vue'

import Vant from 'vant'
import App from './App.vue'
import router from './router'
import store from './store'

// 全局引入 Vant 组件库
// 引入 Vant 组件库样式
import 'vant/lib/index.css'

// 注册 Vant
Vue.use(Vant)

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')
vue
<script setup>
</script>

<template>
  <div id="app">
    <router-view />
    <!-- Vant Button 按钮组件 -->
    <van-button type="primary">
      主要按钮
    </van-button>
    <van-button type="success">
      成功按钮
    </van-button>
    <van-button type="default">
      默认按钮
    </van-button>
    <van-button type="warning">
      警告按钮
    </van-button>
    <van-button type="danger">
      危险按钮
    </van-button>
  </div>
</template>

<style scoped>

</style>

按需引入

注意

unplugin-vue-components 插件不支持 Vite + Vue2 的项目,需要使用 Vite + Vue3 的项目。所以这里只是演示按需引入的方式,后续步骤恢复为全部引入的方式。

  1. 安装 GitHub - unplugin/unplugin-vue-components: 📲 On-demand components auto importing for Vue 插件。

    bash
    pnpm add -D unplugin-vue-components
  2. Vite 中安装配置插件

    ts
    // vite.config.ts
    import vue from '@vitejs/plugin-vue'
    import Components from 'unplugin-vue-components/vite'
    import { VantResolver } from 'unplugin-vue-components/resolvers'
    
    export default defineConfig({
      plugins: [
        ...,
        vue(),
        Components({
          resolvers: [VantResolver()],
        }),
      ],
    })
  3. main.js中局部引入 vant 组件库然后全局注册。

    • 全局注册后,你可以在 app 下的任意子组件中使用注册的 Vant 组件。
    • 如果组件很多,影响 main.js 文件。可以将引入组件的步骤抽离到单独的 js 文件中比如 utils/vant-ui.js,然后在 main.js 中进行导入。
  4. 子组件中使用 Vant 组件。

js
import Vue from 'vue'

import App from './App.vue'
import router from './router'
import store from './store'

// 引入 Vant 组件库
import '@/utils/vant-ui.js'

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')
vue
<script setup>
</script>

<template>
  <div id="app">
    <router-view />
    <!-- Vant Button 按钮组件 -->
    <van-button type="primary">
      主要按钮
    </van-button>
    <van-button type="success">
      成功按钮
    </van-button>
    <van-button type="default">
      默认按钮
    </van-button>
    <van-button type="warning">
      警告按钮
    </van-button>
    <van-button type="danger">
      危险按钮
    </van-button>
    <!-- Vant Icon 图标组件  -->
    <van-icon name="like-o" badge="9" />
    <van-icon name="like" badge="9" />
    <!-- Vant Dialog 弹出框组件  -->
    <van-dialog />
  </div>
</template>

<style scoped>

</style>
js
import Vue from 'vue'

// 局部引入 Vant 组件库 Button/Icon/Dialog 组件
import { Button, Dialog, Icon } from 'vant'

// 注册 Vant 组件库
Vue.use(Button)
Vue.use(Icon)
Vue.use(Dialog)

浏览器适配

浏览器适配 进阶用法 - Vant 3

  • Vant 默认使用 px 作为样式单位,如果需要使用 viewport 单位 (vw, vh, vmin, vmax),推荐使用 postcss-px-to-viewport 进行转换。
  • postcss-px-to-viewport 是一款 PostCSS 插件,用于将 px 单位转化为 vw/vh 单位。
  1. 安装 postcss-px-to-viewport 插件

    bash
    pnpm add postcss-px-to-viewport -D
  2. 添加 postcss 配置

    bash
    // postcss.config.js
    module.exports = {
      plugins: {
        'postcss-px-to-viewport': {
          viewportWidth: 375,
        },
      },
    };

viewportWidth 设计稿的视口宽度

  1. vant-ui 中的组件就是按照 375 的视口宽度设计的
  2. 恰好面经项目中的设计稿也是按照 375 的视口宽度设计的,所以此时 我们只需要配置 375 就可以了
  3. 如果设计稿不是按照 375 而是按照 750 的宽度设计,那此时这个值该怎么填呢?
    • 设计图 750,调成 1 倍 => 适配 375 标准屏幕 viewportWidth: 375
    • 设计图 640,调成 1 倍 => 适配 320 标准屏幕 viewportWidth: 320

路由配置

但凡是单个页面,独立展示的,都是一级路由

路由设计:

  • 登录页 login/index.vue
  • 首页架子 layout/index.vue
    • 首页 - 二级 layout/home.vue
    • 分类页 - 二级 layout/category.vue
    • 购物车 - 二级 layout/cart.vue
    • 我的 - 二级 layout/my.vue
  • 搜索页 search/index.vue
  • 搜索列表页 searchList/index.vue
  • 商品详情页 goodsDetail/index.vue
  • 结算支付页 pay/index.vue
  • 我的订单页 order/index.vue

一级路由配置

  • 一级路由配置在 router/index.js 中配置
  • 一级路由对应的组件在 views 目录下创建对应的文件夹和文件
    • views/layout/index.vue
    • views/login/index.vue
    • views/search/index.vue
    • views/searchList/index.vue
    • views/goodsDetail/index.vue
    • views/pay/index.vue
    • views/order/index.vue
  • App.vue 中配置一级路由出口

示例代码

js
import Vue from 'vue'
import VueRouter from 'vue-router'

import LoginPage from '@/views/login/index.vue'
import LayoutPage from '@/views/layout/index.vue'
import SearchPage from '@/views/search/index.vue'
import SearchListPage from '@/views/searchlist/index.vue'
import GoodsDetailPage from '@/views/goodsdetail/index.vue'
import PayPage from '@/views/pay/index.vue'
import OrderPage from '@/views/order/index.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/login', name: 'login', component: LoginPage },
  { path: '/', name: 'layout', component: LayoutPage },
  { path: '/search', name: 'search', component: SearchPage },
  { path: '/searchlist', name: 'searchlist', component: SearchListPage },
  // 动态路由传参,确认将来是哪个商品,路由参数中携带 id
  {
    path: '/goodsdetail/:id',
    name: 'goodsdetail',
    component: GoodsDetailPage,
    props: true,
  },
  { path: '/pay', name: 'pay', component: PayPage },
  { path: '/order', name: 'order', component: OrderPage },
]

const router = new VueRouter({
  mode: 'history',
  routes,
})

export default router
vue
<script>
export default {
  name: 'LayoutPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    layout页面
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'LoginPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    login页面
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'SearchPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    搜索页面
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'SearchListPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    搜索列表页面
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'GoodsDetailPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    商品详情页面
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'PayPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    支付页面
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'OrderPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    订单页面
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'App',
  data() {
    return {}
  },
}
</script>

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<style scoped>

</style>
js
import Vue from 'vue'

import Vant from 'vant'
import App from './App.vue'
import router from './router'
import store from './store'

// import '@/utils/vant-ui.js'
import 'vant/lib/index.css'

Vue.use(Vant)

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

二级路由配置

  • 二级路由在 router/index.js 中配置:layout 路由下配置 children
  • 二级路由组件在 views/layout 目录下创建对应的文件
    • layout/home.vue
    • layout/category.vue
    • layout/cart.vue
    • layout/my.vue
  • 二级路由出口在 views/layout/index.vue 中配置

示例代码

js
import Vue from 'vue'
import VueRouter from 'vue-router'

import LoginPage from '@/views/login/index.vue'
import LayoutPage from '@/views/layout/index.vue'
import SearchPage from '@/views/search/index.vue'
import SearchListPage from '@/views/searchlist/index.vue'
import GoodsDetailPage from '@/views/goodsdetail/index.vue'
import PayPage from '@/views/pay/index.vue'
import OrderPage from '@/views/order/index.vue'

import HomePage from '@/views/layout/home.vue'
import CategoryPage from '@/views/layout/category.vue'
import CartPage from '@/views/layout/cart.vue'
import MyPage from '@/views/layout/my.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/login', name: 'login', component: LoginPage },
  {
    path: '/',
    name: 'layout',
    component: LayoutPage,
    redirect: '/home',
    children: [
      { path: '/home', name: 'home', component: HomePage },
      { path: '/category', name: 'category', component: CategoryPage },
      { path: '/cart', name: 'cart', component: CartPage },
      { path: '/my', name: 'my', component: MyPage },
    ],
  },
  { path: '/search', name: 'search', component: SearchPage },
  { path: '/searchlist', name: 'searchlist', component: SearchListPage },
  // 动态路由传参,确认将来是哪个商品,路由参数中携带 id
  {
    path: '/goodsdetail/:id',
    name: 'goodsdetail',
    component: GoodsDetailPage,
    props: true,
  },
  { path: '/pay', name: 'pay', component: PayPage },
  { path: '/order', name: 'order', component: OrderPage },
]

const router = new VueRouter({
  mode: 'history',
  routes,
})

export default router
vue
<script>
export default {
  name: 'LayoutPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    <!-- layout页面 -->
    <!-- 二级路由出口:二级组件展示的位置 -->
    <router-view />
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'HomePage',
  data() {
    return {}
  },

}
</script>

<template>
  <div>
    HomePage
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'CategoryPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    CategoryPage
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'CartPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    CartPage
  </div>
</template>

<style lang="less" scoped>

</style>
vue
<script>
export default {
  name: 'MyPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    MyPage
  </div>
</template>

<style lang="less" scoped>

</style>

Tabbar 标签栏组件

Tabbar 标签栏 | Vant 2

  1. utils/vant-ui.js 中引入 TabbarTabbarItem 组件
  2. views/layout/index.vue 中使用 TabbarTabbarItem 组件
    • 复制官方代码
    • 修改显示文本及显示的图标
    • 配置高亮颜色

示例代码

js
import Vue from 'vue'

import { Tabbar, TabbarItem } from 'vant'

Vue.use(Tabbar)
Vue.use(TabbarItem)
vue
<script>
export default {
  name: 'LayoutPage',
  data() {
    return {}
  },
}
</script>

<template>
  <div>
    <!-- layout页面 -->
    <!-- 二级路由出口:二级组件展示的位置 -->
    <router-view />
    <van-tabbar active-color="#ee0a24" inactive-color="#000" route>
      <van-tabbar-item icon="wap-home-o" to="/home">
        首页
      </van-tabbar-item>
      <van-tabbar-item icon="apps-o" to="/category">
        分类页
      </van-tabbar-item>
      <van-tabbar-item icon="shopping-cart-o" to="/cart">
        购物车
      </van-tabbar-item>
      <van-tabbar-item icon="user-o" to="/my">
        我的
      </van-tabbar-item>
    </van-tabbar>
  </div>
</template>

<style lang="less" scoped>

</style>

准备工作

  • 添加公共样式和图片素材

公共样式

  1. 准备 styles/common.less 重置默认样式
  2. main.js 中导入
less
// 重置默认样式
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

// 文字溢出省略号
.text-ellipsis-2 {
  overflow: hidden;
  -webkit-line-clamp: 2;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
}
js
import Vue from 'vue'
import Vant from 'vant'
import App from './App.vue'
import router from './router'
import store from './store'

import '@/styles/common.less'

// import '@/utils/vant-ui.js'
import 'vant/lib/index.css'

Vue.config.productionTip = false

Vue.use(Vant)

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

图片素材

  • src 目录下的 assets 目录存放静态资源,比如图片、字体等
  • 将准备好的一些图片素材拷贝到 src/assets 目录【备用】
src 目录结构
bash
 lsd --tree --icon-theme unicode --group-directories-first ./src
📂 src
├── 📂 api
├── 📂 assets
   ├── 📄 banner1.jpg
   ├── 📄 banner2.jpg
   ├── 📄 banner3.jpg
   ├── 📄 border-line.png
   ├── 📄 categood.png
   ├── 📄 code.png
   ├── 📄 default-avatar.png
   ├── 📄 empty.png
   ├── 📄 main.png
   ├── 📄 nav1.png
   └── 📄 product.jpg
├── 📂 components
├── 📂 router
   └── 📄 index.js
├── 📂 store
   └── 📄 index.js
├── 📂 styles
   └── 📄 common.less
├── 📂 utils
   └── 📄 vant-ui.js
├── 📂 views
   ├── 📂 goodsdetail
   └── 📄 index.vue
   ├── 📂 layout
   ├── 📄 cart.vue
   ├── 📄 category.vue
   ├── 📄 home.vue
   ├── 📄 index.vue
   └── 📄 my.vue
   ├── 📂 login
   └── 📄 index.vue
   ├── 📂 order
   └── 📄 index.vue
   ├── 📂 pay
   └── 📄 index.vue
   ├── 📂 search
   └── 📄 index.vue
   └── 📂 searchlist
       └── 📄 index.vue
├── 📄 App.vue
└── 📄 main.js