№ · 活跃设计 CH.06.29
主页 · 📐 活跃设计 · 《同福客栈》Phase 1 实施切片 v1.0

《同福客栈》Phase 1 实施切片 v1.0

📅 2026-06-29 · 📦 34.0 KB · 📐 活跃设计 · 📝 MD 文档 🏷️ 装备🏷️ 设计🏷️ Phase1🏷️ 词条🏷️ 规范🏷️ 品质

设计 → 代码的施工蓝图

基于:《道具词条与玩法扩展 v3.1.4》+《道具系统新核心 UI 视觉规范 v1.0》+《道具系统新核心 UI 视觉稿 v1.0》 范围:Phase 1 = 品质重构 + 词条区间(不包含 §11 槽位 / §12 攻防 / §13 战力) 工期:5 个工作日(每个 T 半天到 1 天,可并行)


0. 元信息

| 项 | 内容 | | :--- | :--- | | 文档版本 | v1.0 / 2026-06-29 | | 配套文档 | v3.1.4 机制 / 视觉规范 v1.0 / 视觉稿 v1.0 | | 范围(做) | 品质 4 档→9 档重构;词条从字符串 ID 升级到词条库+区间+roll | | 范围(不做) | §11 槽位 / §12 攻防 / §13 战力(Phase 2);强化/精造等 8 小系统(Phase 3+) | | 完成标准 | npm run lint 无新增错误;npm run build 通过;现有游戏功能不破坏;9 档品质+词条标签在背包 UI 可见 |

核心原则

  • 每个 T = 1 个 commit / 1 个 PR 颗粒度
  • 每个 T 完成后游戏必须能跑、可回滚
  • 软迁移(保留旧字段 + 加 TODO)—— Phase 2 才彻底删除 rarity 等旧字段

1. 现状盘点

1.1 现有品质系统

| 项 | 现状 | 跟设计差距 | | :--- | :--- | :--- | | TS 类型 | rarity: 'common' \| 'rare' \| 'epic' \| 'legendary'src/types.ts:137) | 4 档,需扩到 9 档 | | CSS 类 | .rarity-common / .rarity-rare / .rarity-epic / .rarity-mysticsrc/index.css) | 4 档 + mystic 私货,需统一到 9 档 | | 品级放大系数 | 无 | 需新增 1.0x→6.0x 表 | | 品质色阶规范 | 无正式规范 | 套用视觉规范 v1.0 的 6 档 + 扩 3 档 |

1.2 现有词条系统

| 项 | 现状 | 跟设计差距 | | :--- | :--- | :--- | | TS 字段 | prefixId?: string; suffixId?: string;src/types.ts:140-141) | 仅字符串 ID,无数值/区间/类型 | | 词条库 | 无集中定义 | 需新建 src/data/affixes.ts | | 词条类型分类 | 无 | 需分 N/P/S/K/T 5 类(v3.1.4 §3.1) | | 数值区间 | 无 | 需新增区间常量 | | rollValue | 无 | 需新增 0.0~1.0 字段 | | 词条权 | 无 | 需新增 N 1.0 / P 1.5 / S 2.0 / K 2.5 / T 1.0 |

1.3 现有装备模型

  • 文件:src/types.ts:133-151
  • value: number —— 单一数值,无攻防分离(Phase 2 处理)
  • buildTag?: 'chef' \| 'management' \| 'adventure' \| 'defense' —— 已存在但单选
  • 无强化/精造字段
  • 无品质数值化(品级放大)

1.4 现有 gameData

  • 文件:不在 src/,在工作区根 同福客栈挂机游戏/gameData.ts(历史遗留)
  • 装备示例:~30 件,覆盖 weapon / accessory / ingredient / book
  • 全部用旧 4 档 rarity,无 affix / qualityTier / baseStats

1.5 现有拟物化体系(可复用)

| 类名 | 用途 | 设计对应 | | :--- | :--- | :--- | | .tongfu-parchment | 宣纸底 | 装备卡底色(视觉规范 §3.1) | | .tongfu-parchment-light | 浅宣纸 | 详情面板(视觉规范 §4.2) | | .tongfu-button-wooden | 木质按钮 | 槽位按钮(Phase 2) | | .tongfu-seal | 朱砂印章 | 装备卡"印"标签(视觉规范 §3.6) | | .tongfu-crest | 徽章 | 品质徽章(视觉规范 §3.6) |

拟物化体系已就位——宣纸+木+朱砂调性跟视觉规范 v1.0 完全对齐,Phase 1 直接复用,不重做。


2. 8 个任务切片(核心交付物)

