菜单自定义

prod
许宏杰 10 months ago
parent 863dffa20d
commit f3795fedab

@ -1,11 +1,11 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 金鸡湖现代服务业品牌管理系统
# 开发环境配置 # 开发环境配置
ENV = 'development' ENV = 'development'
# 若依管理系统/开发环境 # 金鸡湖现代服务业品牌管理系统/开发环境
VUE_APP_BASE_API = '/dev-api' VUE_APP_BASE_API = 'https://vue.ruoyi.vip/prod-api'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true

@ -1,8 +1,8 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 金鸡湖现代服务业品牌管理系统
# 生产环境配置 # 生产环境配置
ENV = 'production' ENV = 'production'
# 若依管理系统/生产环境 # 金鸡湖现代服务业品牌管理系统/生产环境
VUE_APP_BASE_API = '/prod-api' VUE_APP_BASE_API = '/prod-api'

@ -1,10 +1,10 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 金鸡湖现代服务业品牌管理系统
NODE_ENV = production NODE_ENV = production
# 测试环境配置 # 测试环境配置
ENV = 'staging' ENV = 'staging'
# 若依管理系统/测试环境 # 金鸡湖现代服务业品牌管理系统/测试环境
VUE_APP_BASE_API = '/stage-api' VUE_APP_BASE_API = '/stage-api'

