ARTICLE · 1299855
AiTrader助手策略看板前端实现
前两篇聊了 AiTrader 策略助手的设计思路和后端实现,这篇轮到前端。说实话,前端这块写起来比我预想的麻烦。
麻烦在哪?数据量不算大,但维度很杂:长线看日线,覆盖 30 个资产;中线看 4 小时,4 个资产;短线看 1 小时,也是 4 个。交易员希望一眼看完,不想来回切页面。后台那种一行行的表格肯定不行,看久了眼花,关键信号还容易看漏。
下面按我们实际做的顺序讲:先是路由怎么挂,再是几个主要区块,最后是上线前踩的两个坑。坑那部分我觉得最有参考价值,赶时间的可以直接跳过去。
一、路由:挂在仪表盘下面
我们项目的路由是混合模式,菜单权限由后端下发,页面骨架在前端静态注册。看板放在「仪表盘」下面,跟工作台、BTC ETF、WLFI 几个看板并排。
在 router/routes.ts 的 dashboardLayoutChildren 里加一条就行:
1 2 3 4 5 6 7 8 9 10 { path: "assistant", name: "DashboardAssistant", component: () => import("@views/dashboard/assistant/index.vue"), meta: { title: "menus.dashboard.assistant", icon: "ri:robot-2-line", keepAlive: true, },}, keepAlive 记得打开。交易员经常在几个看板之间来回切,每次切回来都重新请求一遍,体验很差。
组件分了两层。外面那层 index.vue 只干一件事:挂载组件,顺便定个 name,不然 keepAlive 认不出来。
1 2 3 <template> <AssistantDashboard /></template> 真正的逻辑都在 AssistantDashboard.vue 里:拉数据、筛选、渲染,全在这。这么拆的好处是,以后这个看板要搬到别的地方用,外壳换掉就行。
二、页面上的几个区块
从上往下大概五块,挑几块说。
1. 顶部状态栏
最上面放了一个小绿点,用 Tailwind 的 animate-pulse 让它一闪一闪,旁边写着最后更新时间。
1 2 <span class="inline-block w-2 h-2 rounded-full bg-emerald-500 animate-pulse" /><span>更新时间: {{ overviewData?.updated_at }}</span> 这个点看着不起眼,但挺管用。后端是定时任务在跑,任务挂了的话时间就不动了,交易员扫一眼就知道数据是不是新的。
旁边还有个「同步策略数据」按钮,用 ElDropdown 做成下拉,可以选同步全部,或者只同步长线(每天 08:00)、中线(每 4 小时)、短线(每 1 小时)。点了之后后端异步去采,按钮上转个 loading。
2. 三个周期的概览卡片
1D、4H、1H 各一张卡片,主要显示多头占比和下次更新的倒计时。多空占比用一根条来画,底色是淡红,上面盖一层绿,绿色宽度就是多头百分比:
1 2 3 4 5 6 <div class="w-full h-2 rounded-full bg-rose-500/20 overflow-hidden flex"> <div class="h-full bg-emerald-500 transition-all duration-500" :style="{ width: `${overviewData.summaries.long.bull_ratio}%` }" /></div> 加了 transition,数据刷新的时候条会滑过去,不是一下跳过去,看着舒服一点。
3. 共振矩阵
这是整个看板最核心的部分。BTC、ETH、LTC、XRP 四个币,每个一张卡片,里面横着排日线、4H、1H 三个周期,每个周期写清楚方向、参考价、生效时间。
卡片的边框和背景色跟着共振状态变。写了个函数,按状态返回不同的 class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const getResonanceCardClass = (status: string) => { switch (status) { case "STRONG_BULL": // 三个周期都看多,绿 return "border-emerald-500/40 bg-emerald-50/10 dark:bg-emerald-950/20"; case "STRONG_BEAR": // 三个周期都看空,红 return "border-rose-500/40 bg-rose-50/10 dark:bg-rose-950/20"; case "BULL_PULLBACK": // 长多短空,算回调,蓝 return "border-indigo-500/30 bg-indigo-50/10 dark:bg-indigo-950/20"; case "BEAR_REBOUND": // 长空短多,算反弹,紫 return "border-purple-500/30 bg-purple-50/10 dark:bg-purple-950/20"; default: return "border-slate-200 dark:border-slate-800 bg-slate-50/30 dark:bg-slate-900/30"; }}; 颜色我们调了好几轮。一开始透明度给得太高,四张卡片一亮起来,整屏花花绿绿,反而看不出重点。后来压到 /10、/20 这个程度,只是淡淡一层底色,边框稍微明显些,刚好。
4. 长线 30 个资产的列表
30 张卡片排成网格,上面有多空筛选和币种搜索。数据一次全拿回来了,筛选直接在前端用 computed 做,不用再请求后端:
1 2 3 4 5 6 7 8 9 10 11 12 const filteredLongList = computed(() => { if (!overviewData.value?.long_list) return []; return overviewData.value.long_list.filter((item: StrategyItem) => { if (filterAction.value !== "all" && item.action.toLowerCase() !== filterAction.value) { return false; } if (searchSymbol.value.trim()) { return item.symbol.toLowerCase().includes(searchSymbol.value.trim().toLowerCase()); } return true; });}); 30 条数据,这么写完全够用,输入的时候基本感觉不到延迟。
三、踩过的两个坑
坑一:文字被卡片底边切掉半行
第一版里,每张资产卡片下面有一段策略提示,我图省事加了 line-clamp-2,只显示两行:
1 2 3 <p class="text-[11px] text-slate-500 mt-2 line-clamp-2 bg-slate-50 p-1.5 rounded"> {{ item.action_details }}</p> 结果有些提示写得很长,比如「价格若在 2026/10/12 08:00:00 低于 162.73 USDT,长线助手预测出现下跌趋势,可考虑卖出。」这种,要折三行。卡片又没设最小高度,第三行正好被下边框拦腰截断,露出半截字。
难看是一回事,更要命的是被截掉的恰好是价格。交易员要的就是这个数。
改法分两步:
第一,卡片统一给个最小高度 min-h-[195px],再加 flex flex-col justify-between。这样不管内容长短,一行五张卡片高度都是齐的,底部的收益率也能对齐。
第二,去掉 line-clamp-2,行高固定成 leading-[18px],文字容器加 flex-1 让它自己撑开,三行就完整显示三行。顺手加了个 title,鼠标放上去能看全文。
改完的结构大概是这样(省略了部分内容):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <div class="symbol-card p-3.5 rounded-xl border flex flex-col justify-between min-h-[195px]"> <div class="flex flex-col flex-1"> <div class="flex items-center justify-between">...</div> <div class="mt-2.5 flex items-baseline justify-between text-xs">...</div> <div class="text-[11px] leading-[18px] mt-2.5 p-2 rounded-lg flex-1 flex items-center" :title="item.action_details || ''" > <span>{{ item.action_details || '暂无详细提示' }}</span> </div> </div> <div class="mt-3 pt-2.5 border-t text-[11px] flex items-center justify-between"> <span>12M 策略收益:</span> <span class="font-mono font-bold">...</span> </div></div> 坑二:价格后面一长串小数
后端给的阈值价是回测算出来的,精度高得离谱。AAVE 是 162.72552341327236,ADA 是 0.22372494668304982。原来的 formatPrice 只处理了大于 1000 的情况,小于 1000 就原样输出,一串数字把右边挤得满满当当。
后来按价格大小分了三档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const formatPrice = (val?: string | null) => { if (!val) return "-"; const num = Number(val); if (isNaN(num)) return val; if (num >= 1000) { // 比如 $65,230.50 return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } else if (num >= 1) { // 比如 $162.73 return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 4 })}`; } else { // 比如 $0.2237 return `$${num.toLocaleString("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 6 })}`; }}; 为什么小于 1 的要留四到六位?因为很多小币本身就几分钱,只留两位的话,ADA 就成了 $0.22,跟 0.2237 差了不少,阈值价这么显示是要出问题的。
改完之后,BTC 这种几万刀的和几毛钱的小币放在一起,都能显示得比较干净。
四、最后
回头看,这个看板前端没用什么特别的技术,Vue 3 加 Tailwind,大部分人都会。真正花时间的是那些细节:颜色深一点浅一点,文字截不截断,小数留几位。每一处都不大,但交易员每天要盯着看好几个小时,这些地方不顺手,用起来就很难受。
到这里,策略助手看板的设计、后端、前端三篇就写完了。有在做类似东西的朋友,欢迎留言交流,也欢迎拍砖。















































