| # | 任务 | 工期 | 风险 | 依赖 | | :---: | :--- | :---: | :---: | :---: | | T1 | 品质 9 档系统(类型 + 常量 + 放大系数) | 0.5 天 | 低 | — | | T2 | 词条系统(类型 + 词条库 ~40 条) | 0.5 天 | 低 | — | | T3 | 装备模型字段补齐(EquippableItem v2 + gameData 迁移) | 1 天 | 中 | T1, T2 | | T4 | 9 档色板落地(index.css + @theme 变量) | 0.5 天 | 低 | T1 | | T5 | 装备卡基础组件(QualityFrame + AffixChip) | 1 天 | 低 | T1, T2 | | T6 | 装备卡 UI 升级(沿用现有 BaggageTab 改造) | 0.5 天 | 中 | T3, T4, T5 | | T7 | rollEquipment 工具函数(生成新装备) | 0.5 天 | 低 | T2, T3 | | T8 | 回归测试 + 视觉对照 checklist | 0.5 天 | 低 | T1-T7 |

总工期:5 个工作日
关键路径:T1 → T3 → T6 → T8
可并行:T1+T2 并行;T4+T5 并行(与 T3 同步)


3. 任务依赖图

                    ┌──> T1 (品质类型)  ──┐
                    │                       │
T0 (现状) ───────┬──┤                       ├──> T3 (装备模型) ──┐
                  │  └──> T2 (词条类型)  ──┘                    │
                  │                                              │
                  │   T4 (色板)  ──┐                            │
                  │                 ├──> T5 (基础组件)  ─────┐   │
                  │   T2  ─────────┘                          │   │
                  │                                            ├──> T6 (装备卡 UI) ──> T8 (回归)
                  │                                            │                          ▲
                  │                                                                    │
                  │                                            T7 (工具函数)  ──────────┘
                  │
                  └──> T8 (回归测试,最后跑)

4. 任务详细规格

T1. 品质 9 档系统(类型 + 常量)

目标:把 rarity 从 4 档扩到 9 档,定义品级放大系数表。

改动文件

  • src/types.ts —— 新增 QualityTier 类型
  • src/data/quality.ts(新) —— 9 档常量 + 放大系数 + 元数据

新增类型src/types.ts):

/**
 * 品质 9 档(Phase 1 重构引入)
 * 旧 'common' | 'rare' | 'epic' | 'legendary' 保留做软迁移,Phase 2 删除
 */
export type QualityTier =
  | 'waste'       // Q1 白板
  | 'common'      // Q2 普通
  | 'fine'        // Q3 优秀
  | 'rare'        // Q4 稀有
  | 'epic'        // Q5 史诗
  | 'legend'      // Q6 传说
  | 'mythic'      // Q7 神话
  | 'divine'      // Q8 神器
  | 'celestial';  // Q9 不朽

⚠️ 注意:common 名字复用,但语义变了(Q2 普通 vs 旧 common)。下游需要按 import path 区分。

新增常量src/data/quality.ts):

import type { QualityTier } from '@/types';

/**
 * 品质档位元数据(Phase 1)
 * 与 v3.1.4 §2 品质体系 + 视觉规范 v1.0 §2.1 色组对应
 */
export const QUALITY_TIERS: Record<QualityTier, {
  /** 顺序(1-9) */
  rank: number;
  /** 中文名 */
  name: string;
  /** 单字徽章(显示在 tongfu-seal 上) */
  shortName: string;
  /** 品级放大系数(v3.1.4 §13 战力公式用) */
  multiplier: number;
  /** 对应 CSS 变量(@theme --color-q-*) */
  cssVar: string;
  /** 默认词条数 */
  affixCount: number;
  /** rollValue 区间(0.0~1.0,决定词条数值倍率) */
  affixRoll: [number, number];
}> = {
  waste:      { rank: 1, name: '白板', shortName: '白', multiplier: 1.0, cssVar: '--color-q-waste',     affixCount: 0, affixRoll: [0.0, 0.0] },
  common:     { rank: 2, name: '普通', shortName: '普', multiplier: 1.3, cssVar: '--color-q-common',    affixCount: 1, affixRoll: [0.0, 0.3] },
  fine:       { rank: 3, name: '优秀', shortName: '优', multiplier: 1.7, cssVar: '--color-q-fine',      affixCount: 1, affixRoll: [0.3, 0.5] },
  rare:       { rank: 4, name: '稀有', shortName: '稀', multiplier: 2.2, cssVar: '--color-q-rare',      affixCount: 2, affixRoll: [0.4, 0.6] },
  epic:       { rank: 5, name: '史诗', shortName: '史', multiplier: 2.9, cssVar: '--color-q-epic',      affixCount: 2, affixRoll: [0.5, 0.7] },
  legend:     { rank: 6, name: '传说', shortName: '传', multiplier: 3.8, cssVar: '--color-q-legend',    affixCount: 3, affixRoll: [0.6, 0.8] },
  mythic:     { rank: 7, name: '神话', shortName: '神', multiplier: 4.7, cssVar: '--color-q-mythic',    affixCount: 3, affixRoll: [0.7, 0.9] },
  divine:     { rank: 8, name: '神器', shortName: '器', multiplier: 5.5, cssVar: '--color-q-divine',    affixCount: 4, affixRoll: [0.8, 1.0] },
  celestial:  { rank: 9, name: '不朽', shortName: '朽', multiplier: 6.0, cssVar: '--color-q-celestial', affixCount: 4, affixRoll: [0.9, 1.0] },
};

