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 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 { publicRoutes } from "./publicRoutes";
|
||||
import { privateRoutes } from "./privateRoutes";
|
||||
import { useUserStore } from "@/store/user";
|
||||
|
||||
function getRoutes() {
|
||||
const routes = [
|
||||
// 私有路由,请在这里添加
|
||||
|
|
@ -16,6 +18,7 @@ function getRoutes() {
|
|||
}
|
||||
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: getRoutes(),
|
||||
|
|
@ -23,19 +26,26 @@ const router = createRouter({
|
|||
|
||||
// 全局前置守卫,这边可以对身份进行验证
|
||||
router.beforeEach((to, _from, next) => {
|
||||
|
||||
let userRole = "admin";
|
||||
const userStore = useUserStore()
|
||||
let userRole = "";
|
||||
let hasLogin = userStore.getAccessToken;
|
||||
console.log(hasLogin);
|
||||
|
||||
// 如果目标路由没有角色限制
|
||||
if (!to.meta.role) {
|
||||
next();
|
||||
}
|
||||
console.log(to.meta.role);
|
||||
|
||||
// 判断当前用户角色是否在目标路由的允许角色列表中
|
||||
if ((to.meta.role as string[]).includes(userRole)) {
|
||||
// 如果角色匹配,允许进入目标路由
|
||||
// 如果角色匹配,允许进入目标路由
|
||||
next();
|
||||
}else if(hasLogin){
|
||||
next();
|
||||
} else {
|
||||
// 如果角色不匹配,跳转到 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')
|
||||
},
|
||||
|
||||
{
|
||||
path:"/",
|
||||
name:'home',
|
||||
path:"/login",
|
||||
name:'login',
|
||||
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", {
|
||||
persist:true,
|
||||
state:()=>({
|
||||
token:""
|
||||
accessToken:"",
|
||||
refreshToken:""
|
||||
}),
|
||||
getters:{
|
||||
getToken(state){
|
||||
return state.token;
|
||||
getAccessToken(state){
|
||||
return state.accessToken;
|
||||
},
|
||||
getRefreshToken(state){
|
||||
return state.refreshToken
|
||||
}
|
||||
},
|
||||
actions:{
|
||||
setToken(token:string){
|
||||
this.token = token;
|
||||
setAccessToken(token:string){
|
||||
this.accessToken = token;
|
||||
},
|
||||
setRefreshToken(token:string){
|
||||
this.refreshToken = token
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<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="relative h-[36px]">
|
||||
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
|
||||
|
|
|
|||
|
|
@ -55,13 +55,16 @@
|
|||
watch(
|
||||
() => chargingRankingData.value,
|
||||
() => {
|
||||
if(chargingRankingData.value.paymentRanks){
|
||||
products.value = chargingRankingData.value.paymentRanks
|
||||
|
||||
}
|
||||
initData()
|
||||
},
|
||||
);
|
||||
|
||||
const initData = () => {
|
||||
if(chargingRankingData.value.paymentRanks){
|
||||
products.value = chargingRankingData.value.paymentRanks
|
||||
}
|
||||
}
|
||||
|
||||
const headerLeftSvg = ref("");
|
||||
const headerRightSvg = ref("");
|
||||
|
||||
|
|
@ -115,6 +118,8 @@
|
|||
getGoldMedalSvg();
|
||||
getSilverMedalSvg();
|
||||
getBronzeMedalSvg();
|
||||
|
||||
initData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -77,21 +77,26 @@
|
|||
watch(
|
||||
() => 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) => ({
|
||||
name: item.tag,
|
||||
value: item.total,
|
||||
color: colorList.value[index % colorList.value.length],
|
||||
}));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
getHeaderLeftSvg();
|
||||
getHeaderRightSvg();
|
||||
getArrowLeftSvg();
|
||||
getPaymentChartSvg();
|
||||
initData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -76,20 +76,26 @@
|
|||
const chartData = ref<any[]>([]);
|
||||
|
||||
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) => ({
|
||||
name: item.tag,
|
||||
value: item.total,
|
||||
color: colorList.value[index % colorList.value.length],
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
getHeaderLeftSvg();
|
||||
getHeaderRightSvg();
|
||||
getArrowLeftSvg();
|
||||
getPaymentChartSvg();
|
||||
|
||||
initData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<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="relative h-[36px]">
|
||||
<div class="absolute top-[50%] translate-y-[-50%] left-[15px] flex items-center">
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
};
|
||||
|
||||
addRequest("getAcqTrendData", getAcqTrendData, [DateType]);
|
||||
runImmediatelyByKey("getAcqTrendData");
|
||||
|
||||
const handleDateChange = (type: string) => {
|
||||
DateType = type === "week" ? 0 : 1;
|
||||
|
|
|
|||
|
|
@ -11,35 +11,29 @@
|
|||
<SvgComponent :content="headerRightSvg" class="w-[108px] h-[36px]" />
|
||||
</div>
|
||||
<div class="w-full h-[calc(100%-36px)] mt-[36px] flex flex-col">
|
||||
<StudentSourceChart
|
||||
class="w-full h-full"
|
||||
:chartData="[
|
||||
{ name: '线下', value: offlineTotal, itemStyle: { color: 'rgba(147, 219, 255, 1)' } },
|
||||
{ name: '线上', value: onlineTotal, itemStyle: { color: 'rgb(79, 214, 169)' } },
|
||||
]"
|
||||
:ringSize="0.8" />
|
||||
<div class="flex items-center justify-between mx-[20px] my-[33px]">
|
||||
<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>
|
||||
<StudentSourceChart class="w-full h-full" :chartData="chartData" :ringSize="0.8" />
|
||||
<div class="flex items-center justify-between mx-[20px] my-[33px]">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -67,31 +61,43 @@
|
|||
arrowLeftSvg.value = svg;
|
||||
};
|
||||
|
||||
const onlineSvg = ref("")
|
||||
const offlineSvg = ref("")
|
||||
const onlineSvg = ref("");
|
||||
const offlineSvg = ref("");
|
||||
const getOfflineSvg = async () => {
|
||||
const {default: svg} = await import('/src/assets/svg-img/offline.svg?raw')
|
||||
offlineSvg.value = svg
|
||||
}
|
||||
const { default: svg } = await import("/src/assets/svg-img/offline.svg?raw");
|
||||
offlineSvg.value = svg;
|
||||
};
|
||||
const getOnlineSvg = async () => {
|
||||
const {default: svg} = await import('/src/assets/svg-img/online.svg?raw')
|
||||
onlineSvg.value = svg
|
||||
const { default: svg } = await import("/src/assets/svg-img/online.svg?raw");
|
||||
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(() => {
|
||||
getHeaderLeftSvg();
|
||||
getHeaderRightSvg();
|
||||
|
|
@ -99,6 +105,7 @@
|
|||
|
||||
getOfflineSvg();
|
||||
getOnlineSvg();
|
||||
initData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -57,11 +57,15 @@
|
|||
watch(
|
||||
() => chargingRankingData.value,
|
||||
() => {PageTransitionEvent
|
||||
if(chargingRankingData.value.acqRanks){
|
||||
products.value = chargingRankingData.value.acqRanks
|
||||
}
|
||||
initData()
|
||||
},
|
||||
);
|
||||
|
||||
const initData = () => {
|
||||
if(chargingRankingData.value.acqRanks){
|
||||
products.value = chargingRankingData.value.acqRanks
|
||||
}
|
||||
}
|
||||
|
||||
const headerLeftSvg = ref("");
|
||||
const headerRightSvg = ref("");
|
||||
|
|
@ -116,6 +120,8 @@
|
|||
getGoldMedalSvg();
|
||||
getSilverMedalSvg();
|
||||
getBronzeMedalSvg();
|
||||
|
||||
initData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -317,12 +317,14 @@
|
|||
updateChartData();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true,immediate:true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
if(!myChart){
|
||||
initChart();
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
|
|
|
|||
|
|
@ -33,8 +33,20 @@ const props = defineProps({
|
|||
})
|
||||
|
||||
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 = () => {
|
||||
if(!chartRef.value) return;
|
||||
|
|
@ -81,7 +93,7 @@ const initChart = () => {
|
|||
},
|
||||
series: [
|
||||
{
|
||||
name: '数据1',
|
||||
name: '获客数',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: props.chartData.map((item:any) => item.total),
|
||||
|
|
@ -114,7 +126,9 @@ const initChart = () => {
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
if(!chart){
|
||||
initChart();
|
||||
}
|
||||
window.addEventListener('resize', () => {
|
||||
chart?.resize();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import { SurfaceChart } from "echarts-gl/charts";
|
||||
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]);
|
||||
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
});
|
||||
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
let chart: echarts.ECharts | null = null;
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
|
||||
interface SeriesItem {
|
||||
[key: string]: any;
|
||||
|
|
@ -206,7 +206,7 @@
|
|||
height: 106,
|
||||
},
|
||||
z: 0,
|
||||
cursor:"point",
|
||||
cursor: "point",
|
||||
origin: [127, 53],
|
||||
keyframeAnimation: [
|
||||
{
|
||||
|
|
@ -241,173 +241,14 @@
|
|||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chart = echarts.init(chartRef.value);
|
||||
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();
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
updateChart();
|
||||
};
|
||||
|
||||
// 绑定事件
|
||||
const bindEvents = () => {
|
||||
if (!chart) return;
|
||||
|
||||
// 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 updateChart = () => {
|
||||
if (!chartInstance) return;
|
||||
const option = getPie3D(props.chartData, props.ringSize);
|
||||
chartInstance.setOption(option, true);
|
||||
};
|
||||
|
||||
const total = computed(() => props.chartData.reduce((sum: number, item: any) => sum + item.value, 0));
|
||||
|
|
@ -416,15 +257,32 @@
|
|||
watch(
|
||||
() => props.chartData,
|
||||
() => {
|
||||
initChart();
|
||||
if (chartInstance) {
|
||||
updateChart();
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 组件挂载时初始化
|
||||
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>
|
||||
|
||||
<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="text-[#45A2FF] text-[14px]">{{ year }}-{{ month }}-{{ day }} {{ weekday }}</div>
|
||||
<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>
|
||||
</header>
|
||||
<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="flex items-center px-[24px] justify-start">
|
||||
<PaymentTotal />
|
||||
<TodayPayment class="ml-[20px]" />
|
||||
<GainTotal class="ml-[20px]" />
|
||||
<GainToday class="ml-[20px]" />
|
||||
<LossStatic class="ml-[20px]" />
|
||||
<PaymentTotal class="flex-1"/>
|
||||
<TodayPayment class="ml-[20px] flex-1" />
|
||||
<GainTotal class="ml-[20px] flex-1" />
|
||||
<GainToday class="ml-[20px] flex-1" />
|
||||
<LossStatic class="ml-[20px] flex-1" />
|
||||
</div>
|
||||
<div class="flex items-center px-[24px] justify-start">
|
||||
<OperatingTrends class="" />
|
||||
<AskSection class="ml-[20px]" />
|
||||
<div class="grid grid-cols-2 items-center px-[24px] gap-x-[20px]">
|
||||
<OperatingTrends class="flex-1" />
|
||||
<AskSection class="flex-1" />
|
||||
</div>
|
||||
<div class="flex items-center px-[24px] overflow-x-auto">
|
||||
<StudentSource />
|
||||
<OnLineStatus class="ml-[20px]" />
|
||||
<OfflineStatus class="ml-[20px]" />
|
||||
<SixStatistics class="ml-[20px]" />
|
||||
<StudentSource class="min-w-[296px] max-w-[296px]" />
|
||||
<OnLineStatus class="ml-[20px] min-w-[296px] max-w-[296px]" />
|
||||
<OfflineStatus class="ml-[20px] min-w-[296px] max-w-[296px]" />
|
||||
<SixStatistics class="ml-[20px] min-w-[296px] max-w-[296px]" />
|
||||
<ChargingRanking class="ml-[20px]" />
|
||||
<WinCustomer class="ml-[20px]" />
|
||||
</div>
|
||||
|
|
@ -40,28 +45,87 @@
|
|||
<script lang="ts" setup>
|
||||
import SvgComponent from "@/components/SvgComponent.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 PaymentTotal from "@/views/components/PaymentTotal.vue";
|
||||
import TodayPayment from "@/views/components/TodayPayment.vue";
|
||||
import GainTotal from "@/views/components/GainTotal.vue";
|
||||
import GainToday from "@/views/components/GainToday.vue";
|
||||
import LossStatic from "@/views/components/LossStatic.vue";
|
||||
import OperatingTrends from "@/views/components/OperatingTrends.vue";
|
||||
import AskSection from "@/views/components/AskSection.vue";
|
||||
import StudentSource from "@/views/components/StudentSource.vue";
|
||||
import OnLineStatus from "@/views/components/OnlineStatus.vue";
|
||||
import OfflineStatus from "@/views/components/OfflineStatus.vue";
|
||||
import SixStatistics from "@/views/components/SixStatistics.vue";
|
||||
import ChargingRanking from "./components/ChargingRanking.vue";
|
||||
import WinCustomer from "./components/WinCustomer.vue";
|
||||
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
const asyncComponentConfig = {
|
||||
loadingComponent: LoadingComponent,
|
||||
errorComponent: ErrorComponent,
|
||||
onError: (error: Error, retry: () => void, fail: () => void, attempts: number) => {
|
||||
if (attempts <= 3) {
|
||||
retry();
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 {useFetchAllData} from "./composables/useFetchData"
|
||||
import { useFetchAllData } from "./composables/useFetchData"
|
||||
import { runImmediatelyAll, updateTime } from "@/composables/usePolling";
|
||||
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 router = useRouter();
|
||||
|
||||
|
||||
const headerSvg = ref("");
|
||||
|
|
@ -78,15 +142,39 @@
|
|||
|
||||
|
||||
useFetchAllData()
|
||||
|
||||
const updateAllData = () => {
|
||||
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(() => {
|
||||
formateTime();
|
||||
headerBackgroundSvg();
|
||||
headerTitleSvg();
|
||||
getUserInfo();
|
||||
});
|
||||
</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>
|
||||