注释了整个前端文件包

feature/lxh
李炫好 2 months ago
parent 9d444f55a3
commit 140a059809

@ -1,24 +1,35 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<!-- 设置字符编码为utf-8 -->
<meta charset="utf-8">
<!-- 设置IE浏览器兼容模式 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 设置视口宽度为设备宽度初始缩放比例为1.0 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 设置网站图标 -->
<link rel="icon" href="<%= BASE_URL %>logo.png">
<!-- 设置网站标题 -->
<title>在线考试系统</title>
<!-- 设置加载动画样式 -->
<style>#loading-mask{position:fixed;left:0;top:0;height:100%;width:100%;background:#fff;user-select:none;z-index:9999;overflow:hidden}.loading-wrapper{position:absolute;top:50%;left:50%;transform:translate(-50%,-100%)}.loading-dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:64px;width:64px;height:64px;box-sizing:border-box}.loading-dot i{width:22px;height:22px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.loading-dot i:nth-child(1){top:0;left:0}.loading-dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.loading-dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.loading-dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
</head>
<body>
<!-- 如果禁用了JavaScript则显示以下内容 -->
<noscript>
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 应用容器 -->
<div id="app">
<!-- 加载动画遮罩层 -->
<div id="loading-mask">
<!-- 加载动画容器 -->
<div class="loading-wrapper">
<!-- 加载动画 -->
<span class="loading-dot loading-dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
</div>
</div>
<!-- built files will be auto injected -->
<!-- 自动注入构建文件 -->
</body>
</html>

