Skip to content

Developer Console

gxxk-dev edited this page Mar 1, 2026 · 4 revisions

swm_dev 开发者控制台

本文档详细介绍 StudyWithMiku 的开发者控制台 swm_dev,包括使用方法、内部实现原理和完整的 API 参考。


概述

swm_dev 是 StudyWithMiku 的开发者调试工具,通过浏览器控制台暴露内部 API,方便开发者和高级用户进行调试、测试和数据管理。

设计目的

  1. 调试便利 - 无需修改代码即可测试功能
  2. 数据管理 - 直接操作歌单、记录等数据
  3. 状态检查 - 查看应用内部状态
  4. 功能验证 - 验证新功能的正确性

实现位置

src/dev/
├── index.js              # 主入口,创建 swm_dev 对象
└── help/
    ├── index.js          # 帮助系统核心
    ├── introspection.js  # 运行时反射工具
    └── formatter.js      # 控制台格式化输出

快速入门

访问方式

在浏览器开发者工具(DevTools)或 Eruda 控制台中直接访问全局对象 swm_dev

// 查看帮助
swm_dev.help()

// 查看特定模块
swm_dev.help('player')

// 或使用模块内置帮助
swm_dev.player.help()

基本示例

// 播放控制
await swm_dev.player.play()
await swm_dev.player.pause()

// 查看番茄钟状态
swm_dev.focus.state.value      // 'idle' | 'running' | 'paused'
swm_dev.focus.remaining.value  // 剩余秒数

// 显示通知
swm_dev.toast.show('success', '操作成功', '这是一条测试通知')

// 查看歌单列表
swm_dev.playlist.playlists.value

// 修改运行时配置
swm_dev.config.set('UI_CONFIG', 'TOAST_DEFAULT_DURATION', 5000)

帮助系统实现

架构概述

帮助系统由三个核心模块组成:

flowchart TB
    subgraph HelpIndexJS["help/index.js<br/>(帮助系统核心)"]
        createHelpSystem["createHelpSystem(): 创建帮助系统"]
        preloadMetadata["preloadMetadata(): 预加载 JSDoc 元数据"]
        injectModuleHelp["injectModuleHelp(): 为每个模块注入 help() 方法"]
    end

    subgraph IntrospectionJS["introspection.js<br/>(运行时反射)"]
        introspect["introspect(obj)"]
        extractSignature["extractSignature(fn)"]
        isVueRef["isVueRef(value)"]
        summarizeValue["summarizeValue(value)"]
    end

    subgraph FormatterJS["formatter.js<br/>(格式化输出)"]
        printOverview["printOverview()"]
        printModuleDetail["printModuleDetail()"]
        ConsoleBuilder["ConsoleBuilder"]
        STYLES["STYLES (颜色定义)"]
    end

    HelpIndexJS --> IntrospectionJS
    HelpIndexJS --> FormatterJS
Loading

反射机制 (introspection.js)

运行时分析对象结构,提取方法、属性和 Vue Ref 信息。

// src/dev/help/introspection.js

/**
 * 检测值是否为 Vue Ref
 */
const isVueRef = (value) =>
  value && typeof value === 'object' && '__v_isRef' in value

/**
 * 从函数中提取参数签名
 */
const extractSignature = (fn) => {
  const fnStr = fn.toString()
  // 匹配函数参数列表
  const match = fnStr.match(/^(?:async\s+)?(?:function\s*)?\w*\s*\(([^)]*)\)/)
  if (match) {
    return match[1].trim() || ''
  }
  // 箭头函数简写形式
  const arrowMatch = fnStr.match(/^(?:async\s+)?(\w+)\s*=>/)
  if (arrowMatch) {
    return arrowMatch[1]
  }
  return '...'
}

/**
 * 反射分析对象结构
 * @returns {{ methods: [], properties: [], refs: [] }}
 */
