@zeewain/3d-avatar-sdk 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +6 -5
  2. package/dist/assets/Build/webgl.data.unityweb +0 -0
  3. package/dist/assets/Build/webgl.framework.js.unityweb +0 -0
  4. package/dist/assets/Build/webgl.wasm.unityweb +0 -0
  5. package/dist/examples/test-umd/index.html +762 -0
  6. package/dist/examples/test-vue2/.eslintignore +45 -0
  7. package/dist/examples/test-vue2/.eslintrc.js +174 -0
  8. package/dist/examples/test-vue2/.stylelintignore +50 -0
  9. package/dist/examples/test-vue2/.stylelintrc.js +79 -0
  10. package/dist/examples/test-vue2/README.md +139 -0
  11. package/dist/examples/test-vue2/babel.config.js +14 -0
  12. package/dist/examples/test-vue2/package.json +53 -0
  13. package/dist/examples/test-vue2/pnpm-lock.yaml +8776 -0
  14. package/dist/examples/test-vue2/public/index.html +19 -0
  15. package/dist/examples/test-vue2/setup.js +170 -0
  16. package/dist/examples/test-vue2/src/App.vue +943 -0
  17. package/dist/examples/test-vue2/src/components/BroadcastAPI.vue +666 -0
  18. package/dist/examples/test-vue2/src/components/CameraAPI.vue +414 -0
  19. package/dist/examples/test-vue2/src/components/GlobalConfig.vue +200 -0
  20. package/dist/examples/test-vue2/src/components/InfoCards.vue +294 -0
  21. package/dist/examples/test-vue2/src/components/InitAPI.vue +334 -0
  22. package/dist/examples/test-vue2/src/components/LogPanel.vue +249 -0
  23. package/dist/examples/test-vue2/src/components/MotionControlAPI.vue +400 -0
  24. package/dist/examples/test-vue2/src/components/UnityPreview.vue +201 -0
  25. package/dist/examples/test-vue2/src/main.js +16 -0
  26. package/dist/examples/test-vue2/vue.config.js +41 -0
  27. package/dist/examples/test-vue3/.eslintrc +3 -0
  28. package/dist/examples/test-vue3/.stylelintignore +3 -0
  29. package/dist/examples/test-vue3/.stylelintrc +48 -0
  30. package/dist/examples/test-vue3/README.md +236 -0
  31. package/dist/examples/test-vue3/env.d.ts +8 -0
  32. package/dist/examples/test-vue3/index.html +95 -0
  33. package/dist/examples/test-vue3/package.json +55 -0
  34. package/dist/examples/test-vue3/pnpm-lock.yaml +4636 -0
  35. package/dist/examples/test-vue3/setup.js +167 -0
  36. package/dist/examples/test-vue3/src/App.vue +962 -0
  37. package/dist/examples/test-vue3/src/components/BroadcastAPI.vue +636 -0
  38. package/dist/examples/test-vue3/src/components/CameraAPI.vue +376 -0
  39. package/dist/examples/test-vue3/src/components/GlobalConfig.vue +213 -0
  40. package/dist/examples/test-vue3/src/components/InfoCards.vue +288 -0
  41. package/dist/examples/test-vue3/src/components/InitAPI.vue +339 -0
  42. package/dist/examples/test-vue3/src/components/LogPanel.vue +236 -0
  43. package/dist/examples/test-vue3/src/components/MotionControlAPI.vue +373 -0
  44. package/dist/examples/test-vue3/src/components/UnityPreview.vue +189 -0
  45. package/dist/examples/test-vue3/src/main.ts +12 -0
  46. package/dist/examples/test-vue3/src/types.ts +9 -0
  47. package/dist/examples/test-vue3/tsconfig.json +44 -0
  48. package/dist/examples/test-vue3/tsconfig.node.json +14 -0
  49. package/dist/examples/test-vue3/vite.config.ts +75 -0
  50. package/dist/index.d.ts +142 -132
  51. package/dist/index.es5.js +93 -41
  52. package/dist/index.es5.umd.js +93 -41
  53. package/dist/index.esm.js +101 -42
  54. package/dist/index.umd.cjs +101 -42
  55. package/package.json +4 -3
