搭建页面架构

一. 初始化ElementPlus

参照:https://element-plus.gitee.io/zh-CN/guide/quickstart.html#全局配置

  1. 安装:
  1. npm install element-plus --save
  1. 引入
  • 完整引入。字面意思将全部组件加载到项目里,那么在将来打包时,也会全部打包组件到结果中,不推荐。
  • 按需引入。引入需要的组件,打包的时候压力比较小。
  • 但是,如果引入的组件较多,推荐完整引入,不然按需引入还得一个一个组件注册和配置,比较麻烦。
  • 此项目里,需要大量组件,于是选择完整引入。

plugins里创建一个element-plus.ts专门配置elementPlus

  1. import ElementPlus from 'element-plus'
  2. import 'element-plus/dist/index.css'
  3. import { App } from 'vue'
  4. export default {
  5. // 将来注册组件时,自动调用install函数,把vue实例传进来
  6. install (app: App) {
  7. app.use(ElementPlus)
  8. }
  9. }

然后导入到main.ts

  1. import elementPlus from './plugins/element-plus'
  2. createApp(App)
  3. .use(router)
  4. ...
  5. .use(elementPlus)

这就完成了elementPlus的注册

验证一下

  1. <!--home.vue-->
  2. <el-button>测试按钮</el-button>

呈现出来了!

全局配置

可以针对elementPlus组件进行全局配置,比如我想让所有插入的element Plus的组件都显示很小的尺寸size,并且让他们在页面最外层zIndex

只需要在完整引入的element-plus.ts里注册的时候加上设置

  1. app.use(ElementPlus,{size:'small', zIndex:3000})

而如果针对某个组件,想修改尺寸

  1. <!--home.vue-->
  2. <el-button size="mini"></el-button>

国际化:语言

组件内部显示的语言默认为英语,比如当我们插入日期组件,那么日期都是英语的Jan, Feb, March等等这些。

ConfigProvider

可以针对局部生效,也能针对全局生效。

  1. <el-config-provider :locale="locale">
  2. <app />
  3. </el-config-provider>

被包裹的app就生效。

全局生效:

找到根组件,App.vue,包裹住路由的渲染出口,即可全局生效。

  1. <el-config-provider :locale="locale">
  2. <router-view />
  3. </el-config-provider>
  4. <script lang="ts" setup>
  5. // 将locale加载进来,也就是语言。
  6. import locale from 'element-plus/lib/locale/lang/zh-cn'
  7. </script>

这就完事了!

总结
  1. 安装,通过npm
  2. 引入elementPlus,我选择了全局引入,创建了element-plus.ts来存放,然后导入,并且注册app.use(elementPlus)
  3. 将组件的语言设置为中文

二. Layout布局和导航菜单

通过ElementPlus的Container布局容器快速搭建布局

而我们恰好需要以下布局:

image.png

即可直接用。

Layout组件

新建src / layout / AppLayout.vue文件,然后将element提供的代码直接导入。

  1. <template>
  2. <div class="common-layout">
  3. <el-container>
  4. <el-aside width="200px">
  5. <AppMenu />
  6. </el-aside>
  7. <el-container>
  8. <el-header>Header</el-header>
  9. <el-main>
  10. <!-- 子路由出口 -->
  11. <router-view />
  12. </el-main>
  13. </el-container>
  14. </el-container>
  15. </div>
  16. </template>
  17. <script lang="ts" setup>
  18. import AppMenu from './components/AppMenu.vue'
  19. </script>
  20. <style lang="scss" scoped>
  21. .el-header,.el-footer {
  22. background-color: #B3C0D1;
  23. color:#333;
  24. }
  25. .el-aside {
  26. background-color: #304156;
  27. color: #333;
  28. }
  29. .el-main {
  30. background-color: #E9EEF3;
  31. color:#333;
  32. }
  33. .el-container {
  34. height: 100vh;
  35. }
  36. </style>

然后将AppLayout导出。

路由配置

编辑src / router / index.ts文件

