用JSON与TypeScript构建MCU蜘蛛侠数据档案(Vue3实现)
2026/9/5 12:33:40 网站建设 项目流程

说到 MCU 的历代蜘蛛侠,很多非技术背景的观众看到的是“三代演员、多个宇宙”的故事,而做软件和前端的人看到的是另一类问题:如何把参演年份、所属电影宇宙、演员、影片名称、排序口径这些松散信息组织成一份可校验、可查询、可复用的数据清单。这里的 MCU 在影视语境中指 Marvel Cinematic Universe,即漫威电影宇宙;不过它同时也是 Microcontroller Unit 的缩写。在技术文章里提到 MCU,必须先说明上下文,否则团队成员很容易理解成单片机的芯片型号。

这篇文章围绕“MCU 的历代蜘蛛侠”这个主题,演示一个适合放在个人技术站点的侧边栏小项目:用 JSON 维护蜘蛛侠真人影视作品档案,用 TypeScript 校验结构,用 Vue 3 制作一个按宇宙筛选的年份时间轴页面。你可以把文中的影视数据替换成任意视频、图书、开发框架版本列表,方法论完全一样。读完这篇文章,你不仅能快速整理出漫威电影宇宙中的蜘蛛侠作品顺序,还能得到一个具备筛选、排序、校验能力的数据管理模板。

1. 先分清影视语境里的 MCU 到底是什么

1.1 MCU 的常用含义与蜘蛛侠数据口径

在技术交流群里看到“MCU”三个字母时,先别急着把它理解成微控制单元。MCU 在不同场景里的含义差别很大:

场景MCU 含义典型使用对象
嵌入式开发Microcontroller Unit 微控制器单片机、传感器、物联网设备
影视娱乐Marvel Cinematic Universe 漫威电影宇宙影视作品、角色联动、时间线整理
游戏领域某些游戏内的“单位”缩写策略类游戏玩家

本文讨论的是影视娱乐领域的 MCU。漫威电影宇宙由漫威影业制作并发行,从 2008 年的《钢铁侠》开始,形成了一个相互关联的电影和剧集序列。蜘蛛侠作为漫威旗下知名角色,其影视改编历史比 MCU 本身更长。

整理蜘蛛侠数据时,最常见的问题就是口径不一。很多人把“所有真人蜘蛛侠电影”全部称为 MCU 系列,这是不准确的。以本文使用的整理口径为例:

  • 托比·马奎尔主演的《蜘蛛侠》三部曲由索尼影业制作,不属于漫威电影宇宙主线。
  • 安德鲁·加菲尔德主演的《超凡蜘蛛侠》两部同样由索尼制作,也不属于漫威电影宇宙主线。
  • 汤姆·霍兰德出演的蜘蛛侠首次出现在 2016 年的《美国队长3:内战》,该片属于漫威电影宇宙;后续的《蜘蛛侠:英雄归来》《复仇者联盟3:无限战争》《复仇者联盟4:终局之战》《蜘蛛侠:英雄远征》《蜘蛛侠:英雄无归》也属于漫威电影宇宙相关作品。

因此,做数据表时不能只用一个“是否蜘蛛侠电影”布尔值,而应该细分字段:上映年份、制作公司、所属宇宙、是否 MCU 正片、首次出场的影片名称。这样才能准确回答“MCU 中历代蜘蛛侠的出场顺序”这类问题。

1.2 为什么先建立数据口径而不是直接抓网页

有人会问,为什么不直接写一个网页爬虫,从资料站抓取所有电影名称和演员,再自动填表?这种做法在演示项目里可行,但在实际维护中会带来三个麻烦。

第一,资料来源不稳定。影视资料网站的数据结构随时可能调整,演员栏可能缺一名,年份字段可能是字符串,抓下来之后清洗成本往往比手工整理还高。第二,语义口径不能靠爬虫判断。“MCU 主线”和“索尼制作但片尾彩蛋与 MCU 相关”是两种完全不同的归属关系,页面上的分类标签不一定能覆盖你的查询需求。第三,外部资料的版权和访问频率限制不适合放到个人小项目里长期运行。

更稳妥的方案是先人工维护一份结构化的 JSON 快照,再编写校验与排序逻辑。这样数据来源明确、字段可控、运行没有外部依赖,后续想迁移到 Vue、React、小程序都很方便。

2. 搭建最小项目:目录结构、JSON 数据与 TypeScript 校验

2.1 本地环境要求

这里采用 Vue 3 + Vite 作为演示技术栈,因为它是目前搭建前端示例最快的方案之一。运行前先在本地完成环境检查:

