Skip to content

玄学放置 · 双 Tab 切换、favicon 与学科改名

三个细节优化叠加

这一阶段的改动都不大,但都属于"做完了之后整体感会明显提升"的那类细节:

  1. 右侧面板加「装备 / 物品」两个 Tab,区分装备和道具
  2. 生成站点的 favicon,样式用「玄学」像素图标
  3. 把「语文」改成更通用的「语言」,并调整任务列表

物品分类设计

之前所有物品都是装备(有 slot 字段、能装到对应槽位)。但游戏后续肯定会有消耗品、材料、货币这类不能装备的"道具",需要在数据层先把分类抽出来。

ITEMS_DB 加一个 category 字段:

js
export const CATEGORIES = {
  equipment:  { name: '装备',   color: '#5eead4' },
  consumable: { name: '消耗品', color: '#f472b6' },
  material:   { name: '材料',   color: '#a78bfa' },
  currency:   { name: '货币',   color: '#fbbf24' }
}

equipment 类才有 slot 字段,其他三类不可装备、只在物品 Tab 展示。

stores/inventory.js 加两个 getter:

js
equipmentBag() {
  return this.bagView.filter(e => e.item?.category === 'equipment')
},
itemBag() {
  return this.bagView.filter(e => e.item && e.item.category !== 'equipment')
}

视图层只消费 getter,不再自己写过滤逻辑——这是"组件薄、store 厚"的常见做法,确保 UI 层永远拿到符合语义的数据。

Tab 切换交互

EquipmentPanel 顶部加一行 Tab:

vue
<header class="equip-panel__tabs">
  <button v-for="tab in TABS"
          :class="{ 'equip-panel__tab--active': activeTab === tab.key }"
          @click="activeTab = tab.key">
    {{ tab.name }}
    <span class="equip-panel__tab-badge">{{ tab.count }}</span>
  </button>
</header>

每个 Tab 右边带数量徽章(显示当前 Tab 下的物品数量),玩家不切到那个 Tab 也能知道"那边有几件物品"。这是相对低成本但有效的提示。

装备 Tab:保留原有的装备槽 + 人物剪影 + 装备类背包
物品 Tab:装备槽不显示,下方是消耗品/材料/货币的网格

物品 Tab 里再加一行二级筛选 chip:「全部 / 消耗品 / 材料 / 货币」。每个 chip 是 ref 切换:

js
const itemFilter = ref('all')
const filteredItems = computed(() => {
  if (itemFilter.value === 'all') return store.itemBag
  return store.itemBag.filter(e => e.item?.category === itemFilter.value)
})

UI 层的筛选只是过滤已有数据,不动 store,不发请求,状态切换近乎零成本。

5 件 mock 道具

既然分类抽好了,就配套加几件道具进去:

道具分类品质效果文案
矿泉水 ×6消耗品普通回复体力 +20
粉笔 ×12消耗品普通下次任务暴击 +20%
笔记本 ×3材料优秀合成材料
通行证 ×1材料稀有解锁玄学副本
奖学金 ×188货币传说商店消费

每个道具都有自己的 16×16 像素图标,和学科/装备图标共用同一个 PixelIcon 组件。这是上一阶段做像素体系时埋下的好处——新增视觉资产几乎是"加一行字符画 + 调色板"的工作量。

favicon 生成

「玄学」图标本身设计感最强(水晶球 + 全知之眼 + 底座),用它做 favicon 既呼应项目代号 metaphysics,又能立刻让标签页显出风格。

最佳方案是同时提供 SVG 和 ICO 两种格式:

  • favicon.svg:现代浏览器优先用,无损放大
  • favicon.ico:老浏览器 fallback

SVG favicon

直接复用 pixelIcons.js 里玄学图标的字符画数据,跑一段 Node 脚本生成 SVG:

js
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"
  shape-rendering="crispEdges">
  <rect width="16" height="16" rx="2" fill="#0b0f1a"/>
  ${rects.join('')}
</svg>`
fs.writeFileSync('public/favicon.svg', svg)

这里加了一个 rx="2" 的圆角深色底,是给 favicon 专用的(图标本体在游戏里是嵌在面板里的,没有独立背景;做成 favicon 时需要一个完整的"小方块"语义)。

ICO 文件:手写 BMP-in-ICO

ICO 是二进制格式,本来需要 ImageMagick 之类的工具转换。但 ICO 的结构其实很简单,可以直接用 Node Buffer 手写:

[6 字节 ICONDIR]
  reserved (2) | type=1 (2) | count=1 (2)
[16 字节 ICONDIRENTRY]
  width(1) | height(1) | colors(1) | reserved(1)
  planes=1 (2) | bpp=32 (2)
  bytes_in_resource (4) | image_offset (4)
[40 字节 BITMAPINFOHEADER]
  size=40 | width=16 | height=32 (2x: XOR + AND)
  planes=1 | bpp=32 | compression=0 | ...
[1024 字节像素数据] (16×16×4 BGRA, BMP 是底->上扫描)
[32 字节 AND mask] (1 bit/pixel, 16×16)

总共 1118 字节。关键细节:

  • BMP 像素是 BGRA 而不是 RGBA:要在写入时做颜色通道交换
  • BMP 是底->上扫描:循环时 bmpY = H - 1 - y
  • biHeight 要写两倍高度:因为 ICO 内嵌的 BMP 同时包含 XOR mask(彩色)和 AND mask(透明掩码)
  • AND mask 即使 32bpp 也要保留:写全 0 即可

写完后用 file 命令验证:

$ file public/favicon.ico
public/favicon.ico: MS Windows icon resource - 1 icon, 16x16, 32 bits/pixel

合规。

html
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="alternate icon" type="image/x-icon" href="/favicon.ico" />

alternate icon 是兼容性更好的写法。Vite 会自动把 public/ 下的内容复制到构建产物根目录,开发模式与生产模式都可以直接通过 /favicon.ico 访问。

学科改名:语文 → 语言

「语文」对应中文学科,但游戏里其实想覆盖更广的语言学习——包括外语、文言、写作。改名为「语言」之后任务也跟着调整:

背诵唐诗三百首  4s  +6 EXP
记英语单词     3s  +5 EXP
写一篇命题作文  9s  +16 EXP
文言文断句     6s  +10 EXP

新增「记英语单词」是这次扩张的关键——不光是中文了。

改名带来一些连锁修改需要同步:

  • mock/inventory.js 中铅笔、画笔的属性描述「语文经验」改成「语言经验」
  • 装备 Tooltip 里的属性 KV 同步更新

这是产品命名变化时容易忽略的边角工作。但既然涉及到属性文案,就要保持术语一致,否则玩家会困惑「装备到底加哪个学科的经验」。

收获

这一阶段三件事单看都是细节,合在一起会让产品体感跨一个台阶:

  • Tab 切换 + 计数徽章让右侧面板能容纳更多物品分类,扩展性打开
  • favicon 让浏览器标签页一眼就能识别项目,是产品"成型感"的关键标识
  • 学科改名看似小事,但牵动属性文案和任务列表,体现了"命名变化要同步上下游"的工程纪律

下一篇会做一个面向素材复用的工具:把所有像素图标导出成独立的 SVG 文件,方便后续二次使用。