export const introspect = (obj) => {
  const info = { methods: [], properties: [], refs: [] }

  for (const key of Object.keys(obj)) {
    if (key.startsWith('_') || key === 'help') continue

    const value = obj[key]

    if (typeof value === 'function') {
      info.methods.push({
        name: key,
        signature: extractSignature(value),
        isAsync: value.constructor.name === 'AsyncFunction'
      })
    } else if (isVueRef(value)) {
      info.refs.push({
        name: key,
        refType: typeof value.value,
        currentValue: summarizeValue(value.value)
      })
    } else {
      info.properties.push({
        name: key,
        type: typeof value,
        currentValue: summarizeValue(value)
      })
    }
  }

  return info
}

格式化输出 (formatter.js)

提供带颜色的控制台输出:

// src/dev/help/formatter.js

const STYLES = {
  title: 'color: #39c5bb; font-weight: bold; font-size: 14px',
  module: 'color: #4fc3f7; font-weight: bold',
  method: 'color: #81c784',
  param: 'color: #90a4ae',
  type: 'color: #78909c; font-style: italic',
  ref: 'color: #ba68c8',
  // ...
}

// 颜色模式开关
let colorMode = false

export const setColorMode = (enabled) => {
  colorMode = !!enabled
}

// ConsoleBuilder 类用于构建带样式的输出
class ConsoleBuilder {
  constructor(useColor = colorMode) {
    this.useColor = useColor
    this.parts = []
    this.styles = []
  }

  add(text, style = '') {
    if (this.useColor && style) {
      this.parts.push(`%c${text}`)
      this.styles.push(style)
    } else {
      this.parts.push(text)
    }
    return this
  }

  print() {
    if (this.useColor) {
      console.log(this.parts.join(''), ...this.styles)
    } else {
      console.log(this.parts.join(''))
    }
  }
}

JSDoc 元数据加载

构建时从源代码提取的 JSDoc 注释会生成 /help-metadata.json,帮助系统会预加载这些元数据:

// src/dev/help/index.js

let helpMetadata = null
let metadataLoading = null

function preloadMetadata() {
  if (helpMetadata) return Promise.resolve(helpMetadata)
  if (metadataLoading) return metadataLoading

  metadataLoading = fetch('/help-metadata.json')
    .then(response => response.json())
    .then(data => {
      helpMetadata = data
      return data
    })
    .catch(e => {
      console.warn('[help] 无法加载帮助元数据:', e.message)
      helpMetadata = {}
      return {}
    })

  return metadataLoading
}

颜色模式切换

// 启用彩色输出
swm_dev.setColorMode(true)

// 关闭彩色输出
swm_dev.setColorMode(false)

// 查看当前模式
swm_dev.getColorMode()

模块总览

模块 对象路径 说明
player swm_dev.player 播放器控制(play/pause/seek/volume)
focus swm_dev.focus 番茄钟状态和控制
playlist swm_dev.playlist 歌单 CRUD 和管理
music swm_dev.music 音乐源管理和缓存
toast swm_dev.toast 通知系统
config swm_dev.config 运行时配置修改
auth swm_dev.auth 认证状态和操作
sync swm_dev.sync 云端数据同步
authStorage swm_dev.authStorage Token/认证信息存储工具
hooks swm_dev.hooks 钩子系统(Hook CRUD、Provider 管理)

auth 模块

swm_dev.auth 暴露 useAuth() composable 的完整实例,用于调试认证流程。

响应式引用 (Refs)

swm_dev.auth.user                    // Ref<Object|null> 当前用户信息
swm_dev.auth.isAuthenticated         // Ref<boolean> 是否已认证
swm_dev.auth.isLoading               // Ref<boolean> 操作加载中
swm_dev.auth.error                   // Ref<Object|null> 错误信息 {code, message, type}
swm_dev.auth.devices                 // Ref<Array> 已注册的 WebAuthn 设备
swm_dev.auth.authMethods             // Ref<Array> 所有认证方法 (WebAuthn + OAuth)
swm_dev.auth.availableProviders      // Ref<Object> 可用的认证提供商

