<template>
|
<div class="dashboard-page">
|
<section class="panel overview-panel">
|
<div class="section-head">
|
<div class="section-title">数据总览</div>
|
<el-select v-model="overviewRange" class="range-select" placeholder="选择时间">
|
<el-option
|
v-for="item in rangeOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
</div>
|
|
<div class="summary-grid">
|
<article
|
v-for="item in summaryCards"
|
:key="item.title"
|
class="summary-card"
|
:class="item.theme"
|
>
|
<div class="summary-content">
|
<div class="summary-value">{{ item.value }}</div>
|
<div class="summary-title">{{ item.title }}</div>
|
</div>
|
<div class="summary-illustration">
|
<div class="glow"></div>
|
<div class="badge">{{ item.tag }}</div>
|
</div>
|
</article>
|
</div>
|
</section>
|
|
<div class="content-grid">
|
<section class="panel chart-panel">
|
<div class="section-head">
|
<div class="section-title">预约数据</div>
|
<div class="filters">
|
<el-select v-model="deptFilter" class="filter-select" placeholder="全部">
|
<el-option
|
v-for="item in deptOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
<el-select v-model="chartRange" class="range-select" placeholder="选择时间">
|
<el-option
|
v-for="item in rangeOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
</div>
|
</div>
|
|
<div class="gauge-board">
|
<div v-for="item in gaugeCards" :key="item.label" class="gauge-item">
|
<div class="gauge-ring" :style="{ '--ring-color': item.color }">
|
<div class="gauge-inner">
|
<div class="gauge-value">{{ item.value }}</div>
|
<div class="gauge-label">{{ item.label }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="line-chart">
|
<div class="line-grid">
|
<span v-for="line in 5" :key="line" class="grid-line"></span>
|
</div>
|
<div class="y-axis">
|
<span v-for="label in yAxisLabels" :key="label">{{ label }}</span>
|
</div>
|
<svg viewBox="0 0 760 280" preserveAspectRatio="none" class="chart-svg">
|
<polyline
|
v-for="line in chartLines"
|
:key="line.name"
|
:points="line.points"
|
fill="none"
|
:stroke="line.color"
|
stroke-width="3"
|
stroke-linecap="round"
|
stroke-linejoin="round"
|
/>
|
</svg>
|
<div class="x-axis">
|
<span v-for="label in xAxisLabels" :key="label">{{ label }}</span>
|
</div>
|
<div class="legend">
|
<span v-for="line in chartLines" :key="line.name" class="legend-item">
|
<i :style="{ backgroundColor: line.color }"></i>
|
{{ line.name }}
|
</span>
|
</div>
|
</div>
|
</section>
|
|
<section class="panel quick-panel">
|
<div class="section-head">
|
<div class="section-title">便捷入口</div>
|
</div>
|
<div class="quick-grid">
|
<button
|
v-for="entry in quickEntries"
|
:key="entry.title"
|
type="button"
|
class="quick-item"
|
@click="go(entry.path)"
|
>
|
<div class="quick-icon" :class="entry.theme">
|
<el-icon><component :is="entry.icon" /></el-icon>
|
</div>
|
<span>{{ entry.title }}</span>
|
</button>
|
</div>
|
</section>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref } from "vue";
|
import { useRouter } from "vue-router";
|
import {
|
DocumentCopy,
|
FolderOpened,
|
LocationInformation,
|
OfficeBuilding,
|
Operation,
|
Setting,
|
Tickets,
|
User,
|
} from "@element-plus/icons-vue";
|
|
const router = useRouter();
|
|
const rangeOptions = [
|
{ label: "2024.09.13 - 2024.09.19", value: "2024.09.13 - 2024.09.19" },
|
{ label: "2024.09.06 - 2024.09.12", value: "2024.09.06 - 2024.09.12" },
|
];
|
|
const deptOptions = [
|
{ label: "全部", value: "all" },
|
{ label: "综合管理部", value: "综合管理部" },
|
{ label: "信息技术部", value: "信息技术部" },
|
];
|
|
const overviewRange = ref(rangeOptions[0].value);
|
const chartRange = ref(rangeOptions[0].value);
|
const deptFilter = ref(deptOptions[0].value);
|
|
const summaryCards = [
|
{ title: "单日预约数", value: "3", tag: "日", theme: "orange" },
|
{ title: "累计预约数", value: "3,154", tag: "总", theme: "blue" },
|
{ title: "注册用户", value: "43", tag: "户", theme: "cyan" },
|
{ title: "员工数", value: "433", tag: "人", theme: "green" },
|
];
|
|
const gaugeCards = [
|
{ label: "预约数", value: "656", color: "#45c36c" },
|
{ label: "审批中", value: "656", color: "#5878ff" },
|
{ label: "审核未通过", value: "6", color: "#ff5f42" },
|
{ label: "访客取消", value: "6", color: "#ffbb3d" },
|
];
|
|
const yAxisLabels = ["250", "200", "150", "100", "50", "0"];
|
const xAxisLabels = ["2-12", "2-13", "2-14", "2-15", "2-16", "2-17", "2-18", "2-19", "2-20", "2-21", "2-22", "2-23"];
|
|
const chartLines = [
|
{
|
name: "预约数",
|
color: "#6a84ff",
|
points: "20,132 82,116 144,68 206,104 268,86 330,46 392,102 454,82 516,68 578,118 640,60 720,88",
|
},
|
{
|
name: "审批中",
|
color: "#7acb79",
|
points: "20,156 82,124 144,40 206,132 268,92 330,104 392,62 454,96 516,100 578,150 640,112 720,168",
|
},
|
{
|
name: "审核未通过",
|
color: "#ff6a4d",
|
points: "20,220 82,176 144,238 206,188 268,210 330,170 392,238 454,226 516,234 578,182 640,160 720,202",
|
},
|
{
|
name: "访客取消",
|
color: "#ffc24b",
|
points: "20,144 82,170 144,166 206,126 268,118 330,202 392,132 454,136 516,138 578,192 640,134 720,124",
|
},
|
];
|
|
const quickEntries = [
|
{ title: "部门管理", path: "/staff/depts", icon: OfficeBuilding, theme: "indigo" },
|
{ title: "账号管理", path: "/users", icon: User, theme: "purple" },
|
{ title: "人员分组", path: "/staff/groups", icon: FolderOpened, theme: "blue" },
|
{ title: "预约记录", path: "/appointments", icon: Tickets, theme: "green" },
|
{ title: "区域管理", path: "/region/pass-areas", icon: LocationInformation, theme: "orange" },
|
{ title: "用户管理", path: "/staff/employees", icon: DocumentCopy, theme: "sky" },
|
{ title: "审核配置", path: "/system/approval-settings", icon: Operation, theme: "red" },
|
{ title: "预约配置", path: "/system/booking-settings", icon: Setting, theme: "azure" },
|
];
|
|
function go(path: string) {
|
router.push(path);
|
}
|
</script>
|
|
<style scoped>
|
.dashboard-page {
|
min-height: 100%;
|
padding: 18px;
|
background:
|
linear-gradient(180deg, #f7faff 0%, #f2f6ff 100%);
|
}
|
|
.panel {
|
background: #ffffff;
|
border: 1px solid #e5ecfb;
|
border-radius: 16px;
|
box-shadow: 0 10px 30px rgba(47, 99, 212, 0.06);
|
}
|
|
.overview-panel,
|
.chart-panel,
|
.quick-panel {
|
padding: 16px;
|
}
|
|
.section-head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 12px;
|
margin-bottom: 14px;
|
}
|
|
.section-title {
|
position: relative;
|
padding-left: 12px;
|
color: #2e4d92;
|
font-size: 18px;
|
font-weight: 700;
|
}
|
|
.section-title::before {
|
content: "";
|
position: absolute;
|
left: 0;
|
top: 3px;
|
bottom: 3px;
|
width: 4px;
|
border-radius: 999px;
|
background: linear-gradient(180deg, #4b82ff, #67d2ff);
|
}
|
|
.range-select,
|
.filter-select {
|
width: 220px;
|
}
|
|
.filters {
|
display: flex;
|
gap: 10px;
|
}
|
|
.summary-grid {
|
display: grid;
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
gap: 16px;
|
}
|
|
.summary-card {
|
position: relative;
|
overflow: hidden;
|
min-height: 92px;
|
padding: 18px 18px 16px;
|
border-radius: 16px;
|
color: #fff;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.summary-card::before {
|
content: "";
|
position: absolute;
|
inset: 0;
|
background:
|
linear-gradient(135deg, rgba(255, 255, 255, 0.16), transparent 42%),
|
linear-gradient(225deg, rgba(255, 255, 255, 0.14), transparent 46%);
|
}
|
|
.summary-card.orange {
|
background: linear-gradient(135deg, #ff9f32, #ffbe21);
|
}
|
|
.summary-card.blue {
|
background: linear-gradient(135deg, #4a67ff, #47a8ff);
|
}
|
|
.summary-card.cyan {
|
background: linear-gradient(135deg, #35a8f4, #4fd3d7);
|
}
|
|
.summary-card.green {
|
background: linear-gradient(135deg, #3fd6a4, #71dc68);
|
}
|
|
.summary-content,
|
.summary-illustration {
|
position: relative;
|
z-index: 1;
|
}
|
|
.summary-value {
|
font-size: 24px;
|
font-weight: 700;
|
line-height: 1;
|
margin-bottom: 10px;
|
}
|
|
.summary-title {
|
font-size: 14px;
|
opacity: 0.96;
|
}
|
|
.summary-illustration {
|
width: 68px;
|
height: 56px;
|
}
|
|
.glow {
|
position: absolute;
|
right: -8px;
|
top: -4px;
|
width: 56px;
|
height: 56px;
|
border-radius: 18px;
|
background: rgba(255, 255, 255, 0.2);
|
filter: blur(1px);
|
}
|
|
.badge {
|
position: absolute;
|
right: 0;
|
bottom: 2px;
|
width: 42px;
|
height: 42px;
|
border-radius: 14px;
|
background: rgba(255, 255, 255, 0.24);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 18px;
|
font-weight: 700;
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
|
}
|
|
.content-grid {
|
display: grid;
|
grid-template-columns: minmax(0, 1.8fr) minmax(260px, 1fr);
|
gap: 16px;
|
margin-top: 16px;
|
}
|
|
.gauge-board {
|
display: grid;
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
gap: 12px;
|
padding: 18px 10px;
|
border: 1px solid #e8eefc;
|
border-radius: 14px;
|
background: linear-gradient(180deg, #ffffff, #f8fbff);
|
}
|
|
.gauge-item {
|
display: flex;
|
justify-content: center;
|
}
|
|
.gauge-ring {
|
width: 86px;
|
height: 86px;
|
padding: 6px;
|
border-radius: 50%;
|
background:
|
conic-gradient(var(--ring-color) 0 280deg, #edf2ff 280deg 360deg);
|
}
|
|
.gauge-inner {
|
width: 100%;
|
height: 100%;
|
border-radius: 50%;
|
background: #fff;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
color: #4a5f89;
|
}
|
|
.gauge-value {
|
font-size: 21px;
|
font-weight: 700;
|
line-height: 1;
|
}
|
|
.gauge-label {
|
margin-top: 4px;
|
font-size: 12px;
|
}
|
|
.line-chart {
|
position: relative;
|
height: 330px;
|
margin-top: 16px;
|
padding: 18px 16px 32px 42px;
|
border-top: 1px solid #edf2fd;
|
}
|
|
.line-grid {
|
position: absolute;
|
inset: 18px 16px 32px 42px;
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
}
|
|
.grid-line {
|
border-top: 1px dashed #dbe5fb;
|
}
|
|
.y-axis {
|
position: absolute;
|
left: 0;
|
top: 12px;
|
bottom: 32px;
|
width: 34px;
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
color: #8d9aba;
|
font-size: 12px;
|
text-align: right;
|
}
|
|
.chart-svg {
|
position: absolute;
|
inset: 18px 16px 32px 42px;
|
width: calc(100% - 58px);
|
height: calc(100% - 50px);
|
}
|
|
.x-axis {
|
position: absolute;
|
left: 42px;
|
right: 16px;
|
bottom: 8px;
|
display: grid;
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
color: #8d9aba;
|
font-size: 12px;
|
}
|
|
.legend {
|
position: absolute;
|
top: 18px;
|
right: 18px;
|
display: flex;
|
flex-wrap: wrap;
|
justify-content: flex-end;
|
gap: 10px 14px;
|
}
|
|
.legend-item {
|
display: inline-flex;
|
align-items: center;
|
gap: 6px;
|
color: #6d7ea8;
|
font-size: 12px;
|
}
|
|
.legend-item i {
|
width: 10px;
|
height: 10px;
|
border-radius: 50%;
|
}
|
|
.quick-grid {
|
display: grid;
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
gap: 22px 12px;
|
padding: 10px 6px 8px;
|
}
|
|
.quick-item {
|
border: 0;
|
background: transparent;
|
cursor: pointer;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 10px;
|
color: #445882;
|
font-size: 14px;
|
transition: transform 0.18s ease, color 0.18s ease;
|
}
|
|
.quick-item:hover {
|
color: #2f63d4;
|
transform: translateY(-2px);
|
}
|
|
.quick-icon {
|
width: 56px;
|
height: 56px;
|
border-radius: 18px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
color: #fff;
|
font-size: 28px;
|
box-shadow: 0 12px 22px rgba(79, 121, 216, 0.18);
|
}
|
|
.quick-icon.indigo {
|
background: linear-gradient(135deg, #5d79ff, #6d9fff);
|
}
|
|
.quick-icon.purple {
|
background: linear-gradient(135deg, #7e63ff, #ab5ef7);
|
}
|
|
.quick-icon.blue {
|
background: linear-gradient(135deg, #30a5ff, #4e84ff);
|
}
|
|
.quick-icon.green {
|
background: linear-gradient(135deg, #57ca4a, #88d54e);
|
}
|
|
.quick-icon.orange {
|
background: linear-gradient(135deg, #ff7a33, #ff5835);
|
}
|
|
.quick-icon.sky {
|
background: linear-gradient(135deg, #5f83ff, #679cff);
|
}
|
|
.quick-icon.red {
|
background: linear-gradient(135deg, #ff6b45, #f2452d);
|
}
|
|
.quick-icon.azure {
|
background: linear-gradient(135deg, #3aa0ff, #3b82f6);
|
}
|
|
@media (max-width: 1280px) {
|
.summary-grid {
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
}
|
|
.content-grid {
|
grid-template-columns: 1fr;
|
}
|
}
|
|
@media (max-width: 768px) {
|
.dashboard-page {
|
padding: 12px;
|
}
|
|
.section-head {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
|
.filters {
|
flex-direction: column;
|
}
|
|
.range-select,
|
.filter-select {
|
width: 100%;
|
}
|
|
.summary-grid,
|
.gauge-board,
|
.quick-grid {
|
grid-template-columns: 1fr;
|
}
|
|
.line-chart {
|
height: 280px;
|
padding-left: 36px;
|
}
|
|
.line-grid,
|
.chart-svg,
|
.x-axis {
|
left: 36px;
|
}
|
|
.legend {
|
position: static;
|
margin-bottom: 8px;
|
justify-content: flex-start;
|
}
|
}
|
</style>
|