@ -1,7 +1,7 @@
{ {
"name": "ruoyi", "name": "ruoyi",
"version": "3.8.7", "version": "3.8.7",
"description": "若依管理系统", "description": "金鸡湖现代服务业品牌管理系统",
"author": "若依", "author": "若依",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -37,6 +37,7 @@
}, },
"dependencies": { "dependencies": {
"@riophae/vue-treeselect": "0.4.0", "@riophae/vue-treeselect": "0.4.0",
"autofit.js": "^3.1.0",
"axios": "0.24.0", "axios": "0.24.0",
"clipboard": "2.0.8", "clipboard": "2.0.8",
"core-js": "3.25.3", "core-js": "3.25.3",

@ -7,18 +7,31 @@
<script> <script>
import ThemePicker from "@/components/ThemePicker"; import ThemePicker from "@/components/ThemePicker";
// import autofit from "autofit.js";
export default { export default {
name: "App", name: "App",
components: { ThemePicker }, components: { ThemePicker },
mounted() {
// console.log(window.innerHeight);
// autofit.init({
// dh: 919,
// dw: 1920,
// el: "body",
// resize: true,
// });
},
metaInfo() { metaInfo() {
return { return {
title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title, title:
titleTemplate: title => { this.$store.state.settings.dynamicTitle &&
return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE this.$store.state.settings.title,
} titleTemplate: (title) => {
} return title
} ? `${title} - ${process.env.VUE_APP_TITLE}`
: process.env.VUE_APP_TITLE;
},
};
},
}; };
</script> </script>
<style scoped> <style scoped>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@ -1,16 +1,17 @@
@import './variables.scss'; @import "./variables.scss";
@import './mixin.scss'; @import "./mixin.scss";
@import './transition.scss'; @import "./transition.scss";
@import './element-ui.scss'; @import "./element-ui.scss";
@import './sidebar.scss'; @import "./sidebar.scss";
@import './btn.scss'; @import "./btn.scss";
body { body {
height: 100%; height: 100%;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
} }
label { label {
@ -104,7 +105,8 @@ aside {
display: block; display: block;
line-height: 32px; line-height: 32px;
font-size: 16px; font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
color: #2c3e50; color: #2c3e50;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@ -121,7 +123,9 @@ aside {
//main-container //main-container
.app-container { .app-container {
padding: 20px; padding: 10px 10px 0 10px;
height: 100%;
background-color: #fff;
} }
.components-container { .components-container {
@ -134,7 +138,7 @@ aside {
} }
.text-center { .text-center {
text-align: center text-align: center;
} }
.sub-navbar { .sub-navbar {
@ -145,7 +149,13 @@ aside {
text-align: right; text-align: right;
padding-right: 20px; padding-right: 20px;
transition: 600ms ease position; transition: 600ms ease position;
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); background: linear-gradient(
90deg,
rgba(32, 182, 249, 1) 0%,
rgba(32, 182, 249, 1) 0%,
rgba(33, 120, 241, 1) 100%,
rgba(33, 120, 241, 1) 100%
);
.subtitle { .subtitle {
font-size: 20px; font-size: 20px;

@ -1,25 +1,26 @@
// base color // base color
$blue:#324157; $blue: #324157;
$light-blue:#3A71A8; $light-blue: #3a71a8;
$red:#C03639; $red: #c03639;
$pink: #E65D6E; $pink: #e65d6e;
$green: #30B08F; $green: #30b08f;
$tiffany: #4AB7BD; $tiffany: #4ab7bd;
$yellow:#FEC171; $yellow: #fec171;
$panGreen: #30B08F; $panGreen: #30b08f;
$darkBlue: rgb(25, 42, 146);
// //
$base-menu-color:#bfcbd9; $base-menu-color: #bfcbd9;
$base-menu-color-active:#f4f4f5; $base-menu-color-active: #f4f4f5;
$base-menu-background:#304156; $base-menu-background: #304156;
$base-logo-title-color: #ffffff; $base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70); $base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background:#ffffff; $base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529; $base-logo-light-title-color: #001529;
$base-sub-menu-background:#1f2d3d; $base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover:#001528; $base-sub-menu-hover: #001528;
// //
/** /**
@ -50,5 +51,6 @@ $base-sidebar-width: 200px;
subMenuHover: $base-sub-menu-hover; subMenuHover: $base-sub-menu-hover;
sideBarWidth: $base-sidebar-width; sideBarWidth: $base-sidebar-width;
logoTitleColor: $base-logo-title-color; logoTitleColor: $base-logo-title-color;
logoLightTitleColor: $base-logo-light-title-color logoLightTitleColor: $base-logo-light-title-color;
navBackground: $darkBlue;
} }

@ -5,25 +5,37 @@
@select="handleSelect" @select="handleSelect"
> >
<template v-for="(item, index) in topMenus"> <template v-for="(item, index) in topMenus">
<el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"> <el-menu-item
:style="{ '--theme': theme }"
:index="item.path"
:key="index"
v-if="index < visibleNumber"
>
<svg-icon <svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'" v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta.icon"/> :icon-class="item.meta.icon"
/>
{{ item.meta.title }} {{ item.meta.title }}
</el-menu-item> </el-menu-item>
</template> </template>
<!-- 顶部菜单超出数量折叠 --> <!-- 顶部菜单超出数量折叠 -->
<el-submenu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber"> <el-submenu
:style="{ '--theme': theme }"
index="more"
v-if="topMenus.length > visibleNumber"
>
<template slot="title">更多菜单</template> <template slot="title">更多菜单</template>
<template v-for="(item, index) in topMenus"> <template v-for="(item, index) in topMenus">
<el-menu-item <el-menu-item
:index="item.path" :index="item.path"
:key="index" :key="index"
v-if="index >= visibleNumber"> v-if="index >= visibleNumber"
>
<svg-icon <svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'" v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta.icon"/> :icon-class="item.meta.icon"
/>
{{ item.meta.title }} {{ item.meta.title }}
</el-menu-item> </el-menu-item>
</template> </template>
@ -35,7 +47,7 @@
import { constantRoutes } from "@/router"; import { constantRoutes } from "@/router";
// //
const hideList = ['/index', '/user/profile']; const hideList = ["/index", "/user/profile"];
export default { export default {
data() { data() {
@ -43,7 +55,7 @@ export default {
// //
visibleNumber: 5, visibleNumber: 5,
// index // index
currentIndex: undefined currentIndex: undefined,
}; };
}, },
computed: { computed: {
@ -75,11 +87,12 @@ export default {
this.routers.map((router) => { this.routers.map((router) => {
for (var item in router.children) { for (var item in router.children) {
if (router.children[item].parentPath === undefined) { if (router.children[item].parentPath === undefined) {
if(router.path === "/") { if (router.path === "/") {
router.children[item].path = "/" + router.children[item].path; router.children[item].path = "/" + router.children[item].path;
} else { } else {
if(!this.ishttp(router.children[item].path)) { if (!this.ishttp(router.children[item].path)) {
router.children[item].path = router.path + "/" + router.children[item].path; router.children[item].path =
router.path + "/" + router.children[item].path;
} }
} }
router.children[item].parentPath = router.path; router.children[item].parentPath = router.path;
@ -93,25 +106,29 @@ export default {
activeMenu() { activeMenu() {
const path = this.$route.path; const path = this.$route.path;
let activePath = path; let activePath = path;
if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) { if (
path !== undefined &&
path.lastIndexOf("/") > 0 &&
hideList.indexOf(path) === -1
) {
const tmpPath = path.substring(1, path.length); const tmpPath = path.substring(1, path.length);
activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/")); activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
if (!this.$route.meta.link) { if (!this.$route.meta.link) {
this.$store.dispatch('app/toggleSideBarHide', false); this.$store.dispatch("app/toggleSideBarHide", false);
} }
} else if(!this.$route.children) { } else if (!this.$route.children) {
activePath = path; activePath = path;
this.$store.dispatch('app/toggleSideBarHide', true); this.$store.dispatch("app/toggleSideBarHide", true);
} }
this.activeRoutes(activePath); this.activeRoutes(activePath);
return activePath; return activePath;
}, },
}, },
beforeMount() { beforeMount() {
window.addEventListener('resize', this.setVisibleNumber) window.addEventListener("resize", this.setVisibleNumber);
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('resize', this.setVisibleNumber) window.removeEventListener("resize", this.setVisibleNumber);
}, },
mounted() { mounted() {
this.setVisibleNumber(); this.setVisibleNumber();
@ -125,24 +142,24 @@ export default {
// //
handleSelect(key, keyPath) { handleSelect(key, keyPath) {
this.currentIndex = key; this.currentIndex = key;
const route = this.routers.find(item => item.path === key); const route = this.routers.find((item) => item.path === key);
if (this.ishttp(key)) { if (this.ishttp(key)) {
// http(s):// // http(s)://
window.open(key, "_blank"); window.open(key, "_blank");
} else if (!route || !route.children) { } else if (!route || !route.children) {
// //
const routeMenu = this.childrenMenus.find(item => item.path === key); const routeMenu = this.childrenMenus.find((item) => item.path === key);
if (routeMenu && routeMenu.query) { if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query); let query = JSON.parse(routeMenu.query);
this.$router.push({ path: key, query: query }); this.$router.push({ path: key, query: query });
} else { } else {
this.$router.push({ path: key }); this.$router.push({ path: key });
} }
this.$store.dispatch('app/toggleSideBarHide', true); this.$store.dispatch("app/toggleSideBarHide", true);
} else { } else {
// //
this.activeRoutes(key); this.activeRoutes(key);
this.$store.dispatch('app/toggleSideBarHide', false); this.$store.dispatch("app/toggleSideBarHide", false);
} }
}, },
// //
@ -155,15 +172,15 @@ export default {
} }
}); });
} }
if(routes.length > 0) { if (routes.length > 0) {
this.$store.commit("SET_SIDEBAR_ROUTERS", routes); this.$store.commit("SET_SIDEBAR_ROUTERS", routes);
} else { } else {
this.$store.dispatch('app/toggleSideBarHide', true); this.$store.dispatch("app/toggleSideBarHide", true);
} }
}, },
ishttp(url) { ishttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 return url.indexOf("http://") !== -1 || url.indexOf("https://") !== -1;
} },
}, },
}; };
</script> </script>
@ -178,8 +195,9 @@ export default {
margin: 0 10px !important; margin: 0 10px !important;
} }
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-submenu.is-active .el-submenu__title { .topmenu-container.el-menu--horizontal > .el-menu-item.is-active,
border-bottom: 2px solid #{'var(--theme)'} !important; .el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
border-bottom: 2px solid #{"var(--theme)"} !important;
color: #303133; color: #303133;
} }

@ -10,29 +10,31 @@
</template> </template>
<script> <script>
import iframeToggle from "./IframeToggle/index" import iframeToggle from "./IframeToggle/index";
export default { export default {
name: 'AppMain', name: "AppMain",
components: { iframeToggle }, components: { iframeToggle },
computed: { computed: {
cachedViews() { cachedViews() {
return this.$store.state.tagsView.cachedViews return this.$store.state.tagsView.cachedViews;
}, },
key() { key() {
return this.$route.path return this.$route.path;
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.app-main { .app-main {
/* 50= navbar 50 */ /* 50= navbar 50 */
min-height: calc(100vh - 50px); height: calc(100vh - 98px);
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden;
padding: 10px 10px 0 10px;
background: #f6f7f9;
} }
.fixed-header + .app-main { .fixed-header + .app-main {

@ -0,0 +1,147 @@
<template>
<div class="menu-item-container">
<div
class="menu-item"
v-for="(router, routerIndex) in topMenus"
:key="routerIndex"
>
<el-dropdown
v-if="router.children && router.children.length > 0"
class="menu-item-box"
trigger="hover"
>
<div
class="item-title"
:class="handleActive(router.path) ? 'dropdownActive' : ''"
>
{{ router.meta.title }}
</div>
<el-dropdown-menu slot="dropdown">
<router-link
active-class="dropdownActive"
:to="router.path + '/' + item.path"
v-for="(item, itemIndex) in router.children"
:key="itemIndex"
>
<el-dropdown-item>
<el-dropdown
v-if="item.children && item.children.length > 0"
class="menu-item-box"
trigger="hover"
>
<div
class="item-title"
:class="
handleActive(router.path + '/' + item.path)
? 'dropdownActive'
: ''
"
>
<svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta.icon"
/>
{{ item.meta.title }}
</div>
<router-link
active-class="dropdownActive"
v-for="(item2, itemIndex2) in item.children"
:key="itemIndex2"
>
<el-dropdown-item>
<svg-icon
v-if="
item2.meta && item2.meta.icon && item2.meta.icon !== '#'
"
:icon-class="item2.meta.icon"
/>
<span style="margin-left: 6px">{{ item2.meta.title }}</span>
</el-dropdown-item>
</router-link>
</el-dropdown>
<div v-else>
<svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta.icon"
/>
<span style="margin-left: 6px">{{ item.meta.title }}</span>
</div>
</el-dropdown-item>
</router-link>
</el-dropdown-menu>
</el-dropdown>
<router-link
v-else
tag="div"
class="item-title"
active-class="dropdownActive"
:to="router.path"
>
<span>{{ router.meta.title }}</span>
</router-link>
</div>
</div>
</template>
<script>
export default {
data() {
return {};
},
methods: {
handleActive(path) {
return this.$route.path.includes(path);
},
},
computed: {
//
topMenus() {
let topMenus = [];
this.routers.map((menu) => {
if (menu.hidden !== true) {
//
if (menu.path === "/") {
topMenus.push(menu.children[0]);
} else {
topMenus.push(menu);
}
}
});
console.log(topMenus, "aa");
return topMenus;
},
//
routers() {
return this.$store.state.permission.topbarRouters;
},
},
};
</script>
<style lang="scss" scoped>
.menu-item-container {
display: flex;
align-items: center;
.item-title {
padding: 3px 10px;
font-size: 18px;
cursor: pointer;
border-radius: 24px;
margin: 0 5px;
color: #fff;
border: 1px solid transparent;
}
}
.dropdownActive {
border-color: #fff !important;
background: hsla(0, 0%, 100%, 0.15);
}
.dropdownActive li {
color: red;
}
</style>

@ -0,0 +1,125 @@
<template>
<div class="system-header">
<div
class="navigation-bar"
:style="{ background: variables.navBackground }"
>
<div class="left-menu">
<img class="system-logo" src="@/assets/images/logo.png" />
<div class="system-title">{{ systemTitle }}</div>
<div class="sysyem-menu">
<menu-item></menu-item>
</div>
</div>
<div class="right-menu">
<el-dropdown
class="avatar-container right-menu-item hover-effect"
trigger="click"
>
<div class="avatar-wrapper">
<img :src="avatar" class="user-avatar" />
<div class="user-name">{{ name }}</div>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown">
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item divided @click.native="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import variables from "@/assets/styles/variables.scss";
import MenuItem from "./components/MenuItem.vue";
export default {
data() {
return {
systemTitle: process.env.VUE_APP_TITLE,
};
},
components: {
MenuItem,
},
computed: {
...mapGetters(["avatar", "name", "sidebarRouters"]),
variables() {
return variables;
},
},
methods: {
async logout() {
this.$confirm("确定注销并退出系统吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.$store.dispatch("LogOut").then(() => {
location.href = "/index";
});
})
.catch(() => {});
},
},
};
</script>
<style lang="scss" scoped>
.system-header {
height: 98px;
width: 100%;
color: #fff;
.navigation-bar {
height: 64px;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
overflow: hidden;
.left-menu {
display: flex;
align-items: center;
.system-logo {
height: 48px;
margin-right: 10px;
}
.system-title {
margin-right: 20px;
cursor: pointer;
font-size: 22px;
}
.sysyem-menu {
}
}
.right-menu {
.avatar-wrapper {
display: flex;
align-items: center;
color: #fff;
cursor: pointer;
.user-avatar {
height: 40px;
width: 40px;
border-radius: 10px;
}
.user-name {
padding: 0 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
}
}
}
}
}
</style>

@ -1,12 +1,21 @@
<template> <template>
<div class="navbar"> <div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger
id="hamburger-container"
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/> :is-active="sidebar.opened"
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/> class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb
id="breadcrumb-container"
class="breadcrumb-container"
v-if="!topNav"
/>
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav" />
<div class="right-menu"> <div class="right-menu">
<template v-if="device!=='mobile'"> <template v-if="device !== 'mobile'">
<search id="header-search" class="right-menu-item" /> <search id="header-search" class="right-menu-item" />
<el-tooltip content="源码地址" effect="dark" placement="bottom"> <el-tooltip content="源码地址" effect="dark" placement="bottom">
@ -22,12 +31,14 @@
<el-tooltip content="布局大小" effect="dark" placement="bottom"> <el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" /> <size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip> </el-tooltip>
</template> </template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click"> <el-dropdown
class="avatar-container right-menu-item hover-effect"
trigger="click"
>
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<img :src="avatar" class="user-avatar"> <img :src="avatar" class="user-avatar" />
<i class="el-icon-caret-bottom" /> <i class="el-icon-caret-bottom" />
</div> </div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
@ -47,15 +58,15 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from "vuex";
import Breadcrumb from '@/components/Breadcrumb' import Breadcrumb from "@/components/Breadcrumb";
import TopNav from '@/components/TopNav' import TopNav from "@/components/TopNav";
import Hamburger from '@/components/Hamburger' import Hamburger from "@/components/Hamburger";
import Screenfull from '@/components/Screenfull' import Screenfull from "@/components/Screenfull";
import SizeSelect from '@/components/SizeSelect' import SizeSelect from "@/components/SizeSelect";
import Search from '@/components/HeaderSearch' import Search from "@/components/HeaderSearch";
import RuoYiGit from '@/components/RuoYi/Git' import RuoYiGit from "@/components/RuoYi/Git";
import RuoYiDoc from '@/components/RuoYi/Doc' import RuoYiDoc from "@/components/RuoYi/Doc";
export default { export default {
components: { components: {
@ -66,48 +77,46 @@ export default {
SizeSelect, SizeSelect,
Search, Search,
RuoYiGit, RuoYiGit,
RuoYiDoc RuoYiDoc,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters(["sidebar", "avatar", "device"]),
'sidebar',
'avatar',
'device'
]),
setting: { setting: {
get() { get() {
return this.$store.state.settings.showSettings return this.$store.state.settings.showSettings;
}, },
set(val) { set(val) {
this.$store.dispatch('settings/changeSetting', { this.$store.dispatch("settings/changeSetting", {
key: 'showSettings', key: "showSettings",
value: val value: val,
}) });
} },
}, },
topNav: { topNav: {
get() { get() {
return this.$store.state.settings.topNav return this.$store.state.settings.topNav;
} },
} },
}, },
methods: { methods: {
toggleSideBar() { toggleSideBar() {
this.$store.dispatch('app/toggleSideBar') this.$store.dispatch("app/toggleSideBar");
}, },
async logout() { async logout() {
this.$confirm('确定注销并退出系统吗?', '提示', { this.$confirm("确定注销并退出系统吗?", "提示", {
confirmButtonText: '确定', confirmButtonText: "确定",
cancelButtonText: '取消', cancelButtonText: "取消",
type: 'warning' type: "warning",
}).then(() => { })
this.$store.dispatch('LogOut').then(() => { .then(() => {
location.href = '/index'; this.$store.dispatch("LogOut").then(() => {
location.href = "/index";
});
}) })
}).catch(() => {}); .catch(() => {});
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -116,18 +125,18 @@ export default {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: #fff; background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08); box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container { .hamburger-container {
line-height: 46px; line-height: 46px;
height: 100%; height: 100%;
float: left; float: left;
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background 0.3s;
-webkit-tap-highlight-color:transparent; -webkit-tap-highlight-color: transparent;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, 0.025);
} }
} }
@ -164,10 +173,10 @@ export default {
&.hover-effect { &.hover-effect {
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background 0.3s;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, 0.025);
} }
} }
} }

@ -1,16 +1,40 @@
<template> <template>
<div v-if="!item.hidden"> <div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow"> <template
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)"> v-if="
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"> hasOneShowingChild(item.children, item) &&
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" /> (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.alwaysShow
"
>
<app-link
v-if="onlyOneChild.meta"
:to="resolvePath(onlyOneChild.path, onlyOneChild.query)"
>
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<item
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
:title="onlyOneChild.meta.title"
/>
</el-menu-item> </el-menu-item>
</app-link> </app-link>
</template> </template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> <el-submenu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
popper-append-to-body
>
<template slot="title"> <template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> <item
v-if="item.meta"
:icon="item.meta && item.meta.icon"
:title="item.meta.title"
/>
</template> </template>
<sidebar-item <sidebar-item
v-for="(child, index) in item.children" v-for="(child, index) in item.children"
@ -25,76 +49,76 @@
</template> </template>
<script> <script>
import path from 'path' import path from "path";
import { isExternal } from '@/utils/validate' import { isExternal } from "@/utils/validate";
import Item from './Item' import Item from "./Item";
import AppLink from './Link' import AppLink from "./Link";
import FixiOSBug from './FixiOSBug' import FixiOSBug from "./FixiOSBug";
export default { export default {
name: 'SidebarItem', name: "SidebarItem",
components: { Item, AppLink }, components: { Item, AppLink },
mixins: [FixiOSBug], mixins: [FixiOSBug],
props: { props: {
// route object // route object
item: { item: {
type: Object, type: Object,
required: true required: true,
}, },
isNest: { isNest: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
basePath: { basePath: {
type: String, type: String,
default: '' default: "",
} },
}, },
data() { data() {
this.onlyOneChild = null this.onlyOneChild = null;
return {} return {};
}, },
methods: { methods: {
hasOneShowingChild(children = [], parent) { hasOneShowingChild(children = [], parent) {
if (!children) { if (!children) {
children = []; children = [];
} }
const showingChildren = children.filter(item => { const showingChildren = children.filter((item) => {
if (item.hidden) { if (item.hidden) {
return false return false;
} else { } else {
// Temp set(will be used if only has one showing child) // Temp set(will be used if only has one showing child)
this.onlyOneChild = item this.onlyOneChild = item;
return true return true;
} }
}) });
// When there is only one child router, the child router is displayed by default // When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) { if (showingChildren.length === 1) {
return true return true;
} }
// Show parent if there are no child router to display // Show parent if there are no child router to display
if (showingChildren.length === 0) { if (showingChildren.length === 0) {
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true } this.onlyOneChild = { ...parent, path: "", noShowingChildren: true };
return true return true;
} }
return false return false;
}, },
resolvePath(routePath, routeQuery) { resolvePath(routePath, routeQuery) {
if (isExternal(routePath)) { if (isExternal(routePath)) {
return routePath return routePath;
} }
if (isExternal(this.basePath)) { if (isExternal(this.basePath)) {
return this.basePath return this.basePath;
} }
if (routeQuery) { if (routeQuery) {
let query = JSON.parse(routeQuery); let query = JSON.parse(routeQuery);
return { path: path.resolve(this.basePath, routePath), query: query } return { path: path.resolve(this.basePath, routePath), query: query };
} }
return path.resolve(this.basePath, routePath) return path.resolve(this.basePath, routePath);
} },
} },
} };
</script> </script>

@ -1,5 +1,6 @@
export { default as AppMain } from './AppMain' export { default as AppMain } from "./AppMain";
export { default as Navbar } from './Navbar' export { default as Navbar } from "./Navbar";
export { default as Settings } from './Settings' export { default as Settings } from "./Settings";
export { default as Sidebar } from './Sidebar/index.vue' export { default as Sidebar } from "./Sidebar/index.vue";
export { default as TagsView } from './TagsView/index.vue' export { default as TagsView } from "./TagsView/index.vue";
export { default as FixedHeader } from "./FixedHeader/index.vue";

@ -0,0 +1,111 @@
<template>
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
<sidebar v-if="!sidebar.hide" class="sidebar-container"/>
<div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar/>
<tags-view v-if="needTagsView"/>
</div>
<app-main/>
<right-panel>
<settings/>
</right-panel>
</div>
</div>
</template>
<script>
import RightPanel from '@/components/RightPanel'
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss'
export default {
name: 'Layout',
components: {
AppMain,
Navbar,
RightPanel,
Settings,
Sidebar,
TagsView
},
mixins: [ResizeMixin],
computed: {
...mapState({
theme: state => state.settings.theme,
sideTheme: state => state.settings.sideTheme,
sidebar: state => state.app.sidebar,
device: state => state.app.device,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader
}),
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
},
variables() {
return variables;
}
},
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/assets/styles/mixin.scss";
@import "~@/assets/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.sidebarHide .fixed-header {
width: 100%;
}
.mobile .fixed-header {
width: 100%;
}
</style>

