This commit is contained in:
weicw
2021-07-29 16:17:26 +08:00
commit a6eb6f83d1
127 changed files with 60792 additions and 0 deletions

34
src/layout/footer.vue Normal file
View File

@@ -0,0 +1,34 @@
<!-- 页脚 -->
<template>
<div class="ele-text-center" style="padding: 16px 0;">
<!-- <a-space size="large">
<a
class="ele-text-secondary"
href="https://eleadmin.com"
target="_blank">
{{ $t('layout.footer.website') }}
</a>
<a
class="ele-text-secondary"
href="https://eleadmin.com/doc/eleadminpro/"
target="_blank">
{{ $t('layout.footer.document') }}
</a>
<a
class="ele-text-secondary"
href="https://eleadmin.com/goods/9"
target="_blank">
{{ $t('layout.footer.authorization') }}
</a>
</a-space> -->
<div class="ele-text-secondary" style="margin-top: 8px;">
{{ $t('layout.footer.copyright') }}
</div>
</div>
</template>
<script>
export default {
name: 'EleFooter'
}
</script>

163
src/layout/header-right.vue Normal file
View File

@@ -0,0 +1,163 @@
<!-- 顶栏右侧区域按钮 -->
<template>
<div class="ele-admin-header-tool">
<div class="ele-admin-header-tool-item hidden-sm-and-down" @click="changeFullscreen">
<fullscreen-exit-outlined v-if="fullscreen"/>
<fullscreen-outlined v-else/>
</div>
<!-- 语言切换 -->
<!-- <div class="ele-admin-header-tool-item">
<a-dropdown placement="bottomCenter" :overlay-style="{minWidth: '120px', paddingTop: '17px'}">
<global-outlined style="transform: scale(1.08);"/>
<template #overlay>
<a-menu :selected-keys="language" @click="changeLanguage">
<a-menu-item key="en">English</a-menu-item>
<a-menu-item key="zh_CN">简体中文</a-menu-item>
<a-menu-item key="zh_TW">繁體中文</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div> -->
<!-- 消息通知 -->
<!-- <div class="ele-admin-header-tool-item">
<ele-notice/>
</div> -->
<!-- 用户信息 -->
<div class="ele-admin-header-tool-item">
<a-dropdown placement="bottomCenter" :overlay-style="{minWidth: '120px'}">
<div class="ele-admin-header-avatar">
<a-avatar :src="loginUser.avatar"/>
<span>{{ loginUser.nickname }}&nbsp;</span>
<down-outlined/>
</div>
<template #overlay>
<a-menu @click="onUserDropClick">
<a-menu-item key="profile">
<div class="ele-cell">
<user-outlined/>
<div class="ele-cell-content">{{ $t('layout.header.profile') }}</div>
</div>
</a-menu-item>
<a-menu-item key="password">
<div class="ele-cell">
<key-outlined/>
<div class="ele-cell-content">{{ $t('layout.header.password') }}</div>
</div>
</a-menu-item>
<a-menu-divider/>
<a-menu-item key="logout">
<div class="ele-cell">
<logout-outlined/>
<div class="ele-cell-content">{{ $t('layout.header.logout') }}</div>
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
<!-- 主题设置 -->
<div v-if="showSetting" class="ele-admin-header-tool-item" @click="openSetting">
<MoreOutlined/>
</div>
</div>
</template>
<script>
import {createVNode} from 'vue';
import {
DownOutlined,
MoreOutlined,
UserOutlined,
KeyOutlined,
LogoutOutlined,
ExclamationCircleOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
// GlobalOutlined
} from '@ant-design/icons-vue';
import {toggleFullscreen, isFullscreen} from 'ele-admin-pro/packages/util.js';
// import EleNotice from './notice';
export default {
name: 'EleHeaderRight',
components: {
DownOutlined,
MoreOutlined,
UserOutlined,
KeyOutlined,
LogoutOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
// GlobalOutlined,
// EleNotice
},
emits: ['item-click', 'change-language'],
props: {
// 是否显示打开设置抽屉按钮
showSetting: {
type: Boolean,
default: true
}
},
computed: {
// 当前登录用户信息
loginUser() {
return this.$store.state.user.user;
},
// 当前语言
language() {
return [this.$i18n.locale];
}
},
data() {
return {
// 是否全屏状态
fullscreen: false
};
},
methods: {
/* 个人信息下拉菜单点击 */
onUserDropClick({key}) {
if (key === 'logout') {
// 退出登录
this.$confirm({
title: this.$t('layout.logout.title'),
content: this.$t('layout.logout.message'),
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
// 清除缓存的token
this.$store.dispatch('user/removeToken').then(() => {
location.replace('/login'); // 这样跳转避免再次登录重复注册动态路由
});
}
});
} else if (key === 'profile') {
this.$router.push('/user/profile');
} else if (key === 'password') {
this.$emit('item-click', 'password');
}
},
/* 打开设置抽屉 */
openSetting() {
this.$emit('item-click', 'setting');
},
/* 全屏切换 */
changeFullscreen() {
try {
this.fullscreen = toggleFullscreen();
} catch (e) {
this.$message.error('您的浏览器不支持全屏模式');
}
},
/* 检查全屏状态 */
checkFullscreen() {
this.fullscreen = isFullscreen();
},
/* 切换语言 */
changeLanguage({key}) {
this.$emit('change-language', key);
}
}
}
</script>

