前端框架整体修改
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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="description" content="NewFrameWork2023-WEB" />
|
||||
<meta name="keywords" content="NewFrameWork2023-WEB" />
|
||||
|
||||
@ -10,11 +10,13 @@
|
||||
"prettier": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@vueuse/core": "^9.1.1",
|
||||
"@wangeditor/editor": "^5.0.0",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.2.0",
|
||||
"better-scroll": "^2.4.2",
|
||||
"default-passive-events": "^2.0.0",
|
||||
@ -63,6 +65,5 @@
|
||||
},
|
||||
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
|
||||
"author": "有来开源组织",
|
||||
"license": "MIT",
|
||||
"__npminstall_done": false
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
|
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">
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
const appStore = useAppStore();
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import { useAppStore,usetTheme } from '@/store/modules/app';
|
||||
const appStore = useAppStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
||||
<a-config-provider :theme="usetTheme">
|
||||
<router-view />
|
||||
</a-config-provider>
|
||||
</el-config-provider>
|
||||
</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 { useSettingsStore } from '@/store/modules/settings';
|
||||
|
||||
// 图标依赖
|
||||
import { Close, Setting } from '@element-plus/icons-vue';
|
||||
import { ElColorPicker } from 'element-plus';
|
||||
|
||||
// const settingsStore = useSettingsStore();
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
defineProps({
|
||||
@ -32,21 +29,6 @@ watch(show, value => {
|
||||
});
|
||||
|
||||
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);
|
||||
@ -92,7 +74,6 @@ onBeforeUnmount(() => {
|
||||
.showRightPanel {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -16,8 +16,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { useSettingsStore } from '@/store/modules/settings';
|
||||
// const settingsStore = useSettingsStore();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@ -6,11 +6,11 @@ export default {
|
||||
},
|
||||
// 登录页面国际化
|
||||
login: {
|
||||
title: '公司开发平台框架',
|
||||
title: '水电水利建设项目全过程环境管理信息平台',
|
||||
username: '用户名',
|
||||
rulesUsername: '请输入用户名',
|
||||
rulesUsername: '用户账号/身份证号/手机号 不能为空',
|
||||
password: '密码',
|
||||
rulesPassword: '请输入密码',
|
||||
rulesPassword: '密码 不能为空',
|
||||
rulesPasswordPlace: '密码不能少于6位',
|
||||
login: '登 录',
|
||||
code: '请输入验证码',
|
||||
|
||||
@ -22,28 +22,13 @@ const routeKey = computed(() => router.path + Math.random());
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/variables.module.scss" as *;
|
||||
.app-main {
|
||||
min-height: calc(100vh - 114px);
|
||||
min-height: calc(100vh - $layout-header-height - $locationbar-height);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #f0f2f5;
|
||||
padding: 0 16px 16px;
|
||||
background-color: #ffffff;
|
||||
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>
|
||||
|
||||
@ -1,46 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { getToken } from '@/utils/auth';
|
||||
|
||||
import Screenfull from '@/components/Screenfull/index.vue';
|
||||
import SizeSelect from '@/components/SizeSelect/index.vue';
|
||||
import News from './news.vue';
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { UserOutlined, LogoutOutlined } from "@ant-design/icons-vue";
|
||||
// 国际化
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
// import LangSelect from '@/components/LangSelect/index.vue';
|
||||
import MixNav from './Sidebar/MixNav.vue';
|
||||
// import { CaretBottom } from '@element-plus/icons-vue';
|
||||
import Logo from './Sidebar/Logo.vue';
|
||||
import Sidebar from "./Sidebar/index.vue";
|
||||
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
import Cookies from 'js-cookie';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewStore } from "@/store/modules/tagsView";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import Cookies from "js-cookie";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
const url = import.meta.env.VITE_APP_BASE_API;
|
||||
const username = Cookies.get('username');
|
||||
const settingsStore = useSettingsStore();
|
||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
||||
const username = Cookies.get("username");
|
||||
|
||||
const appStore = useAppStore();
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const querystr = ref('');
|
||||
const dialogVisible = ref(false)
|
||||
const device = computed(() => appStore.device);
|
||||
const news = ref()
|
||||
|
||||
function logout() {
|
||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
}).then(() => {
|
||||
userStore
|
||||
.logout()
|
||||
@ -52,114 +39,128 @@ function logout() {
|
||||
});
|
||||
});
|
||||
}
|
||||
function querystrChange() {
|
||||
}
|
||||
const badgeval = ref(0)
|
||||
const isbadge = ref(true)
|
||||
var source = new EventSource(url+ `/sse/connect/` + getToken(),);
|
||||
const badgeval = ref(0);
|
||||
const isbadge = ref(true);
|
||||
var source = new EventSource(url + `/sse/connect/` + getToken());
|
||||
onMounted(() => {
|
||||
if ("EventSource" in window) {
|
||||
source.onmessage = function(e) {
|
||||
if(e.data>0) {
|
||||
badgeval.value = e.data
|
||||
isbadge.value = false
|
||||
news.value.init()
|
||||
source.onmessage = function (e) {
|
||||
if (e.data > 0) {
|
||||
badgeval.value = e.data;
|
||||
isbadge.value = false;
|
||||
} else {
|
||||
isbadge.value = true
|
||||
isbadge.value = true;
|
||||
}
|
||||
};
|
||||
source.onopen = function(e) {
|
||||
};
|
||||
source.onerror = function(e:any) {
|
||||
source.onopen = function (e) {};
|
||||
source.onerror = function (e: any) {
|
||||
if (e.readyState == EventSource.CLOSED) {
|
||||
} else {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
};
|
||||
})
|
||||
onBeforeUnmount(()=>{
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
source.close();
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<logo v-if="sidebarLogo" :collapse="isCollapse" />
|
||||
<!-- <mix-nav v-if="device !== 'mobile' && settingsStore.layout === 'mix'" /> -->
|
||||
<div v-if="settingsStore.layout === 'left'" class="flex justify-start">
|
||||
<div class="flex justify-center items-center">
|
||||
<!--全屏 -->
|
||||
<div v-if="device === 'desktop'" style="position: relative;">
|
||||
<el-input class="keywords" v-model="querystr" placeholder="请输入搜索关键字" clearable
|
||||
@change="querystrChange"></el-input>
|
||||
<img src="@/assets/MenuIcon/top_ss.png" alt="" style="position: absolute;right: 30px;top: 10px;">
|
||||
</div>
|
||||
<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>
|
||||
<a-layout-header class="header">
|
||||
<transition class="bg-white-800">
|
||||
<a
|
||||
href="/"
|
||||
class="h-[50px] min-w-[350px] flex items-center justify-center text-white"
|
||||
>
|
||||
<h1 class="text-blank font-bold fontSize-16">{{ t("login.title") }}</h1></a
|
||||
>
|
||||
</transition>
|
||||
<Sidebar />
|
||||
|
||||
<el-tooltip content="个人中心" effect="dark" placement="bottom">
|
||||
<div class="cursor-pointer flex justify-center items-center pl-[10px]"
|
||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="$router.push('/personalCenter')">{{
|
||||
username }}</div>
|
||||
</el-tooltip>
|
||||
|
||||
<div class="flex justify-center items-center cursor-pointer pr-[12px]"
|
||||
:style="{ fontSize: appStore.size === 'default' ? '14px' : '16px' }" @click="logout"><el-divider
|
||||
direction="vertical" style="padding-right:5px" />{{ $t('navbar.logout') }}</div>
|
||||
<News ref="news" v-model:dialog-visible="dialogVisible" />
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
<a-space class="username">
|
||||
<div>
|
||||
<span class="icon">
|
||||
<UserOutlined />
|
||||
</span>
|
||||
<span class="text">{{ username }}</span>
|
||||
</div>
|
||||
</a-space>
|
||||
<template #overlay>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/variables.module.scss" as *;
|
||||
.navbar {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 60px;
|
||||
height: 110px;
|
||||
display: -webkit-flex;
|
||||
z-index: 98;
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
.keywords {
|
||||
width: 240px;
|
||||
height: 34px;
|
||||
margin-right: 20px;
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
padding-left: 15px;
|
||||
border-radius: 40px;
|
||||
.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;
|
||||
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">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ref, onBeforeMount, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import SidebarItem from './SidebarItem.vue';
|
||||
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();
|
||||
import { usePermissionStore } from "@/store/modules/permission";
|
||||
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 router = useRouter();
|
||||
|
||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
||||
|
||||
const activeMenu = computed<string>(() => {
|
||||
const { meta, path } = route;
|
||||
if (meta?.activeMenu) {
|
||||
return meta.activeMenu as string;
|
||||
// 主菜单切换
|
||||
const handleTabChange = (key: string) => {
|
||||
let data = menus.value.find((item: any) => item.id === key);
|
||||
subMenus.value = data.children;
|
||||
activeKey.value = key;
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'has-logo': sidebarLogo }">
|
||||
<el-scrollbar>
|
||||
<el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="variables.menuBg"
|
||||
:text-color="variables.menuText" :active-text-color="variables.menuActiveText" :unique-opened="false"
|
||||
:collapse-transition="false" mode="vertical" >
|
||||
<sidebar-item v-for="route in permissionStore.routes" :item="route" :key="route.opturl"
|
||||
:is-collapse="isCollapse" />
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
<a-tabs
|
||||
class="sidebar-container"
|
||||
v-model:activeKey="activeKey"
|
||||
@change="handleTabChange"
|
||||
size="small"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="route in menus"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
@ -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 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">
|
||||
import { computed, watchEffect } from 'vue';
|
||||
import { useWindowSize } from '@vueuse/core';
|
||||
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 { computed } from "vue";
|
||||
import { AppMain, Navbar } from "./components/index";
|
||||
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
|
||||
const { width } = useWindowSize();
|
||||
|
||||
/**
|
||||
* 响应式布局容器固定宽度
|
||||
*
|
||||
* 大屏(>=1200px)
|
||||
* 中屏(>=992px)
|
||||
* 小屏(>=768px)
|
||||
*/
|
||||
const WIDTH = 992;
|
||||
import { useAppStore } from "@/store/modules/app";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
const showTagsView = computed(() => settingsStore.tagsView);
|
||||
const showSettings = computed(() => settingsStore.showSettings);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !appStore.sidebar.opened,
|
||||
openSidebar: appStore.sidebar.opened,
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper">
|
||||
<!-- 手机设备 && 侧边栏 → 显示遮罩层 -->
|
||||
<div
|
||||
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" />
|
||||
|
||||
<navbar />
|
||||
<div class="main-container">
|
||||
<!--主页面-->
|
||||
<app-main />
|
||||
|
||||
<!-- 设置面板 -->
|
||||
<RightPanel v-if="showSettings">
|
||||
<settings />
|
||||
</RightPanel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/styles/mixin.scss' as mixin;
|
||||
@use '@/styles/variables.module.scss' as variables;
|
||||
|
||||
.app-wrapper {
|
||||
// @include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 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>
|
||||
@ -8,6 +8,8 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import Pagination from '@/components/Pagination/index.vue';
|
||||
import '@/permission';
|
||||
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/reset.css' // Ant Design 全局样式重置
|
||||
// 引入svg注册脚本
|
||||
import 'virtual:svg-icons-register';
|
||||
|
||||
@ -39,6 +41,7 @@ app
|
||||
.component('Pagination', Pagination)
|
||||
.use(router)
|
||||
.use(ElementPlus)
|
||||
.use(Antd)
|
||||
.use(WujieVue)
|
||||
.use(i18n)
|
||||
.mount('#app');
|
||||
|
||||
@ -5,20 +5,35 @@ import { usePermissionStoreHook } from '@/store/modules/permission';
|
||||
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
NProgress.configure({ showSpinner: false }); // 进度条
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
const permissionStore = usePermissionStoreHook();
|
||||
|
||||
// 白名单路由
|
||||
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) => {
|
||||
NProgress.start();
|
||||
const userStore = useUserStoreHook();
|
||||
|
||||
if (userStore.Token) {
|
||||
// 登录成功,跳转到首页
|
||||
if (to.path === '/login') {
|
||||
@ -26,9 +41,19 @@ router.beforeEach(async (to, from, next) => {
|
||||
NProgress.done();
|
||||
} else {
|
||||
const hasGetUserInfo = userStore.roles.length > 0;
|
||||
// const hasGetUserInfo = true;
|
||||
|
||||
if (hasGetUserInfo) {
|
||||
// 已获取用户信息,检查路由匹配
|
||||
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');
|
||||
} else {
|
||||
next();
|
||||
@ -36,15 +61,25 @@ router.beforeEach(async (to, from, next) => {
|
||||
} else {
|
||||
try {
|
||||
const { roles } = await userStore.getInfo();
|
||||
const accessRoutes: RouteRecordRaw[] =
|
||||
await permissionStore.generateRoutes(roles);
|
||||
const accessRoutes: RouteRecordRaw[] = await permissionStore.generateRoutes(roles);
|
||||
|
||||
accessRoutes.forEach((route: any) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
|
||||
// 关键:如果是根路径,加载完路由后跳转到第一个可用路由
|
||||
if (to.path === '/') {
|
||||
const firstRoute = findFirstAvailableRoute(accessRoutes);
|
||||
if (firstRoute) {
|
||||
next(firstRoute);
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
next({ ...to, replace: true });
|
||||
} catch (error) {
|
||||
// 移除 token 并跳转登录页
|
||||
console.log(error);
|
||||
await userStore.resetToken();
|
||||
next(`/login?redirect=${to.path}`);
|
||||
NProgress.done();
|
||||
|
||||
@ -26,48 +26,11 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/error-page/404.vue'),
|
||||
meta: { hidden: true }
|
||||
},
|
||||
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
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',
|
||||
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 = {
|
||||
title: '公司开发平台框架',
|
||||
title: '水电水利建设项目全过程环境管理信息平台',
|
||||
showSettings: false,
|
||||
tagsView: true,
|
||||
fixedHeader: true,
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
getSidebarStatus,
|
||||
setSidebarStatus,
|
||||
getSize,
|
||||
setSize,
|
||||
@ -24,7 +23,12 @@ export enum SizeType {
|
||||
large,
|
||||
small
|
||||
}
|
||||
|
||||
export const usetTheme = {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
borderRadius: 2,
|
||||
},
|
||||
};
|
||||
// setup
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
// state
|
||||
@ -32,7 +36,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
const size = ref(getSize() || 'default');
|
||||
const language = ref(getLanguage());
|
||||
const sidebar = reactive({
|
||||
opened: getSidebarStatus() !== 'closed',
|
||||
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) {
|
||||
size.value = val;
|
||||
@ -87,11 +64,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
language,
|
||||
locale,
|
||||
size,
|
||||
toggleDevice,
|
||||
changeSize,
|
||||
changeLanguage,
|
||||
toggleSidebar,
|
||||
closeSideBar,
|
||||
openSideBar
|
||||
};
|
||||
});
|
||||
|
||||
@ -8,24 +8,18 @@ import { ref } from 'vue';
|
||||
const modules = import.meta.glob('../../views/**/**.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 res: RouteRecordRaw[] = [];
|
||||
routes.forEach(route => {
|
||||
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)) {
|
||||
tmp.path = tmp.opturl;
|
||||
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-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 './sidebar' as sidebar;
|
||||
@use './tailwind' as tailwind;
|
||||
|
||||
html,body,#app{
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
font-family: auto;
|
||||
}
|
||||
|
||||
/* Webkit 浏览器 (Chrome, Safari, Edge) */
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
// main-container global css
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.search{
|
||||
.search {
|
||||
padding: 18px 0 0 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 6px 2px 6px #CCC;
|
||||
box-shadow: 6px 2px 6px #ccc;
|
||||
}
|
||||
|
||||
svg{
|
||||
svg {
|
||||
display: inline-block;
|
||||
}
|
||||
.el-dialog {
|
||||
@ -28,8 +36,8 @@ svg{
|
||||
flex-direction: column !important;
|
||||
margin: auto !important;
|
||||
position: absolute !important;
|
||||
top: 0 ;
|
||||
left: 0 ;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: max-content;
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
@use 'src/styles/variables.module' as variables;
|
||||
|
||||
svg {
|
||||
vertical-align: text-bottom !important;
|
||||
}
|
||||
@ -7,249 +5,13 @@ svg {
|
||||
.main-container {
|
||||
// min-height: 100%;
|
||||
transition: margin-left 0.28s;
|
||||
margin-left: variables.$sideBarWidth;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body[layout='mix'] {
|
||||
.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
|
||||
.el-menu--vertical {
|
||||
& > .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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
$menuText: #505050;
|
||||
$menuActiveText: #409eff;
|
||||
$subMenuActiveText: #409eff;
|
||||
$main-menu-color: #fff;
|
||||
$main-menu-color-active: #00e6fc;
|
||||
$main-menu-bg-color-active: #003e6e;
|
||||
|
||||
$menuBg: #ffffff;
|
||||
$menuHover: #409eff;
|
||||
|
||||
$subMenuBg: #ffffff;
|
||||
$subMenuHover: #409eff;
|
||||
|
||||
$sideBarWidth: 210px;
|
||||
$layout-header-height: 60px;
|
||||
$locationbar-height: 50px;
|
||||
|
||||
// the :export directive is the magic sauce for webpack
|
||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||
:export {
|
||||
menuText: $menuText;
|
||||
menuActiveText: $menuActiveText;
|
||||
subMenuActiveText: $subMenuActiveText;
|
||||
menuBg: $menuBg;
|
||||
menuHover: $menuHover;
|
||||
subMenuBg: $subMenuBg;
|
||||
subMenuHover: $subMenuHover;
|
||||
sideBarWidth: $sideBarWidth;
|
||||
main-menu-color: $main-menu-color;
|
||||
main-menu-color-active: $main-menu-color-active;
|
||||
main-menu-bg-color-active: $main-menu-bg-color-active;
|
||||
|
||||
layout-header-height: $layout-header-height;
|
||||
locationbar-height: $locationbar-height;
|
||||
}
|
||||
|
||||
@ -21,12 +21,6 @@ export const localStorage = {
|
||||
}
|
||||
};
|
||||
|
||||
// 侧边栏状态(显示/隐藏)
|
||||
const SidebarStatusKey = 'sidebarStatus';
|
||||
export function getSidebarStatus() {
|
||||
return localStorage.get(SidebarStatusKey);
|
||||
}
|
||||
|
||||
export function setSidebarStatus(sidebarStatus: string) {
|
||||
localStorage.set(SidebarStatusKey, sidebarStatus);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 { useUserStoreHook } from '@/store/modules/user';
|
||||
|
||||
@ -33,18 +33,14 @@ service.interceptors.request.use(
|
||||
service.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const { status, msg } = response;
|
||||
console.log(msg)
|
||||
console.log(response);
|
||||
if (status === 200) {
|
||||
if (response.data.code == 401) {
|
||||
ElMessage({
|
||||
message: '用户名或密码错误,请重试!',
|
||||
type: 'error'
|
||||
});
|
||||
message.error(response.data.msg||'请求失败');
|
||||
return;
|
||||
}else if(response.data.code == 1){
|
||||
ElMessage({
|
||||
message: response.data.msg,
|
||||
type: 'error'
|
||||
});
|
||||
message.error(response.data.msg);
|
||||
return;
|
||||
}
|
||||
return response.data;
|
||||
@ -53,10 +49,7 @@ service.interceptors.response.use(
|
||||
if (response.data instanceof ArrayBuffer) {
|
||||
return response;
|
||||
}
|
||||
ElMessage({
|
||||
message: msg || '系统出错',
|
||||
type: 'error'
|
||||
});
|
||||
message.error( msg || '系统出错');
|
||||
return Promise.reject(new Error(msg || 'Error'));
|
||||
}
|
||||
},
|
||||
@ -65,18 +58,18 @@ service.interceptors.response.use(
|
||||
const { status, msg } = error.response.data;
|
||||
// token 过期,重新登录
|
||||
if (status === '403') {
|
||||
ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', {
|
||||
confirmButtonText: 'OK',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: "当前页面已失效,请重新登录",
|
||||
okText: "确定",
|
||||
cancelButtonProps: { style: { display: "none" } },
|
||||
onOk: () => {
|
||||
localStorage.clear();
|
||||
window.location.href = '/';
|
||||
},
|
||||
});
|
||||
} else {
|
||||
ElMessage({
|
||||
message: msg || '当前页面已失效',
|
||||
type: 'error'
|
||||
});
|
||||
message.error(msg || '当前页面已失效');
|
||||
}
|
||||
}
|
||||
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">
|
||||
<li>或者你可以去:</li>
|
||||
<li class="link-type">
|
||||
<router-link to="/dashboard"> 回首页 </router-link>
|
||||
<router-link to="/"> 回首页 </router-link>
|
||||
</li>
|
||||
<li class="link-type">
|
||||
<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": {
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
@ -24,6 +26,6 @@
|
||||
"./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"]
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||
// 线上API地址
|
||||
//target: 'http://192.168.1.20:8090/',
|
||||
// 本地API地址
|
||||
target: 'http://localhost:8093',
|
||||
target: 'http://10.84.1.66:8093',
|
||||
changeOrigin: true,
|
||||
rewrite: path =>
|
||||
path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
||||
@ -41,7 +41,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||
resolve: {
|
||||
// Vite路径别名配置
|
||||
alias: {
|
||||
'@': path.resolve('./src')
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
css: {
|
||||
|
||||