在路由里,将首页改成我们的AppLayout,然后子路由是home/index.vue,这样就被套在里面了。

  1. const routes: RouteRecordRaw[] = [
  2. {
  3. path: '/',
  4. component: AppLayout,
  5. children: [
  6. {
  7. path: '', // 默认子路由
  8. name: 'home',
  9. component: () => import('../views/home/index.vue')
  10. }
  11. ]
  12. },
  13. ....

样式优化

编辑src / styles / common.scss文件:

  1. * {
  2. margin: 0;
  3. padding: 0;
  4. }

完成第一步:

页面展示

image.png

第二步:

三. NavMenu菜单导航:

找到menu,提供的一个模板:

image.png

侧边栏组件

新建src / layout / components / AppMenu文件:

  1. <el-menu
  2. active-text-color="#ffd04b"
  3. background-color="#304156"
  4. class="el-menu-vertical-demo"
  5. default-active="2"
  6. text-color="#fff"
  7. >
  8. <el-sub-menu index="1">
  9. <template #title>
  10. <el-icon><location /></el-icon>
  11. <span>Navigator One</span>
  12. </template>
  13. <el-menu-item-group title="Group One">
  14. <el-menu-item index="1-1">item one</el-menu-item>
  15. <el-menu-item index="1-2">item two</el-menu-item>
  16. </el-menu-item-group>
  17. <el-menu-item-group title="Group Two">
  18. <el-menu-item index="1-3">item three</el-menu-item>
  19. </el-menu-item-group>
  20. <el-sub-menu index="1-4">
  21. <template #title>item four</template>
  22. <el-menu-item index="1-4-1">item one</el-menu-item>
  23. </el-sub-menu>
  24. </el-sub-menu>
  25. <el-menu-item index="2">
  26. <el-icon><icon-menu /></el-icon>
  27. <span>Navigator Two</span>
  28. </el-menu-item>
  29. <el-menu-item index="3" disabled>
  30. <el-icon><document /></el-icon>
  31. <span>Navigator Three</span>
  32. </el-menu-item>
  33. <el-menu-item index="4">
  34. <el-icon><setting /></el-icon>
  35. <span>Navigator Four</span>
  36. </el-menu-item>
  37. </el-menu>
  38. </template>
  39. <script lang="ts" setup>
  40. import {
  41. Document,
  42. Menu as IconMenu,
  43. Location,
  44. Setting
  45. } from '@element-plus/icons-vue'
  46. </script>

注意:ElementPlus的icon只能按需手动引入

并在AppLayout.vue里导入, 并且将菜单放去左上角的Aside位置

  1. <el-aside width="200px">
  2. <AppMenu />
  3. </el-aside>
  4. <script>
  5. import AppMenu from './components/AppMenu.vue'
  6. </script>

完成第二步
image.png

最后我们的成果是需要完成如下:
image.png

因此还需要

  • product商品
    • attr 规则管理
    • classify 分类
    • list 列表
    • reply 评论
  • order订单相关的:
    • list订单列表
    • offine收银订单
  • media商品图片资源
  • permisson权限相关的
    • admin 管理员
    • role 角色
    • rule 规则

路由目录

为了方便管理组件,我们设置每个组件为一个新的路由ts文件

比如:product管理商品列表,商品规格价格等等。

副路由的设置方法

比如我们点击菜单,会下拉,菜单就是副路由,点击后只会拉下菜单。

component设置为RouterView, 而下拉的路由写在children

  1. import { RouteRecordRaw, RouterView } from 'vue-router'
  2. const routes: RouteRecordRaw = {
  3. path: '/product',
  4. component: RouterView,
  5. meta: {
  6. title: '商品'
  7. },
  8. children: [
  9. {
  10. path: 'product_list',
  11. name: 'product-list',
  12. component: () => import('@/views/product/list/index.vue'),
  13. meta: { // 自定义路由元数据
  14. title: '商品列表'
  15. }
  16. },
  17. {
  18. path: 'add_product',
  19. name: 'product-add',
  20. component: () => import('@/views/product/add/index.vue'),
  21. meta: {
  22. title: '添加商品'
  23. }
  24. },
  25. {
  26. path: 'product_attr',
  27. name: 'product-attr',
  28. component: () => import('@/views/product/attr/index.vue'),
  29. meta: {
  30. title: '商品规格'
  31. }
  32. },
  33. {
  34. path: 'product_classify',
  35. name: 'product-classify',
  36. component: () => import('@/views/product/classify/index.vue'),
  37. meta: {
  38. title: '商品分类'
  39. }
  40. },
  41. {
  42. path: 'product_reply',
  43. name: 'product-reply',
  44. component: () => import('@/views/product/reply/index.vue'),
  45. meta: {
  46. title: '商品评论'
  47. }
  48. }
  49. ]
  50. }
  51. export default routes

完成后,将product导入到主路由里

  1. ...
  2. import productRouter from './modules/product'
  3. const routes: RouteRecordRaw[] = [
  4. {
  5. path: '/',
  6. component: AppLayout,
  7. children: [
  8. {
  9. path: '', // 默认子路由
  10. name: 'home',
  11. component: () => import('../views/home/index.vue')
  12. },
  13. productRouter
  14. ]
  15. },
  16. {
  17. path: '/login',
  18. ...

并且分别填写product每个子路由匹配的组件,比如product_list对应的src/views/product/list/index.vue

  1. <template>
  2. <h1>商品列表</h1>
  3. </template>
  4. <script lang="ts" setup>
  5. </script>
  6. <style lang="scss" scoped></style>

页面样式

image.png

项目思路:

分为两个大路由,布局组件和登录组件。

布局的主路由是布局组件,子组件分别是首页,商品:商品列表啥的。设置

登录组件。

四. 切换侧边栏展开收起

也就是实现点击后展开菜单栏,再次点击后收起。

image.png
image.png

源码里,主要实现的方式是通过:collapse="isCollapse"的布尔值来控制收起或者展开。

  1. const isCollapse = ref(true)

思路:

将isCollapse的值注入项目的容器store,默认为false,默认展开状态,这样全局都能看到他了。

然后在AppHeader里写一个按钮组件来控制isCollapse。

最后将这个按钮组件导入目录里就好了。

1. 全局变量vuex和store

编辑src / store / index.tsx

  1. ...
  2. export interface State {
  3. count:number,
  4. //主要变化在这!!!!加入了isCollapse
  5. isCollapse: boolean
  6. }
  7. ...
  8. export const key: InjectionKey<Store<State>> = Symbol('store')
  9. // 创建一个新的 store 实例
  10. export const store = createStore<State>({
  11. state () {
  12. return {
  13. count: 0,
  14. // 默认为false
  15. isCollapse: false
  16. }
  17. mutations: {
  18. // 当触发后,第一个变量等于第二个
  19. setIsCollapse (state, payload) {
  20. state.isCollapse = payload
  21. }
  22. }

2. 侧边组件

编辑@ / layout / components / AppMenu / index.vue

  1. <!-- eslint-disable vue/multi-word-component-names -->
  2. <template>
  3. <el-menu
  4. active-text-color="#ffd04b"
  5. background-color="#304156"
  6. class="el-menu-vertical-demo"
  7. default-active="2"
  8. text-color="#fff"
  9. :collapse="$store.state.isCollapse"
  10. router
  11. >
  12. <el-menu-item index="/">
  13. <el-icon><home-filled /></el-icon>
  14. <span>首页</span>
  15. </el-menu-item>
  16. <el-sub-menu index="1">
  17. <template #title>
  18. <el-icon><goods-filled /></el-icon>
  19. <span>商品</span>
  20. </template>
  21. <el-menu-item index="/product/product_list">
  22. <span>商品列表</span>
  23. </el-menu-item>
  24. <el-menu-item index="/product/product_classify">
  25. <span>商品分类</span>
  26. </el-menu-item>
  27. <el-menu-item index="/product/product_attr">
  28. <span>商品规格</span>
  29. </el-menu-item>
  30. <el-menu-item index="/product/product_reply">
  31. <span>商品评论</span>
  32. </el-menu-item>
  33. </el-sub-menu>
  34. <el-sub-menu index="2">
  35. <template #title>
  36. <el-icon><ShoppingCart /></el-icon>
  37. <span>订单</span>
  38. </template>
  39. <el-menu-item index="/order/order_list">
  40. 订单列表
  41. </el-menu-item>
  42. <el-menu-item index="/order/offine">
  43. 收银订单
  44. </el-menu-item>
  45. </el-sub-menu>
  46. <el-sub-menu index="2-2">
  47. <template #title>
  48. <el-icon><Promotion /></el-icon>
  49. <span>媒体</span>
  50. </template>
  51. </el-sub-menu>
  52. <el-sub-menu index="3">
  53. <template #title>
  54. <el-icon><Setting /></el-icon>
  55. <span>权限</span>
  56. </template>
  57. <el-menu-item index="/permission/permission_role">
  58. 角色
  59. </el-menu-item>
  60. <el-menu-item index="/permission/admin">
  61. 管理员
  62. </el-menu-item>
  63. <el-menu-item index="/permission/rule">
  64. 权限规则
  65. </el-menu-item>
  66. </el-sub-menu>
  67. </el-menu>
  68. </template>
  69. <script lang="ts" setup>
  70. import {
  71. HomeFilled,
  72. GoodsFilled,
  73. ShoppingCart,
  74. Setting,
  75. Promotion
  76. } from '@element-plus/icons-vue'
  77. // import { ref } from 'vue'
  78. // const isCollapse = ref(true)
  79. </script>
  80. <style lang="scss" scoped>
  81. .el-menu {
  82. border-right: none;
  83. }
  84. .el-menu-vertical-demo:not(.el-menu--collapse) {
  85. width: 200px;
  86. min-height: 400px;
  87. }
  88. </style>

3. 头部组件

新建src / layout / AppHeader / ToggleSidebar.vue

  1. <template>
  2. <el-icon
  3. @click="$store.commit('setIsCollapse', false)"
  4. v-show="$store.state.isCollapse"
  5. style="cursor: pointer;"
  6. >
  7. <expand />
  8. </el-icon>
  9. <el-icon
  10. @click="$store.commit('setIsCollapse', true)"
  11. v-show="!$store.state.isCollapse"
  12. style="cursor: pointer;"
  13. >
  14. <fold />
  15. </el-icon>
  16. <h1>header1</h1>
  17. </template>
  18. <script lang="ts" setup>
  19. import { Fold, Expand } from '@element-plus/icons-vue'
  20. </script>
  21. <style lang="scss" scoped></style>

4. Layout布局

编辑src / layout / AppLayout.vue文件

  1. <template>
  2. <div class="common-layout">
  3. <el-container>
  4. <el-aside>
  5. <AppMenu />
  6. </el-aside>
  7. <el-container>
  8. <el-header>
  9. <AppHeader />
  10. </el-header>
  11. <el-main>
  12. <!-- 子路由出口 -->
  13. <router-view />
  14. </el-main>
  15. </el-container>
  16. </el-container>
  17. </div>
  18. </template>
  19. <script lang="ts" setup>
  20. import AppMenu from './AppMenu/index.vue'
  21. import AppHeader from './AppHeader/ToggleSidebar.vue'
  22. </script>
  23. <style lang="scss" scoped>
  24. .el-header{
  25. background-color: #b3c0d1;
  26. color: #333;
  27. display: flex;
  28. justify-content: space-between;
  29. align-items: center;
  30. }
  31. .el-aside {
  32. width: auto;
  33. background-color: #304156;
  34. color: #333;
  35. }
  36. .el-main {
  37. background-color: #e9eef3;
  38. color: #333;
  39. }
  40. .el-container {
  41. height: 100vh;
  42. }
  43. </style>

5. 样式展示

image.png

五. 面包屑导航

也就是每个页面展示当前位置: 首页 > 商品 > 商品价格

如何才能实现当前标题的展示?

思路:

在路由里给每个路由和子路由增加meta属性,并且将title写好。

AppHeader组件上,导入路由,获取当前的路由值,并且过滤,获取含有title的路由。

1. 面包屑导航组件

新建src / layout / AppHeader / Breadcrumb.vue文件:

思路:

导入路由。

然后获取路由useRouter(),通过router.currentRoute.value.matched拿到当前的所有路由,比如当前在商品列表,那么将获得layout => 商品 => 商品列表 三个路由。

将获取的路由过滤一下,获取有title的路由,routes = router.currentRoute.value.matched.filter(item => item.meta.title)

然后通过v-for在模板里输出。

  1. <template>
  2. <el-breadcrumb :separator-icon="ArrowRight">
  3. <el-breadcrumb-item
  4. v-for="item in routes"
  5. :key="item.path"
  6. >
  7. {{ item.meta.title }}
  8. <i class="el-icon-edit" />
  9. </el-breadcrumb-item>
  10. </el-breadcrumb>
  11. </template>
  12. <script lang="ts" setup>
  13. import { computed } from 'vue'
  14. import { useRouter } from 'vue-router'
  15. import { ArrowRight } from '@element-plus/icons-vue'
  16. const router = useRouter()
  17. console.log(router.currentRoute.value.matched)
  18. const routes = computed(() => {
  19. return router.currentRoute.value.matched.filter(item => item.meta.title)
  20. })
  21. </script>
  22. <style lang="scss" scoped></style>

2. 自定义路由元数据

也就是meta

编辑src / router / modules / product.tsx

  1. import { RouteRecordRaw, RouterView } from 'vue-router'
  2. const routes: RouteRecordRaw = {
  3. path: 'product',
  4. component: RouterView,
  5. meta: {
  6. title: '商品'
  7. },
  8. children: [
  9. {
  10. path: 'product_list',
  11. name: 'product-list',
  12. component: () => import('@/views/product/list/index.vue'),
  13. meta: { // 自定义路由元数据
  14. title: '商品列表'
  15. }
  16. },
  17. {
  18. path: 'product_attr',
  19. name: 'product-attr',
  20. component: () => import('@/views/product/attr/index.vue'),
  21. meta: {
  22. title: '商品规格'
  23. }
  24. },
  25. {
  26. path: 'product_classify',
  27. name: 'product-classify',
  28. component: () => import('@/views/product/classify/index.vue'),
  29. meta: {
  30. title: '商品分类'
  31. }
  32. },
  33. {
  34. path: 'product_reply',
  35. name: 'product-reply',
  36. component: () => import('@/views/product/reply/index.vue'),
  37. meta: {
  38. title: '商品评论'
  39. }
  40. }
  41. ]
  42. }
  43. export default routes

以及首页,src / router / index.tsx

  1. ...
  2. import orderRoutes from './modules/order'
  3. const routes: RouteRecordRaw[] = [
  4. {
  5. path: '/',
  6. component: AppLayout,
  7. children: [
  8. {
  9. path: '', // 默认子路由
  10. meta: {
  11. title: '首页'
  12. },
  13. ...

3. 样式展示

image.png

4. meta的类型补充

而当我导入路由时,我发现meta属性的类型是未知的,能不能给他定义类型呢?

image.png

于是,我翻Vue Router的官方网站,惊喜地找到了,在路由元信息,可以通过扩展RoueMeta来做到。(后来不知道什么原因,官方文档删除了TS的这一块)

  1. import 'vue-router'
  2. // 声明一个模块,vue-router, 给他的RoureMeta做类型补充声明
  3. declare module 'vue-router' {
  4. // eslint-disable-next-line no-unused-vars
  5. interface RouteMeta {
  6. // 是可选的
  7. isAdmin?: boolean
  8. // 每个路由都必须声明
  9. requiresAuth: boolean
  10. }
  11. }

那么项目里则不用必须声明

  1. import 'vue-router'
  2. // 声明一个模块,vue-router, 给他的RoureMeta做类型补充声明
  3. declare module 'vue-router' {
  4. // eslint-disable-next-line no-unused-vars
  5. interface RouteMeta {
  6. title: string
  7. }
  8. }

image.png

这不就完成了!!

六. 全屏

参照MDN的H5全屏API

https://developer.mozilla.org/zh-CN/docs/Web/API/Fullscreen_API

1. 全屏组件

新建src / AppHeader / FullScreen.vue文件

  1. <template>
  2. <el-icon
  3. @click="toggleFullScreen"
  4. style="cursor: pointer;"
  5. >
  6. <full-screen />
  7. </el-icon>
  8. </template>
  9. <script lang="ts" setup>
  10. import { FullScreen } from '@element-plus/icons-vue'
  11. const toggleFullScreen = () => {
  12. if (!document.fullscreenElement) {
  13. document.documentElement.requestFullscreen()
  14. } else {
  15. if (document.exitFullscreen) {
  16. document.exitFullscreen()
  17. }
  18. }
  19. }
  20. </script>
  21. <style lang="scss" scoped></style>

2. AppHeader汇总

编辑src / AppHeader / index.vue

  1. <template>
  2. <el-space>
  3. <ToggleSidebar />
  4. <BreadcrumbVue />
  5. </el-space>
  6. <el-space>
  7. <FullScreen />
  8. <h1>header1</h1>
  9. </el-space>
  10. </template>
  11. <script lang="ts" setup>
  12. import ToggleSidebar from './ToggleSidebar.vue'
  13. import BreadcrumbVue from './Breadcrumb.vue'
  14. import FullScreen from './FullScreen.vue'
  15. </script>
  16. <style lang="scss" scoped></style>

3. 样式展示

image.png

七. 页面加载进度条

参考github的NProgress

在页面还没加载完成的时候出现进度条,是不是更好玩,也会提高用户体验。

基本使用

  1. NProgress.start();
  2. NProgress.done();

1. 进度条组件
  1. # . 第一步安装
  2. npm install --save nprogress
  3. # .ts 类型补充
  4. npm i --save-dev @types/nprogress

2. 路由配置

使用路由拦截器,设置每个路由导航前开启和导航完成以后完成加载。

  1. 全局前置守卫router.beforeEach(()=>{}).所有页面的导航都会经过这
  2. 全局后置守卫router.afterEach(()=>{})

编辑src / router / index.ts文件

  1. import nProgress from 'nprogress'
  2. const router = createRouter({
  3. ...
  4. })
  5. router.beforeEach(() => {
  6. nProgress.start() // 开始加载进度条nProgress
  7. })
  8. router.afterEach(() => {
  9. nProgress.done() // 结束进度条
  10. })

3. 样式展示

image.png