UI 重设计 (Editorial Data Console 风): - 设计令牌系统: OKLCH 色彩 + Newsreader/Geist/JetBrains Mono 字体 + exp easing - 全局表格基线 (.n-data-table 统一 editorial 风 + .table-shell 卡片容器) - DataCard / Naive UI 主题对齐新 token (深墨青主色 + 暖琥珀强调) - RoiDashboard: 3 KPI 卡片同字号 + chip 多色筛选 + section editorial 节奏 - ProjectRoiBoard: hero 卡 highlight + ytd-strip 节奏化 (10/13/15px 三层字号) - ProjectList: 自适应卡片 + 产品线 NSelect 筛选 + 拆出独立"类型"列 + 文本链接操作 - RevenuePieChart 重设计: donut + 中心总额 + 底部水平图例 (替代外部 callout 截断) - 全部页面 width:100% + clamp() 流体 padding,断点驱动 auto-fit 网格 - AppSidebar 项目子菜单按产品线分组 + 可折叠 + localStorage 持久化 接口性能优化 (N+1 → 批量 + Map 索引): - /api/overview: 8.5s → 0.5s (17×) - 消除 3 处循环 SQL 查询 - /api/okr: 11.3s → 0.3s (37×) - getOKRByPeriod 一次性 inArray 批量 - ROI 三处时间窗 (aggregate/timeseries/events) launchedAt 截断对齐 ROI 权限锁: - 全部 ROI 端点统一 admin (roiRoutes 全局 requireRole) - 路由 /roi + /projects/:id/roi meta.roles=['admin'] - 侧边栏 ROI 入口 + 项目详情打标按钮/分类标签全部 v-if isAdmin Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
116 lines
3.0 KiB
Vue
116 lines
3.0 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { useECharts, CHART_COLORS } from '@/composables/useECharts';
|
|
|
|
const props = defineProps<{
|
|
byCategory: Record<string, { totalRevenue: number }>;
|
|
}>();
|
|
|
|
const CATEGORY_LABELS: Record<string, string> = {
|
|
cash_cow: '现金牛',
|
|
efficiency_tool: '效能工具',
|
|
moat: '资本护城河',
|
|
composite: '复合型',
|
|
uncategorized: '未打标',
|
|
};
|
|
|
|
function fmtCurrency(n: number): string {
|
|
if (n >= 10000) return `¥${(n / 10000).toFixed(1)}万`;
|
|
return `¥${Math.round(n).toLocaleString()}`;
|
|
}
|
|
|
|
const total = computed(() =>
|
|
Object.values(props.byCategory).reduce((s, v) => s + (v.totalRevenue > 0 ? v.totalRevenue : 0), 0)
|
|
);
|
|
|
|
const option = computed(() => {
|
|
const data = Object.entries(props.byCategory)
|
|
.filter(([, v]) => v.totalRevenue > 0)
|
|
.map(([k, v]) => ({ name: CATEGORY_LABELS[k] || k, value: Math.round(v.totalRevenue) }));
|
|
|
|
return {
|
|
color: CHART_COLORS,
|
|
textStyle: { fontFamily: "'Geist', 'PingFang SC', sans-serif", color: '#4d5258' },
|
|
tooltip: {
|
|
trigger: 'item',
|
|
backgroundColor: '#ffffff',
|
|
borderColor: '#dfe2e6',
|
|
borderWidth: 1,
|
|
textStyle: { color: '#2d3033', fontSize: 12 },
|
|
extraCssText: 'box-shadow: 0 8px 24px rgba(34,40,42,0.06); border-radius: 10px; padding: 8px 12px;',
|
|
formatter: (params: any) => `
|
|
<div style="font-weight:600;margin-bottom:4px">${params.name}</div>
|
|
<div style="font-family:'JetBrains Mono',monospace;font-size:13px">¥${params.value.toLocaleString()} <span style="color:#7a8085">(${params.percent}%)</span></div>
|
|
`,
|
|
},
|
|
legend: {
|
|
orient: 'horizontal',
|
|
bottom: 0,
|
|
left: 'center',
|
|
itemGap: 18,
|
|
itemWidth: 8,
|
|
itemHeight: 8,
|
|
icon: 'circle',
|
|
textStyle: { fontSize: 12, color: '#4d5258' },
|
|
},
|
|
graphic: [
|
|
{
|
|
type: 'text',
|
|
left: 'center',
|
|
top: '38%',
|
|
style: {
|
|
text: '总产出',
|
|
fill: '#7a8085',
|
|
fontSize: 11,
|
|
fontFamily: "'Geist', 'PingFang SC', sans-serif",
|
|
fontWeight: 500,
|
|
},
|
|
},
|
|
{
|
|
type: 'text',
|
|
left: 'center',
|
|
top: '46%',
|
|
style: {
|
|
text: fmtCurrency(total.value),
|
|
fill: '#2d3033',
|
|
fontSize: 22,
|
|
fontWeight: 600,
|
|
fontFamily: "'JetBrains Mono', monospace",
|
|
},
|
|
},
|
|
],
|
|
series: [{
|
|
type: 'pie',
|
|
radius: ['58%', '78%'],
|
|
center: ['50%', '45%'],
|
|
avoidLabelOverlap: true,
|
|
itemStyle: {
|
|
borderColor: '#ffffff',
|
|
borderWidth: 2,
|
|
},
|
|
label: { show: false },
|
|
labelLine: { show: false },
|
|
emphasis: {
|
|
scale: true,
|
|
scaleSize: 6,
|
|
itemStyle: {
|
|
shadowBlur: 12,
|
|
shadowColor: 'rgba(0,0,0,0.10)',
|
|
},
|
|
},
|
|
data,
|
|
}],
|
|
};
|
|
});
|
|
|
|
const { chartRef } = useECharts(option);
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="chartRef" class="revenue-pie"></div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.revenue-pie { width: 100%; height: 320px; }
|
|
</style>
|