方法

// 登录与注册
await swm_dev.auth.login('username')
await swm_dev.auth.register('username', 'deviceName')
swm_dev.auth.loginWithOAuth('github')

// 注销
await swm_dev.auth.logout()

// 设备管理
await swm_dev.auth.getDevices()
await swm_dev.auth.addDevice('新设备')
await swm_dev.auth.removeDevice('credentialId')

// 认证方法管理
await swm_dev.auth.getAuthMethods()
await swm_dev.auth.linkOAuthProvider('google')
await swm_dev.auth.unlinkOAuthAccount('accountId')

// 用户资料
await swm_dev.auth.updateProfile({ displayName: '新名称', email: 'new@email.com' })

// Token 管理
await swm_dev.auth.refreshTokenIfNeeded()

// 配置
await swm_dev.auth.fetchConfig()

// 工具
swm_dev.auth.clearError()

计算属性

swm_dev.auth.isWebAuthnSupported     // Computed<boolean> 浏览器是否支持 WebAuthn
swm_dev.auth.hasDevices              // Computed<boolean> 是否有已注册设备

使用示例

// 检查当前认证状态
console.log('已认证:', swm_dev.auth.isAuthenticated.value)
console.log('用户:', swm_dev.auth.user.value)

// 查看所有认证方法
await swm_dev.auth.getAuthMethods()
console.log(swm_dev.auth.authMethods.value)

// 查看设备列表
await swm_dev.auth.getDevices()
swm_dev.auth.devices.value.forEach(d => console.log(d.deviceName, d.lastUsedAt))

sync 模块

swm_dev.sync 暴露 useDataSync() composable 的完整实例,用于调试数据同步。

响应式引用 (Refs)

swm_dev.sync.syncStatus              // Ref<Object> 各数据类型的同步状态
swm_dev.sync.isSyncing               // Ref<boolean> 是否正在同步
swm_dev.sync.lastSyncTime            // Ref<number> 最后同步时间戳
swm_dev.sync.pendingChanges          // Ref<Array> 离线队列中的待处理变更
swm_dev.sync.error                   // Ref<Object|null> 同步错误

方法

// 全量同步
await swm_dev.sync.triggerSync()

// 单类型操作
await swm_dev.sync.uploadData('focus_records', recordsArray)
await swm_dev.sync.downloadData('focus_records')

// 队列管理
swm_dev.sync.queueChange('focus_settings', settingsObj)
await swm_dev.sync.processQueue()
swm_dev.sync.cancelPendingSync()

// 工具
swm_dev.sync.clearError()

计算属性

swm_dev.sync.hasPendingChanges       // Computed<boolean> 是否有待处理变更
swm_dev.sync.isOnline                // Computed<boolean> 网络是否在线
swm_dev.sync.canSync                 // Computed<boolean> 是否可以同步

使用示例

// 检查同步状态
console.log('正在同步:', swm_dev.sync.isSyncing.value)
console.log('待处理变更:', swm_dev.sync.pendingChanges.value.length)
console.log('可以同步:', swm_dev.sync.canSync.value)

// 手动触发全量同步
await swm_dev.sync.triggerSync()

// 查看各类型同步状态
Object.entries(swm_dev.sync.syncStatus.value).forEach(([type, status]) => {
  console.log(type, '版本:', status.version, '已同步:', status.synced)
})

authStorage 模块

swm_dev.authStorage 直接暴露 authStorage 工具模块,用于检查和操作 Token 存储。

方法

// Token 操作
swm_dev.authStorage.getAccessToken()           // 获取内存中的 Access Token
swm_dev.authStorage.getTokenType()             // 获取 Token 类型 (默认 'Bearer')
swm_dev.authStorage.getTokenExpiresAt()        // 获取过期时间戳
swm_dev.authStorage.isTokenExpiringSoon(60)     // 是否即将过期 (阈值秒数)
swm_dev.authStorage.isTokenExpired()            // 是否已过期
swm_dev.authStorage.getTokens()                // 获取完整 Token 对象
swm_dev.authStorage.clearTokens()              // 清除所有 Token