240
src/layout/index.vue Normal file
View File

@@ -0,0 +1,240 @@
<!-- 框架布局 -->
<template>
<ele-pro-layout
ref="layout"
:i18n="i18n"
:menus="user.menus"
:home-title="homeTitle"
:project-name="projectName"
:show-content="showContent"
v-model:show-setting="showSetting"
:need-setting="setting.needSetting"
:hide-footers="setting.hideFooters"
:hide-sidebars="setting.hideSidebars"
:repeatable-tabs="setting.repeatableTabs"
:tabs="theme.tabs"
:color="theme.color"
:collapse="theme.collapse"
:head-style="theme.headStyle"
:side-style="theme.sideStyle"
:layout-style="theme.layoutStyle"
:side-menu-style="theme.sideMenuStyle"
:fixed-body="theme.fixedBody"
:fixed-header="theme.fixedHeader"
:fixed-sidebar="theme.fixedSidebar"
:body-full="theme.bodyFull"
:show-footer="theme.showFooter"
:colorful-icon="theme.colorfulIcon"
:logo-auto-size="theme.logoAutoSize"
:side-unique-open="theme.sideUniqueOpen"
:show-tabs="theme.showTabs"
:tab-style="theme.tabStyle"
:dark-mode="theme.darkMode"
:weak-mode="theme.weakMode"
@logo-click="onLogoClick"
@reload-page="reloadPage"
@update-screen="updateScreen"
@update-collapse="updateCollapse"
@tab-add="tabAdd"
@tab-remove="tabRemove"
@tab-remove-all="tabRemoveAll"
@tab-remove-left="tabRemoveLeft"
@tab-remove-right="tabRemoveRight"
@tab-remove-other="tabRemoveOther"
@change-style="changeStyle"
@change-color="changeColor"
@change-dark-mode="changeDarkMode"
@change-weak-mode="changeWeakMode"
@set-home-components="setHomeComponents">
<!-- logo图标 -->
<template #logo>
<img src="@/assets/logo.svg" alt="logo"/>
</template>
<!-- 顶栏右侧区域 -->
<template #right>
<ele-header-right
ref="header"
:show-setting="setting.needSetting"
@change-language="changeLanguage"
@item-click="onItemClick"/>
</template>
<!-- 全局页脚 -->
<template #footer>
<ele-footer/>
</template>
<!-- 修改密码弹窗 -->
<ele-password v-model:visible="showPassword"/>
</ele-pro-layout>
</template>
<script>
import {mapGetters} from 'vuex';
import EleHeaderRight from './header-right';
import ElePassword from './password';
import EleFooter from './footer';
import setting from '@/config/setting';
import {
reloadPageTab,
addPageTab,
removePageTab,
removeAllPageTab,
removeLeftPageTab,
removeRightPageTab,
removeOtherPageTab
} from '@/utils/page-tab-util';
export default {
name: 'EleLayout',
components: {
EleHeaderRight,
ElePassword,
EleFooter
},
computed: {
// 主页标题, 移除国际化上面template中使用:home-title="setting.homeTitle"
homeTitle() {
return this.$t('layout.home');
},
...mapGetters(['theme', 'user'])
},
data() {
return {
// 全局配置
setting: setting,
// 是否显示修改密码弹窗
showPassword: false,
// 是否显示主题设置抽屉
showSetting: false,
// 是否显示主体部分
showContent: true,
// 项目名
projectName: process.env.VUE_APP_NAME
};
},
created() {
// 获取用户信息
this.getUserInfo();
},
methods: {
/* 获取当前用户信息 */
getUserInfo() {
if (setting.userUrl) {
this.$http.get(setting.userUrl).then((res) => {
const result = setting.parseUser ? setting.parseUser(res.data) : res.data;
if (result.code === 0) {
const user = result.data;
this.$store.dispatch('user/setUser', user);
this.$store.dispatch('user/setRoles', user ? user.roles : null);
this.$store.dispatch('user/setAuthorities', user ? user.authorities : null);
} else if (result.msg) {
this.$message.error(result.msg);
}
// 在用户权限信息请求完成后再渲染主体部分, 以免权限控制指令不生效
this.showContent = true;
}).catch((e) => {
console.error(e);
this.showContent = true;
this.$message.error(e.message);
});
}
},
/* 顶栏右侧点击 */
onItemClick(key) {
if (key === 'password') {
this.showPassword = true;
} else if (key === 'setting') {
this.showSetting = true;
}
},
/* 刷新页签 */
reloadPage() {
reloadPageTab();
},
/* logo点击事件 */
onLogoClick(isHome) {
if (!isHome) {
this.$router.push('/');
}
},
/* 更新collapse */
updateCollapse(value) {
this.$store.dispatch('theme/set', {key: 'collapse', value: value});
},
/* 更新屏幕尺寸 */
updateScreen() {
this.$store.dispatch('theme/updateScreen');
const checkFullscreen = this.$refs.header.checkFullscreen;
checkFullscreen && checkFullscreen();
},
/* 切换主题风格 */
changeStyle(value) {
this.$store.dispatch('theme/set', value);
},
/* 切换主题色 */
changeColor(value) {
const hide = this.$message.loading({content: '正在加载主题...'});
this.$store.dispatch('theme/setColor', value).then(() => {
hide();
}).catch((e) => {
console.error(e);
hide();
this.$message.error('主题加载失败');
});
},
changeDarkMode(value) {
this.$store.dispatch('theme/setDarkMode', value);
},
changeWeakMode(value) {
this.$store.dispatch('theme/setWeakMode', value);
},
setHomeComponents(components) {
this.$store.dispatch('theme/setHomeComponents', components);
},
/* 添加tab */
tabAdd(value) {
addPageTab(value);
},
/* 移除tab */
tabRemove(obj) {
removePageTab(obj.name).then(({lastPath}) => {
if (obj.active === obj.name) {
this.$router.push(lastPath || '/');
}
});
},
/* 移除全部tab */
tabRemoveAll(active) {
removeAllPageTab();
if (active !== '/') {
this.$router.push('/');
}
},
/* 移除左边tab */
tabRemoveLeft(value) {
removeLeftPageTab(value);
},
/* 移除右边tab */
tabRemoveRight(value) {
removeRightPageTab(value);
},
/* 移除其它tab */
tabRemoveOther(value) {
removeOtherPageTab(value);
},
/* 菜单路由国际化对应的名称 */
i18n(path, key/*, menu*/) {
// 参数三menu即原始菜单数据, 如果需要菜单标题多语言数据从接口返回可用此参数获取对应的多语言标题
// 例如下面这样写, 接口的菜单数据为{path: '/system/user', titles: {zh: '用户管理', en: 'User'}}
// return menu ? menu.titles[this.$i18n.locale] : null;
const k = 'route.' + key + '._name', title = this.$t(k);
return title === k ? null : title;
},
/* 切换语言 */
changeLanguage(lang) {
this.$i18n.locale = lang;
this.$refs.layout.changeLanguage();
localStorage.setItem('i18n-lang', lang);
}
}
}
</script>

