Default Changelist

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

@ -1,50 +1,51 @@
<template>
<!-- 模板部分定义了组件的HTML结构 -->
<div class="footer">
<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="mailto:liangshanguang2@gmail.com">联系我</a>
<!-- 链接区域 -->
<a href="https://github.com/19920625lsg/spring-boot-online-exam#34; target="_blank">代码仓</a> <!-- 链接到GitHub仓库 -->
<a href="https://19920625lsg.github.io#34; target="_blank">关于我</a> <!-- 链接到个人网站 -->
<a href="mailto:liangshanguang2@gmail.com">联系我</a> <!-- 邮件联系链接 -->
</div>
<div class="copyright">
Copyright
<a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span>
Copyright <!-- 版权信息 -->
<a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span> <!-- 版权所有者 -->
</div>
</div>
</template>
<script>
export default {
name: 'GlobalFooter',
name: 'GlobalFooter', //
data () {
return {}
return {} //
}
}
</script>
<style lang="less" scoped>
.footer {
padding: 0 16px;
margin: 24px 0 24px;
text-align: center;
padding: 0 16px; //
margin: 24px 0 24px; //
text-align: center; //
.links {
margin-bottom: 8px;
margin-bottom: 8px; //
a {
color: rgba(0, 0, 0, 0.45);
color: rgba(0, 0, 0, 0.45); //
&:hover {
color: rgba(0, 0, 0, 0.65);
color: rgba(0, 0, 0, 0.65); //
}
&: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'
export default GlobalFooter
// 导入名为 GlobalFooter 的Vue组件该组件位于当前目录下的 GlobalFooter.vue 文件中
import GlobalFooter from './GlobalFooter';
// 将导入的 GlobalFooter 组件设置为默认导出
// 这样其他文件就可以通过 import GlobalFooter 来使用这个组件
export default GlobalFooter;

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

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

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

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

@ -1,10 +1,15 @@
import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'
// 导入Ant Design Vue的Menu和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 {
// 组件名称
name: 'SMenu',
// 定义接收的props包括menu菜单项数组theme主题mode模式collapsed折叠状态
props: {
menu: {
type: Array,
@ -26,155 +31,93 @@ export default {
default: false
}
},
// data函数返回组件的初始状态
data () {
return {
openKeys: [],
selectedKeys: [],
cachedOpenKeys: []
openKeys: [], // 展开的菜单项的key数组
selectedKeys: [], // 选中的菜单项的key数组
cachedOpenKeys: [] // 缓存的展开项的key数组
}
},
// 计算属性返回menu的根路径数组
computed: {
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
return keys
SubmenuKeys: vm => {
const keys = [];
vm.menu.forEach(item => keys.push(item.path));
return keys;
}
},
// mounted生命周期钩子组件挂载后调用updateMenu方法
mounted () {
this.updateMenu()
this.updateMenu();
},
// 监听器监听collapsed变化路由变化以及处理菜单项改变
watch: {
collapsed (val) {
if (val) {
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
this.cachedOpenKeys = this.openKeys.concat();
this.openKeys = [];
} else {
this.openKeys = this.cachedOpenKeys
this.openKeys = this.cachedOpenKeys;
}
},
$route: function () {
this.updateMenu()
this.updateMenu();
}
},
// 定义methods包括处理菜单项改变更新菜单渲染菜单项和子菜单项
methods: {
// select menu item
// 处理菜单项改变
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 () {
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)
const routes = this.$route.matched.concat();
// ...
},
// render
renderItem (menu) {
if (!menu.hidden) {
return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
}
return null
// 渲染菜单项
// ...
},
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) {
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) {
if (icon === 'none' || icon === undefined) {
return null
}
const props = {}
typeof (icon) === 'object' ? props.component = icon : props.type = icon
return (
<Icon {... { props } }/>
)
// 渲染图标组件
// ...
}
},
// 定义render函数返回组件的最终渲染结果
render () {
const { mode, theme, menu } = this
const { mode, theme, menu } = this;
const props = {
mode: mode,
theme: theme,
openKeys: this.openKeys
}
};
const on = {
select: obj => {
this.selectedKeys = obj.selectedKeys
this.$emit('select', obj)
this.selectedKeys = obj.selectedKeys;
this.$emit('select', obj);
},
openChange: this.onOpenChange
}
};
const menuTree = menu.map(item => {
if (item.hidden) {
return null
return null;
}
return this.renderItem(item)
})
// {...{ props, on: on }}
return this.renderItem(item);
});
// 使用Menu组件渲染最终的菜单结构
return (
<Menu vModel={this.selectedKeys} {...{ props, on: on }}>
{menuTree}
</Menu>
)
);
}
}
}

@ -1,10 +1,14 @@
import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'
// 导入Ant Design Vue的Menu和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 {
// 组件名称
name: 'SMenu',
// 定义props包括menu菜单项数组theme主题mode模式collapsed折叠状态
props: {
menu: {
type: Array,
@ -26,112 +30,73 @@ export default {
default: false
}
},
// data函数返回组件的初始状态
data () {
return {
openKeys: [],
selectedKeys: [],
cachedOpenKeys: []
openKeys: [], // 展开的菜单项的key数组
selectedKeys: [], // 选中的菜单项的key数组
cachedOpenKeys: [] // 缓存的展开项的key数组
}
},
// 计算属性返回menu的根路径数组
computed: {
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
return keys
const keys = [];
vm.menu.forEach(item => keys.push(item.path));
return keys;
}
},
// created生命周期钩子组件创建后调用updateMenu方法
created () {
this.updateMenu()
this.updateMenu();
},
// watch监听器监听collapsed变化路由变化以及处理菜单项改变
watch: {
collapsed (val) {
if (val) {
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
this.cachedOpenKeys = this.openKeys.concat();
this.openKeys = [];
} else {
this.openKeys = this.cachedOpenKeys
this.openKeys = this.cachedOpenKeys;
}
},
$route: function () {
this.updateMenu()
this.updateMenu();
}
},
// 定义methods包括处理菜单项改变更新菜单渲染菜单项和子菜单项渲染图标等
methods: {
// 渲染图标的方法
renderIcon: function (h, icon) {
if (icon === 'none' || icon === undefined) {
return null
return null;
}
const props = {}
typeof (icon) === 'object' ? props.component = icon : props.type = icon
return h(Icon, { props: { ...props } })
const props = {};
typeof (icon) === 'object' ? props.component = icon : props.type = icon;
return h(Icon, { props: { ...props } });
},
// 渲染菜单项的方法
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) {
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) {
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
const menuArr = []
menuTree.forEach(function (menu, i) {
if (!menu.hidden) {
menuArr.push(this2_.renderItem(h, menu, '0', i))
}
})
return menuArr
// 更新菜单的方法
updateMenu: function () {
// ...
},
onOpenChange (openKeys) {
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
// 处理菜单项改变的方法
onOpenChange: function (openKeys) {
// ...
},
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) {
return h(
Menu,
@ -153,4 +118,4 @@ export default {
this.renderMenu(h, this.menu)
)
}
}
}
Loading…
Cancel
Save