@@ -0,0 +1,943 @@
1
+ <template>
2
+ <div id="app">
3
+ <el-container class="app-container">
4
+ <!-- 主要内容区域 -->
5
+ <el-main class="app-main">
6
+ <!-- 全局配置 -->
7
+ <el-card class="global-config-card" shadow="never">
8
+ <global-config
9
+ :global-token.sync="globalConfig.token"
10
+ :global-avatar-code.sync="globalConfig.avatarCode"
11
+ @save-config="handleSaveConfig"
12
+ @apply-all="handleApplyAll"
13
+ />
14
+ </el-card>
15
+
16
+ <!-- 移动端和PC端响应式布局 -->
17
+ <div class="main-content" :class="{ 'mobile-layout': isMobile }">
18
+ <!-- PC端布局 -->
19
+ <el-row v-if="!isMobile" :gutter="20">
20
+ <!-- 左侧控制面板 -->
21
+ <el-col :md="8" :lg="8" :xl="8">
22
+ <el-card class="control-panel" shadow="hover">
23
+ <div slot="header" class="card-header">
24
+ <span>API 测试面板</span>
25
+ <el-tag type="info" size="mini">SDK演示</el-tag>
26
+ </div>
27
+
28
+ <!-- API选项卡 -->
29
+ <el-tabs v-model="activeTab" type="card" class="api-tabs">
30
+ <el-tab-pane label="初始化" name="init">
31
+ <init-a-p-i
32
+ :sdk-status="sdkStatus"
33
+ :global-config="globalConfig"
34
+ @init-avatar-with-unity="handleInitAvatarWithUnity"
35
+ />
36
+ </el-tab-pane>
37
+
38
+ <el-tab-pane label="动作管理" name="motion">
39
+ <motion-control-a-p-i
40
+ :sdk-status="sdkStatus"
41
+ :global-config="globalConfig"
42
+ @play-motion="handlePlayMotion"
43
+ @get-current-motion="handleGetCurrentMotion"
44
+ />
45
+ </el-tab-pane>
46
+
47
+ <el-tab-pane label="智能播报" name="text-broadcast">
48
+ <broadcast-a-p-i
49
+ :sdk-status="sdkStatus"
50
+ :global-config="globalConfig"
51
+ @text-broadcast="handleTextBroadcast"
52
+ @audio-broadcast="handleAudioBroadcast"
53
+ @pause-broadcast="handlePauseBroadcast"
54
+ @resume-broadcast="handleResumeBroadcast"
55
+ @stop-broadcast="handleStopBroadcast"
56
+ />
57
+ </el-tab-pane>
58
+
59
+ <el-tab-pane label="镜头控制" name="camera">
60
+ <camera-a-p-i
61
+ :sdk-status="sdkStatus"
62
+ :global-config="globalConfig"
63
+ @set-camera-preset="handleSetCameraPreset"
64
+ />
65
+ </el-tab-pane>
66
+ </el-tabs>
67
+ </el-card>
68
+ </el-col>
69
+
70
+ <!-- 中间Unity预览 -->
71
+ <el-col :md="10" :lg="10" :xl="10">
72
+ <unity-preview
73
+ :sdk-status="sdkStatus"
74
+ :loading-progress="loadingProgress"
75
+ @unload-avatar="handleUnloadAvatar"
76
+ @destroy-sdk="handleDestroySDK"
77
+ />
78
+ </el-col>
79
+
80
+ <!-- 右侧操作日志 -->
81
+ <el-col :md="6" :lg="6" :xl="6">
82
+ <el-card class="log-panel" shadow="hover">
83
+ <div slot="header" class="card-header">
84
+ <span>操作日志</span>
85
+ <el-button
86
+ type="text"
87
+ size="mini"
88
+ @click="clearLogs"
89
+ icon="el-icon-delete"
90
+ >
91
+ 清空
92
+ </el-button>
93
+ </div>
94
+
95
+ <log-panel
96
+ :logs="logs"
97
+ @clear-logs="clearLogs"
98
+ />
99
+ </el-card>
100
+ </el-col>
101
+ </el-row>
102
+
103
+ <!-- 移动端布局 -->
104
+ <div v-else class="mobile-panels">
105
+ <!-- API 测试面板 -->
106
+ <div class="mobile-panel-item">
107
+ <el-card class="control-panel" shadow="hover">
108
+ <div slot="header" class="card-header">
109
+ <span>API 测试面板</span>
110
+ <el-tag type="info" size="mini">SDK演示</el-tag>
111
+ </div>
112
+
113
+ <!-- API选项卡 -->
114
+ <el-tabs v-model="activeTab" type="card" class="api-tabs">
115
+ <el-tab-pane label="初始化" name="init">
116
+ <init-a-p-i
117
+ :sdk-status="sdkStatus"
118
+ :global-config="globalConfig"
119
+ @init-avatar-with-unity="handleInitAvatarWithUnity"
120
+ />
121
+ </el-tab-pane>
122
+
123
+ <el-tab-pane label="动作管理" name="motion">
124
+ <motion-control-a-p-i
125
+ :sdk-status="sdkStatus"
126
+ :global-config="globalConfig"
127
+ @play-motion="handlePlayMotion"
128
+ @get-current-motion="handleGetCurrentMotion"
129
+ />
130
+ </el-tab-pane>
131
+
132
+ <el-tab-pane label="智能播报" name="text-broadcast">
133
+ <broadcast-a-p-i
134
+ :sdk-status="sdkStatus"
135
+ :global-config="globalConfig"
136
+ @text-broadcast="handleTextBroadcast"
137
+ @audio-broadcast="handleAudioBroadcast"
138
+ @pause-broadcast="handlePauseBroadcast"
139
+ @resume-broadcast="handleResumeBroadcast"
140
+ @stop-broadcast="handleStopBroadcast"
141
+ />
142
+ </el-tab-pane>
143
+
144
+ <el-tab-pane label="镜头控制" name="camera">
145
+ <camera-a-p-i
146
+ :sdk-status="sdkStatus"
147
+ :global-config="globalConfig"
148
+ @set-camera-preset="handleSetCameraPreset"
149
+ />
150
+ </el-tab-pane>
151
+ </el-tabs>
152
+ </el-card>
153
+ </div>
154
+
155
+ <!-- Unity 预览 -->
156
+ <div class="mobile-panel-item">
157
+ <unity-preview
158
+ :sdk-status="sdkStatus"
159
+ :loading-progress="loadingProgress"
160
+ @unload-avatar="handleUnloadAvatar"
161
+ @destroy-sdk="handleDestroySDK"
162
+ />
163
+ </div>
164
+
165
+ <!-- 操作日志 -->
166
+ <div class="mobile-panel-item">
167
+ <el-card class="log-panel" shadow="hover">
168
+ <div slot="header" class="card-header">
169
+ <span>操作日志</span>
170
+ <el-button
171
+ type="text"
172
+ size="mini"
173
+ @click="clearLogs"
174
+ icon="el-icon-delete"
175
+ >
176
+ 清空
177
+ </el-button>
178
+ </div>
179
+
180
+ <log-panel
181
+ :logs="logs"
182
+ @clear-logs="clearLogs"
183
+ />
184
+ </el-card>
185
+ </div>
186
+ </div>
187
+ </div>
188
+
189
+ <!-- 信息卡片 -->
190
+ <el-card class="info-cards" shadow="never">
191
+ <info-cards />
192
+ </el-card>
193
+ </el-main>
194
+ </el-container>
195
+
196
+ <!-- 页脚 -->
197
+ <el-footer class="app-footer">
198
+ <div class="footer-content">
199
+ <span>ZEEAvatarSDK © 2025 | 测试控制台 v2.0.0</span>
200
+ <el-divider direction="vertical" />
201
+ <span>SDK重构版演示Demo</span>
202
+ </div>
203
+ </el-footer>
204
+ </div>
205
+ </template>
206
+
207
+ <script>
208
+ // 引入最新版SDK
209
+ import {
210
+ ZEEAvatarLoader,
211
+ BroadcastService,
212
+ AvatarCameraType,
213
+ BroadcastType
214
+ } from '@zeewain/3d-avatar-sdk'
215
+
216
+ // 引入组件
217
+ import GlobalConfig from './components/GlobalConfig.vue'
218
+ import InitAPI from './components/InitAPI.vue'
219
+ import MotionControlAPI from './components/MotionControlAPI.vue'
220
+ import BroadcastAPI from './components/BroadcastAPI.vue'
221
+ import CameraAPI from './components/CameraAPI.vue'
222
+ import LogPanel from './components/LogPanel.vue'
223
+ import InfoCards from './components/InfoCards.vue'
224
+ import UnityPreview from './components/UnityPreview.vue'
225
+
226
+ export default {
227
+ name: 'App',
228
+ components: {
229
+ GlobalConfig,
230
+ InitAPI,
231
+ MotionControlAPI,
232
+ BroadcastAPI,
233
+ CameraAPI,
234
+ LogPanel,
235
+ InfoCards,
236
+ UnityPreview
237
+ },
238
+ data () {
239
+ return {
240
+ // 当前活动选项卡
241
+ activeTab: 'init',
242
+
243
+ // 移动端检测
244
+ isMobile: false,
245
+
246
+ // 加载进度
247
+ loadingProgress: 0,
248
+
249
+ // 全局配置
250
+ globalConfig: {
251
+ token: '',
252
+ avatarCode: ''
253
+ },
254
+
255
+ // SDK状态
256
+ sdkStatus: {
257
+ unityLoaded: false,
258
+ avatarLoaded: false,
259
+ canPlayMotion: false,
260
+ canBroadcast: false,
261
+ canGetMotion: false,
262
+ canControlBroadcast: false,
263
+ canControlCamera: false
264
+ },
265
+
266
+ // 操作日志
267
+ logs: [
268
+ { time: new Date().toLocaleTimeString(), message: '系统准备就绪,请选择API进行测试', type: 'info' },
269
+ { time: new Date().toLocaleTimeString(), message: '支持移动端响应式布局', type: 'info' }
270
+ ],
271
+
272
+ // SDK实例
273
+ sdk: null,
274
+ avatarAPI: null,
275
+ broadcastService: null
276
+ }
277
+ },
278
+
279
+ mounted () {
280
+ this.loadStoredConfig()
281
+ this.checkMobile()
282
+ this.addLog('测试页面已加载(SDK重构版)', 'info')
283
+ this.addLog(`浏览器: ${navigator.userAgent.split(' ')[0]}`, 'info')
284
+
285
+ // 监听窗口大小变化
286
+ window.addEventListener('resize', this.handleResize)
287
+ },
288
+
289
+ beforeDestroy () {
290
+ // 清理监听器
291
+ window.removeEventListener('resize', this.handleResize)
292
+
293
+ // 清理SDK资源
294
+ if (this.broadcastService) {
295
+ this.broadcastService.destroy()
296
+ }
297
+ if (this.sdk) {
298
+ this.sdk.destroy()
299
+ }
300
+ },
301
+
302
+ methods: {
303
+ // 初始化SDK配置
304
+ initializeSDKConfig () {
305
+ if (!this.globalConfig.token) {
306
+ this.addLog('请先配置Token', 'warning')
307
+ return null
308
+ }
309
+
310
+ const config = {
311
+ // Unity构建文件配置
312
+ loaderUrl: './assets/Build/webgl.loader.js',
313
+ dataUrl: './assets/Build/webgl.data.unityweb',
314
+ frameworkUrl: './assets/Build/webgl.framework.js.unityweb',
315
+ codeUrl: './assets/Build/webgl.wasm.unityweb',
316
+ containerId: 'unity-container',
317
+
318
+ // 认证配置
319
+ token: this.globalConfig.token,
320
+ env: 'dev',
321
+
322
+ // 进度回调
323
+ onProgress: progress => {
324
+ this.loadingProgress = progress
325
+ this.addLog(`加载进度: ${Math.round(progress * 100)}%`, 'info')
326
+ }
327
+ }
328
+
329
+ this.sdk = new ZEEAvatarLoader(config)
330
+ return this.sdk
331
+ },
332
+
333
+ // 从本地存储加载配置
334
+ loadStoredConfig () {
335
+ const token = localStorage.getItem('zee_token')
336
+ const avatarCode = localStorage.getItem('zee_avatar')
337
+
338
+ if (token) this.globalConfig.token = token
339
+ if (avatarCode) this.globalConfig.avatarCode = avatarCode
340
+ },
341
+
342
+ // 保存全局配置
343
+ handleSaveConfig (config) {
344
+ this.globalConfig = { ...config }
345
+ localStorage.setItem('zee_token', config.token)
346
+ localStorage.setItem('zee_avatar', config.avatarCode)
347
+ this.addLog('全局配置已保存', 'success')
348
+ this.$message.success('配置保存成功')
349
+ },
350
+
351
+ // 应用配置到所有接口
352
+ handleApplyAll () {
353
+ this.addLog('配置已应用到所有接口', 'info')
354
+ this.$message.info('配置已应用到所有接口')
355
+ },
356
+
357
+ // 初始化Avatar(包含Unity实例化)
358
+ async handleInitAvatarWithUnity (params) {
359
+ try {
360
+ this.$message.info('开始初始化Unity和Avatar...')
361
+
362
+ // 如果Unity还没有加载,先初始化Unity
363
+ if (!this.sdkStatus.unityLoaded) {
364
+ await this.handleUnityInstance()
365
+ }
366
+
367
+ // 然后初始化Avatar
368
+ await this.handleInitAvatar(params)
369
+ } catch (error) {
370
+ this.addLog(`初始化失败: ${error.message}`, 'error')
371
+ this.$message.error(`初始化失败: ${error.message}`)
372
+ }
373
+ },
374
+
375
+ // Unity实例化
376
+ async handleUnityInstance () {
377
+ try {
378
+ this.addLog('开始初始化 SDK...', 'info')
379
+
380
+ // 重新初始化SDK配置(包含最新token)
381
+ if (!this.initializeSDKConfig()) {
382
+ return
383
+ }
384
+
385
+ // 初始化SDK并获取Avatar API
386
+ this.avatarAPI = await this.sdk.init()
387
+ this.addLog('SDK 初始化成功!', 'success')
388
+
389
+ // 创建播报服务实例
390
+ this.broadcastService = new BroadcastService({
391
+ unityInstance: this.sdk.getInstance(),
392
+ callbacks: {
393
+ onStart: () => {
394
+ this.addLog('播报开始', 'success')
395
+ },
396
+ onFinish: () => {
397
+ this.addLog('播报完成', 'success')
398
+ },
399
+ onError: error => {
400
+ this.addLog(`播报错误: ${error.message}`, 'error')
401
+ },
402
+ onPause: () => {
403
+ this.addLog('播报已暂停', 'info')
404
+ },
405
+ onResume: () => {
406
+ this.addLog('播报已继续', 'info')
407
+ },
408
+ onStop: () => {
409
+ this.addLog('播报已停止', 'info')
410
+ }
411
+ }
412
+ })
413
+
414
+ this.addLog('播报服务初始化完成', 'success')
415
+
416
+ // 更新状态
417
+ this.sdkStatus.unityLoaded = true
418
+ this.loadingProgress = 1
419
+ this.$message.success('Unity初始化成功')
420
+ } catch (error) {
421
+ console.error(error)
422
+ this.addLog(`SDK初始化失败: ${error.message}`, 'error')
423
+ this.$message.error(`SDK初始化失败: ${error.message}`)
424
+ this.loadingProgress = 0
425
+ throw error
426
+ }
427
+ },
428
+
429
+ // 初始化Avatar
430
+ async handleInitAvatar (params) {
431
+ try {
432
+ const response = await this.avatarAPI.initializeAvatar(
433
+ params.avatarCode || this.globalConfig.avatarCode,
434
+ AvatarCameraType.WHOLE
435
+ )
436
+
437
+ if (response.success) {
438
+ this.addLog('Avatar初始化成功', 'success')
439
+ this.sdkStatus.avatarLoaded = true
440
+ this.sdkStatus.canPlayMotion = true
441
+ this.sdkStatus.canBroadcast = true
442
+ this.sdkStatus.canGetMotion = true
443
+ this.sdkStatus.canControlBroadcast = true
444
+ this.sdkStatus.canControlCamera = true
445
+ this.$message.success('Avatar初始化成功')
446
+ } else {
447
+ this.addLog(`Avatar初始化失败: ${response.message}`, 'error')
448
+ this.$message.error(`Avatar初始化失败: ${response.message}`)
449
+ }
450
+ } catch (error) {
451
+ this.addLog(`Avatar初始化失败: ${error.message}`, 'error')
452
+ this.$message.error(`Avatar初始化失败: ${error.message}`)
453
+ }
454
+ },
455
+
456
+ // 播放动作
457
+ async handlePlayMotion (params) {
458
+ try {
459
+ const response = await this.avatarAPI.playMotion(params.clipCode, true)
460
+
461
+ if (response.success) {
462
+ this.addLog('动作播放成功', 'success')
463
+ if (response.motionId) {
464
+ this.addLog(`动作ID: ${response.motionId}`, 'info')
465
+ }
466
+ this.$message.success('动作播放成功')
467
+ } else {
468
+ this.addLog(`动作播放失败: ${response.message}`, 'error')
469
+ this.$message.error(`动作播放失败: ${response.message}`)
470
+ }
471
+ } catch (error) {
472
+ this.addLog(`动作播放失败: ${error.message}-${error.code}`, 'error')
473
+ this.$message.error(`动作播放失败: ${error.message}`)
474
+ }
475
+ },
476
+
477
+ // 文本播报
478
+ async handleTextBroadcast (params) {
479
+ try {
480
+ this.addLog('开始文本播报...', 'info')
481
+ await this.broadcastService.startBroadcast({
482
+ type: BroadcastType.TEXT,
483
+ humanCode: params.avatarCode || this.globalConfig.avatarCode,
484
+ text: params.text,
485
+ volume: params.volume,
486
+ voiceCode: params.voiceCode,
487
+ speed: params.speed,
488
+ isSubtitle: false
489
+ })
490
+ this.addLog('文本播报已开始', 'success')
491
+ this.$message.success('文本播报已开始')
492
+ } catch (error) {
493
+ this.addLog(`文本播报失败: ${error.message}`, 'error')
494
+ this.$message.error(`文本播报失败: ${error.message}`)
495
+ }
496
+ },
497
+
498
+ // 音频播报
499
+ async handleAudioBroadcast (params) {
500
+ try {
501
+ this.addLog('开始音频播报...', 'info')
502
+ await this.broadcastService.startBroadcast({
503
+ type: BroadcastType.AUDIO,
504
+ humanCode: params.avatarCode || this.globalConfig.avatarCode,
505
+ audioUrl: params.audioUrl,
506
+ text: params.audioText || '',
507
+ volume: params.volume || 1.0,
508
+ speed: params.speed,
509
+ isSubtitle: false
510
+ })
511
+ this.addLog('音频播报已开始', 'success')
512
+ this.$message.success('音频播报已开始')
513
+ } catch (error) {
514
+ this.addLog(`音频播报失败: ${error.message}`, 'error')
515
+ this.$message.error(`音频播报失败: ${error.message}`)
516
+ }
517
+ },
518
+
519
+ // 获取当前动作
520
+ async handleGetCurrentMotion (params) {
521
+ try {
522
+ const response = await this.avatarAPI.getCurrentMotion(params.getRemainingTime)
523
+ if (response.success) {
524
+ if (params.getRemainingTime) {
525
+ this.addLog(`当前动作编码: ${response.data.motionId || '无'}, 剩余时间: ${response.data.motionRemainingTime || 0}ms`, 'success')
526
+ } else {
527
+ this.addLog(`当前动作编码: ${response.data.motionId || '无'}`, 'success')
528
+ }
529
+ } else {
530
+ this.addLog(`获取动作失败: ${response.message}`, 'error')
531
+ }
532
+ } catch (error) {
533
+ this.addLog(`获取动作失败: ${error.message}`, 'error')
534
+ }
535
+ },
536
+
537
+ // 暂停播报
538
+ async handlePauseBroadcast () {
539
+ try {
540
+ await this.broadcastService.pauseBroadcast()
541
+ this.addLog('播报已暂停', 'success')
542
+ this.$message.success('播报已暂停')
543
+ } catch (error) {
544
+ this.addLog(`暂停播报失败: ${error.message}`, 'error')
545
+ this.$message.error(`暂停播报失败: ${error.message}`)
546
+ }
547
+ },
548
+
549
+ // 恢复播报
550
+ async handleResumeBroadcast () {
551
+ try {
552
+ await this.broadcastService.resumeBroadcast()
553
+ this.addLog('播报已恢复', 'success')
554
+ this.$message.success('播报已恢复')
555
+ } catch (error) {
556
+ this.addLog(`恢复播报失败: ${error.message}`, 'error')
557
+ this.$message.error(`恢复播报失败: ${error.message}`)
558
+ }
559
+ },
560
+
561
+ // 停止播报
562
+ async handleStopBroadcast () {
563
+ try {
564
+ await this.broadcastService.stopBroadcast()
565
+ this.addLog('播报已停止', 'success')
566
+ this.$message.success('播报已停止')
567
+ } catch (error) {
568
+ this.addLog(`停止播报失败: ${error.message}`, 'error')
569
+ this.$message.error(`停止播报失败: ${error.message}`)
570
+ }
571
+ },
572
+
573
+ // 设置预设镜头
574
+ async handleSetCameraPreset (params) {
575
+ try {
576
+ const cameraType = this.convertCameraPreset(params.preset)
577
+ const response = await this.avatarAPI.setCamera(cameraType)
578
+ if (response && response.success) {
579
+ this.addLog(`镜头已设置为${this.getCameraPresetName(params.preset)}`, 'success')
580
+ this.$message.success('镜头设置成功')
581
+ } else {
582
+ this.addLog(`镜头设置失败: ${response?.message || '未知错误'}`, 'error')
583
+ this.$message.error('镜头设置失败')
584
+ }
585
+ } catch (error) {
586
+ this.addLog(`镜头设置失败: ${error.message}`, 'error')
587
+ this.$message.error(`镜头设置失败: ${error.message}`)
588
+ }
589
+ },
590
+
591
+ // 转换镜头预设
592
+ convertCameraPreset (preset) {
593
+ const mapping = {
594
+ whole: AvatarCameraType.WHOLE,
595
+ half: AvatarCameraType.HALF,
596
+ face: AvatarCameraType.FACE
597
+ }
598
+ return mapping[preset] || AvatarCameraType.WHOLE
599
+ },
600
+
601
+ // 获取预设镜头名称
602
+ getCameraPresetName (preset) {
603
+ const names = {
604
+ whole: '全身',
605
+ half: '半身',
606
+ face: '近景'
607
+ }
608
+ return names[preset] || preset
609
+ },
610
+
611
+ // 卸载Avatar
612
+ async handleUnloadAvatar () {
613
+ try {
614
+ const response = await this.avatarAPI.unloadAvatar()
615
+ if (response.success) {
616
+ this.addLog('Avatar已卸载', 'success')
617
+ this.sdkStatus.avatarLoaded = false
618
+ this.sdkStatus.canPlayMotion = false
619
+ this.sdkStatus.canBroadcast = false
620
+ this.sdkStatus.canGetMotion = false
621
+ this.sdkStatus.canControlBroadcast = false
622
+ this.sdkStatus.canControlCamera = false
623
+ this.$message.success('Avatar已卸载')
624
+ } else {
625
+ this.addLog(`卸载失败: ${response.message}`, 'error')
626
+ this.$message.error(`卸载失败: ${response.message}`)
627
+ }
628
+ } catch (error) {
629
+ this.addLog(`卸载失败: ${error.message}`, 'error')
630
+ this.$message.error(`卸载失败: ${error.message}`)
631
+ }
632
+ },
633
+
634
+ // 销毁SDK实例
635
+ handleDestroySDK () {
636
+ try {
637
+ // 销毁播报服务
638
+ if (this.broadcastService) {
639
+ this.broadcastService.destroy()
640
+ this.broadcastService = null
641
+ }
642
+
643
+ // 销毁SDK
644
+ if (this.sdk) {
645
+ this.sdk.destroy()
646
+ this.sdk = null
647
+ }
648
+
649
+ this.avatarAPI = null
650
+ this.loadingProgress = 0
651
+
652
+ this.addLog('SDK 实例已销毁', 'warn')
653
+ this.$message.warning('SDK 实例已销毁')
654
+
655
+ // 重置状态
656
+ this.sdkStatus = {
657
+ unityLoaded: false,
658
+ avatarLoaded: false,
659
+ canPlayMotion: false,
660
+ canBroadcast: false,
661
+ canGetMotion: false,
662
+ canControlBroadcast: false,
663
+ canControlCamera: false
664
+ }
665
+
666
+ // 重新创建容器
667
+ const container = document.getElementById('unity-container')
668
+ if (container) {
669
+ container.innerHTML = '<div class="unity-tip"><i class="el-icon-monitor"></i><p>Unity 内容已销毁,可重新初始化</p></div>'
670
+ }
671
+ } catch (error) {
672
+ this.addLog(`销毁失败: ${error.message}`, 'error')
673
+ this.$message.error(`销毁失败: ${error.message}`)
674
+ }
675
+ },
676
+
677
+ // 添加日志
678
+ addLog (message, type = 'info') {
679
+ const log = {
680
+ time: new Date().toLocaleTimeString(),
681
+ message,
682
+ type
683
+ }
684
+ this.logs.push(log)
685
+
686
+ // 限制日志数量
687
+ if (this.logs.length > 100) {
688
+ this.logs.shift()
689
+ }
690
+ },
691
+
692
+ // 清除日志
693
+ clearLogs () {
694
+ this.logs = []
695
+ this.addLog('日志已清除', 'info')
696
+ this.$message.info('日志已清除')
697
+ },
698
+
699
+ // 检测移动端
700
+ checkMobile () {
701
+ this.isMobile = window.innerWidth <= 768
702
+ },
703
+
704
+ // 处理窗口大小变化
705
+ handleResize () {
706
+ this.checkMobile()
707
+ }
708
+ }
709
+ }
710
+ </script>
711
+
712
+ <style lang="scss">
713
+ // 使用Element-UI主题变量
714
+ @import '~element-ui/lib/theme-chalk/index.css';
715
+
716
+ // 全局样式
717
+ html, body {
718
+ height: 100vh;
719
+ padding: 0;
720
+ margin: 0;
721
+ box-sizing: border-box;
722
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
723
+ }
724
+
725
+ ul,li
726
+ {
727
+ padding: 0;
728
+ margin: 0;
729
+ list-style: none;
730
+ }
731
+
732
+ #app {
733
+ display: flex;
734
+ height: 100vh;
735
+ flex-direction: column;
736
+ }
737
+
738
+ .app-container {
739
+ flex: 1;
740
+ display: flex;
741
+ flex-direction: column;
742
+
743
+ .app-main {
744
+ display: flex;
745
+ padding: 20px;
746
+ background-color: #f5f7fa;
747
+ flex: 1;
748
+ flex-direction: column;
749
+
750
+ // 移动端优化
751
+ @media screen and (width <= 768px) {
752
+ min-height: calc(100vh - 120px); // 减去头部和底部的大致高度
753
+ padding: 10px;
754
+ flex: none; // 取消flex撑满,允许内容滚动
755
+ }
756
+ }
757
+ }
758
+
759
+ // 全局配置卡片
760
+ .global-config-card {
761
+ margin-bottom: 20px;
762
+
763
+ .el-card__body {
764
+ padding: 15px;
765
+ }
766
+ }
767
+
768
+ // 主要内容区域
769
+ .main-content {
770
+ margin-bottom: 20px;
771
+
772
+ // PC端布局
773
+ &:not(.mobile-layout) {
774
+ // 让主要内容区域撑满剩余空间
775
+ flex: 1;
776
+ display: flex;
777
+
778
+ // 确保el-row也能撑满高度
779
+ .el-row {
780
+ flex: 1;
781
+ display: flex;
782
+ align-items: stretch; // 确保所有列高度相同
783
+ }
784
+
785
+ // 让所有列都有相同的高度
786
+ .el-col {
787
+ display: flex;
788
+ flex-direction: column;
789
+ }
790
+ }
791
+
792
+ // 移动端布局
793
+ &.mobile-layout {
794
+ flex: none;
795
+
796
+ .mobile-panels {
797
+ display: flex;
798
+ flex-direction: column;
799
+ gap: 20px;
800
+
801
+ .mobile-panel-item {
802
+ width: 100%;
803
+
804
+ // 移动端面板高度优化
805
+ .control-panel,
806
+ .log-panel {
807
+ height: auto;
808
+
809
+ .el-card__body {
810
+ flex: none;
811
+ }
812
+ }
813
+
814
+ .api-tabs {
815
+ height: auto;
816
+
817
+ .el-tabs__content {
818
+ flex: none;
819
+ }
820
+ }
821
+ }
822
+ }
823
+ }
824
+ }
825
+
826
+ // 控制面板
827
+ .control-panel {
828
+ display: flex;
829
+ height: 100%;
830
+ flex-direction: column;
831
+
832
+ .card-header {
833
+ display: flex;
834
+ justify-content: space-between;
835
+ align-items: center;
836
+ }
837
+
838
+ .el-card__body {
839
+ flex: 1;
840
+ display: flex;
841
+ flex-direction: column;
842
+ }
843
+
844
+ .api-tabs {
845
+ flex: 1;
846
+ display: flex;
847
+ flex-direction: column;
848
+
849
+ .el-tabs__content {
850
+ padding: 15px 0;
851
+ flex: 1;
852
+ }
853
+ }
854
+ }
855
+
856
+ // 日志面板
857
+ .log-panel {
858
+ display: flex;
859
+ height: 100%;
860
+ flex-direction: column;
861
+
862
+ .card-header {
863
+ display: flex;
864
+ justify-content: space-between;
865
+ align-items: center;
866
+ }
867
+
868
+ .el-card__body {
869
+ flex: 1;
870
+ display: flex;
871
+ flex-direction: column;
872
+ overflow: hidden;
873
+ }
874
+ }
875
+
876
+ // 信息卡片
877
+ .info-cards {
878
+ .el-card__body {
879
+ padding: 15px;
880
+ }
881
+ }
882
+
883
+ // 页脚
884
+ .app-footer {
885
+ padding: 15px 0;
886
+ background-color: #fff;
887
+ border-top: 1px solid #e4e7ed;
888
+
889
+ .footer-content {
890
+ display: flex;
891
+ justify-content: center;
892
+ align-items: center;
893
+ font-size: 12px;
894
+ color: #606266;
895
+ }
896
+ }
897
+
898
+ // 移动端优化
899
+ @media screen and (width <= 768px) {
900
+ .mobile-panels {
901
+ .mobile-panel-item {
902
+ .api-tabs {
903
+ .el-tabs__header {
904
+ .el-tabs__nav-wrap {
905
+ .el-tabs__nav-scroll {
906
+ .el-tabs__nav {
907
+ .el-tabs__item {
908
+ padding: 0 10px;
909
+ font-size: 12px;
910
+ }
911
+ }
912
+ }
913
+ }
914
+ }
915
+ }
916
+ }
917
+ }
918
+ }
919
+
920
+ // 响应式断点优化
921
+ @media screen and (width <= 992px) {
922
+ .main-content:not(.mobile-layout) {
923
+ .el-row {
924
+ flex-direction: column; // 平板端也使用垂直布局
925
+ }
926
+
927
+ .el-col {
928
+ margin-bottom: 20px;
929
+
930
+ &:last-child {
931
+ margin-bottom: 0;
932
+ }
933
+ }
934
+
935
+ // 平板端面板高度优化
936
+ .control-panel,
937
+ .log-panel {
938
+ height: auto;
939
+ min-height: 400px; // 设置最小高度
940
+ }
941
+ }
942
+ }
943
+ </style>