branch_cwy
cwy 3 months ago
parent b50b497f91
commit efcacb5985

@ -1,8 +1,13 @@
<template> <template>
<!-- el-breadcrumb 组件用于创建面包屑导航这里给它添加了类名 "app-breadcrumb"并设置分隔符为 "/"用于分隔面包屑中的各个节点 -->
<el-breadcrumb class="app-breadcrumb" separator="/"> <el-breadcrumb class="app-breadcrumb" separator="/">
<!-- transition-group 组件用于对一组元素进行过渡动画效果的包裹这里命名为 "breadcrumb"它会根据内部元素的添加删除移动等操作自动应用过渡动画常用于展示动态变化的元素列表此处用于面包屑导航项的过渡效果处理 -->
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<!-- 使用 v-for 指令遍历 levelList 数组创建多个 el-breadcrumb-item 组件面包屑导航的每一项并将数组中的每个元素item以及对应的索引index传递到循环体内部使用同时通过 :key 绑定每个项的唯一标识这里使用 item.path确保 Vue 能够正确识别每个元素的变化 -->
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path" v-if="item.meta.title"> <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path" v-if="item.meta.title">
<!-- 根据条件判断来决定面包屑导航项的显示方式如果该项的 redirect 属性等于 "noredirect" 或者当前项是 levelList 数组中的最后一项index == levelList.length - 1则使用 span 标签显示该项的标题item.meta.title并且添加类名 "no-redirect"用于后续样式设置 -->
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span> <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
<!-- 如果不满足上述条件即可以进行路由跳转的情况则使用 router-link 组件创建一个路由链接通过 :to 属性绑定要跳转的目标路径优先使用 item.redirect如果不存在则使用 item.path并在链接中显示该项的标题item.meta.title点击该链接可导航到对应的路由页面 -->
<router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link> <router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>
@ -11,14 +16,17 @@
<script> <script>
export default { export default {
// created this.getBreadcrumb
created() { created() {
this.getBreadcrumb() this.getBreadcrumb()
}, },
data() { data() {
return { return {
// levelList null getBreadcrumb
levelList: null levelList: null
} }
}, },
// watch $routeVue this.getBreadcrumb
watch: { watch: {
$route() { $route() {
this.getBreadcrumb() this.getBreadcrumb()
@ -26,11 +34,15 @@ export default {
}, },
methods: { methods: {
getBreadcrumb() { getBreadcrumb() {
// this.$route.matched 使 filter name name 便 matched
let matched = this.$route.matched.filter(item => item.name) let matched = this.$route.matched.filter(item => item.name)
const first = matched[0] const first = matched[0]
if (first && first.name !== 'home') { // name 'home'
if (first && first.name!== 'home') {
// matched '/home'meta ''
matched = [{ path: '/home', meta: { title: '首页' }}].concat(matched) matched = [{ path: '/home', meta: { title: '首页' }}].concat(matched)
} }
// matched levelList 使
this.levelList = matched this.levelList = matched
} }
} }
@ -38,14 +50,22 @@ export default {
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb { // "app-breadcrumb" "el-breadcrumb"
display: inline-block; .app-breadcrumb.el-breadcrumb {
font-size: 14px; // 使
line-height: 50px; display: inline-block;
margin-left: 10px; // 14px
.no-redirect { font-size: 14px;
color: #97a8be; // 50px使
cursor: text; line-height: 50px;
} // 10px使
margin-left: 10px;
// "no-redirect"
.no-redirect {
// #97a8be
color: #97a8be;
// cursor: text
cursor: text;
} }
}
</style> </style>

@ -1,71 +1,113 @@
// 导入Vue构造函数Vue是Vue.js框架的核心用于创建Vue实例以及进行各种组件化开发相关操作
import Vue from 'vue' import Vue from 'vue'
// 导入Vue Router插件Vue Router用于在Vue.js应用中实现路由功能管理不同页面路由组件之间的切换和导航
import Router from 'vue-router' import Router from 'vue-router'
// 使用Vue.use方法来安装Vue Router插件使其能够在Vue应用中被使用这一步是必要的初始化操作
Vue.use(Router) Vue.use(Router)
/* Layout */ /* Layout */
// 从指定的路径 '../views/layout/Layout' 导入一个名为Layout的组件通常这个组件可能是整个应用的布局框架组件包含侧边栏、顶部导航栏等通用布局结构其他页面组件会在这个布局框架内进行展示
import Layout from '../views/layout/Layout' import Layout from '../views/layout/Layout'
/** /**
* 以下是一段注释用于对路由配置对象中的一些属性进行说明方便后续理解和维护路由配置
* hidden: true if `hidden:true` will not show in the sidebar(default is false) * hidden: true if `hidden:true` will not show in the sidebar(default is false)
* 解释如果路由配置中的 `hidden` 属性设置为 `true`则对应的路由对应的菜单项将不会显示在侧边栏中默认值为 `false`即默认会显示在侧边栏
*
* alwaysShow: true if set true, will always show the root menu, whatever its child routes length * alwaysShow: true if set true, will always show the root menu, whatever its child routes length
* if not set alwaysShow, only more than one route under the children * if not set alwaysShow, only more than one route under the children
* it will becomes nested mode, otherwise not show the root menu * it will becomes nested mode, otherwise not show the root menu
* 解释如果 `alwaysShow` 属性设置为 `true`无论该路由下的子路由数量是多少都会始终显示根菜单如果不设置这个属性只有当子路由数量大于1时才会呈现嵌套菜单模式显示根菜单以及子菜单否则不会显示根菜单
*
* redirect: noredirect if `redirect:noredirect` will no redirct in the breadcrumb * redirect: noredirect if `redirect:noredirect` will no redirct in the breadcrumb
* 解释如果 `redirect` 属性设置为 `noredirect`在面包屑导航中不会进行重定向操作通常面包屑导航会根据路由跳转情况进行相应的更新显示这里可用于特殊情况的控制
*
* name:'router-name' the name is used by <keep-alive> (must set!!!) * name:'router-name' the name is used by <keep-alive> (must set!!!)
* 解释`name` 属性用于给路由命名这个名称会被 `<keep-alive>` 组件使用必须设置在进行组件缓存等相关功能时需要通过这个名称来识别路由对应的组件
*
* meta : { * meta : {
title: 'title' the name show in submenu and breadcrumb (recommend set) title: 'title' the name show in submenu and breadcrumb (recommend set)
icon: 'svg-name' the icon show in the sidebar, icon: 'svg-name' the icon show in the sidebar,
} }
* 解释`meta` 是一个自定义的元数据对象可以在路由配置中添加额外的信息其中 `title` 属性用于设置在子菜单和面包屑导航中显示的名称建议设置方便用户直观了解当前页面信息`icon` 属性用于指定在侧边栏中显示的图标方便通过图标区分不同的菜单功能
**/ **/
// 定义一个名为constantRouterMap的常量数组用于存放基础的、固定不变的路由配置信息这些路由通常是应用中始终存在且不需要动态加载的部分
export const constantRouterMap = [ export const constantRouterMap = [
// 定义一个登录页面的路由配置对象,路径为 '/login',对应的组件通过动态导入的方式加载(使用 () => import('@/views/login/index') 这种语法,会在实际访问该路由时才加载对应的组件代码,提高初始加载性能),并且设置 `hidden` 属性为 `true`,意味着这个登录页面不会显示在侧边栏中
{path: '/login', component: () => import('@/views/login/index'), hidden: true}, {path: '/login', component: () => import('@/views/login/index'), hidden: true},
// 定义一个404页面的路由配置对象路径为 '/404',同样通过动态导入加载对应的组件,也设置 `hidden` 属性为 `true`,该页面不会在侧边栏展示,一般用于处理未匹配到的路由情况
{path: '/404', component: () => import('@/views/404'), hidden: true}, {path: '/404', component: () => import('@/views/404'), hidden: true},
{ {
// 定义一个根路径(空字符串表示应用的根路径)的路由配置,它会作为应用的基础布局框架
path: '', path: '',
// 指定该根路径对应的组件为之前导入的Layout组件也就是整个页面的布局框架会围绕这个组件展开
component: Layout, component: Layout,
// 设置重定向路径为 '/home',意味着当访问根路径时,会自动跳转到 '/home' 这个子路径对应的页面
redirect: '/home', redirect: '/home',
// 在路由的元数据meta中设置标题为 '首页',图标为 'home',这两个信息会分别用于侧边栏菜单的显示和面包屑导航等地方的展示
meta: {title: '首页', icon: 'home'}, meta: {title: '首页', icon: 'home'},
children: [{ // 定义该路由下的子路由数组,每个子路由对应一个具体的页面组件
path: 'home', children: [
name: 'home', {
component: () => import('@/views/home/index'), // 子路由的路径为 'home',也就是在根路径下访问 'home' 会对应这个路由配置
meta: {title: '仪表盘', icon: 'dashboard'} path: 'home',
}, // 给这个路由命名为 'home',用于后续的路由导航、组件缓存等相关操作
{ name: 'home',
name: 'document', // 通过动态导入加载对应的组件,这里对应的是 '@/views/home/index' 这个页面组件,一般是应用的仪表盘页面之类的主页面
path: 'https://www.macrozheng.com', component: () => import('@/views/home/index'),
meta: {title: '学习教程', icon: 'document'} // 在子路由的元数据中设置标题为 '仪表盘',图标为 'dashboard',同样用于展示相关信息
}, meta: {title: '仪表盘', icon: 'dashboard'}
{ },
name: 'video', {
path: 'https://www.macrozheng.com/mall/catalog/mall_video.html', // 给这个路由命名为 'document',路径是一个外部链接 'https://www.macrozheng.com',可能用于跳转到外部学习教程网站
meta: {title: '视频教程', icon: 'video'} name: 'document',
}, path: 'https://www.macrozheng.com',
// 在元数据中设置标题为 '学习教程',图标为 'document',用于侧边栏等地方显示相关信息
meta: {title: '学习教程', icon: 'document'}
},
{
// 给这个路由命名为 'video',路径是另一个外部链接 'https://www.macrozheng.com/mall/catalog/mall_video.html',可能用于跳转到外部视频教程页面
name: 'video',
path: 'https://www.macrozheng.com/mall/catalog/mall_video.html',
// 在元数据中设置标题为 '视频教程',图标为 'video',用于展示相关信息
meta: {title: '视频教程', icon: 'video'}
},
] ]
} }
] ]
// 定义一个名为asyncRouterMap的常量数组用于存放需要动态加载的路由配置信息通常这些路由对应的模块可能比较大或者是根据用户权限等情况动态决定是否加载的部分
export const asyncRouterMap = [ export const asyncRouterMap = [
{ {
// 定义一个商品管理模块pms相关的路由配置路径为 '/pms'对应的组件是之前导入的Layout组件作为布局框架
path: '/pms', path: '/pms',
component: Layout, component: Layout,
// 设置重定向路径为 '/pms/product',访问 '/pms' 时会自动跳转到商品列表页面相关的子路由
redirect: '/pms/product', redirect: '/pms/product',
// 给这个路由命名为 'pms',方便后续操作识别
name: 'pms', name: 'pms',
// 在路由的元数据中设置标题为 '商品',图标为 'product',用于侧边栏菜单等地方显示
meta: {title: '商品', icon: 'product'}, meta: {title: '商品', icon: 'product'},
children: [{ // 定义该路由下的子路由数组,包含商品管理相关的各个具体功能页面的路由配置
path: 'product', children: [
name: 'product',
component: () => import('@/views/pms/product/index'),
meta: {title: '商品列表', icon: 'product-list'}
},
{ {
// 商品列表页面的子路由,路径为 'product',命名为 'product',通过动态导入加载对应的组件,用于展示商品列表信息
path: 'product',
name: 'product',
component: () => import('@/views/pms/product/index'),
meta: {title: '商品列表', icon: 'product-list'}
},
{
// 添加商品页面的子路由,命名为 'addProduct',通过动态导入加载对应组件,用于添加商品的操作页面
path: 'addProduct', path: 'addProduct',
name: 'addProduct', name: 'addProduct',
component: () => import('@/views/pms/product/add'), component: () => import('@/views/pms/product/add'),
meta: {title: '添加商品', icon: 'product-add'} meta: {title: '添加商品', icon: 'product-add'}
}, },
{ {
// 修改商品页面的子路由,命名为 'updateProduct',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,意味着这个页面不会显示在侧边栏等常规菜单中,可能是通过其他方式(比如在商品列表页点击编辑按钮进入)访问
path: 'updateProduct', path: 'updateProduct',
name: 'updateProduct', name: 'updateProduct',
component: () => import('@/views/pms/product/update'), component: () => import('@/views/pms/product/update'),
@ -73,12 +115,14 @@ export const asyncRouterMap = [
hidden: true hidden: true
}, },
{ {
// 商品分类页面的子路由,命名为 'productCate',通过动态导入加载对应组件,用于商品分类管理相关操作
path: 'productCate', path: 'productCate',
name: 'productCate', name: 'productCate',
component: () => import('@/views/pms/productCate/index'), component: () => import('@/views/pms/productCate/index'),
meta: {title: '商品分类', icon: 'product-cate'} meta: {title: '商品分类', icon: 'product-cate'}
}, },
{ {
// 添加商品分类页面的子路由,命名为 'addProductCate',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,不会在常规菜单显示,可能是特定操作下进入的页面
path: 'addProductCate', path: 'addProductCate',
name: 'addProductCate', name: 'addProductCate',
component: () => import('@/views/pms/productCate/add'), component: () => import('@/views/pms/productCate/add'),
@ -86,6 +130,7 @@ export const asyncRouterMap = [
hidden: true hidden: true
}, },
{ {
// 修改商品分类页面的子路由,命名为 'updateProductCate',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,同样不在常规菜单展示,用于特定的修改操作入口
path: 'updateProductCate', path: 'updateProductCate',
name: 'updateProductCate', name: 'updateProductCate',
component: () => import('@/views/pms/productCate/update'), component: () => import('@/views/pms/productCate/update'),
@ -93,12 +138,14 @@ export const asyncRouterMap = [
hidden: true hidden: true
}, },
{ {
// 商品类型页面的子路由,命名为 'productAttr',通过动态导入加载对应组件,用于管理商品类型相关功能
path: 'productAttr', path: 'productAttr',
name: 'productAttr', name: 'productAttr',
component: () => import('@/views/pms/productAttr/index'), component: () => import('@/views/pms/productAttr/index'),
meta: {title: '商品类型', icon: 'product-attr'} meta: {title: '商品类型', icon: 'product-attr'}
}, },
{ {
// 商品属性列表页面的子路由,命名为 'productAttrList',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,不常规显示,可能是特定的查看属性列表的入口
path: 'productAttrList', path: 'productAttrList',
name: 'productAttrList', name: 'productAttrList',
component: () => import('@/views/pms/productAttr/productAttrList'), component: () => import('@/views/pms/productAttr/productAttrList'),
@ -106,6 +153,7 @@ export const asyncRouterMap = [
hidden: true hidden: true
}, },
{ {
// 添加商品属性页面的子路由,命名为 'addProductAttr',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,不是常规菜单可见的页面,用于添加商品属性操作入口
path: 'addProductAttr', path: 'addProductAttr',
name: 'addProductAttr', name: 'addProductAttr',
component: () => import('@/views/pms/productAttr/addProductAttr'), component: () => import('@/views/pms/productAttr/addProductAttr'),
@ -113,6 +161,7 @@ export const asyncRouterMap = [
hidden: true hidden: true
}, },
{ {
// 修改商品属性页面的子路由,命名为 'updateProductAttr',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,不在常规菜单展示,用于修改商品属性的操作入口
path: 'updateProductAttr', path: 'updateProductAttr',
name: 'updateProductAttr', name: 'updateProductAttr',
component: () => import('@/views/pms/productAttr/updateProductAttr'), component: () => import('@/views/pms/productAttr/updateProductAttr'),
@ -120,12 +169,14 @@ export const asyncRouterMap = [
hidden: true hidden: true
}, },
{ {
// 品牌管理页面的子路由,命名为 'brand',通过动态导入加载对应组件,用于品牌管理相关操作,比如查看、编辑品牌等
path: 'brand', path: 'brand',
name: 'brand', name: 'brand',
component: () => import('@/views/pms/brand/index'), component: () => import('@/views/pms/brand/index'),
meta: {title: '品牌管理', icon: 'product-brand'} meta: {title: '品牌管理', icon: 'product-brand'}
}, },
{ {
// 添加品牌页面的子路由,命名为 'addBrand',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,不是常规显示的页面,用于添加品牌的操作入口
path: 'addBrand', path: 'addBrand',
name: 'addBrand', name: 'addBrand',
component: () => import('@/views/pms/brand/add'), component: () => import('@/views/pms/brand/add'),
@ -133,6 +184,7 @@ export const asyncRouterMap = [
hidden: true hidden: true
}, },
{ {
// 编辑品牌页面的子路由,命名为 'updateBrand',通过动态导入加载对应组件,设置 `hidden` 属性为 `true`,不在常规菜单出现,用于编辑品牌的操作入口
path: 'updateBrand', path: 'updateBrand',
name: 'updateBrand', name: 'updateBrand',
component: () => import('@/views/pms/brand/update'), component: () => import('@/views/pms/brand/update'),
@ -142,6 +194,8 @@ export const asyncRouterMap = [
] ]
}, },
{ {
// 订单管理模块oms相关的路由配置路径为 '/oms'对应Layout组件作为布局框架以下类似的结构都是对订单管理各功能页面路由的详细配置
path: '/oms', path: '/oms',
component: Layout, component: Layout,
redirect: '/oms/order', redirect: '/oms/order',

@ -1,57 +1,84 @@
// id "app"
#app { #app {
// // "main-container"
.main-container { .main-container {
// 100%
min-height: 100%; min-height: 100%;
transition: margin-left .28s; // margin-left 0.28
transition: margin-left.28s;
// 180px使
margin-left: 180px; margin-left: 180px;
} }
// // "sidebar-container"
.sidebar-container { .sidebar-container {
// "horizontal-collapse-transition"
.horizontal-collapse-transition { .horizontal-collapse-transition {
// 0 widthpadding-leftpadding-right使 ease-in-out 0
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
} }
transition: width .28s; // 0.28
width: 180px !important; transition: width.28s;
// 180px"!important" 使
width: 180px!important;
// 100%使
height: 100%; height: 100%;
// fixed使
position: fixed; position: fixed;
// 0px访
font-size: 0px; font-size: 0px;
// 0
top: 0; top: 0;
// 0 height: 100% 使
bottom: 0; bottom: 0;
// 0使
left: 0; left: 0;
// z-index 1001
z-index: 1001; z-index: 1001;
//
overflow: hidden; overflow: hidden;
// <a> 使 100%
a { a {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
} }
// "svg-icon" SVG 16px
.svg-icon { .svg-icon {
margin-right: 16px; margin-right: 16px;
} }
// "el-menu" 使 Element UI border: none 100%
.el-menu { .el-menu {
border: none; border: none;
width: 100% !important; width: 100%!important;
} }
} }
// "hideSidebar"
.hideSidebar { .hideSidebar {
// "hideSidebar" "sidebar-container" 36px
.sidebar-container { .sidebar-container {
width: 36px !important; width: 36px!important;
} }
// "hideSidebar" "main-container" 36px使
.main-container { .main-container {
margin-left: 36px; margin-left: 36px;
} }
// "submenu-title-noDropdown" 10px"!important" position: relative便
.submenu-title-noDropdown { .submenu-title-noDropdown {
padding-left: 10px !important; padding-left: 10px!important;
position: relative; position: relative;
// "el-tooltip" 0 10px
.el-tooltip { .el-tooltip {
padding: 0 10px !important; padding: 0 10px!important;
} }
} }
// "el-submenu"
.el-submenu { .el-submenu {
// "el-submenu" "el-submenu__title" 10px
&>.el-submenu__title { &>.el-submenu__title {
padding-left: 10px !important; padding-left: 10px!important;
// "el-submenu__title" <span> 0 overflow: hidden visibility: hidden
&>span { &>span {
height: 0; height: 0;
width: 0; width: 0;
@ -59,6 +86,7 @@
visibility: hidden; visibility: hidden;
display: inline-block; display: inline-block;
} }
// "el-submenu__title" "el-submenu__icon-arrow" display: none
.el-submenu__icon-arrow { .el-submenu__icon-arrow {
display: none; display: none;
} }
@ -66,28 +94,34 @@
} }
} }
.sidebar-container .nest-menu .el-submenu>.el-submenu__title, // "sidebar-container" "nest-menu" "el-submenu" "el-submenu__title" "sidebar-container" "el-menu-item" 180px $subMenuBg $subMenuBg SCSS
.sidebar-container .el-submenu .el-menu-item { .sidebar-container.nest-menu.el-submenu>.el-submenu__title,
min-width: 180px !important; .sidebar-container.el-submenu.el-menu-item {
background-color: $subMenuBg !important; min-width: 180px!important;
background-color: $subMenuBg!important;
// $menuHover SCSS
&:hover { &:hover {
background-color: $menuHover !important; background-color: $menuHover!important;
} }
} }
.el-menu--collapse .el-menu .el-submenu { // "el-menu--collapse" "el-menu" "el-submenu" 180px
min-width: 180px !important; .el-menu--collapse.el-menu.el-submenu {
min-width: 180px!important;
} }
// // "mobile"
.mobile { .mobile {
// "main-container" 0px使
.main-container { .main-container {
margin-left: 0px; margin-left: 0px;
} }
// "sidebar-container" 50px 0.28 180px
.sidebar-container { .sidebar-container {
top: 50px; top: 50px;
transition: transform .28s; transition: transform.28s;
width: 180px !important; width: 180px!important;
} }
// "mobile" "hideSidebar" "sidebar-container" 0.3s transform 180px使 translate3d 3D X
&.hideSidebar { &.hideSidebar {
.sidebar-container { .sidebar-container {
transition-duration: 0.3s; transition-duration: 0.3s;
@ -96,7 +130,9 @@
} }
} }
// "withoutAnimation"
.withoutAnimation { .withoutAnimation {
// "withoutAnimation" "main-container" "sidebar-container" none使
.main-container, .main-container,
.sidebar-container { .sidebar-container {
transition: none; transition: none;

@ -1,54 +1,78 @@
// 这个函数用于将给定的时间数据按照指定的格式进行格式化处理支持传入不同类型的时间表示形式如时间戳、Date对象等并转化为格式化后的字符串
export function parseTime(time, cFormat) { export function parseTime(time, cFormat) {
// 如果没有传入任何参数arguments.length === 0表示参数个数为0则直接返回null表示无法进行时间格式化操作
if (arguments.length === 0) { if (arguments.length === 0) {
return null return null
} }
// 如果没有传入自定义的格式化字符串cFormat则使用默认的格式化字符串这里定义了一个包含年、月、日、时、分、秒占位符的格式模板
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date let date
// 判断传入的时间参数是否已经是一个Date对象如果是则直接将其赋值给date变量后续用于提取时间信息进行格式化
if (typeof time === 'object') { if (typeof time === 'object') {
date = time date = time
} else { } else {
// 如果传入的时间参数不是Date对象先进行以下处理
// 如果传入的时间字符串长度为10位通常表示以秒为单位的时间戳比如从后端获取的时间戳可能是这种形式则将其转换为以毫秒为单位的时间戳乘以1000因为JavaScript中Date对象的构造函数接收的是以毫秒为单位的时间戳
if (('' + time).length === 10) time = parseInt(time) * 1000 if (('' + time).length === 10) time = parseInt(time) * 1000
// 使用转换后的时间戳或者本身就是以毫秒为单位的时间戳创建一个Date对象用于后续获取年、月、日等时间信息
date = new Date(time) date = new Date(time)
} }
// 创建一个对象用于存储从Date对象中提取出的各个时间单位对应的数值键名与格式化字符串中的占位符相对应
const formatObj = { const formatObj = {
y: date.getFullYear(), y: date.getFullYear(), // 获取年份例如2024
m: date.getMonth() + 1, m: date.getMonth() + 1, // 获取月份JavaScript中月份是从0开始计数的所以要加1范围是1 - 12
d: date.getDate(), d: date.getDate(), // 获取日期即一个月中的第几天范围是1 - 31
h: date.getHours(), h: date.getHours(), // 获取小时范围是0 - 23
i: date.getMinutes(), i: date.getMinutes(), // 获取分钟范围是0 - 59
s: date.getSeconds(), s: date.getSeconds(), // 获取秒范围是0 - 59
a: date.getDay() a: date.getDay() // 获取星期几返回值是0星期日 - 6星期六
} }
// 使用正则表达式替换格式化字符串中的占位符,将其替换为实际的时间数值对应的字符串表示形式
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key] let value = formatObj[key]
// 如果占位符是 'a'表示星期几则将数字形式的星期几转换为中文汉字形式例如1转换为“一”表示星期一
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
// 如果占位符对应的时间数值小于10并且占位符长度大于0防止出现类似 {0} 这种不需要补0的情况则在数值前面补0使其格式更规范例如1变为01
if (result.length > 0 && value < 10) { if (result.length > 0 && value < 10) {
value = '0' + value value = '0' + value
} }
// 返回处理后的时间数值对应的字符串如果数值为0或者不存在比如没有获取到对应的时间信息则返回0
return value || 0 return value || 0
}) })
// 返回最终格式化后的时间字符串
return time_str return time_str
} }
// 这个函数用于根据时间与当前时间的时间差以相对友好的格式来展示时间例如“刚刚”、“10分钟前”等如果时间差较大或者传入了格式化选项则按照指定格式进行格式化展示
export function formatTime(time, option) { export function formatTime(time, option) {
// 将传入的时间参数转换为以毫秒为单位的时间戳假设传入的是以秒为单位的时间戳乘以1000进行转换方便后续进行时间差的计算等操作
time = +time * 1000 time = +time * 1000
// 根据转换后的时间戳创建一个Date对象用于获取时间信息
const d = new Date(time) const d = new Date(time)
// 获取当前时间的时间戳(以毫秒为单位)
const now = Date.now() const now = Date.now()
// 计算当前时间与给定时间的时间差单位为秒通过将时间戳差值除以1000得到
const diff = (now - d) / 1000 const diff = (now - d) / 1000
// 如果时间差小于30秒直接返回“刚刚”表示时间很近
if (diff < 30) { if (diff < 30) {
return '刚刚' return '刚刚'
} else if (diff < 3600) { // less 1 hour } else if (diff < 3600) { // 如果时间差小于1小时1小时等于3600秒
// 计算时间差对应的分钟数向上取整并返回类似“10分钟前”的格式
return Math.ceil(diff / 60) + '分钟前' return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) { } else if (diff < 3600 * 24) { // 如果时间差小于1天1天等于3600 * 24秒
// 计算时间差对应的小时数向上取整并返回类似“3小时前”的格式
return Math.ceil(diff / 3600) + '小时前' return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) { } else if (diff < 3600 * 24 * 2) { // 如果时间差小于2天
// 直接返回“1天前”
return '1天前' return '1天前'
} }
// 如果传入了格式化选项option则调用parseTime函数按照传入的选项格式来格式化时间并返回
if (option) { if (option) {
return parseTime(time, option) return parseTime(time, option)
} else { } else {
// 如果没有传入格式化选项则以“月日时分”的格式返回时间例如“10月15日12时30分”
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分' return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
} }
} }

@ -1,36 +1,53 @@
<template> <template>
<!-- 最外层的 div 作为整个应用布局的包裹容器通过绑定 :class 属性动态地根据计算属性 classObj 的值来添加相应的类名 -->
<div class="app-wrapper" :class="classObj"> <div class="app-wrapper" :class="classObj">
<!-- 引入自定义的 Sidebar 组件用于展示侧边栏内容给它添加了一个类名 "sidebar-container"方便后续进行样式设置 -->
<sidebar class="sidebar-container"></sidebar> <sidebar class="sidebar-container"></sidebar>
<!-- 这是主要内容的容器 div用于放置页面中除侧边栏之外的主体部分 -->
<div class="main-container"> <div class="main-container">
<!-- 引入自定义的 Navbar 组件用于展示页面顶部的导航栏 -->
<navbar></navbar> <navbar></navbar>
<!-- 引入自定义的 AppMain 组件用于展示页面主体的核心内容 -->
<app-main></app-main> <app-main></app-main>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
// components NavbarSidebar AppMain 便使
import { Navbar, Sidebar, AppMain } from './components' import { Navbar, Sidebar, AppMain } from './components'
// mixin ResizeHandler ResizeMixinmixin
import ResizeMixin from './mixin/ResizeHandler' import ResizeMixin from './mixin/ResizeHandler'
export default { export default {
name: 'layout', name: 'layout',
// components 使 NavbarSidebar AppMain 使
components: { components: {
Navbar, Navbar,
Sidebar, Sidebar,
AppMain AppMain
}, },
// 使 mixins ResizeMixinmixin layout
mixins: [ResizeMixin], mixins: [ResizeMixin],
computed: { computed: {
// sidebar Vuex store app sidebar
// 使 sidebar
sidebar() { sidebar() {
return this.$store.state.app.sidebar return this.$store.state.app.sidebar
}, },
// device Vuex app device 'mobile'
device() { device() {
return this.$store.state.app.device return this.$store.state.app.device
}, },
// classObj sidebar device
//
classObj() { classObj() {
return { return {
hideSidebar: !this.sidebar.opened, // !this.sidebar.opened hideSidebar
hideSidebar:!this.sidebar.opened,
// withoutAnimation true withoutAnimation
withoutAnimation: this.sidebar.withoutAnimation, withoutAnimation: this.sidebar.withoutAnimation,
// 'mobile' this.device ==='mobile' mobile 便
mobile: this.device === 'mobile' mobile: this.device === 'mobile'
} }
} }
@ -38,12 +55,44 @@ export default {
} }
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <script>
@import "src/styles/mixin.scss"; // components NavbarSidebar AppMain 便使
.app-wrapper { import { Navbar, Sidebar, AppMain } from './components'
@include clearfix; // mixin ResizeHandler ResizeMixinmixin
position: relative; import ResizeMixin from './mixin/ResizeHandler'
height: 100%;
width: 100%; export default {
name: 'layout',
// components 使 NavbarSidebar AppMain 使
components: {
Navbar,
Sidebar,
AppMain
},
// 使 mixins ResizeMixinmixin layout
mixins: [ResizeMixin],
computed: {
// sidebar Vuex store app sidebar
// 使 sidebar
sidebar() {
return this.$store.state.app.sidebar
},
// device Vuex app device 'mobile'
device() {
return this.$store.state.app.device
},
// classObj sidebar device
//
classObj() {
return {
// !this.sidebar.opened hideSidebar
hideSidebar:!this.sidebar.opened,
// withoutAnimation true withoutAnimation
withoutAnimation: this.sidebar.withoutAnimation,
// 'mobile' this.device ==='mobile' mobile 便
mobile: this.device === 'mobile'
}
}
} }
</style> }
</script>

@ -1,38 +1,57 @@
// 从项目的 @/store 目录下导入 Vuex 的 store 实例,这个 store 通常用于管理应用中的状态数据以及处理相关的业务逻辑,例如控制侧边栏的显示隐藏、设备类型的切换等操作
import store from '@/store' import store from '@/store'
// 通过解构赋值从 document 对象中获取 body 属性,后续可能会基于这个 body 元素来获取页面相关的尺寸等信息,比如判断页面宽度是否满足移动端的条件
const { body } = document const { body } = document
// 定义一个常量 WIDTH表示一个宽度的阈值单位应该是像素这里设置为 1024px可能用于区分移动端和桌面端设备比如页面宽度小于这个值时认为是移动端
const WIDTH = 1024 const WIDTH = 1024
// 定义一个常量 RATIO具体含义可能和页面尺寸判断逻辑相关结合代码来看可能是在判断页面宽度是否符合移动端条件时需要减去的一个值
const RATIO = 3 const RATIO = 3
export default { export default {
// watch 选项用于监听组件中的数据变化,这里监听了 $routeVue 路由对象)的变化
watch: { watch: {
$route(route) { $route(route) {
// 判断当前设备是否为移动端(通过 this.device ==='mobile' 判断,这里的 this.device 应该是组件中的某个属性用于标识设备类型并且侧边栏是打开状态this.sidebar.opened
if (this.device === 'mobile' && this.sidebar.opened) { if (this.device === 'mobile' && this.sidebar.opened) {
// 如果满足上述条件,通过 store.dispatch 方法触发 Vuex store 中的 'CloseSideBar' 这个 action传入一个配置对象 { withoutAnimation: false },表示关闭侧边栏,并且关闭时是否有动画效果设置为 false可能在对应的 action 实现中有相应的处理逻辑来根据这个配置关闭侧边栏并控制动画展示情况)
store.dispatch('CloseSideBar', { withoutAnimation: false }) store.dispatch('CloseSideBar', { withoutAnimation: false })
} }
} }
}, },
// beforeMount 生命周期钩子函数,在组件挂载到 DOM 之前被调用。这里给 window 对象添加了一个'resize' 事件监听器,当窗口大小发生变化时,会触发 this.resizeHandler 方法,用于处理窗口大小变化相关的逻辑
beforeMount() { beforeMount() {
window.addEventListener('resize', this.resizeHandler) window.addEventListener('resize', this.resizeHandler)
}, },
// mounted 生命周期钩子函数,在组件挂载到 DOM 之后被调用。这里首先调用 this.isMobile 方法来判断当前设备是否为移动端
mounted() { mounted() {
const isMobile = this.isMobile() const isMobile = this.isMobile()
if (isMobile) { if (isMobile) {
// 如果是移动端,通过 store.dispatch 方法触发 Vuex store 中的 'ToggleDevice' 这个 action传入'mobile' 字符串参数,可能用于在 store 中更新设备类型的状态为移动端,以便后续根据这个状态进行相应的布局或功能调整
store.dispatch('ToggleDevice', 'mobile') store.dispatch('ToggleDevice', 'mobile')
// 同时触发 'CloseSideBar' 这个 action传入 { withoutAnimation: true },表示关闭侧边栏并且关闭过程不显示动画,用于在移动端初始加载时关闭侧边栏以适应移动端布局等情况
store.dispatch('CloseSideBar', { withoutAnimation: true }) store.dispatch('CloseSideBar', { withoutAnimation: true })
} }
}, },
methods: { methods: {
// 定义一个名为 isMobile 的方法,用于判断当前设备是否为移动端
isMobile() { isMobile() {
// 通过 body.getBoundingClientRect() 方法获取 body 元素的尺寸信息(返回一个包含元素的位置、宽度、高度等信息的 DOMRect 对象)
const rect = body.getBoundingClientRect() const rect = body.getBoundingClientRect()
// 判断页面可视区域的宽度rect.width减去常量 RATIO 的值是否小于定义的 WIDTH1024px如果小于则认为当前设备是移动端返回 true否则返回 false
return rect.width - RATIO < WIDTH return rect.width - RATIO < WIDTH
}, },
// 定义 resizeHandler 方法,用于处理窗口大小变化的逻辑,当窗口触发'resize' 事件时会调用这个方法
resizeHandler() { resizeHandler() {
// 判断当前页面是否处于隐藏状态比如切换到其他标签页等情况document.hidden 为 true 表示页面被隐藏),如果页面没有隐藏
if (!document.hidden) { if (!document.hidden) {
// 调用 this.isMobile 方法重新判断当前设备是否变为移动端
const isMobile = this.isMobile() const isMobile = this.isMobile()
store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop') // 通过 store.dispatch 方法触发 Vuex store 中的 'ToggleDevice' 这个 action根据 isMobile 的结果传入'mobile' 或者 'desktop',用于更新 store 中设备类型的状态,使其与当前实际设备类型保持一致
store.dispatch('ToggleDevice', isMobile? 'mobile' : 'desktop')
if (isMobile) { if (isMobile) {
// 如果当前设备变为移动端,触发 'CloseSideBar' 这个 action传入 { withoutAnimation: true },关闭侧边栏并且不显示动画,以适应移动端布局要求
store.dispatch('CloseSideBar', { withoutAnimation: true }) store.dispatch('CloseSideBar', { withoutAnimation: true })
} }
} }

Loading…
Cancel
Save