// 用户信息
swm_dev.authStorage.getUser()                  // 获取存储的用户信息
swm_dev.authStorage.saveUser(userObj)           // 保存用户信息
swm_dev.authStorage.clearUser()                // 清除用户信息

// 设备 ID
swm_dev.authStorage.getDeviceId()              // 获取设备 ID
swm_dev.authStorage.saveDeviceId('id')         // 保存设备 ID
swm_dev.authStorage.clearDeviceId()            // 清除设备 ID

// 综合操作
swm_dev.authStorage.hasValidAuth()             // 快速检查认证状态
swm_dev.authStorage.clearAllAuthData()         // 清除所有认证数据
swm_dev.authStorage.cleanupLegacyKeys()        // 清理旧版键名

使用示例

// 检查认证是否有效
console.log('认证有效:', swm_dev.authStorage.hasValidAuth())
console.log('Token 过期:', swm_dev.authStorage.isTokenExpired())

// 查看用户信息
console.log(swm_dev.authStorage.getUser())

// 调试 Token 状态
const expiresAt = swm_dev.authStorage.getTokenExpiresAt()
if (expiresAt) {
  const remaining = (expiresAt - Date.now()) / 1000
  console.log('Token 剩余时间:', remaining, '秒')
}

// 紧急清除所有认证数据
swm_dev.authStorage.clearAllAuthData()

hooks 模块

swm_dev.hooks 暴露 useHooks() composable 的完整实例,用于调试钩子系统。

响应式引用 (Refs)

swm_dev.hooks.hooks                   // Ref<Array> 所有钩子列表

方法

// CRUD 操作
swm_dev.hooks.addHook({
  name: '测试钩子',
  provider: 'notification',
  trigger: 'focus_completed',
  action: { title: '测试', body: '测试通知' }
})  // → 返回新钩子 ID

swm_dev.hooks.updateHook('hookId', { enabled: false })
swm_dev.hooks.removeHook('hookId')  // built-in 不可删除
swm_dev.hooks.clearCustomHooks()     // 清除所有非 built-in 钩子

// 预设
swm_dev.hooks.applyPreset(preset)

// 查询
swm_dev.hooks.getHooksByProvider('notification')
swm_dev.hooks.getHooksByProvider('estim')

// 云同步
swm_dev.hooks.getHooksData()          // 获取用于云同步的数据
swm_dev.hooks.loadFromCloud(data)     // 从云端加载

Estim 解锁

电刺激 Provider 默认不可用,需要解锁:

// 解锁 estim provider
localStorage.setItem('swm_coyote_unlocked', 'true')
// 然后刷新页面,estim provider 会自动注册

Provider Registry

// 查看已注册的 providers
swm_dev.hooks.providerRegistry.getAll()

// 检查某个 provider 是否已注册
swm_dev.hooks.providerRegistry.has('estim')  // false(默认未解锁)
swm_dev.hooks.providerRegistry.has('notification')  // true

使用示例

// 查看所有钩子
swm_dev.hooks.hooks.value.forEach(h =>
  console.log(h.name, h.provider, h.trigger, h.enabled ? '✓' : '✗')
)

// 添加一个测试通知钩子
const id = swm_dev.hooks.addHook({
  name: '专注开始通知',
  provider: 'notification',
  trigger: 'focus_start',
  action: { title: '开始专注', body: '加油!' }
})

// 临时禁用某个钩子
swm_dev.hooks.updateHook(id, { enabled: false })

// 查看通知类型的钩子
swm_dev.hooks.getHooksByProvider('notification')

// 查看已注册的 provider 列表
swm_dev.hooks.providerRegistry.getAll().map(p => p.id)
// → ['notification', 'sound', 'push'](未解锁 estim)

Clone this wiki locally