提交代码
This commit is contained in:
68
src/layout/components/AppMain.vue
Normal file
68
src/layout/components/AppMain.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="app-main">
|
||||
<!---带动画 ,并且具备组件缓存-->
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<component :is="Component" :key="route.path" />
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useStore } from "vuex";
|
||||
import { watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
const store = useStore();
|
||||
const whiteList = ["editPassword", '/home',"/404", "401"];
|
||||
|
||||
/*生成title */
|
||||
const getTitle = (route) => {
|
||||
let title = "";
|
||||
if (!route.meta) {
|
||||
const pathArr = route.path.split("/");
|
||||
title = pathArr[pathArr.length - 1];
|
||||
} else {
|
||||
title = route.meta.title;
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
const isTags = (path) => {
|
||||
return !whiteList.includes(path);
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
watch(route, (to, from) => {
|
||||
const { fullPath, meta, name, params, path, query } = to;
|
||||
if (!isTags(to.path)) return;
|
||||
//并不是所有页面都需要保存;
|
||||
store.commit(
|
||||
"app/addTagsViewList",
|
||||
{
|
||||
fullPath,
|
||||
meta,
|
||||
name,
|
||||
params,
|
||||
path,
|
||||
query,
|
||||
title: getTitle(to)
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
if (store.getters.tagsViewList.length > 12) {
|
||||
store.commit("app/removeTagsView", {
|
||||
type: "index",
|
||||
index: 0
|
||||
});
|
||||
// return false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/transition.scss";
|
||||
@import "@/assets/css/layout.scss";
|
||||
@import "@/assets/css/element-plus.scss";
|
||||
</style>
|
42
src/layout/components/Hamburger/index.vue
Normal file
42
src/layout/components/Hamburger/index.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="hamburger-container" @click="toggleClick">
|
||||
<SvgIcon id="guide-hamburger" class="hamburger" :icon="icon" color="black"></SvgIcon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import emitter from "@/utils/eventBus.js";
|
||||
import { computed,getCurrentInstance,onMounted,ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
const { proxy } = getCurrentInstance()
|
||||
const keyIndex = ref(1);
|
||||
const store = useStore();
|
||||
onMounted(()=>{
|
||||
emitter.on('closeMeun',()=>{
|
||||
toggleClick()
|
||||
})
|
||||
})
|
||||
const toggleClick = () => {
|
||||
store.commit("app/triggerSidebarOpened");
|
||||
proxy.mittBus.emit('mittFn',keyIndex.value++)
|
||||
};
|
||||
|
||||
const icon = computed(() =>
|
||||
store.getters.sidebarOpened ? "hamburger-opened" : "hamburger-closed"
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hamburger-container {
|
||||
padding: 0 8px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
66
src/layout/components/Header.vue
Normal file
66
src/layout/components/Header.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<header>
|
||||
<div class="logo flex align-center" @click="goToHome"><img width="45" class="mr10" src="@/assets/images/jinghui.png" alt="">林芝公安治安防控管理平台</div>
|
||||
<div class="right">
|
||||
<div><img src="@/assets/images/peo.png" /></div>
|
||||
<div class="detail">
|
||||
<div class="hd">
|
||||
<div class="name">姓名:{{ username }}</div>
|
||||
<div class="work">单位:{{ deptName }}</div>
|
||||
</div>
|
||||
<el-dropdown :hide-on-click="false">
|
||||
<span class="el-dropdown-link">
|
||||
<el-icon :size="20" color="#fff"> <CaretBottom /> </el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="loginOut" @click="logout">
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div @click="goToHome">
|
||||
<img src="@/assets/images/meun.png" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getItem } from "@/utils/storage";
|
||||
import NavBar from "./NavBar.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
const store = useStore();
|
||||
const username = ref("测试");
|
||||
const deptName = ref('');
|
||||
|
||||
const router = useRouter();
|
||||
onMounted(() => {
|
||||
deptName.value = localStorage.getItem("deptId") ? JSON.parse(localStorage.getItem("deptId"))[0].deptName : ''
|
||||
})
|
||||
|
||||
// 路由跳转
|
||||
function goToHome() {
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
const active = ref("");
|
||||
onMounted(() => {
|
||||
//登陆用户信息
|
||||
username.value = localStorage.getItem("USERNAME");
|
||||
let dept = getItem("deptId");
|
||||
active.value = "LZ";
|
||||
});
|
||||
const logout = () => {
|
||||
store.dispatch("user/logout");
|
||||
store.commit("app/clearTag", null, { immediate: true });
|
||||
store.commit("permission/deleteRouter", { immediate: true });
|
||||
store.commit("user/deleteKeepLiiveRoute", "home");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/css/layout.scss";
|
||||
</style>
|
101
src/layout/components/NavBar.vue
Normal file
101
src/layout/components/NavBar.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<el-dropdown class="avatar-container" trigger="click">
|
||||
<div class="avatar-wrapper">
|
||||
<el-avatar
|
||||
shape="circle"
|
||||
:size="28"
|
||||
:src="require('@/assets/images/ly-person-icon.png')"
|
||||
></el-avatar>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="user-dropdown">
|
||||
<el-dropdown-item divided @click="logout()">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<UpdatePwdDialog v-model="UpdatePwdVisible"></UpdatePwdDialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter, useRoute,onBeforeRouteLeave } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import UpdatePwdDialog from "./UpdatePwdDialog.vue";
|
||||
const UpdatePwdVisible = ref(false);
|
||||
const updatePwd = () => {
|
||||
UpdatePwdVisible.value = true;
|
||||
};
|
||||
const store = useStore();
|
||||
|
||||
const logout = () => {
|
||||
store.dispatch("user/logout");
|
||||
store.commit("app/clearTag", null, {
|
||||
immediate: true
|
||||
});
|
||||
store.commit("permission/deleteRouter", {
|
||||
immediate: true
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar {
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
// hover 动画
|
||||
transition: background 0.5s;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
float: right;
|
||||
padding-right: 16px;
|
||||
|
||||
::v-deep .right-menu-item {
|
||||
display: inline-block;
|
||||
padding: 0 18px 0 0;
|
||||
font-size: 24px;
|
||||
color: #5a5e66;
|
||||
vertical-align: text-bottom;
|
||||
|
||||
&.hover-effect {
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .avatar-container {
|
||||
cursor: pointer;
|
||||
.avatar-wrapper {
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
.el-avatar {
|
||||
--el-avatar-background-color: none;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
33
src/layout/components/SideBar/MenuItem.vue
Normal file
33
src/layout/components/SideBar/MenuItem.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--element icon-->
|
||||
<i v-if="icon.includes('el-icon')" class="sub-el-icon" :class="icon"></i>
|
||||
<!---非elemtnt icon-->
|
||||
<SvgIcon v-else :icon="icon"></SvgIcon>
|
||||
<span style="padding-left: 12px">{{ title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
import SvgIcon from "../../../components/SvgIcon/index.vue";
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-menu--collapse span {
|
||||
display: block;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
28
src/layout/components/SideBar/SideBarItem.vue
Normal file
28
src/layout/components/SideBar/SideBarItem.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<el-sub-menu v-if="route.children.length > 0" :index="route.path">
|
||||
<template #title>
|
||||
<MenuItem :title="route.meta.title" :icon="route.meta.icon"></MenuItem>
|
||||
</template>
|
||||
<!---循环渲染--->
|
||||
<SideBarItem v-for="item in route.children" :key="item.path" :route="item"></SideBarItem>
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item v-else :index="route.path">
|
||||
<MenuItem :title="route.meta.title" :icon="route.meta.icon"></MenuItem>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MenuItem from './MenuItem.vue'
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
defineProps({
|
||||
route: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
75
src/layout/components/SideBar/SideBarMenu.vue
Normal file
75
src/layout/components/SideBar/SideBarMenu.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<el-menu
|
||||
class="el-menu-vertical-demo"
|
||||
:collapse="!$store.getters.sidebarOpened"
|
||||
:default-active="activeMenu"
|
||||
:unique-opened="true"
|
||||
background-color="rgba(0, 0, 0, 0)"
|
||||
:text-color="$store.getters.cssVar.menuText"
|
||||
:active-text-color="$store.getters.cssVar.menuActiveText"
|
||||
router
|
||||
>
|
||||
<SideBarItem
|
||||
v-for="item in routes"
|
||||
:key="item.path"
|
||||
:route="item"
|
||||
></SideBarItem>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { useStore } from "vuex";
|
||||
import { filterRoutes, generateMenus } from "@/utils/route";
|
||||
import SideBarItem from "./SideBarItem.vue";
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const routes = computed(() => {
|
||||
const fRoutes = filterRoutes(router.getRoutes());
|
||||
return generateMenus(fRoutes);
|
||||
});
|
||||
if (!store.getters.token) {
|
||||
router.push("/login");
|
||||
}
|
||||
if (router.getRoutes().length <= 7 && store.state.permission.routeReady <= 1) {
|
||||
store.commit("user/setIsReady", {});
|
||||
setTimeout(() => {
|
||||
router.go(0);
|
||||
}, 200);
|
||||
}
|
||||
//默认激活项
|
||||
const route = useRoute();
|
||||
const activeMenu = computed(() => {
|
||||
const { path } = route;
|
||||
return path;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .el-menu-item {
|
||||
height: 48px;
|
||||
}
|
||||
::v-deep .el-sub-menu__title {
|
||||
height: 48px;
|
||||
color: rgb(255, 255, 255);
|
||||
background-color: rgb(20, 46, 78);
|
||||
}
|
||||
::v-deep .el-menu-item.is-active {
|
||||
background-image: linear-gradient(to right,#2356d4 0% ,#8efbde 100%);
|
||||
margin: 0 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::v-deep .el-sub-menu .el-menu-item {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
.el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 281px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
</style>
|
54
src/layout/components/SideBar/index.vue
Normal file
54
src/layout/components/SideBar/index.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<el-scrollbar>
|
||||
<div class="meunAside noScollLine">
|
||||
<div style="height:100px"></div>
|
||||
<div style="height: calc(100% - 100px);"><SideBarMenu></SideBarMenu></div>
|
||||
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useStore } from "vuex";
|
||||
|
||||
import SideBarMenu from "./SideBarMenu.vue";
|
||||
const store = useStore();
|
||||
const logoHeight = 44;
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.meunAside {
|
||||
height: calc(100vh - 88px);
|
||||
background: url('~@/assets/images/aside.png') no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
padding: 10px 0 0 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
::v-deep .el-menu {
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
::v-deep .el-menu::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
height: v-bind(logoHeight) + "px";
|
||||
padding: 10px 0 22px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.logo-title {
|
||||
margin-left: 10px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
302
src/layout/components/TagsView.vue
Normal file
302
src/layout/components/TagsView.vue
Normal file
@ -0,0 +1,302 @@
|
||||
|
||||
<template>
|
||||
<div class="tags-view-container">
|
||||
<Hamburger class="hamburger-container"></Hamburger>
|
||||
<router-link
|
||||
@contextmenu.prevent="onContextmenu(tag, $event)"
|
||||
class="tags-view-item"
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
v-for="(tag, index) in $store.getters.tagsViewList"
|
||||
:key="tag.fullPath"
|
||||
:to="{ path: tag.fullPath }"
|
||||
>
|
||||
<span>{{ tag.title || "个人中心" }}</span>
|
||||
<el-icon
|
||||
class="el-icon-close"
|
||||
@click.prevent.stop="onCloseClick(index, isActive(tag))"
|
||||
>
|
||||
<close />
|
||||
</el-icon>
|
||||
</router-link>
|
||||
<!-- <span v-if="store.getters.tagsViewList.length > 11">...</span> -->
|
||||
<div class="icons">
|
||||
<el-dropdown
|
||||
@visible-change="checkActive"
|
||||
@command="dropRoute"
|
||||
trigger="click"
|
||||
>
|
||||
<span class="el-dropdown-link">
|
||||
<el-icon :class="active ? '_icon icon' : 'icon'" color="#24b6dd"
|
||||
><ArrowUpBold
|
||||
/></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="cxjz">重新加载</el-dropdown-item>
|
||||
<el-dropdown-item command="close">关闭其他标签</el-dropdown-item>
|
||||
<el-dropdown-item command="closeAll">关闭全部标签</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<Contextmenu
|
||||
ref="contextmenuRef"
|
||||
:items="contextmenuItems"
|
||||
@contextmenuItemClick="onContextmenuItem"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Contextmenu from "@/components/contextmenu/index.vue";
|
||||
import Hamburger from "./Hamburger/index";
|
||||
import { reactive, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
const route = useRoute();
|
||||
const active = ref(false);
|
||||
|
||||
/**
|
||||
* 是否被选中
|
||||
*/
|
||||
const isActive = (tag) => {
|
||||
return tag.path === route.path;
|
||||
};
|
||||
const contextmenuRef = ref(null);
|
||||
/**
|
||||
* 关闭 tag 的点击事件
|
||||
*/
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const onCloseClick = (index, current) => {
|
||||
store.commit("app/removeTagsView", {
|
||||
type: "index",
|
||||
index: index
|
||||
});
|
||||
if (current === true) {
|
||||
if (store.getters.tagsViewList.length === 0) {
|
||||
window.location.href = '/'
|
||||
// router.push("/");
|
||||
} else if (index > 0) {
|
||||
router.push(store.getters.tagsViewList[index - 1].fullPath);
|
||||
} else {
|
||||
router.push(store.getters.tagsViewList[0].fullPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
function checkActive(e) {
|
||||
active.value = e;
|
||||
}
|
||||
const onContextmenuItem = async (item) => {
|
||||
const { name, menu } = item;
|
||||
switch (name) {
|
||||
case "refresh":
|
||||
router.go(0);
|
||||
break;
|
||||
case "close":
|
||||
const index = store.getters.tagsViewList.indexOf(menu);
|
||||
if (isActive(menu)) {
|
||||
if (store.getters.tagsViewList.length === 1) {
|
||||
// router.push("/");
|
||||
window.location.href = '/'
|
||||
} else if (index > 0) {
|
||||
router.push(store.getters.tagsViewList[index - 1].fullPath);
|
||||
} else if (index == 0) {
|
||||
router.push(store.getters.tagsViewList[1].fullPath || "/");
|
||||
}
|
||||
}
|
||||
store.commit("app/removeTagsView", {
|
||||
type: "data",
|
||||
data: menu
|
||||
});
|
||||
break;
|
||||
case "closeOther":
|
||||
store.commit("app/clearTag", menu);
|
||||
router.push(menu.fullPath);
|
||||
break;
|
||||
case "closeAll":
|
||||
store.commit("app/clearTag", null);
|
||||
// router.push("/");
|
||||
window.location.href = '/'
|
||||
break;
|
||||
// case "fullScreen":
|
||||
|
||||
// break;
|
||||
}
|
||||
};
|
||||
/*生成title */
|
||||
const getTitle = (route) => {
|
||||
let title = "";
|
||||
if (!route.meta) {
|
||||
const pathArr = route.path.split("/");
|
||||
title = pathArr[pathArr.length - 1];
|
||||
} else {
|
||||
title = route.meta.title;
|
||||
}
|
||||
return title;
|
||||
};
|
||||
function dropRoute(e) {
|
||||
switch (e) {
|
||||
case "cxjz":
|
||||
router.go(0);
|
||||
break;
|
||||
case "closeAll":
|
||||
store.commit("app/clearTag", null);
|
||||
// router.push("/");
|
||||
window.location.href = '/'
|
||||
break;
|
||||
case "close":
|
||||
const { fullPath, meta, name, params, path, query } = route;
|
||||
let menu = {
|
||||
fullPath,
|
||||
meta,
|
||||
name,
|
||||
params,
|
||||
path,
|
||||
query,
|
||||
title: getTitle(route)
|
||||
};
|
||||
store.commit("app/clearTag", menu);
|
||||
router.push(menu.fullPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const contextmenuItems = reactive([
|
||||
{ name: "refresh", label: "重新加载", icon: "fa fa-refresh" },
|
||||
{ name: "close", label: "关闭标签", icon: "fa fa-times" },
|
||||
{ name: "closeOther", label: "关闭其他标签", icon: "fa fa-minus" },
|
||||
{ name: "closeAll", label: "关闭全部标签", icon: "fa fa-stop" }
|
||||
]);
|
||||
const onContextmenu = (menu, el) => {
|
||||
// 禁用刷新
|
||||
contextmenuItems[0].disabled = route.path !== menu.path;
|
||||
// 禁用关闭其他和关闭全部
|
||||
contextmenuItems[3].disabled = contextmenuItems[2].disabled = store.getters.tagsViewList.length == 1 ? true : false;
|
||||
const { clientX, clientY } = el;
|
||||
contextmenuRef.value.onShowContextmenu(menu, { x: clientX, y: clientY });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icons {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 6px;
|
||||
}
|
||||
.tags-view-container::-webkit-scrollbar {
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
}
|
||||
.tags-view-container {
|
||||
width: calc(100vw - 307px);
|
||||
height: 36px;
|
||||
overflow: hidden;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
background: #fff;
|
||||
.hamburger-container {
|
||||
line-height: 36px;
|
||||
}
|
||||
.tags-view-item {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
color: #24b6dd;
|
||||
font-size: 14px;
|
||||
margin-left: 5px;
|
||||
margin-right: 28px;
|
||||
// display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
i {
|
||||
vertical-align: middle;
|
||||
position: absolute;
|
||||
right: -18px;
|
||||
top: 11px;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #61f9ff;
|
||||
border-bottom: 2px solid #61f9ff;
|
||||
height: 34px;
|
||||
font-weight: bold;
|
||||
&::before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
font-size: 0px;
|
||||
position: absolute;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #61f9ff;
|
||||
bottom: -1px;
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
}
|
||||
|
||||
// close 按钮
|
||||
.el-icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 10px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
transform-origin: 100% 50%;
|
||||
|
||||
&:before {
|
||||
transform: scale(0.6);
|
||||
display: inline-block;
|
||||
vertical-align: -3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #b4bccc;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon._icon {
|
||||
transform: rotateZ(180deg);
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
.icon {
|
||||
transform: rotateZ(0deg);
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
::v-deep .el-popper.is-light,
|
||||
::v-deep .el-popper.is-light .el-popper__arrow::before {
|
||||
background: #0d2944;
|
||||
border: 1px solid #03438b;
|
||||
}
|
||||
::v-deep .el-dropdown-menu {
|
||||
background-color: transparent;
|
||||
.el-dropdown-menu__item {
|
||||
color: #fff;
|
||||
&:hover {
|
||||
color: #61f9ff;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
150
src/layout/components/UpdatePwdDialog.vue
Normal file
150
src/layout/components/UpdatePwdDialog.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<el-dialog title="修改密码" width="500px" :model-value="modelValue" @close="closed">
|
||||
<el-form :model="params" class="editPassword-page">
|
||||
<el-form-item label="老密码">
|
||||
<el-input placeholder="请输入老密码" v-model="params.oldPassword"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码">
|
||||
<el-input placeholder="请输入新密码" show-password v-model="params.password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码强度:">
|
||||
<div class="input_span">
|
||||
<span id="one" />
|
||||
<span id="two" />
|
||||
<span id="three" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closed">取消</el-button>
|
||||
<el-button type="primary" @click="updatePwd">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useStore } from "vuex";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { editPassword } from "@/api/sys";
|
||||
import { defineProps, watch, ref, onMounted, nextTick } from "vue";
|
||||
import {
|
||||
saveRoleMenuInfo,
|
||||
getRoleMenuIds,
|
||||
getMenuTree,
|
||||
getPasswordLevel
|
||||
} from "@/api/user-manage";
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const store = useStore();
|
||||
const params = ref({});
|
||||
|
||||
const emits = defineEmits(["update:modelValue", "updateRole"]);
|
||||
const closed = () => {
|
||||
emits("update:modelValue", false);
|
||||
};
|
||||
|
||||
const onComfirm = () => {
|
||||
saveRoleMenuInfo(params).then((res) => {
|
||||
ElMessage.success("操作成功");
|
||||
});
|
||||
closed();
|
||||
};
|
||||
const updatePwd = () => {
|
||||
if (Number(sysLevel.value) > Number(currentLevel.value)) {
|
||||
ElMessage.error("当前密码等级不够,请增加复杂度");
|
||||
return false;
|
||||
}
|
||||
if (params.value.password.length > 20) {
|
||||
ElMessage.error("密码长度不能超过20位!");
|
||||
return false;
|
||||
}
|
||||
editPassword({
|
||||
...params.value
|
||||
}).then((res) => {
|
||||
ElMessage.success("修改成功");
|
||||
closed();
|
||||
store.dispatch("user/logout");
|
||||
});
|
||||
};
|
||||
let currentLevel = ref(0);
|
||||
const checkStrong = (sValue) => {
|
||||
let level = 0;
|
||||
// 正则表达式验证符合要求的
|
||||
if (sValue.length >= 6) {
|
||||
level++;
|
||||
}
|
||||
if (/\d/.test(sValue) && /[a-z]/.test(sValue) && /[A-Z]/.test(sValue)) {
|
||||
level++;
|
||||
} // 数字 字母小写,大写
|
||||
// if (/[a-z]/.test(sValue)) { modes++; } // 小写
|
||||
// if (/[A-Z]/.test(sValue)) { modes++; } // 大写
|
||||
if (/\W/.test(sValue)) {
|
||||
level++;
|
||||
} // 特殊字符
|
||||
currentLevel.value = level;
|
||||
return level;
|
||||
};
|
||||
|
||||
const sysLevel = ref(0);
|
||||
const getPwdLevel = () => {
|
||||
getPasswordLevel().then((res) => {
|
||||
sysLevel.value = res;
|
||||
});
|
||||
};
|
||||
getPwdLevel();
|
||||
|
||||
watch(
|
||||
() => params.value.password,
|
||||
(newname, oldname) => {
|
||||
const msgText = checkStrong(newname);
|
||||
if (msgText > 1 || msgText === 1) {
|
||||
document.getElementById("one").style.background = "#00D1B2";
|
||||
} else {
|
||||
document.getElementById("one").style.background = "#eee";
|
||||
}
|
||||
if (msgText > 2 || msgText === 2) {
|
||||
document.getElementById("two").style.background = "orange";
|
||||
} else {
|
||||
document.getElementById("two").style.background = "#eee";
|
||||
}
|
||||
if (msgText === 3) {
|
||||
document.getElementById("three").style.background = "red";
|
||||
} else {
|
||||
document.getElementById("three").style.background = "#eee";
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editPassword-page {
|
||||
.table-header-wrap {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
.input_span {
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
border: 1px solid #ccc;
|
||||
|
||||
&:first-child {
|
||||
border-right: 0;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-left: 0;
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
61
src/layout/index.vue
Normal file
61
src/layout/index.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div>
|
||||
<Header />
|
||||
<div class="app-wrapper" :class="[$store.getters.sidebarOpened ? 'openSidebar' : 'hideSidebar']">
|
||||
<!-- 左侧 menu -->
|
||||
<SideBar id="guide-sidebar" class="sidebar-container" />
|
||||
<!-- 顶部的 navbar -->
|
||||
<div class="main-container">
|
||||
<TagsView></TagsView>
|
||||
<!-- 内容区 -->
|
||||
<AppMain />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Header from "./components/Header";
|
||||
import NavBar from "./components/NavBar";
|
||||
import SideBar from "./components/SideBar/index";
|
||||
import AppMain from "./components/AppMain";
|
||||
import TagsView from "./components/TagsView";
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/styles/mixin.scss";
|
||||
@import "~@/styles/variables.scss";
|
||||
@import "~@/styles/sidebar.scss";
|
||||
@import "~@/styles/transition.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: calc(100vh - 88px);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
.el-scrollbar {
|
||||
|
||||
.is-active {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$sideBarWidth});
|
||||
/* 在variables里面定义的变量 */
|
||||
transition: width #{$sideBarDuration};
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - #{$hideSideBarWidth});
|
||||
}
|
||||
::v-deep .el-scrollbar__view{
|
||||
border-right: 1px solid #07376d;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user