/** 按 rank 排序的品质数组(用于色板对照 / 下拉选择) */
export const QUALITY_ORDER: QualityTier[] = [
  'waste', 'common', 'fine', 'rare', 'epic',
  'legend', 'mythic', 'divine', 'celestial',
];

/** 工具:按 rank 取品质 */
export function getQualityRank(tier: QualityTier): number {
  return QUALITY_TIERS[tier].rank;
}

验收

  • npm run lint 通过
  • 旧的 EquippableItem.rarity 字段引用不报错(这版本不改 EquippableItem,T3 才动)
  • 新增类型 QualityTier 在 storybook/test 页可用

T2. 词条系统(类型 + 词条库)

目标:定义词条类型 + 首批 ~40 个词条(覆盖 5 大类型 + 4 大流派)。

改动文件

  • src/types.ts —— 新增 Affix / AffixKind
  • src/data/affixes.ts(新) —— 首批 ~40 个词条

新增类型src/types.ts):

/**
 * 词条类型(v3.1.4 §3.1)
 * N = Numeric 数值类(+X 直接加成)
 * P = Percentage 百分比(+X% 比率加成)
 * S = Special 特殊(独立机制,如铜臭/旺财)
 * K = Key 关键(重要标识,如传承/好运)
 * T = Triggered 触发(条件触发,如怒意/韧性)
 */
export type AffixKind = 'N' | 'P' | 'S' | 'K' | 'T';

/** 词条权(v3.1.4 §13 战力公式用) */
export const AFFIX_WEIGHT: Record<AffixKind, number> = {
  N: 1.0,
  P: 1.5,
  S: 2.0,
  K: 2.5,
  T: 1.0,
};

/** 词条影响的统计字段 */
export type AffixStat =
  // 攻防效(Phase 2 §12 消费)
  | 'atk' | 'def' | 'eff'
  // 员工生命
  | 'hp' | 'stamina' | 'mood'
  // 收益
  | 'coinBonus' | 'repBonus'
  // 战斗(未来)
  | 'critRate' | 'critDmg' | 'dodgeRate' | 'speed';

/**
 * 词条定义(词条库里的"模板")
 * 实例化时需要 rollValue(决定具体数值)+ 区间
 */
export interface Affix {
  id: string;                   // 词条 ID,如 'affix_atk_flat'
  kind: AffixKind;              // 类型
  stat: AffixStat;              // 影响字段
  name: string;                 // 中文名,如 "锋利"
  desc: string;                 // 描述模板(含 {value} 占位)
  valueRange: [number, number]; // 数值区间下限/上限
  valueUnit: 'flat' | 'pct';     // flat 加数值 / pct 加百分比
  weight: number;               // 抽取权重(默认 1,越大越容易出)
  tags?: ('chef' | 'management' | 'adventure' | 'defense')[]; // 流派过滤
}

/**
 * 实例化后的词条(装备上的"具体词条")
 * 带 rollValue(0.0~1.0 决定数值倍率)和最终数值
 */
export interface RolledAffix extends Affix {
  rollValue: number;            // 0.0~1.0
  rolledValue: number;          // 实际数值 = lerp(valueRange[0], valueRange[1], rollValue)
}

首批词条库src/data/affixes.ts)—— ~40 个词条,分 5 类 + 4 流派 tag:

N 类(数值,10 条)

{ id: 'affix_atk_flat', kind: 'N', stat: 'atk', name: '锋利', desc: '攻击 +{value}',
  valueRange: [5, 15], valueUnit: 'flat', weight: 1.0, tags: ['chef', 'defense'] },
{ id: 'affix_def_flat', kind: 'N', stat: 'def', name: '坚固', desc: '防御 +{value}',
  valueRange: [3, 10], valueUnit: 'flat', weight: 1.0, tags: ['defense'] },
{ id: 'affix_eff_flat', kind: 'N', stat: 'eff', name: '麻利', desc: '效率 +{value}',
  valueRange: [2, 8], valueUnit: 'flat', weight: 1.0, tags: ['chef', 'management'] },
{ id: 'affix_hp_flat', kind: 'N', stat: 'hp', name: '血气', desc: '生命 +{value}',
  valueRange: [10, 30], valueUnit: 'flat', weight: 1.0, tags: ['adventure', 'defense'] },
{ id: 'affix_stamina_flat', kind: 'N', stat: 'stamina', name: '耐力', desc: '体力 +{value}',
  valueRange: [5, 15], valueUnit: 'flat', weight: 1.0, tags: ['adventure'] },
// ... 还有 5 条:好运/巧手/精准/敏捷/沉稳

P 类(百分比,10 条)

{ id: 'affix_atk_pct', kind: 'P', stat: 'atk', name: '凶猛', desc: '攻击 +{value}%',
  valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['chef', 'defense'] },
{ id: 'affix_def_pct', kind: 'P', stat: 'def', name: '坚韧', desc: '防御 +{value}%',
  valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['defense'] },
{ id: 'affix_speed_pct', kind: 'P', stat: 'speed', name: '疾风', desc: '速度 +{value}%',
  valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['chef', 'management'] },
