PlayStation 5 的存储扩展主要依靠 M.2 SSD 插槽,但系统自带的存储管理界面功能有限,无法批量迁移游戏、查看详细健康度或自定义命名。如果将 Vue 3 工程化能力引入这一场景,可以构建一个独立的 Web 控制台,通过后端服务代理访问主机 API 或读取本地数据库。这个控制台不仅适合个人玩家管理多块扩展盘,也能作为店铺或工作室批量维护设备的工具。

工程化初始化与目录设计
使用 Vite 初始化 Vue 3 与 TypeScript 模板是最快捷的起点。执行 npm create vite@latest ps5-storage-manager -- --template vue-ts 之后,建议立刻配置路径别名和严格模式。路径别名能避免组件导入时出现 ../../ 这种脆弱写法,而严格模式配合 ESLint 与 Prettier 可以统一代码风格,减少协作中的格式争议。
目录结构可以按业务域划分,而不是简单按文件类型堆叠。推荐如下结构:
src/ ├── api/ # 后端接口封装 ├── components/ # 通用 UI 组件 │ └── storage/ # 存储扩展相关组件 ├── composables/ # 组合式函数 ├── stores/ # Pinia 状态 ├── types/ # TypeScript 类型定义 ├── views/ # 页面级组件 └── utils/ # 工具函数
在 vite.config.ts 中设置 alias,在 tsconfig.json 中开启 strict 与路径映射。通过这种工程化基础,后续新增功能时开发者可以快速定位文件,不需要在庞大目录中寻找。
存储扩展状态建模与 API 抽象
PS5 存储扩展涉及多个实体:扩展盘本身、内置存储中的游戏、迁移任务以及格式化操作。建议为每个实体定义独立的 TypeScript 接口。例如 StorageDevice 包含 id、name、capacity、used、health、interfaceType 等字段;MigrationTask 包含 sourceGameId、targetDeviceId、progress、status、errorMessage 等字段。清晰的类型定义能让组件与 store 之间减少类型断言。
前端不直接访问硬件,而是通过后端 API 完成操作。可以封装一个 api/storage.ts,使用 fetch 或 axios 暴露 listDevices、formatDevice、migrateGame、getMigrationProgress 等方法。开发阶段可以用本地 mock 数据模拟响应延迟和进度变化,便于调试 UI 状态。
// types/storage.ts
export interface StorageDevice {
id: string
name: string
capacity: number // GB
used: number
health: 'healthy' | 'warning' | 'critical'
interfaceType: 'PCIe Gen4' | 'PCIe Gen3'
isFormatted: boolean
}
export interface MigrationTask {
id: string
gameName: string
sourceDeviceId: string
targetDeviceId: string
progress: number
status: 'pending' | 'running' | 'paused' | 'completed' | 'failed'
error?: string
}
接着用 Pinia 创建 useStorageStore。store 中维护 devices 数组、activeMigrations 数组,以及 getters 计算总可用空间、按设备分组游戏。actions 调用 API 并更新状态。通过将异步逻辑集中在 store,组件层可以保持简洁。
// stores/storage.ts
import { defineStore } from 'pinia'
import { fetchDevices, startMigration } from '@/api/storage'
import type { StorageDevice, MigrationTask } from '@/types/storage'
export const useStorageStore = defineStore('storage', {
state: () => ({
devices: [] as StorageDevice[],
migrations: [] as MigrationTask[],
loading: false
}),
getters: {
totalUsed(state) {
return state.devices.reduce((sum, d) => sum + d.used, 0)
},
runningTasks(state) {
return state.migrations.filter(t => t.status === 'running')
}
},
actions: {
async loadDevices() {
this.loading = true
try {
this.devices = await fetchDevices()
} finally {
this.loading = false
}
},
async migrateGame(gameName: string, sourceId: string, targetId: string) {
const task = await startMigration(gameName, sourceId, targetId)
this.migrations.push(task)
return task.id
}
}
})
关键交互组件实现
在界面层,用户最常用的是设备列表和迁移对话框。设备列表需要展示每个扩展盘的容量、健康状态和格式化按钮。格式化是危险操作,因此必须使用二次确认对话框,并在执行期间显示加载态与禁用按钮。
迁移对话框则应包含源设备、目标设备、游戏选择以及实时进度。由于 PS5 游戏体积较大,迁移任务通常耗时较长,进度展示不能只依赖轮询接口拉取,还可以考虑使用 WebSocket 推送更新。本项目简化实现为 setInterval 定时调用 getMigrationProgress 更新 Pinia store。
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { useStorageStore } from '@/stores/storage'
import StorageDeviceCard from '@/components/storage/StorageDeviceCard.vue'
import MigrationDialog from '@/components/storage/MigrationDialog.vue'
const store = useStorageStore()
const showMigration = computed(() => store.migrations.some(t => t.status === 'running'))
onMounted(() => {
store.loadDevices()
})
</script>
<template>
<div class="storage-page">
<h2>存储扩展设备</h2>
<div v-if="store.loading">加载中...</div>
<div v-else class="device-grid">
<StorageDeviceCard
v-for="device in store.devices"
:key="device.id"
:device="device"
/>
</div>
<MigrationDialog v-if="showMigration" />
</div>
</template>
组件内部通过 props 接收设备对象,使用计算属性展示格式化后的容量。格式化按钮触发一个确认弹窗,确认后调用 store 的 formatDevice action。由于格式化会清空数据,按钮文案采用警示色,并在执行中显示禁用的加载状态。
性能优化与部署策略
当内置存储里有大量游戏时,迁移列表可能包含数百项。直接渲染全部行会导致页面卡顿。可以引入虚拟滚动库或使用 IntersectionObserver 实现懒加载列表。如果项目规模较小,也可以先用 computed 对游戏列表按名称或大小排序,减少用户查找时间。
路由层面使用动态 import 实现代码分割。例如 /devices 和 /migration 页面分别打包成独立 chunk,首屏只加载设备列表相关代码。对于 API 请求,可设置缓存策略,避免每次进入页面都重新拉取静态信息。设备健康度等变化不频繁的数据可缓存 30 秒,而迁移进度必须实时刷新。
部署时,由于前端仅作为管理界面,通常将其构建为纯静态资源,通过 Nginx 反向代理到后端 API。确保后端接口启用 CORS 或同域部署,避免跨域问题。如果采用 Vite 构建,运行 npm run build 后输出 dist 目录,将 dist 上传到 CDN 或静态服务器即可。对于更高安全要求的场景,可以在 Nginx 层增加基础认证或 IP 白名单。