Commit 701dac4f authored by 张伯涛's avatar 张伯涛

业务数据(常青藤)

parent 2dd2fbbf
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
content="Vue3 + Vite4 + TypeScript5 + Element-Plus 的后台管理模板,配套接口文档和后端源码,vue-element-admin 的 Vue3 版本" content="Vue3 + Vite4 + TypeScript5 + Element-Plus 的后台管理模板,配套接口文档和后端源码,vue-element-admin 的 Vue3 版本"
/> />
<meta name="keywords" content="vue-element-admin,vue3-element-admin" /> <meta name="keywords" content="vue-element-admin,vue3-element-admin" />
<title>vue3-element-admin</title> <title>业务数据|管理系统</title>
</head> </head>
<body> <body>
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow-y: hidden;
} }
.mesh-loader { .mesh-loader {
......
{ {
"name": "vue3-element-admin", "name": "业务数据|管理系统",
"version": "2.8.2", "version": "2.8.2",
"private": true, "private": true,
"type": "module", "type": "module",
......
...@@ -56,16 +56,16 @@ onBeforeUnmount(() => { ...@@ -56,16 +56,16 @@ onBeforeUnmount(() => {
<div ref="rightPanel" :class="{ show: show }"> <div ref="rightPanel" :class="{ show: show }">
<div class="right-panel-overlay"></div> <div class="right-panel-overlay"></div>
<div class="right-panel-container"> <div class="right-panel-container">
<div <!-- <div-->
class="right-panel-btn" <!-- class="right-panel-btn"-->
:style="{ <!-- :style="{-->
top: buttonTop + 'px', <!-- top: buttonTop + 'px',-->
}" <!-- }"-->
@click="show = !show" <!-- @click="show = !show"-->
> <!-- >-->
<i-ep-close v-show="show" /> <!-- <i-ep-close v-show="show" />-->
<i-ep-setting v-show="!show" /> <!-- <i-ep-setting v-show="!show" />-->
</div> <!-- </div>-->
<div> <div>
<slot></slot> <slot></slot>
</div> </div>
......
<template> <template>
<!-- 导航栏设置(窄屏隐藏)--> <!-- 导航栏设置(窄屏隐藏)-->
<div v-if="device !== 'mobile'" class="setting-container"> <!-- <div v-if="device !== 'mobile'" class="setting-container">-->
<!--全屏 --> <!-- &lt;!&ndash;全屏 &ndash;&gt;-->
<div class="setting-item" @click="toggle"> <!-- <div class="setting-item" @click="toggle">-->
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" /> <!-- <svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" />-->
</div> <!-- </div>-->
<!-- 布局大小 --> <!-- &lt;!&ndash; 布局大小 &ndash;&gt;-->
<el-tooltip content="布局大小" effect="dark" placement="bottom"> <!-- <el-tooltip content="布局大小" effect="dark" placement="bottom">-->
<size-select class="setting-item" /> <!-- <size-select class="setting-item" />-->
</el-tooltip> <!-- </el-tooltip>-->
</div> <!-- </div>-->
<!-- 用户头像 --> <!-- 用户头像 -->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<div class="avatar-container"> <div class="avatar-container">
<img :src="userStore.user.avatar + '?imageView2/1/w/80/h/80'" /> <!-- <img :src="userStore.user.avatar + '?imageView2/1/w/80/h/80'" />-->
<span style="padding-left: 15px">{{ userName }}</span>
<i-ep-caret-bottom class="w-3 h-3" /> <i-ep-caret-bottom class="w-3 h-3" />
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<router-link to="/">
<el-dropdown-item>{{ $t("navbar.dashboard") }}</el-dropdown-item>
</router-link>
<a
target="_blank"
href="https://github.com/youlaitech/vue3-element-admin"
>
<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://juejin.cn/post/7228990409909108793">
<el-dropdown-item>{{ $t("navbar.document") }}</el-dropdown-item>
</a>
<el-dropdown-item divided @click="logout"> <el-dropdown-item divided @click="logout">
<!-- {{ $t("navbar.logout") }}--> <!-- {{ $t("navbar.logout") }}-->
退出 退出
...@@ -52,7 +38,7 @@ import { useUserStore } from "@/store/modules/user"; ...@@ -52,7 +38,7 @@ import { useUserStore } from "@/store/modules/user";
const appStore = useAppStore(); const appStore = useAppStore();
const tagsViewStore = useTagsViewStore(); const tagsViewStore = useTagsViewStore();
const userStore = useUserStore(); const userStore = useUserStore();
const userName = localStorage.getItem("loginUser");
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
......
...@@ -14,6 +14,7 @@ const appStore = useAppStore(); ...@@ -14,6 +14,7 @@ const appStore = useAppStore();
const { sidebarLogo } = storeToRefs(settingsStore); const { sidebarLogo } = storeToRefs(settingsStore);
const layout = computed(() => settingsStore.layout); const layout = computed(() => settingsStore.layout);
const showContent = ref(true); const showContent = ref(true);
const routerList = JSON.parse(localStorage.getItem("router"));
watch( watch(
() => layout.value, () => layout.value,
() => { () => {
...@@ -33,7 +34,7 @@ watch( ...@@ -33,7 +34,7 @@ watch(
> >
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" /> <logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
<el-scrollbar v-if="showContent"> <el-scrollbar v-if="showContent">
<LeftMenu :menu-list="userStore.menus" base-path="" /> <LeftMenu :menu-list="routerList" base-path="" />
</el-scrollbar> </el-scrollbar>
<NavRight v-if="layout === 'top'" /> <NavRight v-if="layout === 'top'" />
</div> </div>
...@@ -47,6 +48,7 @@ watch( ...@@ -47,6 +48,7 @@ watch(
</div> </div>
</template> </template>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.setting-container) { :deep(.setting-container) {
.setting-item { .setting-item {
......
...@@ -10,66 +10,71 @@ NProgress.configure({ showSpinner: false }); // 进度条 ...@@ -10,66 +10,71 @@ NProgress.configure({ showSpinner: false }); // 进度条
const userStore = useUserStoreHook(); const userStore = useUserStoreHook();
// 白名单路由 // 白名单路由
const whiteList = ["/login"]; const whiteList = ["/login", "dashboard"];
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start(); NProgress.start();
if (getToken()) { // if (getToken()) {
if (to.path === "/login") { // if (to.path === "/login") {
// 如果已登录,跳转首页 // // 如果已登录,跳转首页
next(); // next();
NProgress.done(); // NProgress.done();
} else { // } else {
const hasRoles = userStore.user.roles && userStore.user.roles.length > 0; // const hasRoles = userStore.user.roles && userStore.user.roles.length > 0;
if (hasRoles) { // if (hasRoles) {
// 未匹配到任何路由,跳转404 // // 未匹配到任何路由,跳转404
if (to.matched.length === 0) { // if (to.matched.length === 0) {
from.name ? next({ name: from.name }) : next("/404"); // from.name ? next({ name: from.name }) : next("/404");
} else { // } else {
next(); // next();
} // }
} else { // } else {
try { // try {
const { permissions } = await userStore.getUserInfo(); // const { permissions } = await userStore.getUserInfo();
//'/controlPlatform/control'登录后的跳转的特殊路由 // //'/controlPlatform/control'登录后的跳转的特殊路由
if (to.path === "/controlPlatform/control") { // if (to.path === "/controlPlatform/control") {
if (permissions && permissions.length > 0) { // if (permissions && permissions.length > 0) {
const accessRoutes = await userStore.generateRoutes(); // const accessRoutes = await userStore.generateRoutes();
accessRoutes.forEach((route) => { // accessRoutes.forEach((route) => {
router.addRoute(route); // router.addRoute(route);
}); // });
next({ path: accessRoutes[0].children?.[0].path, replace: true }); // 跳转登录用户的第一个菜单 // next({ path: accessRoutes[0].children?.[0].path, replace: true }); // 跳转登录用户的第一个菜单
// // next({ ...to, replace: true });
// } else {
// userStore.logout().then((_) => {
// alert("用户无权限");
// next(`/login`);
// });
// }
// } else {
// const accessRoutes = await userStore.generateRoutes();
// accessRoutes.forEach((route) => {
// router.addRoute(route);
// });
// next({ ...to, replace: true }); // next({ ...to, replace: true });
} else { // }
userStore.logout().then((_) => { // } catch (error) {
alert("用户无权限"); // // 移除 token 并跳转登录页
next(`/login`); // await userStore.resetToken();
}); // next(`/login?redirect=${to.path}`);
} // NProgress.done();
} else { // }
const accessRoutes = await userStore.generateRoutes(); // }
accessRoutes.forEach((route) => { // }
router.addRoute(route); // } else {
}); // // 未登录可以访问白名单页面
next({ ...to, replace: true }); // if (whiteList.indexOf(to.path) !== -1) {
}
} catch (error) {
// 移除 token 并跳转登录页
await userStore.resetToken();
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
// 未登录可以访问白名单页面
if (whiteList.indexOf(to.path) !== -1) {
next(); next();
} else { // const accessRoutes = await userStore.generateRoutes();
next(`/login?redirect=${to.path}`); // accessRoutes.forEach((route) => {
NProgress.done(); // router.addRoute(route);
} // });
} // next({ ...to, replace: true });
// } else {
// next(`/login?redirect=${to.path}`);
// NProgress.done();
// }
// }
}); });
router.afterEach(() => { router.afterEach(() => {
......
...@@ -4,24 +4,6 @@ export const Layout = () => import("@/layout/index.vue"); ...@@ -4,24 +4,6 @@ export const Layout = () => import("@/layout/index.vue");
// 静态路由 // 静态路由
export const constantRoutes: RouteRecordRaw[] = [ export const constantRoutes: RouteRecordRaw[] = [
{
path: "/redirect",
component: Layout,
meta: { hidden: true },
children: [
{
path: "/redirect/:path(.*)",
component: () => import("@/views/redirect/index.vue"),
},
],
},
{
path: "/login",
component: () => import("@/views/login/index.vue"),
meta: { hidden: true },
},
{ {
path: "/", path: "/",
name: "/", name: "/",
...@@ -32,90 +14,72 @@ export const constantRoutes: RouteRecordRaw[] = [ ...@@ -32,90 +14,72 @@ export const constantRoutes: RouteRecordRaw[] = [
path: "dashboard", path: "dashboard",
component: () => import("@/views/dashboard/index.vue"), component: () => import("@/views/dashboard/index.vue"),
name: "Dashboard", // 用于 keep-alive, 必须与SFC自动推导或者显示声明的组件name一致 name: "Dashboard", // 用于 keep-alive, 必须与SFC自动推导或者显示声明的组件name一致
// https://cn.vuejs.org/guide/built-ins/keep-alive.html#include-exclude
meta: { meta: {
title: "dashboard", title: "dashboard",
icon: "homepage", icon: "homepage",
affix: true, affix: true,
hidden: true,
keepAlive: true, keepAlive: true,
alwaysShow: false, alwaysShow: false,
}, },
}, },
],
},
{ {
path: "dict/type/data/:dictId(\\d+)", path: "/manageSystem/project",
component: () => import("@/views/system/dict/data.vue"), component: Layout,
name: "Data", redirect: "/manageSystem/project/",
meta: { meta: { title: "项目管理", icon: "system" }, // 一级菜单
title: "字典数据", children: [
icon: "", {
hidden: true, path: "ProjectEstablishment",
component: () =>
import("@/views/manageSystem/project/ProjectEstablishment/index.vue"),
name: "UserManagement",
meta: { title: "立项报备", icon: "system" }, // 二级菜单
}, },
{
path: "ProjectEstablishmentDetails",
component: () =>
import(
"@/views/manageSystem/project/ProjectEstablishment/details.vue"
),
name: "ProjectEstablishmentDetails",
meta: { title: "立项报备表单", icon: "system" }, // 二级菜单
}, },
{ {
path: "/401", path: "followUp",
component: () => import("@/views/error-page/401.vue"), component: () =>
meta: { hidden: true }, import("@/views/manageSystem/project/followUp/index.vue"),
name: "followUp",
meta: { title: "跟进洽谈", icon: "system" }, // 二级菜单
}, },
{ {
path: "/404", path: "tender",
component: () => import("@/views/error-page/404.vue"), component: () =>
meta: { hidden: true }, import("@/views/manageSystem/project/tender/index.vue"),
name: "tender",
meta: { title: "投标准备", icon: "system" }, // 二级菜单
}, },
], ],
}, },
// 外部链接
// {
// path: "/external-link",
// component: Layout,
// children: [
// {
// component: () => import("@/views/external-link/index.vue"),
// path: "https://www.cnblogs.com/haoxianrui/",
// meta: { title: "外部链接", icon: "link" },
// },
// ],
// },
// 多级嵌套路由
/* {
path: '/nested',
component: Layout,
redirect: '/nested/level1/level2',
name: 'Nested',
meta: {title: '多级菜单', icon: 'nested'},
children: [
{
path: 'level1',
component: () => import('@/views/nested/level1/index.vue'),
name: 'Level1',
meta: {title: '菜单一级'},
redirect: '/nested/level1/level2',
children: [
{ {
path: 'level2', path: "/redirect",
component: () => import('@/views/nested/level1/level2/index.vue'), component: Layout,
name: 'Level2', meta: { hidden: true },
meta: {title: '菜单二级'},
redirect: '/nested/level1/level2/level3',
children: [ children: [
{ {
path: 'level3-1', path: "/redirect/:path(.*)",
component: () => import('@/views/nested/level1/level2/level3/index1.vue'), component: () => import("@/views/redirect/index.vue"),
name: 'Level3-1',
meta: {title: '菜单三级-1'}
}, },
],
},
{ {
path: 'level3-2', path: "/login",
component: () => import('@/views/nested/level1/level2/level3/index2.vue'), component: () => import("@/views/login/index.vue"),
name: 'Level3-2', meta: { hidden: true },
meta: {title: '菜单三级-2'}
}
]
}
]
}, },
]
}*/
]; ];
/** /**
......
const defaultSettings: AppSettings = { const defaultSettings: AppSettings = {
title: "vue3-element-admin", title: "业务数据|管理系统",
version: "v2.8.2", version: "v2.8.2",
showSettings: true, showSettings: true,
tagsView: true, tagsView: true,
......
...@@ -2,5 +2,5 @@ export const errorCode = { ...@@ -2,5 +2,5 @@ export const errorCode = {
"401": "认证失败,无法访问系统资源", "401": "认证失败,无法访问系统资源",
"403": "当前操作没有权限", "403": "当前操作没有权限",
"404": "访问资源不存在", "404": "访问资源不存在",
default: "系统未知错误,请反馈给管理员", // default: "系统未知错误,请反馈给管理员",
}; };
<script setup lang="ts"> <script setup lang="ts">
defineOptions({
name: "Dashboard",
inheritAttrs: false,
});
import { useUserStore } from "@/store/modules/user";
import { useTransition, TransitionPresets } from "@vueuse/core";
const userStore = useUserStore();
const date: Date = new Date();
const greetings = computed(() => {
const hours = date.getHours();
if (hours >= 6 && hours < 8) {
return "晨起披衣出草堂,轩窗已自喜微凉🌅!";
} else if (hours >= 8 && hours < 12) {
return "上午好," + useUserStore().user.nickname + "!";
} else if (hours >= 12 && hours < 18) {
return "下午好," + useUserStore().user.nickname + "!";
} else if (hours >= 18 && hours < 24) {
return "晚上好," + useUserStore().user.nickname + "!";
} else if (hours >= 0 && hours < 6) {
return "偷偷向银河要了一把碎星,只等你闭上眼睛撒入你的梦中,晚安🌛!";
}
});
const duration = 5000;
// 销售额
const amount = ref(0);
const amountOutput = useTransition(amount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
amount.value = 2000;
// 访客数
const visitCount = ref(0);
const visitCountOutput = useTransition(visitCount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
visitCount.value = 2000;
// IP数
const dauCount = ref(0);
const dauCountOutput = useTransition(dauCount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
dauCount.value = 2000;
// 订单量
const orderCount = ref(0);
const orderCountOutput = useTransition(orderCount, {
duration: duration,
transition: TransitionPresets.easeOutExpo,
});
orderCount.value = 2000;
</script> </script>
<template> <template>
<div class="dashboard-container"> <div class="dashboard-container">
<!-- github角标 --> <div>欢迎进入业务数据管理系统</div>
<github-corner class="github-corner" />
<el-card shadow="never">
<el-row justify="space-between">
<el-col :span="18" :xs="24">
<div class="flex h-full items-center">
<img
class="w-20 h-20 mr-5 rounded-full"
:src="userStore.user.avatar + '?imageView2/1/w/80/h/80'"
/>
<div>
<p>{{ greetings }}</p>
<p class="text-sm text-gray">
今日天气晴朗,气温在15℃至25℃之间,东南风。
</p>
</div>
</div>
</el-col>
<el-col :span="6" :xs="24">
<div class="flex h-full items-center justify-around">
<el-statistic :value="99">
<template #title>
<div class="flex items-center">
<svg-icon icon-class="message" size="20px" />
<span class="text-[16px] ml-1">消息</span>
</div>
</template>
</el-statistic>
<el-statistic :value="50">
<template #title>
<div class="flex items-center">
<svg-icon icon-class="todolist" size="20px" />
<span class="text-[16px] ml-1">待办</span>
</div>
</template>
<template #suffix>/100</template>
</el-statistic>
<el-statistic :value="10">
<template #title>
<div class="flex items-center">
<svg-icon icon-class="project" size="20px" />
<span class="text-[16px] ml-1">项目</span>
</div>
</template>
</el-statistic>
</div>
</el-col>
</el-row>
</el-card>
<!-- 数据卡片 -->
<el-row :gutter="10" class="mt-3">
<el-col :xs="24" :sm="12" :lg="6">
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="text-[var(--el-text-color-secondary)]">访客数</span>
<el-tag type="success"></el-tag>
</div>
</template>
<div class="flex items-center justify-between mt-5">
<div class="text-lg text-right">
{{ Math.round(visitCountOutput) }}
</div>
<svg-icon icon-class="visit" size="2em" />
</div>
<div
class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
>
<span> 总访客数 </span>
<span> {{ Math.round(visitCountOutput * 15) }} </span>
</div>
</el-card>
</el-col>
<!--消息数-->
<el-col :xs="24" :sm="12" :lg="6">
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="text-[var(--el-text-color-secondary)]">IP数</span>
<el-tag type="success"></el-tag>
</div>
</template>
<div class="flex items-center justify-between mt-5">
<div class="text-lg text-right">
{{ Math.round(dauCountOutput) }}
</div>
<svg-icon icon-class="ip" size="2em" />
</div>
<div
class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
>
<span> 总IP数 </span>
<span> {{ Math.round(dauCountOutput) }} </span>
</div>
</el-card>
</el-col>
<!--销售额-->
<el-col :xs="24" :sm="12" :lg="6">
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="text-[var(--el-text-color-secondary)]">销售额</span>
<el-tag></el-tag>
</div>
</template>
<div class="flex items-center justify-between mt-5">
<div class="text-lg text-right">
{{ Math.round(amountOutput) }}
</div>
<svg-icon icon-class="money" size="2em" />
</div>
<div
class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
>
<span> 总销售额 </span>
<span> {{ Math.round(amountOutput * 15) }} </span>
</div>
</el-card>
</el-col>
<!--订单量-->
<el-col :xs="24" :sm="12" :lg="6">
<el-card shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="text-[var(--el-text-color-secondary)]">订单量</span>
<el-tag type="danger"></el-tag>
</div>
</template>
<div class="flex items-center justify-between mt-5">
<div class="text-lg text-right">
{{ Math.round(orderCountOutput) }}
</div>
<svg-icon icon-class="order" size="2em" />
</div>
<div
class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
>
<span> 总订单量 </span>
<span> {{ Math.round(orderCountOutput * 15) }} </span>
</div>
</el-card>
</el-col>
</el-row>
<!-- Echarts 图表 -->
<el-row :gutter="10" class="mt-3">
<el-col :sm="24" :lg="8" class="mb-2">
<BarChart
id="barChart"
height="400px"
width="100%"
class="bg-[var(--el-bg-color-overlay)]"
/>
</el-col>
<el-col :xs="24" :sm="12" :lg="8" class="mb-2">
<PieChart
id="pieChart"
height="400px"
width="100%"
class="bg-[var(--el-bg-color-overlay)]"
/>
</el-col>
<el-col :xs="24" :sm="12" :lg="8" class="mb-2">
<RadarChart
id="radarChart"
height="400px"
width="100%"
class="bg-[var(--el-bg-color-overlay)]"
/>
</el-col>
</el-row>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.dashboard-container { .dashboard-container {
position: relative; width: 100%;
padding: 24px; height: 875px;
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
}
.github-corner {
position: absolute;
top: 0;
right: 0;
z-index: 1;
border: 0;
}
.data-box {
display: flex; display: flex;
justify-content: space-between; justify-content: center;
padding: 20px; align-items: center;
font-weight: bold; font-size: 40px;
color: var(--el-text-color-regular); background-color: white;
background: var(--el-bg-color-overlay);
border-color: var(--el-border-color);
box-shadow: var(--el-box-shadow-dark);
}
.svg-icon {
fill: currentcolor !important;
}
} }
</style> </style>
This diff is collapsed.
This diff is collapsed.
<template>
<div>立项报备
</div>
</template>
<script>
export default {
name: "index"
}
</script>
<style scoped>
</style>
<template>
<div>投标准备</div>
</template>
<script>
export default {
name: "Index",
};
</script>
<style scoped></style>
...@@ -52,7 +52,9 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => { ...@@ -52,7 +52,9 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
[env.VITE_APP_BASE_API]: { [env.VITE_APP_BASE_API]: {
changeOrigin: true, changeOrigin: true,
// 线上接口地址 // 线上接口地址
target: "http://localhost:8082", // target: "http://vapi.youlai.tech",
// target: `http://192.168.1.21:8082/`,
target: `http://106.3.97.198:20024/`,
// 开发接口地址 // 开发接口地址
//target: "http://localhost:8989", //target: "http://localhost:8989",
rewrite: (path) => rewrite: (path) =>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment