From 523365960513f297024a419f94b2b42eccd9456f Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 09 四月 2026 11:21:41 +0800
Subject: [PATCH] #
---
rsf-design/src/views/dashboard/console/index.vue | 279 +++++++++++++++++++++----------------------------------
1 files changed, 108 insertions(+), 171 deletions(-)
diff --git a/rsf-design/src/views/dashboard/console/index.vue b/rsf-design/src/views/dashboard/console/index.vue
index 1a2f229..9bfbcc2 100644
--- a/rsf-design/src/views/dashboard/console/index.vue
+++ b/rsf-design/src/views/dashboard/console/index.vue
@@ -1,143 +1,114 @@
<template>
- <div class="art-full-height flex flex-col gap-6">
- <section class="grid gap-6 md:grid-cols-2 xl:grid-cols-4" v-loading="sectionLoading.summary">
+ <div class="art-full-height flex flex-col gap-6 lg:min-h-0 lg:overflow-hidden lg:gap-2">
+ <section class="grid gap-3 md:grid-cols-2 lg:grid-cols-4 lg:shrink-0" v-loading="sectionLoading.summary">
<div
v-for="item in summaryCardItems"
:key="item.title"
- class="art-card flex items-start justify-between rounded-3xl px-7 py-6"
+ class="art-card flex items-start justify-between rounded-3xl px-4 py-4 lg:px-4 lg:py-3 xl:px-5"
>
- <div class="min-w-0 pr-6">
- <p class="text-sm font-medium text-g-700">{{ item.title }}</p>
- <ArtCountTo class="mt-3 block text-[2.3rem] font-semibold leading-none text-g-900" :target="item.count" :duration="1400" />
- <div class="mt-4 flex items-center gap-2 text-sm">
- <span :class="item.metaTone">{{ item.metaLabel }}</span>
- <span class="text-g-500">{{ item.metaValue }}</span>
+ <div class="min-w-0 flex-1 pr-3">
+ <p class="truncate text-sm font-medium text-g-700 lg:text-[13px]">{{ item.title }}</p>
+ <ArtCountTo
+ class="mt-1 block truncate text-[1.95rem] font-semibold leading-none text-g-900 lg:text-[1.72rem] xl:text-[1.92rem]"
+ :target="item.count"
+ :duration="1400"
+ />
+ <div class="mt-1.5 flex items-center gap-1 text-xs lg:text-[12px] xl:text-sm">
+ <span :class="item.metaTone" class="shrink-0">{{ item.metaLabel }}</span>
+ <span class="truncate text-g-500">{{ item.metaValue }}</span>
</div>
</div>
- <div class="flex size-13 shrink-0 items-center justify-center rounded-2xl" :class="item.iconBoxClass">
- <ArtSvgIcon :icon="item.icon" class="text-2xl" :class="item.iconClass" />
+ <div class="flex size-10 shrink-0 items-center justify-center rounded-2xl lg:size-9 xl:size-11" :class="item.iconBoxClass">
+ <ArtSvgIcon :icon="item.icon" class="text-xl xl:text-2xl" :class="item.iconClass" />
</div>
</div>
</section>
- <section class="grid gap-6 xl:grid-cols-[1.35fr_1fr]">
- <div class="art-card h-115 overflow-hidden p-6 box-border">
- <div class="art-card-header">
- <div class="title">
- <h4>杩� 30 澶╁嚭鍏ュ簱瓒嬪娍</h4>
- <p>鐪熷疄閾捐矾 <span class="text-success">宸叉帴閫�</span></p>
+ <section class="flex min-h-0 flex-1 flex-col gap-2">
+ <div class="grid min-h-0 flex-1 gap-2 lg:grid-cols-[1.35fr_1fr]">
+ <div class="art-card box-border flex h-full min-h-0 flex-col overflow-hidden p-4 lg:p-5">
+ <div class="art-card-header">
+ <div class="title">
+ <h4>杩� 30 澶╁嚭鍏ュ簱瓒嬪娍</h4>
+ <p>鐪熷疄閾捐矾 <span class="text-success">宸叉帴閫�</span></p>
+ </div>
+ </div>
+ <div class="mt-3 min-h-0 flex-1">
+ <ArtBarChart
+ height="100%"
+ :loading="sectionLoading.trend"
+ :data="trendChartSeries"
+ :x-axis-data="trendChartXAxisData"
+ :show-axis-line="false"
+ :show-legend="true"
+ :show-split-line="true"
+ legend-position="top"
+ bar-width="38%"
+ />
</div>
</div>
- <div class="h-[calc(100%-4.5rem)]">
- <ArtBarChart
- height="22rem"
- :loading="sectionLoading.trend"
- :data="trendChartSeries"
- :x-axis-data="trendChartXAxisData"
- :show-axis-line="false"
- :show-legend="true"
- :show-split-line="true"
- legend-position="top"
- bar-width="38%"
- />
- </div>
- </div>
- <div class="art-card h-115 overflow-hidden p-6 box-border" v-loading="sectionLoading.locUsage">
- <div class="art-card-header">
- <div class="title">
- <h4>搴撲綅浣跨敤鍒嗗竷</h4>
- <p>{{ usageLegendCount }} 涓淮搴�</p>
+ <div class="art-card box-border flex h-full min-h-0 flex-col overflow-hidden p-4 lg:p-5" v-loading="sectionLoading.locUsage">
+ <div class="art-card-header">
+ <div class="title">
+ <h4>搴撲綅浣跨敤鍒嗗竷</h4>
+ <p>{{ usageLegendCount }} 涓淮搴�</p>
+ </div>
</div>
- </div>
- <div class="grid h-[calc(100%-4.5rem)] gap-6 lg:grid-cols-[1fr_0.95fr] lg:items-center">
- <ArtRingChart
- height="21rem"
- :data="locUsageList"
- center-text="搴撲綅鍗犳瘮"
- :show-legend="false"
- :show-label="false"
- />
-
- <div class="space-y-1">
- <div
- v-for="item in usageLegend"
- :key="item.name"
- class="flex items-center justify-between border-b border-[var(--art-border-color)] py-4 last:border-b-0"
- >
- <div class="flex items-center gap-3">
- <span class="size-2.5 rounded-full" :style="{ backgroundColor: item.color }"></span>
- <span class="text-sm text-[var(--art-gray-900)]">{{ item.name }}</span>
- </div>
- <span class="text-sm font-medium text-[var(--art-gray-700)]">{{ item.value }}%</span>
+ <div class="mt-3 grid min-h-0 flex-1 gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(220px,0.92fr)] lg:items-center">
+ <div class="min-h-0 h-full">
+ <ArtRingChart
+ height="100%"
+ :data="locUsageList"
+ center-text="搴撲綅鍗犳瘮"
+ :show-legend="false"
+ :show-label="false"
+ />
</div>
- <ElEmpty v-if="!locUsageList.length" description="鏆傛棤搴撲綅浣跨敤鏁版嵁" :image-size="88" />
- </div>
- </div>
- </div>
- </section>
-
- <section class="grid gap-6 xl:grid-cols-[0.95fr_1.05fr]">
- <div class="art-card h-98 p-5 box-border" v-loading="sectionLoading.tasks">
- <div class="art-card-header">
- <div class="title">
- <h4>鎵ц涓换鍔�</h4>
- <p>{{ taskSubtitle }}</p>
- </div>
- </div>
-
- <div class="mt-3 h-[calc(100%-3.75rem)] overflow-hidden">
- <ElScrollbar>
- <div
- v-for="item in taskCardItems"
- :key="`${item.time}-${item.title}`"
- class="flex gap-4 border-b border-g-300 py-4 last:border-b-0"
- >
- <div class="flex flex-col items-center pt-1">
- <span class="size-3 rounded-full bg-[var(--el-color-primary)]"></span>
- <span class="mt-2 min-h-10 w-px bg-[var(--art-border-color)]"></span>
- </div>
- <div class="min-w-0 flex-1">
- <p class="text-xs text-g-500">{{ item.time }}</p>
- <div class="mt-2 flex items-center gap-2">
- <p class="truncate text-base font-medium text-[var(--art-gray-900)]">
- {{ item.title }}
- </p>
- <ElTag size="small" effect="light" :type="item.tagType">{{ item.tagText }}</ElTag>
+ <div class="min-h-0 overflow-hidden">
+ <div
+ v-for="item in usageLegend"
+ :key="item.name"
+ class="flex items-center justify-between gap-3 border-b border-[var(--art-border-color)] py-3 last:border-b-0"
+ >
+ <div class="flex items-center gap-3">
+ <span class="size-2.5 rounded-full" :style="{ backgroundColor: item.color }"></span>
+ <span class="truncate text-sm text-[var(--art-gray-900)]">{{ item.name }}</span>
</div>
- <p class="mt-2 text-sm text-g-600">{{ item.subtitle }}</p>
+ <span class="text-sm font-medium text-[var(--art-gray-700)]">{{ item.value }}%</span>
</div>
+
+ <ElEmpty v-if="!locUsageList.length" description="鏆傛棤搴撲綅浣跨敤鏁版嵁" :image-size="88" />
</div>
- </ElScrollbar>
+ </div>
</div>
</div>
- <div class="art-card h-98 p-5 box-border" v-loading="sectionLoading.deadStock">
+ <div class="art-card box-border flex shrink-0 flex-col p-4 lg:p-3.5">
<div class="art-card-header">
<div class="title">
- <h4>搴撳瓨鏈�杩戝姩鎬�</h4>
- <p>{{ deadStockSubtitle }}</p>
+ <h4>蹇嵎璺宠浆</h4>
+ <p>蹇�熻繘鍏ヤ换鍔¢〉鍜屽簱瀛橀〉</p>
</div>
</div>
- <div class="mt-3 h-[calc(100%-3.75rem)] overflow-hidden">
- <ElScrollbar>
- <div
- v-for="item in stockCardItems"
- :key="`${item.title}-${item.time}`"
- class="flex items-center gap-4 border-b border-g-300 py-4 last:border-b-0"
- >
- <div class="size-12 rounded-2xl bg-[var(--el-color-primary-light-9)] flex-cc">
- <ArtSvgIcon :icon="item.icon" class="text-xl text-[var(--el-color-primary)]" />
- </div>
- <div class="min-w-0 flex-1">
- <p class="truncate text-base font-medium text-[var(--art-gray-900)]">{{ item.title }}</p>
- <p class="mt-1 text-sm text-g-500">{{ item.status }}</p>
- </div>
- <div class="max-w-40 text-right text-sm text-g-500">{{ item.time }}</div>
+ <div class="mt-2.5 grid gap-2 md:grid-cols-2">
+ <button
+ v-for="item in quickLinkCards"
+ :key="item.title"
+ type="button"
+ class="flex items-start justify-between rounded-3xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] px-4 py-3 text-left transition hover:border-[var(--el-color-primary-light-5)] hover:bg-[var(--el-color-primary-light-9)]"
+ @click="navigateTo(item.path)"
+ >
+ <div class="min-w-0 pr-4">
+ <p class="text-base font-medium text-[var(--art-gray-900)]">{{ item.title }}</p>
+ <p class="mt-0.5 text-sm text-g-500">{{ item.description }}</p>
</div>
- </ElScrollbar>
+ <div class="flex size-11 shrink-0 items-center justify-center rounded-2xl" :class="item.iconBoxClass">
+ <ArtSvgIcon :icon="item.icon" class="text-xl" :class="item.iconClass" />
+ </div>
+ </button>
</div>
</div>
</section>
@@ -145,44 +116,55 @@
</template>
<script setup>
+ import { useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store/modules/user'
import {
fetchDashboardHeader,
fetchDashboardTrend,
- fetchDashboardDeadStock,
- fetchDashboardLocUsage,
- fetchDashboardTasks
+ fetchDashboardLocUsage
} from '@/api/dashboard'
import {
EMPTY_SUMMARY,
- buildDashboardDeadStockQuery,
- buildDashboardTaskQuery,
normalizeDashboardSummary,
normalizeDashboardTrend,
- normalizeDashboardTaskList,
normalizeDashboardLocUsage,
- normalizeDashboardDeadStockList,
withDashboardRequestGuard
} from './consolePage.helpers'
defineOptions({ name: 'Console' })
+ const router = useRouter()
const summary = ref({ ...EMPTY_SUMMARY })
const trendModel = ref(normalizeDashboardTrend())
- const taskList = ref([])
- const deadStockList = ref([])
const locUsageList = ref([])
const sectionLoading = reactive({
summary: false,
trend: false,
- deadStock: false,
- locUsage: false,
- tasks: false
+ locUsage: false
})
const userStore = useUserStore()
const { getUserInfo } = storeToRefs(userStore)
+
+ const quickLinkCards = [
+ {
+ title: '浠诲姟椤�',
+ description: '鏌ョ湅鍜屽鐞嗕换鍔$鐞嗘暟鎹�',
+ path: '/manager/task',
+ icon: 'ri:task-line',
+ iconBoxClass: 'bg-[var(--el-color-primary-light-9)]',
+ iconClass: 'text-[var(--el-color-primary)]'
+ },
+ {
+ title: '搴撳瓨椤�',
+ description: '鏌ョ湅褰撳墠搴撳瓨涓庡簱瀛樻槑缁�',
+ path: '/manager/stock',
+ icon: 'ri:archive-stack-line',
+ iconBoxClass: 'bg-[rgba(20,222,186,0.14)]',
+ iconClass: 'text-[#14DEBA]'
+ }
+ ]
const currentUser = computed(() => getUserInfo.value || {})
const currentUserName = computed(() => {
@@ -291,8 +273,6 @@
})
const trendChartXAxisData = computed(() => trendDisplayModel.value.xAxisData)
const trendChartSeries = computed(() => trendDisplayModel.value.series)
- const taskSubtitle = computed(() => `鏈�杩� ${taskList.value.length} 鏉′换鍔″姩鎬乣)
- const deadStockSubtitle = computed(() => `鏈�杩� ${deadStockList.value.length} 鏉″簱瀛樿褰昤)
const usageLegendCount = computed(() => locUsageList.value.length)
const usageLegend = computed(() => {
const palette = ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#E8684A', '#6DC8EC']
@@ -301,16 +281,6 @@
color: palette[index % palette.length]
}))
})
- const taskCardItems = computed(() =>
- taskList.value.slice(0, 6).map((item) => ({
- title: item.title,
- time: item.time,
- subtitle: item.status,
- tagText: resolveTaskTagText(item.status),
- tagType: resolveTaskTagType(item.class)
- }))
- )
- const stockCardItems = computed(() => deadStockList.value.slice(0, 6))
onMounted(() => {
loadDashboard()
@@ -319,9 +289,7 @@
function loadDashboard() {
void loadSummarySection()
void loadTrendSection()
- void loadDeadStockSection()
void loadLocUsageSection()
- void loadTaskSection()
}
async function loadSummarySection() {
@@ -338,16 +306,6 @@
sectionLoading.trend = false
}
- async function loadDeadStockSection() {
- sectionLoading.deadStock = true
- const payload = await withDashboardRequestGuard(
- fetchDashboardDeadStock(buildDashboardDeadStockQuery()),
- {}
- )
- deadStockList.value = normalizeDashboardDeadStockList(payload || {})
- sectionLoading.deadStock = false
- }
-
async function loadLocUsageSection() {
sectionLoading.locUsage = true
const payload = await withDashboardRequestGuard(fetchDashboardLocUsage(), null)
@@ -355,29 +313,8 @@
sectionLoading.locUsage = false
}
- async function loadTaskSection() {
- sectionLoading.tasks = true
- const payload = await withDashboardRequestGuard(
- fetchDashboardTasks(buildDashboardTaskQuery()),
- {}
- )
- taskList.value = normalizeDashboardTaskList(payload || {})
- sectionLoading.tasks = false
- }
-
- function resolveTaskTagType(statusClass) {
- const text = String(statusClass || '')
- if (text.includes('emerald')) return 'success'
- if (text.includes('rose')) return 'danger'
- return 'primary'
- }
-
- function resolveTaskTagText(statusText) {
- const text = String(statusText || '')
- .replace(/\b\d+\./g, '')
- .replace(/\s+/g, ' ')
- .trim()
- const parts = text.split('路').map((item) => item.trim()).filter(Boolean)
- return parts[parts.length - 1] || '澶勭悊涓�'
+ function navigateTo(path) {
+ if (!path) return
+ router.push(path)
}
</script>
--
Gitblit v1.9.1