前端框架整体修改
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="NewFrameWork2023-WEB" />
|
<meta name="description" content="NewFrameWork2023-WEB" />
|
||||||
<meta name="keywords" content="NewFrameWork2023-WEB" />
|
<meta name="keywords" content="NewFrameWork2023-WEB" />
|
||||||
|
|||||||
@ -10,11 +10,13 @@
|
|||||||
"prettier": "prettier --write ."
|
"prettier": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.0.10",
|
||||||
"@types/js-cookie": "^3.0.2",
|
"@types/js-cookie": "^3.0.2",
|
||||||
"@vueuse/core": "^9.1.1",
|
"@vueuse/core": "^9.1.1",
|
||||||
"@wangeditor/editor": "^5.0.0",
|
"@wangeditor/editor": "^5.0.0",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.2.0",
|
"axios": "^1.2.0",
|
||||||
"better-scroll": "^2.4.2",
|
"better-scroll": "^2.4.2",
|
||||||
"default-passive-events": "^2.0.0",
|
"default-passive-events": "^2.0.0",
|
||||||
@ -63,6 +65,5 @@
|
|||||||
},
|
},
|
||||||
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
|
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
|
||||||
"author": "有来开源组织",
|
"author": "有来开源组织",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"__npminstall_done": false
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.2 KiB |
BIN
frontend/public/logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElConfigProvider } from 'element-plus';
|
import { ElConfigProvider } from 'element-plus';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore,usetTheme } from '@/store/modules/app';
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
||||||
<router-view />
|
<a-config-provider :theme="usetTheme">
|
||||||
|
<router-view />
|
||||||
|
</a-config-provider>
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</template>
|
</template>
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import request from '@/utils/request';
|
|
||||||
|
|
||||||
export function getMessageList(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/getMessageList',
|
|
||||||
method: 'get',
|
|
||||||
params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function setMessageStatus(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/setMessageStatus?id=' + data,
|
|
||||||
method: 'post'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function setAllMessageStatus() {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/setAllMessageStatus',
|
|
||||||
method: 'post'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function deleteMessageById(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/message/deleteMessageById?id=' + data,
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import request from '@/utils/request';
|
|
||||||
|
|
||||||
//获取表格内容
|
|
||||||
export function getTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/getQuartzJobList',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//新增表格内容
|
|
||||||
export function addTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/addQuartzJob',
|
|
||||||
method: 'post',
|
|
||||||
data: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//删除定时任务
|
|
||||||
export function delTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/deleteQuartzJob',
|
|
||||||
method: 'post',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//修改定时任务
|
|
||||||
export function updataTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/updateQuartzJob',
|
|
||||||
method: 'post',
|
|
||||||
data: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//定时任务是否有效
|
|
||||||
export function setTaskList(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/setQuartzStatus',
|
|
||||||
method: 'post',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//拖拽
|
|
||||||
export function changeItemOrder(params: any) {
|
|
||||||
return request({
|
|
||||||
url: '/system/quartzjob/changeDictOrder',
|
|
||||||
method: 'post',
|
|
||||||
params: params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
1
frontend/src/assets/icons/menuActiveBg.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="106" height="51" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-32.8%" y="-233.3%" width="165.6%" height="566.7%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="7" in="SourceGraphic"/></filter><filter x="-23.7%" y="-289.3%" width="147.4%" height="678.6%" filterUnits="objectBoundingBox" id="b"><feGaussianBlur stdDeviation="3" in="SourceGraphic"/></filter><filter x="-4.7%" y="-300%" width="109.4%" height="700%" filterUnits="objectBoundingBox" id="d"><feGaussianBlur stdDeviation="1" in="SourceGraphic"/></filter><linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="c"><stop stop-color="#37FDF7" stop-opacity="0" offset="0%"/><stop stop-color="#37FDF7" offset="51.347%"/><stop stop-color="#37FDF7" stop-opacity="0" offset="100%"/></linearGradient></defs><g transform="translate(21 21)" fill="none" fill-rule="evenodd"><ellipse fill="#36FCF6" filter="url(#a)" cx="32" cy="4.5" rx="32" ry="4.5"/><ellipse fill="#11FFF2" filter="url(#b)" cx="32" cy="2" rx="19" ry="1.556"/><path fill="url(#c)" filter="url(#d)" d="M0 1h64v1H0z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 171 B |
BIN
frontend/src/assets/images/login-bg.jpg
Normal file
|
After Width: | Height: | Size: 673 KiB |
BIN
frontend/src/assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 569 B |
|
Before Width: | Height: | Size: 409 B |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
@ -1,105 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-breadcrumb
|
|
||||||
separator-class="el-icon-arrow-right"
|
|
||||||
class="h-[50px] flex items-center"
|
|
||||||
>
|
|
||||||
<transition-group name="breadcrumb">
|
|
||||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
|
||||||
<span
|
|
||||||
v-if="
|
|
||||||
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
|
|
||||||
"
|
|
||||||
class="text-[#97a8be]"
|
|
||||||
>{{ generateTitle(item.meta.title) }}</span
|
|
||||||
>
|
|
||||||
<a v-else @click.prevent="handleLink(item)">
|
|
||||||
{{ generateTitle(item.meta.title) }}
|
|
||||||
</a>
|
|
||||||
</el-breadcrumb-item>
|
|
||||||
</transition-group>
|
|
||||||
</el-breadcrumb>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onBeforeMount, ref, watch } from 'vue';
|
|
||||||
import { useRoute, RouteLocationMatched } from 'vue-router';
|
|
||||||
import { compile } from 'path-to-regexp';
|
|
||||||
import router from '@/router';
|
|
||||||
import { generateTitle } from '@/utils/i18n';
|
|
||||||
|
|
||||||
const currentRoute = useRoute();
|
|
||||||
const pathCompile = (path: string) => {
|
|
||||||
const { params } = currentRoute;
|
|
||||||
const toPath = compile(path);
|
|
||||||
return toPath(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
const breadcrumbs = ref([] as Array<RouteLocationMatched>);
|
|
||||||
|
|
||||||
function getBreadcrumb() {
|
|
||||||
let matched = currentRoute.matched.filter(
|
|
||||||
item => item.meta && item.meta.title
|
|
||||||
);
|
|
||||||
const first = matched[0];
|
|
||||||
if (!isDashboard(first)) {
|
|
||||||
matched = [
|
|
||||||
{ path: '/dashboard', meta: { title: 'dashboard' } } as any
|
|
||||||
].concat(matched);
|
|
||||||
}
|
|
||||||
breadcrumbs.value = matched.filter(item => {
|
|
||||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDashboard(route: RouteLocationMatched) {
|
|
||||||
const name = route && route.name;
|
|
||||||
if (!name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
name.toString().trim().toLocaleLowerCase() ===
|
|
||||||
'Dashboard'.toLocaleLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLink(item: any) {
|
|
||||||
const { redirect, path } = item;
|
|
||||||
if (redirect) {
|
|
||||||
router.push(redirect).catch(err => {
|
|
||||||
console.warn(err);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.push(pathCompile(path)).catch(err => {
|
|
||||||
console.warn(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => currentRoute.path,
|
|
||||||
path => {
|
|
||||||
if (path.startsWith('/redirect/')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getBreadcrumb();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
getBreadcrumb();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-breadcrumb.el-breadcrumb {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 50px;
|
|
||||||
margin-left: 8px;
|
|
||||||
|
|
||||||
.no-redirect {
|
|
||||||
color: #97a8be;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a
|
|
||||||
href="https://github.com/hxrui"
|
|
||||||
target="_blank"
|
|
||||||
class="github-corner"
|
|
||||||
aria-label="View source on Github"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="80"
|
|
||||||
height="80"
|
|
||||||
viewBox="0 0 250 250"
|
|
||||||
style="fill: #40c9c6; color: #fff"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
|
|
||||||
<path
|
|
||||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
|
||||||
fill="currentColor"
|
|
||||||
style="transform-origin: 130px 106px"
|
|
||||||
class="octo-arm"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
|
||||||
fill="currentColor"
|
|
||||||
class="octo-body"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.github-corner:hover .octo-arm {
|
|
||||||
animation: octocat-wave 560ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes octocat-wave {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
transform: rotate(0);
|
|
||||||
}
|
|
||||||
20%,
|
|
||||||
60% {
|
|
||||||
transform: rotate(-25deg);
|
|
||||||
}
|
|
||||||
40%,
|
|
||||||
80% {
|
|
||||||
transform: rotate(10deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
.github-corner:hover .octo-arm {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
.github-corner .octo-arm {
|
|
||||||
animation: octocat-wave 560ms ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
@click="toggleClick"
|
|
||||||
class="px-[15px] hover:bg-gray-50 cursor-pointer h-[50px] leading-[50px] text-center fixed bottom-2 "
|
|
||||||
style="z-index:1005;display: flex;display: -webkit-flex; justify-content: center; align-items: center; -webkit-justify-content: center; -webkit-align-items: center;"
|
|
||||||
:style="appStore.sidebar.opened?'left:80px': 'left:2px'"
|
|
||||||
>
|
|
||||||
<img v-if="isActive" src="@/assets/MenuIcon/dh_sq.png" alt="">
|
|
||||||
<img v-else src="@/assets/MenuIcon/dh_sq1.png" alt="">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
defineProps({
|
|
||||||
isActive: {
|
|
||||||
required: true,
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['toggleClick']);
|
|
||||||
|
|
||||||
function toggleClick() {
|
|
||||||
emit('toggleClick');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.hamburger {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
fill: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger.is-active {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -3,14 +3,11 @@ import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|||||||
|
|
||||||
import { addClass, removeClass } from '@/utils/index';
|
import { addClass, removeClass } from '@/utils/index';
|
||||||
|
|
||||||
// import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
|
|
||||||
// 图标依赖
|
// 图标依赖
|
||||||
import { Close, Setting } from '@element-plus/icons-vue';
|
import { Close, Setting } from '@element-plus/icons-vue';
|
||||||
import { ElColorPicker } from 'element-plus';
|
import { ElColorPicker } from 'element-plus';
|
||||||
|
|
||||||
// const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
@ -32,21 +29,6 @@ watch(show, value => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function addEventClick() {
|
function addEventClick() {
|
||||||
window.addEventListener('click', closeSidebar, { passive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSidebar(evt: any) {
|
|
||||||
// 主题选择点击不关闭
|
|
||||||
let parent = evt.target.closest('.theme-picker-dropdown');
|
|
||||||
if (parent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = evt.target.closest('.right-panel');
|
|
||||||
if (!parent) {
|
|
||||||
show.value = false;
|
|
||||||
window.removeEventListener('click', closeSidebar);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rightPanel = ref(ElColorPicker);
|
const rightPanel = ref(ElColorPicker);
|
||||||
@ -92,7 +74,6 @@ onBeforeUnmount(() => {
|
|||||||
.showRightPanel {
|
.showRightPanel {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(100% - 15px);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -16,8 +16,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
// const settingsStore = useSettingsStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -6,11 +6,11 @@ export default {
|
|||||||
},
|
},
|
||||||
// 登录页面国际化
|
// 登录页面国际化
|
||||||
login: {
|
login: {
|
||||||
title: '公司开发平台框架',
|
title: '水电水利建设项目全过程环境管理信息平台',
|
||||||
username: '用户名',
|
username: '用户名',
|
||||||
rulesUsername: '请输入用户名',
|
rulesUsername: '用户账号/身份证号/手机号 不能为空',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
rulesPassword: '请输入密码',
|
rulesPassword: '密码 不能为空',
|
||||||
rulesPasswordPlace: '密码不能少于6位',
|
rulesPasswordPlace: '密码不能少于6位',
|
||||||
login: '登 录',
|
login: '登 录',
|
||||||
code: '请输入验证码',
|
code: '请输入验证码',
|
||||||
|
|||||||
@ -22,28 +22,13 @@ const routeKey = computed(() => router.path + Math.random());
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/variables.module.scss" as *;
|
||||||
.app-main {
|
.app-main {
|
||||||
min-height: calc(100vh - 114px);
|
min-height: calc(100vh - $layout-header-height - $locationbar-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f0f2f5;
|
background-color: #ffffff;
|
||||||
padding: 0 16px 16px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
padding-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hasTagsView {
|
|
||||||
.app-main {
|
|
||||||
/* 84 = navbar + tags-view = 50 + 34 */
|
|
||||||
min-height: calc(100vh - 114px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
padding-top: 84px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,46 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from "element-plus";
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from "@/utils/auth";
|
||||||
|
import { UserOutlined, LogoutOutlined } from "@ant-design/icons-vue";
|
||||||
import Screenfull from '@/components/Screenfull/index.vue';
|
// 国际化
|
||||||
import SizeSelect from '@/components/SizeSelect/index.vue';
|
import { useI18n } from "vue-i18n";
|
||||||
import News from './news.vue';
|
const { t } = useI18n();
|
||||||
// import LangSelect from '@/components/LangSelect/index.vue';
|
// import LangSelect from '@/components/LangSelect/index.vue';
|
||||||
import MixNav from './Sidebar/MixNav.vue';
|
import Sidebar from "./Sidebar/index.vue";
|
||||||
// import { CaretBottom } from '@element-plus/icons-vue';
|
|
||||||
import Logo from './Sidebar/Logo.vue';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useTagsViewStore } from "@/store/modules/tagsView";
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import Cookies from "js-cookie";
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { storeToRefs } from "pinia";
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
const url = import.meta.env.VITE_APP_BASE_API;
|
const url = import.meta.env.VITE_APP_BASE_API;
|
||||||
const username = Cookies.get('username');
|
const username = Cookies.get("username");
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
|
||||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const querystr = ref('');
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const device = computed(() => appStore.device);
|
|
||||||
const news = ref()
|
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: "取消",
|
||||||
type: 'warning'
|
type: "warning",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
userStore
|
userStore
|
||||||
.logout()
|
.logout()
|
||||||
@ -52,114 +39,128 @@ function logout() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function querystrChange() {
|
const badgeval = ref(0);
|
||||||
}
|
const isbadge = ref(true);
|
||||||
const badgeval = ref(0)
|
var source = new EventSource(url + `/sse/connect/` + getToken());
|
||||||
const isbadge = ref(true)
|
|
||||||
var source = new EventSource(url+ `/sse/connect/` + getToken(),);
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if ("EventSource" in window) {
|
if ("EventSource" in window) {
|
||||||
source.onmessage = function(e) {
|
source.onmessage = function (e) {
|
||||||
if(e.data>0) {
|
if (e.data > 0) {
|
||||||
badgeval.value = e.data
|
badgeval.value = e.data;
|
||||||
isbadge.value = false
|
isbadge.value = false;
|
||||||
news.value.init()
|
|
||||||
} else {
|
} else {
|
||||||
isbadge.value = true
|
isbadge.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
source.onopen = function(e) {
|
source.onopen = function (e) {};
|
||||||
};
|
source.onerror = function (e: any) {
|
||||||
source.onerror = function(e:any) {
|
|
||||||
if (e.readyState == EventSource.CLOSED) {
|
if (e.readyState == EventSource.CLOSED) {
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
};
|
}
|
||||||
})
|
});
|
||||||
onBeforeUnmount(()=>{
|
onBeforeUnmount(() => {
|
||||||
source.close();
|
source.close();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<logo v-if="sidebarLogo" :collapse="isCollapse" />
|
<a-layout-header class="header">
|
||||||
<!-- <mix-nav v-if="device !== 'mobile' && settingsStore.layout === 'mix'" /> -->
|
<transition class="bg-white-800">
|
||||||
<div v-if="settingsStore.layout === 'left'" class="flex justify-start">
|
<a
|
||||||
<div class="flex justify-center items-center">
|
href="/"
|
||||||
<!--全屏 -->
|
class="h-[50px] min-w-[350px] flex items-center justify-center text-white"
|
||||||
<div v-if="device === 'desktop'" style="position: relative;">
|
>
|
||||||
<el-input class="keywords" v-model="querystr" placeholder="请输入搜索关键字" clearable
|
<h1 class="text-blank font-bold fontSize-16">{{ t("login.title") }}</h1></a
|
||||||
@change="querystrChange"></el-input>
|
>
|
||||||
<img src="@/assets/MenuIcon/top_ss.png" alt="" style="position: absolute;right: 30px;top: 10px;">
|
</transition>
|
||||||
</div>
|
<Sidebar />
|
||||||
<div class="flex items-center cursor-pointer" style="margin-right:15px;margin-top: 6px;">
|
|
||||||
<el-badge :value="badgeval" :hidden="isbadge" :max="10">
|
|
||||||
<el-icon style="font-size: 22px;" @click="dialogVisible = true,news.init()"><Bell /></el-icon>
|
|
||||||
</el-badge>
|
|
||||||
</div>
|
|
||||||
<!-- 布局大小 -->
|
|
||||||
<el-tooltip content="字号大小" effect="dark" placement="bottom">
|
|
||||||
<size-select />
|
|
||||||
</el-tooltip>
|
|
||||||
<!-- 全屏 -->
|
|
||||||
<el-tooltip content="全屏缩放" effect="dark" placement="bottom">
|
|
||||||
<screenfull id="screenfull" />
|
|
||||||
</el-tooltip>
|
|
||||||
<!--语言选择-->
|
|
||||||
<!-- <lang-select /> -->
|
|
||||||
</div>
|
|
||||||
<!-- 头像 -->
|
|
||||||
<div style="heigth:100%;display: flex;display: -webkit-flex;align-items: center; -webkit-align-items: center; ">
|
|
||||||
<div class="heighta"></div>
|
|
||||||
<img v-if="userStore.avatar!== ''" :src="url + '/avatar/' + userStore.avatar + '?imageView2/1/w/80/h/80'"
|
|
||||||
class="w-[30px] h-[30px] rounded-lg rounded-full" style="border-radius: 50%;" />
|
|
||||||
<img v-else src="@/assets/MenuIcon/top_tx.png" alt="" class="w-[30px] h-[30px] rounded-lg rounded-full" style="border-radius: 50%;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-tooltip content="个人中心" effect="dark" placement="bottom">
|
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||||
<div class="cursor-pointer flex justify-center items-center pl-[10px]"
|
<a-space class="username">
|
||||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="$router.push('/personalCenter')">{{
|
<div>
|
||||||
username }}</div>
|
<span class="icon">
|
||||||
</el-tooltip>
|
<UserOutlined />
|
||||||
|
</span>
|
||||||
<div class="flex justify-center items-center cursor-pointer pr-[12px]"
|
<span class="text">{{ username }}</span>
|
||||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="logout"><el-divider
|
</div>
|
||||||
direction="vertical" style="padding-right:5px" />{{ $t('navbar.logout') }}</div>
|
</a-space>
|
||||||
<News ref="news" v-model:dialog-visible="dialogVisible" />
|
<template #overlay>
|
||||||
</div>
|
<a-menu>
|
||||||
|
<a-menu-item key="changePassword">
|
||||||
|
<UserOutlined />
|
||||||
|
修改密码
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout" @click="logout">
|
||||||
|
<LogoutOutlined />
|
||||||
|
退出登录
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-layout-header>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/variables.module.scss" as *;
|
||||||
.navbar {
|
.navbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 60px;
|
height: 110px;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
-webkit-justify-content: space-between;
|
|
||||||
box-shadow: 0px 0px 10px rgb(219 225 236);
|
|
||||||
z-index: 98;
|
z-index: 98;
|
||||||
}
|
|
||||||
|
|
||||||
.keywords {
|
.header {
|
||||||
width: 240px;
|
width: 100%;
|
||||||
height: 34px;
|
display: flex;
|
||||||
margin-right: 20px;
|
align-items: center;
|
||||||
|
background-color: #005293;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||||
|
height: $layout-header-height;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.username {
|
||||||
|
height: $layout-header-height;
|
||||||
|
line-height: $layout-header-height;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
div {
|
||||||
padding-left: 15px;
|
display: flex;
|
||||||
border-radius: 40px;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
text-align: center;
|
||||||
|
color: $main-menu-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: $main-menu-color;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.heighta{
|
|
||||||
border-left:1px solid #dcdfe6 ;
|
.heighta {
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,198 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Sunny, Moon } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
|
|
||||||
import { useDark, useToggle } from '@vueuse/core';
|
|
||||||
import { ElDivider, ElSwitch, ElTooltip } from 'element-plus';
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const isDark = useDark();
|
|
||||||
|
|
||||||
function toggleTheme() {
|
|
||||||
const isDark = useDark();
|
|
||||||
useToggle(isDark);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window.document.body.setAttribute('layout', settingsStore.layout);
|
|
||||||
});
|
|
||||||
|
|
||||||
function changeLayout(layout: string) {
|
|
||||||
settingsStore.changeSetting({ key: 'layout', value: layout });
|
|
||||||
window.document.body.setAttribute('layout', settingsStore.layout);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="settings-container">
|
|
||||||
<h3 class="text-base font-bold">项目配置</h3>
|
|
||||||
<el-divider />
|
|
||||||
<div class="drawer-item">
|
|
||||||
<span>开启 Tags-View</span>
|
|
||||||
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-item">
|
|
||||||
<span>固定 Header</span>
|
|
||||||
<el-switch v-model="settingsStore.fixedHeader" class="drawer-switch" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-item">
|
|
||||||
<span>侧边栏 Logo</span>
|
|
||||||
<el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-divider>主题</el-divider>
|
|
||||||
|
|
||||||
<div class="flex justify-center" @click.stop>
|
|
||||||
<el-switch
|
|
||||||
v-model="isDark"
|
|
||||||
inline-prompt
|
|
||||||
@change="toggleTheme"
|
|
||||||
:active-icon="Sunny"
|
|
||||||
:inactive-icon="Moon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-divider>导航栏布局</el-divider>
|
|
||||||
|
|
||||||
<ul class="layout">
|
|
||||||
<el-tooltip content="左侧模式" placement="bottom">
|
|
||||||
<li
|
|
||||||
:class="
|
|
||||||
'layout-item layout-left ' +
|
|
||||||
(settingsStore.layout == 'left' ? 'is-active' : '')
|
|
||||||
"
|
|
||||||
@click="changeLayout('left')"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</li>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="顶部模式" placement="bottom">
|
|
||||||
<li
|
|
||||||
:class="
|
|
||||||
'layout-item layout-top ' +
|
|
||||||
(settingsStore.layout == 'top' ? 'is-active' : '')
|
|
||||||
"
|
|
||||||
@click="changeLayout('top')"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</li>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="混合模式" placement="bottom">
|
|
||||||
<li
|
|
||||||
:class="
|
|
||||||
'layout-item layout-mix ' +
|
|
||||||
(settingsStore.layout == 'mix' ? 'is-active' : '')
|
|
||||||
"
|
|
||||||
@click="changeLayout('mix')"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</li>
|
|
||||||
</el-tooltip>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.settings-container {
|
|
||||||
padding: 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.drawer-title {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: rgba(0, 0, 0, 0.85);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-item {
|
|
||||||
color: rgba(0, 0, 0, 0.65);
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-switch {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout {
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-around;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&-item {
|
|
||||||
width: 18%;
|
|
||||||
height: 45px;
|
|
||||||
background: #f0f2f5;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
&-item.is-active {
|
|
||||||
border: 2px solid var(--el-color-primary);
|
|
||||||
}
|
|
||||||
&-left {
|
|
||||||
div {
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
height: 100%;
|
|
||||||
background: #1b2a47;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
width: 70%;
|
|
||||||
height: 30%;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-top {
|
|
||||||
div {
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 100%;
|
|
||||||
height: 30%;
|
|
||||||
background: #1b2a47;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-mix {
|
|
||||||
div {
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 100%;
|
|
||||||
height: 30%;
|
|
||||||
background: #1b2a47;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
width: 30%;
|
|
||||||
height: 70%;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #1b2a47;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { isExternal } from '@/utils/validate';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const sidebar = computed(() => appStore.sidebar);
|
|
||||||
const device = computed(() => appStore.device);
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
to: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
function push() {
|
|
||||||
if (device.value === 'mobile' && sidebar.value.opened == true) {
|
|
||||||
appStore.closeSideBar(false);
|
|
||||||
}
|
|
||||||
router.push(props.to).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
|
||||||
<slot />
|
|
||||||
</a>
|
|
||||||
<div v-else @click="push">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
|
|
||||||
// 国际化
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
const { t } = useI18n();
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
collapse: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const logo = ref<string>(
|
|
||||||
new URL(`../../../assets/logo.png`, import.meta.url).href
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<transition class="ml-5 bg-white-800">
|
|
||||||
<router-link
|
|
||||||
key="collapse"
|
|
||||||
class="h-[50px] flex items-center justify-center"
|
|
||||||
to="/dashboard"
|
|
||||||
>
|
|
||||||
<img :src="logo" class="w-5 h-5" />
|
|
||||||
<h1 class="ml-3 text-blank font-bold" :style="{fontSize: appStore.size === 'default' ? '20px' : '22px'}">{{t('login.title')}}</h1>
|
|
||||||
</router-link>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
@ -1,159 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, onMounted } from 'vue';
|
|
||||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
|
||||||
import {
|
|
||||||
ElDropdown,
|
|
||||||
ElDropdownItem,
|
|
||||||
ElDropdownMenu,
|
|
||||||
ElMenu,
|
|
||||||
ElMessageBox,
|
|
||||||
ElTooltip
|
|
||||||
} from 'element-plus';
|
|
||||||
|
|
||||||
import Screenfull from '@/components/Screenfull/index.vue';
|
|
||||||
import SizeSelect from '@/components/SizeSelect/index.vue';
|
|
||||||
import LangSelect from '@/components/LangSelect/index.vue';
|
|
||||||
import { CaretBottom } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
import SidebarItem from './SidebarItem.vue';
|
|
||||||
import variables from '@/styles/variables.module.scss';
|
|
||||||
|
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
|
||||||
import { useUserStore } from '@/store/modules/user';
|
|
||||||
import { usePermissionStore } from '@/store/modules/permission';
|
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const permissionStore = usePermissionStore();
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const routes = [] as any[];
|
|
||||||
onMounted(() => {
|
|
||||||
permissionStore.routes.forEach(item => {
|
|
||||||
const { ...newItem } = item;
|
|
||||||
routes.push(newItem);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeMenu = computed<string>(() => {
|
|
||||||
const { meta, path } = route;
|
|
||||||
if (meta?.activeMenu) {
|
|
||||||
return meta.activeMenu as string;
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
});
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
userStore
|
|
||||||
.logout()
|
|
||||||
.then(() => {
|
|
||||||
tagsViewStore.delAllViews();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
router.push(`/login?redirect=${route.fullPath}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="horizontal-header">
|
|
||||||
<el-menu
|
|
||||||
class="horizontal-header-menu"
|
|
||||||
:default-active="activeMenu"
|
|
||||||
:background-color="variables.menuBg"
|
|
||||||
:text-color="variables.menuText"
|
|
||||||
:active-text-color="variables.menuActiveText"
|
|
||||||
mode="horizontal"
|
|
||||||
>
|
|
||||||
<sidebar-item
|
|
||||||
v-for="route in routes"
|
|
||||||
:item="route"
|
|
||||||
:key="route.path"
|
|
||||||
:base-path="route.path"
|
|
||||||
/>
|
|
||||||
</el-menu>
|
|
||||||
|
|
||||||
<div class="horizontal-header-right">
|
|
||||||
<!--全屏 -->
|
|
||||||
<screenfull id="screenfull" />
|
|
||||||
|
|
||||||
<!-- 布局大小 -->
|
|
||||||
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
|
||||||
<size-select />
|
|
||||||
</el-tooltip>
|
|
||||||
123
|
|
||||||
<!--语言选择-->
|
|
||||||
<lang-select />
|
|
||||||
|
|
||||||
<el-dropdown trigger="click">
|
|
||||||
<div class="flex justify-center items-center pr-[20px]">
|
|
||||||
<img
|
|
||||||
:src="userStore.avatar + '?imageView2/1/w/80/h/80'"
|
|
||||||
class="w-[40px] h-[40px] rounded-lg"
|
|
||||||
/>
|
|
||||||
<CaretBottom class="w-3 h-3" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<router-link to="/">
|
|
||||||
<el-dropdown-item>{{ $t('navbar.dashboard') }}</el-dropdown-item>
|
|
||||||
</router-link>
|
|
||||||
<a target="_blank" href="https://github.com/hxrui">
|
|
||||||
<el-dropdown-item>Github</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://gitee.com/haoxr">
|
|
||||||
<el-dropdown-item>{{ $t('navbar.gitee') }}</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
|
|
||||||
<el-dropdown-item>{{ $t('navbar.document') }}</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<el-dropdown-item divided @click="logout">
|
|
||||||
{{ $t('navbar.logout') }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@use '@/styles/variables.module' as variables;
|
|
||||||
|
|
||||||
.horizontal-header {
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
-webkit-justify-content: space-around;
|
|
||||||
background: #001529;
|
|
||||||
|
|
||||||
&-menu {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-right {
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
min-width: 340px;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
-webkit-justify-content: flex-end;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import { isExternal } from "@/utils/validate";
|
|
||||||
import AppLink from "./Link.vue";
|
|
||||||
import { generateTitle } from "@/utils/i18n";
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const url = import.meta.env.VITE_APP_BASE_API;
|
|
||||||
defineProps({
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isNest: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
// basePath: {
|
|
||||||
// type: String,
|
|
||||||
// required: true,
|
|
||||||
// },
|
|
||||||
});
|
|
||||||
|
|
||||||
const onlyOneChild = ref();
|
|
||||||
|
|
||||||
function hasOneShowingChild(children = [] as any, parent: any) {
|
|
||||||
if (!children) {
|
|
||||||
children = [];
|
|
||||||
}
|
|
||||||
const showingChildren = children.filter((item: any) => {
|
|
||||||
if (item.meta && item.meta.hidden) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Temp set(will be used if only has one showing child)
|
|
||||||
onlyOneChild.value = item;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// When there is only one child router, the child router is displayed by default
|
|
||||||
if (showingChildren.length === 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show parent if there are no child router to display
|
|
||||||
if (showingChildren.length === 0) {
|
|
||||||
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePath(routePath: any) {
|
|
||||||
// if (isExternal(props.basePath)) {
|
|
||||||
// return props.basePath;
|
|
||||||
// }
|
|
||||||
if (routePath.islink == 0) {
|
|
||||||
if (isExternal(routePath.opturl)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return routePath.opturl;
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
generateTitle
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div v-if="!item.meta || !item.meta.hidden || item.isdisplay == '1'">
|
|
||||||
<template v-if="
|
|
||||||
hasOneShowingChild(item.children, item) &&
|
|
||||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
|
||||||
(!item.meta || !item.meta.alwaysShow)
|
|
||||||
">
|
|
||||||
<app-link v-if="onlyOneChild.meta || item.isdisplay == '1'" :to="resolvePath(onlyOneChild)">
|
|
||||||
<el-menu-item :index="resolvePath(onlyOneChild)" :class="{ 'submenu-title-noDropdown': !isNest }"
|
|
||||||
style="height: 44px;">
|
|
||||||
<img v-if="onlyOneChild.icon && onlyOneChild.name == '首页'" src="@/assets/MenuIcon/dh_sy.png"
|
|
||||||
style="margin-right:10px;width: 16px">
|
|
||||||
<img v-if="onlyOneChild.icon && onlyOneChild.name !== '首页'" :src="url + '/menu/' + item.icon"
|
|
||||||
style="margin-right:10px;width: 16px">
|
|
||||||
<template #title>
|
|
||||||
<span :style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }">{{ generateTitle(onlyOneChild.name)
|
|
||||||
}}</span>
|
|
||||||
</template>
|
|
||||||
</el-menu-item>
|
|
||||||
</app-link>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-sub-menu v-else :index="resolvePath(item)" telested>
|
|
||||||
<template #title>
|
|
||||||
<div style="height: 44px; display: flex;display: -webkit-flex; align-items: center;-webkit-align-items: center;justify-content: space-around;">
|
|
||||||
<img v-if="item.icon" :icon-class="item.icon" :src="url + '/menu/' + item.icon" alt=""
|
|
||||||
style="margin-right:10px;width: 16px; " class="management">
|
|
||||||
<span :style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" style="display: block;" >{{
|
|
||||||
generateTitle(item.name) }}</span>
|
|
||||||
</div>
|
|
||||||
<!-- <svg-icon v-if="item.icon" :icon-class="item.icon" :style="{fontSize: appStore.size === 'default' ? '14px' : '16px'}" /> -->
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<sidebar-item v-for="child in item.children" :key="child.opturl" :item="child" :is-nest="true"
|
|
||||||
:base-path="resolvePath(child)" />
|
|
||||||
</el-sub-menu>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
:deep(.el-sub-menu__title) {
|
|
||||||
height: 44px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-icon) {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
:deep(.el-tooltip__trigger span) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
:deep(.el-tooltip__trigger .management) {
|
|
||||||
margin-left:20px !important ;
|
|
||||||
}
|
|
||||||
:deep(.el-menu-tooltip__trigger){
|
|
||||||
width: 105% !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@ -1,47 +1,173 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { ref, onBeforeMount, onMounted } from "vue";
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
import SidebarItem from './SidebarItem.vue';
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
import variables from '@/styles/variables.module.scss';
|
|
||||||
|
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
import { usePermissionStore } from '@/store/modules/permission';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
|
const menus: any = ref([]); // 主菜单列表
|
||||||
|
const subMenus: any = ref([]); // 子菜单列表
|
||||||
|
const activeKey = ref(""); // 主菜单选中项
|
||||||
|
const subActiveKey = ref(""); // 子菜单选中项
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
// 主菜单切换
|
||||||
|
const handleTabChange = (key: string) => {
|
||||||
const activeMenu = computed<string>(() => {
|
let data = menus.value.find((item: any) => item.id === key);
|
||||||
const { meta, path } = route;
|
subMenus.value = data.children;
|
||||||
if (meta?.activeMenu) {
|
activeKey.value = key;
|
||||||
return meta.activeMenu as string;
|
if (data.children.length > 0) {
|
||||||
|
subActiveKey.value = data.children[0].id;
|
||||||
|
let subData = subMenus.value.find((item: any) => item.id === subActiveKey.value);
|
||||||
|
router.push(subData.path || subData.opturl);
|
||||||
|
} else {
|
||||||
|
subActiveKey.value = "";
|
||||||
}
|
}
|
||||||
return path;
|
};
|
||||||
});
|
// 子菜单切换
|
||||||
|
const handleSubTabChange = (key: string) => {
|
||||||
|
subActiveKey.value = key;
|
||||||
|
let data = subMenus.value.find((item: any) => item.id === key);
|
||||||
|
router.push(data.path || data.opturl);
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
console.log(route)
|
||||||
|
console.log(route)
|
||||||
|
console.log(permissionStore.routes)
|
||||||
|
permissionStore.routes.map((item: any) => {
|
||||||
|
if (item.meta?.hidden) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
if (item?.parentid == null) {
|
||||||
|
menus.value.push(item?.children?.[0]);
|
||||||
|
} else {
|
||||||
|
menus.value.push(item);
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
item.children.map((child: any) => {
|
||||||
|
if (child.path === route.path) {
|
||||||
|
activeKey.value = item.id;
|
||||||
|
subMenus.value = item.children;
|
||||||
|
subActiveKey.value = child.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onMounted(() => {});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ 'has-logo': sidebarLogo }">
|
<a-tabs
|
||||||
<el-scrollbar>
|
class="sidebar-container"
|
||||||
<el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="variables.menuBg"
|
v-model:activeKey="activeKey"
|
||||||
:text-color="variables.menuText" :active-text-color="variables.menuActiveText" :unique-opened="false"
|
@change="handleTabChange"
|
||||||
:collapse-transition="false" mode="vertical" >
|
size="small"
|
||||||
<sidebar-item v-for="route in permissionStore.routes" :item="route" :key="route.opturl"
|
>
|
||||||
:is-collapse="isCollapse" />
|
<a-tab-pane
|
||||||
</el-menu>
|
v-for="route in menus"
|
||||||
</el-scrollbar>
|
:key="route.id"
|
||||||
|
:tab="route.meta.title"
|
||||||
|
></a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<div class="sub-menus">
|
||||||
|
<a-tabs v-model:activeKey="subActiveKey" @change="handleSubTabChange" size="small">
|
||||||
|
<a-tab-pane
|
||||||
|
v-for="route in subMenus"
|
||||||
|
:key="route.id"
|
||||||
|
:tab="route.meta.title"
|
||||||
|
></a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@use '@/styles/variables.module' as variables;
|
@use "@/styles/variables.module.scss" as *;
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: hidden !important;
|
||||||
|
margin: 0 30px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
height: $layout-header-height;
|
||||||
|
margin-bottom: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav-operations {
|
||||||
|
color: $main-menu-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-tab) {
|
||||||
|
padding: 0 20px;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
color: $main-menu-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-tab-active) {
|
||||||
|
color: $main-menu-color-active !important;
|
||||||
|
background: $main-menu-bg-color-active;
|
||||||
|
|
||||||
|
.ant-tabs-tab-btn {
|
||||||
|
color: $main-menu-color-active !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-tab:hover) {
|
||||||
|
color: $main-menu-color-active;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-ink-bar) {
|
||||||
|
visibility: visible;
|
||||||
|
border: none;
|
||||||
|
background: url("@/assets/icons/menuActiveBg.svg") no-repeat center 30px;
|
||||||
|
height: 51px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-menus {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: $layout-header-height;
|
||||||
|
height: $locationbar-height;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
z-index: 100;
|
||||||
|
padding-left: 12px;
|
||||||
|
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.ant-tabs-tab {
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAxMS8wMy8yMrqNQAoAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAOklEQVQImWNgQAchkx3QhZhQeEFTExj+MexnCJoyH1mYEUUBwz8kScYFDOtyEhGKMBSgKmTErQChEAA6FRM7O0rIOgAAAABJRU5ErkJggg==")
|
||||||
|
no-repeat center bottom;
|
||||||
|
border-bottom: 2px solid #005293;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab-active {
|
||||||
|
.ant-tabs-tab-btn {
|
||||||
|
color: #2f6b98 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,130 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
computed,
|
|
||||||
onMounted,
|
|
||||||
onBeforeUnmount,
|
|
||||||
getCurrentInstance
|
|
||||||
} from 'vue';
|
|
||||||
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
|
|
||||||
|
|
||||||
const tagAndTagSpacing = ref(4);
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
|
|
||||||
const emits = defineEmits(['scroll']);
|
|
||||||
const emitScroll = () => {
|
|
||||||
emits('scroll');
|
|
||||||
};
|
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
|
|
||||||
const scrollWrapper = computed(
|
|
||||||
() => proxy?.$refs.scrollContainer.$refs.wrapRef
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
scrollWrapper.value.addEventListener('scroll', emitScroll, true);
|
|
||||||
});
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
scrollWrapper.value.removeEventListener('scroll', emitScroll);
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleScroll(e: WheelEvent) {
|
|
||||||
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
|
|
||||||
scrollWrapper.value.scrollLeft =
|
|
||||||
scrollWrapper.value.scrollLeft + eventDelta / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveToTarget(currentTag: TagView) {
|
|
||||||
const $container = proxy.$refs.scrollContainer.$el;
|
|
||||||
const $containerWidth = $container.offsetWidth;
|
|
||||||
const $scrollWrapper = scrollWrapper.value;
|
|
||||||
|
|
||||||
let firstTag = null;
|
|
||||||
let lastTag = null;
|
|
||||||
|
|
||||||
// find first tag and last tag
|
|
||||||
if (tagsViewStore.visitedViews.length > 0) {
|
|
||||||
firstTag = tagsViewStore.visitedViews[0];
|
|
||||||
lastTag = tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstTag === currentTag) {
|
|
||||||
$scrollWrapper.scrollLeft = 0;
|
|
||||||
} else if (lastTag === currentTag) {
|
|
||||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
|
|
||||||
} else {
|
|
||||||
const tagListDom = document.getElementsByClassName('tags-item');
|
|
||||||
const currentIndex = tagsViewStore.visitedViews.findIndex(
|
|
||||||
item => item === currentTag
|
|
||||||
);
|
|
||||||
let prevTag = null;
|
|
||||||
let nextTag = null;
|
|
||||||
for (const k in tagListDom) {
|
|
||||||
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
|
|
||||||
if (
|
|
||||||
(tagListDom[k] as any).dataset.path ===
|
|
||||||
tagsViewStore.visitedViews[currentIndex - 1].path
|
|
||||||
) {
|
|
||||||
prevTag = tagListDom[k];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(tagListDom[k] as any).dataset.path ===
|
|
||||||
tagsViewStore.visitedViews[currentIndex + 1].path
|
|
||||||
) {
|
|
||||||
nextTag = tagListDom[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the tag's offsetLeft after of nextTag
|
|
||||||
const afterNextTagOffsetLeft =
|
|
||||||
(nextTag as any).offsetLeft +
|
|
||||||
(nextTag as any).offsetWidth +
|
|
||||||
tagAndTagSpacing.value;
|
|
||||||
|
|
||||||
// the tag's offsetLeft before of prevTag
|
|
||||||
const beforePrevTagOffsetLeft =
|
|
||||||
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
|
|
||||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
|
||||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
|
|
||||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
|
||||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
moveToTarget
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<el-scrollbar
|
|
||||||
ref="scrollContainer"
|
|
||||||
:vertical="false"
|
|
||||||
class="scroll-container"
|
|
||||||
@wheel.prevent="handleScroll"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</el-scrollbar>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scroll-container {
|
|
||||||
.el-scrollbar__bar {
|
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar__wrap {
|
|
||||||
height: 49px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-container {
|
|
||||||
white-space: nowrap;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,356 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
getCurrentInstance,
|
|
||||||
nextTick,
|
|
||||||
ref,
|
|
||||||
watch,
|
|
||||||
onMounted,
|
|
||||||
ComponentInternalInstance
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
import path from 'path-browserify';
|
|
||||||
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import ScrollPane from './ScrollPane.vue';
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
|
||||||
import { generateTitle } from '@/utils/i18n';
|
|
||||||
|
|
||||||
import { usePermissionStore } from '@/store/modules/permission';
|
|
||||||
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const permissionStore = usePermissionStore();
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const visible = ref(false);
|
|
||||||
const selectedTag = ref({});
|
|
||||||
const scrollPaneRef = ref();
|
|
||||||
const left = ref(0);
|
|
||||||
const top = ref(0);
|
|
||||||
const affixTags = ref<TagView[]>([]);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
route,
|
|
||||||
() => {
|
|
||||||
addTags();
|
|
||||||
moveToCurrentTag();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
//初始化立即执行
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(visible, value => {
|
|
||||||
if (value) {
|
|
||||||
document.body.addEventListener('click', closeMenu);
|
|
||||||
} else {
|
|
||||||
document.body.removeEventListener('click', closeMenu);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function filterAffixTags(routes: any[], basePath = '/') {
|
|
||||||
let tags: TagView[] = [];
|
|
||||||
routes.forEach(route => {
|
|
||||||
if (route.meta && route.meta.affix) {
|
|
||||||
const tagPath = path.resolve(basePath, route.path);
|
|
||||||
tags.push({
|
|
||||||
fullPath: tagPath,
|
|
||||||
path: tagPath,
|
|
||||||
name: route.name,
|
|
||||||
meta: { ...route.meta }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.children) {
|
|
||||||
const childTags = filterAffixTags(route.children, route.path);
|
|
||||||
if (childTags.length >= 1) {
|
|
||||||
tags = tags.concat(childTags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initTags() {
|
|
||||||
const tags = filterAffixTags(permissionStore.routes);
|
|
||||||
affixTags.value = tags;
|
|
||||||
for (const tag of tags) {
|
|
||||||
// Must have tag name
|
|
||||||
if ((tag as TagView).name) {
|
|
||||||
tagsViewStore.addVisitedView(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTags() {
|
|
||||||
if (route.name) {
|
|
||||||
tagsViewStore.addView(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveToCurrentTag() {
|
|
||||||
nextTick(() => {
|
|
||||||
for (const r of tagsViewStore.visitedViews) {
|
|
||||||
if (r.path === route.path) {
|
|
||||||
scrollPaneRef.value.moveToTarget(r);
|
|
||||||
// when query is different then update
|
|
||||||
if (r.fullPath !== route.fullPath) {
|
|
||||||
tagsViewStore.updateVisitedView(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isActive(tag: TagView) {
|
|
||||||
return tag.path === route.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAffix(tag: TagView) {
|
|
||||||
return tag.meta && tag.meta.affix;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFirstView() {
|
|
||||||
try {
|
|
||||||
return (
|
|
||||||
(selectedTag.value as TagView).fullPath ===
|
|
||||||
tagsViewStore.visitedViews[1].fullPath ||
|
|
||||||
(selectedTag.value as TagView).fullPath === '/index'
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLastView() {
|
|
||||||
try {
|
|
||||||
return (
|
|
||||||
(selectedTag.value as TagView).fullPath ===
|
|
||||||
tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1].fullPath
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshSelectedTag(view: TagView) {
|
|
||||||
tagsViewStore.delCachedView(view);
|
|
||||||
const { fullPath } = view;
|
|
||||||
nextTick(() => {
|
|
||||||
router.replace({ path: '/redirect' + fullPath }).catch(err => {
|
|
||||||
console.warn(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toLastView(visitedViews: TagView[], view?: any) {
|
|
||||||
const latestView = visitedViews.slice(-1)[0];
|
|
||||||
if (latestView && latestView.fullPath) {
|
|
||||||
router.push(latestView.fullPath);
|
|
||||||
} else {
|
|
||||||
// now the default is to redirect to the home page if there is no tags-view,
|
|
||||||
// you can adjust it according to your needs.
|
|
||||||
if (view.name === 'Dashboard') {
|
|
||||||
// to reload home page
|
|
||||||
router.replace({ path: '/redirect' + view.fullPath });
|
|
||||||
} else {
|
|
||||||
router.push('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSelectedTag(view: TagView) {
|
|
||||||
tagsViewStore.delView(view).then((res: any) => {
|
|
||||||
if (isActive(view)) {
|
|
||||||
toLastView(res.visitedViews, view);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeLeftTags() {
|
|
||||||
tagsViewStore.delLeftViews(selectedTag.value).then((res: any) => {
|
|
||||||
if (
|
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
|
||||||
) {
|
|
||||||
toLastView(res.visitedViews);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function closeRightTags() {
|
|
||||||
tagsViewStore.delRightViews(selectedTag.value).then((res: any) => {
|
|
||||||
if (
|
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
|
||||||
) {
|
|
||||||
toLastView(res.visitedViews);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeOtherTags() {
|
|
||||||
router.push(selectedTag.value);
|
|
||||||
tagsViewStore.delOtherViews(selectedTag.value).then(() => {
|
|
||||||
moveToCurrentTag();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeAllTags(view: TagView) {
|
|
||||||
tagsViewStore.delAllViews().then((res: any) => {
|
|
||||||
toLastView(res.visitedViews, view);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openMenu(tag: TagView, e: any) {
|
|
||||||
const menuMinWidth = 105;
|
|
||||||
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
|
|
||||||
const offsetWidth = proxy?.$el.offsetWidth; // container width
|
|
||||||
const maxLeft = offsetWidth - menuMinWidth; // left boundary
|
|
||||||
const l = e.clientX - offsetLeft + 15; // 15: margin right
|
|
||||||
|
|
||||||
if (l > maxLeft) {
|
|
||||||
left.value = maxLeft;
|
|
||||||
} else {
|
|
||||||
left.value = l;
|
|
||||||
}
|
|
||||||
top.value = e.layerY + 10;
|
|
||||||
visible.value = true;
|
|
||||||
selectedTag.value = tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeMenu() {
|
|
||||||
visible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleScroll() {
|
|
||||||
closeMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initTags();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="w-full h-[54px] py-[10px]" style="background:#f0f2f5">
|
|
||||||
<scroll-pane ref="scrollPaneRef" class="tags-container" @scroll="handleScroll">
|
|
||||||
<router-link v-for="tag in tagsViewStore.visitedViews" :key="tag.path" :data-path="tag.path"
|
|
||||||
:class="isActive(tag) ? 'active' : ''" :to="{ path: tag.path, query: tag.query }" class="tags-item"
|
|
||||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }"
|
|
||||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @contextmenu.prevent="openMenu(tag, $event)">
|
|
||||||
{{ generateTitle(tag.name) }}
|
|
||||||
<span v-if="!isAffix(tag)" class="tags-item-remove" @click.prevent.stop="closeSelectedTag(tag)" >
|
|
||||||
<svg-icon icon-class="close" />
|
|
||||||
</span>
|
|
||||||
</router-link>
|
|
||||||
</scroll-pane>
|
|
||||||
|
|
||||||
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="tags-item-menu">
|
|
||||||
<li @click="refreshSelectedTag(selectedTag)">
|
|
||||||
<svg-icon icon-class="refresh" />
|
|
||||||
刷新
|
|
||||||
</li>
|
|
||||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
|
||||||
<svg-icon icon-class="close" />
|
|
||||||
关闭
|
|
||||||
</li>
|
|
||||||
<!-- <li @click="closeOtherTags">
|
|
||||||
<svg-icon icon-class="close_other" />
|
|
||||||
关闭其它
|
|
||||||
</li> -->
|
|
||||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
|
||||||
<svg-icon icon-class="close_left" />
|
|
||||||
关闭左侧
|
|
||||||
</li>
|
|
||||||
<li v-if="!isLastView()" @click="closeRightTags">
|
|
||||||
<svg-icon icon-class="close_right" />
|
|
||||||
关闭右侧
|
|
||||||
</li>
|
|
||||||
<li @click="closeAllTags(selectedTag)">
|
|
||||||
<svg-icon icon-class="close_all" />
|
|
||||||
关闭所有
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.tags-container {
|
|
||||||
|
|
||||||
.tags-item {
|
|
||||||
display: inline-block;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid #d8dce5;
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 4px 10px;
|
|
||||||
margin: 0 0 0 5px;
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: 0px 0px 10px rgb(219 225 236);
|
|
||||||
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: var(--el-color-primary-light-9);
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
border-color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-remove {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
line-height: 10px;
|
|
||||||
// text-align: center;
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 50%;
|
|
||||||
&:hover {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #ee7b7b;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-scrollbar__view) {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
display: -webkit-flex;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags-item-menu {
|
|
||||||
background: #fff;
|
|
||||||
z-index: 99;
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding: 8px 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #eee;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,4 +1,2 @@
|
|||||||
export { default as Navbar } from './Navbar.vue';
|
export { default as Navbar } from './Navbar.vue';
|
||||||
export { default as AppMain } from './AppMain.vue';
|
export { default as AppMain } from './AppMain.vue';
|
||||||
export { default as Settings } from './Settings/index.vue';
|
|
||||||
export { default as TagsView } from './TagsView/index.vue';
|
|
||||||
|
|||||||
@ -1,257 +0,0 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
||||||
import { getMessageList, setMessageStatus, setAllMessageStatus, deleteMessageById } from '@/api/message/index'
|
|
||||||
import Page from '@/components/Pagination/page.vue'
|
|
||||||
const emit = defineEmits(['update:dialogVisible'])
|
|
||||||
const props = defineProps({
|
|
||||||
dialogVisible: {
|
|
||||||
type: Boolean
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const active = ref('0')
|
|
||||||
const loading = ref(false)
|
|
||||||
const tableData = ref([])
|
|
||||||
const tableDataSel = ref([])
|
|
||||||
const isLookOver = ref(false)
|
|
||||||
const info = ref({
|
|
||||||
title: '',
|
|
||||||
createtime: '',
|
|
||||||
content: ''
|
|
||||||
})
|
|
||||||
const queryParams = ref({
|
|
||||||
current: 1,
|
|
||||||
size: 10,
|
|
||||||
title: '',
|
|
||||||
type: '',
|
|
||||||
status: active.value,
|
|
||||||
startDate: '',
|
|
||||||
endDate: ''
|
|
||||||
});
|
|
||||||
const createtime = ref('')
|
|
||||||
const total = ref()
|
|
||||||
const handleClose = () => {
|
|
||||||
emit(
|
|
||||||
'update:dialogVisible',
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function handleSelectionChange(val: any) {
|
|
||||||
tableDataSel.value = val;
|
|
||||||
}
|
|
||||||
function reset() {
|
|
||||||
queryParams.value.title = ''
|
|
||||||
queryParams.value.type = ''
|
|
||||||
queryParams.value.startDate = ''
|
|
||||||
queryParams.value.endDate = ''
|
|
||||||
createtime.value = ''
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
function init() {
|
|
||||||
loading.value = true
|
|
||||||
queryParams.value.status = active.value
|
|
||||||
if(createtime.value) {
|
|
||||||
queryParams.value.startDate = createtime.value[0]
|
|
||||||
queryParams.value.endDate = createtime.value[1]
|
|
||||||
}
|
|
||||||
if(Number(active.value) == 0) {
|
|
||||||
queryParams.value.title = ''
|
|
||||||
queryParams.value.type = ''
|
|
||||||
createtime.value = ''
|
|
||||||
}
|
|
||||||
getMessageList(queryParams.value).then((result) => {
|
|
||||||
loading.value = false
|
|
||||||
tableData.value = result.data.records
|
|
||||||
total.value = result.data.total
|
|
||||||
queryParams.value.size = result.data.size;
|
|
||||||
queryParams.value.current = result.data.current
|
|
||||||
}).catch(() => {
|
|
||||||
loading.value = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function open(row: any) {
|
|
||||||
const id = [row.id]
|
|
||||||
setMessageStatus(id.join(',')).then(() => {
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
isLookOver.value = true
|
|
||||||
info.value = JSON.parse(JSON.stringify(row))
|
|
||||||
}
|
|
||||||
function del(row:any) {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
'确定删除此消息吗',
|
|
||||||
'删除提示',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then( async () => {
|
|
||||||
const id = [] as any[]
|
|
||||||
if(row.id == null) {
|
|
||||||
tableDataSel.value.forEach((element:any) => {
|
|
||||||
id.push(element.id)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
id.push(row.id)
|
|
||||||
}
|
|
||||||
deleteMessageById(id.join(',')).then(() => {
|
|
||||||
ElMessage({
|
|
||||||
message: "删除成功",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function tabClick() {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
'确定将全部消息标记为已阅吗?',
|
|
||||||
'提示信息',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then( async () => {
|
|
||||||
setAllMessageStatus().then(() => {
|
|
||||||
ElMessage({
|
|
||||||
message: "已读成功",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
init()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
defineExpose({
|
|
||||||
init
|
|
||||||
});
|
|
||||||
onMounted(() => {
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<el-dialog class="dialog" draggable v-model="props.dialogVisible" width="1100" top="3vh" append-to-body
|
|
||||||
:before-close="handleClose" :show-close="!isLookOver">
|
|
||||||
<el-tabs v-show="!isLookOver" v-model="active" @tab-change="init" type="border-card">
|
|
||||||
<el-tab-pane label="最新消息">
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="历史消息">
|
|
||||||
<div class="flex justify-between mb-4">
|
|
||||||
<div class="flex items-center ">
|
|
||||||
<el-input v-model="queryParams.title" placeholder="请输入消息标题" clearable style="width: 200px;margin-right:10px" @keyup.enter="init" />
|
|
||||||
<el-select v-model="queryParams.type" clearable default-first-option placeholder="消息类型" style="width: 200px;margin-right:10px" @change="init">
|
|
||||||
<el-option label="定时任务" value="1" />
|
|
||||||
<el-option label="工作流触发" value="2" />
|
|
||||||
<el-option label="人工任务" value="3" />
|
|
||||||
</el-select>
|
|
||||||
<el-date-picker
|
|
||||||
v-model="createtime"
|
|
||||||
type="daterange"
|
|
||||||
range-separator="-"
|
|
||||||
start-placeholder="开始时间"
|
|
||||||
end-placeholder="结束时间"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
@change="init"
|
|
||||||
/>
|
|
||||||
<el-button type="primary" style="margin-left: 10px;" @click="init">搜索</el-button>
|
|
||||||
<el-button @click="reset">重置</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button :disabled="tableDataSel.length==0" type="primary" @click="del">
|
|
||||||
<span>删除</span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-table v-loading="loading" :data="tableData" style="width: 100%; margin-bottom: 20px;overflow:hidden;" height="400px"
|
|
||||||
row-key="id" border @selection-change="handleSelectionChange" default-expand-all
|
|
||||||
:header-cell-style="{ background: 'rgb(250 250 250)', height: '50px' }">
|
|
||||||
<el-table-column v-if="active == '1'" type="selection" align="center" width="50" />
|
|
||||||
<el-table-column type="index" label="序号" align="center" width="70" />
|
|
||||||
<el-table-column prop="title" label="消息标题">
|
|
||||||
<template #default="scope">
|
|
||||||
<span class="title" @click="open(scope.row)">{{ scope.row.title }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="type" label="消息类型" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.type == '1' ? '定时任务' : (scope.row.type == '2' ? '工作流触发' : '人工触发') }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="senderName" label="发送者" width="120"></el-table-column>
|
|
||||||
<el-table-column prop="createtime" label="通知时间" width="180"></el-table-column>
|
|
||||||
<el-table-column label="状态" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<span :style="{color:scope.row.status == '2'?'rgb(55, 196, 122)':scope.row.status == '9'?'rgb(255, 153, 0)':''}">{{ scope.row.status != '1'?'●':''}} {{ scope.row.status == '1' ? '初始创建' : (scope.row.status == '2' ? '消息已阅' : '消息过期') }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="active == '1'" fixed="right" align="center" label="操作" width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
<img src="@/assets/MenuIcon/lbcz_sc.png" alt="" @click="del(scope.row)"
|
|
||||||
title="删除" style="cursor: pointer;margin-left:10px">
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
|
||||||
<Page :total="total" v-model:size="queryParams.size" v-model:current="queryParams.current" @pagination="init()" ></Page>
|
|
||||||
<el-button v-if="active == '0'" type="primary" @click="tabClick">
|
|
||||||
<span>全部已读</span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-tabs>
|
|
||||||
<div v-show="isLookOver" class="p-6">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="font-black" style="color: #000;font-size: 20px;">{{ info.title }}</span>
|
|
||||||
<el-button type="primary" plain @click="isLookOver = false">
|
|
||||||
返回
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<div style="height:50px;line-height: 50px;border-bottom: 1px solid #e4e4e4;">
|
|
||||||
<span style="color: rgb(148, 148, 148);font-size:14px;">{{ info.createtime }}</span>
|
|
||||||
</div>
|
|
||||||
<div style="color:rgb(80, 80, 80);font-size: 14px;padding-top:15px;overflow:auto">
|
|
||||||
{{ info.content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
<style lang="scss">
|
|
||||||
.title{
|
|
||||||
color:rgb(64, 158, 255);
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover{
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
height: 500px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs__content,
|
|
||||||
.el-tabs--border-card {
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs__item {
|
|
||||||
height: 50px !important;
|
|
||||||
line-height: 50px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__headerbtn {
|
|
||||||
z-index: 100;
|
|
||||||
top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__header {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__body {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,134 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, watchEffect } from 'vue';
|
import { computed } from "vue";
|
||||||
import { useWindowSize } from '@vueuse/core';
|
import { AppMain, Navbar } from "./components/index";
|
||||||
import { AppMain, Navbar, Settings, TagsView } from './components/index';
|
|
||||||
import Sidebar from './components/Sidebar/index.vue';
|
|
||||||
import RightPanel from '@/components/RightPanel/index.vue';
|
|
||||||
import Hamburger from '@/components/Hamburger/index.vue';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from "@/store/modules/app";
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
|
||||||
|
|
||||||
const { width } = useWindowSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 响应式布局容器固定宽度
|
|
||||||
*
|
|
||||||
* 大屏(>=1200px)
|
|
||||||
* 中屏(>=992px)
|
|
||||||
* 小屏(>=768px)
|
|
||||||
*/
|
|
||||||
const WIDTH = 992;
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
|
||||||
const showTagsView = computed(() => settingsStore.tagsView);
|
|
||||||
const showSettings = computed(() => settingsStore.showSettings);
|
|
||||||
|
|
||||||
const classObj = computed(() => ({
|
const classObj = computed(() => ({
|
||||||
hideSidebar: !appStore.sidebar.opened,
|
|
||||||
openSidebar: appStore.sidebar.opened,
|
|
||||||
withoutAnimation: appStore.sidebar.withoutAnimation,
|
withoutAnimation: appStore.sidebar.withoutAnimation,
|
||||||
mobile: appStore.device === 'mobile'
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (width.value < WIDTH) {
|
|
||||||
appStore.toggleDevice('mobile');
|
|
||||||
appStore.closeSideBar(true);
|
|
||||||
} else {
|
|
||||||
appStore.toggleDevice('desktop');
|
|
||||||
|
|
||||||
if (width.value >= 1200) {
|
|
||||||
//大屏
|
|
||||||
appStore.openSideBar(true);
|
|
||||||
} else {
|
|
||||||
appStore.closeSideBar(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleOutsideClick() {
|
|
||||||
appStore.closeSideBar(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSideBar() {
|
|
||||||
appStore.toggleSidebar(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="classObj" class="app-wrapper">
|
<div :class="classObj" class="app-wrapper">
|
||||||
<!-- 手机设备 && 侧边栏 → 显示遮罩层 -->
|
<navbar />
|
||||||
<div
|
<div class="main-container">
|
||||||
class="drawer-bg"
|
|
||||||
v-if="classObj.mobile && classObj.openSidebar"
|
|
||||||
@click="handleOutsideClick"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<Sidebar class="sidebar-container" />
|
|
||||||
<navbar :class="{ 'fixed-header': fixedHeader }" />
|
|
||||||
<div v-show="fixedHeader" style="height:60px"></div>
|
|
||||||
<hamburger
|
|
||||||
:is-active="appStore.sidebar.opened"
|
|
||||||
@toggleClick="toggleSideBar"
|
|
||||||
/>
|
|
||||||
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
|
||||||
<tags-view v-if="showTagsView" />
|
|
||||||
|
|
||||||
<!--主页面-->
|
<!--主页面-->
|
||||||
<app-main />
|
<app-main />
|
||||||
|
|
||||||
<!-- 设置面板 -->
|
|
||||||
<RightPanel v-if="showSettings">
|
|
||||||
<settings />
|
|
||||||
</RightPanel>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@use '@/styles/mixin.scss' as mixin;
|
@use '@/styles/mixin.scss' as mixin;
|
||||||
@use '@/styles/variables.module.scss' as variables;
|
|
||||||
|
|
||||||
.app-wrapper {
|
.app-wrapper {
|
||||||
// @include clearfix;
|
// @include clearfix;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 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: 55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 98;
|
|
||||||
width: 100%;
|
|
||||||
transition: width 0.28s;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
.hideSidebar .fixed-header {
|
|
||||||
// width: calc(100% - 54px);
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
.mobile .fixed-header {
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -8,6 +8,8 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|||||||
import Pagination from '@/components/Pagination/index.vue';
|
import Pagination from '@/components/Pagination/index.vue';
|
||||||
import '@/permission';
|
import '@/permission';
|
||||||
|
|
||||||
|
import Antd from 'ant-design-vue'
|
||||||
|
import 'ant-design-vue/dist/reset.css' // Ant Design 全局样式重置
|
||||||
// 引入svg注册脚本
|
// 引入svg注册脚本
|
||||||
import 'virtual:svg-icons-register';
|
import 'virtual:svg-icons-register';
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ app
|
|||||||
.component('Pagination', Pagination)
|
.component('Pagination', Pagination)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(ElementPlus)
|
.use(ElementPlus)
|
||||||
|
.use(Antd)
|
||||||
.use(WujieVue)
|
.use(WujieVue)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.mount('#app');
|
.mount('#app');
|
||||||
|
|||||||
@ -5,20 +5,35 @@ import { usePermissionStoreHook } from '@/store/modules/permission';
|
|||||||
|
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
NProgress.configure({ showSpinner: false }); // 进度条
|
NProgress.configure({ showSpinner: false });
|
||||||
|
|
||||||
const permissionStore = usePermissionStoreHook();
|
const permissionStore = usePermissionStoreHook();
|
||||||
|
|
||||||
// 白名单路由
|
// 白名单路由
|
||||||
const whiteList = ['/login'];
|
const whiteList = ['/login'];
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
// 查找第一个可用路由
|
||||||
whiteList.push('/process/antd-demo');
|
function findFirstAvailableRoute(routes: RouteRecordRaw[]): string | undefined {
|
||||||
|
for (const route of routes) {
|
||||||
|
if (route.meta?.hidden) continue;
|
||||||
|
|
||||||
|
if (route.children?.length > 0) {
|
||||||
|
const child = route.children[0];
|
||||||
|
// 优先使用 opturl 或 path
|
||||||
|
const targetPath = child.opturl || child.path;
|
||||||
|
return targetPath?.startsWith('/') ? targetPath : `/${targetPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPath = route.opturl || route.path;
|
||||||
|
return targetPath;
|
||||||
|
}
|
||||||
|
return '/404';
|
||||||
}
|
}
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
const userStore = useUserStoreHook();
|
const userStore = useUserStoreHook();
|
||||||
|
|
||||||
if (userStore.Token) {
|
if (userStore.Token) {
|
||||||
// 登录成功,跳转到首页
|
// 登录成功,跳转到首页
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
@ -26,9 +41,19 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
NProgress.done();
|
NProgress.done();
|
||||||
} else {
|
} else {
|
||||||
const hasGetUserInfo = userStore.roles.length > 0;
|
const hasGetUserInfo = userStore.roles.length > 0;
|
||||||
// const hasGetUserInfo = true;
|
|
||||||
if (hasGetUserInfo) {
|
if (hasGetUserInfo) {
|
||||||
|
// 已获取用户信息,检查路由匹配
|
||||||
if (to.matched.length === 0) {
|
if (to.matched.length === 0) {
|
||||||
|
// 路由未匹配,可能是访问根路径
|
||||||
|
if (to.path === '/') {
|
||||||
|
const firstRoute = findFirstAvailableRoute(permissionStore.routes);
|
||||||
|
if (firstRoute) {
|
||||||
|
next(firstRoute);
|
||||||
|
NProgress.done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
from.name ? next({ name: from.name as any }) : next('/401');
|
from.name ? next({ name: from.name as any }) : next('/401');
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
@ -36,15 +61,25 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const { roles } = await userStore.getInfo();
|
const { roles } = await userStore.getInfo();
|
||||||
const accessRoutes: RouteRecordRaw[] =
|
const accessRoutes: RouteRecordRaw[] = await permissionStore.generateRoutes(roles);
|
||||||
await permissionStore.generateRoutes(roles);
|
|
||||||
|
|
||||||
accessRoutes.forEach((route: any) => {
|
accessRoutes.forEach((route: any) => {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 关键:如果是根路径,加载完路由后跳转到第一个可用路由
|
||||||
|
if (to.path === '/') {
|
||||||
|
const firstRoute = findFirstAvailableRoute(accessRoutes);
|
||||||
|
if (firstRoute) {
|
||||||
|
next(firstRoute);
|
||||||
|
NProgress.done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
next({ ...to, replace: true });
|
next({ ...to, replace: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 移除 token 并跳转登录页
|
console.log(error);
|
||||||
await userStore.resetToken();
|
await userStore.resetToken();
|
||||||
next(`/login?redirect=${to.path}`);
|
next(`/login?redirect=${to.path}`);
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
|
|||||||
@ -26,48 +26,11 @@ export const constantRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/error-page/404.vue'),
|
component: () => import('@/views/error-page/404.vue'),
|
||||||
meta: { hidden: true }
|
meta: { hidden: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
{
|
path: '/401',
|
||||||
path: '/',
|
component: () => import('@/views/error-page/401.vue'),
|
||||||
component: Layout,
|
meta: { hidden: true }
|
||||||
redirect: '/dashboard',
|
},
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'dashboard',
|
|
||||||
opturl: '/dashboard',
|
|
||||||
component: () => import('@/views/dashboard/index.vue'),
|
|
||||||
name: '首页',
|
|
||||||
icon: 'homepage',
|
|
||||||
meta: { title: 'dashboard', icon: 'homepage', affix: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'personalCenter',
|
|
||||||
component: () => import('@/views/system/user/personalCenter.vue'),
|
|
||||||
name: '个人中心',
|
|
||||||
meta: { title: 'personalCenter',hidden: true, icon: 'personalCenter' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '401',
|
|
||||||
component: () => import('@/views/error-page/401.vue'),
|
|
||||||
meta: { hidden: true }
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/process',
|
|
||||||
opturl: '/process',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/process/antd-demo',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'antd-demo',
|
|
||||||
opturl: '/process/antd-demo',
|
|
||||||
component: () => import('@/views/process/antd-demo.vue'),
|
|
||||||
name: 'AntD Demo',
|
|
||||||
meta: { title: 'AntD Demo' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 创建路由
|
// 创建路由
|
||||||
|
|||||||
@ -10,7 +10,7 @@ interface DefaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultSettings: DefaultSettings = {
|
const defaultSettings: DefaultSettings = {
|
||||||
title: '公司开发平台框架',
|
title: '水电水利建设项目全过程环境管理信息平台',
|
||||||
showSettings: false,
|
showSettings: false,
|
||||||
tagsView: true,
|
tagsView: true,
|
||||||
fixedHeader: true,
|
fixedHeader: true,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
getSidebarStatus,
|
|
||||||
setSidebarStatus,
|
setSidebarStatus,
|
||||||
getSize,
|
getSize,
|
||||||
setSize,
|
setSize,
|
||||||
@ -24,7 +23,12 @@ export enum SizeType {
|
|||||||
large,
|
large,
|
||||||
small
|
small
|
||||||
}
|
}
|
||||||
|
export const usetTheme = {
|
||||||
|
token: {
|
||||||
|
colorPrimary: '#1890ff',
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
// setup
|
// setup
|
||||||
export const useAppStore = defineStore('app', () => {
|
export const useAppStore = defineStore('app', () => {
|
||||||
// state
|
// state
|
||||||
@ -32,7 +36,6 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
const size = ref(getSize() || 'default');
|
const size = ref(getSize() || 'default');
|
||||||
const language = ref(getLanguage());
|
const language = ref(getLanguage());
|
||||||
const sidebar = reactive({
|
const sidebar = reactive({
|
||||||
opened: getSidebarStatus() !== 'closed',
|
|
||||||
withoutAnimation: false
|
withoutAnimation: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,32 +47,6 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// actions
|
|
||||||
function toggleSidebar(withoutAnimation: boolean) {
|
|
||||||
sidebar.opened = !sidebar.opened;
|
|
||||||
sidebar.withoutAnimation = withoutAnimation;
|
|
||||||
if (sidebar.opened) {
|
|
||||||
setSidebarStatus('opened');
|
|
||||||
} else {
|
|
||||||
setSidebarStatus('closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSideBar(withoutAnimation: boolean) {
|
|
||||||
sidebar.opened = false;
|
|
||||||
sidebar.withoutAnimation = withoutAnimation;
|
|
||||||
setSidebarStatus('closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSideBar(withoutAnimation: boolean) {
|
|
||||||
sidebar.opened = true;
|
|
||||||
sidebar.withoutAnimation = withoutAnimation;
|
|
||||||
setSidebarStatus('opened');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleDevice(val: string) {
|
|
||||||
device.value = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeSize(val: string) {
|
function changeSize(val: string) {
|
||||||
size.value = val;
|
size.value = val;
|
||||||
@ -87,11 +64,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
language,
|
language,
|
||||||
locale,
|
locale,
|
||||||
size,
|
size,
|
||||||
toggleDevice,
|
|
||||||
changeSize,
|
changeSize,
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
toggleSidebar,
|
|
||||||
closeSideBar,
|
|
||||||
openSideBar
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,24 +8,18 @@ import { ref } from 'vue';
|
|||||||
const modules = import.meta.glob('../../views/**/**.vue');
|
const modules = import.meta.glob('../../views/**/**.vue');
|
||||||
export const Layout = () => import('@/layout/index.vue');
|
export const Layout = () => import('@/layout/index.vue');
|
||||||
|
|
||||||
// const hasPermission = (roles: string[], route: RouteRecordRaw) => {
|
|
||||||
// if (route.meta && route.meta.roles) {
|
|
||||||
// if (roles.includes('ROOT')) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// return roles.some(role => {
|
|
||||||
// if (route.meta?.roles !== undefined) {
|
|
||||||
// return (route.meta.roles as string[]).includes(role);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
||||||
const res: RouteRecordRaw[] = [];
|
const res: RouteRecordRaw[] = [];
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
const tmp = { ...route } as any;
|
const tmp = { ...route } as any;
|
||||||
|
// ✅ 保存原始名称到 meta,用于菜单显示
|
||||||
|
tmp.meta = {
|
||||||
|
...tmp.meta,
|
||||||
|
title: tmp.name || tmp.menuName, // 原始名称用于显示
|
||||||
|
};
|
||||||
|
// ✅ name 使用路径生成唯一值
|
||||||
|
tmp.name = tmp.path || tmp.opturl;
|
||||||
|
|
||||||
// if (hasPermission(roles, tmp)) {
|
// if (hasPermission(roles, tmp)) {
|
||||||
tmp.path = tmp.opturl;
|
tmp.path = tmp.opturl;
|
||||||
if (tmp.type == '0') {
|
if (tmp.type == '0') {
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import defaultSettings from '../../settings';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useStorage } from '@vueuse/core';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题类型
|
|
||||||
*/
|
|
||||||
export enum ThemeType {
|
|
||||||
light,
|
|
||||||
dark
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 布局类型
|
|
||||||
*/
|
|
||||||
export enum LayoutType {
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
mix
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSettingsStore = defineStore('setting', () => {
|
|
||||||
// state
|
|
||||||
const showSettings = ref<boolean>(defaultSettings.showSettings);
|
|
||||||
const tagsView = useStorage<boolean>('tagsView', defaultSettings.tagsView);
|
|
||||||
const fixedHeader = ref<boolean>(defaultSettings.fixedHeader);
|
|
||||||
const sidebarLogo = ref<boolean>(defaultSettings.sidebarLogo);
|
|
||||||
|
|
||||||
const layout = useStorage<string>('layout', defaultSettings.layout);
|
|
||||||
|
|
||||||
// actions
|
|
||||||
function changeSetting(param: { key: string; value: any }) {
|
|
||||||
const { key, value } = param;
|
|
||||||
switch (key) {
|
|
||||||
case 'showSettings':
|
|
||||||
showSettings.value = value;
|
|
||||||
break;
|
|
||||||
case 'fixedHeader':
|
|
||||||
fixedHeader.value = value;
|
|
||||||
break;
|
|
||||||
case 'tagsView':
|
|
||||||
tagsView.value = value;
|
|
||||||
break;
|
|
||||||
case 'sidevarLogo':
|
|
||||||
sidebarLogo.value = value;
|
|
||||||
break;
|
|
||||||
case 'layout':
|
|
||||||
layout.value = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
showSettings,
|
|
||||||
tagsView,
|
|
||||||
fixedHeader,
|
|
||||||
sidebarLogo,
|
|
||||||
layout,
|
|
||||||
changeSetting
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@ -4,56 +4,4 @@
|
|||||||
--el-color-primary-dark: #0d84ff;
|
--el-color-primary-dark: #0d84ff;
|
||||||
// --el-font-size-base: 16px !important;
|
// --el-font-size-base: 16px !important;
|
||||||
}
|
}
|
||||||
.el-button--large, .el-input--large, .el-table--large, .el-form--large, .el-select__tags-text{
|
|
||||||
font-size: 16px !important;
|
|
||||||
.el-form-item__label{
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
--el-font-size-base: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 覆盖 element-plus 的样式
|
|
||||||
.el-breadcrumb__inner,
|
|
||||||
.el-breadcrumb__inner a {
|
|
||||||
font-weight: 400 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-upload {
|
|
||||||
input[type='file'] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-upload__input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dropdown
|
|
||||||
.el-dropdown-menu {
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// to fix el-date-picker css style
|
|
||||||
.el-range-separator {
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选中行背景色值
|
|
||||||
.el-table__body tr.current-row td {
|
|
||||||
background-color: #e1f3d8b5 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// card 的header统一高度
|
|
||||||
.el-card__header {
|
|
||||||
height: 60px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格表头和表体未对齐
|
|
||||||
.el-table__header col[name='gutter'] {
|
|
||||||
display: table-cell !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +1,34 @@
|
|||||||
@use 'src/styles/variables.module' as variables;
|
|
||||||
@use 'src/styles/element-plus' as element-plus;
|
@use 'src/styles/element-plus' as element-plus;
|
||||||
@use './sidebar' as sidebar;
|
@use './sidebar' as sidebar;
|
||||||
@use './tailwind' as tailwind;
|
@use './tailwind' as tailwind;
|
||||||
|
|
||||||
html,body,#app{
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
font-family: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Webkit 浏览器 (Chrome, Safari, Edge) */
|
||||||
|
body::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
// main-container global css
|
// main-container global css
|
||||||
.app-container {
|
.app-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search{
|
.search {
|
||||||
padding: 18px 0 0 10px;
|
padding: 18px 0 0 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
box-shadow: 6px 2px 6px #CCC;
|
box-shadow: 6px 2px 6px #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg{
|
svg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
@ -28,8 +36,8 @@ svg{
|
|||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
margin: auto !important;
|
margin: auto !important;
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
top: 0 ;
|
top: 0;
|
||||||
left: 0 ;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: max-content;
|
height: max-content;
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
@use 'src/styles/variables.module' as variables;
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
vertical-align: text-bottom !important;
|
vertical-align: text-bottom !important;
|
||||||
}
|
}
|
||||||
@ -7,249 +5,13 @@ svg {
|
|||||||
.main-container {
|
.main-container {
|
||||||
// min-height: 100%;
|
// min-height: 100%;
|
||||||
transition: margin-left 0.28s;
|
transition: margin-left 0.28s;
|
||||||
margin-left: variables.$sideBarWidth;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
transition: width 0.28s;
|
|
||||||
width: variables.$sideBarWidth !important;
|
|
||||||
background-color: variables.$menuBg;
|
|
||||||
height: calc(100vh - 60px);
|
|
||||||
padding-top: 15px;
|
|
||||||
position: absolute;
|
|
||||||
top: 60px;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 98;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
// reset element-ui css
|
|
||||||
.horizontal-collapse-transition {
|
|
||||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out,
|
|
||||||
0s padding-right ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar-wrapper {
|
|
||||||
overflow-x: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar__bar.is-vertical {
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-logo {
|
|
||||||
.el-scrollbar {
|
|
||||||
height: calc(100% - 50px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-horizontal {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-left: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu {
|
|
||||||
border: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// menu hover
|
|
||||||
.submenu-title-noDropdown,
|
|
||||||
.el-sub-menu__title {
|
|
||||||
&:hover {
|
|
||||||
color: variables.$menuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// .is-active > .el-sub-menu__title {
|
|
||||||
// color: variables.$subMenuActiveText !important;
|
|
||||||
// }
|
|
||||||
|
|
||||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
|
||||||
& .el-sub-menu .el-menu-item {
|
|
||||||
min-width: variables.$sideBarWidth !important;
|
|
||||||
background-color: variables.$subMenuBg !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: variables.$subMenuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.el-menu-item.is-active {
|
|
||||||
border-right: 3px solid variables.$subMenuHover;
|
|
||||||
background: #e8f3ff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideSidebar {
|
|
||||||
.sidebar-container {
|
|
||||||
width: 54px !important;
|
|
||||||
.svg-icon {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
margin-left: 54px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submenu-title-noDropdown {
|
|
||||||
padding: 0 !important;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.el-tooltip {
|
|
||||||
padding: 0 !important;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-left: 19px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-sub-menu {
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
& > .el-sub-menu__title {
|
|
||||||
padding: 0 !important;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-left: 19px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-sub-menu__icon-arrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu--collapse {
|
|
||||||
.el-sub-menu {
|
|
||||||
& > .el-sub-menu__title {
|
|
||||||
& > span {
|
|
||||||
height: 0;
|
|
||||||
width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
visibility: hidden;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu--collapse .el-menu .el-sub-menu {
|
|
||||||
min-width: variables.$sideBarWidth !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mobile responsive
|
|
||||||
.mobile {
|
|
||||||
.main-container {
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
transition: transform 0.28s;
|
|
||||||
width: variables.$sideBarWidth !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.hideSidebar {
|
|
||||||
.sidebar-container {
|
|
||||||
pointer-events: none;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
transform: translate3d(- variables.$sideBarWidth, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.withoutAnimation {
|
|
||||||
.main-container,
|
|
||||||
.sidebar-container {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// when menu collapsed
|
body[layout='mix'] {
|
||||||
.el-menu--vertical {
|
.svg-icon {
|
||||||
& > .el-menu {
|
|
||||||
.svg-icon {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-left: -2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nest-menu .el-sub-menu > .el-sub-menu__title,
|
|
||||||
.el-menu-item {
|
|
||||||
&:hover {
|
|
||||||
background-color: #ffffff;
|
|
||||||
// you can use variables.$subMenuHover
|
|
||||||
color: variables.$menuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the scroll bar appears when the subMenu is too long
|
|
||||||
> .el-menu--popup {
|
|
||||||
max-height: 100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track-piece {
|
|
||||||
background: #d3dce6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: #99a9bf;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body[layout="mix"] {
|
|
||||||
|
|
||||||
.horizontal-header{
|
|
||||||
.el-menu-item{
|
|
||||||
height: 50px!important;
|
|
||||||
line-height: 50px!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-sub-menu__title {
|
|
||||||
background-color: #001529!important;
|
|
||||||
height: 50px!important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.horizontal-header-right>div {
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
.svg-icon{
|
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
:export {
|
|
||||||
menuText: #409eff;
|
|
||||||
menuActiveText: #409eff;
|
|
||||||
subMenuActiveText: #409eff;
|
|
||||||
menuBg: #ffffff;
|
|
||||||
menuHover: #409eff;
|
|
||||||
subMenuBg: #ffffff;
|
|
||||||
subMenuHover: #409eff;
|
|
||||||
sideBarWidth: 210px;
|
|
||||||
}
|
|
||||||
/*# sourceMappingURL=variables.module.css.map */
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"mappings": "AAeA,AAAA,OAAO,CAAC;EACN,QAAQ,EAfC,OAAO;EAgBhB,cAAc,EAfC,OAAO;EAgBtB,iBAAiB,EAfC,OAAO;EAgBzB,MAAM,EAdC,OAAO;EAed,SAAS,EAdC,OAAO;EAejB,SAAS,EAbC,OAAO;EAcjB,YAAY,EAbC,OAAO;EAcpB,YAAY,EAZC,KAAK;CAanB",
|
|
||||||
"sources": [
|
|
||||||
"variables.module.scss"
|
|
||||||
],
|
|
||||||
"names": [],
|
|
||||||
"file": "variables.module.css"
|
|
||||||
}
|
|
||||||
@ -1,25 +1,17 @@
|
|||||||
// sidebar
|
$main-menu-color: #fff;
|
||||||
$menuText: #505050;
|
$main-menu-color-active: #00e6fc;
|
||||||
$menuActiveText: #409eff;
|
$main-menu-bg-color-active: #003e6e;
|
||||||
$subMenuActiveText: #409eff;
|
|
||||||
|
|
||||||
$menuBg: #ffffff;
|
$layout-header-height: 60px;
|
||||||
$menuHover: #409eff;
|
$locationbar-height: 50px;
|
||||||
|
|
||||||
$subMenuBg: #ffffff;
|
|
||||||
$subMenuHover: #409eff;
|
|
||||||
|
|
||||||
$sideBarWidth: 210px;
|
|
||||||
|
|
||||||
// the :export directive is the magic sauce for webpack
|
// the :export directive is the magic sauce for webpack
|
||||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||||
:export {
|
:export {
|
||||||
menuText: $menuText;
|
main-menu-color: $main-menu-color;
|
||||||
menuActiveText: $menuActiveText;
|
main-menu-color-active: $main-menu-color-active;
|
||||||
subMenuActiveText: $subMenuActiveText;
|
main-menu-bg-color-active: $main-menu-bg-color-active;
|
||||||
menuBg: $menuBg;
|
|
||||||
menuHover: $menuHover;
|
layout-header-height: $layout-header-height;
|
||||||
subMenuBg: $subMenuBg;
|
locationbar-height: $locationbar-height;
|
||||||
subMenuHover: $subMenuHover;
|
|
||||||
sideBarWidth: $sideBarWidth;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,12 +21,6 @@ export const localStorage = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 侧边栏状态(显示/隐藏)
|
|
||||||
const SidebarStatusKey = 'sidebarStatus';
|
|
||||||
export function getSidebarStatus() {
|
|
||||||
return localStorage.get(SidebarStatusKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setSidebarStatus(sidebarStatus: string) {
|
export function setSidebarStatus(sidebarStatus: string) {
|
||||||
localStorage.set(SidebarStatusKey, sidebarStatus);
|
localStorage.set(SidebarStatusKey, sidebarStatus);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { useUserStoreHook } from '@/store/modules/user';
|
import { useUserStoreHook } from '@/store/modules/user';
|
||||||
|
|
||||||
@ -33,18 +33,14 @@ service.interceptors.request.use(
|
|||||||
service.interceptors.response.use(
|
service.interceptors.response.use(
|
||||||
(response: AxiosResponse) => {
|
(response: AxiosResponse) => {
|
||||||
const { status, msg } = response;
|
const { status, msg } = response;
|
||||||
|
console.log(msg)
|
||||||
|
console.log(response);
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
if (response.data.code == 401) {
|
if (response.data.code == 401) {
|
||||||
ElMessage({
|
message.error(response.data.msg||'请求失败');
|
||||||
message: '用户名或密码错误,请重试!',
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}else if(response.data.code == 1){
|
}else if(response.data.code == 1){
|
||||||
ElMessage({
|
message.error(response.data.msg);
|
||||||
message: response.data.msg,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
@ -53,10 +49,7 @@ service.interceptors.response.use(
|
|||||||
if (response.data instanceof ArrayBuffer) {
|
if (response.data instanceof ArrayBuffer) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
ElMessage({
|
message.error( msg || '系统出错');
|
||||||
message: msg || '系统出错',
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
return Promise.reject(new Error(msg || 'Error'));
|
return Promise.reject(new Error(msg || 'Error'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -65,18 +58,18 @@ service.interceptors.response.use(
|
|||||||
const { status, msg } = error.response.data;
|
const { status, msg } = error.response.data;
|
||||||
// token 过期,重新登录
|
// token 过期,重新登录
|
||||||
if (status === '403') {
|
if (status === '403') {
|
||||||
ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', {
|
Modal.confirm({
|
||||||
confirmButtonText: 'OK',
|
title: "提示",
|
||||||
type: 'warning'
|
content: "当前页面已失效,请重新登录",
|
||||||
}).then(() => {
|
okText: "确定",
|
||||||
localStorage.clear();
|
cancelButtonProps: { style: { display: "none" } },
|
||||||
window.location.href = '/';
|
onOk: () => {
|
||||||
|
localStorage.clear();
|
||||||
|
window.location.href = '/';
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ElMessage({
|
message.error(msg || '当前页面已失效');
|
||||||
message: msg || '当前页面已失效',
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(error.message);
|
return Promise.reject(error.message);
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by PanJiaChen on 16/11/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} path
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
export function isExternal(path: string) {
|
|
||||||
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
|
|
||||||
return isExternal;
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Editor from '@/components/WangEditor/index.vue';
|
|
||||||
import { ElForm } from 'element-plus';
|
|
||||||
import { reactive, ref, toRefs } from 'vue';
|
|
||||||
|
|
||||||
const dataFormRef = ref(ElForm);
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
formData: {
|
|
||||||
content: '初始内容'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { formData } = toRefs(state);
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-form ref="dataFormRef" :model="formData">
|
|
||||||
<editor v-model="formData.content" style="height: 600px" />
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import SingleUpload from '@/components/Upload/SingleUpload.vue';
|
|
||||||
import MultiUpload from '@/components/Upload/MultiUpload.vue';
|
|
||||||
import { ElForm } from 'element-plus';
|
|
||||||
import { reactive, ref, toRefs } from 'vue';
|
|
||||||
|
|
||||||
const dataFormRef = ref(ElForm);
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
formData: {
|
|
||||||
picUrl:
|
|
||||||
'https://oss.youlai.tech/default/2022/11/20/18e206dae97b40329661537d1e433639.jpg',
|
|
||||||
picUrls: [
|
|
||||||
'https://oss.youlai.tech/default/2022/11/20/8af5567816094545b53e76b38ae9c974.webp',
|
|
||||||
'https://oss.youlai.tech/default/2022/11/20/13dbfd7feaf848c2acec2b21675eb9d3.webp'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { formData } = toRefs(state);
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-form ref="dataFormRef" :model="formData">
|
|
||||||
<el-form-item label="单图上传">
|
|
||||||
<single-upload v-model="formData.picUrl"></single-upload>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="多图上传">
|
|
||||||
<multi-upload v-model="formData.picUrls"></multi-upload>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,174 +0,0 @@
|
|||||||
<!-- 线 + 柱混合图 -->
|
|
||||||
<template>
|
|
||||||
<div :id="id" :class="className" :style="{ height, width }" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
nextTick,
|
|
||||||
onActivated,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onDeactivated,
|
|
||||||
onMounted,
|
|
||||||
} from 'vue';
|
|
||||||
import { init, EChartsOption } from 'echarts';
|
|
||||||
import * as echarts from 'echarts';
|
|
||||||
import resize from '@/utils/resize';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: 'barChart',
|
|
||||||
},
|
|
||||||
className: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
|
||||||
|
|
||||||
function initChart() {
|
|
||||||
const barChart = init(document.getElementById(props.id) as HTMLDivElement);
|
|
||||||
|
|
||||||
barChart.setOption({
|
|
||||||
title: {
|
|
||||||
show: true,
|
|
||||||
text: '业绩总览',
|
|
||||||
x: 'center',
|
|
||||||
padding: 15,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#337ecc',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '2%',
|
|
||||||
right: '2%',
|
|
||||||
bottom: '10%',
|
|
||||||
containLabel: true,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'cross',
|
|
||||||
crossStyle: {
|
|
||||||
color: '#999',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
x: 'center',
|
|
||||||
y: 'bottom',
|
|
||||||
data: ['收入', '毛利润', '收入增长率', '利润增长率'],
|
|
||||||
},
|
|
||||||
xAxis: [
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
data: [ '浙江', '北京', '上海', '广东','深圳'],
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
yAxis: [
|
|
||||||
{
|
|
||||||
type: 'value',
|
|
||||||
min: 0,
|
|
||||||
max: 10000,
|
|
||||||
interval: 2000,
|
|
||||||
axisLabel: {
|
|
||||||
formatter: '{value} ',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'value',
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
interval: 20,
|
|
||||||
axisLabel: {
|
|
||||||
formatter: '{value}%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '收入',
|
|
||||||
type: 'bar',
|
|
||||||
data: [7000, 7100, 7200, 7300,7400],
|
|
||||||
barWidth: 20,
|
|
||||||
itemStyle: {
|
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
||||||
{ offset: 0, color: '#83bff6' },
|
|
||||||
{ offset: 0.5, color: '#188df0' },
|
|
||||||
{ offset: 1, color: '#188df0' },
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '毛利润',
|
|
||||||
type: 'bar',
|
|
||||||
data: [ 8000,8200, 8400, 8600, 8800],
|
|
||||||
barWidth: 20,
|
|
||||||
itemStyle: {
|
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
||||||
{ offset: 0, color: '#25d73c' },
|
|
||||||
{ offset: 0.5, color: '#1bc23d' },
|
|
||||||
{ offset: 1, color: '#179e61' },
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '收入增长率',
|
|
||||||
type: 'line',
|
|
||||||
yAxisIndex: 1,
|
|
||||||
data: [ 60,65, 70, 75, 80],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#67C23A',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '利润增长率',
|
|
||||||
type: 'line',
|
|
||||||
yAxisIndex: 1,
|
|
||||||
data: [ 70,75, 80, 85, 90],
|
|
||||||
itemStyle: {
|
|
||||||
color: '#409EFF',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as EChartsOption);
|
|
||||||
chart.value = barChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
beforeDestroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
onActivated(() => {
|
|
||||||
activated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onDeactivated(() => {
|
|
||||||
deactivated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
mounted();
|
|
||||||
nextTick(() => {
|
|
||||||
initChart();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
<!-- 漏斗图 -->
|
|
||||||
<template>
|
|
||||||
<div :id="id" :class="className" :style="{ height, width }" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
nextTick,
|
|
||||||
onActivated,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onDeactivated,
|
|
||||||
onMounted
|
|
||||||
} from 'vue';
|
|
||||||
import { init, EChartsOption } from 'echarts';
|
|
||||||
import resize from '@/utils/resize';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: 'funnelChart'
|
|
||||||
},
|
|
||||||
className: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
|
||||||
|
|
||||||
function initChart() {
|
|
||||||
const funnelChart = init(document.getElementById(props.id) as HTMLDivElement);
|
|
||||||
|
|
||||||
funnelChart.setOption({
|
|
||||||
title: {
|
|
||||||
show: true,
|
|
||||||
text: '订单线索转化漏斗图',
|
|
||||||
x: 'center',
|
|
||||||
padding: 15,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#337ecc'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '2%',
|
|
||||||
right: '2%',
|
|
||||||
bottom: '10%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
x: 'center',
|
|
||||||
y: 'bottom',
|
|
||||||
data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
|
|
||||||
},
|
|
||||||
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Funnel',
|
|
||||||
type: 'funnel',
|
|
||||||
left: '20%',
|
|
||||||
top: 60,
|
|
||||||
bottom: 60,
|
|
||||||
width: '60%',
|
|
||||||
sort: 'descending',
|
|
||||||
gap: 2,
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: 'inside'
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
length: 10,
|
|
||||||
lineStyle: {
|
|
||||||
width: 1,
|
|
||||||
type: 'solid'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
borderColor: '#fff',
|
|
||||||
borderWidth: 1
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
label: {
|
|
||||||
fontSize: 20
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{ value: 60, name: 'Visit' },
|
|
||||||
{ value: 40, name: 'Inquiry' },
|
|
||||||
{ value: 20, name: 'Order' },
|
|
||||||
{ value: 80, name: 'Click' },
|
|
||||||
{ value: 100, name: 'Show' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as EChartsOption);
|
|
||||||
chart.value = funnelChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
beforeDestroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
onActivated(() => {
|
|
||||||
activated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onDeactivated(() => {
|
|
||||||
deactivated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
mounted();
|
|
||||||
nextTick(() => {
|
|
||||||
initChart();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
<!-- 饼图 -->
|
|
||||||
<template>
|
|
||||||
<div :id="id" :class="className" :style="{ height, width }" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
nextTick,
|
|
||||||
onActivated,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onDeactivated,
|
|
||||||
onMounted
|
|
||||||
} from 'vue';
|
|
||||||
import { init, EChartsOption } from 'echarts';
|
|
||||||
import resize from '@/utils/resize';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: 'pieChart'
|
|
||||||
},
|
|
||||||
className: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
|
||||||
|
|
||||||
function initChart() {
|
|
||||||
const pieChart = init(document.getElementById(props.id) as HTMLDivElement);
|
|
||||||
|
|
||||||
pieChart.setOption({
|
|
||||||
title: {
|
|
||||||
show: true,
|
|
||||||
text: '产品分类总览',
|
|
||||||
x: 'center',
|
|
||||||
padding: 15,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#337ecc'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '2%',
|
|
||||||
right: '2%',
|
|
||||||
bottom: '10%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
top: 'bottom'
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Nightingale Chart',
|
|
||||||
type: 'pie',
|
|
||||||
radius: [50, 130],
|
|
||||||
center: ['50%', '50%'],
|
|
||||||
roseType: 'area',
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 1,
|
|
||||||
color: function (params: any) {
|
|
||||||
//自定义颜色
|
|
||||||
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
|
|
||||||
return colorList[params.dataIndex];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{ value: 26, name: '家用电器' },
|
|
||||||
{ value: 27, name: '户外运动' },
|
|
||||||
{ value: 24, name: '汽车用品' },
|
|
||||||
{ value: 23, name: '手机数码' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as EChartsOption);
|
|
||||||
|
|
||||||
chart.value = pieChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
beforeDestroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
onActivated(() => {
|
|
||||||
activated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onDeactivated(() => {
|
|
||||||
deactivated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
mounted();
|
|
||||||
nextTick(() => {
|
|
||||||
initChart();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
<!-- 雷达图 -->
|
|
||||||
<template>
|
|
||||||
<div :id="id" :class="className" :style="{ height, width }" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
nextTick,
|
|
||||||
onActivated,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onDeactivated,
|
|
||||||
onMounted
|
|
||||||
} from 'vue';
|
|
||||||
import { init, EChartsOption } from 'echarts';
|
|
||||||
import resize from '@/utils/resize';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: 'radarChart'
|
|
||||||
},
|
|
||||||
className: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '200px',
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
|
||||||
|
|
||||||
function initChart() {
|
|
||||||
const radarChart = init(document.getElementById(props.id) as HTMLDivElement);
|
|
||||||
|
|
||||||
radarChart.setOption({
|
|
||||||
title: {
|
|
||||||
show: true,
|
|
||||||
text: '订单状态统计',
|
|
||||||
x: 'center',
|
|
||||||
padding: 15,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#337ecc'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '2%',
|
|
||||||
right: '2%',
|
|
||||||
bottom: '10%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
x: 'center',
|
|
||||||
y: 'bottom',
|
|
||||||
data: ['预定数量', '下单数量', '发货数量']
|
|
||||||
},
|
|
||||||
radar: {
|
|
||||||
// shape: 'circle',
|
|
||||||
radius: '60%',
|
|
||||||
indicator: [
|
|
||||||
{ name: '家用电器' },
|
|
||||||
{ name: '服装箱包' },
|
|
||||||
{ name: '运动户外' },
|
|
||||||
{ name: '手机数码' },
|
|
||||||
{ name: '汽车用品' },
|
|
||||||
{ name: '家具厨具' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'Budget vs spending',
|
|
||||||
type: 'radar',
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 6,
|
|
||||||
color: function (params: any) {
|
|
||||||
//自定义颜色
|
|
||||||
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
|
|
||||||
return colorList[params.dataIndex];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: [400, 400, 400, 400, 400, 400],
|
|
||||||
name: '预定数量'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: [300, 300, 300, 300, 300, 300],
|
|
||||||
name: '下单数量'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: [200, 200, 200, 200, 200, 200],
|
|
||||||
name: '发货数量'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as EChartsOption);
|
|
||||||
|
|
||||||
chart.value = radarChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
beforeDestroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
onActivated(() => {
|
|
||||||
activated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onDeactivated(() => {
|
|
||||||
deactivated();
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
mounted();
|
|
||||||
nextTick(() => {
|
|
||||||
initChart();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="component-container">
|
|
||||||
<el-card class="project-card">
|
|
||||||
<template #header>
|
|
||||||
<span class="fw-b">有来项目简介</span>
|
|
||||||
</template>
|
|
||||||
<div class="project-card__main">
|
|
||||||
<!-- 项目简介 -->
|
|
||||||
<el-link target="_blank" type="primary" href="https://gitee.com/haoxr">
|
|
||||||
youlai-mall
|
|
||||||
</el-link>
|
|
||||||
是基于Spring Boot 2.7、Spring Cloud 2021 & Alibaba
|
|
||||||
2021、Vue3、Element-Plus、uni-app等主流技术栈构建的一整套全栈开源商城项目,
|
|
||||||
涉及
|
|
||||||
<el-link
|
|
||||||
target="_blank"
|
|
||||||
type="primary"
|
|
||||||
href="https://gitee.com/youlaitech/youlai-mall"
|
|
||||||
>后端微服务</el-link
|
|
||||||
>
|
|
||||||
、
|
|
||||||
<el-link
|
|
||||||
target="_blank"
|
|
||||||
type="success"
|
|
||||||
href="https://gitee.com/youlaitech/youlai-mall-admin"
|
|
||||||
>前端管理</el-link
|
|
||||||
>
|
|
||||||
、
|
|
||||||
<el-link
|
|
||||||
target="_blank"
|
|
||||||
type="warning"
|
|
||||||
href="https://gitee.com/youlaitech/youlai-mall-weapp"
|
|
||||||
>微信小程序
|
|
||||||
</el-link>
|
|
||||||
和
|
|
||||||
<el-link
|
|
||||||
target="_blank"
|
|
||||||
type="danger"
|
|
||||||
href="https://gitee.com/youlaitech/youlai-mall-weapp"
|
|
||||||
>APP应用</el-link
|
|
||||||
>
|
|
||||||
等多端的开发。
|
|
||||||
<el-divider />
|
|
||||||
|
|
||||||
<!-- 源码地址 -->
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-badge value="免费开源" class="fw-b"> 项目地址 </el-badge>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-link
|
|
||||||
target="_blank"
|
|
||||||
type="warning"
|
|
||||||
href="https://www.youlai.tech/"
|
|
||||||
>官方文档</el-link
|
|
||||||
>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-link
|
|
||||||
target="_blank"
|
|
||||||
type="primary"
|
|
||||||
href="https://github.com/youlaitech"
|
|
||||||
>Github</el-link
|
|
||||||
>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-link
|
|
||||||
target="_blank"
|
|
||||||
type="success"
|
|
||||||
href="https://gitee.com/youlaiorg"
|
|
||||||
>码云</el-link
|
|
||||||
>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-divider />
|
|
||||||
<!-- 技术栈 -->
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col :span="6" class="fw-b"> 后端技术栈 </el-col>
|
|
||||||
<el-col :span="18">
|
|
||||||
Spring Boot、Spring Cloud & Alibaba、Spring Security
|
|
||||||
OAuth2、JWT、Elastic Stack 、K8s...
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-divider />
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col :span="6" class="fw-b"> 前端技术栈 </el-col>
|
|
||||||
<el-col :span="18">
|
|
||||||
Vue3、TypeScript、Element-Plus、uni-app、vue3-element-admin ...
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'index'
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.component-container {
|
|
||||||
.project-card {
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
&__main {
|
|
||||||
line-height: 28px;
|
|
||||||
height: 320px;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fw-b {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,240 +0,0 @@
|
|||||||
<!-- 团队介绍 -->
|
|
||||||
<template>
|
|
||||||
<div class="component-container">
|
|
||||||
<el-card class="team-card">
|
|
||||||
<template #header>
|
|
||||||
<span class="fw-b">有来开源组织 & 技术团队</span>
|
|
||||||
</template>
|
|
||||||
<el-tabs v-model="teamActiveName">
|
|
||||||
<el-tab-pane label="开发者" name="1">
|
|
||||||
<div class="developer" ref="dev_wrapper">
|
|
||||||
<ul class="developer__container">
|
|
||||||
<li
|
|
||||||
class="developer__item"
|
|
||||||
v-for="(item, index) in developers"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<div class="developer__inner">
|
|
||||||
<el-image
|
|
||||||
class="developer__img"
|
|
||||||
:src="item.imgUrl"
|
|
||||||
:preview-src-list="[item.imgUrl]"
|
|
||||||
></el-image>
|
|
||||||
<div class="developer__info">
|
|
||||||
<span class="developer__nickname">{{ item.nickname }}</span>
|
|
||||||
<div class="developer__position">
|
|
||||||
<el-tag
|
|
||||||
v-for="(position, i) in item.positions"
|
|
||||||
:type="(colors[i % colors.length] as any)"
|
|
||||||
:class="i !== 0 ? 'f-ml' : ''"
|
|
||||||
size="small"
|
|
||||||
:key="i"
|
|
||||||
>{{ position }}</el-tag
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="developer__homepage">
|
|
||||||
<a :href="item.homepage" target="_blank">个人主页</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<el-image class="developer__indicator" :src="indicatorImgUrl" />
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
<el-tab-pane label="交流群" name="2">
|
|
||||||
<div class="group">
|
|
||||||
<el-image
|
|
||||||
class="group-img"
|
|
||||||
src="https://www.youlai.tech/files/blog/youlaiqun.png"
|
|
||||||
:preview-src-list="[
|
|
||||||
'https://www.youlai.tech/files/blog/youlaiqun.png'
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<div class="group-tip">
|
|
||||||
群二维码过期可添加开发者微信由其拉入群,备注「有来」即可。
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
<el-tab-pane label="加入我们" name="3">
|
|
||||||
<div class="join">
|
|
||||||
<p>1. 人品良好、善于思考、执行力强;</p>
|
|
||||||
<p>2. 熟悉项目,且至少给项目提交(过)一个PR;</p>
|
|
||||||
<p>3. Git代码库活跃,个人主页或博客完善者优先;</p>
|
|
||||||
<p>4. 过分优秀者我们会主动联系您...</p>
|
|
||||||
<div class="join__desc">申请加入方式: 添加开发者微信申请即可。</div>
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { nextTick, onMounted, reactive, ref, toRefs, watchEffect } from 'vue';
|
|
||||||
import BScroll from 'better-scroll';
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
teamActiveName: '1',
|
|
||||||
developers: [
|
|
||||||
{
|
|
||||||
imgUrl: 'https://s2.loli.net/2022/04/06/yRx8uzj4emA5QVr.jpg',
|
|
||||||
nickname: '郝先瑞',
|
|
||||||
positions: ['后端', '前端', '文档'],
|
|
||||||
homepage: 'https://www.cnblogs.com/haoxianrui/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imgUrl: 'https://s2.loli.net/2022/04/06/cQihGv9uPsTjXk1.jpg',
|
|
||||||
nickname: '张川',
|
|
||||||
positions: ['后端', '前端'],
|
|
||||||
homepage: 'https://blog.csdn.net/qq_41595149'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imgUrl: 'https://s2.loli.net/2022/04/07/2IiOYBHnRGKgCSd.jpg',
|
|
||||||
nickname: '张加林',
|
|
||||||
positions: ['DevOps'],
|
|
||||||
homepage: 'https://gitee.com/ximy'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
colors: ['', 'success', 'warning', 'danger'],
|
|
||||||
indicatorImgUrl: new URL(
|
|
||||||
`../../../../assets/index/indicator.png`,
|
|
||||||
import.meta.url
|
|
||||||
).href
|
|
||||||
});
|
|
||||||
|
|
||||||
const { teamActiveName, developers, colors, indicatorImgUrl } = toRefs(state);
|
|
||||||
|
|
||||||
let bScroll = reactive({});
|
|
||||||
|
|
||||||
const dev_wrapper = ref<HTMLElement | any>(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
bScroll = new BScroll(dev_wrapper.value, {
|
|
||||||
mouseWheel: true, //开启鼠标滚轮
|
|
||||||
disableMouse: false, //启用鼠标拖动
|
|
||||||
scrollX: true //X轴滚动启用
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
bScroll && (bScroll as any).refresh();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.component-container {
|
|
||||||
.team-card {
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.el-tabs__content {
|
|
||||||
.el-tab-pane {
|
|
||||||
height: 265px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.developer {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&__container {
|
|
||||||
display: inline-flex;
|
|
||||||
overflow: hidden;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
.developer__item {
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
list-style: none;
|
|
||||||
width: 180px;
|
|
||||||
min-width: 180px;
|
|
||||||
|
|
||||||
.developer__inner {
|
|
||||||
border: 1px solid #cccccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 6px 6px 6px #aaa;
|
|
||||||
padding: 8px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.developer__img {
|
|
||||||
height: 100px;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.developer__info {
|
|
||||||
padding: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.developer__position {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.developer__homepage {
|
|
||||||
margin-top: 16px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 4px 10px;
|
|
||||||
color: #409eff;
|
|
||||||
border: 1px solid #409eff;
|
|
||||||
border-radius: 5px;
|
|
||||||
background: #ecf5ff;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #409eff;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__indicator {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 120px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.join {
|
|
||||||
overflow: hidden;
|
|
||||||
p {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__desc {
|
|
||||||
margin-top: 20px;
|
|
||||||
color: #409eff;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.group {
|
|
||||||
&-img {
|
|
||||||
height: 200px;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fw-b {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.f-ml {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="dashboard-container">
|
|
||||||
<github-corner class="github-corner" />
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
export default { name: 'Dashboard' };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 组件引用
|
|
||||||
import GithubCorner from '@/components/GithubCorner/index.vue';
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
</style>
|
|
||||||
@ -40,7 +40,7 @@ function back() {
|
|||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li>或者你可以去:</li>
|
<li>或者你可以去:</li>
|
||||||
<li class="link-type">
|
<li class="link-type">
|
||||||
<router-link to="/dashboard"> 回首页 </router-link>
|
<router-link to="/"> 回首页 </router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="link-type">
|
<li class="link-type">
|
||||||
<a href="https://www.taobao.com/">随便看看</a>
|
<a href="https://www.taobao.com/">随便看看</a>
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>环保设施情况</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
5
frontend/src/views/home/huanBaoZhiLiangZhuangKuang.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>监测工作开展情况</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
6
frontend/src/views/home/shuiDianKaiFaZhuangKuang.vue
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>水电开放平台</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding: 30px">
|
|
||||||
<el-alert :closable="false" title="菜单一级">
|
|
||||||
<router-view />
|
|
||||||
</el-alert>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding: 30px">
|
|
||||||
<el-alert :closable="false" title="菜单二级" type="success">
|
|
||||||
<router-view />
|
|
||||||
</el-alert>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding: 30px">
|
|
||||||
<el-alert :closable="false" title="菜单三级-1" type="error" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="padding: 30px">
|
|
||||||
<el-alert :closable="false" title="菜单三级-2" type="warning" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="height: 100%">
|
|
||||||
<WujieVue name="process" :url="entryUrl" :sync="true" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const entryUrl = import.meta.env.VITE_PROCESS_ENTRY || '/process/';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2>生态流量达标情况</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>水质监测</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
@ -24,6 +26,6 @@
|
|||||||
"./types"
|
"./types"
|
||||||
] /* 指定多个文件夹,这些文件夹的作用类似于 './node_modules/@types'. */
|
] /* 指定多个文件夹,这些文件夹的作用类似于 './node_modules/@types'. */
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.vue", "types/**/*.d.ts"],
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
"exclude": ["node_modules", "dist", "**/*.js"]
|
"exclude": ["node_modules", "dist", "**/*.js"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
|||||||
// 线上API地址
|
// 线上API地址
|
||||||
//target: 'http://192.168.1.20:8090/',
|
//target: 'http://192.168.1.20:8090/',
|
||||||
// 本地API地址
|
// 本地API地址
|
||||||
target: 'http://localhost:8093',
|
target: 'http://10.84.1.66:8093',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: path =>
|
rewrite: path =>
|
||||||
path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
||||||
@ -41,7 +41,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
|||||||
resolve: {
|
resolve: {
|
||||||
// Vite路径别名配置
|
// Vite路径别名配置
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve('./src')
|
'@': path.resolve(__dirname, './src')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
|
|||||||