// ... 还有 7 条

S 类(特殊,8 条)

{ id: 'affix_coin_flat', kind: 'S', stat: 'coinBonus', name: '铜臭', desc: '铜钱/时辰 +{value}',
  valueRange: [10, 30], valueUnit: 'flat', weight: 1.0, tags: ['management'] },
{ id: 'affix_coin_pct', kind: 'S', stat: 'coinBonus', name: '旺财', desc: '收益 +{value}%',
  valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['management'] },
// ... 还有 6 条

K 类(关键,6 条)

{ id: 'affix_journey_xp', kind: 'K', stat: 'repBonus', name: '传承', desc: '阅历/天 +{value}',
  valueRange: [1, 3], valueUnit: 'flat', weight: 1.0 },
{ id: 'affix_lucky', kind: 'K', stat: 'critRate', name: '好运', desc: '暴击率 +{value}%',
  valueRange: [3, 8], valueUnit: 'pct', weight: 1.0 },
// ... 还有 4 条

T 类(触发,6 条)

{ id: 'affix_fury', kind: 'T', stat: 'atk', name: '怒意', desc: 'HP<30% 时攻击 +{value}%',
  valueRange: [10, 30], valueUnit: 'pct', weight: 1.0, tags: ['defense', 'adventure'] },
{ id: 'affix_resilience', kind: 'T', stat: 'def', name: '韧性', desc: '受击时减伤 +{value}%',
  valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['defense'] },
// ... 还有 4 条

汇总:N 10 + P 10 + S 8 + K 6 + T 6 = 40 个词条

验收

  • npm run lint 通过
  • src/data/affixes.ts 至少 40 个词条
  • 区间合理:数值类 ±50%、百分比类 ±5%
  • 每个词条都有 id / kind / stat / name / desc / valueRange / valueUnit / weight

T3. 装备模型字段补齐(EquippableItem v2)

目标:在 EquippableItem 上加 qualityTier / affixes / baseStats / 升级 buildTag

改动文件

  • src/types.ts —— 修改 EquippableItem 字段
  • 同福客栈挂机游戏/gameData.ts(根目录历史遗留) —— 迁移 ~30 件装备

修改后src/types.ts,行 133-151):

export interface EquippableItem {
  kind?: 'equipment';
  id: string;
  name: string;
  /** 新:品质 9 档(Phase 1 引入,替代旧的 rarity) */
  qualityTier: QualityTier;
  /** 旧:保留做软迁移,Phase 2 删除(标 TODO) */
  // TODO: Phase 2 删除
  rarity: 'common' | 'rare' | 'epic' | 'legendary';

  type: 'weapon' | 'accessory' | 'ingredient' | 'book';
  baseId: string;
  prefixId?: string;
  suffixId?: string;

  /** 新:实例化词条(0~4 个) */
  affixes: RolledAffix[];
  /** 新:基础三维(Phase 2 §12 消费) */
  baseStats: {
    atk?: number;
    def?: number;
    eff?: number;
  };
  /** 新:单一数值保留(兼容旧显示) */
  stat: 'cookingSpeed' | 'cleanPower' | 'security' | 'incomeMultiplier' | 'passiveIncome';
  value: number;
  level: number;

  /** 升级:buildTag 改多选 */
  buildTag: ('chef' | 'management' | 'adventure' | 'defense')[];

  recommendedStaff?: StaffId[];
  equippedTo?: StaffId | null;
  flavorText: string;
  isSkillScroll?: boolean;
  isLocked?: boolean;
}

gameData.ts 迁移示例(每件装备补 4 个字段):

// 旧:
{ id: 'wok_iron', name: '铁锅', rarity: 'common', type: 'weapon', value: 50, ... }

// 新(保留 rarity,加 qualityTier/affixes/baseStats/buildTag 数组):
{
  id: 'wok_iron', name: '铁锅',
  rarity: 'common',                     // 保留软迁移
  qualityTier: 'common',                // 新
  affixes: [                            // 新:示例词条
    { id: 'affix_atk_flat', kind: 'N', stat: 'atk', name: '锋利', desc: '...',
      valueRange: [5,15], valueUnit: 'flat', weight: 1,
      rollValue: 0.4, rolledValue: 9 },
  ],
  baseStats: { atk: 12, def: 2, eff: 5 },  // 新
  // ... 其他字段
  buildTag: ['chef', 'management'],         // 升级为数组
}

30 件装备迁移策略

  • 白板装备(10 件):qualityTier: 'waste'affixes: []
  • 普通装备(10 件):qualityTier: 'common',1 个词条
  • 优秀装备(5 件):qualityTier: 'fine',1 个词条(rollValue 偏高)
  • 稀有装备(3 件):qualityTier: 'rare',2 个词条
  • 史诗装备(2 件):qualityTier: 'epic',2 个词条

验收

  • npm run lint 通过
  • 30 件装备全部迁移
  • 打开游戏 → 背包能正常显示(旧字段仍在,不报错)

