feat(home): surface ops shortcuts and realtime stats
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<text class="text-28rpx text-[#1F2937] font-bold">常用应用</text>
|
||||
</view>
|
||||
<view class="ai-card p-32rpx">
|
||||
<view class="grid grid-cols-3 gap-y-40rpx">
|
||||
<view class="grid grid-cols-4 gap-y-40rpx">
|
||||
<view
|
||||
v-for="app in quickApps"
|
||||
:key="app.key"
|
||||
@@ -93,12 +93,10 @@ const expandedGroups = ref<string[]>([])
|
||||
|
||||
/** 快速应用 — 重新设计图标与配色 */
|
||||
const quickApps = [
|
||||
{ key: 'workOrder', name: '工单池', icon: 'i-carbon-task-complete', color: '#F97316', bgLight: '#FFF7ED', url: '/pages/scan/work-order/index' },
|
||||
{ key: 'staff', name: '员工管理', icon: 'i-carbon-events', color: '#3B82F6', bgLight: '#EFF6FF', url: '/pages/scan/staff/index' },
|
||||
{ key: 'inspection', name: '扫码巡检', icon: 'i-carbon-qr-code', color: '#8B5CF6', bgLight: '#F5F3FF', url: '/pages/scan/index' },
|
||||
{ key: 'dashboard', name: '数据看板', icon: 'i-carbon-analytics', color: '#10B981', bgLight: '#ECFDF5' },
|
||||
{ key: 'attendance', name: '考勤打卡', icon: 'i-carbon-location-person', color: '#06B6D4', bgLight: '#ECFEFF' },
|
||||
{ key: 'repair', name: '报修管理', icon: 'i-carbon-settings-adjust', color: '#EC4899', bgLight: '#FDF2F8' },
|
||||
{ key: 'workOrder', name: '工单中心', icon: 'i-carbon-task-complete', color: '#F97316', bgLight: '#FFF7ED', url: '/pages/scan/work-order/index' },
|
||||
{ key: 'inspection', name: '巡检记录', icon: 'i-carbon-list-checked', color: '#8B5CF6', bgLight: '#F5F3FF', url: '/pages/scan/inspection/list' },
|
||||
{ key: 'workOrderStats', name: '工单统计', icon: 'i-carbon-chart-bar', color: '#3B82F6', bgLight: '#EFF6FF', url: '/pages/scan/work-order/stats' },
|
||||
{ key: 'trafficStats', name: '客流统计', icon: 'i-carbon-pedestrian', color: '#10B981', bgLight: '#ECFDF5', url: '/pages/scan/traffic/index' },
|
||||
]
|
||||
|
||||
function handleQuickApp(app: any) {
|
||||
|
||||
@@ -50,11 +50,11 @@
|
||||
<view class="mt-32rpx flex px-8rpx">
|
||||
<view class="flex flex-1 flex-col">
|
||||
<text class="stat-label">实时客流统计</text>
|
||||
<text class="stat-value">2,450</text>
|
||||
<text class="stat-value">{{ trafficCount }}</text>
|
||||
</view>
|
||||
<view class="stat-divider flex flex-1 flex-col pl-32rpx">
|
||||
<text class="stat-label">实时工单统计</text>
|
||||
<text class="stat-value">128</text>
|
||||
<text class="stat-value">{{ orderCount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -102,6 +102,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { getWorkspaceStats } from '@/api/ops/order-center'
|
||||
import { getTrafficRealtime } from '@/api/ops/traffic'
|
||||
import { useUserStore } from '@/store'
|
||||
import { menuButtonInfo, statusBarHeight } from '@/utils/systemInfo'
|
||||
|
||||
@@ -149,29 +151,36 @@ const avatarStyle = computed(() => {
|
||||
return { width: '80rpx', height: '80rpx' }
|
||||
})
|
||||
|
||||
// ---- 统计数据 ----
|
||||
const trafficTotal = ref<number | undefined>()
|
||||
const orderTotal = ref<number | undefined>()
|
||||
|
||||
/** 格式化数字:大于 1000 使用逗号分隔 */
|
||||
function formatNumber(num: number): string {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const trafficCount = computed(() =>
|
||||
trafficTotal.value !== undefined ? formatNumber(trafficTotal.value) : '--',
|
||||
)
|
||||
const orderCount = computed(() =>
|
||||
orderTotal.value !== undefined ? formatNumber(orderTotal.value) : '--',
|
||||
)
|
||||
|
||||
// ---- 柱状图数据 ----
|
||||
const rawData = [
|
||||
{ label: '08:00', value: 180 },
|
||||
{ label: '09:00', value: 360 },
|
||||
{ label: '10:00', value: 520 },
|
||||
{ label: '11:00', value: 720 },
|
||||
{ label: '12:00', value: 490 },
|
||||
{ label: '13:00', value: 410 },
|
||||
{ label: '14:00', value: 670 },
|
||||
{ label: '15:00', value: 810 },
|
||||
{ label: '16:00', value: 580 },
|
||||
{ label: '17:00', value: 450 },
|
||||
{ label: '18:00', value: 320 },
|
||||
{ label: '19:00', value: 240 },
|
||||
]
|
||||
const rawData = ref<{ label: string, value: number }[]>([])
|
||||
|
||||
const maxValue = Math.max(...rawData.map(d => d.value))
|
||||
|
||||
const chartData = rawData.map((d, i) => ({
|
||||
...d,
|
||||
percent: (d.value / maxValue) * 100,
|
||||
showLabel: i % 2 === 0, // 隔一个显示
|
||||
}))
|
||||
const chartData = computed(() => {
|
||||
const data = rawData.value
|
||||
if (!data.length)
|
||||
return []
|
||||
const max = Math.max(...data.map(d => d.value), 1)
|
||||
return data.map((d, i) => ({
|
||||
...d,
|
||||
percent: (d.value / max) * 100,
|
||||
showLabel: i % 2 === 0,
|
||||
}))
|
||||
})
|
||||
|
||||
/** 当前选中的柱子,-1 未选中 */
|
||||
const activeBar = ref(-1)
|
||||
@@ -180,6 +189,38 @@ function handleBarTap(index: number) {
|
||||
activeBar.value = activeBar.value === index ? -1 : index
|
||||
}
|
||||
|
||||
/** 获取当天日期 yyyy-MM-dd */
|
||||
function getTodayStr() {
|
||||
const d = new Date()
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
/** 加载统计数据(并行请求) */
|
||||
async function loadStats() {
|
||||
const [trafficResult, workspaceResult] = await Promise.allSettled([
|
||||
getTrafficRealtime(getTodayStr()),
|
||||
getWorkspaceStats(),
|
||||
])
|
||||
|
||||
if (trafficResult.status === 'fulfilled') {
|
||||
trafficTotal.value = trafficResult.value.totalIn
|
||||
if (trafficResult.value.hourlyTrend) {
|
||||
const { hours, inData } = trafficResult.value.hourlyTrend
|
||||
rawData.value = hours
|
||||
.map((h, i) => ({ label: h, value: inData[i] || 0 }))
|
||||
.filter(d => d.label >= '06:00' && d.label <= '23:00')
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaceResult.status === 'fulfilled') {
|
||||
orderTotal.value = workspaceResult.value.todayOrderCount
|
||||
}
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
loadStats()
|
||||
})
|
||||
|
||||
/** 根据时间获取问候语 */
|
||||
const greeting = computed(() => {
|
||||
const hour = new Date().getHours()
|
||||
|
||||
Reference in New Issue
Block a user