278
src/layout/notice.vue Normal file
View File

@@ -0,0 +1,278 @@
<!-- 顶栏消息通知图标 -->
<template>
<a-dropdown v-model:visible="visible" :trigger="['click']">
<a-badge :count="allNum" class="ele-notice-trigger">
<bell-outlined style="padding: 6px;"/>
</a-badge>
<template #overlay>
<div class="ant-dropdown-menu ele-notice-pop">
<div @click.stop="">
<a-tabs v-model:active-key="active">
<a-tab-pane key="notice" :tab="noticeTitle" force-render>
<a-list item-layout="horizontal" :data-source="notice">
<template #renderItem="{item}">
<a-list-item>
<a-list-item-meta :title="item.title" :description="item.time">
<template #avatar>
<a-avatar :style="{background: item.color}">
<template #icon>
<component :is="item.icon"/>
</template>
</a-avatar>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
<div v-if="notice.length" class="ele-cell ele-notice-actions">
<div class="ele-cell-content" @click="clear('notice')">清空通知</div>
<a-divider type="vertical"/>
<div class="ele-cell-content" @click="more('notice')">查看更多</div>
</div>
</a-tab-pane>
<a-tab-pane key="message" :tab="messageTitle" force-render>
<a-list item-layout="horizontal" :data-source="message">
<template #renderItem="{item}">
<a-list-item>
<a-list-item-meta :title="item.title">
<template #avatar>
<a-avatar :src="item.avatar"/>
</template>
<template #description>
<div>{{ item.content }}</div>
<div>{{ item.time }}</div>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
<div v-if="message.length" class="ele-cell ele-notice-actions">
<div class="ele-cell-content" @click="clear('message')">清空私信</div>
<a-divider type="vertical"/>
<div class="ele-cell-content" @click="more('message')">查看更多</div>
</div>
</a-tab-pane>
<a-tab-pane key="todo" :tab="todoTitle" force-render>
<a-list item-layout="horizontal" :data-source="todo">
<template #renderItem="{item}">
<a-list-item>
<a-list-item-meta :description="item.desc">
<template #title>
<div class="ele-cell">
<div class="ele-cell-content">{{ item.title }}</div>
<a-tag :color="['', 'red', 'blue'][item.state]">
{{ ['未开始', '即将到期', '进行中'][item.state] }}
</a-tag>
</div>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
<div v-if="todo.length" class="ele-cell ele-notice-actions">
<div class="ele-cell-content" @click="clear('todo')">清空待办</div>
<a-divider type="vertical"/>
<div class="ele-cell-content" @click="more('todo')">查看更多</div>
</div>
</a-tab-pane>
</a-tabs>
</div>
</div>
</template>
</a-dropdown>
</template>
<script>
import {
BellOutlined,
NotificationFilled,
PushpinFilled,
VideoCameraFilled,
CarryOutFilled,
BellFilled
} from '@ant-design/icons-vue';
export default {
name: 'EleNotice',
components: {
BellOutlined,
NotificationFilled,
PushpinFilled,
VideoCameraFilled,
CarryOutFilled,
BellFilled
},
data() {
return {
visible: false,
active: 'notice',
notice: [
{
color: '#60B2FC',
icon: 'NotificationFilled',
title: '你收到了一封14份新周报',
time: '2020-07-27 18:30:18'
},
{
color: '#F5686F',
icon: 'PushpinFilled',
title: '许经理同意了你的请假申请',
time: '2020-07-27 09:08:36'
},
{
color: '#7CD734',
icon: 'VideoCameraFilled',
title: '陈总邀请你参加视频会议',
time: '2020-07-26 18:30:01'
},
{
color: '#FAAD14',
icon: 'CarryOutFilled',
title: '你推荐的刘诗雨已通过第三轮面试',
time: '2020-07-25 16:38:46'
},
{
color: '#2BCACD',
icon: 'BellFilled',
title: '你的6月加班奖金已发放',
time: '2020-07-25 11:03:31'
}
],
message: [
{
avatar: 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg',
title: 'SunSmile 评论了你的日志',
content: '写的不错, 以后多多向你学习~',
time: '2020-07-27 18:30:18'
},
{
avatar: 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg',
title: '刘诗雨 点赞了你的日志',
content: '写的不错, 以后多多向你学习~',
time: '2020-07-27 09:08:36'
},
{
avatar: 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg',
title: '酷酷的大叔 评论了你的周报',
content: '写的不错, 以后多多向你学习~',
time: '2020-07-26 18:30:01'
},
{
avatar: 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg',
title: 'Jasmine 点赞了你的周报',
content: '写的不错, 以后多多向你学习~',
time: '2020-07-25 11:03:31'
}
],
todo: [
{
state: 0,
title: '刘诗雨的请假审批',
desc: '刘诗雨在 07-27 18:30 提交的请假申请'
},
{
state: 1,
title: '第三方代码紧急变更',
desc: '需要在 2020-07-27 之前完成'
},
{
state: 2,
title: '信息安全考试',
desc: '需要在 2020-07-26 18:30 前完成'
},
{
state: 2,
title: 'EleAdmin发布新版本',
desc: '需要在 2020-07-25 11:03 前完成'
}
]
};
},
computed: {
// 通知标题
noticeTitle() {
return this.notice.length ? `通知(${this.notice.length})` : '通知';
},
// 私信标题
messageTitle() {
return this.message.length ? `私信(${this.message.length})` : '私信';
},
// 待办标题
todoTitle() {
return this.todo.length ? `待办(${this.todo.length})` : '待办';
},
// 所有消息数量
allNum() {
return this.notice.length + this.message.length + this.todo.length;
}
},
methods: {
/* 清空消息 */
clear(type) {
if (type === 'notice') {
this.notice = [];
} else if (type === 'message') {
this.message = [];
} else if (type === 'todo') {
this.todo = [];
}
},
/* 查看更多 */
more(type) {
this.visible = false;
if (this.$route.path !== '/user/message' || this.$route.query.type !== type) {
this.$router.push({path: '/user/message', query: {type: type}});
}
}
}
}
</script>
<style>
.ele-notice-trigger.ant-badge {
color: inherit;
}
.ele-notice-pop.ant-dropdown-menu {
padding: 0;
width: 336px;
max-width: 100%;
margin-top: 11px;
}
/* 内容 */
.ele-notice-pop .ant-tabs-nav-wrap {
text-align: center;
}
.ele-notice-pop .ant-list-item {
padding-left: 24px;
padding-right: 24px;
transition: background-color .3s;
cursor: pointer;
}
.ele-notice-pop .ant-list-item:hover {
background: hsla(0, 0%, 60%, .05);
}
.ele-notice-pop .ant-tag {
margin: 0;
}
/* 操作按钮 */
.ele-notice-pop .ele-notice-actions {
border-top: 1px solid hsla(0, 0%, 60%, .15);
}
.ele-notice-pop .ele-notice-actions > .ele-cell-content {
line-height: 46px;
text-align: center;
transition: background-color .3s;
cursor: pointer;
}
.ele-notice-pop .ele-notice-actions > .ele-cell-content:hover {
background: hsla(0, 0%, 60%, .05);
}
</style>