@ -1,111 +1,83 @@
<template> <template>
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}"> <div class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/> <FixedHeader />
<sidebar v-if="!sidebar.hide" class="sidebar-container"/> <app-main />
<div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar/>
<tags-view v-if="needTagsView"/>
</div>
<app-main/>
<right-panel>
<settings/>
</right-panel>
</div>
</div> </div>
</template> </template>
<script> <script>
import RightPanel from '@/components/RightPanel' import { AppMain } from "./components";
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components' import { mapState } from "vuex";
import ResizeMixin from './mixin/ResizeHandler' import { FixedHeader } from "./components";
import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss'
export default { export default {
name: 'Layout', name: "Layout",
components: { components: {
AppMain, AppMain,
Navbar, FixedHeader,
RightPanel,
Settings,
Sidebar,
TagsView
}, },
mixins: [ResizeMixin],
computed: { computed: {
...mapState({ ...mapState({
theme: state => state.settings.theme, theme: (state) => state.settings.theme,
sideTheme: state => state.settings.sideTheme, sideTheme: (state) => state.settings.sideTheme,
sidebar: state => state.app.sidebar, sidebar: (state) => state.app.sidebar,
device: state => state.app.device, device: (state) => state.app.device,
needTagsView: state => state.settings.tagsView, needTagsView: (state) => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader fixedHeader: (state) => state.settings.fixedHeader,
}), }),
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
},
variables() {
return variables;
}
}, },
methods: { methods: {
handleClickOutside() { handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "~@/assets/styles/mixin.scss"; @import "~@/assets/styles/mixin.scss";
@import "~@/assets/styles/variables.scss"; @import "~@/assets/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg { .app-wrapper {
background: #000; @include clearfix;
opacity: 0.3; position: relative;
width: 100%; height: 100%;
top: 0; width: 100%;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header { &.mobile.openSidebar {
position: fixed; position: fixed;
top: 0; top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s;
} }
}
.hideSidebar .fixed-header { .drawer-bg {
width: calc(100% - 54px); background: #000;
} opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.sidebarHide .fixed-header { .fixed-header {
width: 100%; position: fixed;
} top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s;
}
.mobile .fixed-header { .hideSidebar .fixed-header {
width: 100%; width: calc(100% - 54px);
} }
.sidebarHide .fixed-header {
width: 100%;
}
.mobile .fixed-header {
width: 100%;
}
</style> </style>

@ -2,7 +2,7 @@ module.exports = {
/** /**
* 侧边栏主题 深色主题theme-dark浅色主题theme-light * 侧边栏主题 深色主题theme-dark浅色主题theme-light
*/ */
sideTheme: 'theme-dark', sideTheme: "theme-dark",
/** /**
* 是否系统布局配置 * 是否系统布局配置
@ -12,12 +12,12 @@ module.exports = {
/** /**
* 是否显示顶部导航 * 是否显示顶部导航
*/ */
topNav: false, topNav: true,
/** /**
* 是否显示 tagsView * 是否显示 tagsView
*/ */
tagsView: true, tagsView: false,
/** /**
* 是否固定头部 * 是否固定头部
@ -40,5 +40,5 @@ module.exports = {
* The default is only used in the production env * The default is only used in the production env
* If you want to also use it in dev, you can pass ['production', 'development'] * If you want to also use it in dev, you can pass ['production', 'development']
*/ */
errorLog: 'production' errorLog: "production",
} };

@ -41,7 +41,7 @@
<el-col :sm="24" :lg="12" style="padding-left: 20px"> <el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>若依后台管理框架</h2> <h2>若依后台管理框架</h2>
<p> <p>
一直想做一款后台管理系统看了很多优秀的开源项目但是发现没有合适自己的于是利用空闲休息时间开始自己写一套后台系统如此有了若依管理系统她可以用于所有的Web应用程序如网站管理后台网站会员中心CMSCRMOA等等当然您也可以对她进行深度定制以做出更强系统所有前端后台代码封装过后十分精简易上手出错概率低同时支持移动客户端访问系统会陆续更新一些实用功能 一直想做一款后台管理系统看了很多优秀的开源项目但是发现没有合适自己的于是利用空闲休息时间开始自己写一套后台系统如此有了金鸡湖现代服务业品牌管理系统她可以用于所有的Web应用程序如网站管理后台网站会员中心CMSCRMOA等等当然您也可以对她进行深度定制以做出更强系统所有前端后台代码封装过后十分精简易上手出错概率低同时支持移动客户端访问系统会陆续更新一些实用功能
</p> </p>
<p> <p>
<b>当前版本:</b> <span>v{{ version }}</span> <b>当前版本:</b> <span>v{{ version }}</span>
@ -118,12 +118,18 @@
> >
</p> </p>
<p> <p>
<i class="el-icon-user-solid"></i> QQ群<s> 满937441 </s> <s> 满887144332 </s> <i class="el-icon-user-solid"></i> QQ群<s> 满937441 </s>
<s> 满180251782 </s> <s> 满104180207 </s> <s> 满186866453 </s> <s> 满201396349 </s> <s> 满887144332 </s> <s> 满180251782 </s> <s> 满104180207 </s>
<s> 满101456076 </s> <s> 满101539465 </s> <s> 满264312783 </s> <s> 满167385320 </s> <s> 满186866453 </s> <s> 满201396349 </s> <s> 满101456076 </s>
<s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s> <s> 满101539465 </s> <s> 满264312783 </s> <s> 满167385320 </s>
<s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s> <s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s>
<s> 满161281055 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063" target="_blank">138988063</a> <s> 满108482800 </s> <s> 满101046199 </s> <s> 满136919097 </s>
<s> 满143961921 </s> <s> 满174951577 </s> <s> 满161281055 </s>
<a
href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063"
target="_blank"
>138988063</a
>
</p> </p>
<p> <p>
<i class="el-icon-chat-dot-round"></i> 微信<a <i class="el-icon-chat-dot-round"></i> 微信<a
@ -262,7 +268,9 @@
<li>修复table中更多按钮切换主题色未生效修复问题</li> <li>修复table中更多按钮切换主题色未生效修复问题</li>
<li>修复某些特性的环境生成代码变乱码TXT文件问题</li> <li>修复某些特性的环境生成代码变乱码TXT文件问题</li>
<li>修复代码生成图片/文件/单选时选择必填无法校验问题</li> <li>修复代码生成图片/文件/单选时选择必填无法校验问题</li>
<li>修复某些特性的情况用户编辑对话框中角色和部门无法修改问题</li> <li>
修复某些特性的情况用户编辑对话框中角色和部门无法修改问题
</li>
<li>其他细节优化</li> <li>其他细节优化</li>
</ol> </ol>
</el-collapse-item> </el-collapse-item>
@ -323,7 +331,9 @@
<li>新增获取不带后缀文件名称方法</li> <li>新增获取不带后缀文件名称方法</li>
<li>新增获取配置文件中的属性值方法</li> <li>新增获取配置文件中的属性值方法</li>
<li>新增内容编码/解码方便插件集成使用</li> <li>新增内容编码/解码方便插件集成使用</li>
<li>字典类型必须以字母开头且只能为小写字母数字下滑线</li> <li>
字典类型必须以字母开头且只能为小写字母数字下滑线
</li>
<li>优化设置分页参数默认值</li> <li>优化设置分页参数默认值</li>
<li>优化对空字符串参数处理的过滤</li> <li>优化对空字符串参数处理的过滤</li>
<li>优化显示顺序orderNum类型为整型</li> <li>优化显示顺序orderNum类型为整型</li>
@ -527,8 +537,12 @@
<li>BLOB下载时清除URL对象引用</li> <li>BLOB下载时清除URL对象引用</li>
<li>代码生成导入表按创建时间排序</li> <li>代码生成导入表按创建时间排序</li>
<li>修复代码生成页面数据编辑保存之后总是跳转第一页的问题</li> <li>修复代码生成页面数据编辑保存之后总是跳转第一页的问题</li>
<li>修复带safari浏览器无法格式化utc日期格式yyyy-MM-dd'T'HH:mm:ss.SSS问题</li> <li>
<li>多图上传组件移除多余的api地址&验证失败导致图片删除问题&无法删除相应图片修复</li> 修复带safari浏览器无法格式化utc日期格式yyyy-MM-dd'T'HH:mm:ss.SSS问题
</li>
<li>
多图上传组件移除多余的api地址&验证失败导致图片删除问题&无法删除相应图片修复
</li>
<li>其他细节优化</li> <li>其他细节优化</li>
</ol> </ol>
</el-collapse-item> </el-collapse-item>
@ -573,7 +587,9 @@
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="v3.5.0 - 2021-05-25"> <el-collapse-item title="v3.5.0 - 2021-05-25">
<ol> <ol>
<li>新增菜单导航显示风格TopNavfalse为左侧导航菜单true为顶部导航菜单</li> <li>
新增菜单导航显示风格TopNavfalse为左侧导航菜单true为顶部导航菜单
</li>
<li>布局设置支持保存&重置配置</li> <li>布局设置支持保存&重置配置</li>
<li>修复树表数据显示不全&加载慢问题</li> <li>修复树表数据显示不全&加载慢问题</li>
<li>新增IE浏览器版本过低提示页面</li> <li>新增IE浏览器版本过低提示页面</li>
@ -592,7 +608,9 @@
<li>升级druid到最新版本v1.2.6</li> <li>升级druid到最新版本v1.2.6</li>
<li>升级mybatis到最新版3.5.6 阻止远程代码执行漏洞</li> <li>升级mybatis到最新版3.5.6 阻止远程代码执行漏洞</li>
<li>升级oshi到最新版本v5.6.0</li> <li>升级oshi到最新版本v5.6.0</li>
<li>velocity剔除commons-collections版本防止3.2.1版本的反序列化漏洞</li> <li>
velocity剔除commons-collections版本防止3.2.1版本的反序列化漏洞
</li>
<li>数据监控页默认账户密码防止越权访问</li> <li>数据监控页默认账户密码防止越权访问</li>
<li>修复firefox下表单构建拖拽会新打卡一个选项卡</li> <li>修复firefox下表单构建拖拽会新打卡一个选项卡</li>
<li>修正后端导入表权限标识</li> <li>修正后端导入表权限标识</li>
@ -969,11 +987,7 @@
<span>捐赠支持</span> <span>捐赠支持</span>
</div> </div>
<div class="body"> <div class="body">
<img <img src="@/assets/images/pay.png" alt="donate" width="100%" />
src="@/assets/images/pay.png"
alt="donate"
width="100%"
/>
<span style="display: inline-block; height: 30px; line-height: 30px" <span style="display: inline-block; height: 30px; line-height: 30px"
>你可以请作者喝杯咖啡表示鼓励</span >你可以请作者喝杯咖啡表示鼓励</span
> >
@ -990,14 +1004,14 @@ export default {
data() { data() {
return { return {
// //
version: "3.8.7" version: "3.8.7",
}; };
}, },
methods: { methods: {
goTarget(href) { goTarget(href) {
window.open(href, "_blank"); window.open(href, "_blank");
} },
} },
}; };
</script> </script>
@ -1064,4 +1078,3 @@ export default {
} }
} }
</style> </style>

