feat: 更新项目首页
This commit is contained in:
parent
b573a80574
commit
70d2fa91c6
39
src/components/ReFlicker/index.css
Normal file
39
src/components/ReFlicker/index.css
Normal file
@ -0,0 +1,39 @@
|
||||
.point {
|
||||
width: var(--point-width);
|
||||
height: var(--point-height);
|
||||
background: var(--point-background);
|
||||
position: relative;
|
||||
border-radius: var(--point-border-radius);
|
||||
}
|
||||
|
||||
.point-flicker:after {
|
||||
background: var(--point-background);
|
||||
}
|
||||
|
||||
.point-flicker:before,
|
||||
.point-flicker:after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
border-radius: var(--point-border-radius);
|
||||
animation: flicker 1.2s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
30% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(var(--point-scale));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
44
src/components/ReFlicker/index.ts
Normal file
44
src/components/ReFlicker/index.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import "./index.css";
|
||||
import { type Component, h, defineComponent } from "vue";
|
||||
|
||||
export interface attrsType {
|
||||
width?: string;
|
||||
height?: string;
|
||||
borderRadius?: number | string;
|
||||
background?: string;
|
||||
scale?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆点、方形闪烁动画组件
|
||||
* @param width 可选 string 宽
|
||||
* @param height 可选 string 高
|
||||
* @param borderRadius 可选 number | string 传0为方形、传50%或者不传为圆形
|
||||
* @param background 可选 string 闪烁颜色
|
||||
* @param scale 可选 number | string 闪烁范围,默认2,值越大闪烁范围越大
|
||||
* @returns Component
|
||||
*/
|
||||
export function useRenderFlicker(attrs?: attrsType): Component {
|
||||
return defineComponent({
|
||||
name: "ReFlicker",
|
||||
render() {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "point point-flicker",
|
||||
style: {
|
||||
"--point-width": attrs?.width ?? "12px",
|
||||
"--point-height": attrs?.height ?? "12px",
|
||||
"--point-background":
|
||||
attrs?.background ?? "var(--el-color-primary)",
|
||||
"--point-border-radius": attrs?.borderRadius ?? "50%",
|
||||
"--point-scale": attrs?.scale ?? "2"
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => []
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
14
src/main.ts
14
src/main.ts
@ -4,13 +4,13 @@ import { setupStore } from "@/store";
|
||||
import { useI18n } from "@/plugins/i18n";
|
||||
import { getPlatformConfig } from "./config";
|
||||
import { MotionPlugin } from "@vueuse/motion";
|
||||
// import { useEcharts } from "@/plugins/echarts";
|
||||
import { useEcharts } from "@/plugins/echarts";
|
||||
import { createApp, type Directive } from "vue";
|
||||
import { useElementPlus } from "@/plugins/elementPlus";
|
||||
import { injectResponsiveStorage } from "@/utils/responsive";
|
||||
|
||||
import Table from "@pureadmin/table";
|
||||
// import PureDescriptions from "@pureadmin/descriptions";
|
||||
import PureDescriptions from "@pureadmin/descriptions";
|
||||
|
||||
// 引入重置样式
|
||||
import "./style/reset.scss";
|
||||
@ -56,8 +56,12 @@ getPlatformConfig(app).then(async config => {
|
||||
app.use(router);
|
||||
await router.isReady();
|
||||
injectResponsiveStorage(app, config);
|
||||
app.use(MotionPlugin).use(useI18n).use(useElementPlus).use(Table);
|
||||
// .use(PureDescriptions)
|
||||
// .use(useEcharts);
|
||||
app
|
||||
.use(MotionPlugin)
|
||||
.use(useI18n)
|
||||
.use(useElementPlus)
|
||||
.use(Table)
|
||||
.use(PureDescriptions)
|
||||
.use(useEcharts);
|
||||
app.mount("#app");
|
||||
});
|
||||
|
@ -10,6 +10,8 @@ import { useNav } from "@/layout/hooks/useNav";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import type { FormInstance } from "element-plus";
|
||||
import { $t, transformI18n } from "@/plugins/i18n";
|
||||
import { getConfig } from "@/config";
|
||||
|
||||
// import { operates } from "./utils/enums";
|
||||
import { useLayout } from "@/layout/hooks/useLayout";
|
||||
import LoginPhone from "./components/LoginPhone.vue";
|
||||
@ -34,6 +36,7 @@ import Info from "@iconify-icons/ri/information-line";
|
||||
import { GetCaptchaAPI } from "@/api/login";
|
||||
import { getUserInfoAPI } from "@/api/login";
|
||||
import { setUserInfo } from "@/utils/auth";
|
||||
const Period = getConfig("Period");
|
||||
|
||||
defineOptions({
|
||||
name: "Login"
|
||||
@ -347,12 +350,8 @@ onMounted(async () => {
|
||||
<div
|
||||
class="w-full flex-c absolute bottom-3 text-sm text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]"
|
||||
>
|
||||
Copyright © 2020-present
|
||||
<a
|
||||
class="hover:text-primary"
|
||||
href="https://github.com/pure-admin"
|
||||
target="_blank"
|
||||
>
|
||||
Copyright © {{ Period }}
|
||||
<a class="hover:text-primary" href="#" target="_blank">
|
||||
{{ title }}
|
||||
</a>
|
||||
</div>
|
||||
|
108
src/views/welcome/components/ChartBar.vue
Normal file
108
src/views/welcome/components/ChartBar.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<script setup lang="ts">
|
||||
import { useDark, useECharts } from "@pureadmin/utils";
|
||||
import { type PropType, ref, computed, watch, nextTick } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
requireData: {
|
||||
type: Array as PropType<Array<number>>,
|
||||
default: () => []
|
||||
},
|
||||
questionData: {
|
||||
type: Array as PropType<Array<number>>,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const { isDark } = useDark();
|
||||
|
||||
const theme = computed(() => (isDark.value ? "dark" : "light"));
|
||||
|
||||
const chartRef = ref();
|
||||
const { setOptions } = useECharts(chartRef, {
|
||||
theme
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
async () => {
|
||||
await nextTick(); // 确保DOM更新完成后再执行
|
||||
setOptions({
|
||||
container: ".bar-card",
|
||||
color: ["#41b6ff", "#e85f33"],
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "none"
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: "20px",
|
||||
left: "50px",
|
||||
right: 0
|
||||
},
|
||||
legend: {
|
||||
data: ["查询数量", "匹配数量"],
|
||||
textStyle: {
|
||||
color: "#606266",
|
||||
fontSize: "0.875rem"
|
||||
},
|
||||
bottom: 0
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
|
||||
axisLabel: {
|
||||
fontSize: "0.875rem"
|
||||
},
|
||||
axisPointer: {
|
||||
type: "shadow"
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
axisLabel: {
|
||||
fontSize: "0.875rem"
|
||||
},
|
||||
splitLine: {
|
||||
show: false // 去网格线
|
||||
}
|
||||
// name: "单位: 个"
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "查询数量",
|
||||
type: "bar",
|
||||
barWidth: 45,
|
||||
itemStyle: {
|
||||
color: "#41b6ff",
|
||||
borderRadius: [10, 10, 0, 0]
|
||||
},
|
||||
data: props.requireData
|
||||
},
|
||||
{
|
||||
name: "匹配数量",
|
||||
type: "bar",
|
||||
barWidth: 45,
|
||||
itemStyle: {
|
||||
color: "#e86033ce",
|
||||
borderRadius: [10, 10, 0, 0]
|
||||
},
|
||||
data: props.questionData
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="chartRef" style="width: 100%; height: 550px" />
|
||||
</template>
|
@ -2,8 +2,346 @@
|
||||
defineOptions({
|
||||
name: "Welcome"
|
||||
});
|
||||
import { ref, markRaw } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import ChartBar from "./components/ChartBar.vue";
|
||||
import { useRenderFlicker } from "@/components/ReFlicker";
|
||||
import Segmented, { type OptionsType } from "@/components/ReSegmented";
|
||||
import { useDark, cloneDeep, randomGradient } from "@pureadmin/utils";
|
||||
|
||||
import GroupLine from "@iconify-icons/ri/group-line";
|
||||
import Question from "@iconify-icons/ri/question-answer-line";
|
||||
import CheckLine from "@iconify-icons/ri/chat-check-line";
|
||||
import Smile from "@iconify-icons/ri/star-smile-line";
|
||||
const { isDark } = useDark();
|
||||
const getRandomIntBetween = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
let curWeek = ref(1); // 0上周、1本周
|
||||
const optionsBasis: Array<OptionsType> = [
|
||||
{
|
||||
label: "上周"
|
||||
},
|
||||
{
|
||||
label: "本周"
|
||||
}
|
||||
];
|
||||
const days = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
||||
|
||||
/** 需求人数、提问数量、解决数量、用户满意度 */
|
||||
const chartData = [
|
||||
{
|
||||
icon: GroupLine,
|
||||
bgColor: "#effaff",
|
||||
color: "#41b6ff",
|
||||
duration: 2200,
|
||||
name: "需求人数",
|
||||
value: 36000,
|
||||
percent: "+88%",
|
||||
data: [2101, 5288, 4239, 4962, 6752, 5208, 7450] // 平滑折线图数据
|
||||
},
|
||||
{
|
||||
icon: Question,
|
||||
bgColor: "#fff5f4",
|
||||
color: "#e85f33",
|
||||
duration: 1600,
|
||||
name: "提问数量",
|
||||
value: 16580,
|
||||
percent: "+70%",
|
||||
data: [2216, 1148, 1255, 788, 4821, 1973, 4379]
|
||||
},
|
||||
{
|
||||
icon: CheckLine,
|
||||
bgColor: "#eff8f4",
|
||||
color: "#26ce83",
|
||||
duration: 1500,
|
||||
name: "解决数量",
|
||||
value: 16499,
|
||||
percent: "+99%",
|
||||
data: [861, 1002, 3195, 1715, 3666, 2415, 3645]
|
||||
},
|
||||
{
|
||||
icon: Smile,
|
||||
bgColor: "#f6f4fe",
|
||||
color: "#7846e5",
|
||||
duration: 100,
|
||||
name: "用户满意度",
|
||||
value: 100,
|
||||
percent: "+100%",
|
||||
data: [100]
|
||||
}
|
||||
];
|
||||
|
||||
/** 分析概览 */
|
||||
const barChartData = [
|
||||
{
|
||||
requireData: [2101, 5288, 4239, 4962, 6752, 5208, 7450],
|
||||
questionData: [2216, 1148, 1255, 1788, 4821, 1973, 4379]
|
||||
},
|
||||
{
|
||||
requireData: [2101, 3280, 4400, 4962, 5752, 6889, 7600],
|
||||
questionData: [2116, 3148, 3255, 3788, 4821, 4970, 5390]
|
||||
}
|
||||
];
|
||||
/** 数据统计 */
|
||||
const tableData = Array.from({ length: 30 }).map((_, index) => {
|
||||
return {
|
||||
id: index + 1,
|
||||
requiredNumber: getRandomIntBetween(13500, 19999),
|
||||
questionNumber: getRandomIntBetween(12600, 16999),
|
||||
resolveNumber: getRandomIntBetween(13500, 17999),
|
||||
satisfaction: getRandomIntBetween(95, 100),
|
||||
date: dayjs().subtract(index, "day").format("YYYY-MM-DD")
|
||||
};
|
||||
});
|
||||
/** 最新动态 */
|
||||
const latestNewsData = cloneDeep(tableData)
|
||||
.slice(0, 14)
|
||||
.map((item, index) => {
|
||||
return Object.assign(item, {
|
||||
date: `${dayjs().subtract(index, "day").format("YYYY-MM-DD")} ${
|
||||
days[dayjs().subtract(index, "day").day()]
|
||||
}`
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>首页</h1>
|
||||
<div>
|
||||
<el-row :gutter="24" justify="space-around">
|
||||
<re-col
|
||||
v-motion
|
||||
class="mb-[18px]"
|
||||
:value="8"
|
||||
:md="12"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 100
|
||||
}"
|
||||
:enter="{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: 80
|
||||
}
|
||||
}"
|
||||
>
|
||||
<el-card class="line-card" shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-md font-medium"> 今日查询 </span>
|
||||
<div
|
||||
class="w-8 h-8 flex justify-center items-center rounded-md"
|
||||
:style="{
|
||||
backgroundColor: isDark ? 'transparent' : '#effaff'
|
||||
}"
|
||||
>
|
||||
<IconifyIconOffline
|
||||
:icon="GroupLine"
|
||||
color="#41b6ff"
|
||||
width="18"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center items-start mt-3">
|
||||
<div class="w-1/2 text-center">
|
||||
<h2 class="font-bold text-blue-500">688780</h2>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-motion
|
||||
class="mb-[18px]"
|
||||
:value="8"
|
||||
:md="12"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 100
|
||||
}"
|
||||
:enter="{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: 80
|
||||
}
|
||||
}"
|
||||
>
|
||||
<el-card class="line-card" shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-md font-medium"> 当月查询 </span>
|
||||
<div
|
||||
class="w-8 h-8 flex justify-center items-center rounded-md"
|
||||
:style="{
|
||||
backgroundColor: isDark ? 'transparent' : '#fff5f4'
|
||||
}"
|
||||
>
|
||||
<IconifyIconOffline :icon="Question" color="#e85f33" width="18" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center items-start mt-3">
|
||||
<div class="w-1/2 text-center">
|
||||
<h2 class="font-bold text-blue-500">79999897098</h2>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-motion
|
||||
class="mb-[18px]"
|
||||
:value="8"
|
||||
:md="12"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 100
|
||||
}"
|
||||
:enter="{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: 80
|
||||
}
|
||||
}"
|
||||
>
|
||||
<el-card class="line-card" shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-md font-medium"> 上月查询 </span>
|
||||
<div
|
||||
class="w-8 h-8 flex justify-center items-center rounded-md"
|
||||
:style="{
|
||||
backgroundColor: isDark ? 'transparent' : '#eff8f4'
|
||||
}"
|
||||
>
|
||||
<IconifyIconOffline
|
||||
:icon="CheckLine"
|
||||
color="#26ce83"
|
||||
width="18"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center items-start mt-3">
|
||||
<div class="w-1/2 text-center">
|
||||
<h2 class="font-bold text-blue-500">7898067690</h2>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-motion
|
||||
class="mb-[18px]"
|
||||
:value="16"
|
||||
:xs="24"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 100
|
||||
}"
|
||||
:enter="{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: 400
|
||||
}
|
||||
}"
|
||||
>
|
||||
<el-card class="bar-card" shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-md font-medium">数据概览</span>
|
||||
<Segmented v-model="curWeek" :options="optionsBasis" />
|
||||
</div>
|
||||
<div class="flex justify-between items-start mt-3">
|
||||
<ChartBar
|
||||
:requireData="barChartData[curWeek].requireData"
|
||||
:questionData="barChartData[curWeek].questionData"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-motion
|
||||
class="mb-[18px]"
|
||||
:value="8"
|
||||
:xs="24"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 100
|
||||
}"
|
||||
:enter="{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: 640
|
||||
}
|
||||
}"
|
||||
>
|
||||
<el-card shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-md font-medium">查询动态</span>
|
||||
</div>
|
||||
<el-scrollbar max-height="558" class="mt-3">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in latestNewsData"
|
||||
:key="index"
|
||||
center
|
||||
placement="top"
|
||||
:icon="
|
||||
markRaw(
|
||||
useRenderFlicker({
|
||||
background: randomGradient({
|
||||
randomizeHue: true
|
||||
})
|
||||
})
|
||||
)
|
||||
"
|
||||
:timestamp="item.date"
|
||||
>
|
||||
<p class="text-text_color_regular text-sm">
|
||||
{{
|
||||
`查询 ${item.requiredNumber} 条数据,${item.resolveNumber} 条已匹配`
|
||||
}}
|
||||
</p>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-card) {
|
||||
--el-card-border-color: none;
|
||||
|
||||
/* 解决概率进度条宽度 */
|
||||
.el-progress--line {
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
/* 解决概率进度条字体大小 */
|
||||
.el-progress-bar__innerText {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* 隐藏 el-scrollbar 滚动条 */
|
||||
.el-scrollbar__bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* el-timeline 每一项上下、左右边距 */
|
||||
.el-timeline-item {
|
||||
margin: 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 20px 20px 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user