feat: 手机端样式编写,PC端查看更多
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/icons/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/icons/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
||||||
<title></title>
|
<title></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host 0.0.0.0",
|
||||||
"build": "vue-tsc -b && vite build",
|
"build": "vue-tsc -b && vite build",
|
||||||
"upload": "bash ./upload.sh",
|
"upload": "bash ./upload.sh",
|
||||||
"build-and-upload": "pnpm run build && pnpm run upload",
|
"build-and-upload": "pnpm run build && pnpm run upload",
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
"@unocss/preset-wind": "^0.65.2",
|
"@unocss/preset-wind": "^0.65.2",
|
||||||
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"@vueuse/core": "^13.4.0",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.4.2",
|
||||||
"sass-embedded": "^1.86.0",
|
"sass-embedded": "^1.86.0",
|
||||||
"svg-sprite-loader": "^6.0.11",
|
"svg-sprite-loader": "^6.0.11",
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,9 @@ importers:
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: ^5.2.1
|
specifier: ^5.2.1
|
||||||
version: 5.2.1(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2))(vue@3.5.13(typescript@5.6.3))
|
version: 5.2.1(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2))(vue@3.5.13(typescript@5.6.3))
|
||||||
|
'@vueuse/core':
|
||||||
|
specifier: ^13.4.0
|
||||||
|
version: 13.4.0(vue@3.5.13(typescript@5.6.3))
|
||||||
prettier:
|
prettier:
|
||||||
specifier: 3.4.2
|
specifier: 3.4.2
|
||||||
version: 3.4.2
|
version: 3.4.2
|
||||||
|
|
@ -92,7 +95,7 @@ importers:
|
||||||
version: 0.65.2(postcss@5.2.18)(rollup@4.28.1)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2))(vue@3.5.13(typescript@5.6.3))
|
version: 0.65.2(postcss@5.2.18)(rollup@4.28.1)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2))(vue@3.5.13(typescript@5.6.3))
|
||||||
unplugin-auto-import:
|
unplugin-auto-import:
|
||||||
specifier: ^0.19.0
|
specifier: ^0.19.0
|
||||||
version: 0.19.0(@nuxt/kit@3.14.1592(rollup@4.28.1))(rollup@4.28.1)
|
version: 0.19.0(@nuxt/kit@3.14.1592(rollup@4.28.1))(@vueuse/core@13.4.0(vue@3.5.13(typescript@5.6.3)))(rollup@4.28.1)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2)
|
version: 6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2)
|
||||||
|
|
@ -653,6 +656,9 @@ packages:
|
||||||
'@types/qs@6.9.17':
|
'@types/qs@6.9.17':
|
||||||
resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
|
resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.21':
|
||||||
|
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||||
|
|
||||||
'@unocss/astro@0.65.2':
|
'@unocss/astro@0.65.2':
|
||||||
resolution: {integrity: sha512-lpGoleJToxaYeN5LTGrNbvbXATNWswgoQwlljIJ9kWOjx4NbGC71pXRvDQSb9yRFDTCr5S2hMtupna4ulrHisA==}
|
resolution: {integrity: sha512-lpGoleJToxaYeN5LTGrNbvbXATNWswgoQwlljIJ9kWOjx4NbGC71pXRvDQSb9yRFDTCr5S2hMtupna4ulrHisA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -801,6 +807,19 @@ packages:
|
||||||
'@vue/shared@3.5.13':
|
'@vue/shared@3.5.13':
|
||||||
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
||||||
|
|
||||||
|
'@vueuse/core@13.4.0':
|
||||||
|
resolution: {integrity: sha512-OnK7zW3bTq/QclEk17+vDFN3tuAm8ONb9zQUIHrYQkkFesu3WeGUx/3YzpEp+ly53IfDAT9rsYXgGW6piNZC5w==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
|
'@vueuse/metadata@13.4.0':
|
||||||
|
resolution: {integrity: sha512-CPDQ/IgOeWbqItg1c/pS+Ulum63MNbpJ4eecjFJqgD/JUCJ822zLfpw6M9HzSvL6wbzMieOtIAW/H8deQASKHg==}
|
||||||
|
|
||||||
|
'@vueuse/shared@13.4.0':
|
||||||
|
resolution: {integrity: sha512-+AxuKbw8R1gYy5T21V5yhadeNM7rJqb4cPaRI9DdGnnNl3uqXh+unvQ3uCaA2DjYLbNr1+l7ht/B4qEsRegX6A==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
acorn@8.14.0:
|
acorn@8.14.0:
|
||||||
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
|
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
@ -3058,6 +3077,8 @@ snapshots:
|
||||||
|
|
||||||
'@types/qs@6.9.17': {}
|
'@types/qs@6.9.17': {}
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.21': {}
|
||||||
|
|
||||||
'@unocss/astro@0.65.2(rollup@4.28.1)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2))(vue@3.5.13(typescript@5.6.3))':
|
'@unocss/astro@0.65.2(rollup@4.28.1)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(sass-embedded@1.86.0)(tsx@4.19.2))(vue@3.5.13(typescript@5.6.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unocss/core': 0.65.2
|
'@unocss/core': 0.65.2
|
||||||
|
|
@ -3306,6 +3327,19 @@ snapshots:
|
||||||
|
|
||||||
'@vue/shared@3.5.13': {}
|
'@vue/shared@3.5.13': {}
|
||||||
|
|
||||||
|
'@vueuse/core@13.4.0(vue@3.5.13(typescript@5.6.3))':
|
||||||
|
dependencies:
|
||||||
|
'@types/web-bluetooth': 0.0.21
|
||||||
|
'@vueuse/metadata': 13.4.0
|
||||||
|
'@vueuse/shared': 13.4.0(vue@3.5.13(typescript@5.6.3))
|
||||||
|
vue: 3.5.13(typescript@5.6.3)
|
||||||
|
|
||||||
|
'@vueuse/metadata@13.4.0': {}
|
||||||
|
|
||||||
|
'@vueuse/shared@13.4.0(vue@3.5.13(typescript@5.6.3))':
|
||||||
|
dependencies:
|
||||||
|
vue: 3.5.13(typescript@5.6.3)
|
||||||
|
|
||||||
acorn@8.14.0: {}
|
acorn@8.14.0: {}
|
||||||
|
|
||||||
alien-signals@0.2.2: {}
|
alien-signals@0.2.2: {}
|
||||||
|
|
@ -5161,7 +5195,7 @@ snapshots:
|
||||||
- supports-color
|
- supports-color
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
unplugin-auto-import@0.19.0(@nuxt/kit@3.14.1592(rollup@4.28.1))(rollup@4.28.1):
|
unplugin-auto-import@0.19.0(@nuxt/kit@3.14.1592(rollup@4.28.1))(@vueuse/core@13.4.0(vue@3.5.13(typescript@5.6.3)))(rollup@4.28.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/utils': 0.7.10
|
'@antfu/utils': 0.7.10
|
||||||
'@rollup/pluginutils': 5.1.3(rollup@4.28.1)
|
'@rollup/pluginutils': 5.1.3(rollup@4.28.1)
|
||||||
|
|
@ -5172,6 +5206,7 @@ snapshots:
|
||||||
unplugin: 2.1.0
|
unplugin: 2.1.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@nuxt/kit': 3.14.1592(rollup@4.28.1)
|
'@nuxt/kit': 3.14.1592(rollup@4.28.1)
|
||||||
|
'@vueuse/core': 13.4.0(vue@3.5.13(typescript@5.6.3))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- rollup
|
- rollup
|
||||||
|
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<teleport to="body">
|
||||||
|
<Transition name="fade">
|
||||||
|
<div v-if="modelValue" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60" @click.self="close">
|
||||||
|
<div :class="`bg-white ${modalClass}`">
|
||||||
|
<header class="">
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</header>
|
||||||
|
<main class="">
|
||||||
|
<slot></slot>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
modelValue:{
|
||||||
|
type:Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
modalClass:{
|
||||||
|
type:String,
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
emit('update:modelValue', false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fade-enter-active, .fade-leave-active {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.fade-enter-from, .fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center justify-center w-full py-2 leading-[1]">
|
||||||
|
<!-- 分页数量选择 -->
|
||||||
|
<div class="flex items-center space-x-2 text-[#68A1FF] mr-[26px]">
|
||||||
|
<span>每页</span>
|
||||||
|
<select v-model="currentPageSize" class="border rounded pl-[8px] py-[2px] border-[#68A1FF] bg-[#001C38] w-[65px]">
|
||||||
|
<option v-for="size in pageSizes" :key="size" :value="size">{{ size }}</option>
|
||||||
|
</select>
|
||||||
|
<span>条</span>
|
||||||
|
</div>
|
||||||
|
<!-- 页码 -->
|
||||||
|
<div class="flex items-center text-[#68A1FF]">
|
||||||
|
<button
|
||||||
|
class="px-2 bg-transparent text-[28px] leading-[1.8]"
|
||||||
|
:disabled="isFirstPage"
|
||||||
|
@click="prev"
|
||||||
|
>‹</button>
|
||||||
|
<template v-for="page in pageList" :key="page">
|
||||||
|
<button
|
||||||
|
class="px-2 rounded text-[17px] bg-transparent h-[28px]"
|
||||||
|
:class="page === currentPage ? ' text-white' : ''"
|
||||||
|
@click="changePage(page)"
|
||||||
|
>{{ page }}</button>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
class="px-2 bg-transparent text-[28px] leading-[1.8]"
|
||||||
|
:disabled="isLastPage"
|
||||||
|
@click="next"
|
||||||
|
>›</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch, toRef } from 'vue';
|
||||||
|
import { useOffsetPagination } from '@vueuse/core';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
total: number,
|
||||||
|
modelValue: number,
|
||||||
|
pageSize?: number,
|
||||||
|
pageSizes?: number[]
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'update:pageSize', 'change']);
|
||||||
|
|
||||||
|
const fetchData = ({ currentPage, currentPageSize }: { currentPage: number, currentPageSize: number }) => {
|
||||||
|
emit('update:modelValue', currentPage);
|
||||||
|
emit('update:pageSize', currentPageSize);
|
||||||
|
emit('change', currentPage, currentPageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create local, writable refs for page and pageSize
|
||||||
|
const page = ref(props.modelValue);
|
||||||
|
const pageSize = ref(props.pageSize || 10);
|
||||||
|
|
||||||
|
// Sync local state with props when they are changed from outside
|
||||||
|
watch(() => props.modelValue, (v) => page.value = v);
|
||||||
|
watch(() => props.pageSize, (v) => pageSize.value = v || 10);
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentPage,
|
||||||
|
currentPageSize,
|
||||||
|
pageCount,
|
||||||
|
isFirstPage,
|
||||||
|
isLastPage,
|
||||||
|
prev,
|
||||||
|
next,
|
||||||
|
} = useOffsetPagination({
|
||||||
|
total: toRef(props, 'total'),
|
||||||
|
page: page,
|
||||||
|
pageSize: pageSize,
|
||||||
|
onPageChange: fetchData,
|
||||||
|
onPageSizeChange: fetchData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageSizes = computed(() => props.pageSizes || [10, 20, 50, 100]);
|
||||||
|
|
||||||
|
const PAGE_BLOCK_SIZE = 5;
|
||||||
|
|
||||||
|
const pageList = computed(() => {
|
||||||
|
const pages = [];
|
||||||
|
if (pageCount.value <= PAGE_BLOCK_SIZE) {
|
||||||
|
for (let i = 1; i <= pageCount.value; i++) pages.push(i);
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = currentPage.value - Math.floor(PAGE_BLOCK_SIZE / 2);
|
||||||
|
start = Math.max(1, start);
|
||||||
|
start = Math.min(start, pageCount.value - PAGE_BLOCK_SIZE + 1);
|
||||||
|
|
||||||
|
for (let i = start; i < start + PAGE_BLOCK_SIZE; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
});
|
||||||
|
|
||||||
|
function changePage(p: number) {
|
||||||
|
currentPage.value = p;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import LoadingComponent from "@/components/LoadingComponent.vue";
|
||||||
|
import ErrorComponent from "@/components/ErrorComponent.vue";
|
||||||
|
|
||||||
|
|
||||||
|
export const asyncComponentConfig = {
|
||||||
|
loadingComponent: LoadingComponent,
|
||||||
|
errorComponent: ErrorComponent,
|
||||||
|
onError: (_error: Error, retry: () => void, fail: () => void, attempts: number) => {
|
||||||
|
if (attempts <= 3) {
|
||||||
|
retry();
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import '@unocss/reset/tailwind-compat.css'
|
import '@unocss/reset/tailwind-compat.css'
|
||||||
// import './style.css'
|
import './style.css'
|
||||||
import 'uno.css';
|
import 'uno.css';
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
// Pinia 持久化
|
// Pinia 持久化
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,13 @@ export const privateRoutes = [
|
||||||
},
|
},
|
||||||
component:()=>import('../views/home.vue')
|
component:()=>import('../views/home.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path:"/app",
|
||||||
|
name:'app',
|
||||||
|
meta:{
|
||||||
|
title:`${new Date().getFullYear()}深泉教育招生数据可视化`,
|
||||||
|
role:['admin']
|
||||||
|
},
|
||||||
|
component:()=>import('../views/app.vue')
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
155
src/style.css
|
|
@ -1,156 +1,5 @@
|
||||||
* {
|
@media (max-width: 1919px){
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
touch-action: manipulation;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html{
|
html{
|
||||||
line-height: 1;
|
font-size: calc(100vw / 93.75);
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
margin: 0.67em 0;
|
|
||||||
}
|
|
||||||
hr {
|
|
||||||
box-sizing: content-box;
|
|
||||||
height: 0;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
font-family: monospace, monospace;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
abbr[title] {
|
|
||||||
border-bottom: none;
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp {
|
|
||||||
font-family: monospace, monospace;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 100%;
|
|
||||||
line-height: 1.15;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
button,
|
|
||||||
input {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
[type="button"],
|
|
||||||
[type="reset"],
|
|
||||||
[type="submit"],
|
|
||||||
button {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
[type="button"]::-moz-focus-inner,
|
|
||||||
[type="reset"]::-moz-focus-inner,
|
|
||||||
[type="submit"]::-moz-focus-inner,
|
|
||||||
button::-moz-focus-inner {
|
|
||||||
border-style: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
[type="button"]:-moz-focusring,
|
|
||||||
[type="reset"]:-moz-focusring,
|
|
||||||
[type="submit"]:-moz-focusring,
|
|
||||||
button:-moz-focusring {
|
|
||||||
outline: 1px dotted ButtonText;
|
|
||||||
}
|
|
||||||
fieldset {
|
|
||||||
padding: 0.35em 0.75em 0.625em;
|
|
||||||
}
|
|
||||||
legend {
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: inherit;
|
|
||||||
display: table;
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
[type="checkbox"],
|
|
||||||
[type="radio"] {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
[type="number"]::-webkit-inner-spin-button,
|
|
||||||
[type="number"]::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
[type="search"] {
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
outline-offset: -2px;
|
|
||||||
}
|
|
||||||
[type="search"]::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
details {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
template {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: auto;
|
z-index: auto;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
-webkit-mask: linear-gradient(to bottom, transparent, #000);
|
-webkit-mask: var(--mask-gradient, linear-gradient(to bottom, transparent, #000));
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
export const usePlatformType = () => {
|
||||||
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
|
||||||
|
// 判断是否为移动设备
|
||||||
|
const isMobile = /mobile|android|iphone|ipad|ipod|windows phone/i.test(userAgent);
|
||||||
|
|
||||||
|
// 判断是否为 APP 环境(这里假设 APP 环境会在 userAgent 中包含特定标识)
|
||||||
|
const isApp = /myapp|appname/i.test(userAgent); // 请根据实际 APP 的标识修改
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMobile,
|
||||||
|
isApp,
|
||||||
|
isPC: !isMobile
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-screen px-[3rem] custom-bg overflow-auto leading-[1]">
|
||||||
|
<HeaderStatic />
|
||||||
|
<TabStatic class="mt-[3rem]" />
|
||||||
|
<SixStatistics class="mt-[3rem]" />
|
||||||
|
<div class="mt-[3rem] grid grid-cols-2 gap-[2.75rem]">
|
||||||
|
<ChargingRanking />
|
||||||
|
<WinCustomer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { asyncComponentConfig } from "@/composables/useLazyLoad";
|
||||||
|
import { useFetchAllData } from "./composables/useFetchData";
|
||||||
|
import SixStatistics from "./appComponents/SixStatistics.vue";
|
||||||
|
import ChargingRanking from "./appComponents/ChargingRanking.vue";
|
||||||
|
import WinCustomer from "./appComponents/WinCustomer.vue";
|
||||||
|
import { usePlatformType } from "@/utils/device";
|
||||||
|
import { useUserStore } from "@/store/user";
|
||||||
|
import { getRequest } from "@/api/customFetch";
|
||||||
|
import { getUserInfoUrl } from "@/api/fetchUrl";
|
||||||
|
|
||||||
|
useFetchAllData();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const HeaderStatic = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("./appComponents/HeaderStatic.vue"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const TabStatic = defineAsyncComponent({
|
||||||
|
...asyncComponentConfig,
|
||||||
|
loader: () => import("./appComponents/TabStatic.vue"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const getUserInfo = () => {
|
||||||
|
const accessToken = userStore.getAccessToken;
|
||||||
|
getRequest(getUserInfoUrl(), {}, { headers: { Authorization: `Bearer ${accessToken}` } }).then((resp) => {
|
||||||
|
if (resp.code === 401) {
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
const { isMobile } = usePlatformType();
|
||||||
|
// 跳转到首页
|
||||||
|
if (!isMobile) {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
getUserInfo()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.custom-bg {
|
||||||
|
background-image: url("/images/main-bg.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
<template>
|
||||||
|
<div class="ranking-bg aspect-[0.8/1] px-[2.5rem] pt-[2.5rem] pb-[1.5rem]">
|
||||||
|
<header class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<SvgComponent :content="arrowLeftSvg" class="w-[3.75rem] h-[3.75rem]" />
|
||||||
|
<div class="text-color text-[3.5rem] ml-[1.25rem] font-700" data-text="缴费排行榜">缴费排行榜</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center" @click="isDrawerVisible = true">
|
||||||
|
<div class="text-[3rem] text-[#84E8FF]">更多</div>
|
||||||
|
<SvgComponent :content="moreArrowSvg" class="w-[2.2rem] h-[3.75rem]" />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<RankingTable :value="products.slice(0,5)" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
||||||
|
<template #rank="{ index }">
|
||||||
|
<SvgComponent :content="goldMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 0" />
|
||||||
|
<SvgComponent :content="silverMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 1" />
|
||||||
|
<SvgComponent :content="bronzeMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 2" />
|
||||||
|
<span class="text-[2.75rem] font-600" v-if="index > 2">{{ index + 1 }}</span>
|
||||||
|
</template>
|
||||||
|
<template #name="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.name }}</span>
|
||||||
|
</template>
|
||||||
|
<template #total="{data}">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.total }}</span>
|
||||||
|
</template>
|
||||||
|
</RankingTable>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<MobileDrawer v-model="isDrawerVisible" :transition-duration="300" panelStyle="border-radius:8rem 8rem 0 0;background-color:#072362;">
|
||||||
|
<!-- 自定义头部 -->
|
||||||
|
<template #header>
|
||||||
|
<div class="drawer-header aspect-[10.7/1] w-full bg-[#072362] flex items-center justify-center relative rounded-[8rem_8rem_0_0]">
|
||||||
|
<div class="text-color text-[4.5rem] font-700 italic" style="--mask-gradient: linear-gradient(to top, transparent, #000)" data-text="缴费排行榜">
|
||||||
|
缴费排行榜
|
||||||
|
</div>
|
||||||
|
<img src="/images/close.png" class="w-[3rem] h-[3rem] absolute right-[5rem] top-[3.75rem]" alt="" @click="isDrawerVisible = false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 自定义内容 -->
|
||||||
|
<RankingTable :value="products" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
||||||
|
<template #rank="{ index }">
|
||||||
|
<SvgComponent :content="goldMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 0" />
|
||||||
|
<SvgComponent :content="silverMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 1" />
|
||||||
|
<SvgComponent :content="bronzeMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 2" />
|
||||||
|
<span class="text-[2.75rem] font-600" v-if="index > 2">{{ index + 1 }}</span>
|
||||||
|
</template>
|
||||||
|
<template #name="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.name }}</span>
|
||||||
|
</template>
|
||||||
|
<template #total="{data}">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.total }}</span>
|
||||||
|
</template>
|
||||||
|
</RankingTable>
|
||||||
|
</MobileDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import SvgComponent from "@/components/SvgComponent.vue";
|
||||||
|
import RankingTable from "@/components/table/RankingTable.vue";
|
||||||
|
import MobileDrawer from "./MobileDrawer.vue";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ field: "rank", header: "名次", align: "justify-center", width: "6rem" },
|
||||||
|
{ field: "name", header: "姓名", align: "justify-left", width: "13.5rem" },
|
||||||
|
{ field: "total", header: "收费人数", align: "justify-center", width: "15rem" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const isDrawerVisible = ref(false)
|
||||||
|
|
||||||
|
let products = ref<any[]>([]);
|
||||||
|
const chargingRankingData = inject(
|
||||||
|
"chargingRankingData",
|
||||||
|
ref<{
|
||||||
|
paymentRanks: any[];
|
||||||
|
}>({
|
||||||
|
paymentRanks: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => chargingRankingData.value,
|
||||||
|
() => {
|
||||||
|
initData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if (chargingRankingData.value.paymentRanks) {
|
||||||
|
products.value = chargingRankingData.value.paymentRanks;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const arrowLeftSvg = ref("");
|
||||||
|
const getArrowLeftSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw");
|
||||||
|
arrowLeftSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const goldMedalSvg = ref("");
|
||||||
|
const getGoldMedalSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/gold-medal.svg?raw");
|
||||||
|
goldMedalSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const silverMedalSvg = ref("");
|
||||||
|
const getSilverMedalSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/silver-medal.svg?raw");
|
||||||
|
silverMedalSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bronzeMedalSvg = ref("");
|
||||||
|
const getBronzeMedalSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/bronze-medal.svg?raw");
|
||||||
|
bronzeMedalSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const moreArrowSvg = ref("");
|
||||||
|
const getMoreArrowSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/more-arrow.svg?raw");
|
||||||
|
moreArrowSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getArrowLeftSvg();
|
||||||
|
getGoldMedalSvg();
|
||||||
|
getSilverMedalSvg();
|
||||||
|
getBronzeMedalSvg();
|
||||||
|
getMoreArrowSvg();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
|
||||||
|
.ranking-bg {
|
||||||
|
background-image: url("/images/ranking-border.png");
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table-header) {
|
||||||
|
tr {
|
||||||
|
background-color: rgb(14, 39, 97);
|
||||||
|
th {
|
||||||
|
padding: 2rem 1.5rem;
|
||||||
|
color: #44c1ef;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 2.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table-body) {
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
padding: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background: linear-gradient( 270deg, rgba(62,118,171,0) 0%, rgba(62, 118, 171, 0.2) 45%, rgba(62,118,171,0) 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-header {
|
||||||
|
background-image: url("/images/drawer-header.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div class="custom-bg mt-[5rem] aspect-[4/1] flex items-center justify-center">
|
||||||
|
|
||||||
|
<div class="mt-[3.5rem] custom-border px-[2.5rem] pt-[1.5rem] h-full">
|
||||||
|
<div class="text-[#44C1EF] italic text-[3.5rem] font-700 mb-[2.5rem]">总缴费</div>
|
||||||
|
<div class="mb-[3.75rem] z-10 font-700 leading-[1] flex items-baseline">
|
||||||
|
<span class="text-[5.5rem] italic text-color" :data-text="`${paymentData.chargeTotal || 0}`">{{ paymentData.chargeTotal || 0 }}</span>
|
||||||
|
<span class="text-[3rem] text-color" data-text="人">人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-[3.5rem] custom-border px-[3.5rem] justify-items-center pt-[1.5rem] h-full">
|
||||||
|
<div class="text-[#44C1EF] italic text-[3.5rem] font-700">今日缴费</div>
|
||||||
|
<div class="font-700 leading-[1] flex items-baseline justify-center mt-[1rem] mb-[1.5rem]">
|
||||||
|
<span class="text-[5.5rem] italic text-color" :data-text="paymentData.toDayTotal">{{ paymentData.toDayTotal || 0 }}</span>
|
||||||
|
<span class="text-[3rem] text-color" data-text="人">人</span>
|
||||||
|
</div>
|
||||||
|
<div :class="`${paymentDifferent > 0 ? 'text-[#4AFFA2]' : 'text-[#FF4E4E]'} text-[2.5rem] flex items-center flex-wrap justify-center`">
|
||||||
|
<span class="">较昨日</span>
|
||||||
|
<SvgIcon :name="paymentDifferent > 0 ? 'arrow-up' : 'arrow-down'" class="text-[1.5rem]" />
|
||||||
|
<span>{{ Math.abs(paymentDifferent) }}人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-[3.5rem] custom-border pl-[2.25rem] pr-[2rem] justify-items-center pt-[1.5rem] h-full">
|
||||||
|
<div class="text-[#44C1EF] italic text-[3.5rem] font-700 mb-[3.5rem]">企微获客</div>
|
||||||
|
<div class="leading-[1] flex items-baseline mb-[1.5rem] z-10 font-700">
|
||||||
|
<span class="text-[4.5rem] italic text-color" :data-text="gainData.acqTotal || 0">{{ gainData.acqTotal || 0 }}</span>
|
||||||
|
<span class="text-[3rem] text-color" data-text="人">人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-[3.5rem] px-[2.5rem] justify-items-center pt-[1.5rem] h-full">
|
||||||
|
<div class="text-[#44C1EF] italic text-[3.5rem] font-700">今日获客</div>
|
||||||
|
<div class="leading-[1] flex items-baseline justify-center mt-[1rem] mb-[1.5rem] z-10 font-700">
|
||||||
|
<span class="text-[4.5rem] italic text-color" :data-text="gainData.toDayAcq || 0">{{ gainData.toDayAcq || 0 }}</span>
|
||||||
|
<span class="text-[3rem] text-color" data-text="人">人</span>
|
||||||
|
</div>
|
||||||
|
<div :class="`${gainDifferentTody > 0 ? 'text-[#4AFFA2]' : 'text-[#FF4E4E]'} text-[2.5rem] flex items-center flex-wrap justify-center`">
|
||||||
|
<span class="">较昨日</span>
|
||||||
|
<SvgIcon :name="gainDifferentTody > 0 ? 'arrow-up' : 'arrow-down'" class="text-[1.5rem]" />
|
||||||
|
<span>{{ Math.abs(gainDifferentTody) }}人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
|
||||||
|
|
||||||
|
const paymentData = inject("paymentData", ref({ chargeTotal: 0, estimatedTotal: 0, items: [], toDayTotal: 0, yesterdayTotal: 0 }));
|
||||||
|
const gainData = inject("gainData", ref({ acqTotal: 0, goalTotal: 0, toDayAcq: 0, yestertDayAcq: 0 }));
|
||||||
|
|
||||||
|
const paymentDifferent = computed(() => {
|
||||||
|
if (
|
||||||
|
Object.prototype.toString.call(paymentData.value.toDayTotal) !== "[object Undefined]" &&
|
||||||
|
Object.prototype.toString.call(paymentData.value.yesterdayTotal) !== "[object Undefined]"
|
||||||
|
) {
|
||||||
|
return paymentData.value.toDayTotal - paymentData.value.yesterdayTotal;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const gainDifferentTody = computed(() => {
|
||||||
|
if (
|
||||||
|
Object.prototype.toString.call(gainData.value.toDayAcq) !== "[object Undefined]" &&
|
||||||
|
Object.prototype.toString.call(gainData.value.yestertDayAcq) !== "[object Undefined]"
|
||||||
|
) {
|
||||||
|
return gainData.value.toDayAcq - gainData.value.yestertDayAcq;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
|
||||||
|
.custom-bg {
|
||||||
|
background-image: url("/images/top-bg.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-border {
|
||||||
|
border-right: 1px solid;
|
||||||
|
border-image: linear-gradient(180deg, #217ac600, #227cc8, #217ac600) 1 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
<template>
|
||||||
|
<teleport to="body">
|
||||||
|
<!-- 遮罩层 -->
|
||||||
|
<Transition name="drawer-fade">
|
||||||
|
<div
|
||||||
|
v-if="modelValue"
|
||||||
|
class="fixed top-0 left-0 w-screen h-screen bg-black/50 z-[1]"
|
||||||
|
:style="`--transition-duration: ${props.transitionDuration}ms;`"
|
||||||
|
@click="closeDrawer"
|
||||||
|
></div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<!-- 抽屉面板 -->
|
||||||
|
<Transition name="drawer-slide" @after-leave="onTransitionEnd">
|
||||||
|
<div
|
||||||
|
v-if="modelValue"
|
||||||
|
class="fixed bottom-0 left-0 z-[2] flex w-screen flex-col rounded-t-lg bg-white shadow-lg"
|
||||||
|
:style="`--transition-duration: ${props.transitionDuration}ms;${panelStyle}`"
|
||||||
|
>
|
||||||
|
<header class="flex-shrink-0">
|
||||||
|
<slot name="header"></slot>
|
||||||
|
</header>
|
||||||
|
<main class="flex-grow overflow-y-auto max-h-[55vh]">
|
||||||
|
<slot></slot>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
transitionDuration: {
|
||||||
|
type: Number,
|
||||||
|
default: 250,
|
||||||
|
},
|
||||||
|
panelStyle:{
|
||||||
|
type:String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const closeDrawer = () => {
|
||||||
|
emit('update:modelValue', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 防止页面滚动
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(isOpen) => {
|
||||||
|
document.body.style.overflow = isOpen ? 'hidden' : '';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 组件卸载时恢复滚动
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过渡动画结束后,可以执行一些清理操作
|
||||||
|
const onTransitionEnd = () => {
|
||||||
|
// console.log('Transition ended');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* Transitions are defined here to use the dynamic --transition-duration variable */
|
||||||
|
.drawer-fade-enter-active,
|
||||||
|
.drawer-fade-leave-active {
|
||||||
|
transition: opacity var(--transition-duration) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-fade-enter-from,
|
||||||
|
.drawer-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-slide-enter-active,
|
||||||
|
.drawer-slide-leave-active {
|
||||||
|
transition: transform var(--transition-duration) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-slide-enter-from,
|
||||||
|
.drawer-slide-leave-to {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div class="relative mt-[6.25rem] flex items-center justify-center">
|
||||||
|
<ProportionCharts :chart-data="chartData" class="w-[41.25rem] h-[41.25rem]" :width="canvasWidth" :height="canvasWidth" />
|
||||||
|
<div class="leading-[1] absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-3 flex items-center flex-col font-700 italic">
|
||||||
|
<div class="flex items-baseline">
|
||||||
|
<div class="text-[7rem] text-color" :data-text="offlineTotal">{{ offlineTotal }}</div>
|
||||||
|
<div class="text-[5.75rem] text-color" data-text="人">人</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-[#FFFFFF] text-[4.5rem] text-shadow-[0px_2px_2px_rgba(12,32,72,0.42)] mt-[2rem]">线上</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="px-[3.75rem] grid grid-cols-2 gap-x-[2.5rem] gap-y-[2.25rem] mt-[5rem] leading-[1] pb-[11rem]">
|
||||||
|
<li class="flex items-center flex-col" v-for="item in chartData" :key="item.name">
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="w-[1.5rem] h-[1.5rem] rounded-full" :style="{ backgroundColor: item.color }"></div>
|
||||||
|
<div class="flex-1 flex items-center text-[#C0EEFF] text-[3.5rem] justify-between">
|
||||||
|
<span class="ml-[1.5rem] mr-[2rem]">{{ item.name }}</span>
|
||||||
|
<span>{{ item.value }}人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-image w-full mt-[1.5rem]"></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ProportionCharts from "@/views/components/chartsComponents/ProportionCharts.vue";
|
||||||
|
|
||||||
|
const canvasWidth = ref(0);
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
canvasWidth.value = 41.25 * (document.documentElement.clientWidth / 93.75);
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorList = ref(["#0783FA", "#07D1FA", "#20E6A4", "#FFD15C"]);
|
||||||
|
|
||||||
|
const askSectionData = inject("askSectionData", ref<{ offline: any[]; scource: any[] }>({ offline: [], scource: [] }));
|
||||||
|
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => askSectionData.value,
|
||||||
|
() => {
|
||||||
|
initData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const offlineTotal = ref<any>(0);
|
||||||
|
|
||||||
|
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],
|
||||||
|
}));
|
||||||
|
offlineTotal.value = askSectionData.value.scource.filter((item) => item.tag === "线下")[0].total;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initData();
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
.border-image {
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(90deg, #217ac600, #227cc8, #217ac600) 1 1;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<div class="relative mt-[6.25rem] flex items-center justify-center">
|
||||||
|
<ProportionCharts :chart-data="chartData" class="w-[41.25rem] h-[41.25rem]" :width="canvasWidth" :height="canvasWidth" />
|
||||||
|
<div class="leading-[1] absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-3 flex items-center flex-col font-700 italic">
|
||||||
|
<div class="flex items-baseline">
|
||||||
|
<div class="text-[7rem] text-color" :data-text="onlineTotal">{{ onlineTotal }}</div>
|
||||||
|
<div class="text-[5.75rem] text-color" data-text="人">人</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-[#FFFFFF] text-[4.5rem] text-shadow-[0px_2px_2px_rgba(12,32,72,0.42)] mt-[2rem]">线上</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="px-[3.75rem] grid grid-cols-2 gap-x-[2.5rem] gap-y-[2.25rem] mt-[5rem] leading-[1] pb-[11rem]">
|
||||||
|
<li class="flex items-center flex-col" v-for="item in chartData" :key="item.name">
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="w-[1.5rem] h-[1.5rem] rounded-full" :style="{ backgroundColor: item.color }"></div>
|
||||||
|
<div class="flex-1 flex items-center text-[#C0EEFF] text-[3.5rem] justify-between">
|
||||||
|
<span class="ml-[1.5rem] mr-[2rem]">{{ item.name }}</span>
|
||||||
|
<span>{{ item.value }}人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-image w-full mt-[1.5rem]"></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ProportionCharts from "@/views/components/chartsComponents/ProportionCharts.vue";
|
||||||
|
|
||||||
|
const canvasWidth = ref(0);
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
canvasWidth.value = 41.25 * (document.documentElement.clientWidth / 93.75);
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorList = ref(["#0783FA", "#07D1FA", "#20E6A4", "#FFD15C", "#9A68FF"]);
|
||||||
|
|
||||||
|
const askSectionData = inject("askSectionData", ref<{ online: any[]; scource: any[] }>({ online: [], scource: [] }));
|
||||||
|
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const onlineTotal = ref<any>(0);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => askSectionData.value,
|
||||||
|
() => {
|
||||||
|
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],
|
||||||
|
}));
|
||||||
|
onlineTotal.value = askSectionData.value.scource.filter((item) => item.tag === "线上")[0].total;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initData();
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
.border-image {
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(90deg, #217ac600, #227cc8, #217ac600) 1 1;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<template>
|
||||||
|
<div class="aspect-[4.875/1] custom-bg p-[3.5rem] flex">
|
||||||
|
<div class="flex flex-col justify-between">
|
||||||
|
<div class="text-[#44C1EF] text-[3.75rem] italic font-700">六纬志愿获客</div>
|
||||||
|
<div class="mt-[2.25rem] flex items-center">
|
||||||
|
<span class="mr-[1rem] text-[6.5rem] text-color font-500 italic" :data-text="sixStatisticsData.total">{{ sixStatisticsData.total }}</span>
|
||||||
|
<span class="text-[3.5rem] font-500 text-color" data-text="人">人</span>
|
||||||
|
<SvgIcon name="arrow-up" class="text-[9px] text-[#4AFFA2] ml-[1rem]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SixStatisticsChart :chartData="sixStatisticsData.items || []" class="flex-1 h-full ml-[6rem]"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SvgIcon from "@/components/svg-icon/SvgIcon.vue";
|
||||||
|
import SixStatisticsChart from "@/views/components/chartsComponents/SixStatisticsChart.vue";
|
||||||
|
|
||||||
|
const headerLeftSvg = ref("");
|
||||||
|
const headerRightSvg = ref("");
|
||||||
|
|
||||||
|
const getHeaderLeftSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/header-bg-left-sort.svg?raw");
|
||||||
|
headerLeftSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHeaderRightSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/header-bg-right-sort.svg?raw");
|
||||||
|
headerRightSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrowLeftSvg = ref("");
|
||||||
|
const getArrowLeftSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw");
|
||||||
|
arrowLeftSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sixStatisticsData = inject("sixStatisticsData", ref<{ total: number; items: { data: string; total: number }[] }>({ total: 0, items: [] }));
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getHeaderLeftSvg();
|
||||||
|
getHeaderRightSvg();
|
||||||
|
getArrowLeftSvg();
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
|
||||||
|
.custom-bg {
|
||||||
|
background-image: url("/images/top-bg.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
<template>
|
||||||
|
<div class="custom-bg aspect-[1.8/1] overflow-hidden flex flex-col">
|
||||||
|
<ul class="grid grid-cols-4 items-center mt-[5px]">
|
||||||
|
<li
|
||||||
|
@click="handleChoose(index)"
|
||||||
|
:class="`parallelogram aspect-[8/3] text-[3.25rem] flex items-center justify-center text-[#fff] font-500 ${chosenItem === index ? 'choose' : ''}`"
|
||||||
|
v-for="(item, index) in tabList"
|
||||||
|
:key="index">
|
||||||
|
<span v-if="chosenItem !== index">{{ item }}</span>
|
||||||
|
<span v-else class="text-[3.75rem] text-color font-700" :data-text="item">{{ item }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="flex-1">
|
||||||
|
<PaymentChart class="mt-[4rem]" v-if="chosenItem === 0" />
|
||||||
|
<OperatingTrendsChart :chartDataArray="acqTrend" v-if="chosenItem === 1" :need-date="false" :ledge-gap="ledgeGap" :grid="grid" :tooltipFixed="true"/>
|
||||||
|
<AskSectionChart :chart-data="askSectionData.stages || []" v-if="chosenItem === 2" :shorthand="true"/>
|
||||||
|
<StudentStatus v-if="chosenItem === 3"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getRequest } from "@/api/customFetch";
|
||||||
|
import PaymentChart from "./appEchart/PaymentChart.vue";
|
||||||
|
import OperatingTrendsChart from "@/views/components/chartsComponents/OperatingTrendsChart.vue";
|
||||||
|
import AskSectionChart from "@/views/components/chartsComponents/AskSectionChart.vue";
|
||||||
|
import StudentStatus from "./appEchart/StudentStatus.vue";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import { getAcqTrend } from "@/api/fetchUrl";
|
||||||
|
import { addRequest, runImmediatelyByKey } from "@/composables/usePolling";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const tabList = ["缴费占比", "经营趋势", "咨询学段", "学生来源"];
|
||||||
|
|
||||||
|
const askSectionData = inject("askSectionData", ref({ stages: [],}));
|
||||||
|
|
||||||
|
const chosenItem = ref(0);
|
||||||
|
const handleChoose = (index: number) => {
|
||||||
|
chosenItem.value = index;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ledgeGap = ref(0);
|
||||||
|
const grid = ref({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
});
|
||||||
|
const handleResize = () => {
|
||||||
|
let draftsSize = document.documentElement.clientWidth / 93.75;
|
||||||
|
ledgeGap.value = draftsSize * 3.75;
|
||||||
|
grid.value = { top: 50, left: 50, right: 10, bottom: 30 };
|
||||||
|
};
|
||||||
|
|
||||||
|
let DateType = 0;
|
||||||
|
const acqTrend = ref<any>([]);
|
||||||
|
const getAcqTrendData = (type: number) => {
|
||||||
|
getRequest(getAcqTrend(), { Type: type }, {}).then((resp) => {
|
||||||
|
if (resp.code === 200) {
|
||||||
|
acqTrend.value = resp.result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addRequest("getAcqTrendData", getAcqTrendData, [DateType]);
|
||||||
|
runImmediatelyByKey("getAcqTrendData");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
handleResize();
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
.custom-bg {
|
||||||
|
background-image: url("/images/top-bg.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parallelogram {
|
||||||
|
width: 100%;
|
||||||
|
height: 7.5rem;
|
||||||
|
position: relative;
|
||||||
|
background-image: url("/images/tab-border.png");
|
||||||
|
background-position: center center;
|
||||||
|
background-size: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
scale: 0.8;
|
||||||
|
transform: skewX(-45deg);
|
||||||
|
// background-color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.parallelogram.choose {
|
||||||
|
width: calc(100% + 2.5rem);
|
||||||
|
height: 9rem;
|
||||||
|
background-image: url("/images/tab-border-choose.png");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
<template>
|
||||||
|
<div class="ranking-bg aspect-[0.8/1] px-[2.5rem] pt-[2.5rem] pb-[1.5rem]">
|
||||||
|
<header class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<SvgComponent :content="arrowLeftSvg" class="w-[3.75rem] h-[3.75rem]" />
|
||||||
|
<div class="text-color text-[3.5rem] ml-[1.25rem] font-700" data-text="获客排行榜">获客排行榜</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center" @click="isDrawerVisible = true">
|
||||||
|
<div class="text-[3rem] text-[#84E8FF]">更多</div>
|
||||||
|
<SvgComponent :content="moreArrowSvg" class="w-[2.2rem] h-[3.75rem]" />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<RankingTable :value="products.slice(0, 5)" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
||||||
|
<template #rank="{ index }">
|
||||||
|
<SvgComponent :content="goldMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 0" />
|
||||||
|
<SvgComponent :content="silverMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 1" />
|
||||||
|
<SvgComponent :content="bronzeMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 2" />
|
||||||
|
<span class="text-[2.75rem] font-600" v-if="index > 2">{{ index + 1 }}</span>
|
||||||
|
</template>
|
||||||
|
<template #name="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.name }}</span>
|
||||||
|
</template>
|
||||||
|
<template #total="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.total }}</span>
|
||||||
|
</template>
|
||||||
|
</RankingTable>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<MobileDrawer v-model="isDrawerVisible" :transition-duration="300" panelStyle="border-radius:8rem 8rem 0 0;background-color:#072362;">
|
||||||
|
<!-- 自定义头部 -->
|
||||||
|
<template #header>
|
||||||
|
<div class="drawer-header aspect-[10.7/1] w-full bg-[#072362] flex items-center justify-center relative rounded-[8rem_8rem_0_0]">
|
||||||
|
<div class="text-color text-[4.5rem] font-700 italic" style="--mask-gradient: linear-gradient(to top, transparent, #000)" data-text="获客排行榜">
|
||||||
|
获客排行榜
|
||||||
|
</div>
|
||||||
|
<img src="/images/close.png" class="w-[3rem] h-[3rem] absolute right-[5rem] top-[3.75rem]" alt="" @click="isDrawerVisible = false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 自定义内容 -->
|
||||||
|
<RankingTable :value="products" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
||||||
|
<template #rank="{ index }">
|
||||||
|
<SvgComponent :content="goldMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 0" />
|
||||||
|
<SvgComponent :content="silverMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 1" />
|
||||||
|
<SvgComponent :content="bronzeMedalSvg" class="w-[6rem] h-[3.5rem]" v-if="index === 2" />
|
||||||
|
<span class="text-[2.75rem] font-600" v-if="index > 2">{{ index + 1 }}</span>
|
||||||
|
</template>
|
||||||
|
<template #name="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.name }}</span>
|
||||||
|
</template>
|
||||||
|
<template #total="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[2.75rem]">{{ data.total }}</span>
|
||||||
|
</template>
|
||||||
|
</RankingTable>
|
||||||
|
</MobileDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import SvgComponent from "@/components/SvgComponent.vue";
|
||||||
|
import RankingTable from "@/components/table/RankingTable.vue";
|
||||||
|
import MobileDrawer from "./MobileDrawer.vue";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ field: "rank", header: "名次", align: "justify-center", width: "6rem" },
|
||||||
|
{ field: "name", header: "姓名", align: "justify-left", width: "13.5rem" },
|
||||||
|
{ field: "total", header: "获客人数", align: "justify-center", width: "15rem" },
|
||||||
|
];
|
||||||
|
const isDrawerVisible = ref(false);
|
||||||
|
|
||||||
|
let products = ref<any[]>([]);
|
||||||
|
const chargingRankingData = inject(
|
||||||
|
"chargingRankingData",
|
||||||
|
ref<{
|
||||||
|
acqRanks: any[];
|
||||||
|
}>({
|
||||||
|
acqRanks: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => chargingRankingData.value,
|
||||||
|
() => {
|
||||||
|
PageTransitionEvent;
|
||||||
|
initData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if (chargingRankingData.value.acqRanks) {
|
||||||
|
products.value = chargingRankingData.value.acqRanks;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerLeftSvg = ref("");
|
||||||
|
const headerRightSvg = ref("");
|
||||||
|
|
||||||
|
const getHeaderLeftSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/header-bg-left-sort.svg?raw");
|
||||||
|
headerLeftSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHeaderRightSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/header-bg-right-sort.svg?raw");
|
||||||
|
headerRightSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrowLeftSvg = ref("");
|
||||||
|
const getArrowLeftSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/arrow-left.svg?raw");
|
||||||
|
arrowLeftSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const moreArrowSvg = ref("");
|
||||||
|
const getMoreArrowSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/more-arrow.svg?raw");
|
||||||
|
moreArrowSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const goldMedalSvg = ref("");
|
||||||
|
const getGoldMedalSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/gold-medal.svg?raw");
|
||||||
|
goldMedalSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const silverMedalSvg = ref("");
|
||||||
|
const getSilverMedalSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/silver-medal.svg?raw");
|
||||||
|
silverMedalSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bronzeMedalSvg = ref("");
|
||||||
|
const getBronzeMedalSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/bronze-medal.svg?raw");
|
||||||
|
bronzeMedalSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getHeaderLeftSvg();
|
||||||
|
getHeaderRightSvg();
|
||||||
|
getArrowLeftSvg();
|
||||||
|
|
||||||
|
getMoreArrowSvg();
|
||||||
|
|
||||||
|
getGoldMedalSvg();
|
||||||
|
getSilverMedalSvg();
|
||||||
|
getBronzeMedalSvg();
|
||||||
|
|
||||||
|
initData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
|
||||||
|
.ranking-bg {
|
||||||
|
background-image: url("/images/ranking-border.png");
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table-header) {
|
||||||
|
tr {
|
||||||
|
background-color: rgb(14, 39, 97);
|
||||||
|
th {
|
||||||
|
padding: 2rem 1.5rem;
|
||||||
|
color: #44c1ef;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 2.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table-body) {
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
padding: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background: linear-gradient(270deg, rgba(62, 118, 171, 0) 0%, rgba(62, 118, 171, 0.2) 45%, rgba(62, 118, 171, 0) 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-header {
|
||||||
|
background-image: url("/images/drawer-header.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex items-stretch pl-[6rem]">
|
||||||
|
<div class="relative flex items-center justify-center">
|
||||||
|
<ProportionCharts :chart-data="chartData" class="w-[31rem] h-[31rem]" :width="canvasWidth" :height="canvasWidth" />
|
||||||
|
<div class="leading-[1] absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-3 flex items-center flex-col font-700 italic">
|
||||||
|
<div class="flex items-baseline">
|
||||||
|
<div class="text-[4.5rem] text-color" :data-text="total">{{ total }}</div>
|
||||||
|
<div class="text-[2.75rem] text-color" data-text="人">人</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="flex flex-col justify-evenly flex-1 h-[31rem] px-[6.5rem]">
|
||||||
|
<li class="flex items-center flex-wrap" v-for="item in chartData" :key="item.name">
|
||||||
|
<div class="w-[1.5rem] h-[1.5rem] rounded-full" :style="{ backgroundColor: item.color }"></div>
|
||||||
|
<div class="flex-1 flex items-center text-[#C0EEFF] text-[3rem] justify-between">
|
||||||
|
<span class="ml-[1rem] mr-[2.25rem]">{{ item.name }}</span>
|
||||||
|
<span class="w-max break-keep">{{ item.value }}人</span>
|
||||||
|
</div>
|
||||||
|
<div class="border-image w-full mt-[1.5rem]"></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ProportionCharts from "@/views/components/chartsComponents/ProportionCharts.vue";
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const paymentData = inject("paymentData", ref({ chargeTotal: 0, estimatedTotal: 0, items: [] }));
|
||||||
|
const colors = ["#0783FA", "#07D1FA", "#20E6A4", "#FFD15C", "#9A68FF"];
|
||||||
|
const total = ref(0);
|
||||||
|
watchEffect(() => {
|
||||||
|
if (paymentData.value.items) {
|
||||||
|
chartData.value = paymentData.value.items.map((item: any, index) => {
|
||||||
|
total.value += item.value;
|
||||||
|
return { ...item, color: colors[index % colors.length] };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const canvasWidth = ref(0);
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
canvasWidth.value = 31 * (document.documentElement.clientWidth / 93.75);
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
|
||||||
|
.border-image {
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(90deg, #217ac600, #227cc8, #217ac600) 1 1;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
<template>
|
||||||
|
<div class="w-full h-full flex">
|
||||||
|
<StudentSourceChart class="w-full h-full" :chartData="chartData" :ringSize="0.8" />
|
||||||
|
<ul class="ml-[4rem] pt-[4rem] pr-[3rem]">
|
||||||
|
<li class="flex flex-col w-[32.5rem]">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="text-[3rem] text-[#C7F0FF]">未标记来源</div>
|
||||||
|
<span class="text-[3.5rem] font-500 text-color" :data-text="`${unMarkTotal}人`">{{ unMarkTotal }}人</span>
|
||||||
|
</div>
|
||||||
|
<div class="border-image w-full mt-[3rem]"></div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-col w-[32.5rem] mt-[3rem]">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="text-[3rem] text-[#C7F0FF]">线上来源</div>
|
||||||
|
<div class="flex items-center" @click="handleOpenDrawer('线上详情')">
|
||||||
|
<span class="text-[3.5rem] font-500 text-color" :data-text="`${onlineTotal}人`">{{ onlineTotal }}人</span>
|
||||||
|
<SvgComponent :content="moreArrowSvg" class="w-[14px] h-[22px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-image w-full mt-[3rem]"></div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-col w-[32.5rem] mt-[3rem]">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="text-[3rem] text-[#C7F0FF]">线下来源</div>
|
||||||
|
<div class="flex items-center" @click="handleOpenDrawer('线下详情')">
|
||||||
|
<span class="text-[3.5rem] font-500 text-color" :data-text="`${offlineTotal}人`">{{ offlineTotal }}人</span>
|
||||||
|
<SvgComponent :content="moreArrowSvg" class="w-[14px] h-[22px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<MobileDrawer v-model="isDrawerVisible" :transition-duration="300" panelStyle="border-radius:8rem 8rem 0 0;background-color:#072362;">
|
||||||
|
<!-- 自定义头部 -->
|
||||||
|
<template #header>
|
||||||
|
<div class="drawer-header aspect-[10.7/1] w-full bg-[#072362] flex items-center justify-center relative rounded-[8rem_8rem_0_0]">
|
||||||
|
<div class="text-color text-[4.5rem] font-700 italic" style="--mask-gradient: linear-gradient(to top, transparent, #000)" :data-text="drawerTitle">
|
||||||
|
{{ drawerTitle }}
|
||||||
|
</div>
|
||||||
|
<img src="/images/close.png" class="w-[3rem] h-[3rem] absolute right-[5rem] top-[3.75rem]" alt="" @click="isDrawerVisible = false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 自定义内容 -->
|
||||||
|
<OnlineStatic v-if="drawerTitle === '线上详情'" />
|
||||||
|
<OfflineStatic v-else />
|
||||||
|
</MobileDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import StudentSourceChart from "@/views/components/chartsComponents/StudentSourceChart.vue";
|
||||||
|
|
||||||
|
import SvgComponent from "@/components/SvgComponent.vue";
|
||||||
|
import MobileDrawer from "../MobileDrawer.vue";
|
||||||
|
import OnlineStatic from "../OnlineStatic.vue";
|
||||||
|
import OfflineStatic from "../OfflineStatic.vue";
|
||||||
|
|
||||||
|
const askSectionData = inject("askSectionData", ref({ online: [], offline: [] }));
|
||||||
|
const gainData = inject("gainData", ref({ acqTotal: 0 }));
|
||||||
|
const onlineTotal = ref(0);
|
||||||
|
const offlineTotal = ref(0);
|
||||||
|
const unMarkTotal = ref(0);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const isDrawerVisible = ref(false);
|
||||||
|
|
||||||
|
const drawerTitle = ref("线上详情");
|
||||||
|
|
||||||
|
const handleOpenDrawer = (title:string) => {
|
||||||
|
isDrawerVisible.value = true;
|
||||||
|
drawerTitle.value = title
|
||||||
|
}
|
||||||
|
const initData = () => {
|
||||||
|
if (askSectionData.value.online && askSectionData.value.online.length > 0) {
|
||||||
|
onlineTotal.value = askSectionData.value.online.reduce((acc, curr: any) => acc + curr.total, 0);
|
||||||
|
}
|
||||||
|
if (askSectionData.value.offline && askSectionData.value.offline.length > 0) {
|
||||||
|
offlineTotal.value = askSectionData.value.offline.reduce((acc, curr: any) => acc + curr.total, 0);
|
||||||
|
}
|
||||||
|
if (gainData.value.acqTotal && askSectionData.value.offline && askSectionData.value.online) {
|
||||||
|
unMarkTotal.value = gainData.value.acqTotal - offlineTotal.value - onlineTotal.value;
|
||||||
|
}
|
||||||
|
chartData.value = [
|
||||||
|
{ name: "线下", value: offlineTotal.value, itemStyle: { color: "rgba(147, 219, 255, 1)" } },
|
||||||
|
{ name: "线上", value: onlineTotal.value, itemStyle: { color: "rgb(79, 214, 169)" } },
|
||||||
|
{ name: "未标记", value: unMarkTotal.value, itemStyle: { color: "#FF4E4E" } },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const moreArrowSvg = ref("");
|
||||||
|
const getMoreArrowSvg = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/more-arrow.svg?raw");
|
||||||
|
moreArrowSvg.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => askSectionData.value,
|
||||||
|
() => {
|
||||||
|
initData();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getMoreArrowSvg();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/text-color.scss";
|
||||||
|
|
||||||
|
.border-image {
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(90deg, #217ac600, #227cc8, #217ac600) 1 1;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.particle-base) {
|
||||||
|
width: 50rem;
|
||||||
|
height: 21rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-header {
|
||||||
|
background-image: url("/images/drawer-header.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -9,12 +9,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[296px] h-[calc(100%-36px)] flex flex-col">
|
<div class="w-[296px] h-[calc(100%-36px)] flex flex-col">
|
||||||
<div class="flex items-center justify-end mr-[13px] transform-translate-y-[-50%] h-0">
|
<div class="flex items-center justify-end mr-[13px] transform-translate-y-[-50%] h-0" @click="show = true">
|
||||||
<div class="text-[15px] text-[#84E8FF]">更多</div>
|
<div class="text-[15px] text-[#84E8FF]">更多</div>
|
||||||
<SvgComponent :content="moreArrowSvg" class="w-[14px] h-[22px]" />
|
<SvgComponent :content="moreArrowSvg" class="w-[14px] h-[22px]" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-[26px] mx-[16px]">
|
<div class="mt-[26px] mx-[16px]">
|
||||||
<RankingTable :value="products" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
<RankingTable :value="products.slice(0,5)" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
||||||
<template #rank="{ index }">
|
<template #rank="{ index }">
|
||||||
<SvgComponent :content="goldMedalSvg" class="w-[34px] h-[20px]" v-if="index === 0" />
|
<SvgComponent :content="goldMedalSvg" class="w-[34px] h-[20px]" v-if="index === 0" />
|
||||||
<SvgComponent :content="silverMedalSvg" class="w-[34px] h-[20px]" v-if="index === 1" />
|
<SvgComponent :content="silverMedalSvg" class="w-[34px] h-[20px]" v-if="index === 1" />
|
||||||
|
|
@ -28,11 +28,59 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal v-model="show" modal-class="modal-class">
|
||||||
|
<template #title>
|
||||||
|
<div class="modal-header h-[40px] w-full bg-[#051b36] flex items-center justify-center relative">
|
||||||
|
<div class="text-color text-[24px] font-700 italic" style="--mask-gradient: linear-gradient(to top, transparent, #000)" data-text="缴费排行榜">
|
||||||
|
缴费排行榜
|
||||||
|
</div>
|
||||||
|
<img src="/images/close-clip.png" class="w-[32px] h-[32px] absolute right-0 top-0 cursor-pointer" alt="" @click="show = false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<RankingTable
|
||||||
|
:value="paginatedProducts"
|
||||||
|
class="bg-[#051b36] pl-[25px] pr-[35px] pt-[15px] max-h-[70vh] overflow-auto"
|
||||||
|
:columns="columns"
|
||||||
|
header-class="modal-table-header"
|
||||||
|
body-class="modal-table-body">
|
||||||
|
<template #rank="{ index }">
|
||||||
|
<img src="@/assets/svg-img/gold-medal.svg" class="w-[34px] h-[20px]" v-if="index === 0" alt="" />
|
||||||
|
<img src="@/assets/svg-img/silver-medal.svg" class="w-[34px] h-[20px]" v-if="index === 1" alt="" />
|
||||||
|
<img src="@/assets/svg-img/bronze-medal.svg" class="w-[34px] h-[20px]" v-if="index === 2" alt="" />
|
||||||
|
<span class="text-[14px] font-600" v-if="index > 2">{{ index + 1 }}</span>
|
||||||
|
</template>
|
||||||
|
<template #name="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[14px]">{{ data.name }}</span>
|
||||||
|
</template>
|
||||||
|
</RankingTable>
|
||||||
|
<Pagination
|
||||||
|
v-model="page"
|
||||||
|
:total="products.length"
|
||||||
|
v-model:pageSize="pageSize"
|
||||||
|
:pageSizes="[15, 20, 50, 100]"
|
||||||
|
@change="
|
||||||
|
(p, s) => {
|
||||||
|
page = p;
|
||||||
|
pageSize = s;
|
||||||
|
}
|
||||||
|
" />
|
||||||
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SvgComponent from "@/components/SvgComponent.vue";
|
import SvgComponent from "@/components/SvgComponent.vue";
|
||||||
import RankingTable from "@/components/table/RankingTable.vue";
|
import RankingTable from "@/components/table/RankingTable.vue";
|
||||||
|
import Modal from "@/components/modal/Modal.vue";
|
||||||
|
import Pagination from "@/components/pagination/index.vue";
|
||||||
|
|
||||||
|
const page = ref(1);
|
||||||
|
const pageSize = ref(15);
|
||||||
|
|
||||||
|
const paginatedProducts = computed(() => {
|
||||||
|
const start = (page.value - 1) * pageSize.value;
|
||||||
|
const end = start + pageSize.value;
|
||||||
|
return products.value.slice(start, end);
|
||||||
|
});
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ field: "rank", header: "名次", align: "justify-center", width: "68px" },
|
{ field: "rank", header: "名次", align: "justify-center", width: "68px" },
|
||||||
|
|
@ -40,6 +88,8 @@
|
||||||
{ field: "total", header: "收费人数", align: "justify-center", width: "96px" },
|
{ field: "total", header: "收费人数", align: "justify-center", width: "96px" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
|
||||||
let products = ref<any[]>([]);
|
let products = ref<any[]>([]);
|
||||||
const chargingRankingData = inject(
|
const chargingRankingData = inject(
|
||||||
"chargingRankingData",
|
"chargingRankingData",
|
||||||
|
|
@ -140,6 +190,7 @@
|
||||||
color: #44c1ef;
|
color: #44c1ef;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +200,48 @@
|
||||||
td {
|
td {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
background-image: url("/images/drawer-header.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.modal-class {
|
||||||
|
width: 560px;
|
||||||
|
border: 1px solid #0e2744;
|
||||||
|
background-color: #051b36;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-table-header {
|
||||||
|
background-color: #0d2744;
|
||||||
|
|
||||||
|
.cell-content {
|
||||||
|
padding-top: 7px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
color: #44c1ef;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-table-body {
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
div {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #0d2744;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,32 +11,35 @@
|
||||||
<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 class="w-full h-full" :chartData="chartData" :ringSize="0.8" />
|
<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 justify-between mx-[20px] my-[33px]">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center flex-col">
|
||||||
<div class="flex flex-col ml-[9px] items-center justify-center">
|
<div class="flex flex-col ml-[9px] items-center justify-center">
|
||||||
<div class="font-700 flex items-baseline">
|
<div class="font-700 flex items-baseline mb-[2px]">
|
||||||
<span class="text-[18px] text-color" :data-text="onlineTotal">{{ onlineTotal }}</span>
|
<span class="text-[18px] text-color" :data-text="onlineTotal">{{ onlineTotal }}</span>
|
||||||
<span class="text-[14px] text-color" data-text="人">人</span>
|
<span class="text-[14px] text-color" data-text="人">人</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[#C7F0FF] text-[12px]">线上来源</span>
|
<span class="text-[#C7F0FF] text-[12px]">线上来源</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="border-image w-full mt-[6px]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center flex-col">
|
||||||
<div class="flex flex-col ml-[9px] items-center justify-center">
|
<div class="flex flex-col ml-[9px] items-center justify-center">
|
||||||
<div class="font-700 flex items-baseline">
|
<div class="font-700 flex items-baseline mb-[2px]">
|
||||||
<span class="text-[18px] text-color" :data-text="offlineTotal">{{ offlineTotal }}</span>
|
<span class="text-[18px] text-color" :data-text="offlineTotal">{{ offlineTotal }}</span>
|
||||||
<span class="text-[14px] text-color" data-text="人">人</span>
|
<span class="text-[14px] text-color" data-text="人">人</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[#C7F0FF] text-[12px]">线下来源</span>
|
<span class="text-[#C7F0FF] text-[12px]">线下来源</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="border-image w-full mt-[6px]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center flex-col">
|
||||||
<div class="flex flex-col ml-[9px] items-center justify-center">
|
<div class="flex flex-col ml-[9px] items-center justify-center">
|
||||||
<div class="font-700 flex items-baseline">
|
<div class="font-700 flex items-baseline mb-[2px]">
|
||||||
<span class="text-[18px] text-color" :data-text="unMarkTotal">{{ unMarkTotal }}</span>
|
<span class="text-[18px] text-color" :data-text="unMarkTotal">{{ unMarkTotal }}</span>
|
||||||
<span class="text-[14px] text-color" data-text="人">人</span>
|
<span class="text-[14px] text-color" data-text="人">人</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[#C7F0FF] text-[12px]">未标记</span>
|
<span class="text-[#C7F0FF] text-[12px]">未标记</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="border-image w-full mt-[6px]"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -129,4 +132,10 @@
|
||||||
border-image-source: url("src/assets/svg-img/border-image.png");
|
border-image-source: url("src/assets/svg-img/border-image.png");
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-image {
|
||||||
|
border: 1px solid;
|
||||||
|
border-image: linear-gradient(90deg, #217ac600, #227cc8, #217ac600) 1 1;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[296px] h-[calc(100%-36px)] flex flex-col">
|
<div class="w-[296px] h-[calc(100%-36px)] flex flex-col">
|
||||||
<div class="flex items-center justify-end mr-[13px] transform-translate-y-[-50%] h-0">
|
<div class="flex items-center justify-end mr-[13px] transform-translate-y-[-50%] h-0" @click="show = true">
|
||||||
<div class="text-[15px] text-[#84E8FF]">更多</div>
|
<div class="text-[15px] text-[#84E8FF]">更多</div>
|
||||||
<SvgComponent :content="moreArrowSvg" class="w-[14px] h-[22px]" />
|
<SvgComponent :content="moreArrowSvg" class="w-[14px] h-[22px]" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-[26px] mx-[16px]">
|
<div class="mt-[26px] mx-[16px]">
|
||||||
<RankingTable :value="products" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
<RankingTable :value="products.slice(0,5)" :columns="columns" header-class="custom-table-header" body-class="custom-table-body">
|
||||||
<template #rank="{ index }">
|
<template #rank="{ index }">
|
||||||
<SvgComponent :content="goldMedalSvg" class="w-[34px] h-[20px]" v-if="index === 0" />
|
<SvgComponent :content="goldMedalSvg" class="w-[34px] h-[20px]" v-if="index === 0" />
|
||||||
<SvgComponent :content="silverMedalSvg" class="w-[34px] h-[20px]" v-if="index === 1" />
|
<SvgComponent :content="silverMedalSvg" class="w-[34px] h-[20px]" v-if="index === 1" />
|
||||||
|
|
@ -28,12 +28,62 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal v-model="show" modal-class="modal-class">
|
||||||
|
<template #title>
|
||||||
|
<div class="modal-header h-[40px] w-full bg-[#051b36] flex items-center justify-center relative">
|
||||||
|
<div class="text-color text-[24px] font-700 italic" style="--mask-gradient: linear-gradient(to top, transparent, #000)" data-text="获客排行榜">
|
||||||
|
获客排行榜
|
||||||
|
</div>
|
||||||
|
<img src="/images/close-clip.png" class="w-[32px] h-[32px] absolute right-0 top-0 cursor-pointer" alt="" @click="show = false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<RankingTable
|
||||||
|
:value="paginatedProducts"
|
||||||
|
class="bg-[#051b36] pl-[25px] pr-[35px] pt-[15px] max-h-[70vh] overflow-auto"
|
||||||
|
:columns="columns"
|
||||||
|
header-class="modal-table-header"
|
||||||
|
body-class="modal-table-body">
|
||||||
|
<template #rank="{ index }">
|
||||||
|
<img src="@/assets/svg-img/gold-medal.svg" class="w-[34px] h-[20px]" v-if="index === 0" alt="" />
|
||||||
|
<img src="@/assets/svg-img/silver-medal.svg" class="w-[34px] h-[20px]" v-if="index === 1" alt="" />
|
||||||
|
<img src="@/assets/svg-img/bronze-medal.svg" class="w-[34px] h-[20px]" v-if="index === 2" alt="" />
|
||||||
|
<span class="text-[14px] font-600" v-if="index > 2">{{ index + 1 }}</span>
|
||||||
|
</template>
|
||||||
|
<template #name="{ data }">
|
||||||
|
<span class="text-[#C0EEFF] text-[14px]">{{ data.name }}</span>
|
||||||
|
</template>
|
||||||
|
</RankingTable>
|
||||||
|
<Pagination
|
||||||
|
v-model="page"
|
||||||
|
:total="products.length"
|
||||||
|
v-model:pageSize="pageSize"
|
||||||
|
:pageSizes="[15, 20, 50, 100]"
|
||||||
|
@change="
|
||||||
|
(p, s) => {
|
||||||
|
page = p;
|
||||||
|
pageSize = s;
|
||||||
|
}
|
||||||
|
" />
|
||||||
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SvgComponent from "@/components/SvgComponent.vue";
|
import SvgComponent from "@/components/SvgComponent.vue";
|
||||||
import RankingTable from "@/components/table/RankingTable.vue";
|
import RankingTable from "@/components/table/RankingTable.vue";
|
||||||
|
|
||||||
|
import Modal from "@/components/modal/Modal.vue";
|
||||||
|
import Pagination from "@/components/pagination/index.vue";
|
||||||
|
|
||||||
|
const page = ref(1);
|
||||||
|
const pageSize = ref(15);
|
||||||
|
const show = ref(false);
|
||||||
|
|
||||||
|
const paginatedProducts = computed(() => {
|
||||||
|
const start = (page.value - 1) * pageSize.value;
|
||||||
|
const end = start + pageSize.value;
|
||||||
|
return products.value.slice(start, end);
|
||||||
|
});
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ field: "rank", header: "名次", align: "justify-center", width: "68px" },
|
{ field: "rank", header: "名次", align: "justify-center", width: "68px" },
|
||||||
{ field: "name", header: "姓名", align: "justify-left", width: "100px" },
|
{ field: "name", header: "姓名", align: "justify-left", width: "100px" },
|
||||||
|
|
@ -141,6 +191,7 @@
|
||||||
color: #44c1ef;
|
color: #44c1ef;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +201,48 @@
|
||||||
td {
|
td {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
background-image: url("/images/drawer-header.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.modal-class {
|
||||||
|
width: 560px;
|
||||||
|
border: 1px solid #0e2744;
|
||||||
|
background-color: #051b36;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-table-header {
|
||||||
|
background-color: #0d2744;
|
||||||
|
|
||||||
|
.cell-content {
|
||||||
|
padding-top: 7px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
color: #44c1ef;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-table-body {
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
div {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #0d2744;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { convertNumber } from "@/utils/convertNumber";
|
||||||
|
import { usePlatformType } from "@/utils/device";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
|
|
||||||
interface DataItem {
|
interface DataItem {
|
||||||
|
|
@ -10,9 +12,16 @@
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps({
|
||||||
chartData: DataItem[];
|
chartData:{
|
||||||
}>();
|
type:Array<DataItem>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
shorthand:{
|
||||||
|
type:Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const renderItem = (params: any, api: { coord: (arg0: any[]) => any[]; value: (arg0: number) => any; style: () => any }) => {
|
const renderItem = (params: any, api: { coord: (arg0: any[]) => any[]; value: (arg0: number) => any; style: () => any }) => {
|
||||||
// 柱子索引值
|
// 柱子索引值
|
||||||
|
|
@ -96,10 +105,11 @@
|
||||||
if (!chartDom.value) return;
|
if (!chartDom.value) return;
|
||||||
|
|
||||||
myChart = echarts.init(chartDom.value);
|
myChart = echarts.init(chartDom.value);
|
||||||
|
const {isMobile} = usePlatformType()
|
||||||
// 侧面宽度
|
// 侧面宽度
|
||||||
const WIDTH = 10;
|
const WIDTH = isMobile ? 6 : 10;
|
||||||
// 斜角高度
|
// 斜角高度
|
||||||
const OBLIQUE_ANGLE_HEIGHT = 3.5;
|
const OBLIQUE_ANGLE_HEIGHT = isMobile ? 2 : 3.5;
|
||||||
const leftShape = echarts.graphic.extendShape({
|
const leftShape = echarts.graphic.extendShape({
|
||||||
buildPath(ctx, shape) {
|
buildPath(ctx, shape) {
|
||||||
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
|
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
|
||||||
|
|
@ -171,25 +181,29 @@
|
||||||
// 初始化时设置空数据,后续通过updateChartData更新
|
// 初始化时设置空数据,后续通过updateChartData更新
|
||||||
const options = {
|
const options = {
|
||||||
grid: {
|
grid: {
|
||||||
top: "12%",
|
top: props.shorthand ? 30:40,
|
||||||
left: "5%",
|
left: props.shorthand ? 40 : 60,
|
||||||
right: "3%",
|
right: props.shorthand ?20 : 30,
|
||||||
bottom: "15%",
|
bottom: props.shorthand ?30:45,
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: "category",
|
type: "category",
|
||||||
data: [],
|
data: [],
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: (value: any) => {
|
formatter: (value: any) => {
|
||||||
|
if(props.shorthand){
|
||||||
|
return value.slice(0,2)
|
||||||
|
}else{
|
||||||
if (value.length <= 4) return value;
|
if (value.length <= 4) return value;
|
||||||
const arr = [];
|
const arr = [];
|
||||||
for (let i = 0; i < value.length; i += 4) {
|
for (let i = 0; i < value.length; i += 4) {
|
||||||
arr.push(value.slice(i, i + 4));
|
arr.push(value.slice(i, i + 4));
|
||||||
}
|
}
|
||||||
return arr.join('\n');
|
return arr.join('\n');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
color: "#C0EEFF",
|
color: "#C0EEFF",
|
||||||
fontSize: 14,
|
fontSize: props.shorthand ? 10 : 14,
|
||||||
interval: 0,
|
interval: 0,
|
||||||
},
|
},
|
||||||
nameTextStyle: {
|
nameTextStyle: {
|
||||||
|
|
@ -206,7 +220,7 @@
|
||||||
nameLocation: "end",
|
nameLocation: "end",
|
||||||
nameTextStyle: {
|
nameTextStyle: {
|
||||||
color: "#C0EEFF",
|
color: "#C0EEFF",
|
||||||
fontSize: 14,
|
fontSize: props.shorthand ? 10 : 14,
|
||||||
padding: [0, 5, 0, 0],
|
padding: [0, 5, 0, 0],
|
||||||
align: "right",
|
align: "right",
|
||||||
},
|
},
|
||||||
|
|
@ -216,7 +230,14 @@
|
||||||
interval: 20,
|
interval: 20,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: "#C0EEFF",
|
color: "#C0EEFF",
|
||||||
fontSize: 14,
|
fontSize: props.shorthand ? 10 : 14,
|
||||||
|
formatter: (value: any) => {
|
||||||
|
if(props.shorthand){
|
||||||
|
return convertNumber(value)
|
||||||
|
}else{
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
axisLine: {
|
axisLine: {
|
||||||
show: true,
|
show: true,
|
||||||
|
|
@ -245,7 +266,7 @@
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: "top",
|
position: "top",
|
||||||
fontSize: 12,
|
fontSize: props.shorthand ? 10 : 12,
|
||||||
color: "rgba(192, 238, 255, 1)",
|
color: "rgba(192, 238, 255, 1)",
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
|
@ -258,7 +279,6 @@
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(options);
|
myChart.setOption(options);
|
||||||
|
|
||||||
// 如果有数据,立即更新
|
// 如果有数据,立即更新
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="chartDom" class="w-full h-full"></div>
|
<div ref="chartDom" class="w-full h-full" :style="`--tip-width:${tooltipWidth}`"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -12,8 +12,33 @@
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
chartDataArray: {
|
chartDataArray: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
needDate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
top: 40,
|
||||||
|
left: 60,
|
||||||
|
right: 30,
|
||||||
|
bottom: 30,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
ledgeGap: {
|
||||||
|
type: Number,
|
||||||
|
default: 40,
|
||||||
|
},
|
||||||
|
tooltipFixed:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
tooltipWidth:{
|
||||||
|
type:String,
|
||||||
|
default: '184px'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emits = defineEmits(["dateChange"]);
|
const emits = defineEmits(["dateChange"]);
|
||||||
|
|
@ -58,7 +83,8 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
}
|
}
|
||||||
// 处理图表数据
|
// 处理图表数据
|
||||||
const processChartData = (dataArray: ChartTypeData[], mode: string): ChartDataFormat => {
|
const processChartData = (dataArray: ChartTypeData[], mode: string): ChartDataFormat => {
|
||||||
if (!dataArray || dataArray.length === 0) return {
|
if (!dataArray || dataArray.length === 0)
|
||||||
|
return {
|
||||||
dates: [],
|
dates: [],
|
||||||
seriesData: [
|
seriesData: [
|
||||||
{ name: "获客", data: [], color: "#20E6A4" },
|
{ name: "获客", data: [], color: "#20E6A4" },
|
||||||
|
|
@ -82,8 +108,8 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
// 将日期转换为数组并排序
|
// 将日期转换为数组并排序
|
||||||
let sortedDates = Array.from(allDates).sort((a: string, b: string) => {
|
let sortedDates = Array.from(allDates).sort((a: string, b: string) => {
|
||||||
// 假设日期格式为 MM-DD
|
// 假设日期格式为 MM-DD
|
||||||
const [monthA, dayA] = a.split('-').map(Number);
|
const [monthA, dayA] = a.split("-").map(Number);
|
||||||
const [monthB, dayB] = b.split('-').map(Number);
|
const [monthB, dayB] = b.split("-").map(Number);
|
||||||
if (monthA !== monthB) return monthA - monthB;
|
if (monthA !== monthB) return monthA - monthB;
|
||||||
return dayA - dayB;
|
return dayA - dayB;
|
||||||
});
|
});
|
||||||
|
|
@ -106,7 +132,7 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
|
|
||||||
// 格式化日期显示
|
// 格式化日期显示
|
||||||
const formattedDates = sortedDates.map((date: string) => {
|
const formattedDates = sortedDates.map((date: string) => {
|
||||||
const [month, day] = date.split('-');
|
const [month, day] = date.split("-");
|
||||||
return `${month}.${day}`;
|
return `${month}.${day}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -122,7 +148,7 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
// 对每个系列填充当前日期的值
|
// 对每个系列填充当前日期的值
|
||||||
dataArray.forEach((typeData: ChartTypeData, typeIndex: number) => {
|
dataArray.forEach((typeData: ChartTypeData, typeIndex: number) => {
|
||||||
if (typeIndex < seriesData.length) {
|
if (typeIndex < seriesData.length) {
|
||||||
const dateItem = typeData.items.find(item => item.date === date);
|
const dateItem = typeData.items.find((item) => item.date === date);
|
||||||
seriesData[typeIndex].data.push(dateItem ? dateItem.value : 0);
|
seriesData[typeIndex].data.push(dateItem ? dateItem.value : 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -155,7 +181,6 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
|
|
||||||
const legendIcons = ref<{ [key: string]: string }>({});
|
const legendIcons = ref<{ [key: string]: string }>({});
|
||||||
|
|
||||||
|
|
||||||
// 初始化图例图标
|
// 初始化图例图标
|
||||||
const initLegendIcons = () => {
|
const initLegendIcons = () => {
|
||||||
chartData.value.seriesData.forEach((item) => {
|
chartData.value.seriesData.forEach((item) => {
|
||||||
|
|
@ -236,7 +261,7 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
// 计算y轴最大值
|
// 计算y轴最大值
|
||||||
const calculateMaxValue = () => {
|
const calculateMaxValue = () => {
|
||||||
let maxValue = 0;
|
let maxValue = 0;
|
||||||
chartData.value.seriesData.forEach(series => {
|
chartData.value.seriesData.forEach((series) => {
|
||||||
const seriesMax = Math.max(...series.data);
|
const seriesMax = Math.max(...series.data);
|
||||||
maxValue = Math.max(maxValue, seriesMax);
|
maxValue = Math.max(maxValue, seriesMax);
|
||||||
});
|
});
|
||||||
|
|
@ -256,12 +281,9 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="72" height="28" viewBox="0 0 72 28"><defs><radialGradient cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" id="master_svg0_85_00187" gradientTransform="translate(36 28) rotate(90) scale(16.125258922576904 182.11454980862436)"><stop offset="0%" stop-color="#FFCE4F" stop-opacity="0.5749016404151917"/><stop offset="99.90439414978027%" stop-color="#E0BF00" stop-opacity="0"/></radialGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg1_85_00188"><stop offset="0%" stop-color="#E0BF00" stop-opacity="0.4000000059604645"/><stop offset="100%" stop-color="#E0BF00" stop-opacity="0"/></linearGradient><linearGradient x1="0.5" y1="1" x2="0.5" y2="1.9133386611938477" id="master_svg2_85_00190"><stop offset="0%" stop-color="#FFF7A6" stop-opacity="1"/><stop offset="100%" stop-color="#046172" stop-opacity="0"/></linearGradient></defs><g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg0_85_00187)" fill-opacity="1"/><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg1_85_00188)" fill-opacity="1"/></g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113ZM14.5113,1L71,1L71,13.4887Q71,18.6574,70.8935,20.1702Q70.753,22.1653,70.237,23.1779Q69.2045,25.2045,67.1779,26.237Q66.1653,26.753,64.1702,26.8935Q62.6574,27,57.4887,27L1,27L1,14.5113Q1,9.34262,1.10652,7.82977Q1.247,5.83468,1.76295,4.82207Q2.79553,2.79553,4.82207,1.76295Q5.83468,1.247,7.82977,1.10652Q9.34262,1,14.5113,1Z" fill-rule="evenodd" fill="url(#master_svg2_85_00190)" fill-opacity="1"/></g></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="72" height="28" viewBox="0 0 72 28"><defs><radialGradient cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" id="master_svg0_85_00187" gradientTransform="translate(36 28) rotate(90) scale(16.125258922576904 182.11454980862436)"><stop offset="0%" stop-color="#FFCE4F" stop-opacity="0.5749016404151917"/><stop offset="99.90439414978027%" stop-color="#E0BF00" stop-opacity="0"/></radialGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg1_85_00188"><stop offset="0%" stop-color="#E0BF00" stop-opacity="0.4000000059604645"/><stop offset="100%" stop-color="#E0BF00" stop-opacity="0"/></linearGradient><linearGradient x1="0.5" y1="1" x2="0.5" y2="1.9133386611938477" id="master_svg2_85_00190"><stop offset="0%" stop-color="#FFF7A6" stop-opacity="1"/><stop offset="100%" stop-color="#046172" stop-opacity="0"/></linearGradient></defs><g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg0_85_00187)" fill-opacity="1"/><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg1_85_00188)" fill-opacity="1"/></g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113ZM14.5113,1L71,1L71,13.4887Q71,18.6574,70.8935,20.1702Q70.753,22.1653,70.237,23.1779Q69.2045,25.2045,67.1779,26.237Q66.1653,26.753,64.1702,26.8935Q62.6574,27,57.4887,27L1,27L1,14.5113Q1,9.34262,1.10652,7.82977Q1.247,5.83468,1.76295,4.82207Q2.79553,2.79553,4.82207,1.76295Q5.83468,1.247,7.82977,1.10652Q9.34262,1,14.5113,1Z" fill-rule="evenodd" fill="url(#master_svg2_85_00190)" fill-opacity="1"/></g></g></svg>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const option = {
|
const option: { [key: string]: any } = {
|
||||||
grid: {
|
grid: {
|
||||||
top: "14%",
|
...props.grid,
|
||||||
left: "5%",
|
|
||||||
right: "3%",
|
|
||||||
bottom: "15%",
|
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
type: "plain",
|
type: "plain",
|
||||||
|
|
@ -269,7 +291,7 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
itemHeight: 16,
|
itemHeight: 16,
|
||||||
top: 3,
|
top: 3,
|
||||||
left: "center",
|
left: "center",
|
||||||
itemGap: 40,
|
itemGap: props.ledgeGap,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: "#C0EEFF",
|
color: "#C0EEFF",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
@ -290,84 +312,12 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 使用graphic组件添加自定义按钮
|
// 使用graphic组件添加自定义按钮
|
||||||
graphic: [
|
|
||||||
// 按周按钮
|
|
||||||
{
|
|
||||||
type: "group",
|
|
||||||
right: 97,
|
|
||||||
top: 0,
|
|
||||||
// z: 100,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: "image",
|
|
||||||
// z: 100,
|
|
||||||
style: {
|
|
||||||
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "week" ? activeButtonSvgBase64 : buttonSvgBase64}`,
|
|
||||||
width: 72,
|
|
||||||
height: 28,
|
|
||||||
},
|
|
||||||
cursor: "pointer",
|
|
||||||
onclick: () => switchDisplayMode("week"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
// z: 100,
|
|
||||||
style: {
|
|
||||||
text: "按周",
|
|
||||||
x: 36,
|
|
||||||
y: 14,
|
|
||||||
textAlign: "center",
|
|
||||||
textVerticalAlign: "middle",
|
|
||||||
fill: `${currentMode.value === "week" ? "#F8EA21" : "#DBF7FF"}`,
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
cursor: "pointer",
|
|
||||||
onclick: () => switchDisplayMode("week"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// 按月按钮
|
|
||||||
{
|
|
||||||
type: "group",
|
|
||||||
right: 15,
|
|
||||||
top: 0,
|
|
||||||
// z: 100,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: "image",
|
|
||||||
// z: 100,
|
|
||||||
style: {
|
|
||||||
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "month" ? activeButtonSvgBase64 : buttonSvgBase64}`,
|
|
||||||
width: 72,
|
|
||||||
height: 28,
|
|
||||||
},
|
|
||||||
cursor: "pointer",
|
|
||||||
onclick: () => switchDisplayMode("month"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
// z: 100,
|
|
||||||
style: {
|
|
||||||
text: "按月",
|
|
||||||
x: 36,
|
|
||||||
y: 14,
|
|
||||||
textAlign: "center",
|
|
||||||
textVerticalAlign: "middle",
|
|
||||||
fill: `${currentMode.value === "month" ? "#F8EA21" : "#DBF7FF"}`,
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
cursor: "pointer",
|
|
||||||
onclick: () => switchDisplayMode("month"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
renderMode: "html",
|
renderMode: "html",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
extraCssText: "background-color: transparent;",
|
extraCssText: `background-color: transparent;box-shadow:unset;${props.tooltipFixed ? 'scale:0.8;':''}`,
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
const date = params[0].name;
|
const date = params[0].name;
|
||||||
const seriesData = params
|
const seriesData = params
|
||||||
|
|
@ -483,6 +433,85 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (props.needDate) {
|
||||||
|
option.graphic = [
|
||||||
|
// 按周按钮
|
||||||
|
{
|
||||||
|
type: "group",
|
||||||
|
right: 97,
|
||||||
|
top: 0,
|
||||||
|
// z: 100,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "image",
|
||||||
|
// z: 100,
|
||||||
|
style: {
|
||||||
|
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "week" ? activeButtonSvgBase64 : buttonSvgBase64}`,
|
||||||
|
width: 72,
|
||||||
|
height: 28,
|
||||||
|
},
|
||||||
|
cursor: "pointer",
|
||||||
|
onclick: () => switchDisplayMode("week"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
// z: 100,
|
||||||
|
style: {
|
||||||
|
text: "按周",
|
||||||
|
x: 36,
|
||||||
|
y: 14,
|
||||||
|
textAlign: "center",
|
||||||
|
textVerticalAlign: "middle",
|
||||||
|
fill: `${currentMode.value === "week" ? "#F8EA21" : "#DBF7FF"}`,
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
cursor: "pointer",
|
||||||
|
onclick: () => switchDisplayMode("week"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// 按月按钮
|
||||||
|
{
|
||||||
|
type: "group",
|
||||||
|
right: 15,
|
||||||
|
top: 0,
|
||||||
|
// z: 100,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "image",
|
||||||
|
// z: 100,
|
||||||
|
style: {
|
||||||
|
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "month" ? activeButtonSvgBase64 : buttonSvgBase64}`,
|
||||||
|
width: 72,
|
||||||
|
height: 28,
|
||||||
|
},
|
||||||
|
cursor: "pointer",
|
||||||
|
onclick: () => switchDisplayMode("month"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
// z: 100,
|
||||||
|
style: {
|
||||||
|
text: "按月",
|
||||||
|
x: 36,
|
||||||
|
y: 14,
|
||||||
|
textAlign: "center",
|
||||||
|
textVerticalAlign: "middle",
|
||||||
|
fill: `${currentMode.value === "month" ? "#F8EA21" : "#DBF7FF"}`,
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
cursor: "pointer",
|
||||||
|
onclick: () => switchDisplayMode("month"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(props.tooltipFixed){
|
||||||
|
option.tooltip.position=["center",'0']
|
||||||
|
}
|
||||||
|
|
||||||
myChart.setOption(option, true);
|
myChart.setOption(option, true);
|
||||||
|
|
||||||
// 跟踪鼠标指示器状态
|
// 跟踪鼠标指示器状态
|
||||||
|
|
@ -597,12 +626,26 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
|
initChart();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听图表数据变化,更新图表
|
// 监听图表数据变化,更新图表
|
||||||
watch(() => props.chartDataArray, () => {
|
watch(
|
||||||
|
() => props.chartDataArray,
|
||||||
|
() => {
|
||||||
initChart();
|
initChart();
|
||||||
}, { deep: true });
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.grid,
|
||||||
|
(newGrid) => {
|
||||||
|
console.log(newGrid);
|
||||||
|
|
||||||
|
myChart?.setOption({ grid: { ...newGrid } });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
|
|
@ -614,7 +657,7 @@ function svgToPercentEncodedDataUrl(svg: string) {
|
||||||
:deep(.tool-tip-wrapper) {
|
:deep(.tool-tip-wrapper) {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 184px;
|
width: var(--tip-width);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0px 4px 10px 0px #0d3472;
|
box-shadow: 0px 4px 10px 0px #0d3472;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-[143px] h-[143px]">
|
<div class="w-[143px] h-[143px]">
|
||||||
<div class="w-[143px] h-[143px] bg-transparent" ref="chartRef"></div>
|
<div class="w-full h-inherit bg-transparent" ref="chartRef"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -19,6 +19,14 @@
|
||||||
required: true,
|
required: true,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 143,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 143,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const generatedFormattedData = () => {
|
const generatedFormattedData = () => {
|
||||||
|
|
@ -49,6 +57,10 @@
|
||||||
|
|
||||||
// 配置项
|
// 配置项
|
||||||
const option = {
|
const option = {
|
||||||
|
grid:{
|
||||||
|
width:'auto',
|
||||||
|
height:'auto'
|
||||||
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "item",
|
trigger: "item",
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||||
|
|
@ -58,6 +70,7 @@
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
},
|
},
|
||||||
formatter: "{b}: {d}%",
|
formatter: "{b}: {d}%",
|
||||||
|
position: ['25%', '40%']
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
|
|
@ -98,9 +111,9 @@
|
||||||
top: "center",
|
top: "center",
|
||||||
style: {
|
style: {
|
||||||
image: "/images/rotate-circle.webp",
|
image: "/images/rotate-circle.webp",
|
||||||
width: 143,
|
width: props.width,
|
||||||
height: 143,
|
height: props.height,
|
||||||
fill:"transparent"
|
fill: "transparent",
|
||||||
},
|
},
|
||||||
z: 2,
|
z: 2,
|
||||||
cursor: "point",
|
cursor: "point",
|
||||||
|
|
@ -111,7 +124,6 @@
|
||||||
animation: true,
|
animation: true,
|
||||||
animationDuration: 500,
|
animationDuration: 500,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置配置项
|
// 设置配置项
|
||||||
chartInstance.setOption(option);
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
|
@ -148,6 +160,36 @@
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.width,
|
||||||
|
(newWidth) => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.setOption({
|
||||||
|
graphic: [
|
||||||
|
{
|
||||||
|
id: "circle-rotate",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "image",
|
||||||
|
left: "center",
|
||||||
|
top: "center",
|
||||||
|
style: {
|
||||||
|
image: "/images/rotate-circle.webp",
|
||||||
|
width: newWidth,
|
||||||
|
height: props.height,
|
||||||
|
fill: "transparent",
|
||||||
|
},
|
||||||
|
z: 2,
|
||||||
|
cursor: "point",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 移除事件监听
|
// 移除事件监听
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="chartRef" class="chart-container"></div>
|
<div ref="chartRef" class="w-full h-[400px]"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
@ -143,8 +143,4 @@ onUnmounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.chart-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -161,6 +161,7 @@
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
triggerOn:"mousemove|click",
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
if (params.seriesName !== "mouseoutSeries" && params.seriesName !== "pie2d") {
|
if (params.seriesName !== "mouseoutSeries" && params.seriesName !== "pie2d") {
|
||||||
const bfb = ((series[params.seriesIndex].pieData.endRatio - series[params.seriesIndex].pieData.startRatio) * 100).toFixed(2);
|
const bfb = ((series[params.seriesIndex].pieData.endRatio - series[params.seriesIndex].pieData.startRatio) * 100).toFixed(2);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export const useFetchAllData = () => {
|
||||||
|
|
||||||
const chargingRankingData = ref<any>({});
|
const chargingRankingData = ref<any>({});
|
||||||
const getChargingRankingData = () => {
|
const getChargingRankingData = () => {
|
||||||
getRequest(getBigScreenRanking(), {}, {}).then((resp) => {
|
getRequest(getBigScreenRanking(), {take:9999}, {}).then((resp) => {
|
||||||
if (resp.code === 200) {
|
if (resp.code === 200) {
|
||||||
chargingRankingData.value = resp.result;
|
chargingRankingData.value = resp.result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,88 +44,74 @@
|
||||||
<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 { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from "vue";
|
||||||
|
import { useFetchAllData } from "./composables/useFetchData";
|
||||||
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 { 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 { getUserInfoUrl, getLogoutUrl } from "@/api/fetchUrl";
|
||||||
import { getRequest, postRequest } from "@/api/customFetch";
|
import { getRequest, postRequest } from "@/api/customFetch";
|
||||||
import { useUserStore } from "@/store/user";
|
import { useUserStore } from "@/store/user";
|
||||||
|
import { asyncComponentConfig } from "@/composables/useLazyLoad";
|
||||||
|
import { usePlatformType } from "@/utils/device";
|
||||||
|
|
||||||
|
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"),
|
||||||
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const headerSvg = ref("");
|
const headerSvg = ref("");
|
||||||
const headerBackgroundSvg = async () => {
|
const headerBackgroundSvg = async () => {
|
||||||
const { default: svg } = await import("/src/assets/svg-img/header-background.svg?raw");
|
const { default: svg } = await import("/src/assets/svg-img/header-background.svg?raw");
|
||||||
|
|
@ -138,42 +124,45 @@
|
||||||
titleSvg.value = svg;
|
titleSvg.value = svg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useFetchAllData();
|
||||||
useFetchAllData()
|
|
||||||
const updateAllData = () => {
|
const updateAllData = () => {
|
||||||
runImmediatelyAll()
|
runImmediatelyAll();
|
||||||
}
|
};
|
||||||
|
|
||||||
const username = ref("")
|
const username = ref("");
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore();
|
||||||
const getUserInfo = () => {
|
const getUserInfo = () => {
|
||||||
const accessToken = userStore.getAccessToken
|
const accessToken = userStore.getAccessToken;
|
||||||
getRequest(getUserInfoUrl(),{},{headers:{ Authorization: `Bearer ${accessToken}`}}).then(resp => {
|
getRequest(getUserInfoUrl(), {}, { headers: { Authorization: `Bearer ${accessToken}` } }).then((resp) => {
|
||||||
if (resp.code === 200) {
|
if (resp.code === 200) {
|
||||||
username.value = (resp.result as {account:string}).account
|
username.value = (resp.result as { account: string }).account;
|
||||||
} else if (resp.code === 401) {
|
} else if (resp.code === 401) {
|
||||||
router.push("/login")
|
router.push("/login");
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
const accessToken = userStore.getAccessToken
|
const accessToken = userStore.getAccessToken;
|
||||||
postRequest(getLogoutUrl(),{},{headers:{"content-type": "application/json; charset=utf-8",Authorization: `Bearer ${accessToken}`}}).then(resp => {
|
postRequest(getLogoutUrl(), {}, { headers: { "content-type": "application/json; charset=utf-8", Authorization: `Bearer ${accessToken}` } }).then((resp) => {
|
||||||
if (resp.code === 200) {
|
if (resp.code === 200) {
|
||||||
userStore.setAccessToken('')
|
userStore.setAccessToken("");
|
||||||
userStore.setRefreshToken('')
|
userStore.setRefreshToken("");
|
||||||
|
|
||||||
}
|
|
||||||
router.push("/login")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
router.push("/login");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
headerBackgroundSvg();
|
headerBackgroundSvg();
|
||||||
headerTitleSvg();
|
headerTitleSvg();
|
||||||
getUserInfo();
|
getUserInfo();
|
||||||
|
const { isMobile } = usePlatformType();
|
||||||
|
// 跳转到首页
|
||||||
|
if (isMobile) {
|
||||||
|
router.push("/app");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="login-bg">
|
<div class="login-bg">
|
||||||
<div class="flex items-center justify-end mt-[17px] mr-[24px]">
|
<div class="flex items-center md:justify-end justify-center mt-[17px] mr-[24px]">
|
||||||
<DigitalWatch class="" />
|
<DigitalWatch class="" />
|
||||||
</div>
|
</div>
|
||||||
<SvgComponent :content="titleSvg" class="h-[156px] mt-[95px]" />
|
<SvgComponent :content="titleSvg" class="md:h-[156px] mt-[95px] w-full" />
|
||||||
<div class="login-form-wrapper w-[622px] h-[419px] mx-auto mt-[87px]">
|
<div class="login-form-wrapper md:w-[622px] md:h-[419px] aspect-[1.48/1] w-full mx-auto mt-[87px]">
|
||||||
<form class="w-full h-full flex flex-col items-center pt-[126px]" @submit="handleSubmit">
|
<form class="w-full h-full flex flex-col items-center md:pt-[126px] pt-[21.5rem]" @submit="handleSubmit">
|
||||||
<div class="form-item px-[71px] w-full">
|
<div class="form-item px-[71px] w-full">
|
||||||
<div class="input-bg w-full h-[48px] flex items-center px-[18px]">
|
<div class="input-bg w-full md:h-[48px] h-[6rem] flex items-center px-[18px]">
|
||||||
<SvgIcon name="avatar" class="text-[#00D5FF] mr-[13px]" />
|
<SvgIcon name="avatar" class="text-[#00D5FF] mr-[13px] w-[3.5rem]! h-[3.5rem]! md:w-[16px]! md:h-[16px]!" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="formData.username"
|
v-model="formData.username"
|
||||||
placeholder="请输入账号"
|
placeholder="请输入账号"
|
||||||
class="flex-1 h-[48px] text-white placeholder:text-[#666666] focus:outline-none focus:border-[#29F1FA] bg-transparent" />
|
class="flex-1 md:h-[48px] h-[6rem] text-white placeholder:text-[#666666] focus:outline-none focus:border-[#29F1FA] bg-transparent text-[3rem] md:text-[100%]" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-bg w-full h-[48px] flex items-center px-[18px] mt-[20px]">
|
<div class="input-bg w-full md:h-[48px] h-[6rem] flex items-center px-[18px] md:mt-[20px] mt-[5rem]">
|
||||||
<SvgIcon :name="showPassword ? 'eye' : 'password'" class="text-[#00D5FF] mr-[13px] cursor-pointer" @click="togglePasswordVisibility" />
|
<SvgIcon :name="showPassword ? 'eye' : 'password'" class="text-[#00D5FF] mr-[13px] cursor-pointer w-[3.5rem]! h-[3.5rem]! md:w-[16px]! md:h-[16px]!" @click="togglePasswordVisibility" />
|
||||||
<input
|
<input
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
v-model="formData.password"
|
v-model="formData.password"
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
class="flex-1 h-[48px] text-white placeholder:text-[#666666] focus:outline-none focus:border-[#29F1FA] bg-transparent" />
|
class="flex-1 md:h-[48px] h-[6rem] text-white placeholder:text-[#666666] focus:outline-none focus:border-[#29F1FA] bg-transparent text-[3rem] md:text-[100%]" />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="w-full h-[48px] bg-[#2A8EFE] text-[#fff] text-[18px] font-700 border-submit mt-[48px]">登录</button>
|
<button type="submit" class="w-full md:h-[48px] h-[6rem] bg-[#2A8EFE] text-[#fff] md:text-[18px] text-[3rem] font-700 border-submit md:mt-[48px] mt-[6rem]">登录</button>
|
||||||
<p class="text-[#657295] mt-[10px] text-center text-[14px]">如忘记账号密码,请联系管理员:18845000222</p>
|
<p class="text-[#657295] mt-[10px] text-center md:text-[14px] text-[2.5rem]">如忘记账号密码,请联系管理员:18845000222</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
import { postRequest } from "@/api/customFetch";
|
import { postRequest } from "@/api/customFetch";
|
||||||
import { useUserStore } from "@/store/user";
|
import { useUserStore } from "@/store/user";
|
||||||
import DigitalWatch from "@/components/watch/DigitalWatch.vue";
|
import DigitalWatch from "@/components/watch/DigitalWatch.vue";
|
||||||
|
import { usePlatformType } from "@/utils/device";
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -87,9 +88,14 @@
|
||||||
const { accessToken, refreshToken } = resp.result as { refreshToken: string; accessToken: string };
|
const { accessToken, refreshToken } = resp.result as { refreshToken: string; accessToken: string };
|
||||||
userStore.setAccessToken(accessToken);
|
userStore.setAccessToken(accessToken);
|
||||||
userStore.setRefreshToken(refreshToken);
|
userStore.setRefreshToken(refreshToken);
|
||||||
|
const {isMobile} = usePlatformType()
|
||||||
// 跳转到首页
|
// 跳转到首页
|
||||||
|
if(isMobile){
|
||||||
|
router.push('/app')
|
||||||
|
}else{
|
||||||
router.push("/");
|
router.push("/");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 登录失败提示
|
// 登录失败提示
|
||||||
alert(resp.message || "登录失败");
|
alert(resp.message || "登录失败");
|
||||||
|
|
|
||||||