数据准确性 - 票数初始为 0,不再用 Math.sqrt 公式造假票 - 排序 tiebreaker 统一为 votes desc + no asc,确保稳定 - store.rank() 与 sortArtists() 行为对齐 Top12 / 出道位 - Top12Bar 仅收纳真正有票的人(votes > 0),0 票时显示"出道位尚未产生"空态 - ArtistCard / RankingRow / SearchModal / MyFanSupport / RankCard 的 inTop12 高亮全部加 votes > 0 守卫 - ArtistFilters 新增"实时排名 / 编号顺序"分段切换 + 首页 sortKey 状态 领奖台 (Top3Podium) - 1 人有票即可显示领奖台(此前要求 >= 3 人才显示) - 缺位的 #2/#3 用"虚位以待"占位卡片填充,与正式卡片同 3:4 比例对齐 - 全员 0 票时三张全部显示虚位以待 - 卡片间距拉大到 gap-8 sm:gap-12 排行榜页 (/ranking) - API 票数与本地乐观投票取 max() 合并,避免 API 落后覆盖本地新票 - 合并后重新排序与赋 rank - 仅 >= 12 人有票才显示出道线分隔与复活位标记 - 复活位 gapToDebut 计算修正 跨日额度 - selectRemaining 按 quotaDate 判断是否跨日,跨日自动回满额(此前会卡在昨日剩余值) 搜索 (SearchModal) - 改为订阅 store 拿活的票数,投票后立即反映 - 默认"热门 Top12"只在真正有票时显示,否则降级为"推荐艺人 · 编号顺序" - 票数显示统一走 formatVotes(0 票不再显示 0.0w) 人物详情 - 36 人真实数据接入,移除全部静态数据(slogan/birthday/cv/themeColor) - 接入人物小传 docx 数据(年龄/身高/性格/口头禅/技能/赛道/座右铭/长简介) - 视频区与版心同宽 + 首帧自动作为封面 + 整区点击播放/暂停 + 可拖拽进度条 - 表演图片改为三张氛围图竖向 3:4,左对齐 - 移除分享按钮,投票按钮全宽 个人页 (/me) - 移除等级/邀请好友/签到/编辑资料等静态数据 - 退出登录按钮在移动端 icon-only 显示(此前 sm:hidden 导致移动端无法登出) - 我的应援 list 基于真实 myVotesByArtist 派生,凯之类的投票真正同步过去 导航 - 余票徽章未登录态显示 0/0,已登录显示 N/10 - 登录/注册按钮样式与登录后头像胶囊保持一致(紫色实心) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CYBER ✦ STAR
虚拟偶像 Top12 出道企划 · 投票网页
技术栈
| 层 | 选型 |
|---|---|
| 框架 | Next.js 16(App Router)· React 19 · TypeScript |
| 样式 | Tailwind CSS v4(CSS-first @theme)· Framer Motion |
| 字体 | Megrim / Audiowide / Cinzel / Inter(全部 SIL OFL 商用免费) |
| 数据库 | MySQL 8 · Prisma 6 ORM |
| 缓存 / 限流 | Redis(ioredis,未配置时降级内存) |
| 身份 | Auth.js v5(手机号 OTP · 可扩展微信 / QQ) |
| 校验 | Zod |
| 部署 | 火山引擎 ECS · 火山引擎 TOS(对象存储) |
本地启动
pnpm install # 安装依赖(自动 prisma generate)
cp .env.example .env # 配置环境变量(首次)
pnpm dev # http://localhost:3000
未配置数据库时,前端页面会回退到 src/lib/mock-data.ts 的 35 位艺人 mock 数据,UI 完全可用。
数据库初始化(部署时)
# 1. 创建 MySQL 库 + 在 .env 设置 DATABASE_URL
# 2. 推送 schema 到数据库
pnpm db:push
# 3. 灌入 35 位艺人 + 活动配置
pnpm db:seed
# 可视化管理(可选)
pnpm db:studio
关键脚本
| 命令 | 说明 |
|---|---|
pnpm dev |
开发服务器(Turbopack) |
pnpm build |
生产构建(先 prisma generate) |
pnpm start |
生产服务器 |
pnpm lint |
ESLint |
pnpm db:push |
把 schema 推到数据库 |
pnpm db:migrate |
生成迁移脚本 |
pnpm db:seed |
灌入种子数据 |
pnpm db:studio |
Prisma Studio(GUI) |
项目结构
prisma/
schema.prisma # 数据库 schema(10 个模型)
seed.ts # 初始化 35 位艺人
src/
app/
layout.tsx # 根布局(含氛围装饰层)
page.tsx # 首页(Hero PV + Top12 + 35 卡片)
artist/[id]/ # 艺人详情页
ranking/ # 排行榜(Top3 podium + 出道线 + 候补区)
me/ # 个人中心
login/ # 登录页(手机号 OTP)
api/
artists/ # GET 艺人列表 / 单个详情
ranking/ # GET 实时排名
vote/ # POST 投票(含风控限流 + 事务)
me/ # GET 当前用户 / POST 签到
auth/ # NextAuth handlers + send-otp
components/
Logo / Navigation / Footer / NavLinks
HeroBanner / Top12Bar / ArtistFilters
VoteModal / FloatingVoteButton / LiveBadge
ui/ # Button / Countdown
cards/ # ArtistCard / ArtistPortrait
artist/ # 详情页组件(视频 / 画廊 / 排名卡等)
ranking/ # 排行榜组件
me/ # 个人中心组件
hooks/
useRanking.ts # 实时排名轮询 hook
lib/
prisma / redis / rate-limit
auth / current-user / api-response
cn / mock-data / mock-user
types/
artist.ts
设计资料
- 交互原型:
../交互原型线框图.html - 视觉规范:
../视觉规范.html· v1.1 紫调主导 - 需求文档:
../需求分析文档.md - 参考视觉:
../参考图.png
团队需配置的外部服务
| 服务 | 用途 | 环境变量 |
|---|---|---|
| MySQL(火山 RDS) | 主数据库 | DATABASE_URL |
| Redis(火山实例) | 限流 / OTP 缓存 / 实时聚合 | REDIS_URL |
| TOS(火山对象存储) | 立绘 / 视频 / 头像 | TOS_* |
| 短信网关 | 手机号 OTP | SMS_* |
| 微信开放平台 | 微信扫码登录 | WECHAT_APP_ID WECHAT_APP_SECRET |
| hCaptcha | 反作弊验证码 | HCAPTCHA_* |
详见 .env.example。所有这些都用 TODO 注释标记在代码中,可灰度配置 —— 没配置时功能自动降级。
开发态便利
- 登录 :
/login页面下,开发环境接受万能验证码123456 - mock 用户 :API 调用前自动落到
cs_user_idcookie,可在开发面板设置 - mock 数据 :未配 DB 时前端自动使用
mock-data.ts的 35 位艺人 - 实时排名 :API 不可用时静默回退到 mock 数据
设计原则
参考 ../视觉规范.html 的 16 个章节,核心:
- 紫罗兰为主调色(不是蓝),承担 CTA / 激活态 / 描边
- 装饰星标 ✦ 用于 Logo 中间,体现品牌签
- TOP12 头像方形圆角(不是圆形)— "明星卡片"质感
- VOTE NOW 紫色侧栏面板(不是普通按钮)— 视觉锚点
- 背景始终在深紫黑系,星点 + 紫雾装饰层
- 投票交互必须有动画仪式感
Description
Languages
TypeScript
82.5%
JavaScript
12.7%
CSS
2.3%
Shell
1.6%
Dockerfile
0.9%