@ -1,6 +1,11 @@
<template> <template>
<div class="login"> <div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form"> <el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">若依后台管理系统</h3> <h3 class="title">若依后台管理系统</h3>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
@ -9,7 +14,11 @@
auto-complete="off" auto-complete="off"
placeholder="账号" placeholder="账号"
> >
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> <svg-icon
slot="prefix"
icon-class="user"
class="el-input__icon input-icon"
/>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
@ -20,7 +29,11 @@
placeholder="密码" placeholder="密码"
@keyup.enter.native="handleLogin" @keyup.enter.native="handleLogin"
> >
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> <svg-icon
slot="prefix"
icon-class="password"
class="el-input__icon input-icon"
/>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" v-if="captchaEnabled"> <el-form-item prop="code" v-if="captchaEnabled">
@ -31,40 +44,50 @@
style="width: 63%" style="width: 63%"
@keyup.enter.native="handleLogin" @keyup.enter.native="handleLogin"
> >
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> <svg-icon
slot="prefix"
icon-class="validCode"
class="el-input__icon input-icon"
/>
</el-input> </el-input>
<div class="login-code"> <div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/> <img :src="codeUrl" @click="getCode" class="login-code-img" />
</div> </div>
</el-form-item> </el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;"></el-checkbox> <el-checkbox
<el-form-item style="width:100%;"> v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>记住密码</el-checkbox
>
<el-form-item style="width: 100%">
<el-button <el-button
:loading="loading" :loading="loading"
size="medium" size="medium"
type="primary" type="primary"
style="width:100%;" style="width: 100%"
@click.native.prevent="handleLogin" @click.native.prevent="handleLogin"
> >
<span v-if="!loading"> </span> <span v-if="!loading"> </span>
<span v-else> ...</span> <span v-else> ...</span>
</el-button> </el-button>
<div style="float: right;" v-if="register"> <div style="float: right" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link> <router-link class="link-type" :to="'/register'"
>立即注册</router-link
>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 底部 --> <!-- 底部 -->
<div class="el-login-footer"> <!-- <div class="el-login-footer">
<span>Copyright © 2018-2024 ruoyi.vip All Rights Reserved.</span> <span>Copyright © 2018-2024 ruoyi.vip All Rights Reserved.</span>
</div> </div> -->
</div> </div>
</template> </template>
<script> <script>
import { getCodeImg } from "@/api/login"; import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt' import { encrypt, decrypt } from "@/utils/jsencrypt";
export default { export default {
name: "Login", name: "Login",
@ -76,32 +99,32 @@ export default {
password: "admin123", password: "admin123",
rememberMe: false, rememberMe: false,
code: "", code: "",
uuid: "" uuid: "",
}, },
loginRules: { loginRules: {
username: [ username: [
{ required: true, trigger: "blur", message: "请输入您的账号" } { required: true, trigger: "blur", message: "请输入您的账号" },
], ],
password: [ password: [
{ required: true, trigger: "blur", message: "请输入您的密码" } { required: true, trigger: "blur", message: "请输入您的密码" },
], ],
code: [{ required: true, trigger: "change", message: "请输入验证码" }] code: [{ required: true, trigger: "change", message: "请输入验证码" }],
}, },
loading: false, loading: false,
// //
captchaEnabled: true, captchaEnabled: true,
// //
register: false, register: false,
redirect: undefined redirect: undefined,
}; };
}, },
watch: { watch: {
$route: { $route: {
handler: function(route) { handler: function (route) {
this.redirect = route.query && route.query.redirect; this.redirect = route.query && route.query.redirect;
}, },
immediate: true immediate: true,
} },
}, },
created() { created() {
this.getCode(); this.getCode();
@ -109,8 +132,9 @@ export default {
}, },
methods: { methods: {
getCode() { getCode() {
getCodeImg().then(res => { getCodeImg().then((res) => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled; this.captchaEnabled =
res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (this.captchaEnabled) { if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img; this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid; this.loginForm.uuid = res.uuid;
@ -120,49 +144,55 @@ export default {
getCookie() { getCookie() {
const username = Cookies.get("username"); const username = Cookies.get("username");
const password = Cookies.get("password"); const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe') const rememberMe = Cookies.get("rememberMe");
this.loginForm = { this.loginForm = {
username: username === undefined ? this.loginForm.username : username, username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password), password:
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe) password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
}; };
}, },
handleLogin() { handleLogin() {
this.$refs.loginForm.validate(valid => { this.$refs.loginForm.validate((valid) => {
if (valid) { if (valid) {
this.loading = true; this.loading = true;
if (this.loginForm.rememberMe) { if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 }); Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 }); Cookies.set("password", encrypt(this.loginForm.password), {
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 }); expires: 30,
});
Cookies.set("rememberMe", this.loginForm.rememberMe, {
expires: 30,
});
} else { } else {
Cookies.remove("username"); Cookies.remove("username");
Cookies.remove("password"); Cookies.remove("password");
Cookies.remove('rememberMe'); Cookies.remove("rememberMe");
} }
this.$store.dispatch("Login", this.loginForm).then(() => { this.$store
this.$router.push({ path: this.redirect || "/" }).catch(()=>{}); .dispatch("Login", this.loginForm)
}).catch(() => { .then(() => {
this.loading = false; this.$router.push({ path: this.redirect || "/" }).catch(() => {});
if (this.captchaEnabled) { })
this.getCode(); .catch(() => {
} this.loading = false;
}); if (this.captchaEnabled) {
this.getCode();
}
});
} }
}); });
} },
} },
}; };
</script> </script>
<style rel="stylesheet/scss" lang="scss"> <style rel="stylesheet/scss" lang="scss">
.login { .login {
display: flex; position: relative;
justify-content: center;
align-items: center;
height: 100%; height: 100%;
background-image: url("../assets/images/login-background.jpg"); background: url("../assets/images/login-background.jpg") no-repeat;
background-size: cover; background-size: 100% 100%;
} }
.title { .title {
margin: 0px auto 30px auto; margin: 0px auto 30px auto;
@ -171,10 +201,15 @@ export default {
} }
.login-form { .login-form {
position: absolute;
top: 45%;
left: 68%;
transform: translate(-50%, -50%);
border-radius: 6px; border-radius: 6px;
background: #ffffff; background: #ffffff;
width: 400px; width: 400px;
padding: 25px 25px 5px 25px; padding: 25px 25px 5px 25px;
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
.el-input { .el-input {
height: 38px; height: 38px;
input { input {

@ -1,15 +1,15 @@
'use strict' "use strict";
const path = require('path') const path = require("path");
function resolve(dir) { function resolve(dir) {
return path.join(__dirname, dir) return path.join(__dirname, dir);
} }
const CompressionPlugin = require('compression-webpack-plugin') const CompressionPlugin = require("compression-webpack-plugin");
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题 const name = process.env.VUE_APP_TITLE || "金鸡湖现代服务业品牌管理系统"; // 网页标题
const port = process.env.port || process.env.npm_config_port || 80 // 端口 const port = process.env.port || process.env.npm_config_port || 80; // 端口
// vue.config.js 配置说明 // vue.config.js 配置说明
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions //官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
@ -20,16 +20,16 @@ module.exports = {
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === "production" ? "/" : "/", publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
// 在npm run build 或 yarn build 时 生成文件的目录名称要和baseUrl的生产环境路径一致默认dist // 在npm run build 或 yarn build 时 生成文件的目录名称要和baseUrl的生产环境路径一致默认dist
outputDir: 'dist', outputDir: "dist",
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: 'static', assetsDir: "static",
// 是否开启eslint保存检测有效值ture | false | 'error' // 是否开启eslint保存检测有效值ture | false | 'error'
lintOnSave: process.env.NODE_ENV === 'development', lintOnSave: process.env.NODE_ENV === "development",
// 如果你不需要生产环境的 source map可以将其设置为 false 以加速生产环境构建。 // 如果你不需要生产环境的 source map可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false, productionSourceMap: false,
// webpack-dev-server 相关配置 // webpack-dev-server 相关配置
devServer: { devServer: {
host: '0.0.0.0', host: "0.0.0.0",
port: port, port: port,
open: true, open: true,
proxy: { proxy: {
@ -38,92 +38,91 @@ module.exports = {
target: `http://localhost:8080`, target: `http://localhost:8080`,
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '' ["^" + process.env.VUE_APP_BASE_API]: "",
} },
} },
}, },
disableHostCheck: true disableHostCheck: true,
}, },
css: { css: {
loaderOptions: { loaderOptions: {
sass: { sass: {
sassOptions: { outputStyle: "expanded" } sassOptions: { outputStyle: "expanded" },
} },
} },
}, },
configureWebpack: { configureWebpack: {
name: name, name: name,
resolve: { resolve: {
alias: { alias: {
'@': resolve('src') "@": resolve("src"),
} },
}, },
plugins: [ plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionPlugin({ new CompressionPlugin({
cache: false, // 不启用文件缓存 cache: false, // 不启用文件缓存
test: /\.(js|css|html)?$/i, // 压缩文件格式 test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: '[path].gz[query]', // 压缩后的文件名 filename: "[path].gz[query]", // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩 algorithm: "gzip", // 使用gzip压缩
minRatio: 0.8 // 压缩率小于1才会压缩 minRatio: 0.8, // 压缩率小于1才会压缩
}) }),
], ],
}, },
chainWebpack(config) { chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test config.plugins.delete("preload"); // TODO: need test
config.plugins.delete('prefetch') // TODO: need test config.plugins.delete("prefetch"); // TODO: need test
// set svg-sprite-loader // set svg-sprite-loader
config.module.rule("svg").exclude.add(resolve("src/assets/icons")).end();
config.module config.module
.rule('svg') .rule("icons")
.exclude.add(resolve('src/assets/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/) .test(/\.svg$/)
.include.add(resolve('src/assets/icons')) .include.add(resolve("src/assets/icons"))
.end() .end()
.use('svg-sprite-loader') .use("svg-sprite-loader")
.loader('svg-sprite-loader') .loader("svg-sprite-loader")
.options({ .options({
symbolId: 'icon-[name]' symbolId: "icon-[name]",
}) })
.end() .end();
config.when(process.env.NODE_ENV !== 'development', config => { config.when(process.env.NODE_ENV !== "development", (config) => {
config config
.plugin('ScriptExtHtmlWebpackPlugin') .plugin("ScriptExtHtmlWebpackPlugin")
.after('html') .after("html")
.use('script-ext-html-webpack-plugin', [{ .use("script-ext-html-webpack-plugin", [
{
// `runtime` must same as runtimeChunk name. default is `runtime` // `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/ inline: /runtime\..*\.js$/,
}]) },
.end() ])
.end();
config.optimization.splitChunks({ config.optimization.splitChunks({
chunks: 'all', chunks: "all",
cacheGroups: { cacheGroups: {
libs: { libs: {
name: 'chunk-libs', name: "chunk-libs",
test: /[\\/]node_modules[\\/]/, test: /[\\/]node_modules[\\/]/,
priority: 10, priority: 10,
chunks: 'initial' // only package third parties that are initially dependent chunks: "initial", // only package third parties that are initially dependent
}, },
elementUI: { elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package name: "chunk-elementUI", // split elementUI into a single package
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
}, },
commons: { commons: {
name: 'chunk-commons', name: "chunk-commons",
test: resolve('src/components'), // can customize your rules test: resolve("src/components"), // can customize your rules
minChunks: 3, // minimum common number minChunks: 3, // minimum common number
priority: 5, priority: 5,
reuseExistingChunk: true reuseExistingChunk: true,
} },
} },
}) });
config.optimization.runtimeChunk('single') config.optimization.runtimeChunk("single");
}) });
} },
} };

Loading…
Cancel
Save