Files
MrleeAdmin/frontend/app/pages/statistics/index.vue
T

917 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="statistics-page" :class="`theme-${themeMode}`">
<view class="hero-card">
<view class="hero-main">
<text class="hero-title">个人统计</text>
<text class="hero-subtitle">查看当前登录用户的扫码与处理数据</text>
</view>
<view class="hero-avatar">
<u-icon name="account-fill" color="#ffffff" size="44" />
</view>
</view>
<view class="profile-card">
<view class="profile-row">
<view class="profile-badge">{{ profile.userCode }}</view>
<view class="profile-meta">
<text class="profile-name">{{ profile.name }}</text>
<text class="profile-desc">岗位:{{ profile.position }} · 班组:{{ profile.team }}</text>
</view>
</view>
<view class="profile-extra">
<view class="profile-chip">{{ profile.department }}</view>
<view class="profile-chip success">{{ profile.level }}</view>
</view>
</view>
<view class="metric-card">
<view class="section-header">
<text class="section-title">核心指标</text>
<text class="section-subtitle">本月累计</text>
</view>
<view class="metric-grid">
<view v-for="item in metrics" :key="item.label" class="metric-item">
<text class="metric-value" :class="item.type">{{ item.value }}</text>
<text class="metric-label">{{ item.label }}</text>
</view>
</view>
</view>
<view class="process-card">
<view class="section-header">
<text class="section-title">工序步骤统计</text>
<text class="section-subtitle">本月 Top 10</text>
</view>
<view v-if="processBreakdown.length" class="process-list">
<view v-for="item in processBreakdown" :key="item.key" class="process-row">
<text class="process-name">{{ item.name }}</text>
<text class="process-count">{{ item.quantity }}</text>
</view>
</view>
<text v-else class="empty-text">本月暂无工序步骤记录</text>
</view>
<view class="trend-card">
<view class="section-header">
<text class="section-title">个人趋势</text>
<text class="section-subtitle">最近 7 天</text>
</view>
<view class="bar-chart">
<view v-for="item in trendData" :key="item.label" class="bar-item">
<view class="bar-track">
<view class="bar-fill" :style="{ height: item.percent + '%', backgroundColor: item.color }"></view>
</view>
<text class="bar-label">{{ item.label }}</text>
</view>
</view>
</view>
<view class="calendar-card">
<view class="calendar-title-row">
<view class="calendar-title"><u-icon name="list-dot" color="#1677ff" size="34" /><text>月度总表({{ calendarMonthLabel }})</text></view>
<view class="month-actions"><text @tap="changeMonth(-1)">‹</text><text @tap="changeMonth(1)">›</text></view>
</view>
<text class="calendar-hint">日历仅显示是否有数据,点击日期查看当天统计</text>
<view class="weekday-row"><text v-for="day in weekdays" :key="day">{{ day }}</text></view>
<view v-for="(week, index) in calendarWeeks" :key="index" class="calendar-week">
<view v-for="cell in week" :key="cell.date" class="calendar-cell" :class="{ muted: !cell.currentMonth, selected: cell.date === selectedDate, today: cell.isToday, hasData: cell.hasData }" @tap="selectCalendarDate(cell)">
<text>{{ cell.day }}</text><view v-if="cell.hasData" class="data-dot"></view>
</view>
</view>
<view class="day-detail">
<view class="day-detail-header"><text>当天明细</text><text>{{ selectedDate }}</text></view>
<view class="day-metrics">
<view><text>订单扫码</text><strong>{{ selectedDaySummary.order }}</strong></view>
<view><text>整机数量</text><strong>{{ selectedDaySummary.product }}</strong></view>
<view><text>总计</text><strong>{{ selectedDaySummary.total }}</strong></view>
</view>
<view v-if="selectedDaySummary.steps.length" class="day-step-list">
<view v-for="step in selectedDaySummary.steps" :key="step.key" class="day-step-row"><text>{{ step.name }}</text><strong>{{ step.quantity }}</strong></view>
</view>
<text v-else class="day-empty">当天暂无工序数据</text>
</view>
</view>
<view class="record-card">
<view class="section-header">
<text class="section-title">最近明细</text>
<text class="section-subtitle">最新 5 条</text>
</view>
<view v-for="item in recentRecords" :key="item.id" class="record-item">
<view class="record-top">
<view>
<text class="record-main">{{ item.title }}</text>
<text class="record-sub">{{ item.subtitle }}</text>
</view>
<view class="record-pill" :class="item.statusClass">{{ item.status }}</view>
</view>
<view class="record-bottom">
<text>{{ item.time }}</text>
<text>{{ item.count }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
import { getUserInfo } from '@/api/member.js'
import { getExceptionStatistics, getOrderRecentRecords, getOrderStatistics, getProductRecentRecords, getProductStatistics, getRepairStatistics, getTodayDashboard } from '@/api/produce/data-entry.js'
export default {
data() {
return {
profile: {
userCode: 'U001',
name: '-',
position: '-',
team: '-',
department: '-',
level: '-'
},
metrics: [
{ label: '订单数', value: '0', type: 'primary' }, { label: '整机数', value: '0', type: 'success' },
{ label: '本月总计', value: '0', type: 'primary' }, { label: '异常提报', value: '0', type: 'warning' },
{ label: '维修量', value: '0', type: 'warning' }, { label: '责任订单', value: '0', type: 'danger' }
],
trendData: [],
processBreakdown: [],
recentRecords: [],
weekdays: ['一', '二', '三', '四', '五', '六', '日'],
calendarMonth: new Date(),
selectedDate: '',
calendarWeeks: [],
calendarData: {},
selectedDaySummary: { order: 0, product: 0, quantity: 0, total: 0, steps: [] },
}
},
computed: {
calendarMonthLabel() {
return `${this.calendarMonth.getFullYear()}-${String(this.calendarMonth.getMonth() + 1).padStart(2, '0')}`
}
},
onLoad() {
this.loadPageData()
},
methods: {
getCurrentUserCode() {
const user = uni.getStorageSync('scanapp_user') || {}
return user.username || user.nickname || 'U001'
},
async loadPageData() {
const userCode = this.getCurrentUserCode()
const today = new Date()
const dateText = (date) => date.toISOString().slice(0, 10)
const todayDate = dateText(today)
this.selectedDate = todayDate
this.buildCalendar()
const monthDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-01`
try {
const [profile, orderStats, productStats, repairStats, exceptionStats, dashboard, todayOrderStats, todayProductStats, todayRepairStats, orderRecent, productRecent] = await Promise.all([
getUserInfo(),
getOrderStatistics({ user_code: userCode, start_date: monthDate, end_date: todayDate }),
getProductStatistics({ user_code: userCode, start_date: monthDate, end_date: todayDate }),
getRepairStatistics({ user_code: userCode, start_date: monthDate, end_date: todayDate }),
getExceptionStatistics({ user_code: userCode, start_date: monthDate, end_date: todayDate }),
getTodayDashboard(),
getOrderStatistics({ user_code: userCode, start_date: todayDate, end_date: todayDate }),
getProductStatistics({ user_code: userCode, start_date: todayDate, end_date: todayDate }),
getRepairStatistics({ user_code: userCode, start_date: todayDate, end_date: todayDate }),
getOrderRecentRecords({ user_code: userCode, start_date: monthDate, end_date: todayDate, limit: 3 }),
getProductRecentRecords({ user_code: userCode, start_date: monthDate, end_date: todayDate, limit: 3 })
])
this.profile = {
userCode,
name: profile.nickname || profile.username || '-',
position: profile.position || profile.role_name || '-',
team: profile.team_name || profile.group_name || '-',
department: profile.department_name || '-',
level: Number(profile.enabled) === 0 ? '已停用' : '稳定输出'
}
const repairTotal = Number(repairStats?.completed_today || 0)
const exceptionTotal = Number(exceptionStats?.pending || 0) + Number(exceptionStats?.review || 0) + Number(exceptionStats?.done || 0)
const monthScanTotal = Number(orderStats?.today_total || 0) + Number(productStats?.today_total || 0)
const todayTotal = Number(todayOrderStats?.today_total || 0) + Number(todayProductStats?.today_total || 0)
this.selectedDaySummary = this.makeDaySummary(todayOrderStats, todayProductStats, todayRepairStats)
const responsibilityTotal = Number(dashboard?.responsibility_order_total || 0)
this.metrics = [
{ label: '订单数', value: String(orderStats?.today_total || 0), type: 'primary' },
{ label: '整机数', value: String(productStats?.today_total || 0), type: 'success' },
{ label: '本月总计', value: String(monthScanTotal), type: 'primary' },
{ label: '异常提报', value: String(exceptionTotal), type: 'warning' },
{ label: '维修量', value: String(repairTotal), type: 'warning' },
{ label: '责任订单', value: String(responsibilityTotal), type: 'danger' }
]
const breakdown = [...(orderStats?.process_breakdown || []), ...(productStats?.process_breakdown || []), ...(repairStats?.process_breakdown || [])]
const grouped = {}
breakdown.forEach((item) => {
const key = `${item.process_id || 'none'}-${item.step_id || 'none'}`
if (!grouped[key]) grouped[key] = { key, name: `${item.process_name || '未记录工序'} / ${item.step_name || '未记录步骤'}`, quantity: 0 }
grouped[key].quantity += Number(item.quantity || 0)
})
this.processBreakdown = Object.values(grouped).sort((a, b) => b.quantity - a.quantity).slice(0, 10)
const trendMap = {}
;[...(orderStats?.daily_trend || []), ...(productStats?.daily_trend || [])].forEach((item) => {
trendMap[item.date] = (trendMap[item.date] || 0) + Number(item.quantity || 0)
})
const trendDates = Object.keys(trendMap).sort().slice(-7)
const maxTrend = Math.max(1, ...trendDates.map((date) => trendMap[date]))
this.trendData = trendDates.map((date, index) => ({
label: date.slice(5),
value: trendMap[date],
percent: Math.round(trendMap[date] / maxTrend * 100),
color: index % 2 ? '#35bfa3' : '#2f6cf6'
}))
this.calendarData = trendMap
this.buildCalendar()
const orderItems = Array.isArray(orderRecent?.items) ? orderRecent.items : []
const productItems = Array.isArray(productRecent?.items) ? productRecent.items : []
this.recentRecords = [...orderItems, ...productItems].slice(0, 5).map((item, index) => ({
id: item.id || index,
title: item.order_no || item.process_order_no || '-',
subtitle: item.process_name && item.step_name ? `${item.process_name} / ${item.step_name}` : '生产记录',
status: Number(item.scan_result) === 0 ? '失败' : '成功',
statusClass: Number(item.scan_result) === 0 ? 'failed' : 'success',
time: item.scan_date || item.created_date || '-',
count: item.quantity ? `+${item.quantity}` : '1'
}))
} catch (error) {
console.error('[scanapp] 统计页数据加载失败', error)
}
},
formatDate(date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
},
buildCalendar() {
const year = this.calendarMonth.getFullYear()
const month = this.calendarMonth.getMonth()
const firstDay = (new Date(year, month, 1).getDay() + 6) % 7
const cells = []
for (let index = 0; index < 42; index++) {
const date = new Date(year, month, index - firstDay + 1)
const value = this.formatDate(date)
cells.push({ date: value, day: date.getDate(), currentMonth: date.getMonth() === month, isToday: value === this.formatDate(new Date()), hasData: Boolean(this.calendarData[value]) })
}
this.calendarWeeks = Array.from({ length: 6 }, (_, index) => cells.slice(index * 7, index * 7 + 7))
},
async changeMonth(offset) {
this.calendarMonth = new Date(this.calendarMonth.getFullYear(), this.calendarMonth.getMonth() + offset, 1)
const year = this.calendarMonth.getFullYear()
const month = this.calendarMonth.getMonth() + 1
const startDate = `${year}-${String(month).padStart(2, '0')}-01`
const endDate = this.formatDate(new Date(year, month, 0))
try {
const [order, product] = await Promise.all([
getOrderStatistics({ user_code: this.getCurrentUserCode(), start_date: startDate, end_date: endDate }),
getProductStatistics({ user_code: this.getCurrentUserCode(), start_date: startDate, end_date: endDate })
])
this.calendarData = [...(order?.daily_trend || []), ...(product?.daily_trend || [])].reduce((map, item) => ({ ...map, [item.date]: true }), {})
} catch (error) {
this.calendarData = {}
}
this.buildCalendar()
const firstCell = this.calendarWeeks.flat().find((cell) => cell.currentMonth)
if (firstCell) this.selectCalendarDate(firstCell)
},
async selectCalendarDate(cell) {
if (!cell.currentMonth) return
this.selectedDate = cell.date
try {
const [order, product, repair] = await Promise.all([
getOrderStatistics({ user_code: this.getCurrentUserCode(), start_date: cell.date, end_date: cell.date }),
getProductStatistics({ user_code: this.getCurrentUserCode(), start_date: cell.date, end_date: cell.date }),
getRepairStatistics({ user_code: this.getCurrentUserCode(), start_date: cell.date, end_date: cell.date })
])
this.selectedDaySummary = this.makeDaySummary(order, product, repair)
} catch (error) {
console.error('[scanapp] 日期统计加载失败', error)
}
},
makeDaySummary(order, product, repair) {
const orderTotal = Number(order?.today_total || 0)
const productTotal = Number(product?.today_total || 0)
const repairTotal = Number(repair?.completed_today || 0)
const rows = [...(order?.process_breakdown || []), ...(product?.process_breakdown || []), ...(repair?.process_breakdown || [])]
const grouped = {}
rows.forEach((item) => {
const key = `${item.process_id || 'none'}-${item.step_id || 'none'}`
if (!grouped[key]) grouped[key] = { key, name: `${item.process_name || '未记录工序'} / ${item.step_name || '未记录步骤'}`, quantity: 0 }
grouped[key].quantity += Number(item.quantity || 0)
})
return { order: orderTotal, product: productTotal, quantity: productTotal, total: orderTotal + productTotal + repairTotal, steps: Object.values(grouped).sort((a, b) => b.quantity - a.quantity) }
}
}
}
</script>
<style lang="scss" scoped>
.statistics-page {
min-height: 100vh;
padding: 24rpx 22rpx 40rpx;
box-sizing: border-box;
background: #f5f7fb;
}
.hero-card,
.profile-card,
.calendar-card,
.metric-card,
.trend-card,
.record-card,
.process-card {
margin-top: 18rpx;
padding: 24rpx;
border-radius: 22rpx;
background: #ffffff;
box-shadow: 0 10rpx 26rpx rgba(23, 35, 61, 0.05);
}
.calendar-title-row,
.calendar-title,
.month-actions,
.weekday-row,
.calendar-week,
.day-detail-header,
.day-metrics {
display: flex;
align-items: center;
}
.calendar-title-row,
.day-detail-header {
justify-content: space-between;
}
.calendar-title {
gap: 12rpx;
color: #17233d;
font-size: 28rpx;
font-weight: 700;
}
.month-actions text {
padding: 4rpx 14rpx;
color: #1677ff;
font-size: 36rpx;
}
.calendar-hint {
display: block;
margin-top: 16rpx;
padding: 16rpx;
border-radius: 12rpx;
background: #f5f8fd;
color: #7d8798;
font-size: 20rpx;
}
.weekday-row,
.calendar-week {
justify-content: space-between;
}
.weekday-row {
margin-top: 18rpx;
color: #9aa4b4;
font-size: 20rpx;
text-align: center;
}
.weekday-row text,
.calendar-cell {
width: 13.2%;
text-align: center;
}
.calendar-week {
margin-top: 10rpx;
}
.calendar-cell {
position: relative;
padding: 18rpx 0;
border-radius: 16rpx;
color: #303949;
font-size: 26rpx;
}
.calendar-cell.muted {
color: #c4cad5;
}
.calendar-cell.hasData::after {
content: '';
position: absolute;
bottom: 8rpx;
left: 50%;
width: 6rpx;
height: 6rpx;
border-radius: 50%;
background: #19be6b;
transform: translateX(-50%);
}
.calendar-cell.selected {
background: #2f80ed;
color: #ffffff;
box-shadow: 0 6rpx 14rpx rgba(47, 128, 237, 0.25);
}
.calendar-cell.selected.hasData::after {
background: #ffffff;
}
.day-detail {
margin-top: 18rpx;
padding: 18rpx;
border: 1rpx solid #dceafd;
border-radius: 16rpx;
background: #fbfdff;
}
.day-detail-header text:first-child {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
}
.day-detail-header text:last-child {
color: #7d8798;
font-size: 20rpx;
}
.day-metrics {
gap: 12rpx;
margin-top: 16rpx;
}
.day-metrics view {
flex: 1;
padding: 14rpx 6rpx;
border-radius: 12rpx;
background: #f0f6ff;
text-align: center;
}
.day-metrics view:nth-child(2) { background: #f2fbea; }
.day-metrics view:nth-child(3) { background: #fff7e6; }
.day-metrics text,
.day-metrics strong {
display: block;
}
.day-metrics text {
color: #7d8798;
font-size: 18rpx;
}
.day-metrics strong {
margin-top: 6rpx;
color: #303949;
font-size: 28rpx;
}
.day-empty {
display: block;
margin-top: 16rpx;
color: #9aa4b4;
font-size: 20rpx;
text-align: center;
}
.day-step-list {
margin-top: 14rpx;
border-top: 1rpx solid #e7edf6;
}
.day-step-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12rpx 4rpx;
border-bottom: 1rpx solid #e7edf6;
color: #526078;
font-size: 20rpx;
}
.day-step-row strong {
color: #2f6cf6;
font-size: 22rpx;
}
.hero-card {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 0;
padding: 30rpx 26rpx;
background: linear-gradient(135deg, #2f6cf6 0%, #35bfa3 100%);
}
.hero-title,
.hero-subtitle,
.section-title,
.section-subtitle,
.profile-name,
.profile-desc,
.metric-label,
.record-main,
.record-sub,
.record-bottom,
.progress-label,
.progress-value {
display: block;
}
.hero-title {
color: #ffffff;
font-size: 38rpx;
font-weight: 800;
line-height: 1.35;
}
.hero-subtitle {
margin-top: 8rpx;
color: rgba(255, 255, 255, 0.84);
font-size: 22rpx;
line-height: 1.35;
}
.hero-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 84rpx;
height: 84rpx;
border-radius: 26rpx;
background: rgba(255, 255, 255, 0.18);
}
.profile-row {
display: flex;
align-items: center;
gap: 0;
}
.profile-badge {
min-width: 98rpx;
margin-right: 24rpx;
padding: 18rpx 14rpx;
border-radius: 18rpx;
background: rgba(47, 108, 246, 0.1);
color: #2f6cf6;
font-size: 22rpx;
font-weight: 700;
text-align: center;
flex: none;
}
.profile-meta {
flex: 1;
min-width: 0;
}
.profile-name {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.profile-desc {
margin-top: 6rpx;
color: #7d8798;
font-size: 20rpx;
line-height: 1.4;
}
.profile-extra {
display: flex;
gap: 0;
margin-top: 16rpx;
flex-wrap: wrap;
}
.profile-chip {
padding: 6rpx 16rpx;
border-radius: 999rpx;
background: #eef3fb;
color: #6b778c;
font-size: 18rpx;
line-height: 1.35;
flex: none;
}
.profile-chip + .profile-chip {
margin-left: 14rpx;
}
.profile-chip.success {
background: rgba(25, 190, 107, 0.12);
color: #19be6b;
}
.section-header {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-bottom: 16rpx;
}
.section-header.compact {
margin-bottom: 12rpx;
}
.section-title {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.section-subtitle {
color: #9aa4b4;
font-size: 20rpx;
line-height: 1.35;
}
.metric-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14rpx;
}
.metric-item {
padding: 18rpx;
border-radius: 18rpx;
background: #f8fbff;
}
.metric-value {
color: #17233d;
font-size: 32rpx;
font-weight: 800;
line-height: 1.2;
}
.metric-value.primary {
color: #2f6cf6;
}
.metric-value.success {
color: #19be6b;
}
.metric-value.danger {
color: #e91f3f;
}
.metric-value.warning {
color: #f7a928;
}
.metric-label {
margin-top: 6rpx;
color: #7d8798;
font-size: 20rpx;
line-height: 1.35;
}
.bar-chart {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 12rpx;
align-items: end;
height: 220rpx;
}
.bar-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
height: 100%;
}
.bar-track {
display: flex;
align-items: end;
justify-content: center;
width: 100%;
height: 170rpx;
border-radius: 16rpx;
background: #f3f6fb;
overflow: hidden;
}
.bar-fill {
width: 72%;
border-radius: 16rpx 16rpx 0 0;
}
.bar-label {
margin-top: 10rpx;
color: #9aa4b4;
font-size: 18rpx;
line-height: 1.35;
}
.record-item {
padding: 18rpx 0;
border-bottom: 1rpx solid #eef1f6;
}
.process-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14rpx 0;
border-bottom: 1rpx solid #eef1f6;
}
.process-row:last-child {
border-bottom: 0;
}
.process-name {
color: #4d5b70;
font-size: 22rpx;
}
.process-count {
color: #2f6cf6;
font-size: 24rpx;
font-weight: 700;
}
.empty-text {
display: block;
padding: 20rpx 0 6rpx;
color: #9aa4b4;
font-size: 20rpx;
text-align: center;
}
.record-item:last-child {
padding-bottom: 0;
border-bottom: 0;
}
.record-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14rpx;
}
.record-main {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.record-sub {
margin-top: 5rpx;
color: #7d8798;
font-size: 20rpx;
line-height: 1.35;
}
.record-pill {
padding: 4rpx 14rpx;
border-radius: 999rpx;
font-size: 18rpx;
line-height: 1.35;
flex: none;
}
.record-pill.success {
background: rgba(25, 190, 107, 0.12);
color: #19be6b;
}
.record-pill.failed {
background: rgba(233, 31, 63, 0.12);
color: #e91f3f;
}
.record-bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10rpx;
color: #9aa4b4;
font-size: 18rpx;
line-height: 1.35;
}
.theme-dark.statistics-page {
background: #151923;
}
.theme-dark .hero-card,
.theme-dark .profile-card,
.theme-dark .calendar-card,
.theme-dark .metric-card,
.theme-dark .trend-card,
.theme-dark .record-card,
.theme-dark .process-card {
border-color: #364152;
background: #222936;
box-shadow: none;
}
.theme-dark .metric-item,
.theme-dark .profile-chip {
background: #1c2431;
}
.theme-dark .profile-name,
.theme-dark .calendar-title,
.theme-dark .section-title,
.theme-dark .metric-value,
.theme-dark .record-main,
.theme-dark .progress-value {
color: #f2f5fa;
}
.theme-dark .profile-desc,
.theme-dark .calendar-hint,
.theme-dark .section-subtitle,
.theme-dark .metric-label,
.theme-dark .bar-label,
.theme-dark .record-sub,
.theme-dark .record-bottom,
.theme-dark .progress-label,
.theme-dark .profile-chip {
color: #aeb9ca;
}
.theme-dark .process-name,
.theme-dark .empty-text {
color: #aeb9ca;
}
.theme-dark .process-row {
border-color: #364152;
}
.theme-dark .record-item {
border-color: #364152;
}
.theme-dark .bar-track {
background: #1c2431;
}
.theme-dark .calendar-hint,
.theme-dark .day-detail {
background: #1c2431;
border-color: #364152;
}
.theme-dark .calendar-cell {
color: #f2f5fa;
}
.theme-dark .calendar-cell.muted,
.theme-dark .day-detail-header text:last-child,
.theme-dark .day-metrics text,
.theme-dark .day-empty {
color: #aeb9ca;
}
.theme-dark .day-detail-header text:first-child,
.theme-dark .day-metrics strong {
color: #f2f5fa;
}
.theme-dark .day-step-list,
.theme-dark .day-step-row {
border-color: #364152;
}
.theme-dark .day-step-row {
color: #aeb9ca;
}
.theme-dark .profile-badge {
background: rgba(47, 108, 246, 0.2);
}
.theme-dark .record-pill.success,
.theme-dark .record-pill.failed {
background: rgba(25, 190, 107, 0.18);
}
.theme-dark .record-pill.failed {
background: rgba(233, 31, 63, 0.18);
}
</style>