T4. 9 档色板落地(index.css)

目标:把视觉规范 v1.0 §2.1 的 6 档色 + 扩 3 档,落到 CSS @theme

改动文件

  • src/index.css —— 新增 @theme 变量 + 9 档品质类 + 不朽档专属动效

新增 CSS(追加到 @theme {} 块后):

@theme {
  /* ── 9 档品质色(与 src/data/quality.ts 对应)── */
  --color-q-waste: #9ca3af;        /* Q1 白板:冷灰 */
  --color-q-common: #6b7280;       /* Q2 普通:暖灰 */
  --color-q-fine: #84cc16;         /* Q3 优秀:黄绿 */
  --color-q-rare: #06b6d4;         /* Q4 稀有:青蓝 */
  --color-q-epic: #a855f7;         /* Q5 史诗:紫 */
  --color-q-legend: #f59e0b;       /* Q6 传说:金 */
  --color-q-mythic: #ef4444;       /* Q7 神话:朱红 */
  --color-q-divine: #ec4899;       /* Q8 神器:洋红 */
  --color-q-celestial-from: #fbbf24;
  --color-q-celestial-mid: #ef4444;
  --color-q-celestial-to: #a855f7;
}

/* ── 9 档品质通用类(与视觉规范 v1.0 §2.1 对照) ── */
.rarity-waste    { color: var(--color-q-waste);    border-color: var(--color-q-waste); }
.rarity-common   { color: var(--color-q-common);   border-color: var(--color-q-common); }
.rarity-fine     { color: var(--color-q-fine);     border-color: var(--color-q-fine); }
.rarity-rare     { color: var(--color-q-rare);     border-color: var(--color-q-rare); }
.rarity-epic     { color: var(--color-q-epic);     border-color: var(--color-q-epic); }
.rarity-legend   { color: var(--color-q-legend);   border-color: var(--color-q-legend); }
.rarity-mythic   { color: var(--color-q-mythic);   border-color: var(--color-q-mythic); }
.rarity-divine   { color: var(--color-q-divine);   border-color: var(--color-q-divine); }