工具建议版本用途
Node.js18 或更高版本运行构建工具
pnpm8 或更高版本安装依赖,也可以用 npm 替代
Vite5 或兼容版本启动开发服务器
Vue3.4 或兼容版本编写页面组件

如果你只是做数据校验练习,不打算启动页面,可以跳过 Vue,只用 Node.js + TypeScript 运行脚本。下面的示例会把数据、校验、渲染拆开,方便你按需裁剪。

命令行创建项目:

pnpm create vite spider-verse-manifest --template vue-ts cd spider-verse-manifest pnpm install

创建完成后的目录结构如下:

spider-verse-manifest/ ├── index.html ├── package.json ├── tsconfig.json ├── vite.config.ts └── src/ ├── main.ts ├── App.vue ├── data/ │ └── spiderManRecords.json ├── types/ │ └── SpiderManRecord.ts └── utils/ └── sortAndFilter.ts

data 目录存放原始影视档案,types 目录定义 TypeScript 类型,utils 目录放置筛选和排序函数,App.vue 负责最终展示。

2.2 设计影视档案数据类型

设计类型是这一步最关键的工作。不要把电影名称、演员、年份全部塞进一个普通字符串,否则后续做排序和筛选会非常痛苦。推荐建立如下字段:

// src/types/SpiderManRecord.ts export type UniverseType = | 'MCU' | 'SonyClassic' | 'SonyAmazing'; export interface SpiderManRecord { /** 唯一标识,建议使用拼音或英文短横线格式 */ id: string; /** 影片中文常用名 */ title: string; /** 首映年份 */ releaseYear: number; /** 饰演蜘蛛侠的演员 */ mainActor: string; /** 作品归属的宇宙分类 */ universe: UniverseType; /** 是否属于 MCU 主线影片 */ inMCU: boolean; /** 是否为该演员的首次真人登场 */ isFirstAppearance: boolean; /** 排序备注,说明采用上映顺序还是 MCU 叙事顺序 */ orderNote?: string; }

inMCU 和 universe 是两套信息。universe 描述制作背景,inMCU 描述是否属于漫威电影宇宙主线。以《蜘蛛侠:英雄无归》为例,它属于 MCU 主线,但故事里出现了前两代蜘蛛侠演员,也有索尼影业的制作参与背景。只用两个字段可以让筛选逻辑更清晰,也避免“归属”判断被写死成单一维度。

2.3 准备一份 JSON 档案

在 src/data/spiderManRecords.json 中存放一份待校验的数据。下面是一个精简但完整的最小示例,数据字段覆盖了不同宇宙、不同年份和不同演员:

[ { "id": "spider-man-2002", "title": "蜘蛛侠", "releaseYear": 2002, "mainActor": "托比·马奎尔", "universe": "SonyClassic", "inMCU": false, "isFirstAppearance": true, "orderNote": "老版三部曲第一部" }, { "id": "spider-man-2-2004", "title": "蜘蛛侠2", "releaseYear": 2004, "mainActor": "托比·马奎尔", "universe": "SonyClassic", "inMCU": false, "isFirstAppearance": false }, { "id": "spider-man-3-2007", "title": "蜘蛛侠3", "releaseYear": 2007, "mainActor": "托比·马奎尔", "universe": "SonyClassic", "inMCU": false, "isFirstAppearance": false }, { "id": "amazing-spider-man-2012", "title": "超凡蜘蛛侠", "releaseYear": 2012, "mainActor": "安德鲁·加菲尔德", "universe": "SonyAmazing", "inMCU": false, "isFirstAppearance": true }, { "id": "amazing-spider-man-2-2014", "title": "超凡蜘蛛侠2", "releaseYear": 2014, "mainActor": "安德鲁·加菲尔德", "universe": "SonyAmazing", "inMCU": false, "isFirstAppearance": false }, { "id": "civil-war-2016", "title": "美国队长3:内战", "releaseYear": 2016, "mainActor": "汤姆·霍兰德", "universe": "MCU", "inMCU": true, "isFirstAppearance": true, "orderNote": "蜘蛛侠在 MCU 中首次正式出场" }, { "id": "homecoming-2017", "title": "蜘蛛侠:英雄归来", "releaseYear": 2017, "mainActor": "汤姆·霍兰德", "universe": "MCU", "inMCU": true, "isFirstAppearance": false }, { "id": "infinity-war-2018", "title": "复仇者联盟3:无限战争", "releaseYear": 2018, "mainActor": "汤姆·霍兰德", "universe": "MCU", "inMCU": true, "isFirstAppearance": false }, { "id": "endgame-2019", "title": "复仇者联盟4:终局之战", "releaseYear": 2019, "mainActor": "汤姆·霍兰德", "universe": "MCU", "inMCU": true, "isFirstAppearance": false }, { "id": "far-from-home-2019", "title": "蜘蛛侠:英雄远征", "releaseYear": 2019, "mainActor": "汤姆·霍兰德", "universe": "MCU", "inMCU": true, "isFirstAppearance": false, "orderNote": "发布年份虽晚于《终局之战》,叙事顺序在终局之战之后" }, { "id": "no-way-home-2021", "title": "蜘蛛侠:英雄无归", "releaseYear": 2021, "mainActor": "汤姆·霍兰德", "universe": "MCU", "inMCU": true, "isFirstAppearance": false } ]

