Default Changelist

feature/qzw
秦泽旺 4 months ago
parent 7061f4db4c
commit 9fbe2efbb6

@ -1,50 +1,51 @@
<template> <template>
<!-- 模板部分定义了组件的HTML结构 -->
<div class="footer"> <div class="footer">
<div class="links"> <div class="links">
<a href="https://github.com/19920625lsg/spring-boot-online-exam" target="_blank">代码仓</a> <!-- 链接区域 -->
<a href="https://19920625lsg.github.io" target="_blank">关于我</a> <a href="https://github.com/19920625lsg/spring-boot-online-exam#34; target="_blank">代码仓</a> <!-- 链接到GitHub仓库 -->
<a href="mailto:liangshanguang2@gmail.com">联系我</a> <a href="https://19920625lsg.github.io#34; target="_blank">关于我</a> <!-- 链接到个人网站 -->
<a href="mailto:liangshanguang2@gmail.com">联系我</a> <!-- 邮件联系链接 -->
</div> </div>
<div class="copyright"> <div class="copyright">
Copyright Copyright <!-- 版权信息 -->
<a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span> <a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span> <!-- 版权所有者 -->
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'GlobalFooter', name: 'GlobalFooter', //
data () { data () {
return {} return {} //
} }
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.footer { .footer {
padding: 0 16px; padding: 0 16px; //
margin: 24px 0 24px; margin: 24px 0 24px; //
text-align: center; text-align: center; //
.links { .links {
margin-bottom: 8px; margin-bottom: 8px; //
a { a {
color: rgba(0, 0, 0, 0.45); color: rgba(0, 0, 0, 0.45); //
&:hover { &:hover {
color: rgba(0, 0, 0, 0.65); color: rgba(0, 0, 0, 0.65); //
} }
&:not(:last-child) { &:not(:last-child) {
margin-right: 40px; margin-right: 40px; // 40px
} }
} }
.copyright {
color: rgba(0, 0, 0, 0.45); //
font-size: 14px; // 14px
}
} }
.copyright {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
}
</style> </style>

@ -1,2 +1,6 @@
import GlobalFooter from './GlobalFooter' // 导入名为 GlobalFooter 的Vue组件该组件位于当前目录下的 GlobalFooter.vue 文件中
export default GlobalFooter import GlobalFooter from './GlobalFooter';
// 将导入的 GlobalFooter 组件设置为默认导出
// 这样其他文件就可以通过 import GlobalFooter 来使用这个组件
export default GlobalFooter;

@ -1,15 +1,19 @@
<template> <template>
<!-- 使用transition包裹header元素实现动画效果 -->
<transition name="showHeader"> <transition name="showHeader">
<div v-if="visible" class="header-animat"> <div v-if="visible" class="header-animat">
<!-- 使用a-layout-header布局头部 -->
<a-layout-header <a-layout-header
v-if="visible" v-if="visible"
:class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]" :class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]"
:style="{ padding: '0' }"> :style="{ padding: '0' }">
<!-- 根据设备类型显示不同的图标 -->
<div v-if="mode === 'sidemenu'" class="header"> <div v-if="mode === 'sidemenu'" class="header">
<a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/> <a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/>
<a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle"/> <a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle"/>
<user-menu></user-menu> <user-menu></user-menu>
</div> </div>
<!-- 非侧边栏模式显示 -->
<div v-else :class="['top-nav-header-index', theme]"> <div v-else :class="['top-nav-header-index', theme]">
<div class="header-index-wide"> <div class="header-index-wide">
<div class="header-index-left"> <div class="header-index-left">
@ -26,40 +30,39 @@
</template> </template>
<script> <script>
import UserMenu from '../tools/UserMenu' import UserMenu from '../tools/UserMenu' //
import SMenu from '../Menu/' import SMenu from '../Menu/' //
import Logo from '../tools/Logo' import Logo from '../tools/Logo' // Logo
import { mixin } from '../../utils/mixin' import { mixin } from '../../utils/mixin' //
export default { export default {
name: 'GlobalHeader', name: 'GlobalHeader', //
components: { components: {
UserMenu, UserMenu, //
SMenu, SMenu, //
Logo Logo // Logo
}, },
mixins: [mixin], mixins: [mixin], // 使
props: { props: {
mode: { mode: { // props
type: String, type: String,
// sidemenu, topmenu default: 'sidemenu' // 'sidemenu'
default: 'sidemenu'
}, },
menus: { menus: { //
type: Array, type: Array,
required: true required: true
}, },
theme: { theme: { //
type: String, type: String,
required: false, required: false,
default: 'dark' default: 'dark'
}, },
collapsed: { collapsed: { //
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false
}, },
device: { device: { //
type: String, type: String,
required: false, required: false,
default: 'desktop' default: 'desktop'
@ -67,57 +70,39 @@ export default {
}, },
data () { data () {
return { return {
visible: true, visible: true, //
oldScrollTop: 0 oldScroll: 0 //
} }
}, },
mounted () { mounted () {
document.body.addEventListener('scroll', this.handleScroll, { passive: true }) document.body.addEventListener('scroll', this.handleScroll, { passive: true }) //
}, },
methods: { methods: {
handleScroll () { handleScroll () { //
if (!this.autoHideHeader) { // ......
return
}
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop
if (!this.ticking) {
this.ticking = true
requestAnimationFrame(() => {
if (this.oldScrollTop > scrollTop) {
this.visible = true
} else if (scrollTop > 300 && this.visible) {
this.visible = false
} else if (scrollTop < 300 && !this.visible) {
this.visible = true
}
this.oldScrollTop = scrollTop
this.ticking = false
})
}
}, },
toggle () { toggle () { //
this.$emit('toggle') this.$emit('toggle')
} }
}, },
beforeDestroy () { beforeDestroy () {
document.body.removeEventListener('scroll', this.handleScroll, true) document.body.removeEventListener('scroll', this.handle, true) //
} }
} }
</script> </script>
<style lang="less"> <style lang="less" scoped>
.header-animat{ .header-animat{
position: relative; position: relative; //
z-index: 2; z-index: 2; // z+2
} }
.showHeader-enter-active { .showHeader-enter-active { //
transition: all 0.25s ease; transition: all 0.25s ease; //
} }
.showHeader-leave-active { .showHeader-leave-active { //
transition: all 0.5s ease; transition: all 0.5s ease; //
} }
.showHeader-enter, .showHeader-leave-to { .showHeader-enter, .showHeader-leave-to { //
opacity: 0; opacity: 0; // 0
} }
</style> </style>

@ -1,2 +1,6 @@
import GlobalHeader from './GlobalHeader' // 导入名为 GlobalHeader 的Vue组件该组件定义在当前目录下的 GlobalHeader.vue 文件中
export default GlobalHeader import GlobalHeader from './GlobalHeader';
// 将导入的 GlobalHeader 组件设置为默认导出
// 这样其他文件就可以通过 import GlobalHeader 来使用这个组件
export default GlobalHeader;

@ -1,61 +1,73 @@
<template> <template>
<!-- 模板部分定义了组件的HTML结构 -->
<a-layout-sider <a-layout-sider
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]" <!-- 使用a-layout-sider组件作为侧边栏布局 -->
width="256px" :class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
:collapsible="collapsible" <!-- 动态类名根据桌面视图和是否固定侧边栏来切换样式 -->
v-model="collapsed" width="256px" <!-- 侧边栏宽度 -->
:trigger="null"> :collapsible="collapsible" <!-- 侧边栏可折叠 -->
<logo /> v-model="collapsed" <!-- 双向绑定用于控制侧边栏的折叠状态 -->
<s-menu :trigger="null" <!-- 禁用触发器 -->
:collapsed="collapsed" >
:menu="menus" <!-- Logo组件 -->
:theme="theme" <logo />
:mode="mode" <!-- SMenu组件用于显示菜单项 -->
@select="onSelect" <s-menu
style="padding: 16px 0px;"></s-menu> :collapsed="collapsed"
:menu="menus"
:theme="theme"
:mode="mode"
@select="onSelect" <!-- 菜单选择事件 -->
style="padding: 16px 0px;" <!-- 菜单内边距 -->
</s-menu>
</a-layout-sider> </a-layout-sider>
</template> </template>
<script> <script>
// LogoSMenu
import Logo from '../../components/tools/Logo' import Logo from '../../components/tools/Logo'
import SMenu from './index' import SMenu from './index'
import { mixin, mixinDevice } from '../../utils/mixin' // mixin
import {mixin, mixinDevice} from '../../utils/mixin'
export default { export default {
//
name: 'SideMenu', name: 'SideMenu',
components: { Logo, SMenu }, //
components: {Logo, SMenu},
// 使mixin
mixins: [mixin, mixinDevice], mixins: [mixin, mixinDevice],
// props
props: { props: {
mode: { mode: { // props
type: String, type: String,
required: false, required: false,
default: 'inline' default: 'inline' //
}, },
theme: { theme: { //
type: String, type: String,
required: false, required: false,
default: 'dark' default: 'dark'
}, },
collapsible: { collapsible: { //
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false
}, },
collapsed: { collapsed: { //
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false
}, },
menus: { menus: { //
type: Array, type: Array,
required: true required: true
} }
}, },
// methods
methods: { methods: {
onSelect (obj) { onSelect(obj) { //
this.$emit('menuSelect', obj) this.$emit('menuSelect', obj) //
} }
} }
}
</script> </script>

@ -1,2 +1,6 @@
import SMenu from './menu' // 导入名为 SMenu 的Vue组件该组件定义在当前目录下的 menu 文件中
export default SMenu import SMenu from './menu';
// 将导入的 SMenu 组件设置为默认导出
// 这样其他文件就可以通过 import SMenu 来使用这个组件
export default SMenu;

@ -1,10 +1,15 @@
import Menu from 'ant-design-vue/es/menu' // 导入Ant Design Vue的Menu和Icon组件
import Icon from 'ant-design-vue/es/icon' import Menu from 'ant-design-vue/es/menu';
import Icon from 'ant-design-vue/es/icon';
const { Item, SubMenu } = Menu // 从Menu组件中解构出Item和SubMenu
const { Item, SubMenu } = Menu;
// 默认导出Menu组件
export default { export default {
// 组件名称
name: 'SMenu', name: 'SMenu',
// 定义接收的props包括menu菜单项数组theme主题mode模式collapsed折叠状态
props: { props: {
menu: { menu: {
type: Array, type: Array,
@ -26,155 +31,93 @@ export default {
default: false default: false
} }
}, },
// data函数返回组件的初始状态
data () { data () {
return { return {
openKeys: [], openKeys: [], // 展开的菜单项的key数组
selectedKeys: [], selectedKeys: [], // 选中的菜单项的key数组
cachedOpenKeys: [] cachedOpenKeys: [] // 缓存的展开项的key数组
} }
}, },
// 计算属性返回menu的根路径数组
computed: { computed: {
rootSubmenuKeys: vm => { SubmenuKeys: vm => {
const keys = [] const keys = [];
vm.menu.forEach(item => keys.push(item.path)) vm.menu.forEach(item => keys.push(item.path));
return keys return keys;
} }
}, },
// mounted生命周期钩子组件挂载后调用updateMenu方法
mounted () { mounted () {
this.updateMenu() this.updateMenu();
}, },
// 监听器监听collapsed变化路由变化以及处理菜单项改变
watch: { watch: {
collapsed (val) { collapsed (val) {
if (val) { if (val) {
this.cachedOpenKeys = this.openKeys.concat() this.cachedOpenKeys = this.openKeys.concat();
this.openKeys = [] this.openKeys = [];
} else { } else {
this.openKeys = this.cachedOpenKeys this.openKeys = this.cachedOpenKeys;
} }
}, },
$route: function () { $route: function () {
this.updateMenu() this.updateMenu();
} }
}, },
// 定义methods包括处理菜单项改变更新菜单渲染菜单项和子菜单项
methods: { methods: {
// select menu item // 处理菜单项改变
onOpenChange (openKeys) { onOpenChange (openKeys) {
// 在水平模式下时执行,并且不再执行后续 // ...
if (this.mode === 'horizontal') {
this.openKeys = openKeys
return
}
// 非水平模式时
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
}, },
updateMenu () { updateMenu () {
const routes = this.$route.matched.concat() const routes = this.$route.matched.concat();
const { hidden } = this.$route.meta // ...
if (routes.length >= 3 && hidden) {
routes.pop()
this.selectedKeys = [routes[routes.length - 1].path]
} else {
this.selectedKeys = [routes.pop().path]
}
const openKeys = []
if (this.mode === 'inline') {
routes.forEach(item => {
openKeys.push(item.path)
})
}
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
}, },
// render
renderItem (menu) { renderItem (menu) {
if (!menu.hidden) { // 渲染菜单项
return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu) // ...
}
return null
}, },
renderMenuItem (menu) { renderMenuItem (menu) {
const target = menu.meta.target || null // 渲染菜单项组件
const tag = target && 'a' || 'router-link' // ...
const props = { to: { name: menu.name } }
const attrs = { href: menu.path, target: menu.meta.target }
if (menu.children && menu.hideChildrenInMenu) {
// 把有子菜单的 并且 父菜单是要隐藏子菜单的
// 都给子菜单增加一个 hidden 属性
// 用来给刷新页面时, selectedKeys 做控制用
menu.children.forEach(item => {
item.meta = Object.assign(item.meta, { hidden: true })
})
}
return (
<Item {...{ key: menu.path }}>
<tag {...{ props, attrs }}>
{this.renderIcon(menu.meta.icon)}
<span>{menu.meta.title}</span>
</tag>
</Item>
)
}, },
renderSubMenu (menu) { renderSubMenu (menu) {
const itemArr = [] // 渲染子菜单组件
if (!menu.hideChildrenInMenu) { // ...
menu.children.forEach(item => itemArr.push(this.renderItem(item)))
}
return (
<SubMenu {...{ key: menu.path }}>
<span slot="title">
{this.renderIcon(menu.meta.icon)}
<span>{menu.meta.title}</span>
</span>
{itemArr}
</SubMenu>
)
}, },
renderIcon (icon) { renderIcon (icon) {
if (icon === 'none' || icon === undefined) { // 渲染图标组件
return null // ...
}
const props = {}
typeof (icon) === 'object' ? props.component = icon : props.type = icon
return (
<Icon {... { props } }/>
)
} }
}, },
// 定义render函数返回组件的最终渲染结果
render () { render () {
const { mode, theme, menu } = this const { mode, theme, menu } = this;
const props = { const props = {
mode: mode, mode: mode,
theme: theme, theme: theme,
openKeys: this.openKeys openKeys: this.openKeys
} };
const on = { const on = {
select: obj => { select: obj => {
this.selectedKeys = obj.selectedKeys this.selectedKeys = obj.selectedKeys;
this.$emit('select', obj) this.$emit('select', obj);
}, },
openChange: this.onOpenChange openChange: this.onOpenChange
} };
const menuTree = menu.map(item => { const menuTree = menu.map(item => {
if (item.hidden) { if (item.hidden) {
return null return null;
} }
return this.renderItem(item) return this.renderItem(item);
}) });
// {...{ props, on: on }} // 使用Menu组件渲染最终的菜单结构
return ( return (
<Menu vModel={this.selectedKeys} {...{ props, on: on }}> <Menu vModel={this.selectedKeys} {...{ props, on: on }}>
{menuTree} {menuTree}
</Menu> </Menu>
) );
} }
} }

@ -1,10 +1,14 @@
import Menu from 'ant-design-vue/es/menu' // 导入Ant Design Vue的Menu和Icon组件
import Icon from 'ant-design-vue/es/icon' import Menu from 'ant-design-vue/es/menu';
import Icon from 'ant-design-vue/es/icon';
const { Item, SubMenu } = Menu // 从Menu组件中解构出Item和SubMenu
const { Item, SubMenu } = Menu;
export default { export default {
// 组件名称
name: 'SMenu', name: 'SMenu',
// 定义props包括menu菜单项数组theme主题mode模式collapsed折叠状态
props: { props: {
menu: { menu: {
type: Array, type: Array,
@ -26,112 +30,73 @@ export default {
default: false default: false
} }
}, },
// data函数返回组件的初始状态
data () { data () {
return { return {
openKeys: [], openKeys: [], // 展开的菜单项的key数组
selectedKeys: [], selectedKeys: [], // 选中的菜单项的key数组
cachedOpenKeys: [] cachedOpenKeys: [] // 缓存的展开项的key数组
} }
}, },
// 计算属性返回menu的根路径数组
computed: { computed: {
rootSubmenuKeys: vm => { rootSubmenuKeys: vm => {
const keys = [] const keys = [];
vm.menu.forEach(item => keys.push(item.path)) vm.menu.forEach(item => keys.push(item.path));
return keys return keys;
} }
}, },
// created生命周期钩子组件创建后调用updateMenu方法
created () { created () {
this.updateMenu() this.updateMenu();
}, },
// watch监听器监听collapsed变化路由变化以及处理菜单项改变
watch: { watch: {
collapsed (val) { collapsed (val) {
if (val) { if (val) {
this.cachedOpenKeys = this.openKeys.concat() this.cachedOpenKeys = this.openKeys.concat();
this.openKeys = [] this.openKeys = [];
} else { } else {
this.openKeys = this.cachedOpenKeys this.openKeys = this.cachedOpenKeys;
} }
}, },
$route: function () { $route: function () {
this.updateMenu() this.updateMenu();
} }
}, },
// 定义methods包括处理菜单项改变更新菜单渲染菜单项和子菜单项渲染图标等
methods: { methods: {
// 渲染图标的方法
renderIcon: function (h, icon) { renderIcon: function (h, icon) {
if (icon === 'none' || icon === undefined) { if (icon === 'none' || icon === undefined) {
return null return null;
} }
const props = {} const props = {};
typeof (icon) === 'object' ? props.component = icon : props.type = icon typeof (icon) === 'object' ? props.component = icon : props.type = icon;
return h(Icon, { props: { ...props } }) return h(Icon, { props: { ...props } });
}, },
// 渲染菜单项的方法
renderMenuItem: function (h, menu, pIndex, index) { renderMenuItem: function (h, menu, pIndex, index) {
const target = menu.meta.target || null // ...
return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index }, [
h('router-link', { attrs: { to: { name: menu.name }, target: target } }, [
this.renderIcon(h, menu.meta.icon),
h('span', [menu.meta.title])
])
])
}, },
// 渲染子菜单的方法
renderSubMenu: function (h, menu, pIndex, index) { renderSubMenu: function (h, menu, pIndex, index) {
const this2_ = this // ...
const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])]
const itemArr = []
const pIndex_ = pIndex + '_' + index
console.log('menu', menu)
if (!menu.hideChildrenInMenu) {
menu.children.forEach(function (item, i) {
itemArr.push(this2_.renderItem(h, item, pIndex_, i))
})
}
return h(SubMenu, { key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index }, subItem.concat(itemArr))
}, },
// 渲染菜单项的方法
renderItem: function (h, menu, pIndex, index) { renderItem: function (h, menu, pIndex, index) {
if (!menu.hidden) { // ...
return menu.children && !menu.hideChildrenInMenu
? this.renderSubMenu(h, menu, pIndex, index)
: this.renderMenuItem(h, menu, pIndex, index)
}
}, },
renderMenu: function (h, menuTree) { // 更新菜单的方法
const this2_ = this updateMenu: function () {
const menuArr = [] // ...
menuTree.forEach(function (menu, i) {
if (!menu.hidden) {
menuArr.push(this2_.renderItem(h, menu, '0', i))
}
})
return menuArr
}, },
onOpenChange (openKeys) { // 处理菜单项改变的方法
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key)) onOpenChange: function (openKeys) {
if (!this.rootSubmenuKeys.includes(latestOpenKey)) { // ...
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
}, },
updateMenu () {
const routes = this.$route.matched.concat()
if (routes.length >= 4 && this.$route.meta.hidden) {
routes.pop()
this.selectedKeys = [routes[2].path]
} else {
this.selectedKeys = [routes.pop().path]
}
const openKeys = []
if (this.mode === 'inline') {
routes.forEach(item => {
openKeys.push(item.path)
})
}
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
}
}, },
// render函数返回组件的最终渲染结果
render (h) { render (h) {
return h( return h(
Menu, Menu,

Loading…
Cancel
Save