From aaf8a50511d77dbc209ca93bbba308c21179a8bc Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 31 三月 2026 15:38:47 +0800
Subject: [PATCH] #前端
---
rsf-design/src/views/dashboard/console/index.vue | 542 +++++++++++++++++++++++++++++++++--------------------
1 files changed, 338 insertions(+), 204 deletions(-)
diff --git a/rsf-design/src/views/dashboard/console/index.vue b/rsf-design/src/views/dashboard/console/index.vue
index 90691e1..1a2f229 100644
--- a/rsf-design/src/views/dashboard/console/index.vue
+++ b/rsf-design/src/views/dashboard/console/index.vue
@@ -1,145 +1,145 @@
<template>
- <div class="art-full-height flex flex-col gap-5">
- <section
- class="overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(135deg,var(--art-main-bg-color),var(--art-card-bg-color))] p-6 shadow-[0_24px_80px_rgba(15,23,42,0.08)]"
- >
- <div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
- <div class="max-w-3xl">
- <div
- class="mb-3 inline-flex items-center gap-2 rounded-full border border-emerald-500/20 bg-emerald-500/10 px-3 py-1 text-xs font-medium text-emerald-600"
- >
- <span class="size-2 rounded-full bg-emerald-500"></span>
- RSF Phase 1 Landing
+ <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
+ v-for="item in summaryCardItems"
+ :key="item.title"
+ class="art-card flex items-start justify-between rounded-3xl px-7 py-6"
+ >
+ <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>
- <h1
- class="m-0 text-3xl font-semibold tracking-tight text-[var(--art-gray-900)] md:text-4xl"
- >
- 杩愯楠ㄦ灦宸茬粡鍒囧埌 `rsf-design`
- </h1>
- <p class="mt-4 max-w-2xl text-sm leading-7 text-[var(--art-gray-600)] md:text-base">
- 褰撳墠鍏ュ彛宸茬粡鎺ュ叆鐪熷疄鍚庣鐧诲綍銆佸姩鎬佽彍鍗曞拰鏉冮檺閾捐矾銆傝繖涓椤靛彧灞曠ず宸茬粡鍙敤鐨� phase-1
- 鑳藉姏锛屼笉鍐嶄繚鐣欐ā鏉块噷鐨勭ず渚嬪浘琛ㄥ拰婕旂ず鏁版嵁銆�
- </p>
</div>
-
- <div class="rounded-2xl border border-white/10 bg-white/70 px-4 py-3 backdrop-blur-sm">
- <p class="text-xs uppercase tracking-[0.24em] text-[var(--art-gray-500)]"
- >Current Entry</p
- >
- <p class="mt-2 text-lg font-semibold text-[var(--art-gray-900)]">
- {{ currentUserName }}
- </p>
- <p class="mt-1 text-sm text-[var(--art-gray-600)]">
- {{ currentUserRoleText }} 路 {{ currentMenuLabel }}
- </p>
+ <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>
</div>
</section>
- <section class="grid gap-4 md:grid-cols-3">
- <ArtStatsCard
- title="宸叉帴鍏ュ悗绔�"
- :count="backendSwitchCount"
- description="鐧诲綍銆佺敤鎴蜂俊鎭�佽彍鍗曞叏閮ㄦ潵鑷� rsf-server"
- icon="ri:server-line"
- />
- <ArtStatsCard
- title="鍔ㄦ�佽彍鍗�"
- :count="visibleMenuCount"
- description="浠呭彂甯� phase-1 鍏佽杩涘叆鐨勬柊鍏ュ彛"
- icon="ri:route-line"
- />
- <ArtStatsCard
- title="鏉冮檺閾捐矾"
- :count="permissionSignalCount"
- description="瑙掕壊涓庢潈闄愯妭鐐瑰凡浠庣湡瀹炵敤鎴锋暟鎹仮澶�"
- icon="ri:shield-check-line"
- />
+ <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>
+ </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>
+ </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>
+
+ <ElEmpty v-if="!locUsageList.length" description="鏆傛棤搴撲綅浣跨敤鏁版嵁" :image-size="88" />
+ </div>
+ </div>
+ </div>
</section>
- <section class="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]">
- <ElCard
- class="rounded-3xl border border-white/10 shadow-[0_18px_50px_rgba(15,23,42,0.06)]"
- shadow="never"
- >
- <template #header>
- <div class="flex items-center justify-between">
- <div>
- <h2 class="m-0 text-base font-semibold text-[var(--art-gray-900)]">鐪熷疄杩愯鐘舵��</h2>
- <p class="mt-1 text-xs text-[var(--art-gray-500)]">
- 杩欎簺淇℃伅鏉ヨ嚜褰撳墠鐧诲綍鐢ㄦ埛鍜岃彍鍗� store
- </p>
- </div>
- <ElTag type="success" effect="light">Backend mode</ElTag>
- </div>
- </template>
-
- <div class="grid gap-4 md:grid-cols-2">
- <div class="rounded-2xl bg-[var(--art-gray-50)] p-4">
- <p class="text-xs uppercase tracking-[0.2em] text-[var(--art-gray-500)]">User</p>
- <p class="mt-3 text-xl font-semibold text-[var(--art-gray-900)]">
- {{ currentUserName }}
- </p>
- <p class="mt-2 text-sm text-[var(--art-gray-600)]">
- {{ currentUserRoleText }}
- </p>
- <div class="mt-4 flex flex-wrap gap-2">
- <ElTag v-for="role in currentRoles" :key="role" type="info" effect="plain">
- {{ role }}
- </ElTag>
- <ElTag v-if="!currentRoles.length" type="info" effect="plain">No roles</ElTag>
- </div>
- </div>
-
- <div class="rounded-2xl bg-[var(--art-gray-50)] p-4">
- <p class="text-xs uppercase tracking-[0.2em] text-[var(--art-gray-500)]">Permissions</p>
- <p class="mt-3 text-xl font-semibold text-[var(--art-gray-900)]">
- {{ currentAuthorities.length }} auth nodes
- </p>
- <p class="mt-2 text-sm text-[var(--art-gray-600)]">
- 鏉冮檺鑺傜偣鐩存帴鏉ヨ嚜褰撳墠鐢ㄦ埛鐨勭湡瀹� `authorities` 杞借嵎锛屼笉鍐嶄緷璧栨ā鏉挎紨绀烘�併��
- </p>
- <div class="mt-4 flex flex-wrap gap-2">
- <ElTag v-for="item in previewAuthorities" :key="item" type="warning" effect="plain">
- {{ item }}
- </ElTag>
- <ElTag v-if="!previewAuthorities.length" type="warning" effect="plain">
- No authorities
- </ElTag>
- </div>
+ <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>
- </ElCard>
- <ElCard
- class="rounded-3xl border border-white/10 shadow-[0_18px_50px_rgba(15,23,42,0.06)]"
- shadow="never"
- >
- <template #header>
- <div>
- <h2 class="m-0 text-base font-semibold text-[var(--art-gray-900)]">鑿滃崟鎺ュ叆娓呭崟</h2>
- <p class="mt-1 text-xs text-[var(--art-gray-500)]">
- 褰撳墠鑿滃崟鏍戠敤浜庨獙璇� `rsf-design` 鏄惁鐪熸鎺ヤ綇鍚庣鍙戝竷
- </p>
- </div>
- </template>
-
- <div class="space-y-3">
- <div
- v-for="item in menuPreview"
- :key="item.key"
- class="flex items-center justify-between rounded-2xl border border-[var(--art-gray-200)] px-4 py-3"
- >
- <div>
- <p class="text-sm font-medium text-[var(--art-gray-900)]">{{ item.title }}</p>
- <p class="mt-1 text-xs text-[var(--art-gray-500)]">{{ item.description }}</p>
+ <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>
+ <p class="mt-2 text-sm text-g-600">{{ item.subtitle }}</p>
+ </div>
</div>
- <ElTag :type="item.type" effect="light">
- {{ item.value }}
- </ElTag>
+ </ElScrollbar>
+ </div>
+ </div>
+
+ <div class="art-card h-98 p-5 box-border" v-loading="sectionLoading.deadStock">
+ <div class="art-card-header">
+ <div class="title">
+ <h4>搴撳瓨鏈�杩戝姩鎬�</h4>
+ <p>{{ deadStockSubtitle }}</p>
</div>
</div>
- </ElCard>
+
+ <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>
+ </ElScrollbar>
+ </div>
+ </div>
</section>
</div>
</template>
@@ -147,15 +147,42 @@
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store/modules/user'
- import { useMenuStore } from '@/store/modules/menu'
- import { formatMenuTitle } from '@/utils/router'
+ import {
+ fetchDashboardHeader,
+ fetchDashboardTrend,
+ fetchDashboardDeadStock,
+ fetchDashboardLocUsage,
+ fetchDashboardTasks
+ } from '@/api/dashboard'
+ import {
+ EMPTY_SUMMARY,
+ buildDashboardDeadStockQuery,
+ buildDashboardTaskQuery,
+ normalizeDashboardSummary,
+ normalizeDashboardTrend,
+ normalizeDashboardTaskList,
+ normalizeDashboardLocUsage,
+ normalizeDashboardDeadStockList,
+ withDashboardRequestGuard
+ } from './consolePage.helpers'
defineOptions({ name: 'Console' })
+ 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
+ })
+
const userStore = useUserStore()
- const menuStore = useMenuStore()
const { getUserInfo } = storeToRefs(userStore)
- const { menuList } = storeToRefs(menuStore)
const currentUser = computed(() => getUserInfo.value || {})
const currentUserName = computed(() => {
@@ -166,84 +193,191 @@
'RSF User'
)
})
- const currentRoles = computed(() => {
- const roles = currentUser.value.roles
- if (!Array.isArray(roles)) return []
- return roles
- .map((role) => {
- if (typeof role === 'string') {
- return role
- }
- return role?.code || role?.name || role?.title || ''
- })
- .filter(Boolean)
+ const currentDateText = computed(() => {
+ return new Date().toLocaleDateString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit'
+ })
})
- const currentAuthorities = computed(() => {
- const authorities = currentUser.value.authorities
- if (!Array.isArray(authorities)) return []
- return authorities.map((item) => item?.authority || '').filter(Boolean)
- })
- const currentMenuLabel = computed(() => {
- const firstMenu = menuList.value?.[0]
- return formatMenuTitle(firstMenu?.meta?.title || 'menus.dashboard.console')
- })
- const currentUserRoleText = computed(() => {
- if (!currentRoles.value.length) {
- return 'No role information yet'
+ const summaryCardItems = computed(() => [
+ {
+ title: '寰呭叆搴撴暟閲�',
+ count: summary.value.pendingIn,
+ metaLabel: '鎴嚦',
+ metaValue: currentDateText.value,
+ metaTone: 'text-g-500',
+ icon: 'ri:pie-chart-line',
+ iconBoxClass: 'bg-[var(--el-color-primary-light-9)]',
+ iconClass: 'text-[var(--el-color-primary)]'
+ },
+ {
+ title: '寰呭嚭搴撴暟閲�',
+ count: summary.value.pendingOut,
+ metaLabel: '鐘舵��',
+ metaValue: '褰撳墠寰呮墽琛屽嚭搴撻噺',
+ metaTone: 'text-success',
+ icon: 'ri:fire-line',
+ iconBoxClass: 'bg-[rgba(255,175,32,0.14)]',
+ iconClass: 'text-[#FFAF20]'
+ },
+ {
+ title: '宸插叆搴撴暟閲�',
+ count: summary.value.completedIn,
+ metaLabel: '缁撴灉',
+ metaValue: '绱瀹屾垚鍏ュ簱缁撴灉',
+ metaTone: 'text-g-500',
+ icon: 'ri:archive-line',
+ iconBoxClass: 'bg-[rgba(20,222,186,0.14)]',
+ iconClass: 'text-[#14DEBA]'
+ },
+ {
+ title: '鎵ц涓换鍔�',
+ count: summary.value.taskQty,
+ metaLabel: '褰撳墠鐢ㄦ埛',
+ metaValue: currentUserName.value,
+ metaTone: 'text-g-500',
+ icon: 'ri:progress-2-line',
+ iconBoxClass: 'bg-[rgba(139,92,246,0.16)]',
+ iconClass: 'text-[#8B5CF6]'
}
- return `Roles: ${currentRoles.value.join(' / ')}`
- })
- const backendSwitchCount = computed(() => {
- return currentUserName.value !== 'RSF User' || menuList.value.length > 0 ? 1 : 0
- })
- const visibleMenuCount = computed(() => countVisibleMenus(menuList.value))
- const permissionSignalCount = computed(() => {
- return currentRoles.value.length + currentAuthorities.value.length
- })
- const previewAuthorities = computed(() => currentAuthorities.value.slice(0, 4))
- const menuPreview = computed(() => {
- const total = visibleMenuCount.value
- const rootCount = Array.isArray(menuList.value) ? menuList.value.length : 0
- const firstMenu = menuList.value?.[0]
- const firstChildren = Array.isArray(firstMenu?.children) ? firstMenu.children.length : 0
+ ])
+ const trendDisplayModel = computed(() => {
+ const xAxisData = Array.isArray(trendModel.value.xAxisData) ? trendModel.value.xAxisData : []
+ const series = Array.isArray(trendModel.value.series) ? trendModel.value.series : []
+ if (!xAxisData.length || !series.length) {
+ return { xAxisData: [], series: [] }
+ }
- return [
- {
- key: 'entry',
- title: '鍏ュ彛妯″紡',
- description: '褰撳墠椤甸潰浠� backend mode 杩愯锛岃彍鍗曠敱鏈嶅姟绔┍鍔�',
- value: 'backend',
- type: 'success'
- },
- {
- key: 'menus',
- title: '鍙鑿滃崟',
- description: '宸查�氳繃鍔ㄦ�佽矾鐢遍�傞厤鍚庤繘鍏ュ墠绔彍鍗曟爲',
- value: `${total}`,
- type: 'primary'
- },
- {
- key: 'root',
- title: '涓�绾х洰褰�',
- description: '鏍圭骇鑿滃崟鑺傜偣鏁伴噺',
- value: `${rootCount}`,
- type: 'info'
- },
- {
- key: 'children',
- title: '棣栦釜鐩綍瀛愰」',
- description: '鐢ㄤ簬蹇�熺‘璁よ彍鍗曟爲宸茶姝g‘灞曞紑',
- value: `${firstChildren}`,
- type: 'warning'
+ const bucketSize = Math.max(1, Math.ceil(xAxisData.length / 8))
+ const bucketLabels = []
+ const bucketSeries = series.map((item) => ({
+ ...item,
+ data: []
+ }))
+
+ for (let index = 0; index < xAxisData.length; index += bucketSize) {
+ const labelBucket = xAxisData.slice(index, index + bucketSize)
+ bucketLabels.push(labelBucket[labelBucket.length - 1] || '--')
+ bucketSeries.forEach((seriesItem, seriesIndex) => {
+ const sourceBucket = Array.isArray(series[seriesIndex]?.data)
+ ? series[seriesIndex].data.slice(index, index + bucketSize)
+ : []
+ const bucketTotal = sourceBucket.reduce((total, value) => total + Number(value || 0), 0)
+ seriesItem.data.push(bucketTotal)
+ })
+ }
+
+ const visibleIndexes = bucketLabels.reduce((indexes, _, index) => {
+ const hasData = bucketSeries.some((item) => Number(item.data[index] || 0) > 0)
+ if (hasData) {
+ indexes.push(index)
}
- ]
+ return indexes
+ }, [])
+
+ const effectiveIndexes =
+ visibleIndexes.length > 0
+ ? visibleIndexes
+ : bucketLabels.map((_, index) => index).slice(-8)
+
+ return {
+ xAxisData: effectiveIndexes.map((index) => bucketLabels[index]),
+ series: bucketSeries.map((item) => ({
+ ...item,
+ data: effectiveIndexes.map((index) => item.data[index])
+ }))
+ }
+ })
+ 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']
+ return locUsageList.value.map((item, index) => ({
+ ...item,
+ 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()
})
- function countVisibleMenus(items) {
- if (!Array.isArray(items)) return 0
- return items.reduce((total, item) => {
- const current = item?.meta?.isHide ? 0 : 1
- return total + current + countVisibleMenus(item?.children)
- }, 0)
+ function loadDashboard() {
+ void loadSummarySection()
+ void loadTrendSection()
+ void loadDeadStockSection()
+ void loadLocUsageSection()
+ void loadTaskSection()
+ }
+
+ async function loadSummarySection() {
+ sectionLoading.summary = true
+ const payload = await withDashboardRequestGuard(fetchDashboardHeader(), null)
+ summary.value = normalizeDashboardSummary(payload)
+ sectionLoading.summary = false
+ }
+
+ async function loadTrendSection() {
+ sectionLoading.trend = true
+ const payload = await withDashboardRequestGuard(fetchDashboardTrend(), null)
+ trendModel.value = normalizeDashboardTrend(payload)
+ 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)
+ locUsageList.value = normalizeDashboardLocUsage(payload)
+ 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] || '澶勭悊涓�'
}
</script>
--
Gitblit v1.9.1