feat: 登陆
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><mask id="master_svg0_117_7235" style="mask-type:alpha" maskUnits="objectBoundingBox"><g><path d="M0 0C0 0 0 0 0 0L16 0C16 0 16 0 16 0L16 16C16 16 16 16 16 16L0 16C0 16 0 16 0 16Z" fill="#FFFFFF" fill-opacity="1"/></g></mask></defs><g mask="url(#master_svg0_117_7235)"><g><path d="M3.542045296936035,4.23C3.542045296936035,6.56522,5.470995296936035,8.47,7.8420452969360355,8.47C10.213095296936036,8.47,12.142065296936035,6.56523,12.142065296936035,4.23C12.142065296936035,1.89477,10.212095296936035,0,7.8420452969360355,0C5.471815296936035,0,3.542045296936035,1.89478,3.542045296936035,4.23ZM15.011265296936035,14.1141C15.011265296936035,11.1742,12.575965296936035,8.78406,9.591265296936035,8.78406L6.401265296936035,8.78406C3.416565296936035,8.78406,0.9912652969360352,11.1728,0.9912652969360352,14.1141L0.9912652969360352,14.4341C0.9912652969360352,16.0031,3.376765296936035,16.0041,6.401265296936035,16.0041L9.591265296936035,16.0041C12.496365296936036,16.0041,15.011265296936035,16.0031,15.011265296936035,14.4341L15.011265296936035,14.1141Z" fill-rule="evenodd" fill="#0C90FF" fill-opacity="1"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
||||||
|
<path d="M10,50 C20,30 40,20 50,20 C60,20 80,30 90,50 C80,70 60,80 50,80 C40,80 20,70 10,50 Z" fill="none" stroke="black" stroke-width="5"/>
|
||||||
|
<circle cx="50" cy="50" r="18" fill="none" stroke="black" stroke-width="5"/>
|
||||||
|
<circle cx="50" cy="50" r="8" fill="black"/>
|
||||||
|
<circle cx="50" cy="50" r="4" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 411 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><g><g style="opacity:0;"><path d="M0 0C0 0 0 0 0 0L16 0C16 0 16 0 16 0L16 16C16 16 16 16 16 16L0 16C0 16 0 16 0 16Z" fill="#D8D8D8" fill-opacity="1"/><path d="M0.5 0.5C0.5 0.5 0.5 0.5 0.5 0.5L15.5 0.5C15.5 0.5 15.5 0.5 15.5 0.5L15.5 15.5C15.5 15.5 15.5 15.5 15.5 15.5L0.5 15.5C0.5 15.5 0.5 15.5 0.5 15.5Z" fill-opacity="0" stroke-opacity="0" stroke="#979797" fill="none" stroke-width="1"/></g><g><g><path d="M5.373828125,11.463425234375L9.364458124999999,11.463425234375C9.914448125,11.463425234375,10.361328125,11.910315234375,10.361328125,12.463415234375C10.361328125,13.013415234375,9.914448125,13.458715234375,9.364458124999999,13.458715234375L4.384768125,13.458715234375C3.284765125,13.458715234375,2.392578125,12.566515234375,2.392578125,11.466555234375L2.392578125,3.497805234375C2.392578125,2.397802234375,3.284765125,1.505615234375,4.384768125,1.505615234375L9.364458124999999,1.505615234375C9.916018125,1.505615234375,10.361328125,1.950927234375,10.361328125,2.500928234375C10.361328125,3.0509252343749997,9.914448125,3.496245234375,9.364458124999999,3.496245234375L5.381638125,3.496245234375C4.830078125,3.496245234375,4.384768125,3.938425234375,4.384768125,4.483745234375L4.384768125,10.468115234375C4.384768125,11.018115234375,4.828518125,11.463425234375,5.373828125,11.463425234375ZM12.244528125,4.678955234375L14.236678125,7.168015234375C14.382078125,7.350825234375,14.382078125,7.608645234375,14.235178125,7.788325234375L12.242968125,10.278955234375C12.146098125,10.399265234375,12.002348125,10.466455234375,11.853908125,10.466455234375Q11.771878125,10.466455234375,11.689848125,10.438325234375C11.491408125,10.368015234375,11.357028125,10.178955234375,11.357028125,9.968015234375L11.357028125,8.474265234375L7.374218125,8.474265234375C6.824218125,8.474265234375,6.378908125000001,8.028955234375001,6.378908125000001,7.478955234375C6.378908125000001,6.928955234375,6.824218125,6.483645234375,7.374218125,6.483645234375L11.358598125,6.483645234375L11.358598125,4.989895234375C11.358598125,4.778955234375,11.491408125,4.589895234375,11.691408125,4.519575234375C11.744528125,4.500825234375,11.800778125,4.491455234375,11.855468125,4.491455234375C12.003908125,4.491455234375,12.147658125,4.558645234375,12.244528125,4.678955234375Z" fill-rule="evenodd" fill="#45A2FF" fill-opacity="1"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><mask id="master_svg0_117_7222" style="mask-type:alpha" maskUnits="objectBoundingBox"><g><path d="M0 0C0 0 0 0 0 0L16 0C16 0 16 0 16 0L16 16C16 16 16 16 16 16L0 16C0 16 0 16 0 16Z" fill="#FFFFFF" fill-opacity="1"/></g></mask></defs><g mask="url(#master_svg0_117_7222)"><g><g><path d="M4.646874904632568,3.9093748554587364C4.999999904632569,3.9093748554587364,5.287499904632568,4.195311855458736,5.287499904632568,4.549999855458736L5.287499904632568,7.218754855458736C5.287499904632568,7.571874855458736,5.001562904632569,7.859374855458737,4.646874904632568,7.859374855458737C4.293749904632568,7.859374855458737,4.006249904632568,7.5734348554587365,4.006249904632568,7.218754855458736L4.006249904632568,4.549999855458736C4.006249904632568,4.196874855458736,4.293749904632568,3.9093748554587364,4.646874904632568,3.9093748554587364Z" fill-rule="evenodd" fill="#0C90FF" fill-opacity="1"/></g><g><path d="M10.9609375,3.846874952316284C11.3140625,3.846874952316284,11.6015625,4.132811952316284,11.6015625,4.487499952316284L11.6015625,7.129684952316284C11.6015625,7.482814952316284,11.3156255,7.7703149523162836,10.9609375,7.7703149523162836C10.6078125,7.7703149523162836,10.3203125,7.484374952316284,10.3203125,7.129684952316284L10.3203125,4.487499952316284C10.3203125,4.134374952316284,10.6078125,3.846874952316284,10.9609375,3.846874952316284Z" fill-rule="evenodd" fill="#0C90FF" fill-opacity="1"/></g></g><g><path d="M10.315862738418579,4.492187428474426L11.59586273841858,4.492187428474426C11.59586273841858,2.3953074284744265,9.902742738418578,0.6921874284744263,7.805862738418579,0.6921874284744263C5.7089827384185785,0.6921874284744263,4.005862738418579,2.3953074284744265,4.005862738418579,4.492187428474426L5.2858627384185795,4.492187428474426C5.2858627384185795,3.1015574284744263,6.416802738418579,1.9721874284744263,7.805862738418579,1.9721874284744263C9.19648273841858,1.9721874284744263,10.315862738418579,3.1015574284744263,10.315862738418579,4.492187428474426ZM13.520312738418578,7.529637428474426C13.46721273841858,7.007757428474426,13.040012738418579,6.599947428474426,12.510312738418579,6.579637428474427L3.130312738418579,6.579637428474427C2.5443757384185792,6.579637428474427,2.070312738418579,7.053697428474426,2.070312738418579,7.639637428474426L2.070312738418579,14.579687428474426C2.132812738418579,15.051587428474427,2.500000738418579,15.432487428474426,2.970312738418579,15.499687428474425L12.690312738418578,15.499687428474425C13.11221273841858,15.415287428474427,13.43901273841858,15.081587428474426,13.520312738418578,14.659687428474426L13.520312738418578,7.529637428474426Z" fill-rule="evenodd" fill="#0C90FF" fill-opacity="1"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
|
@ -11,3 +11,9 @@ export const getSegmentStatic = () => `${baseUrl}/api/bigScreenData/bigScreenSta
|
||||||
export const getBigScreenRanking = () => `${baseUrl}/api/bigScreenData/bigScreenRanks`;
|
export const getBigScreenRanking = () => `${baseUrl}/api/bigScreenData/bigScreenRanks`;
|
||||||
|
|
||||||
export const getSixStatisticsUrl = () => `${baseUrl}/api/bigScreenData/wechatData`
|
export const getSixStatisticsUrl = () => `${baseUrl}/api/bigScreenData/wechatData`
|
||||||
|
|
||||||
|
export const postLoginUrl=() => `${baseUrl}/api/sysAuth/BigScreenLogin`
|
||||||
|
|
||||||
|
export const getUserInfoUrl = ()=> `${baseUrl}/api/sysAuth/userInfo`
|
||||||
|
|
||||||
|
export const getLogoutUrl = () => `${baseUrl}/api/sysAuth/logout`
|
||||||
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="480" height="48" viewBox="0 0 480 48"><g><path d="M0,48L480,48L480,0L10.1181,0L0,12.5L0,48L0,48Z" fill="#061E3A" fill-opacity="1"/><path d="M480,48L480,0L10.1181,0L0,12.5L0,48L480,48ZM1,47L479,47L479,1L10.5952,1L1,12.854L1,47Z" fill-rule="evenodd" fill="#2A8EFE" fill-opacity="1"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 407 B |
|
After Width: | Height: | Size: 27 KiB |
|
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<div class="error-container flex flex-col items-center justify-center w-full h-full">
|
||||||
|
<SvgIcon name="error" class="w-[48px] h-[48px] text-red-500 mb-2"/>
|
||||||
|
<div class="text-red-500 text-sm">加载失败</div>
|
||||||
|
<button
|
||||||
|
class="mt-2 px-4 py-1 bg-[#45A2FF] text-white rounded hover:bg-[#3d8fe0] transition-colors"
|
||||||
|
@click="retry"
|
||||||
|
>
|
||||||
|
重试
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['retry']);
|
||||||
|
|
||||||
|
const retry = () => {
|
||||||
|
emit('retry');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.error-container {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<div class="loading-container flex items-center justify-center w-full h-full">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.loading-container {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #45A2FF;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import { publicRoutes } from "./publicRoutes";
|
import { publicRoutes } from "./publicRoutes";
|
||||||
import { privateRoutes } from "./privateRoutes";
|
import { privateRoutes } from "./privateRoutes";
|
||||||
|
import { useUserStore } from "@/store/user";
|
||||||
|
|
||||||
function getRoutes() {
|
function getRoutes() {
|
||||||
const routes = [
|
const routes = [
|
||||||
// 私有路由,请在这里添加
|
// 私有路由,请在这里添加
|
||||||
|
|
@ -16,6 +18,7 @@ function getRoutes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: getRoutes(),
|
routes: getRoutes(),
|
||||||
|
|
@ -23,19 +26,26 @@ const router = createRouter({
|
||||||
|
|
||||||
// 全局前置守卫,这边可以对身份进行验证
|
// 全局前置守卫,这边可以对身份进行验证
|
||||||
router.beforeEach((to, _from, next) => {
|
router.beforeEach((to, _from, next) => {
|
||||||
|
const userStore = useUserStore()
|
||||||
let userRole = "admin";
|
let userRole = "";
|
||||||
|
let hasLogin = userStore.getAccessToken;
|
||||||
|
console.log(hasLogin);
|
||||||
|
|
||||||
// 如果目标路由没有角色限制
|
// 如果目标路由没有角色限制
|
||||||
if (!to.meta.role) {
|
if (!to.meta.role) {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
console.log(to.meta.role);
|
||||||
|
|
||||||
// 判断当前用户角色是否在目标路由的允许角色列表中
|
// 判断当前用户角色是否在目标路由的允许角色列表中
|
||||||
if ((to.meta.role as string[]).includes(userRole)) {
|
if ((to.meta.role as string[]).includes(userRole)) {
|
||||||
// 如果角色匹配,允许进入目标路由
|
// 如果角色匹配,允许进入目标路由
|
||||||
|
next();
|
||||||
|
}else if(hasLogin){
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
// 如果角色不匹配,跳转到 unauthorized 页面
|
// 如果角色不匹配,跳转到 unauthorized 页面
|
||||||
next({ path: "/unauthorized" });
|
next({ path: "/login" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,11 @@
|
||||||
export const privateRoutes = [];
|
export const privateRoutes = [
|
||||||
|
{
|
||||||
|
path:"/",
|
||||||
|
name:'home',
|
||||||
|
meta:{
|
||||||
|
title:'主页',
|
||||||
|
role:['admin']
|
||||||
|
},
|
||||||
|
component:()=>import('../views/home.vue')
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ export const publicRoutes = [
|
||||||
},
|
},
|
||||||
component:()=>import('../views/unauthorized.vue')
|
component:()=>import('../views/unauthorized.vue')
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path:"/",
|
path:"/login",
|
||||||
name:'home',
|
name:'login',
|
||||||
meta:{
|
meta:{
|
||||||
title:'home'
|
title:'登陆'
|
||||||
},
|
},
|
||||||
component:()=>import('../views/home.vue')
|
component:()=>import('../views/login.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -2,16 +2,23 @@ import { defineStore } from "pinia";
|
||||||
export const useUserStore = defineStore("user", {
|
export const useUserStore = defineStore("user", {
|
||||||
persist:true,
|
persist:true,
|
||||||
state:()=>({
|
state:()=>({
|
||||||
token:""
|
accessToken:"",
|
||||||
|
refreshToken:""
|
||||||
}),
|
}),
|
||||||
getters:{
|
getters:{
|
||||||
getToken(state){
|
getAccessToken(state){
|
||||||
return state.token;
|
return state.accessToken;
|
||||||
|
},
|
||||||
|
getRefreshToken(state){
|
||||||
|
return state.refreshToken
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions:{
|
actions:{
|
||||||
setToken(token:string){
|
setAccessToken(token:string){
|
||||||
this.token = token;
|
this.accessToken = token;
|
||||||
|
},
|
||||||
|
setRefreshToken(token:string){
|
||||||
|
this.refreshToken = token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-[926px] h-[358px] relative bg-[#082059]">
|
<div class="h-[358px] relative bg-[#082059]">
|
||||||
<div class="flex h-full custom-border absolute top-0 left-0">
|
<div class="flex h-full custom-border absolute top-0 left-0">
|
||||||
<div class="relative h-[36px]">
|
<div class="relative h-[36px]">
|
||||||
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
|
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,16 @@
|
||||||
watch(
|
watch(
|
||||||
() => chargingRankingData.value,
|
() => chargingRankingData.value,
|
||||||
() => {
|
() => {
|
||||||
if(chargingRankingData.value.paymentRanks){
|
initData()
|
||||||
products.value = chargingRankingData.value.paymentRanks
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if(chargingRankingData.value.paymentRanks){
|
||||||
|
products.value = chargingRankingData.value.paymentRanks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const headerLeftSvg = ref("");
|
const headerLeftSvg = ref("");
|
||||||
const headerRightSvg = ref("");
|
const headerRightSvg = ref("");
|
||||||
|
|
||||||
|
|
@ -115,6 +118,8 @@
|
||||||
getGoldMedalSvg();
|
getGoldMedalSvg();
|
||||||
getSilverMedalSvg();
|
getSilverMedalSvg();
|
||||||
getBronzeMedalSvg();
|
getBronzeMedalSvg();
|
||||||
|
|
||||||
|
initData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,21 +77,26 @@
|
||||||
watch(
|
watch(
|
||||||
() => askSectionData.value,
|
() => askSectionData.value,
|
||||||
() => {
|
() => {
|
||||||
if (askSectionData.value.offline.length > 0) {
|
initData()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if (askSectionData.value.offline && askSectionData.value.offline.length > 0) {
|
||||||
chartData.value = askSectionData.value.offline.map((item, index) => ({
|
chartData.value = askSectionData.value.offline.map((item, index) => ({
|
||||||
name: item.tag,
|
name: item.tag,
|
||||||
value: item.total,
|
value: item.total,
|
||||||
color: colorList.value[index % colorList.value.length],
|
color: colorList.value[index % colorList.value.length],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
getHeaderLeftSvg();
|
getHeaderLeftSvg();
|
||||||
getHeaderRightSvg();
|
getHeaderRightSvg();
|
||||||
getArrowLeftSvg();
|
getArrowLeftSvg();
|
||||||
getPaymentChartSvg();
|
getPaymentChartSvg();
|
||||||
|
initData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,20 +76,26 @@
|
||||||
const chartData = ref<any[]>([]);
|
const chartData = ref<any[]>([]);
|
||||||
|
|
||||||
watch(() => askSectionData.value, () => {
|
watch(() => askSectionData.value, () => {
|
||||||
if (askSectionData.value.online.length > 0) {
|
initData()
|
||||||
|
});
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if (askSectionData.value.online && askSectionData.value.online.length > 0) {
|
||||||
chartData.value = askSectionData.value.online.map((item, index) => ({
|
chartData.value = askSectionData.value.online.map((item, index) => ({
|
||||||
name: item.tag,
|
name: item.tag,
|
||||||
value: item.total,
|
value: item.total,
|
||||||
color: colorList.value[index % colorList.value.length],
|
color: colorList.value[index % colorList.value.length],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
getHeaderLeftSvg();
|
getHeaderLeftSvg();
|
||||||
getHeaderRightSvg();
|
getHeaderRightSvg();
|
||||||
getArrowLeftSvg();
|
getArrowLeftSvg();
|
||||||
getPaymentChartSvg();
|
getPaymentChartSvg();
|
||||||
|
|
||||||
|
initData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-[926px] h-[358px] relative bg-[#082059]">
|
<div class="h-[358px] relative bg-[#082059]">
|
||||||
<div class="flex h-full custom-border absolute top-0 left-0">
|
<div class="flex h-full custom-border absolute top-0 left-0">
|
||||||
<div class="relative h-[36px]">
|
<div class="relative h-[36px]">
|
||||||
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
|
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
addRequest("getAcqTrendData", getAcqTrendData, [DateType]);
|
addRequest("getAcqTrendData", getAcqTrendData, [DateType]);
|
||||||
|
runImmediatelyByKey("getAcqTrendData");
|
||||||
|
|
||||||
const handleDateChange = (type: string) => {
|
const handleDateChange = (type: string) => {
|
||||||
DateType = type === "week" ? 0 : 1;
|
DateType = type === "week" ? 0 : 1;
|
||||||
|
|
|
||||||
|
|
@ -11,35 +11,29 @@
|
||||||
<SvgComponent :content="headerRightSvg" class="w-[108px] h-[36px]" />
|
<SvgComponent :content="headerRightSvg" class="w-[108px] h-[36px]" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-[calc(100%-36px)] mt-[36px] flex flex-col">
|
<div class="w-full h-[calc(100%-36px)] mt-[36px] flex flex-col">
|
||||||
<StudentSourceChart
|
<StudentSourceChart class="w-full h-full" :chartData="chartData" :ringSize="0.8" />
|
||||||
class="w-full h-full"
|
<div class="flex items-center justify-between mx-[20px] my-[33px]">
|
||||||
:chartData="[
|
<div class="flex items-center">
|
||||||
{ name: '线下', value: offlineTotal, itemStyle: { color: 'rgba(147, 219, 255, 1)' } },
|
<SvgComponent :content="onlineSvg" class="w-[53px] h-[38px]" />
|
||||||
{ name: '线上', value: onlineTotal, itemStyle: { color: 'rgb(79, 214, 169)' } },
|
<div class="flex flex-col ml-[9px] items-center">
|
||||||
]"
|
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700">
|
||||||
:ringSize="0.8" />
|
<span class="text-[18px]">{{ onlineTotal }}</span>
|
||||||
<div class="flex items-center justify-between mx-[20px] my-[33px]">
|
<span class="text-[14px]">人</span>
|
||||||
<div class="flex items-center">
|
|
||||||
<SvgComponent :content="onlineSvg" class="w-[53px] h-[38px]" />
|
|
||||||
<div class="flex flex-col ml-[9px] items-center">
|
|
||||||
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700">
|
|
||||||
<span class="text-[18px]">{{onlineTotal }}</span>
|
|
||||||
<span class="text-[14px]">人</span>
|
|
||||||
</div>
|
|
||||||
<span class="text-[#C7F0FF] text-[12px]">线上来源</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<SvgComponent :content="offlineSvg" class="w-[53px] h-[38px]" />
|
|
||||||
<div class="flex flex-col ml-[9px] items-center">
|
|
||||||
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700">
|
|
||||||
<span class="text-[18px]">{{ offlineTotal }}</span>
|
|
||||||
<span class="text-[14px]">人</span>
|
|
||||||
</div>
|
|
||||||
<span class="text-[#C7F0FF] text-[12px]">线下来源</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span class="text-[#C7F0FF] text-[12px]">线上来源</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<SvgComponent :content="offlineSvg" class="w-[53px] h-[38px]" />
|
||||||
|
<div class="flex flex-col ml-[9px] items-center">
|
||||||
|
<div class="bg-gradient-to-b from-[#8FC8FF] to-white bg-clip-text text-transparent font-700">
|
||||||
|
<span class="text-[18px]">{{ offlineTotal }}</span>
|
||||||
|
<span class="text-[14px]">人</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[#C7F0FF] text-[12px]">线下来源</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -67,31 +61,43 @@
|
||||||
arrowLeftSvg.value = svg;
|
arrowLeftSvg.value = svg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onlineSvg = ref("")
|
const onlineSvg = ref("");
|
||||||
const offlineSvg = ref("")
|
const offlineSvg = ref("");
|
||||||
const getOfflineSvg = async () => {
|
const getOfflineSvg = async () => {
|
||||||
const {default: svg} = await import('/src/assets/svg-img/offline.svg?raw')
|
const { default: svg } = await import("/src/assets/svg-img/offline.svg?raw");
|
||||||
offlineSvg.value = svg
|
offlineSvg.value = svg;
|
||||||
}
|
};
|
||||||
const getOnlineSvg = async () => {
|
const getOnlineSvg = async () => {
|
||||||
const {default: svg} = await import('/src/assets/svg-img/online.svg?raw')
|
const { default: svg } = await import("/src/assets/svg-img/online.svg?raw");
|
||||||
onlineSvg.value = svg
|
onlineSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const askSectionData = inject("askSectionData", ref<{ online: any[]; offline: any[] }>({ online: [], offline: [] }));
|
||||||
|
|
||||||
|
|
||||||
|
const onlineTotal = ref(0);
|
||||||
|
const offlineTotal = ref(0);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
watch(
|
||||||
|
() => askSectionData.value,
|
||||||
|
() => {
|
||||||
|
initData()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if (askSectionData.value.online && askSectionData.value.online.length > 0) {
|
||||||
|
onlineTotal.value = askSectionData.value.online.reduce((acc, curr) => acc + curr.total, 0);
|
||||||
|
}
|
||||||
|
if (askSectionData.value.offline && askSectionData.value.offline.length > 0) {
|
||||||
|
offlineTotal.value = askSectionData.value.offline.reduce((acc, curr) => acc + curr.total, 0);
|
||||||
|
}
|
||||||
|
chartData.value = [
|
||||||
|
{ name: "线下", value: offlineTotal.value, itemStyle: { color: "rgba(147, 219, 255, 1)" } },
|
||||||
|
{ name: "线上", value: onlineTotal.value, itemStyle: { color: "rgb(79, 214, 169)" } },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const askSectionData = inject("askSectionData",ref<{online:any[],offline:any[]}>({online:[],offline:[]}))
|
|
||||||
|
|
||||||
const onlineTotal = ref(0)
|
|
||||||
const offlineTotal = ref(0)
|
|
||||||
watch(() =>askSectionData.value,() => {
|
|
||||||
|
|
||||||
if(askSectionData.value.online.length > 0){
|
|
||||||
onlineTotal.value = askSectionData.value.online.reduce((acc, curr) => acc + curr.total, 0)
|
|
||||||
}
|
|
||||||
if(askSectionData.value.offline.length > 0){
|
|
||||||
offlineTotal.value = askSectionData.value.offline.reduce((acc, curr) => acc + curr.total, 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
getHeaderLeftSvg();
|
getHeaderLeftSvg();
|
||||||
getHeaderRightSvg();
|
getHeaderRightSvg();
|
||||||
|
|
@ -99,6 +105,7 @@
|
||||||
|
|
||||||
getOfflineSvg();
|
getOfflineSvg();
|
||||||
getOnlineSvg();
|
getOnlineSvg();
|
||||||
|
initData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,15 @@
|
||||||
watch(
|
watch(
|
||||||
() => chargingRankingData.value,
|
() => chargingRankingData.value,
|
||||||
() => {PageTransitionEvent
|
() => {PageTransitionEvent
|
||||||
if(chargingRankingData.value.acqRanks){
|
initData()
|
||||||
products.value = chargingRankingData.value.acqRanks
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if(chargingRankingData.value.acqRanks){
|
||||||
|
products.value = chargingRankingData.value.acqRanks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const headerLeftSvg = ref("");
|
const headerLeftSvg = ref("");
|
||||||
const headerRightSvg = ref("");
|
const headerRightSvg = ref("");
|
||||||
|
|
@ -116,6 +120,8 @@
|
||||||
getGoldMedalSvg();
|
getGoldMedalSvg();
|
||||||
getSilverMedalSvg();
|
getSilverMedalSvg();
|
||||||
getBronzeMedalSvg();
|
getBronzeMedalSvg();
|
||||||
|
|
||||||
|
initData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -317,12 +317,14 @@
|
||||||
updateChartData();
|
updateChartData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true,immediate:true }
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if(!myChart){
|
||||||
initChart();
|
initChart();
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,20 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.chartData,() => {
|
watch(() => props.chartData,() => {
|
||||||
initChart();
|
if (chart) {
|
||||||
})
|
|
||||||
|
const options = {
|
||||||
|
xAxis: {
|
||||||
|
data: props.chartData.map((item:any) => item.date)
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
data: props.chartData.map((item:any) => item.total)
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
chart.setOption(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
if(!chartRef.value) return;
|
if(!chartRef.value) return;
|
||||||
|
|
@ -81,7 +93,7 @@ const initChart = () => {
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '数据1',
|
name: '获客数',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
data: props.chartData.map((item:any) => item.total),
|
data: props.chartData.map((item:any) => item.total),
|
||||||
|
|
@ -114,7 +126,9 @@ const initChart = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if(!chart){
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
chart?.resize();
|
chart?.resize();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import { CanvasRenderer } from "echarts/renderers";
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
import { SurfaceChart } from "echarts-gl/charts";
|
import { SurfaceChart } from "echarts-gl/charts";
|
||||||
import { Grid3DComponent } from "echarts-gl/components";
|
import { Grid3DComponent } from "echarts-gl/components";
|
||||||
import { ref, onMounted, watch } from "vue";
|
import { ref, onMounted, watch, onBeforeUnmount } from "vue";
|
||||||
|
|
||||||
echarts.use([TooltipComponent, Grid3DComponent, SurfaceChart, CanvasRenderer]);
|
echarts.use([TooltipComponent, Grid3DComponent, SurfaceChart, CanvasRenderer]);
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartRef = ref<HTMLElement | null>(null);
|
const chartRef = ref<HTMLElement | null>(null);
|
||||||
let chart: echarts.ECharts | null = null;
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
interface SeriesItem {
|
interface SeriesItem {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
@ -206,7 +206,7 @@
|
||||||
height: 106,
|
height: 106,
|
||||||
},
|
},
|
||||||
z: 0,
|
z: 0,
|
||||||
cursor:"point",
|
cursor: "point",
|
||||||
origin: [127, 53],
|
origin: [127, 53],
|
||||||
keyframeAnimation: [
|
keyframeAnimation: [
|
||||||
{
|
{
|
||||||
|
|
@ -241,173 +241,14 @@
|
||||||
// 初始化图表
|
// 初始化图表
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
if (!chartRef.value) return;
|
if (!chartRef.value) return;
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
chart = echarts.init(chartRef.value);
|
updateChart();
|
||||||
const option = getPie3D(props.chartData, props.ringSize);
|
|
||||||
|
|
||||||
option.series.push({
|
|
||||||
name: "pie2d",
|
|
||||||
type: "pie",
|
|
||||||
labelLine: {
|
|
||||||
length: 18,
|
|
||||||
length2: 18,
|
|
||||||
smooth: true,
|
|
||||||
minTurnAngle: 20,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: "outside",
|
|
||||||
color: "inherit",
|
|
||||||
rich: {
|
|
||||||
a: {
|
|
||||||
width: 5,
|
|
||||||
height: 5,
|
|
||||||
borderRadius: 50,
|
|
||||||
backgroundColor: "inherit",
|
|
||||||
},
|
|
||||||
b: {
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
c: {
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
formatter: "{a|} {b|{b}}{d|{d}}%",
|
|
||||||
},
|
|
||||||
startAngle: -30,
|
|
||||||
clockwise: false,
|
|
||||||
radius: ["30%", "60%"],
|
|
||||||
padAngle: 360,
|
|
||||||
center: ["50%", "50%"],
|
|
||||||
data: props.chartData,
|
|
||||||
itemStyle: {
|
|
||||||
opacity: 1,
|
|
||||||
borderWidth: 0,
|
|
||||||
},
|
|
||||||
z: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
chart.setOption(option);
|
|
||||||
bindEvents();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 绑定事件
|
const updateChart = () => {
|
||||||
const bindEvents = () => {
|
if (!chartInstance) return;
|
||||||
if (!chart) return;
|
const option = getPie3D(props.chartData, props.ringSize);
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
// let selectedIndex = "";
|
|
||||||
// let hoveredIndex = "";
|
|
||||||
|
|
||||||
// chart.on("click", (params: any) => {
|
|
||||||
// if (!chart) return;
|
|
||||||
|
|
||||||
// const option = chart.getOption() as any;
|
|
||||||
// const isSelected = !option.series[params.seriesIndex].pieStatus.selected;
|
|
||||||
// const isHovered = option.series[params.seriesIndex].pieStatus.hovered;
|
|
||||||
// const k = option.series[params.seriesIndex].pieStatus.k;
|
|
||||||
// const startRatio = option.series[params.seriesIndex].pieData.startRatio;
|
|
||||||
// const endRatio = option.series[params.seriesIndex].pieData.endRatio;
|
|
||||||
|
|
||||||
// if (selectedIndex !== "" && selectedIndex !== params.seriesIndex) {
|
|
||||||
// option.series[selectedIndex].parametricEquation = getParametricEquation(
|
|
||||||
// option.series[selectedIndex].pieData.startRatio,
|
|
||||||
// option.series[selectedIndex].pieData.endRatio,
|
|
||||||
// false,
|
|
||||||
// false,
|
|
||||||
// k,
|
|
||||||
// option.series[selectedIndex].pieData.value,
|
|
||||||
// );
|
|
||||||
// option.series[selectedIndex].pieStatus.selected = false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// option.series[params.seriesIndex].parametricEquation = getParametricEquation(
|
|
||||||
// startRatio,
|
|
||||||
// endRatio,
|
|
||||||
// isSelected,
|
|
||||||
// isHovered,
|
|
||||||
// k,
|
|
||||||
// option.series[params.seriesIndex].pieData.value,
|
|
||||||
// );
|
|
||||||
// option.series[params.seriesIndex].pieStatus.selected = isSelected;
|
|
||||||
|
|
||||||
// isSelected ? (selectedIndex = params.seriesIndex) : null;
|
|
||||||
// chart.setOption(option);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 鼠标悬停事件
|
|
||||||
// chart.on("mouseover", (params: any) => {
|
|
||||||
// if (!chart) return;
|
|
||||||
|
|
||||||
// const option = chart.getOption() as any;
|
|
||||||
// if (hoveredIndex === params.seriesIndex) return;
|
|
||||||
|
|
||||||
// if (hoveredIndex !== "") {
|
|
||||||
// const isSelected = option.series[hoveredIndex].pieStatus.selected;
|
|
||||||
// const isHovered = false;
|
|
||||||
// const k = option.series[hoveredIndex].pieStatus.k;
|
|
||||||
// const startRatio = option.series[hoveredIndex].pieData.startRatio;
|
|
||||||
// const endRatio = option.series[hoveredIndex].pieData.endRatio;
|
|
||||||
|
|
||||||
// option.series[hoveredIndex].parametricEquation = getParametricEquation(
|
|
||||||
// startRatio,
|
|
||||||
// endRatio,
|
|
||||||
// isSelected,
|
|
||||||
// isHovered,
|
|
||||||
// k,
|
|
||||||
// option.series[hoveredIndex].pieData.value,
|
|
||||||
// );
|
|
||||||
// option.series[hoveredIndex].pieStatus.hovered = isHovered;
|
|
||||||
// hoveredIndex = "";
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (params.seriesName !== "mouseoutSeries" && params.seriesName !== "pie2d") {
|
|
||||||
// const isSelected = option.series[params.seriesIndex].pieStatus.selected;
|
|
||||||
// const isHovered = true;
|
|
||||||
// const k = option.series[params.seriesIndex].pieStatus.k;
|
|
||||||
// const startRatio = option.series[params.seriesIndex].pieData.startRatio;
|
|
||||||
// const endRatio = option.series[params.seriesIndex].pieData.endRatio;
|
|
||||||
|
|
||||||
// option.series[params.seriesIndex].parametricEquation = getParametricEquation(
|
|
||||||
// startRatio,
|
|
||||||
// endRatio,
|
|
||||||
// isSelected,
|
|
||||||
// isHovered,
|
|
||||||
// k,
|
|
||||||
// option.series[params.seriesIndex].pieData.value + 5,
|
|
||||||
// );
|
|
||||||
// option.series[params.seriesIndex].pieStatus.hovered = isHovered;
|
|
||||||
// hoveredIndex = params.seriesIndex;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// chart.setOption(option);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // 鼠标移出事件
|
|
||||||
// chart.on("globalout", () => {
|
|
||||||
// if (!chart) return;
|
|
||||||
|
|
||||||
// const option = chart.getOption() as any;
|
|
||||||
// if (hoveredIndex !== "") {
|
|
||||||
// const isSelected = option.series[hoveredIndex].pieStatus.selected;
|
|
||||||
// const isHovered = false;
|
|
||||||
// const k = option.series[hoveredIndex].pieStatus.k;
|
|
||||||
// const startRatio = option.series[hoveredIndex].pieData.startRatio;
|
|
||||||
// const endRatio = option.series[hoveredIndex].pieData.endRatio;
|
|
||||||
|
|
||||||
// option.series[hoveredIndex].parametricEquation = getParametricEquation(
|
|
||||||
// startRatio,
|
|
||||||
// endRatio,
|
|
||||||
// isSelected,
|
|
||||||
// isHovered,
|
|
||||||
// k,
|
|
||||||
// option.series[hoveredIndex].pieData.value,
|
|
||||||
// );
|
|
||||||
// option.series[hoveredIndex].pieStatus.hovered = isHovered;
|
|
||||||
// hoveredIndex = "";
|
|
||||||
// }
|
|
||||||
|
|
||||||
// chart.setOption(option);
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const total = computed(() => props.chartData.reduce((sum: number, item: any) => sum + item.value, 0));
|
const total = computed(() => props.chartData.reduce((sum: number, item: any) => sum + item.value, 0));
|
||||||
|
|
@ -416,15 +257,32 @@
|
||||||
watch(
|
watch(
|
||||||
() => props.chartData,
|
() => props.chartData,
|
||||||
() => {
|
() => {
|
||||||
initChart();
|
if (chartInstance) {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// 组件挂载时初始化
|
// 组件挂载时初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initChart();
|
if (!chartInstance) {
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@
|
||||||
<div class="absolute top-[25%] right-[24px] translate-y-[-25%] z-1 flex items-center justify-center w-max">
|
<div class="absolute top-[25%] right-[24px] translate-y-[-25%] z-1 flex items-center justify-center w-max">
|
||||||
<div class="text-[#45A2FF] text-[14px]">{{ year }}-{{ month }}-{{ day }} {{ weekday }}</div>
|
<div class="text-[#45A2FF] text-[14px]">{{ year }}-{{ month }}-{{ day }} {{ weekday }}</div>
|
||||||
<DigitalWatch class="ml-[10px]" />
|
<DigitalWatch class="ml-[10px]" />
|
||||||
|
<div class="text-[#45A2FF] flex items-center ml-[10px]">
|
||||||
|
<SvgIcon name="avatar" class="w-[16px] h-[16px]"/>
|
||||||
|
<div class="mx-[5px]">{{ username }}</div>
|
||||||
|
<SvgIcon name="logout" class="w-[16px] h-[16px] cursor-pointer" @click="handleLogout"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="flex items-center justify-end pr-[24px] cursor-pointer mb-[13px]" @click="updateAllData">
|
<div class="flex items-center justify-end pr-[24px] cursor-pointer mb-[13px]" @click="updateAllData">
|
||||||
|
|
@ -15,21 +20,21 @@
|
||||||
|
|
||||||
<div class="grid grid-rows-[210px_358px_320px] gap-y-5 w-full min-h-[928px] gap-[20px] flex-1 overflow-auto mb-[27px]">
|
<div class="grid grid-rows-[210px_358px_320px] gap-y-5 w-full min-h-[928px] gap-[20px] flex-1 overflow-auto mb-[27px]">
|
||||||
<div class="flex items-center px-[24px] justify-start">
|
<div class="flex items-center px-[24px] justify-start">
|
||||||
<PaymentTotal />
|
<PaymentTotal class="flex-1"/>
|
||||||
<TodayPayment class="ml-[20px]" />
|
<TodayPayment class="ml-[20px] flex-1" />
|
||||||
<GainTotal class="ml-[20px]" />
|
<GainTotal class="ml-[20px] flex-1" />
|
||||||
<GainToday class="ml-[20px]" />
|
<GainToday class="ml-[20px] flex-1" />
|
||||||
<LossStatic class="ml-[20px]" />
|
<LossStatic class="ml-[20px] flex-1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-[24px] justify-start">
|
<div class="grid grid-cols-2 items-center px-[24px] gap-x-[20px]">
|
||||||
<OperatingTrends class="" />
|
<OperatingTrends class="flex-1" />
|
||||||
<AskSection class="ml-[20px]" />
|
<AskSection class="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-[24px] overflow-x-auto">
|
<div class="flex items-center px-[24px] overflow-x-auto">
|
||||||
<StudentSource />
|
<StudentSource class="min-w-[296px] max-w-[296px]" />
|
||||||
<OnLineStatus class="ml-[20px]" />
|
<OnLineStatus class="ml-[20px] min-w-[296px] max-w-[296px]" />
|
||||||
<OfflineStatus class="ml-[20px]" />
|
<OfflineStatus class="ml-[20px] min-w-[296px] max-w-[296px]" />
|
||||||
<SixStatistics class="ml-[20px]" />
|
<SixStatistics class="ml-[20px] min-w-[296px] max-w-[296px]" />
|
||||||
<ChargingRanking class="ml-[20px]" />
|
<ChargingRanking class="ml-[20px]" />
|
||||||
<WinCustomer class="ml-[20px]" />
|
<WinCustomer class="ml-[20px]" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -40,28 +45,87 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import SvgComponent from "@/components/SvgComponent.vue";
|
import SvgComponent from "@/components/SvgComponent.vue";
|
||||||
import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
|
import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
|
||||||
|
import LoadingComponent from "@/components/LoadingComponent.vue";
|
||||||
|
import ErrorComponent from "@/components/ErrorComponent.vue";
|
||||||
import DigitalWatch from "@/components/watch/DigitalWatch.vue";
|
import DigitalWatch from "@/components/watch/DigitalWatch.vue";
|
||||||
import PaymentTotal from "@/views/components/PaymentTotal.vue";
|
|
||||||
import TodayPayment from "@/views/components/TodayPayment.vue";
|
import { defineAsyncComponent } from 'vue';
|
||||||
import GainTotal from "@/views/components/GainTotal.vue";
|
|
||||||
import GainToday from "@/views/components/GainToday.vue";
|
const asyncComponentConfig = {
|
||||||
import LossStatic from "@/views/components/LossStatic.vue";
|
loadingComponent: LoadingComponent,
|
||||||
import OperatingTrends from "@/views/components/OperatingTrends.vue";
|
errorComponent: ErrorComponent,
|
||||||
import AskSection from "@/views/components/AskSection.vue";
|
onError: (error: Error, retry: () => void, fail: () => void, attempts: number) => {
|
||||||
import StudentSource from "@/views/components/StudentSource.vue";
|
if (attempts <= 3) {
|
||||||
import OnLineStatus from "@/views/components/OnlineStatus.vue";
|
retry();
|
||||||
import OfflineStatus from "@/views/components/OfflineStatus.vue";
|
} else {
|
||||||
import SixStatistics from "@/views/components/SixStatistics.vue";
|
fail();
|
||||||
import ChargingRanking from "./components/ChargingRanking.vue";
|
}
|
||||||
import WinCustomer from "./components/WinCustomer.vue";
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaymentTotal = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/PaymentTotal.vue")
|
||||||
|
});
|
||||||
|
const TodayPayment = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/TodayPayment.vue")
|
||||||
|
});
|
||||||
|
const GainTotal = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/GainTotal.vue")
|
||||||
|
});
|
||||||
|
const GainToday = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/GainToday.vue")
|
||||||
|
});
|
||||||
|
const LossStatic = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/LossStatic.vue")
|
||||||
|
});
|
||||||
|
const OperatingTrends = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/OperatingTrends.vue")
|
||||||
|
});
|
||||||
|
const AskSection = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/AskSection.vue")
|
||||||
|
});
|
||||||
|
const StudentSource = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/StudentSource.vue")
|
||||||
|
});
|
||||||
|
const OnLineStatus = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/OnlineStatus.vue")
|
||||||
|
});
|
||||||
|
const OfflineStatus = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/OfflineStatus.vue")
|
||||||
|
});
|
||||||
|
const SixStatistics = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("@/views/components/SixStatistics.vue")
|
||||||
|
});
|
||||||
|
const ChargingRanking = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("./components/ChargingRanking.vue")
|
||||||
|
});
|
||||||
|
const WinCustomer = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("./components/WinCustomer.vue")
|
||||||
|
});
|
||||||
|
|
||||||
import { useDate } from "@/composables/useDate";
|
import { useDate } from "@/composables/useDate";
|
||||||
import {useFetchAllData} from "./composables/useFetchData"
|
import { useFetchAllData } from "./composables/useFetchData"
|
||||||
import { runImmediatelyAll, updateTime } from "@/composables/usePolling";
|
import { runImmediatelyAll, updateTime } from "@/composables/usePolling";
|
||||||
import { formatDatetime } from "@/utils/date";
|
import { formatDatetime } from "@/utils/date";
|
||||||
|
import {getUserInfoUrl,getLogoutUrl} from "@/api/fetchUrl";
|
||||||
|
import { getRequest, postRequest } from "@/api/customFetch";
|
||||||
|
import { useUserStore } from "@/store/user";
|
||||||
|
|
||||||
const { year, month, day, weekday, formateTime } = useDate();
|
const { year, month, day, weekday, formateTime } = useDate();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const headerSvg = ref("");
|
const headerSvg = ref("");
|
||||||
|
|
@ -78,15 +142,39 @@
|
||||||
|
|
||||||
|
|
||||||
useFetchAllData()
|
useFetchAllData()
|
||||||
|
|
||||||
const updateAllData = () => {
|
const updateAllData = () => {
|
||||||
runImmediatelyAll()
|
runImmediatelyAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const username = ref("")
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const getUserInfo = () => {
|
||||||
|
const accessToken = userStore.getAccessToken
|
||||||
|
getRequest(getUserInfoUrl(),{},{headers:{ Authorization: `Bearer ${accessToken}`}}).then(resp => {
|
||||||
|
if(resp.code === 200){
|
||||||
|
username.value = (resp.result as {account:string}).account
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
const accessToken = userStore.getAccessToken
|
||||||
|
postRequest(getLogoutUrl(),{},{headers:{"content-type": "application/json; charset=utf-8",Authorization: `Bearer ${accessToken}`}}).then(resp => {
|
||||||
|
if(resp.code === 200){
|
||||||
|
userStore.setAccessToken('')
|
||||||
|
userStore.setRefreshToken('')
|
||||||
|
|
||||||
|
}
|
||||||
|
router.push("/login")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
formateTime();
|
formateTime();
|
||||||
headerBackgroundSvg();
|
headerBackgroundSvg();
|
||||||
headerTitleSvg();
|
headerTitleSvg();
|
||||||
|
getUserInfo();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
<template>
|
||||||
|
<div class="login-bg">
|
||||||
|
<SvgComponent :content="titleSvg" class="h-[156px] mt-[141px]" />
|
||||||
|
|
||||||
|
<div class="login-form-wrapper w-[622px] h-[419px] mx-auto mt-[87px]">
|
||||||
|
<form class="w-full h-full flex flex-col items-center pt-[126px]" @submit="handleSubmit">
|
||||||
|
<div class="form-item px-[71px] w-full">
|
||||||
|
<div class="input-bg w-full h-[48px] flex items-center px-[18px]">
|
||||||
|
<SvgIcon name="avatar" class="text-[#00D5FF] mr-[13px]" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="formData.username"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
class="flex-1 h-[48px] text-white placeholder:text-[#666666] focus:outline-none focus:border-[#29F1FA] bg-transparent" />
|
||||||
|
</div>
|
||||||
|
<div class="input-bg w-full h-[48px] flex items-center px-[18px] mt-[20px]">
|
||||||
|
<SvgIcon :name="showPassword ? 'eye' : 'password'" class="text-[#00D5FF] mr-[13px] cursor-pointer" @click="togglePasswordVisibility" />
|
||||||
|
<input
|
||||||
|
:type="showPassword ? 'text' : 'password'"
|
||||||
|
v-model="formData.password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
class="flex-1 h-[48px] text-white placeholder:text-[#666666] focus:outline-none focus:border-[#29F1FA] bg-transparent" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="w-full h-[48px] bg-[#2A8EFE] text-[#fff] text-[18px] font-700 border-submit mt-[48px]">登录</button>
|
||||||
|
<p class="text-[#657295] mt-[10px] text-center text-[14px]">如忘记账号密码,请联系管理员:18845000222</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import SvgComponent from "@/components/SvgComponent.vue";
|
||||||
|
import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
|
||||||
|
import { postLoginUrl } from "@/api/fetchUrl";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { postRequest } from "@/api/customFetch";
|
||||||
|
import { useUserStore } from "@/store/user";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
|
||||||
|
const titleSvg = ref("");
|
||||||
|
const getTitleSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/login-title.svg?raw");
|
||||||
|
titleSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加密码显示状态控制
|
||||||
|
const showPassword = ref(false);
|
||||||
|
const togglePasswordVisibility = () => {
|
||||||
|
showPassword.value = !showPassword.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加表单数据
|
||||||
|
const formData = ref({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理表单提交
|
||||||
|
const handleSubmit = async (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
if (!formData.value.username || !formData.value.password) {
|
||||||
|
// 这里可以添加你的提示组件
|
||||||
|
alert("请输入账号和密码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
postRequest(
|
||||||
|
postLoginUrl(),
|
||||||
|
{ account: formData.value.username, password: formData.value.password },
|
||||||
|
{ headers: { "content-type": "application/json; charset=utf-8" } },
|
||||||
|
).then((resp) => {
|
||||||
|
if (resp.code === 200) {
|
||||||
|
// 登录成功,存储token
|
||||||
|
const {accessToken,refreshToken} = resp.result as {refreshToken:string,accessToken:string}
|
||||||
|
userStore.setAccessToken(accessToken)
|
||||||
|
userStore.setRefreshToken(refreshToken)
|
||||||
|
|
||||||
|
// 跳转到首页
|
||||||
|
router.push('/')
|
||||||
|
} else {
|
||||||
|
// 登录失败提示
|
||||||
|
alert(resp.message || "登录失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("登录请求失败:", error);
|
||||||
|
alert("登录失败,请稍后重试");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getTitleSvg();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.login-bg {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-image: url("/images/login-bg.png");
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-wrapper {
|
||||||
|
background-image: url("@/assets/svg-img/login-border.svg");
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-bg {
|
||||||
|
background-image: url("@/assets/svg-img/login-input.svg");
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-submit {
|
||||||
|
clip-path: polygon(0 15px, 10px 0, 100% 0, 100% 100%, 0 100%, 0 15px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||