基于 SwiftUI 的卡拉彼丘(Strinova / 卡拉彼丘)游戏 Wiki 浏览器,数据源为 biligame wiki。
⚠️ 本项目为纯粹的粉丝作品,仅供学习和交流使用,不涉及任何商业盈利目的。 游戏「卡拉彼丘」及其所有相关素材(角色、武器、图片、名称等)的知识产权均归其版权方所有。
本项目的功能设计与数据解析思路深受以下开源项目的启发,在此向原作者表示衷心感谢:
- CalabiYauVoice_GUI — 由 znzsofficial 开发的 Compose Multiplatform 版卡拉彼丘 Wiki 客户端。本项目的模块划分、数据解析逻辑与 API 调用方式均参考了该项目的优秀设计。(原项目采用 Apache-2.0 许可证)
所有游戏数据均来源于 哔哩哔哩游戏 Wiki - 卡拉彼丘,感谢 Wiki 维护者们的辛勤贡献。
| 项目 | 说明 |
|---|---|
| 语言 | Swift 6.0 |
| UI 框架 | SwiftUI (iOS 26.0+) |
| 架构模式 | MVVM(View + ViewModel + Service) |
| 项目管理 | XcodeGen (project.yml) |
| 最低版本 | iOS 26.0 |
| 开发语言 | 简体中文 (zh-Hans) |
| Bundle ID | hariteki.StrinovaWiki |
项目坚持 原生优先 策略,几乎全部使用 Apple 原生 API。唯一引入的第三方库:
| 依赖 | 用途 | 来源 |
|---|---|---|
| Ifrit (>=4.0.0) | SwiftUI 动画/辅助工具 | SPM |
重要:不引入 Alamofire、Kingfisher、SwiftyJSON 等第三方库。网络用原生
URLSession,图片用原生AsyncImage+ 自定义ImageCacheService,HTML 解析用原生NSRegularExpression+ 自定义解析函数。
# 1. 生成 Xcode 项目文件
xcodegen generate
# 2. 打开项目
open StrinovaWiki.xcodeproj
# 3. 或直接命令行构建到模拟器
xcodebuild -project StrinovaWiki.xcodeproj -scheme StrinovaWiki \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' buildDerivedData/ 目录已在 .gitignore 中忽略。不要编辑 project.pbxproj,所有项目配置通过 project.yml 管理。
StrinovaWiki/
├── StrinovaWikiApp.swift # @main 入口 + AppDelegate
├── Info.plist
├── Assets.xcassets/ # 颜色、图标等资源
│
├── Models/ # Codable 数据模型(每个模块一个文件)
│ ├── CommonModels.swift # AppRoute、LoadState、NetworkError 等全局类型
│ ├── WikiModels.swift # Wiki API 通用响应模型
│ ├── CharacterModels.swift # 角色相关模型
│ ├── WeaponModels.swift # 武器相关模型
│ ├── GalleryModels.swift # 壁纸/表情包模型
│ ├── MeowModels.swift # 喵言喵语模型
│ ├── BiochemicalCardModels.swift
│ ├── ... (其他模块模型)
│
├── Services/ # 网络、缓存、本地存储(均为 actor 或 @MainActor class)
│ ├── WikiNetworkService.swift # 核心网络服务:MediaWiki API + HTML 解析
│ ├── WikiNetworkService+*.swift # 各模块的网络请求扩展
│ ├── CacheService.swift # 离线缓存(FileManager 持久化 + TTL)
│ ├── ImageCacheService.swift # 图片双级缓存(内存 + 磁盘)
│ ├── LocalStorageService.swift # UserDefaults 封装(主题、搜索历史等)
│ └── WikiIndexService.swift # Wiki 索引检索
│
├── ViewModels/ # @MainActor ObservableObject
│ ├── BaseViewModel.swift # ViewModel 基类:isLoading、errorMessage、loadCacheThenFetch
│ ├── HomeViewModel.swift # 首页数据 + 菜单配置
│ ├── GenericWikiPageViewModel.swift # 通用 Wiki 页面 ViewModel
│ ├── ... (各模块 ViewModel)
│
├── Views/ # SwiftUI 页面(按功能模块分目录)
│ ├── ContentView.swift # 根视图:TabView + NavigationStack 路由分发
│ ├── Home/HomeScreen.swift # 首页(模块入口卡片网格)
│ ├── Character/ # 角色列表 + 详情
│ ├── Weapon/ # 武器列表 + 详情
│ ├── Gallery/ # 壁纸 + 表情包
│ ├── Meow/MeowScreen.swift
│ ├── Biochemical/BiochemicalCardScreen.swift
│ ├── Decoration/ # 玩家装饰
│ ├── GameMode/GameModeScreen.swift
│ ├── Core/CoreScreen.swift # 游戏核心模块聚合页
│ ├── Map/, Item/, History/, ...
│ └── WikiBrowserView.swift # WKWebView 内嵌浏览器
│
├── Components/ # 可复用 UI 组件
│ ├── WikiCard.swift # 通用卡片容器
│ ├── CachedAsyncImage.swift # 带持久化缓存的异步图片
│ ├── GIFWebView.swift # GIF 动图渲染(WKWebView)
│ ├── SearchBar.swift
│ ├── FilterChipGroup.swift # 筛选标签组
│ ├── ErrorStateView.swift # 错误状态 + 重试按钮
│ ├── LoadingView.swift
│ ├── EmptyStateView.swift
│ ├── SectionHeader.swift
│ ├── ImagePreviewView.swift # 图片全屏预览
│ └── WikiBottomSheet.swift
│
└── Utils/
├── Theme.swift # 全局设计 Token(间距、圆角、颜色)
├── Extensions.swift # View、Color、String 扩展
└── WikiParsingHelpers.swift # HTML/Wikitext 解析工具函数
Wiki API (biligame.com)
│
▼
WikiNetworkService (actor, URLSession + NSRegularExpression)
│ fetchParseHtml / fetchJSON / fetchAskResult
▼
ViewModel (@MainActor, ObservableObject)
│ loadCacheThenFetch → 先读缓存 → 网络更新 → 写缓存
▼
View (SwiftUI)
@StateObject viewModel → 渲染 UI
所有页面导航通过 AppRoute 枚举(定义在 Models/CommonModels.swift)统一管理:
enum AppRoute: Hashable {
case home
case characterDetail(name: String, imageUrl: String?)
case weaponDetail(name: String, imageUrl: String?)
case decorationCategory(type: DecorationType)
case wikiBrowser(urlString: String)
// ... 约 20+ 路由
}ContentView管理 4 个 Tab(首页/武器/角色/设置),每个 Tab 内嵌独立NavigationStack- 路由分发在
ContentView.destination(for:)中集中处理 - 详情页通过路由参数(name, imageUrl)定位,ViewModel 按参数加载
WikiNetworkService 是 actor(线程安全),采用 extension 模式组织各模块:
WikiNetworkService.swift # 核心:fetchCharacters, fetchCategoryMembers, resolveImageURLs, search, fetchParseHtml, fetchAskResult
+CharacterDetail.swift # fetchCharacterDetail(name:)
+WeaponList.swift # fetchWeapons()
+WeaponDetail.swift # fetchWeaponDetail(name:)
+Decoration.swift # fetchDecorations(type:)
+Gallery.swift # fetchWallpapers(), fetchStickers()
+Meow.swift # fetchMeowQuotes()
+BiochemicalCard.swift # fetchBiochemicalCards(platform:)
+Map.swift, +Story.swift, +Chronicle.swift, +Collab.swift, +Item.swift, +Achievement.swift, +GameMode.swift, +Activity.swift, +Announcement.swift, +Submission.swift, +Generic.swift
添加新模块时的步骤:
- 在
Models/新建数据模型文件 - 在
Services/新建WikiNetworkService+新模块.swiftextension - 在
ViewModels/新建 ViewModel(继承BaseViewModel,使用loadCacheThenFetch) - 在
Views/新建页面 - 在
CommonModels.swift的AppRoute添加新路由 - 在
ContentView.destination(for:)添加路由分发
所有 ViewModel 继承 BaseViewModel,遵循统一模式:
@MainActor
final class SomeViewModel: BaseViewModel {
@Published var data: SomeData = []
func load() async {
guard data.isEmpty else { return } // 避免重复请求
await loadCacheThenFetch(
cacheType: .genericPage,
cacheKey: "some_cache_key",
setValue: { self.data = $0 },
fetch: { try await WikiNetworkService.shared.fetchSomeData() }
)
}
}loadCacheThenFetch 自动处理:先读缓存立即展示 → 网络请求 → 更新 UI → 写入缓存 → 错误处理。
| 场景 | 方案 |
|---|---|
| 一般图片 | CachedAsyncImage — 内存+磁盘双级缓存 |
| GIF 动图 | GIFWebView — WKWebView 渲染原图(不缩放) |
| 壁纸缩略图 | 低分辨率(原图缩略图 URL) |
| 表情包非 GIF | 低分辨率缩略图 |
| 表情包 GIF | 原图 URL,WKWebView 渲染 |
图片 URL 处理规则(定义在 WikiParsingHelpers.swift):
normalizeImageURL(_:)— 处理//和/开头的相对路径,补全https:或wiki.biligame.comthumbnailImageURL(_:width:)— 构造 200px 缩略图路径originalFromThumbnail(_:)— 从缩略图 URL 还原原图 URL- 所有外部图片 URL 必须通过
normalizeImageURL处理,确保路径不重复拼接/klbq/
所有 UI 尺寸/间距/圆角/颜色必须使用 Theme 枚举,禁止硬编码数值:
| 类别 | 属性 | 说明 |
|---|---|---|
| 圆角 | chipRadius=12, cardRadius=16, sheetRadius=28, dialogRadius=28 |
卡片、弹窗使用 |
| 间距 | tiny=2 ~ xxxLarge=32 |
8 个等级 |
| 图标 | iconSmall=18, iconMedium=20, iconLarge=24 |
SF Symbols 尺寸 |
| 颜色 | brandColor=#1A73E8 |
品牌主色 |
- 模块入口按钮:3 列
LazyVGrid,72×72 圆形图标,24pt 圆角,标题在按钮下方(.subheadline.weight(.semibold)) - 预览卡片描边:使用
.overlay+.stroke绘制在内容上方,内容区加.padding(4)防止内容贴边 - 磨砂描边效果:在描边下方叠加
.ultraThinMaterial+.blur(radius: 1) - 筛选菜单:小巧、磨砂半透明背景、大圆角
- Toast 通知:顶部显示,1.5 秒自动消失
- 生化卡牌:固定 160pt 宽、3:4 比例、12pt 内边距、向内 3pt 描边(
strokeBorder) - 生化卡牌稀有度颜色:Refined=蓝(#1E3A8A)、Excellent=紫(#4C1D95)、Perfect=金(#78350F);暗黑模式下自动切换亮色
.task不能放在条件分支内 — 状态变化会导致 task 取消。必须放在稳定容器 View 上。system .popover在 iPhone 上可能全屏 — 自定义卡片 popover 需用overlay实现。- SPM 动态库必须 embed —
project.yml中embed: true,否则运行时报Library not loaded。 - 第三方输入法按回车清空搜索文本 —
MeowScreen.swift中 submitSearch 加了 0.05 秒延迟 + 文本快照恢复。 - WAF/限流重试 — 567/503/429 状态码自动重试一次(
WikiNetworkService.perform)。 - 筛选
.svg.png图标 — 仅用后缀匹配,不要用\d+px-前缀正则(会误杀有效缩略图)。
- 所有中文注释使用
// MARK: -分组 - 文件顶部加模块说明注释(如
// MARK: - 角色列表 ViewModel) - ViewModel 内部需要缓存的对象定义 fileprivate
Codablestruct - 网络请求方法中 HTML 解析函数标记为
nonisolated(避免阻塞 actor)
所有项目配置在 project.yml 中修改后执行 xcodegen generate 重新生成 .xcodeproj。不要直接编辑 .pbxproj。
| 模块 | 页面文件 | ViewModel | 网络服务扩展 |
|---|---|---|---|
| 首页 | Home/HomeScreen.swift |
HomeViewModel |
WikiNetworkService(核心) |
| 角色列表 | Character/CharacterListScreen.swift |
CharacterListViewModel |
+CharacterDetail |
| 角色详情 | Character/CharacterDetailScreen.swift |
CharacterDetailViewModel |
+CharacterDetail |
| 武器列表 | Weapon/WeaponListScreen.swift |
WeaponListViewModel |
+WeaponList |
| 武器详情 | Weapon/WeaponDetailScreen.swift |
WeaponDetailViewModel |
+WeaponDetail |
| 壁纸 | Gallery/WallpaperScreen.swift |
内联 | +Gallery |
| 表情包 | Gallery/StickerScreen.swift |
内联 | +Gallery |
| 图库入口 | Gallery/GalleryScreen.swift |
- | - |
| 喵言喵语 | Meow/MeowScreen.swift |
内联 | +Meow |
| 生化卡牌 | Biochemical/BiochemicalCardScreen.swift |
BiochemicalCardViewModel |
+BiochemicalCard |
| 玩家装饰 | Decoration/DecorationScreen.swift |
DecorationCategoryViewModel |
+Decoration |
| 战斗模式 | GameMode/GameModeScreen.swift |
GameModeViewModel |
+GameMode |
| 地图 | Map/MapScreen.swift |
MapViewModel |
+Map |
| 道具 | Item/ItemScreen.swift |
ItemViewModel |
+Item |
| 剧情故事 | History/StoryScreen.swift |
StoryViewModel |
+Story |
| 游戏历史 | History/GameHistoryScreen.swift |
GameHistoryViewModel |
+Chronicle |
| 成就 | Achievement/AchievementScreen.swift |
AchievementViewModel |
+Achievement |
| 公告 | Announcement/AnnouncementScreen.swift |
AnnouncementViewModel |
+Announcement |
| 活动 | Activity/ActivityScreen.swift |
ActivityViewModel |
+Activity |
| 联动 | Collaboration/CollaborationScreen.swift |
内联 | +Collab |
| 投稿 | Submission/SubmissionScreen.swift |
SubmissionViewModel |
+Submission |
| 玩家等级 | PlayerLevel/PlayerLevelScreen.swift |
内联 | +Generic |
| 印记 | Imprint/ImprintScreen.swift |
内联 | +Generic |
| 设置 | Settings/SettingsScreen.swift |
SettingsViewModel |
- |
| 通用Wiki页 | Common/GenericWikiPageScreen.swift |
GenericWikiPageViewModel |
+Generic |
| Wiki浏览器 | WikiBrowserView.swift |
- | - |
| 游戏核心聚合 | Core/CoreScreen.swift |
- | - |
数据源:https://wiki.biligame.com/klbq/api.php
核心 API 调用方式(封装在 WikiNetworkService):
| API action | 用途 | 对应方法 |
|---|---|---|
parse (page) |
获取 Wiki 页面渲染 HTML | fetchParseHtml(page:) |
parse (text) |
渲染 wikitext 为 HTML(POST) | fetchParseHtml(text:) |
ask |
Semantic MediaWiki 查询(POST) | fetchAskResult(query:) |
query + categorymembers |
获取分类成员列表 | fetchCategoryMembers(category:) |
query + imageinfo |
批量解析图片 URL | resolveImageURLs(titles:) |
query + search |
全文搜索 | search(query:) |
请求头包含随机 User-Agent、zh-CN Accept-Language、Referer 模拟浏览器,以降低 WAF 拦截概率。POST 请求使用 form-urlencoded 编码。
- 基于 FileManager,按
CacheType分目录存储 - 每个 CacheType 有不同 TTL(1小时~7天)
- 缓存 Key 使用 MD5 哈希
- 用户可在设置中开启"永不过期"
- 双级缓存:NSCache(50MB 内存,200 张上限)+ 磁盘持久化
- 缓存 Key 使用 SHA256 哈希 URL
- 提供
clearAll()清除所有缓存
- 支持浅色/深色/跟随系统三种主题模式,通过
LocalStorageService.themeMode持久化 - UI 颜色使用
.primary/.secondary等语义色,自动适配深浅模式 - 中文界面,无多语言国际化需求
游戏「卡拉彼丘(Strinova)」及其所有相关素材(包括但不限于角色形象、武器设计、游戏图标、背景图片、音效、名称、商标等)的知识产权均归其各自的版权方(深圳市腾讯计算机系统有限公司及其关联公司、或相关权利人)所有。本项目不包含任何游戏官方原始素材文件,仅通过公开 API 从哔哩哔哩游戏 Wiki 获取公开信息进行展示。
本项目为个人独立开发的粉丝作品,与游戏官方或哔哩哔哩官方无任何隶属、赞助或认可关系。本项目的任何内容均不代表游戏官方或版权方的立场和观点。
本项目不以任何方式进行商业盈利,包括但不限于:
- 不出售、不租赁、不转让本项目的任何部分
- 不通过本项目获取任何形式的直接经济收益
- 不包含任何内购、订阅、广告或付费功能
本项目仅供卡拉彼丘爱好者学习、交流和便利查阅游戏资料使用。
本项目按「原样」提供,不附带任何明示或默示的担保,包括但不限于适销性、特定用途适用性及非侵权性的担保。使用本项目的风险由用户自行承担。在任何情况下,作者均不对因使用或无法使用本项目而导致的任何损失承担责任。
本项目所有游戏数据均通过公开 API 从 哔哩哔哩游戏 Wiki - 卡拉彼丘 获取。用户在使用本项目时应遵守:
- 哔哩哔哩游戏 Wiki 的使用条款和社区规范
- 卡拉彼丘游戏官方的用户协议
- 相关法律法规
如 Wiki 数据源发生变更或停止服务,本项目的部分功能可能无法正常使用。
本项目采用 MIT License 开源许可证。