把数据放在 json 文件而不是直接写进 Vue 组件,是为了让数据和视图分离。后续更新片单时只需要改 JSON,不需要动渲染逻辑。影视数据会不断扩充,如果直接在组件里写死数组,后期维护会变得困难。

注意:这里的 JSON 是按“真人电影”整理的。动画《蜘蛛侠:平行宇宙》虽然在口碑上很重要,但如果要做全量片单,应该单独放在另一个 universe 分类里,不建议直接混入 MCU 主线口径。

2.4 用 TypeScript 做运行时校验

光写类型定义还不够,JSON 文件在运行时不会自动检查字段。最稳妥的方法是用一个小校验函数读取数据,至少要确认三个关键点:

  • releaseYear 是数字;
  • inMCU 是布尔值;
  • id 没有重复。

在 Node.js 环境里用 tsx 运行脚本:

// src/scripts/validateData.ts import fs from 'fs'; import path from 'path'; import type { SpiderManRecord } from '../types/SpiderManRecord'; const filePath = path.resolve( __dirname, '../data/spiderManRecords.json' ); const raw = fs.readFileSync(filePath, 'utf-8'); const records = JSON.parse(raw) as SpiderManRecord[]; const idSet = new Set<string>(); for (const record of records) { if (typeof record.releaseYear !== 'number') { throw new Error(`releaseYear 必须是数字: ${record.id}`); } if (typeof record.inMCU !== 'boolean') { throw new Error(`inMCU 必须是布尔值: ${record.id}`); } if (idSet.has(record.id)) { throw new Error(`id 重复: ${record.id}`); } idSet.add(record.id); } console.log(`校验通过,共 ${records.length} 条记录`);

这段脚本可以留作 CI 流水线中的前置检查。以后每增加一部新片,运行一次校验,能避免在页面已经发布后才发现字段错误。

3. 编写筛选与排序工具函数

3.1 按上映年份排序

胶片资料最基本的排序规则按 releaseYear 升序排列。这里避免直接修改原始数组,使用扩展运算符创建副本:

// src/utils/sortAndFilter.ts import type { SpiderManRecord } from '../types/SpiderManRecord'; export function sortByReleaseYear( records: SpiderManRecord[] ): SpiderManRecord[] { return [...records].sort( (a, b) => a.releaseYear - b.releaseYear ); }

3.2 按 MCU 筛选

筛选 inMCU 为 true 的记录可以得到 MCU 主线中的蜘蛛侠出场作品:

export function filterByMCU( records: SpiderManRecord[] ): SpiderManRecord[] { return records.filter((record) => record.inMCU); }

如果后续想看索尼旧系列,可以把筛选条件扩展成接收 universe 参数:

export function filterByUniverse( records: SpiderManRecord[], universe: string ): SpiderManRecord[] { return records.filter( (record) => record.universe === universe ); }

3.3 区分上映顺序与 MCU 内部叙事顺序

这里有一个容易被忽略的问题:MCU 电影的上映顺序和 MCU 故事内部时间顺序并不总是完全一致。

以上面的数据为例,《蜘蛛侠:英雄远征》上映于 2019 年,《复仇者联盟4:终局之战》也上映于 2019 年。前者的叙事发生在终局之战之后,所以虽然发布年份可能靠前或接近,但从剧情时间线看,它的位置应该排在终局之战之后。这个细节正好说明:单靠 releaseYear 排序不能解决所有“历代顺序”的问题。

一种解决方法是增加一个可选的 sortRank 字段。在没有官方排序表时,可以只按 releaseYear 展示;如果需要按 MCU 叙事顺序展示,则根据人工维护的 sortRank 排序:

