12451 b553c4e519 feat: API 统一 + 设备详情补全 + H5 导航优化
- API 路由统一: 移除 /api/h5/,统一 /api/pms/ 入口
- 二进制改名: api-server -> pms-api(与服务名一致)
- nginx: 合并 /api/ + /pms/api/ 为 /api/pms/
- 新增 docs/API.md 接口文档
- 后端: CabinetDetail 增加 rssi/pow_fail/address/soc 等字段
- Redis 管道批量读取仓体实时数据(SOC/电压/电流/功率)
- DB: 所有 TIMESTAMP 列改为 DATETIME(修复类型不匹配)
- 用户管理: 新增创建时间/修改时间列,状态改用 Switch
- 角色管理/用户管理: 操作按钮改为图标
- PC 设备详情: 基本信息补全 RSSI/ICCID/电源/地址
- PC 卡片: 通道统计改用 Lucide 图标,颜色统一
- PC 二维码: canvas 渲染,含编号文字,支持右键下载
- H5: 按 uni-app 规范修正导航(navigateTo 替代 reLaunch)
- H5: 详情页加返回导航条
- H5: 扫码支持摄像头(jsQR)+ ?id= 参数
- H5: 底部 tabBar 在所有页面常显
- H5: 首页统计改为 6 项(设备/通道/空闲/充电/充满/故障)
- H5: 下拉刷新 / 上拉加载
- 产品首页: 添加业务联系
- 文档: 清理过时文件,补充缺失规范章节
2026-07-04 04:03:02 +08:00

4.3 KiB

Use Symbol Injection Keys in Plugins

Rule

When using provide/inject in Vue plugins, use Symbol keys (preferably with InjectionKey<T> for TypeScript) instead of string keys to prevent naming collisions and ensure type safety.

Why This Matters

  1. Collision prevention: String keys like 'i18n' or 'api' can easily collide between multiple plugins or different parts of your application.

  2. Type safety: TypeScript's InjectionKey<T> provides automatic type inference when using inject().

  3. Refactoring safety: Symbols are unique, so renaming is safe and explicit.

  4. Debugging: Symbols can have descriptive names for debugging while remaining unique.

Bad Practice

// plugin.ts
export default {
  install(app) {
    // String key - can collide with other plugins!
    app.provide('http', axios)
    app.provide('config', appConfig)
  }
}

// component.vue
const http = inject('http')  // Type is unknown
const config = inject('config')  // Type is unknown

// Another plugin accidentally uses the same key
otherPlugin.install = (app) => {
  app.provide('http', differentHttpClient)  // COLLISION! Overwrites first
}

Good Practice

// plugins/keys.ts
import type { InjectionKey } from 'vue'
import type { AxiosInstance } from 'axios'

export interface AppConfig {
  apiUrl: string
  timeout: number
}

// Typed injection keys - unique and type-safe
export const httpKey: InjectionKey<AxiosInstance> = Symbol('http')
export const configKey: InjectionKey<AppConfig> = Symbol('appConfig')

// plugin.ts
import { httpKey, configKey } from './keys'

export default {
  install(app) {
    app.provide(httpKey, axios)
    app.provide(configKey, { apiUrl: '/api', timeout: 5000 })
  }
}

// component.vue
<script setup lang="ts">
import { inject } from 'vue'
import { httpKey, configKey } from '@/plugins/keys'

// Fully typed! No 'unknown' type
const http = inject(httpKey)  // Type: AxiosInstance | undefined
const config = inject(configKey)  // Type: AppConfig | undefined
</script>

Providing Default Values with Type Safety

// With InjectionKey, default values are type-checked
const config = inject(configKey, {
  apiUrl: '/default-api',
  timeout: 3000
})
// Type: AppConfig (not undefined because default provided)

// Type error if default doesn't match!
const config = inject(configKey, {
  apiUrl: '/api'
  // Missing 'timeout' - TypeScript error!
})

Organizing Injection Keys

For larger applications, organize keys by domain:

// injection-keys/index.ts
export * from './auth'
export * from './i18n'
export * from './http'

// injection-keys/auth.ts
import type { InjectionKey } from 'vue'

export interface AuthService {
  login: (credentials: Credentials) => Promise<User>
  logout: () => Promise<void>
  currentUser: Ref<User | null>
}

export const authKey: InjectionKey<AuthService> = Symbol('auth')

// injection-keys/i18n.ts
export interface I18n {
  t: (key: string, params?: Record<string, string>) => string
  locale: Ref<string>
}

export const i18nKey: InjectionKey<I18n> = Symbol('i18n')

Creating a useInject Helper

For cleaner component code, create typed composables:

// composables/useAuth.ts
import { inject } from 'vue'
import { authKey, type AuthService } from '@/injection-keys'

export function useAuth(): AuthService {
  const auth = inject(authKey)
  if (!auth) {
    throw new Error('Auth plugin not installed. Did you forget app.use(authPlugin)?')
  }
  return auth
}

// component.vue
<script setup>
import { useAuth } from '@/composables/useAuth'

const auth = useAuth()  // Type: AuthService (not undefined)
await auth.login(credentials)
</script>

When String Keys Are Acceptable

  1. Internal plugin use: If both provide and inject are in the same plugin file
  2. Simple applications: Very small apps with no collision risk
  3. Dynamic keys: When the key name must be computed at runtime

Even then, consider using a namespaced string:

// Better than plain 'config'
app.provide('myPlugin:config', config)

References