110
src/layout/password.vue Normal file
View File

@@ -0,0 +1,110 @@
<!-- 修改密码弹窗 -->
<template>
<a-modal
:width="420"
title="修改密码"
:visible="visible"
:confirm-loading="loading"
:body-style="{paddingBottom: '16px'}"
@update:visible="onUpdateVisible"
@cancel="onCancel"
@ok="onOk">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{sm: {span: 6}}"
:wrapper-col="{sm: {span: 18}}">
<a-form-item label="旧密码" name="old">
<a-input-password v-model:value="form.old" placeholder="请输入旧密码"/>
</a-form-item>
<a-form-item label="新密码" name="password">
<a-input-password v-model:value="form.password" placeholder="请输入新密码"/>
</a-form-item>
<a-form-item label="确认密码" name="password2">
<a-input-password v-model:value="form.password2" placeholder="请再次输入新密码"/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
export default {
name: 'ElePassword',
emits: ['update:visible'],
props: {
visible: Boolean
},
data() {
return {
// 表单数据
form: {
old: '',
password: '',
password2: ''
},
// 表单验证
rules: {
old: [
{required: true, message: '请输入旧密码', type: 'string', trigger: 'blur'}
],
password: [
{required: true, message: '请输入新密码', type: 'string', trigger: 'blur'}
],
password2: [
{
required: true,
type: 'string',
trigger: 'blur',
validator: async (rule, value) => {
if (!value) {
return Promise.reject('请再次输入新密码');
}
if (value === this.form.password) {
return Promise.resolve();
}
return Promise.reject('两次输入密码不一致');
}
}
]
},
// 按钮loading
loading: false
};
},
methods: {
/* 保存修改 */
onOk() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http.put('/main/password', {
oldPsw: this.form.old,
newPsw: this.form.password
}).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.onUpdateVisible(false);
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 关闭回调 */
onCancel() {
this.form = {};
this.loading = false;
this.$refs.form.resetFields();
},
/* 修改visible */
onUpdateVisible(value) {
this.$emit('update:visible', value);
}
}
}
</script>