export function sortByOrderRank( records: SpiderManRecord[] ): SpiderManRecord[] { return [...records].sort((a, b) => { const aRank = a.orderRank ?? a.releaseYear; const bRank = b.orderRank ?? b.releaseYear; return aRank - bRank; }); }

字段是空值时用顺序戳替代,这样可以兼容“未维护 orderRank 的旧数据”。但要注意,这种方法只能在人工核对过叙事顺序后使用,不能靠代码自动推断剧情时间。

4. 用 Vue 3 写一个电影清单展示页

4.1 页面功能拆解

页面可以只做三件事:

  • 读取 JSON 数据;
  • 按筛选条件显示 MCU 主线或全部影片;
  • 按上映年份排序后展示卡片。

这种精简交互很适合作为前端入门练习,也能让不熟悉 Vue 的读者快速看到数组操作和页面渲染是如何连接起来的。

4.2 App.vue 基础实现

<script setup lang="ts"> import { ref, computed } from 'vue'; import spiderManRecords from './data/spiderManRecords.json'; import type { SpiderManRecord } from './types/SpiderManRecord'; const records = ref<SpiderManRecord[]>( spiderManRecords as SpiderManRecord[] ); const filterOption = ref<'all' | 'MCU' | 'SonyClassic'>('all'); const sortedRecords = computed<SpiderManRecord[]>(() => { const source = [...records.value]; return source.sort( (a, b) => a.releaseYear - b.releaseYear ); }); const filteredRecords = computed<SpiderManRecord[]>(() => { if (filterOption.value === 'all') { return sortedRecords.value; } if (filterOption.value === 'MCU') { return sortedRecords.value.filter( (record) => record.inMCU ); } return sortedRecords.value.filter( (record) => record.universe === filterOption.value ); }); </script> <template> <main> <h2>MCU 的历代蜘蛛侠资料清单</h2> <div class="filters"> <button :class="{ active: filterOption === 'all' }" @click="filterOption = 'all'" > 全部 </button> <button :class="{ active: filterOption === 'MCU' }" @click="filterOption = 'MCU'" > 仅 MCU </button> <button :class="{ active: filterOption === 'SonyClassic' }" @click="filterOption = 'SonyClassic'" > 老版三部曲 </button> </div> <ul class="record-list"> <li v-for="record in filteredRecords" :key="record.id" class="record-card" > <section> <h3>{{ record.title }}</h3> <p>上映年份:{{ record.releaseYear }}</p> <p>主演:{{ record.mainActor }}</p> <p>宇宙归属:{{ record.universe }}</p> <p> 是否 MCU 主线: {{ record.inMCU ? '是' : '否' }} </p> <p v-if="record.orderNote"> 备注:{{ record.orderNote }} </p> </section> </li> </ul> </main> </template> <style scoped> .filters { display: flex; gap: 12px; margin-bottom: 24px; } .filters button { cursor: pointer; padding: 6px 14px; border: 1px solid #ccc; border-radius: 6px; background: #fff; } .filters button.active { background: #b91c1c; color: #fff; } .record-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; list-style: none; padding: 0; } .record-card { padding: 16px; border-radius: 8px; background: #f8f8f8; border: 1px solid #e5e5e5; } </style>

这段代码把数据读取、筛选、渲染放到了同一个文件中,适合演示。真实项目如果记录很多,建议再拆分一个请求函数或组件。

点击“仅 MCU”按钮后,页面会保留汤姆·霍兰德出场的 MCU 作品;切回“全部”后可以看到三个不同系列混合在一起按年份排列。如果发现列表没有变化,优先检查 JSON 文件是否被正确加载,以及 computed 属性中是否引用了正确的数组。

4.3 组件展示与状态的一致性

上面示例中,filterOption 是整个视图的唯一状态来源。筛选按钮通过点击修改 filterOption,computed 属性根据状态重新计算记录。这种方式比在方法里手动修改页面 DOM 更可靠,也更容易排查问题。

当筛选条件增加时,不要继续在模板里写大量 v-if,而是把筛选逻辑收敛到 computed 过滤器函数中。这样按钮文案和过滤逻辑可以解耦,以后改成下拉选择也会很容易。

5. 常见问题排查与数据坑位

5.1 为什么我筛选后还是能看到前两代蜘蛛侠

很多人会把“MCU 的历代蜘蛛侠”理解成“所有出现在 MCU 银幕上的蜘蛛侠演员”。这样《蜘蛛侠:英雄无归》里出现的托比·马奎尔和安德鲁·加菲尔德也算出现了。但如果我们要的是“属于 MCU 主线电影名单”,则应该过滤的是影片的 inMCU 字段,而不是演员名。

在排查这类问题时,建议先明确要回答的问题是:

  • 想查“汤姆·霍兰德在 MCU 里出演了哪些电影”;
  • 还是想查“历代蜘蛛侠演员都出现在哪些影片中”。

两个问题的数据口径不同,过滤字段也不同。前者不需要管演员是不是前代,后者需要按 mainActor 分组。

5.2 页面正常渲染但年份顺序不对

检查排序函数,确认是否直接修改了 props 或原始数组。Vue 的响应式数据可以直接排序,但如果 data 来自 json 文件并赋给了 ref,使用原数组排序会改变 records 的数据顺序,可能导致多次点击后结果不稳定。

正确做法是在排序函数内部先复制:

const yearSorted = [...records.value].sort((a, b) => { return a.releaseYear - b.releaseYear; });

5.3 数据缺失导致控制台报错

如果某条记录缺少 mainActor 字段,页面上就会渲染一个空段落。这种问题不会导致页面崩溃,但容易让演示数据出现“主演空缺”的观感。

错误预防办法是在校验脚本里增加字段存在性检查:

if (!record.mainActor || !record.title) { throw new Error(`字段不能为空: ${record.id ?? '未知记录'}`); }

5.4 用错字段名导致筛不到任何数据

常见字段名冲突有以下几种:

易混淆写法推荐写法原因
is_mcuinMCU保持 camelCase 统一
MCUinMCU大写名称不适合做布尔字段
belongsTouniverse / inMCU一个字段无法表达两类含义
yearsreleaseYearreleaseYear 语义更准确

排查筛选异常时,先在浏览器 console 里打印一条原始记录,确认 JSON key 和代码中的 key 完全一致,再检查 filter 条件。这种“找不到数据”的问题往往不是 Vue 的问题,而是字段命名不一致。

6. 学习环境与生产环境的整理建议

6.1 学习阶段的必要步骤

如果你只是想通过这个项目熟悉 Vue 和 TypeScript,建议按以下顺序练习:

  • 学习阶段只维护 data 目录下的 JSON,不要加入网络请求;
  • 确认校验脚本能抛出重复 id 错误;
  • 在 App.vue 中增加全部、MCU、老版三部曲三个筛选;
  • 理解 computed 属性如何随 filterOption 变化。

这个阶段追求的是“最小闭环”:数据在手,类型可查,页面可切换。不要一开始就引入状态管理库、路由或后端接口。

6.2 发布展示页面前的检查清单

如果要把这个页面作为个人技术博客中的一个小案例发布,需要先完成一轮检查:

检查项说明
数据来源说明在页面底部或代码仓库 README 中写明片单整理时间和参考口径
影片名称一致性英文与中文片名不混用
排序按钮可用切换筛选条件后页面无空白
多端显示移动端卡片布局不挤压
类型校验通过运行 validateData.ts 无报错
版权风险自查不使用影视海报、剧照等受版权保护的图片作为素材

很多个人项目会忽略数据来源说明,导致后续自己都搞不清楚某些影片为什么没有被放进去。对于 MCU 蜘蛛侠这类跨制片公司、跨宇宙的资料,维护口径比追求大而全更重要。建议在 README 文件里也写出这样一段文字:

本片单仅用于技术演示,数据整理截至当前时间段的人工核对结果。漫威电影宇宙与索尼相关作品的归属关系可能随后续新片发生变化。

6.3 扩展成完整资料库的方向

同一套数据结构可以做很多功能扩展,技术侧常见方向如下:

  • 增加电影海报字段时,使用图片服务器或自托管图片,不要热链接第三方站点图片;
  • 增加“角色首次出现”年份统计,可以实现按演员分组的柱状图;
  • 引入 Pinia 或 Vuex 之前,先用状态提升和 props 解决多组件筛选;
  • 如果片单达到上百条,再考虑引入 JSON Schema 或 Zod,让运行时校验更加严格。

把数据模型设计成 universe、inMCU、mainActor 三个维度后,即使后续加入《蜘蛛侠:平行宇宙》等动画电影,也可以在不破坏结构的前提下扩展。比如新增一个新的 universe 枚举值 SonyAnimated,再补充一条记录即可。

对新手来说,最值得反复练习的并不是记忆 MCU 片单,而是理解同一份数据在不同查询条件下为什么会有不同结果。你可能需要先解释“MCU 主线电影”,再解释“历代蜘蛛侠出现过的所有作品”,最后通过一组布尔字段和枚举字段完成精确筛选。能把这个问题讲清楚,说明你已经理解了数据结构、筛选逻辑和页面渲染是如何共同工作的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询