@ -3,23 +3,30 @@
import api from './index'
import { axios } from '../utils/request'
// 导出一个函数,用于获取问题列表
export function getQuestionList (parameter) {
// 使用axios发送get请求获取问题列表
return axios({
url: api.ExamQuestionList,
method: 'get',
params: parameter
url: api.ExamQuestionList, // 请求的URL
method: 'get', // 请求的方法
params: parameter // 请求的参数
})
}
// 导出一个函数,用于获取所有题目
export function getQuestionAll () {
// 使用axios发送get请求获取所有题目
return axios({
url: api.ExamQuestionAll,
method: 'get'
})
}
// 导出一个名为questionUpdate的函数参数为parameter
export function questionUpdate (parameter) {
// 打印参数
console.log(parameter)
// 返回一个axios请求请求的url为api.ExamQuestionUpdate请求方法为post请求的数据为parameter
return axios({
url: api.ExamQuestionUpdate,
method: 'post',
@ -27,18 +34,26 @@ export function questionUpdate (parameter) {
})
}
// 导出一个函数,用于获取题目选择
export function getQuestionSelection () {
// 使用axios发送get请求获取题目选择
return axios({
// 请求的URL
url: api.ExamQuestionSelection,
// 请求的方法
method: 'get',
// 请求的头部信息
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
// 导出一个名为questionCreate的函数参数为parameter
export function questionCreate (parameter) {
// 打印参数
console.log(parameter)
// 使用axios发送post请求url为api.ExamQuestionCreate参数为parameter
return axios({
url: api.ExamQuestionCreate,
method: 'post',
@ -46,7 +61,9 @@ export function questionCreate (parameter) {
})
}
// 导出一个函数,用于获取考试列表
export function getExamList (parameter) {
// 使用axios发送get请求获取考试列表
return axios({
url: api.ExamList,
method: 'get',
@ -54,15 +71,18 @@ export function getExamList (parameter) {
})
}
// 导出一个函数,用于获取所有考试
export function getExamAll () {
// 使用axios发送get请求获取所有考试
return axios({
url: api.ExamAll,
method: 'get'
})
}
// 获取所有问题,按照单选、多选和判断进行分类
// 导出一个函数,用于获取所有问题,按照单选、多选和判断进行分类
export function getExamQuestionTypeList () {
// 使用axios发送get请求获取所有问题按照单选、多选和判断进行分类
return axios({
url: api.ExamQuestionTypeList,
method: 'get',
@ -72,7 +92,9 @@ export function getExamQuestionTypeList () {
})
}
// 导出一个函数,用于获取考试卡片列表
export function getExamCardList () {
// 使用axios发送get请求获取考试卡片列表
return axios({
url: api.ExamCardList,
method: 'get',
@ -82,8 +104,11 @@ export function getExamCardList () {
})
}
// 导出一个函数examCreate用于创建考试
export function examCreate (parameter) {
// 打印传入的参数
console.log(parameter)
// 返回一个axios请求请求的url为api.ExamCreate请求方法为post请求的数据为parameter
return axios({
url: api.ExamCreate,
method: 'post',
@ -91,36 +116,44 @@ export function examCreate (parameter) {
})
}
// 导出一个函数examUpdate用于更新考试
export function examUpdate (parameter) {
// 打印传入的参数
console.log(parameter)
// 返回一个axios请求请求的url为api.ExamUpdate请求方法为post请求的数据为parameter
return axios({
url: api.ExamUpdate,
method: 'post',
data: parameter
})
}
// 导出一个函数,用于获取考试详情
export function getExamDetail (examId) {
// 使用axios发送get请求获取考试详情
return axios({
url: api.ExamDetail + examId,
method: 'get',
url: api.ExamDetail + examId, // 请求的URL拼接了examId
method: 'get', // 请求方法为get
headers: {
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF-8' // 设置请求头指定请求体的类型为json
}
})
}
// 导出一个函数,用于获取考试记录详情
export function getExamRecordDetail (recordId) {
// 使用axios发送get请求获取考试记录详情
return axios({
url: api.recordDetail + recordId,
method: 'get',
url: api.recordDetail + recordId, // 请求的URL拼接了recordId
method: 'get', // 请求方法为get
headers: {
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF-8' // 设置请求头指定请求体的类型为json
}
})
}
// 导出一个函数,用于获取问题详情
export function getQuestionDetail (questionId) {
// 使用axios发送get请求获取问题详情
return axios({
url: api.QuestionDetail + questionId,
method: 'get',
@ -130,8 +163,11 @@ export function getQuestionDetail (questionId) {
})
}
// 导出一个函数,用于完成考试
export function finishExam (examId, answersMap) {
// 打印答案
console.log(answersMap)
// 使用axios发送post请求完成考试
return axios({
url: api.FinishExam + examId,
method: 'post',
@ -142,7 +178,9 @@ export function finishExam (examId, answersMap) {
})
}
// 导出一个函数,用于获取考试记录列表
export function getExamRecordList () {
// 使用axios发送get请求获取考试记录列表
return axios({
url: api.ExamRecordList,
method: 'get',

@ -1,38 +1,61 @@
// 定义一个api对象包含各种接口的路径
const api = {
// 登录接口
Login: '/auth/login',
// 登出接口
Logout: '/auth/logout',
// 密码找回接口
ForgePassword: '/auth/forge-password',
// 注册接口
Register: '/auth/register',
// 二维码接口
twoStepCode: '/auth/2step-code',
// 发送短信接口
SendSms: '/account/sms',
// 发送短信错误接口
SendSmsErr: '/account/sms_err',
// get my info
// 获取用户信息接口
UserInfo: '/user/info',
// 下面是自己的用户认证的接口
// 用户注册接口
UserRegister: '/user/register',
// 用户登录接口
UserLogin: '/user/login',
// 考试的接口
// 获取问题列表接口
ExamQuestionList: '/exam/question/list',
// 获取所有问题接口
ExamQuestionAll: '/exam/question/all',
// 更新问题接口
ExamQuestionUpdate: '/exam/question/update',
// 选择问题接口
ExamQuestionSelection: '/exam/question/selection',
// 创建问题接口
ExamQuestionCreate: '/exam/question/create',
// 获取考试列表接口
ExamList: '/exam/list',
// 获取所有考试接口
ExamAll: '/exam/all',
// 获取问题列表,按照单选、多选和判断进行分类
// 获取问题列表,按照单选、多选和判断进行分类接口
ExamQuestionTypeList: '/exam/question/type/list',
// 创建考试接口
ExamCreate: '/exam/create',
// 更新考试接口
ExamUpdate: '/exam/update',
// 获取考试卡片列表接口
ExamCardList: '/exam/card/list',
// 获取考试详情
// 获取考试详情接口
ExamDetail: '/exam/detail/',
// 获取考试详情
// 获取问题详情接口
QuestionDetail: '/exam/question/detail/',
// 交卷
// 交卷接口
FinishExam: '/exam/finish/',
// 获取考试记录列表接口
ExamRecordList: '/exam/record/list',
// 获取考试记录详情接口
recordDetail: '/exam/record/detail/'
}
export default api
// 导出api对象
export default api

@ -4,15 +4,24 @@ import { axios } from '../utils/request'
/**
* login func
* parameter: {
*
* username: '',
*
* password: '',
*
* remember_me: true,
*
*
* captcha: '12345'
*
* }
* @param parameter
*
* @returns {*}
*/
// 导出一个名为login的函数参数为parameter
export function login (parameter) {
// 使用axios发送post请求请求地址为api.UserLogin参数为parameter
return axios({
// 用户登录接口改成自己的
url: api.UserLogin,
@ -21,7 +30,9 @@ export function login (parameter) {
})
}
// 导出一个函数,用于获取短信验证码
export function getSmsCaptcha (parameter) {
// 使用axios发送post请求请求的url为api.SendSms参数为parameter
return axios({
url: api.SendSms,
method: 'post',
@ -29,20 +40,27 @@ export function getSmsCaptcha (parameter) {
})
}
// 导出一个函数,用于获取用户信息
export function getInfo () {
// 使用axios发送get请求获取用户信息
return axios({
// 请求的URL
url: api.UserInfo,
// 请求的方法
method: 'get',
// 请求的头部信息
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
// 导出一个名为logout的函数
export function logout () {
// 使用axios发送post请求请求地址为api.Logout
return axios({
url: api.Logout,
method: 'post',
// 设置请求头内容类型为application/json;charset=UTF-8
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
@ -51,12 +69,15 @@ export function logout () {
/**
* get user 2step code open?
*
* @param parameter {*}
*/
// 导出一个名为get2step的函数参数为parameter
export function get2step (parameter) {
// 使用axios发送post请求请求的url为api.twoStepCode请求的数据为parameter
return axios({
url: api.twoStepCode,
method: 'post',
data: parameter
})
}
}

@ -3,7 +3,9 @@
import api from './index'
import { axios } from '../utils/request'
// 导出一个名为login的函数用于用户登录
export function login (parameter) {
// 使用axios发送post请求请求地址为api.UserLogin请求参数为parameter
return axios({
url: api.UserLogin,
method: 'post',
@ -11,7 +13,9 @@ export function login (parameter) {
})
}
// 导出一个名为register的函数用于用户注册
export function register (parameter) {
// 使用axios发送post请求请求地址为api.UserRegister请求参数为parameter
return axios({
url: api.UserRegister,
method: 'post',

@ -1,6 +1,9 @@
<template>
<!-- 使用v-bind指令动态绑定class属性根据size和layout的值来决定class的值 -->
<div :class="['description-list', size, layout === 'vertical' ? 'vertical': 'horizontal']">
<!-- 如果title存在则显示title -->
<div v-if="title" class="title">{{ title }}</div>
<!-- 使用a-row组件来包裹slot -->
<a-row>
<slot></slot>
</a-row>
@ -8,10 +11,13 @@
</template>
<script>
// Col
import { Col } from 'ant-design-vue/es/grid/'
// Item
const Item = {
name: 'DetailListItem',
// props
props: {
term: {
type: String,
@ -19,21 +25,27 @@ const Item = {
required: false
}
},
// col
inject: {
col: {
type: Number
}
},
//
render () {
// Colprops
return (
<Col {...{ props: responsive[this.col] }}>
// term
<div class="term">{this.$props.term}</div>
//
<div class="content">{this.$slots.default}</div>
</Col>
)
}
}
//
const responsive = {
1: { xs: 24 },
2: { xs: 24, sm: 12 },
@ -41,12 +53,16 @@ const responsive = {
4: { xs: 24, sm: 12, md: 6 }
}
// DetailList
export default {
name: 'DetailList',
// Item
Item: Item,
// Col
components: {
Col
},
// props
props: {
title: {
type: String,
@ -69,6 +85,7 @@ export default {
default: 'horizontal'
}
},
// col
provide () {
return {
col: this.col > 4 ? 4 : this.col
@ -81,6 +98,7 @@ export default {
.description-list {
//
.title {
color: rgba(0, 0, 0, .85);
font-size: 14px;
@ -88,6 +106,7 @@ export default {
margin-bottom: 16px;
}
//
/deep/ .term {
color: rgba(0, 0, 0, .85);
display: table-cell;
@ -96,6 +115,7 @@ export default {
padding-bottom: 16px;
white-space: nowrap;
//
&:not(:empty):after {
content: ":";
margin: 0 8px 0 2px;
@ -112,6 +132,7 @@ export default {
padding-bottom: 16px;
width: 100%;
//
&:empty {
content: ' ';
height: 38px;
@ -119,6 +140,7 @@ export default {
}
}
//
&.small {
.title {
@ -133,6 +155,7 @@ export default {
}
}
//
&.large {
/deep/ .term, .content {
padding-bottom: 16px;
@ -143,6 +166,7 @@ export default {
}
}
//
&.vertical {
.term {
padding-bottom: 8px;

@ -14,117 +14,53 @@
</div>
</template>
<script>
import types from './type'
export default {
name: 'Exception',
props: {
type: {
type: String,
default: '404'
}
},
data () {
return {
config: types
}
},
methods: {
handleToHome () {
this.$router.push({ name: 'dashboard' })
}
}
}
</script>
<style lang="less">
@import "~ant-design-vue/lib/style/index";
.exception {
display: flex;
align-items: center;
height: 80%;
min-height: 500px;
.imgBlock {
flex: 0 0 62.5%;
width: 62.5%;
padding-right: 152px;
zoom: 1;
&::before,
&::after {
content: ' ';
display: table;
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
}
}
.imgEle {
float: right;
width: 100%;
max-width: 430px;
height: 360px;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
}
.content {
flex: auto;
h1 {
margin-bottom: 24px;
color: #434e59;
font-weight: 600;
font-size: 72px;
line-height: 72px;
}
.desc {
margin-bottom: 16px;
color: @text-color-secondary;
font-size: 20px;
line-height: 28px;
}
.actions {
button:not(:last-child) {
margin-right: 8px;
}
}
}
}
@media screen and (max-width: @screen-xl) {
.exception {
.imgBlock {
padding-right: 88px;
}
}
}
@media screen and (max-width: @screen-sm) {
.exception {
display: block;
text-align: center;
.imgBlock {
margin: 0 auto 24px;
padding-right: 0;
}
}
}
@media screen and (max-width: @screen-xs) {
.exception {
.imgBlock {
margin-bottom: -24px;
overflow: hidden;
}
}
}
</style>
<template>
<div class="exception">
<!-- 图片块 -->
<div class="imgBlock">
<!-- 图片元素 -->
<div class="imgEle" :style="{backgroundImage: `url(${config[type].img})`}">
</div>
</div>
<!-- 内容块 -->
<div class="content">
<!-- 标题 -->
<h1>{{ config[type].title }}</h1>
<!-- 描述 -->
<div class="desc">{{ config[type].desc }}</div>
<!-- 操作按钮 -->
<div class="actions">
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template> <!-- 描述 -->
<div class="desc">{{ config[type].desc }}</div>
<!-- 操作按钮 -->
<div class="actions">
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template>
<template>
<div class="exception">
<!-- 图片块 -->
<div class="imgBlock">
<!-- 图片元素 -->
<div class="imgEle" :style="{backgroundImage: `url(${config[type].img})`}">
</div>
</div>
<!-- 内容块 -->
<div class="content">
<!-- 标题 -->
<h1>{{ config[type].title }}</h1>
<!-- 描述 -->
<div class="desc">{{ config[type].desc }}</div>
<!-- 操作按钮 -->
<div class="actions">
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template>

@ -1,20 +1,30 @@
<template>
<!-- 页脚部分 -->
<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>
</div>
<!-- 版权部分 -->
<div class="copyright">
Copyright
<!-- 版权图标 -->
<a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span>
</div>
</div>
</template>
<script>
// Vue
export default {
//
name: 'GlobalFooter',
//
data () {
return {}
}
@ -23,27 +33,41 @@ export default {
<style lang="less" scoped>
.footer {
//
padding: 0 16px;
//
margin: 24px 0 24px;
//
text-align: center;
//
.links {
//
margin-bottom: 8px;
//
a {
//
color: rgba(0, 0, 0, 0.45);
//
&:hover {
//
color: rgba(0, 0, 0, 0.65);
}
//
&:not(:last-child) {
//
margin-right: 40px;
}
}
}
//
.copyright {
//
color: rgba(0, 0, 0, 0.45);
//
font-size: 14px;
}
}

@ -1,22 +1,35 @@
<template>
<!-- 使用transition组件实现动画效果name属性指定动画名称为showHeader -->
<transition name="showHeader">
<!-- 如果visible为true则显示div -->
<div v-if="visible" class="header-animat">
<a-layout-header
<!-- 如果visible为true则显示a-layout-header组件 -->
<a-layout-header>
v-if="visible"
<!-- 根据fixedHeader和sidebarOpened的值动态添加class -->
:class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]"
<!-- 设置padding为0 -->
:style="{ padding: '0' }">
<!-- 如果mode为'sidemenu'则显示header -->
<div v-if="mode === 'sidemenu'" class="header">
<!-- 如果device为'mobile'则显示menu-fold图标否则显示menu-unfold图标 -->
<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></user-menu>
</div>
<!-- 否则显示top-nav-header-index -->
<div v-else :class="['top-nav-header-index', theme]">
<div class="header-index-wide">
<div class="header-index-left">
<!-- 显示logo组件如果device不为'mobile'则显示title -->
<logo class="top-nav-header" :show-title="device !== 'mobile'"/>
<!-- 如果device不为'mobile'则显示s-menu组件 -->
<s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" />
<!-- 如果device为'mobile'则显示menu-fold图标 -->
<a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" />
</div>
<!-- 显示user-menu组件 -->
<user-menu class="header-index-right"></user-menu>
</div>
</div>
@ -72,35 +85,46 @@ export default {
}
},
mounted () {
//
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
// 300
} else if (scrollTop > 300 && this.visible) {
this.visible = false
// 300
} else if (scrollTop < 300 && !this.visible) {
this.visible = true
}
//
this.oldScrollTop = scrollTop
//
this.ticking = false
})
}
},
toggle () {
// toggle
this.$emit('toggle')
}
},
beforeDestroy () {
//
document.body.removeEventListener('scroll', this.handleScroll, true)
}
}

@ -1,59 +1,87 @@
<template>
<a-layout-sider
<!-- 定义一个布局侧边栏 -->
<a-layout-sider>
<!-- 根据不同条件添加不同的class -->
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
<!-- 设置宽度为256px -->
width="256px"
<!-- 是否可折叠 -->
:collapsible="collapsible"
<!-- 绑定collapsed变量 -->
v-model="collapsed"
<!-- 设置触发器为null -->
:trigger="null">
<!-- 引入logo组件 -->
<logo />
<s-menu
<!-- 引入s-menu组件 -->
<s-menu>
<!-- 绑定collapsed变量 -->
:collapsed="collapsed"
<!-- 绑定menus变量 -->
:menu="menus"
<!-- 绑定theme变量 -->
:theme="theme"
<!-- 绑定mode变量 -->
:mode="mode"
<!-- 监听select事件 -->
@select="onSelect"
<!-- 设置样式 -->
style="padding: 16px 0px;"></s-menu>
</a-layout-sider>
</template>
<script>
// Logo
import Logo from '../../components/tools/Logo'
// SMenu
import SMenu from './index'
// mixinmixinDevice
import { mixin, mixinDevice } from '../../utils/mixin'
export default {
name: 'SideMenu',
//
components: { Logo, SMenu },
// 使mixinmixinDevice
mixins: [mixin, mixinDevice],
// props
props: {
// inline
mode: {
type: String,
required: false,
default: 'inline'
},
// dark
theme: {
type: String,
required: false,
default: 'dark'
},
// false
collapsible: {
type: Boolean,
required: false,
default: false
},
// false
collapsed: {
type: Boolean,
required: false,
default: false
},
//
menus: {
type: Array,
required: true
}
},
//
methods: {
//
onSelect (obj) {
// menuSelect
this.$emit('menuSelect', obj)
}
}

@ -87,18 +87,29 @@ export default {
}
},
// 更新菜单
updateMenu () {
// 更新菜单
updateMenu () {
// 获取当前路由匹配的路径
const routes = this.$route.matched.concat()
// 获取当前路由的meta信息
const { hidden } = this.$route.meta
// 如果路由长度大于等于3且meta信息中hidden为true
if (routes.length >= 3 && hidden) {
// 移除最后一个路径
routes.pop()
// 设置选中的路径为倒数第二个路径
this.selectedKeys = [routes[routes.length - 1].path]
} else {
// 设置选中的路径为最后一个路径
this.selectedKeys = [routes.pop().path]
}
// 定义打开的路径
const openKeys = []
// 如果模式为inline
if (this.mode === 'inline') {
// 遍历路由
routes.forEach(item => {
// 将路径添加到打开的路径中
openKeys.push(item.path)
})
}
@ -115,20 +126,25 @@ export default {
},
// 渲染菜单项
renderMenuItem (menu) {
// 获取菜单项的目标
const target = menu.meta.target || null
// 根据目标判断使用a标签还是router-link标签
const tag = target && 'a' || 'router-link'
// 设置router-link的属性
const props = { to: { name: menu.name } }
// 设置a标签的属性
const attrs = { href: menu.path, target: menu.meta.target }
// 如果菜单项有子菜单并且父菜单是要隐藏子菜单的
// 给子菜单增加一个hidden属性
// 用来给刷新页面时selectedKeys做控制用
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 }}>
@ -140,10 +156,14 @@ export default {
},
// 渲染子菜单
renderSubMenu (menu) {
// 创建一个数组用来存放子菜单
const itemArr = []
// 如果父菜单不是要隐藏子菜单的
// 就把子菜单渲染出来
if (!menu.hideChildrenInMenu) {
menu.children.forEach(item => itemArr.push(this.renderItem(item)))
}
// 返回渲染后的子菜单
return (
<SubMenu {...{ key: menu.path }}>
<span slot="title">
@ -156,11 +176,15 @@ export default {
},
// 渲染图标
renderIcon (icon) {
// 如果图标是none或者undefined则返回null
if (icon === 'none' || icon === undefined) {
return null
}
// 创建一个属性对象
const props = {}
// 如果图标是对象则设置component属性
typeof (icon) === 'object' ? props.component = icon : props.type = icon
// 返回渲染后的图标
return (
<Icon {... { props } }/>
)
@ -168,27 +192,36 @@ export default {
},
render () {
// 解构赋值从this中获取mode、theme、menu属性
const { mode, theme, menu } = this
// 定义props对象包含mode、theme、openKeys属性
const props = {
mode: mode,
theme: theme,
openKeys: this.openKeys
}
// 定义on对象包含select和openChange方法
const on = {
select: obj => {
// 将选中的key赋值给this.selectedKeys
this.selectedKeys = obj.selectedKeys
// 触发select事件并传递obj参数
this.$emit('select', obj)
},
openChange: this.onOpenChange
}
// 遍历menu数组生成menuTree
const menuTree = menu.map(item => {
// 如果item.hidden为true则返回null
if (item.hidden) {
return null
}
// 否则调用renderItem方法生成菜单项
return this.renderItem(item)
})
// {...{ props, on: on }}
// 返回Menu组件vModel绑定this.selectedKeysprops和on分别绑定props和on对象
return (
<Menu vModel={this.selectedKeys} {...{ props, on: on }}>
{menuTree}

@ -3,23 +3,31 @@ import Icon from 'ant-design-vue/es/icon'
const { Item, SubMenu } = Menu
export default {
// 导出一个默认对象该对象是一个名为SMenu的组件
render (h)
export default {
// 组件的名称
name: 'SMenu',
// 组件的属性
props: {
// 菜单数据,类型为数组,必填
menu: {
type: Array,
required: true
},
// 主题,类型为字符串,非必填,默认值为'dark'
theme: {
type: String,
required: false,
default: 'dark'
},
// 模式,类型为字符串,非必填,默认值为'inline'
mode: {
type: String,
required: false,
default: 'inline'
},
// 是否折叠类型为布尔值非必填默认值为false
collapsed: {
type: Boolean,
required: false,
@ -27,6 +35,7 @@ export default {
}
},
data () {
// 定义三个变量openKeys用于存储当前打开的菜单项selectedKeys用于存储当前选中的菜单项cachedOpenKeys用于存储折叠前的菜单项
return {
openKeys: [],
selectedKeys: [],
@ -34,123 +43,183 @@ export default {
}
},
computed: {
// 计算属性用于获取根菜单项的key
rootSubmenuKeys: vm => {
const keys = []
// 遍历menu数组将每个菜单项的path添加到keys数组中
vm.menu.forEach(item => keys.push(item.path))
return keys
}
},
created () {
// 组件创建时调用updateMenu方法
this.updateMenu()
},
watch: {
// 监听collapsed属性当折叠状态改变时更新openKeys和cachedOpenKeys
collapsed (val) {
if (val) {
// 如果折叠将当前打开的菜单项保存到cachedOpenKeys中并将openKeys置空
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
} else {
// 如果展开将cachedOpenKeys赋值给openKeys
this.openKeys = this.cachedOpenKeys
}
},
// 监听$route属性当路由改变时调用updateMenu方法
$route: function () {
this.updateMenu()
}
},
methods: {
// 渲染图标
renderIcon: function (h, icon) {
// 如果图标为空或未定义则返回null
if (icon === 'none' || icon === undefined) {
return null
}
// 定义props对象
const props = {}
// 如果图标是对象则将图标赋值给props.component否则将图标赋值给props.type
typeof (icon) === 'object' ? props.component = icon : props.type = icon
// 返回Icon组件并传入props对象
return h(Icon, { props: { ...props } })
},
// 渲染菜单项
renderMenuItem: function (h, menu, pIndex, index) {
// 获取菜单的target属性如果不存在则赋值为null
const target = menu.meta.target || null
// 返回Item组件并传入key属性和子组件
return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index }, [
// 返回router-link组件并传入to属性和target属性
h('router-link', { attrs: { to: { name: menu.name }, target: target } }, [
// 调用renderIcon方法传入h和菜单的icon属性
this.renderIcon(h, menu.meta.icon),
// 返回span组件并传入菜单的title属性
h('span', [menu.meta.title])
])
])
},
renderSubMenu: function (h, menu, pIndex, index) {
// 渲染子菜单
renderSubMenu: function (h, menu, pIndex, index) {
// 定义this2_为当前对象
const this2_ = this
// 定义subItem为子菜单项
const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])]
// 定义itemArr为子菜单项数组
const itemArr = []
// 定义pIndex_为父菜单项索引
const pIndex_ = pIndex + '_' + index
// 打印menu
console.log('menu', menu)
// 如果菜单项不隐藏子菜单
if (!menu.hideChildrenInMenu) {
// 遍历子菜单项
menu.children.forEach(function (item, i) {
// 将子菜单项添加到itemArr数组中
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) {
// 渲染菜单
renderMenu: function (h, menuTree) {
// 定义this2_为当前对象
const this2_ = this
// 定义menuArr为空数组
const menuArr = []
// 遍历menuTree
menuTree.forEach(function (menu, i) {
// 如果menu不隐藏
if (!menu.hidden) {
// 将renderItem方法返回的值添加到menuArr数组中
menuArr.push(this2_.renderItem(h, menu, '0', i))
}
})
// 返回menuArr数组
return menuArr
},
// 打开菜单
onOpenChange (openKeys) {
// 定义latestOpenKey为openKeys中不包含在this.openKeys中的值
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
// 如果latestOpenKey不包含在rootSubmenuKeys中
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
// 将openKeys赋值给this.openKeys
this.openKeys = openKeys
} else {
// 如果latestOpenKey存在将latestOpenKey赋值给this.openKeys
// 否则将this.openKeys赋值为空数组
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
},
updateMenu () {
// 更新菜单
updateMenu () {
// 获取当前路由匹配的路径
const routes = this.$route.matched.concat()
// 如果路由长度大于等于4且meta中hidden属性为true
if (routes.length >= 4 && this.$route.meta.hidden) {
// 移除最后一个路径
routes.pop()
// 设置选中的路径为倒数第二个路径
this.selectedKeys = [routes[2].path]
} else {
// 否则设置选中的路径为最后一个路径
this.selectedKeys = [routes.pop().path]
}
// 定义打开的路径
const openKeys = []
// 如果模式为inline
if (this.mode === 'inline') {
// 遍历路径
routes.forEach(item => {
// 将路径添加到打开的路径中
openKeys.push(item.path)
})
}
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
},
this:collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
}
},
render (h) {
};{
// 使用h函数渲染Menu组件
return h(
Menu,
{
// 设置Menu组件的props属性
props: {
theme: this.$props.theme,
mode: this.$props.mode,
openKeys: this.openKeys,
selectedKeys: this.selectedKeys
},
// 设置Menu组件的事件
on: {
openChange: this.onOpenChange,
select: obj => {
// 设置selectedKeys属性
this.selectedKeys = obj.selectedKeys
// 触发select事件
this.$emit('select', obj)
}
}
},
// 渲染菜单
this.renderMenu(h, this.menu)
)
}
}
}

@ -30,37 +30,51 @@ export default {
name: 'MultiTab',
data () {
return {
//
fullPathList: [],
//
pages: [],
//
activeKey: '',
//
newTabIndex: 0
}
},
created () {
//
this.pages.push(this.$route)
//
this.fullPathList.push(this.$route.fullPath)
//
this.selectedLastPath()
},
methods: {
//
onEdit (targetKey, action) {
this[action](targetKey)
},
//
remove (targetKey) {
//
this.pages = this.pages.filter(page => page.fullPath !== targetKey)
//
this.fullPathList = this.fullPathList.filter(path => path !== targetKey)
//
if (!this.fullPathList.includes(this.activeKey)) {
this.selectedLastPath()
}
},
//
selectedLastPath () {
this.activeKey = this.fullPathList[this.fullPathList.length - 1]
},
// content menu
//
closeThat (e) {
this.remove(e)
},
//
closeLeft (e) {
const currentIndex = this.fullPathList.indexOf(e)
if (currentIndex > 0) {
@ -73,6 +87,7 @@ export default {
this.$message.info('左侧没有标签')
}
},
//
closeRight (e) {
const currentIndex = this.fullPathList.indexOf(e)
if (currentIndex < (this.fullPathList.length - 1)) {
@ -85,6 +100,7 @@ export default {
this.$message.info('右侧没有标签')
}
},
//
closeAll (e) {
const currentIndex = this.fullPathList.indexOf(e)
this.fullPathList.forEach((item, index) => {
@ -93,6 +109,7 @@ export default {
}
})
},
//
closeMenuClick ({ key, item, domEvent }) {
const vkey = domEvent.target.getAttribute('data-vkey')
switch (key) {
@ -111,6 +128,7 @@ export default {
break
}
},
//
renderTabPaneMenu (e) {
return (
<a-menu {...{ on: { click: this.closeMenuClick } }}>
@ -121,7 +139,7 @@ export default {
</a-menu>
)
},
// render
//
renderTabPane (title, keyPath) {
const menu = this.renderTabPaneMenu(keyPath)
@ -133,19 +151,25 @@ export default {
}
},
watch: {
//
'$route': function (newVal) {
//
this.activeKey = newVal.fullPath
//
if (this.fullPathList.indexOf(newVal.fullPath) < 0) {
this.fullPathList.push(newVal.fullPath)
this.pages.push(newVal)
}
},
//
activeKey: function (newPathKey) {
//
this.$router.push({ path: newPathKey })
}
},
render () {
const { onEdit, $data: { pages } } = this
//
const panes = pages.map(page => {
return (
<a-tab-pane

@ -52,20 +52,27 @@ export default {
name: 'HeaderNotice',
data () {
return {
//
loadding: false,
//
visible: false
}
},
methods: {
//
fetchNotice () {
//
if (!this.visible) {
this.loadding = true
// 2
setTimeout(() => {
this.loadding = false
}, 2000)
} else {
//
this.loadding = false
}
//
this.visible = !this.visible
}
}
@ -73,17 +80,22 @@ export default {
</script>
<style lang="css">
/* 设置头部通知的顶部位置 */
.header-notice-wrapper {
top: 50px !important;
}
</style>
<style lang="less" scoped>
/* 设置头部通知的样式 */
.header-notice{
/* 设置显示方式为行内块 */
display: inline-block;
/* 设置过渡效果 */
transition: all 0.3s;
/* 设置span标签的垂直对齐方式 */
span {
vertical-align: initial;
}
}
</style>
</style>

@ -1,27 +1,41 @@
<template>
<!-- 页面头部 -->
<div class="page-header">
<!-- 页面头部索引宽 -->
<div class="page-header-index-wide">
<!-- 面包屑导航 -->
<s-breadcrumb />
<!-- 详情 -->
<div class="detail">
<!-- 主要内容 -->
<div class="main" v-if="!$route.meta.hiddenHeaderContent">
<!-- -->
<div class="row">
<!-- logo -->
<img v-if="logo" :src="logo" class="logo"/>
<!-- 标题 -->
<h1 v-if="title" class="title">{{ title }}</h1>
<!-- 操作 -->
<div class="action">
<slot name="action"></slot>
</div>
</div>
<!-- -->
<div class="row">
<!-- 头像 -->
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar" />
</div>
<!-- 内容 -->
<div v-if="this.$slots.content" class="headerContent">
<slot name="content"></slot>
</div>
<!-- 额外内容 -->
<div v-if="this.$slots.extra" class="extra">
<slot name="extra"></slot>
</div>
</div>
<!-- 页面菜单 -->
<div>
<slot name="pageMenu"></slot>
</div>
@ -32,30 +46,37 @@
</template>
<script>
//
import Breadcrumb from '../../components/tools/Breadcrumb'
export default {
name: 'PageHeader',
//
components: {
's-breadcrumb': Breadcrumb
},
//
props: {
// true
title: {
type: [String, Boolean],
default: true,
required: false
},
// logo
logo: {
type: String,
default: '',
required: false
},
//
avatar: {
type: String,
default: '',
required: false
}
},
//
data () {
return {}
}

@ -1,22 +1,33 @@
<template>
<!-- 定义一个result类名的div -->
<div class="result">
<!-- 定义一个div -->
<div>
<!-- 使用a-icon组件根据localIsSuccess的值来决定显示check-circle还是close-circle -->
<a-icon :class="{ 'icon': true, [`${type}`]: true }" :type="localIsSuccess ? 'check-circle' : 'close-circle'"/>
</div>
<!-- 定义一个title类名的div -->
<div class="title">
<!-- 使用slot插槽如果没有传入title插槽则显示title的值 -->
<slot name="title">
{{ title }}
</slot>
</div>
<!-- 定义一个description类名的div -->
<div class="description">
<!-- 使用slot插槽如果没有传入description插槽则显示description的值 -->
<slot name="description">
{{ description }}
</slot>
</div>
<!-- 如果有传入default插槽则显示extra类名的div -->
<div class="extra" v-if="$slots.default">
<!-- 使用slot插槽 -->
<slot></slot>
</div>
<!-- 如果有传入action插槽则显示action类名的div -->
<div class="action" v-if="$slots.action">
<!-- 使用slot插槽 -->
<slot name="action"></slot>
</div>
</div>

@ -1,7 +1,11 @@
<template>
<!-- 设置抽屉索引项 -->
<div class="setting-drawer-index-item">
<!-- 设置抽屉索引项标题 -->
<h3 class="setting-drawer-index-title">{{ title }}</h3>
<!-- 插槽 -->
<slot></slot>
<!-- 如果divider为true则显示分割线 -->
<a-divider v-if="divider"/>
</div>
</template>
@ -10,10 +14,12 @@
export default {
name: 'SettingItem',
props: {
//
title: {
type: String,
default: ''
},
// 线
divider: {
type: Boolean,
default: false
@ -24,13 +30,20 @@ export default {
<style lang="less" scoped>
//
.setting-drawer-index-item {
//
margin-bottom: 24px;
//
.setting-drawer-index-title {
//
font-size: 14px;
//
color: rgba(0, 0, 0, .85);
//
line-height: 22px;
//
margin-bottom: 12px;
}

@ -1,8 +1,11 @@
<template>
<!-- 使用v-bind指令绑定class属性根据条件动态添加class -->
<div :class="[prefixCls, lastCls, blockCls, gridCls]">
<!-- 如果title存在则显示title -->
<div v-if="title" class="antd-pro-components-standard-form-row-index-label">
<span>{{ title }}</span>
</div>
<!-- 显示slot内容 -->
<div class="antd-pro-components-standard-form-row-index-content">
<slot></slot>
</div>
@ -10,6 +13,7 @@
</template>
<script>
//
const classes = [
'antd-pro-components-standard-form-row-index-standardFormRowBlock',
'antd-pro-components-standard-form-row-index-standardFormRowGrid',
@ -17,6 +21,7 @@ const classes = [
]
export default {
name: 'StandardFormRow',
//
props: {
prefixCls: {
type: String,
@ -36,13 +41,17 @@ export default {
type: Boolean
}
},
//
computed: {
// lasttrueclassesnull
lastCls () {
return this.last ? classes[2] : null
},
// blocktrueclassesnull
blockCls () {
return this.block ? classes[0] : null
},
// gridtrueclassesnull
gridCls () {
return this.grid ? classes[1] : null
}
@ -53,12 +62,15 @@ export default {
<style lang="less" scoped>
@import '../index.less';
// index.less
.antd-pro-components-standard-form-row-index-standardFormRow {
display: flex;
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px dashed @border-color-split;
//
/deep/ .ant-form-item {
margin-right: 24px;
}
@ -72,6 +84,7 @@ export default {
line-height: 32px;
}
//
.antd-pro-components-standard-form-row-index-label {
flex: 0 0 auto;
margin-right: 24px;
@ -88,6 +101,7 @@ export default {
}
}
//
.antd-pro-components-standard-form-row-index-content {
flex: 1 1 0;
/deep/ .ant-form-item:last-child {
@ -95,12 +109,14 @@ export default {
}
}
//
&.antd-pro-components-standard-form-row-index-standardFormRowLast {
margin-bottom: 0;
padding-bottom: 0;
border: none;
}
//
&.antd-pro-components-standard-form-row-index-standardFormRowBlock {
/deep/ .ant-form-item,
div.ant-form-item-control-wrapper {
@ -108,6 +124,7 @@ export default {
}
}
//
&.antd-pro-components-standard-form-row-index-standardFormRowGrid {
/deep/ .ant-form-item,
div.ant-form-item-control-wrapper {
@ -119,4 +136,4 @@ export default {
}
}
</style>
</style>

@ -176,16 +176,22 @@ export default {
})
}
},
initTotalList (columns) {
// 初始化总列表
initTotalList (columns) {
// 定义一个空数组
const totalList = []
// 判断columns是否为数组
columns && columns instanceof Array && columns.forEach(column => {
// 判断column是否需要统计
if (column.needTotal) {
// 将column添加到totalList中并初始化total为0
totalList.push({
...column,
total: 0
})
}
})
// 返回totalList
return totalList
},
/**
@ -194,14 +200,21 @@ export default {
* @param selectedRows
*/
updateSelect (selectedRowKeys, selectedRows) {
// 将selectedRows赋值给this.selectedRows
this.selectedRows = selectedRows
// 将selectedRowKeys赋值给this.selectedRowKeys
this.selectedRowKeys = selectedRowKeys
// 获取需要统计的列表
const list = this.needTotalList
// 更新needTotalList
this.needTotalList = list.map(item => {
// 返回一个新的对象包含item的所有属性并更新total属性
return {
...item,
total: selectedRows.reduce((sum, val) => {
// 将val中item.dataIndex对应的值转换为整数并累加到sum中
const total = sum + parseInt(get(val, item.dataIndex))
// 如果total不是数字则返回0
return isNaN(total) ? 0 : total
}, 0)
}
@ -222,7 +235,9 @@ export default {
* @returns {*}
*/
renderClear (callback) {
// 如果没有选中行,则返回 null
if (this.selectedRowKeys.length <= 0) return null
// 返回一个 a 标签,点击时调用 callback 函数,并清空选中行
return (
<a style="margin-left: 24px" onClick={() => {
callback()
@ -240,8 +255,10 @@ export default {
// 绘制 清空 按钮
const clearItem = (typeof this.alert.clear === 'boolean' && this.alert.clear) ? (
// 如果 alert.clear 为 true则调用 this.renderClear(this.clearSelected)
this.renderClear(this.clearSelected)
) : (this.alert !== null && typeof this.alert.clear === 'function') ? (
// 如果 alert.clear 为函数,则调用 this.renderClear(this.alert.clear)
this.renderClear(this.alert.clear)
) : null
@ -259,19 +276,26 @@ export default {
},
render () {
// 定义一个空对象props
const props = {}
// 获取当前组件的data中的所有key
const localKeys = Object.keys(this.$data)
// 判断是否需要显示alert
const showAlert = (typeof this.alert === 'object' && this.alert !== null && this.alert.show) && typeof this.rowSelection.selectedRowKeys !== 'undefined' || this.alert
// 遍历T.props中的所有key
Object.keys(T.props).forEach(k => {
// 将key的首字母大写并拼接成localKey
const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}`
// 如果localKeys中包含localKey则将localKey对应的值赋给props[k]
if (localKeys.includes(localKey)) {
props[k] = this[localKey]
return props[k]
}
// 如果key为rowSelection
if (k === 'rowSelection') {
// 如果需要使用alert则重新绑定 rowSelection 事件
if (showAlert && this.rowSelection) {
// 如果需要使用alert则重新绑定 rowSelection 事件
props[k] = {
selectedRows: this.selectedRows,
selectedRowKeys: this.selectedRowKeys,
@ -281,21 +305,24 @@ export default {
}
}
return props[k]
// 如果没打算开启 rowSelection 则清空默认的选择项
} else if (!this.rowSelection) {
// 如果没打算开启 rowSelection 则清空默认的选择项
props[k] = null
return props[k]
}
}
// 如果this[k]存在则将this[k]赋给props[k]
this[k] && (props[k] = this[k])
return props[k]
})
// 定义一个table组件
const table = (
<a-table {...{ props, scopedSlots: { ...this.$scopedSlots } }} onChange={this.loadData}>
{ Object.keys(this.$slots).map(name => (<template slot={name}>{this.$slots[name]}</template>)) }
</a-table>
)
// 返回一个div组件包含alert和table
return (
<div class="table-wrapper">
{ showAlert ? this.renderAlert() : null }
@ -303,4 +330,5 @@ export default {
</div>
)
}
}

@ -1,11 +1,15 @@
/**
* components util
*
*/
/**
* 清理空值对象
*
* @param children
*
* @returns {*[]}
*
*/
// 导出一个函数,用于过滤掉空节点
export function filterEmpty (children = []) {
@ -15,7 +19,9 @@ export function filterEmpty (children = []) {
/**
* 获取字符串长度英文字符 长度1中文字符长度2
*
* @param {*} str
*
*/
// 导出一个函数,用于获取字符串长度
export const getStrFullLength = (str = '') =>
@ -33,7 +39,9 @@ export const getStrFullLength = (str = '') =>
/**
* 截取字符串根据 maxLength 截取后返回
*
* @param {*} str
*
* @param {*} maxLength
*/
// 导出一个函数,用于截取字符串

@ -19,21 +19,27 @@ export default {
}
},
created () {
//
this.getBreadcrumb()
},
methods: {
getBreadcrumb () {
//
this.breadList = []
// this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: ''}})
//
this.name = this.$route.name
//
this.$route.matched.forEach(item => {
// index
// item.name !== 'index' && this.breadList.push(item)
this.breadList.push(item)
})
}
},
watch: {
//
$route () {
this.getBreadcrumb()
}

@ -1,26 +1,35 @@
<template>
<!-- logo组件 -->
<div class="logo">
<!-- 使用router-link组件跳转到dashboard页面 -->
<router-link :to="{name:'dashboard'}">
<!-- 使用LogoSvg组件显示logo -->
<LogoSvg alt="logo" />
<!-- 如果showTitle为true则显示title -->
<h1 v-if="showTitle">{{ title }}</h1>
</router-link>
</div>
</template>
<script>
// logo.svg
import LogoSvg from '../../assets/logo.svg?inline'
export default {
name: 'Logo',
//
components: {
LogoSvg
},
// props
props: {
//
title: {
type: String,
default: 'Online Exam',
required: false
},
//
showTitle: {
type: Boolean,
default: true,
@ -28,4 +37,4 @@ export default {
}
}
}
</script>
</script>

@ -41,6 +41,7 @@
<script>
export default {
props: {
//
visible: {
type: Boolean,
default: false
@ -48,12 +49,15 @@ export default {
},
data () {
return {
//
stepLoading: false,
//
form: null
}
},
methods: {
//
handleStepOk () {
const vm = this
this.stepLoading = true
@ -70,10 +74,12 @@ export default {
this.$emit('error', { err })
})
},
//
handleCancel () {
this.visible = false
this.$emit('cancel')
},
//
onForgeStepCode () {
}
@ -81,9 +87,13 @@ export default {
}
</script>
<style lang="less" scoped>
// step-form-wrapper
.step-form-wrapper {
// 0
margin: 0 auto;
// 80%
width: 80%;
// 400px
max-width: 400px;
}
</style>

@ -15,18 +15,30 @@ import {
} from '../store/mutation-types'
import config from '../config/defaultSettings'
// 导出一个默认的初始化函数
export default function Initializer () {
// 从localStorage中获取SIDEBAR_TYPE如果不存在则默认为true并提交给store
store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))
// 从localStorage中获取DEFAULT_THEME如果不存在则默认为config.navTheme并提交给store
store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))
// 从localStorage中获取DEFAULT_LAYOUT_MODE如果不存在则默认为config.layout并提交给store
store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))
// 从localStorage中获取DEFAULT_FIXED_HEADER如果不存在则默认为config.fixedHeader并提交给store
store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
// 从localStorage中获取DEFAULT_FIXED_SIDEMENU如果不存在则默认为config.fixSiderbar并提交给store
store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
// 从localStorage中获取DEFAULT_CONTENT_WIDTH_TYPE如果不存在则默认为config.contentWidth并提交给store
store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
// 从localStorage中获取DEFAULT_FIXED_HEADER_HIDDEN如果不存在则默认为config.autoHideHeader并提交给store
store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
// 从localStorage中获取DEFAULT_COLOR_WEAK如果不存在则默认为config.colorWeak并提交给store
store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak))
// 从localStorage中获取DEFAULT_COLOR如果不存在则默认为config.primaryColor并提交给store
store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))
// 从localStorage中获取DEFAULT_MULTI_TAB如果不存在则默认为config.multiTab并提交给store
store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab))
// 从localStorage中获取ACCESS_TOKEN并提交给store
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
// last step
// 最后一步
}

@ -14,16 +14,25 @@ import store from '../../store'
*
* @see https://github.com/sendya/ant-design-pro-vue/pull/53
*/
// 定义一个名为action的Vue指令
const action = Vue.directive('action', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el, binding, vnode) {
// 获取指令的参数即actionName
const actionName = binding.arg
// 获取当前用户的角色
const roles = store.getters.roles
// 获取当前路由的权限
const elVal = vnode.context.$route.meta.permission
// 将权限转换为数组
const permissionId = elVal instanceof String && [elVal] || elVal
// 遍历当前用户的权限
roles.permissions.forEach(p => {
// 如果当前权限不包含在路由权限中,则返回
if (!permissionId.includes(p.permissionId)) {
return
}
// 如果当前权限的actionList中不包含指令的参数则移除元素或隐藏元素
if (p.actionList && !p.actionList.includes(actionName)) {
el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none')
}

@ -11,10 +11,15 @@ import VueClipboard from 'vue-clipboard2'
import PermissionHelper from '../utils/helper/permission'
import './directives/action'
// 设置VueClipboard自动设置容器
VueClipboard.config.autoSetContainer = true
// 使用Viser插件
Vue.use(Viser)
// 使用VueStorage插件并传入配置参数
Vue.use(VueStorage, config.storageOptions)
// 使用VueClipboard插件
Vue.use(VueClipboard)
// 使用PermissionHelper插件
Vue.use(PermissionHelper)

@ -14,12 +14,19 @@ import PermissionHelper from '../utils/helper/permission'
// import '../components/use'
import './directives/action'
// 设置VueClipboard自动设置容器
VueClipboard.config.autoSetContainer = true
// 使用Antd组件库
Vue.use(Antd)
// 使用Viser组件库
Vue.use(Viser)
// 使用VueStorage插件并传入配置参数
Vue.use(VueStorage, config.storageOptions)
// 使用VueClipboard插件
Vue.use(VueClipboard)
// 使用PermissionHelper插件
Vue.use(PermissionHelper)
// 使用VueCropper插件
Vue.use(VueCropper)

@ -1,6 +1,7 @@
<template>
<a-layout :class="['layout', device]">
<!-- SideMenu -->
<!-- 如果是移动设备则显示抽屉菜单 -->
<a-drawer
v-if="isMobile()"
placement="left"
@ -9,6 +10,7 @@
:visible="collapsed"
@close="drawerClose"
>
<!-- 抽屉菜单 -->
<side-menu
mode="inline"
:menus="menus"
@ -19,6 +21,7 @@
></side-menu>
</a-drawer>
<!-- 如果不是移动设备则显示侧边菜单 -->
<side-menu
v-else-if="isSideMenu()"
mode="inline"
@ -28,8 +31,10 @@
:collapsible="true"
></side-menu>
<!-- layout -->
<a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }">
<!-- layout header -->
<!-- 全局头部 -->
<global-header
:mode="layoutMode"
:menus="menus"
@ -40,19 +45,24 @@
/>
<!-- layout content -->
<!-- layout 内容 -->
<a-layout-content :style="{ height: '100%', margin: '24px 24px 0', paddingTop: fixedHeader ? '64px' : '0' }">
<!-- 多标签页 -->
<multi-tab v-if="multiTab"></multi-tab>
<!-- 路由视图 -->
<transition name="page-transition">
<route-view />
</transition>
</a-layout-content>
<!-- layout footer -->
<!-- 全局底部 -->
<a-layout-footer>
<global-footer />
</a-layout-footer>
<!-- Setting Drawer (show in development mode) -->
<!-- 设置抽屉仅在开发模式下显示 -->
<setting-drawer v-if="!production"></setting-drawer>
</a-layout>
</a-layout>
@ -60,11 +70,16 @@
</template>
<script>
//
import { triggerWindowResizeEvent } from '../utils/util'
// vuexmapStatemapActions
import { mapState, mapActions } from 'vuex'
// mixinmixinDevice
import { mixin, mixinDevice } from '../utils/mixin'
//
import config from '../config/defaultSettings'
//
import RouteView from './RouteView'
import MultiTab from '../components/MultiTab'
import SideMenu from '../components/Menu/SideMenu'
@ -74,6 +89,7 @@ import SettingDrawer from '../components/SettingDrawer'
export default {
name: 'BasicLayout',
// 使mixinmixinDevice
mixins: [mixin, mixinDevice],
components: {
RouteView,
@ -85,16 +101,20 @@ export default {
},
data () {
return {
//
production: config.production,
//
collapsed: false,
//
menus: []
}
},
computed: {
//
...mapState({
//
mainMenu: state => state.permission.addRouters
}),
//
contentPaddingLeft () {
if (!this.fixSidebar || this.isMobile()) {
return '0'
@ -106,15 +126,19 @@ export default {
}
},
watch: {
// sidebarOpened
sidebarOpened (val) {
this.collapsed = !val
}
},
created () {
//
this.menus = this.mainMenu.find(item => item.path === '/').children
// collapsed
this.collapsed = !this.sidebarOpened
},
mounted () {
// Edge
const userAgent = navigator.userAgent
if (userAgent.indexOf('Edge') > -1) {
this.$nextTick(() => {
@ -126,12 +150,15 @@ export default {
}
},
methods: {
// 使vuexmapActions
...mapActions(['setSidebar']),
//
toggle () {
this.collapsed = !this.collapsed
this.setSidebar(!this.collapsed)
triggerWindowResizeEvent()
},
//
paddingCalc () {
let left = ''
if (this.sidebarOpened) {
@ -141,11 +168,13 @@ export default {
}
return left
},
//
menuSelect () {
if (!this.isDesktop()) {
this.collapsed = false
}
},
//
drawerClose () {
this.collapsed = false
}

@ -61,14 +61,17 @@ export default {
PageHeader
},
props: {
//
avatar: {
type: String,
default: null
},
//
title: {
type: [String, Boolean],
default: true
},
// logo
logo: {
type: String,
default: null
@ -76,26 +79,36 @@ export default {
},
data () {
return {
//
pageTitle: null,
//
description: null,
//
linkList: [],
//
extraImage: '',
//
search: false,
//
tabs: {}
}
},
computed: {
...mapState({
//
multiTab: state => state.app.multiTab
})
},
mounted () {
//
this.getPageMeta()
},
updated () {
//
this.getPageMeta()
},
methods: {
//
getPageMeta () {
// eslint-disable-next-line
this.pageTitle = (typeof(this.title) === 'string' || !this.title) ? this.title : this.$route.meta.title
@ -103,8 +116,10 @@ export default {
const content = this.$refs.content
if (content) {
if (content.pageMeta) {
// pageMetapageMeta
Object.assign(this, content.pageMeta)
} else {
// descriptionlinkListextraImagesearchtabs
this.description = content.description
this.linkList = content.linkList
this.extraImage = content.extraImage

@ -1,6 +1,7 @@
<script>
export default {
name: 'RouteView',
// props keepAlive true
props: {
keepAlive: {
type: Boolean,
@ -10,13 +11,17 @@ export default {
data () {
return {}
},
//
render () {
// meta store getters
const { $route: { meta }, $store: { getters } } = this
// inKeep
const inKeep = (
<keep-alive>
<router-view />
</keep-alive>
)
// notKeep
const notKeep = (
<router-view />
)
@ -26,6 +31,7 @@ export default {
if (!getters.multiTab && meta.keepAlive === false) {
return notKeep
}
// keepAlivemultiTab meta.keepAlive inKeep notKeep
return this.keepAlive || getters.multiTab || meta.keepAlive ? inKeep : notKeep
}
}

@ -30,29 +30,40 @@
</template>
<script>
// RouteView
import RouteView from './RouteView'
// mixinDevice
import { mixinDevice } from '../utils/mixin'
export default {
//
name: 'UserLayout',
//
components: { RouteView },
// mixinDevice
mixins: [mixinDevice],
//
data () {
return {}
},
//
mounted () {
// userLayout
document.body.classList.add('userLayout')
},
//
beforeDestroy () {
// userLayout
document.body.classList.remove('userLayout')
}
}
</script>
<style lang="less" scoped>
//
#userLayout.user-layout-wrapper {
height: 100%;
//
&.mobile {
.container {
.main {
@ -62,6 +73,7 @@ export default {
}
}
//
.container {
width: 100%;
min-height: 100%;
@ -70,17 +82,21 @@ export default {
padding: 110px 0 144px;
position: relative;
//
a {
text-decoration: none;
}
//
.top {
text-align: center;
//
.header {
height: 44px;
line-height: 44px;
//
.badge {
position: absolute;
display: inline-block;
@ -91,6 +107,7 @@ export default {
opacity: 0.8;
}
// logo
.logo {
height: 44px;
vertical-align: top;
@ -98,6 +115,7 @@ export default {
border-style: none;
}
//
.title {
font-size: 33px;
color: rgba(0, 0, 0, .85);
@ -108,6 +126,7 @@ export default {
}
}
//
.desc {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
@ -116,12 +135,14 @@ export default {
}
}
//
.main {
min-width: 260px;
width: 368px;
margin: 0 auto;
}
//
.footer {
position: absolute;
width: 100%;
@ -130,6 +151,7 @@ export default {
margin: 48px 0 24px;
text-align: center;
//
.links {
margin-bottom: 8px;
font-size: 14px;
@ -138,12 +160,14 @@ export default {
color: rgba(0, 0, 0, 0.45);
transition: all 0.3s;
//
&:not(:last-child) {
margin-right: 40px;
}
}
}
//
.copyright {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;

Loading…
Cancel
Save