提交代码

This commit is contained in:
2025-04-12 14:54:02 +08:00
parent f7761e99a1
commit a2e89f5ea1
599 changed files with 194300 additions and 0 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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>