/* 不朽档(Q9)专属:金光渐变 + 脉动 */
.rarity-celestial {
  background: linear-gradient(
    135deg,
    var(--color-q-celestial-from) 0%,
    var(--color-q-celestial-mid) 50%,
    var(--color-q-celestial-to) 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  border-color: var(--color-q-celestial-from);
  font-weight: 800;
}

@keyframes celestial-shimmer {
  0%, 100% { filter: brightness(1.0) drop-shadow(0 0 4px rgba(251,191,36,0.4)); }
  50%      { filter: brightness(1.2) drop-shadow(0 0 12px rgba(251,191,36,0.8)); }
}
.rarity-celestial-card {
  animation: celestial-shimmer 3s ease-in-out infinite;
}

/* ── 旧 4 档保留(兼容性,Phase 2 才删) ── */
.rarity-common-legacy { /* 旧 common 灰,保持原样 */ }
.rarity-rare-legacy    { /* 旧 rare 蓝,保持原样 */ }
.rarity-epic-legacy    { /* 旧 epic 紫,保持原样 */ }
.rarity-mystic         { /* 保留特殊 mystic(金光脉动) */ }

验收

  • 在 dev/storybook 页用 9 个 <div class="rarity-{tier}">测试</div> 验证色对
  • 不朽档有金光渐变 + 脉动
  • 旧的 .rarity-common 等不报错(兼容)

T5. 装备卡基础组件(QualityFrame + AffixChip)

目标:抽出 2 个新组件,可复用到装备卡 / 详情面板 / 列表。

新建文件

  • src/components/item/QualityFrame.tsx —— 品质外框
  • src/components/item/AffixChip.tsx —— 词条标签
  • src/components/item/index.ts —— 统一导出

QualityFrame.tsx(对应视觉规范 §3.1 装备卡组件):

import type { ReactNode } from 'react';
import { QUALITY_TIERS, type QualityTier } from '@/data/quality';

interface Props {
  tier: QualityTier;
  children: ReactNode;
  className?: string;
  /** 是否启用卡片级脉动(仅不朽档默认开) */
  shimmer?: boolean;
}

export const QualityFrame: React.FC<Props> = ({
  tier,
  children,
  className = '',
  shimmer,
}) => {
  const meta = QUALITY_TIERS[tier];
  const enableShimmer = shimmer ?? tier === 'celestial';

  return (
    <div
      className={[
        'relative rounded-lg border-2 p-2 transition-shadow',
        `rarity-${tier}`,
        enableShimmer ? 'rarity-celestial-card' : '',
        className,
      ].filter(Boolean).join(' ')}
      data-tier={tier}
      data-quality-rank={meta.rank}
    >
      {/* 品质徽章:右上角小印 */}
      <span
        className="tongfu-seal absolute -top-2 -right-2 z-10"
        title={meta.name}
        aria-label={`品质 ${meta.name}`}
      >
        {meta.shortName}
      </span>

      {children}
    </div>
  );
};

AffixChip.tsx(对应视觉规范 §3.5 词条标签):

import type { Affix, RolledAffix, AffixKind } from '@/types';

const KIND_STYLE: Record<AffixKind, string> = {
  N: 'bg-slate-100 text-slate-700 border-slate-300',     // 数值:素
  P: 'bg-cyan-100 text-cyan-800 border-cyan-300',        // 百分比:冷
  S: 'bg-amber-100 text-amber-800 border-amber-300',     // 特殊:暖
  K: 'bg-purple-100 text-purple-800 border-purple-300',  // 关键:贵
  T: 'bg-rose-100 text-rose-800 border-rose-300',        // 触发:烈
};

interface Props {
  affix: Affix | RolledAffix;
  /** 实例化后的实际数值(RollAffix.rolledValue) */
  rolledValue?: number;
  compact?: boolean;  // 紧凑模式(卡片内用)
}

export const AffixChip: React.FC<Props> = ({ affix, rolledValue, compact = false }) => {
  const value = rolledValue ?? (affix as RolledAffix).rolledValue;
  const display = affix.valueUnit === 'pct'
    ? `+${(value * 100).toFixed(1)}%`
    : `+${value.toFixed(0)}`;

  return (
    <span
      className={[
        'inline-flex items-center gap-1 rounded border font-medium',
        compact ? 'text-[10px] px-1 py-0' : 'text-xs px-1.5 py-0.5',
        KIND_STYLE[affix.kind],
      ].join(' ')}
      data-affix-id={affix.id}
      data-affix-kind={affix.kind}
    >
      <span className="font-bold">{affix.name}</span>
      <span className="opacity-80">{display}</span>
    </span>
  );
};

index.ts

export { QualityFrame } from './QualityFrame';
export { AffixChip } from './AffixChip';

验收

  • 在 dev/storybook 页手动 <QualityFrame tier="mythic">测试</QualityFrame> 验证 9 档色
  • 不朽档有金光脉动
  • AffixChip 5 种 kind 颜色正确(素/冷/暖/贵/烈)

T6. 装备卡 UI 升级(沿用现有 BaggageTab 改造)

目标:现有 BaggageTab 中的装备卡升级——从「4 档 rarity」升级到「9 档 QualityFrame + AffixChip」。

改动文件

  • src/components/BaggageTab.tsx(或 item 目录里新建 ItemCard.tsx 后引用)

改造点

  1. 装备卡外层
// 旧:
<div className={`item-card rarity-${item.rarity}`}>
  {/* ... */}
</div>

// 新:
<QualityFrame tier={item.qualityTier ?? 'waste'} className="item-card">
  {/* ... */}
</QualityFrame>
  1. 词条区域
// 新增(在装备卡底部):
{item.affixes?.length > 0 && (
  <div className="mt-1.5 flex flex-wrap gap-1">
    {item.affixes.map((affix, i) => (
      <AffixChip key={i} affix={affix} rolledValue={affix.rolledValue} compact />
    ))}
  </div>
)}
  1. 保留旧视觉元素:图标 / 名称 / 装备按钮 / 锁定按钮(不变)

  2. Fallback:如果装备没有 qualityTier(极端旧数据),fallback 到 waste

验收

  • 打开游戏 → 进背包 → 看到 30 件装备按 9 档品质上色
  • 词条标签在卡片里可见
  • buildTag 仍正确显示(数组化后不破版)
  • 装备/卸下/锁定按钮功能不变

T7. rollEquipment 工具函数

目标:根据品质 + 词条库,随机生成一件新装备。

新建文件

  • src/utils/itemGen.ts

API 设计

import type { Affix, QualityTier, EquippableItem, StaffId } from '@/types';
import { QUALITY_TIERS } from '@/data/quality';
import { AFFIX_POOL } from '@/data/affixes';

export interface RollOptions {
  tier: QualityTier;                              // 品质
  type: EquippableItem['type'];                   // 类型
  buildTag?: EquippableItem['buildTag'][number];  // 流派过滤
  affixPool?: Affix[];                            // 自定义词条池(默认用全库)
  rng?: () => number;                             // 注入随机数(测试用)
}

/**
 * 根据品质和流派随机生成一件装备
 * Phase 1 实现:基础三维 + 词条抽取 + rollValue 计算
 * Phase 2 扩展:装备战力计算
 */
export function rollEquipment(opts: RollOptions): EquippableItem {
  const {
    tier,
    type,
    buildTag,
    affixPool = AFFIX_POOL,
    rng = Math.random,
  } = opts;

  const meta = QUALITY_TIERS[tier];
  const pool = buildTag
    ? affixPool.filter(a => !a.tags || a.tags.length === 0 || a.tags.includes(buildTag))
    : affixPool;

  // 1. 抽取词条(按权重 + 词条数)
  const affixes: RolledAffix[] = [];
  const usedIds = new Set<string>();
  for (let i = 0; i < meta.affixCount; i++) {
    const candidates = pool.filter(a => !usedIds.has(a.id));
    if (candidates.length === 0) break;
    const picked = weightedPick(candidates, a => a.weight, rng);
    usedIds.add(picked.id);

    // 2. 计算 rollValue(在品质区间内)
    const [rollMin, rollMax] = meta.affixRoll;
    const rollValue = rollMin + rng() * (rollMax - rollMin);
    const [vMin, vMax] = picked.valueRange;
    const rolledValue = vMin + rollValue * (vMax - vMin);

    affixes.push({ ...picked, rollValue, rolledValue });
  }

  // 3. 基础三维(按 type 推默认值)
  const baseStats = generateBaseStats(type, tier);

  // 4. 组装 EquippableItem(其他字段用占位)
  return {
    kind: 'equipment',
    id: `gen_${Date.now()}_${Math.floor(rng() * 100000)}`,
    name: `${meta.name}级${typeLabel(type)}`,
    qualityTier: tier,
    rarity: tierToOldRarity(tier),  // 软迁移
    type,
    baseId: `base_${type}`,
    affixes,
    baseStats,
    stat: defaultStat(type),
    value: sumBaseStats(baseStats) * meta.multiplier,
    level: 1,
    buildTag: buildTag ? [buildTag] : [],
    flavorText: `${meta.name}品质的${typeLabel(type)},附 ${affixes.length} 条词条。`,
  };
}

// 内部工具:加权随机抽取
function weightedPick<T>(items: T[], weight: (item: T) => number, rng: () => number): T {
  const total = items.reduce((s, x) => s + weight(x), 0);
  let r = rng() * total;
  for (const item of items) {
    r -= weight(item);
    if (r <= 0) return item;
  }
  return items[items.length - 1];
}

// 内部工具:基础三维默认值(按 type + tier)
function generateBaseStats(type: EquippableItem['type'], tier: QualityTier): { atk?: number; def?: number; eff?: number } {
  const base = 5 + QUALITY_TIERS[tier].rank * 2;
  switch (type) {
    case 'weapon':     return { atk: base };
    case 'accessory':  return { def: Math.floor(base * 0.6), eff: Math.floor(base * 0.4) };
    case 'ingredient': return { eff: base };
    case 'book':       return { eff: base, def: Math.floor(base * 0.3) };
  }
}

// 内部工具:求和基础三维
function sumBaseStats(s: { atk?: number; def?: number; eff?: number }): number {
  return (s.atk ?? 0) + (s.def ?? 0) + (s.eff ?? 0);
}

验收

  • 单元测试:固定 rng → 输出确定性(rollValue === 0.5
  • 单元测试:品质 = mythic + buildTag = 'chef' → 词条至少 1 个 chef tag
  • 单元测试:词条数 === QUALITY_TIERS[tier].affixCount
  • 单元测试:词条数值在 valueRange

T8. 回归测试 + 视觉对照 checklist

目标:确保 T1-T7 改完后没破坏现有功能,且 9 档色板跟视觉稿 v1.0 一致。

新建文件

  • docs/design/active/Phase1实施切片-v1-checklist.md

checklist 内容

# Phase 1 实施回归 Checklist

> 跑这个 checklist 前:所有 T1-T7 的 commit 都已合并到 main

## A. 代码质量
- [ ] `npm run lint` 通过(tsc --noEmit 无新增错误)
- [ ] `npm run build` 通过
- [ ] 旧 4 档 rarity 引用全部带 `// TODO: Phase 2` 注释
- [ ] 无新增 `any` 类型(除非有显式注释)

## B. 视觉对照(跟 HTML 视觉稿 v1.0 对)
- [ ] 9 档品质色全对(白灰/暖灰/黄绿/青蓝/紫/金/朱红/洋红/金光渐变)
- [ ] 不朽档有金光渐变 + 3s 脉动
- [ ] AffixChip 5 种 kind 颜色(素/冷/暖/贵/烈)跟视觉稿对
- [ ] 拟物化调性保留:tongfu-parchment / tongfu-seal / tongfu-crest 仍生效
- [ ] 装备卡在 360 / 768 / 1440 三个断点下不破版

## C. 功能完整性
- [ ] 30 件装备迁移后无数据丢失
- [ ] 装备/卸下/锁定功能正常
- [ ] buildTag 多选后过滤仍正常(如果旧代码用单选)
- [ ] localStorage 存档兼容(用旧档打开不报错)

## D. 性能
- [ ] 背包 100 件装备渲染 < 100ms(用 React Profiler 验证)
- [ ] rollEquipment 单次调用 < 5ms

## E. 手动测试场景
1. 开新档 → 进游戏 → 进背包 → 看到 30 件装备(9 档色按概率分布)
2. 点击一件装备 → 详情面板显示词条
3. 装备/卸下 → 装备关系正确
4. 跨页面刷新(localStorage) → 数据不丢
5. 极端场景:装备词条 4 个 + 不朽品质 → 卡片不被截断
6. rollEquipment(legend, weapon, chef) × 10 次 → 输出 10 件不同装备

T8 本身执行:把所有 checkbox 跑一遍,失败的回滚对应 T,重做。


5. 验收清单(汇总)

每个 T 完成后都跑一遍:

npm run lint     # tsc --noEmit
npm run build    # vite build
npm run dev      # 启动后手动玩一遍:开档 → 进游戏 → 进背包 → 看装备

T8 完成后跑完整 checklist(见 §4 T8)。


6. 回滚方案

每个 T 都是独立 commit(git revert 单个 PR 即可回滚)。

| T | 回滚方式 | 风险 | | :-: | :--- | :--- | | T1 | git revert T1-commit —— qualityTier 类型删除,下游不引用 | 低 | | T2 | git revert T2-commit —— Affix 类型删除,affixes.ts 删除 | 低 | | T3 | 还原 同福客栈挂机游戏/gameData.tsgit checkout T3^ -- 同福客栈挂机游戏/gameData.ts + revert types.ts 改动 | 中(数据迁移可能丢) | | T4 | 还原 src/index.cssgit checkout T4^ -- src/index.css | 低 | | T5 | git revert T5-commit —— 新组件目录删除 | 低 | | T6 | 还原 src/components/BaggageTab.tsxgit checkout T6^ -- src/components/BaggageTab.tsx | 中(可能丢失装备卡改进) | | T7 | git revert T7-commit —— itemGen.ts 删除 | 低 | | T8 | 删除 checklist 文件 | 低 |

核心回滚原则:T1/T2/T4/T5/T7 是纯增量,回滚零风险;T3/T6 是改造,回滚时需要还原数据/UI 文件。


7. 与设计文档的对应

| 切片任务 | 设计文档章节 | | :--- | :--- | | T1 品质 9 档 | v3.1.4 §2 品质体系 | | T2 词条系统 | v3.1.4 §3 词条机制(含 §3.1 类型 / §3.2 区间 / §3.3 rollValue) | | T3 装备模型 | v3.1.4 §2.5 装备生成规则 | | T4 9 档色板 | 视觉规范 v1.0 §2.1 品质色组 + §3.1 装备卡组件 | | T5 基础组件 | 视觉规范 v1.0 §3 核心组件规格 | | T6 装备卡 UI | 视觉规范 v1.0 §4.1 装备详情界面 | | T7 rollEquipment | v3.1.4 §2.5 + §3.3 词条 roll 机制 | | T8 回归测试 | 视觉稿 v1.0 全局对照 |

配套文档索引(v3.1.4 附录 B 新增):

《道具词条与玩法扩展 v3.1.4》         ← 机制源头
《道具系统新核心 UI 视觉规范 v1.0》  ← 视觉契约
《道具系统新核心 UI 视觉稿 v1.html》 ← 视觉展示
《Phase 1 实施切片 v1.0》            ← 施工蓝图(本文件)

8. 后续 Phase 预告(不在本切片范围)

| Phase | 范围 | 设计依据 | 预计工期 | | :---: | :--- | :--- | :---: | | Phase 2 | §11 槽位 + §12 攻防 + §13 战力 | v3.1.4 §11/§12/§13 | 10-15 天 | | Phase 3 | §14 装备强化 + §15 员工换装 | v3.1.4 §14/§15 | 5-7 天 | | Phase 4 | §16 装备获得 + §17 背包管理 | 待补设计 | 5-7 天 |

Phase 1 完工后,下一步直接进入 Phase 2——届时所有"展示"基础设施就位,新功能直接对接 §11/§12/§13 的数据模型。


9. 实施节奏建议

| 工作日 | 任务 | 备注 | | :---: | :--- | :--- | | 第 1 天 上午 | T1 品质 9 档类型 + 常量 | 半天搞定 | | 第 1 天 下午 | T2 词条系统 + 词条库 | 半天搞定 | | 第 2 天 上午 | T3 装备模型字段补齐 | types.ts 改完 | | 第 2 天 下午 | T3 迁移 gameData 30 件装备 + T4 9 档色板 | 并行做 | | 第 3 天 | T5 QualityFrame + AffixChip 组件 | 独立日 | | 第 4 天 上午 | T6 装备卡 UI 改造 | 半天 | | 第 4 天 下午 | T7 rollEquipment 工具函数 | 半天 | | 第 5 天 | T8 回归测试 + checklist | 收尾 |

每天 commit 一次,PR 互相独立可 review。


附录 A. 设计哲学速记(避免实施时漂移)

  1. 拟物化 —— 宣纸做底、墨色做字、朱砂做强调;不要纯白/纯黑/霓虹色
  2. 视觉降噪 —— 5 秒决策铁律(每屏只解决 1 个决策)+ 1 焦点铁律(同屏只 1 个视觉焦点)
  3. 客栈调性 —— 三主色上限(宣纸/墨色/朱砂);装备卡像"宣纸+印章",槽位像"木质相框"(Phase 2)
  4. 新手友好 —— 5 秒看懂"为什么这件强"(词条可视化)+ 3 秒看懂"换不换"(Phase 2 替换对比)

Phase 1 实施切片 v1.0 完。 等你拍板开 T1,还是先讨论切片本身?