@tencent-rtc/trtc-agent-skills 0.1.0

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 (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/README.zh.md +173 -0
  4. package/bin/cli.js +434 -0
  5. package/knowledge-base/index.yaml +454 -0
  6. package/knowledge-base/platform-slice-template.md +233 -0
  7. package/knowledge-base/scenario-spec.md +350 -0
  8. package/knowledge-base/scenarios/conference/base/general-conference.md +365 -0
  9. package/knowledge-base/scenarios/conference/base/webinar-conference.md +130 -0
  10. package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +145 -0
  11. package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +113 -0
  12. package/knowledge-base/scenarios/live/entertainment-live-room.md +118 -0
  13. package/knowledge-base/slice-spec.md +546 -0
  14. package/knowledge-base/slices/conference/web/ai-tools.md +225 -0
  15. package/knowledge-base/slices/conference/web/beauty-effects.md +188 -0
  16. package/knowledge-base/slices/conference/web/device-control.md +338 -0
  17. package/knowledge-base/slices/conference/web/login-auth.md +261 -0
  18. package/knowledge-base/slices/conference/web/network-quality.md +190 -0
  19. package/knowledge-base/slices/conference/web/official-roomkit-api.md +298 -0
  20. package/knowledge-base/slices/conference/web/official-roomkit-login-ui.md +246 -0
  21. package/knowledge-base/slices/conference/web/participant-list.md +238 -0
  22. package/knowledge-base/slices/conference/web/participant-management.md +718 -0
  23. package/knowledge-base/slices/conference/web/prejoin-check.md +293 -0
  24. package/knowledge-base/slices/conference/web/room-call.md +213 -0
  25. package/knowledge-base/slices/conference/web/room-chat.md +426 -0
  26. package/knowledge-base/slices/conference/web/room-lifecycle.md +534 -0
  27. package/knowledge-base/slices/conference/web/room-schedule.md +281 -0
  28. package/knowledge-base/slices/conference/web/screen-share.md +211 -0
  29. package/knowledge-base/slices/conference/web/video-layout.md +675 -0
  30. package/knowledge-base/slices/conference/web/virtual-background.md +197 -0
  31. package/knowledge-base/slices/conference/web/webinar-interaction.md +206 -0
  32. package/knowledge-base/slices/live/anchor-lifecycle.md +122 -0
  33. package/knowledge-base/slices/live/anchor-preview.md +90 -0
  34. package/knowledge-base/slices/live/anchor-room-config.md +104 -0
  35. package/knowledge-base/slices/live/audience-list.md +86 -0
  36. package/knowledge-base/slices/live/audience-manage.md +92 -0
  37. package/knowledge-base/slices/live/audience-watch.md +85 -0
  38. package/knowledge-base/slices/live/audio.md +116 -0
  39. package/knowledge-base/slices/live/barrage.md +88 -0
  40. package/knowledge-base/slices/live/beauty.md +99 -0
  41. package/knowledge-base/slices/live/coguest-apply.md +105 -0
  42. package/knowledge-base/slices/live/device-control.md +91 -0
  43. package/knowledge-base/slices/live/error-codes.md +167 -0
  44. package/knowledge-base/slices/live/gift.md +84 -0
  45. package/knowledge-base/slices/live/ios/.gitkeep +0 -0
  46. package/knowledge-base/slices/live/ios/anchor-lifecycle.md +313 -0
  47. package/knowledge-base/slices/live/ios/anchor-preview.md +228 -0
  48. package/knowledge-base/slices/live/ios/anchor-room-config.md +257 -0
  49. package/knowledge-base/slices/live/ios/audience-list.md +353 -0
  50. package/knowledge-base/slices/live/ios/audience-manage.md +381 -0
  51. package/knowledge-base/slices/live/ios/audience-watch.md +286 -0
  52. package/knowledge-base/slices/live/ios/audio.md +373 -0
  53. package/knowledge-base/slices/live/ios/barrage.md +285 -0
  54. package/knowledge-base/slices/live/ios/beauty.md +323 -0
  55. package/knowledge-base/slices/live/ios/coguest-apply.md +506 -0
  56. package/knowledge-base/slices/live/ios/device-control.md +286 -0
  57. package/knowledge-base/slices/live/ios/error-codes.md +270 -0
  58. package/knowledge-base/slices/live/ios/gift.md +315 -0
  59. package/knowledge-base/slices/live/ios/live-list.md +269 -0
  60. package/knowledge-base/slices/live/ios/login-auth.md +247 -0
  61. package/knowledge-base/slices/live/live-list.md +82 -0
  62. package/knowledge-base/slices/live/login-auth.md +78 -0
  63. package/package.json +34 -0
  64. package/skills/trtc/SKILL.md +326 -0
  65. package/skills/trtc/room-builder/SKILL.md +138 -0
  66. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +108 -0
  67. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/backend-contract.zh-CN.md +162 -0
  68. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/integration.zh-CN.md +154 -0
  69. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/theme.zh-CN.md +78 -0
  70. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/index.html +12 -0
  71. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/package.json +28 -0
  72. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/postcss.config.js +5 -0
  73. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/App.vue +25 -0
  74. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/ConsultationManagePanel.vue +838 -0
  75. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LanguageSwitch.vue +102 -0
  76. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LoadingSpinner.vue +6 -0
  77. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalAlert.vue +34 -0
  78. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalBusinessPanel.vue +148 -0
  79. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalButton.vue +49 -0
  80. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalConfirmDialog.vue +68 -0
  81. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalDataPanel.vue +196 -0
  82. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalRecordPanel.vue +270 -0
  83. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/PrescriptionPanel.vue +363 -0
  84. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/basic-info-config.ts +29 -0
  85. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
  86. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
  87. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/runtime-config.ts +12 -0
  88. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/env.d.ts +32 -0
  89. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +123 -0
  90. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +230 -0
  91. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +135 -0
  92. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +113 -0
  93. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +132 -0
  94. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +50 -0
  95. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/types.ts +77 -0
  96. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationChat.ts +97 -0
  97. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationDevices.ts +48 -0
  98. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationParticipants.ts +121 -0
  99. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationPermissions.ts +25 -0
  100. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/utils.ts +70 -0
  101. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/en-US/index.ts +553 -0
  102. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/index.ts +25 -0
  103. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/medicalTranslate.ts +85 -0
  104. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/state.ts +49 -0
  105. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/zh-CN/index.ts +463 -0
  106. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/main.ts +12 -0
  107. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/appointments.ts +96 -0
  108. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/users.ts +79 -0
  109. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/router/index.ts +63 -0
  110. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/index.ts +25 -0
  111. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/appointmentService.ts +77 -0
  112. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/authService.ts +38 -0
  113. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/launchContext.ts +31 -0
  114. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/userService.ts +35 -0
  115. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/appointmentService.ts +43 -0
  116. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/authService.ts +33 -0
  117. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/userService.ts +43 -0
  118. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/types.ts +135 -0
  119. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/shared/icons.ts +53 -0
  120. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/index.css +106 -0
  121. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/tailwind.css +3 -0
  122. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/theme.css +209 -0
  123. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/auth.ts +50 -0
  124. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/format.ts +24 -0
  125. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/navigation.ts +12 -0
  126. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/session.ts +28 -0
  127. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorConsultationView.vue +777 -0
  128. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorDashboardView.vue +678 -0
  129. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/LoginView.vue +441 -0
  130. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationFinishedView.vue +185 -0
  131. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationView.vue +1003 -0
  132. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientSelectDoctorView.vue +317 -0
  133. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientWaitingView.vue +454 -0
  134. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.json +21 -0
  135. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.node.json +8 -0
  136. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/vite.config.ts +17 -0
  137. package/skills/trtc/room-builder/templates/scenarios/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md +6 -0
  138. package/skills/trtc/room-builder/tools/render_ai_instructions.py +226 -0
  139. package/skills/trtc-apply/SKILL.md +97 -0
  140. package/skills/trtc-apply/guardrails/apply_lib/__init__.py +0 -0
  141. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  142. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/rule_parser.cpython-313.pyc +0 -0
  143. package/skills/trtc-apply/guardrails/apply_lib/rule_parser.py +268 -0
  144. package/skills/trtc-docs/SKILL.md +207 -0
  145. package/skills/trtc-onboarding/SKILL.md +839 -0
  146. package/skills/trtc-onboarding/reference/path-a1-demo.md +103 -0
  147. package/skills/trtc-onboarding/reference/path-a2-integrate.md +693 -0
  148. package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +115 -0
  149. package/skills/trtc-onboarding/reference/path-c-expand.md +43 -0
  150. package/skills/trtc-onboarding/reference/reporting-protocol.md +174 -0
  151. package/skills/trtc-onboarding/reference/supported-matrix.md +100 -0
  152. package/skills/trtc-onboarding/reference/usersig-handling.md +140 -0
  153. package/skills/trtc-search/SKILL.md +221 -0
  154. package/skills/trtc-topic/SKILL.md +638 -0
  155. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_read.cpython-313.pyc +0 -0
  156. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_write.cpython-313.pyc +0 -0
  157. package/skills/trtc-topic/guardrails/__pycache__/stop_require_apply_evidence.cpython-313.pyc +0 -0
  158. package/skills/trtc-topic/guardrails/gate_slice_read.py +133 -0
  159. package/skills/trtc-topic/guardrails/gate_slice_write.py +169 -0
  160. package/skills/trtc-topic/guardrails/stop_require_apply_evidence.py +97 -0
  161. package/skills/trtc-topic/references/execution-units.yaml +58 -0
  162. package/skills/trtc-topic/runtime/README.md +50 -0
  163. package/skills/trtc-topic/runtime/RUNTIME.md +128 -0
  164. package/skills/trtc-topic/runtime/lib/__init__.py +0 -0
  165. package/skills/trtc-topic/runtime/lib/platforms.py +194 -0
  166. package/skills/trtc-topic/runtime/package-lock.json +1211 -0
  167. package/skills/trtc-topic/runtime/package.json +13 -0
  168. package/skills/trtc-topic/runtime/telemetry-bridge.mjs +339 -0
  169. package/skills/trtc-topic/runtime/telemetry_collector.py +293 -0
  170. package/skills/trtc-topic/scripts/STATE-MACHINE-GUIDE.md +186 -0
  171. package/skills/trtc-topic/scripts/__pycache__/apply.cpython-313.pyc +0 -0
  172. package/skills/trtc-topic/scripts/apply.py +581 -0
  173. package/skills/trtc-topic/scripts/finalize_session.py +113 -0
  174. package/skills/trtc-topic/scripts/init_slice_queue.py +96 -0
  175. package/skills/trtc-topic/scripts/lib/__pycache__/state_machine.cpython-313.pyc +0 -0
  176. package/skills/trtc-topic/scripts/lib/state_machine.py +328 -0
  177. package/skills/trtc-topic/scripts/next_slice.py +137 -0
  178. package/skills/trtc-topic/tests/README.md +70 -0
  179. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  180. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  181. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.2.pyc +0 -0
  182. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  183. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.2.pyc +0 -0
  184. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.3.pyc +0 -0
  185. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.2.pyc +0 -0
  186. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.3.pyc +0 -0
  187. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.2.pyc +0 -0
  188. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.3.pyc +0 -0
  189. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.2.pyc +0 -0
  190. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.3.pyc +0 -0
  191. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.2.pyc +0 -0
  192. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.3.pyc +0 -0
  193. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.2.pyc +0 -0
  194. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.3.pyc +0 -0
  195. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.2.pyc +0 -0
  196. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.3.pyc +0 -0
  197. package/skills/trtc-topic/tests/conftest.py +72 -0
  198. package/skills/trtc-topic/tests/test_apply_cli.py +480 -0
  199. package/skills/trtc-topic/tests/test_end_to_end.py +305 -0
  200. package/skills/trtc-topic/tests/test_finalize_session.py +51 -0
  201. package/skills/trtc-topic/tests/test_gates.py +316 -0
  202. package/skills/trtc-topic/tests/test_session_resolver.py +260 -0
  203. package/skills/trtc-topic/tests/test_state_machine.py +414 -0
  204. package/skills/trtc-topic/tests/test_stop_require_apply.py +99 -0
  205. package/skills/trtc-topic/tests/test_topic_skill_invariants.py +130 -0
@@ -0,0 +1,441 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref } from 'vue';
3
+ import { useRouter } from 'vue-router';
4
+ import { useUIKit } from '@tencentcloud/uikit-base-component-vue3';
5
+ import { ChevronDown, Stethoscope, User, Lock, Shield } from '@/shared/icons';
6
+ import { useLoginState, useRoomEngine } from 'tuikit-atomicx-vue3/room';
7
+ import { MEDICAL_MODE } from '@/config/runtime-config';
8
+ import { getBasicInfo } from '@/config/basic-info-config';
9
+ import {
10
+ services,
11
+ type LaunchContext,
12
+ type UserRole,
13
+ } from '@/services/adapters';
14
+ import { getDefaultRoute } from '@/utils/navigation';
15
+ import { saveSession } from '@/utils/session';
16
+ import MedicalButton from '@/components/MedicalButton.vue';
17
+ import LanguageSwitch from '@/components/LanguageSwitch.vue';
18
+
19
+ const router = useRouter();
20
+ const loginState = useLoginState();
21
+ const { t } = useUIKit();
22
+
23
+ const activeRole = ref<UserRole>('doctor');
24
+ const doctorUsers = computed(() => services.user.listDoctors());
25
+ const patientUsers = computed(() => services.user.listPatients());
26
+ const selectedDoctorId = ref('');
27
+ const selectedPatientId = ref('');
28
+ const loading = ref(false);
29
+ const errorMessage = ref('');
30
+ const launchHint = ref('');
31
+ const accountDropdownVisible = ref(false);
32
+
33
+ const doctorOptions = computed(() => doctorUsers.value);
34
+ const patientOptions = computed(() => patientUsers.value);
35
+ const accountOptions = computed(() =>
36
+ activeRole.value === 'doctor' ? doctorOptions.value : patientOptions.value
37
+ );
38
+ const selectedAccountId = computed(() =>
39
+ activeRole.value === 'doctor'
40
+ ? selectedDoctorId.value
41
+ : selectedPatientId.value
42
+ );
43
+ const selectedAccount = computed(() =>
44
+ services.user.getUserById(selectedAccountId.value)
45
+ );
46
+ const selectedAccountLabel = computed(() => {
47
+ const currentUser = selectedAccount.value;
48
+ if (!currentUser) {
49
+ return t('Medical.Login.SelectAccount');
50
+ }
51
+ if (currentUser.role === 'doctor') {
52
+ return `${currentUser.userName} / ${currentUser.department} / ${currentUser.hospital}`;
53
+ }
54
+ return `${currentUser.userName} / ${currentUser.userId}`;
55
+ });
56
+
57
+ function selectAccount(userId: string) {
58
+ if (activeRole.value === 'doctor') {
59
+ selectedDoctorId.value = userId;
60
+ } else {
61
+ selectedPatientId.value = userId;
62
+ }
63
+ accountDropdownVisible.value = false;
64
+ }
65
+
66
+ function switchRole(role: UserRole) {
67
+ activeRole.value = role;
68
+ accountDropdownVisible.value = false;
69
+ }
70
+
71
+ function buildLaunchContext(): LaunchContext | null {
72
+ const currentId =
73
+ activeRole.value === 'doctor'
74
+ ? selectedDoctorId.value
75
+ : selectedPatientId.value;
76
+ const currentUser = services.user.getUserById(currentId);
77
+ if (!currentUser) {
78
+ return null;
79
+ }
80
+ return {
81
+ role: currentUser.role,
82
+ userId: currentUser.userId,
83
+ userName: currentUser.userName,
84
+ avatarUrl: currentUser.avatarUrl,
85
+ };
86
+ }
87
+
88
+ async function handleLogin() {
89
+ loading.value = true;
90
+ errorMessage.value = '';
91
+ const launchContext = buildLaunchContext();
92
+ const currentUser = launchContext
93
+ ? services.user.getUserById(launchContext.userId)
94
+ : null;
95
+
96
+ if (!launchContext || !currentUser) {
97
+ errorMessage.value = t('Medical.Login.PresetAccountNotFound');
98
+ loading.value = false;
99
+ return;
100
+ }
101
+
102
+ try {
103
+ await loginState.login(getBasicInfo(currentUser.userId));
104
+ await loginState.setSelfInfo({
105
+ userName: currentUser.userName,
106
+ avatarUrl: currentUser.avatarUrl,
107
+ });
108
+ const session = await services.auth.login(launchContext);
109
+ saveSession(session);
110
+ const roomEngine = useRoomEngine()?.instance;
111
+ const tim = roomEngine?.getTIM();
112
+ tim?.callExperimentalAPI('reportTUIFeatureUsage', {
113
+ atomicStoreID: 1206,
114
+ uiPlatform: 50,
115
+ });
116
+ router.replace(getDefaultRoute(currentUser.role, session.appointmentId));
117
+ } catch (error) {
118
+ errorMessage.value =
119
+ error instanceof Error ? error.message : t('Medical.Login.LoginFailed');
120
+ } finally {
121
+ loading.value = false;
122
+ }
123
+ }
124
+
125
+ async function tryIntegrationLogin() {
126
+ if (MEDICAL_MODE !== 'integration') {
127
+ return;
128
+ }
129
+
130
+ const launchContext = services.auth.getLaunchContext();
131
+ if (!launchContext) {
132
+ launchHint.value =
133
+ t('Medical.Login.IntegrationHint');
134
+ return;
135
+ }
136
+
137
+ loading.value = true;
138
+ try {
139
+ const session = await services.auth.login(launchContext);
140
+ const currentUser = session.user ||
141
+ services.user.getUserById(session.userId) || {
142
+ userId: session.userId,
143
+ userName: launchContext.userName || session.userId,
144
+ avatarUrl: launchContext.avatarUrl || '',
145
+ role: session.role,
146
+ };
147
+ await loginState.login(getBasicInfo(session.userId));
148
+ await loginState.setSelfInfo({
149
+ userName: currentUser.userName,
150
+ avatarUrl: currentUser.avatarUrl,
151
+ });
152
+ saveSession({ ...session, user: currentUser });
153
+ router.replace(getDefaultRoute(session.role, session.appointmentId));
154
+ } catch (error) {
155
+ errorMessage.value =
156
+ error instanceof Error ? error.message : t('Medical.Login.IntegrationInitFailed');
157
+ } finally {
158
+ loading.value = false;
159
+ }
160
+ }
161
+
162
+ onMounted(() => {
163
+ selectedDoctorId.value = doctorOptions.value[0]?.userId || '';
164
+ selectedPatientId.value = patientOptions.value[0]?.userId || '';
165
+ void tryIntegrationLogin();
166
+ });
167
+ </script>
168
+
169
+ <template>
170
+ <div
171
+ class="min-h-screen bg-gradient-to-br from-[#F8FAFB] via-[#E0F2F1] to-[#F1F5F9] px-4 py-6 md:flex md:items-center md:justify-center"
172
+ @click="accountDropdownVisible = false"
173
+ >
174
+ <div
175
+ class="mx-auto grid w-full max-w-md gap-0 overflow-hidden rounded-[24px] bg-white shadow-[0_8px_30px_rgba(0,0,0,0.08)] md:max-w-5xl md:grid-cols-2"
176
+ >
177
+ <div
178
+ class="bg-gradient-to-br from-[#0D9488] to-[#0F766E] p-6 text-white md:flex md:flex-col md:justify-between md:p-12"
179
+ >
180
+ <div>
181
+ <div class="mb-5 flex items-start justify-between gap-3 md:mb-8">
182
+ <div class="flex items-center gap-3 min-w-0">
183
+ <div class="rounded-2xl bg-white/10 p-2.5 backdrop-blur-sm md:p-3">
184
+ <Stethoscope class="h-7 w-7 md:h-8 md:w-8" />
185
+ </div>
186
+ <div class="min-w-0">
187
+ <h1 class="text-xl font-semibold md:text-2xl">
188
+ {{ t('Medical.Common.PlatformName') }}
189
+ </h1>
190
+ <p class="text-white/80 text-sm">{{ t('Medical.Common.TemplateName') }}</p>
191
+ </div>
192
+ </div>
193
+ <LanguageSwitch />
194
+ </div>
195
+
196
+ <div class="mt-6 space-y-4 md:mt-12 md:space-y-6">
197
+ <div class="flex items-start gap-4">
198
+ <div class="bg-white/10 backdrop-blur-sm p-2 rounded-lg mt-1">
199
+ <Shield :size="20" />
200
+ </div>
201
+ <div>
202
+ <h3 class="font-medium mb-1">{{ t('Medical.Login.VideoSecurityTitle') }}</h3>
203
+ <p class="text-white/70 text-sm">
204
+ {{ t('Medical.Login.VideoSecurityDescription') }}
205
+ </p>
206
+ </div>
207
+ </div>
208
+
209
+ <div class="hidden items-start gap-4 sm:flex">
210
+ <div class="bg-white/10 backdrop-blur-sm p-2 rounded-lg mt-1">
211
+ <Stethoscope :size="20" />
212
+ </div>
213
+ <div>
214
+ <h3 class="font-medium mb-1">{{ t('Medical.Login.WorkflowTitle') }}</h3>
215
+ <p class="text-white/70 text-sm">
216
+ {{ t('Medical.Login.WorkflowDescription') }}
217
+ </p>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+
223
+ <div class="mt-5 text-xs text-white/60 md:mt-0">
224
+ <p>© {{ t('Medical.Common.PlatformName') }} | {{ t('Medical.Common.SourceTemplate') }}</p>
225
+ </div>
226
+ </div>
227
+
228
+ <div class="flex flex-col justify-center p-6 md:p-12">
229
+ <div class="mb-6 md:mb-8">
230
+ <h2 class="mb-2 text-xl font-semibold text-gray-900 md:text-2xl">
231
+ {{ MEDICAL_MODE === 'mock' ? t('Medical.Login.MockEntryTitle') : t('Medical.Login.IntegrationEntryTitle') }}
232
+ </h2>
233
+ <p class="text-sm leading-6 text-gray-500 md:text-base">
234
+ {{
235
+ MEDICAL_MODE === 'mock'
236
+ ? t('Medical.Login.MockEntryDescription')
237
+ : t('Medical.Login.IntegrationEntryDescription')
238
+ }}
239
+ </p>
240
+ </div>
241
+
242
+ <div v-if="MEDICAL_MODE === 'mock'" class="w-full">
243
+ <div
244
+ class="mb-6 grid w-full grid-cols-2 rounded-xl bg-gray-100 p-1 md:mb-8"
245
+ >
246
+ <button
247
+ @click="switchRole('doctor')"
248
+ :class="[
249
+ 'px-4 py-2.5 rounded-lg font-medium text-sm transition-all flex items-center justify-center gap-2',
250
+ activeRole === 'doctor'
251
+ ? 'bg-white text-gray-900 shadow-sm'
252
+ : 'text-gray-600 hover:text-gray-900',
253
+ ]"
254
+ >
255
+ <Stethoscope :size="16" />
256
+ {{ t('Medical.Login.DoctorLogin') }}
257
+ </button>
258
+ <button
259
+ @click="switchRole('patient')"
260
+ :class="[
261
+ 'px-4 py-2.5 rounded-lg font-medium text-sm transition-all flex items-center justify-center gap-2',
262
+ activeRole === 'patient'
263
+ ? 'bg-white text-gray-900 shadow-sm'
264
+ : 'text-gray-600 hover:text-gray-900',
265
+ ]"
266
+ >
267
+ <User :size="16" />
268
+ {{ t('Medical.Login.PatientLogin') }}
269
+ </button>
270
+ </div>
271
+
272
+ <div class="space-y-4 md:space-y-5">
273
+ <div class="space-y-2">
274
+ <label class="block text-sm font-medium text-gray-700">
275
+ {{ activeRole === 'doctor' ? t('Medical.Login.DoctorAccount') : t('Medical.Login.PatientAccount') }}
276
+ </label>
277
+ <div class="relative" @click.stop>
278
+ <User
279
+ class="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400"
280
+ />
281
+ <button
282
+ type="button"
283
+ class="h-12 w-full rounded-2xl border border-gray-200 bg-white pl-12 pr-12 text-left text-base font-medium text-gray-900 shadow-sm outline-none transition-colors hover:border-[#0D9488]/40 focus:border-[#0D9488] focus:ring-2 focus:ring-[#0D9488]/20 md:h-14"
284
+ @click="accountDropdownVisible = !accountDropdownVisible"
285
+ >
286
+ <span class="block truncate">{{ selectedAccountLabel }}</span>
287
+ </button>
288
+ <ChevronDown
289
+ :class="[
290
+ 'pointer-events-none absolute right-4 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-500 transition-transform',
291
+ accountDropdownVisible ? 'rotate-180' : '',
292
+ ]"
293
+ />
294
+ <div
295
+ v-if="accountDropdownVisible"
296
+ class="absolute left-0 right-0 top-[calc(100%+8px)] z-30 overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-[0_14px_35px_rgba(15,23,42,0.16)]"
297
+ >
298
+ <button
299
+ v-for="item in accountOptions"
300
+ :key="item.userId"
301
+ type="button"
302
+ @click="selectAccount(item.userId)"
303
+ :class="[
304
+ 'flex w-full items-center gap-3 px-4 py-3 text-left text-sm transition-colors',
305
+ item.userId === selectedAccountId
306
+ ? 'bg-[#0D9488]/10 text-[#0F766E]'
307
+ : 'text-gray-700 hover:bg-gray-50',
308
+ ]"
309
+ >
310
+ <span
311
+ :class="[
312
+ 'h-2 w-2 rounded-full',
313
+ item.userId === selectedAccountId
314
+ ? 'bg-[#0D9488]'
315
+ : 'bg-gray-300',
316
+ ]"
317
+ ></span>
318
+ <span class="min-w-0 flex-1 truncate">
319
+ {{
320
+ item.role === 'doctor'
321
+ ? `${item.userName} / ${item.department} / ${item.hospital}`
322
+ : `${item.userName} / ${item.userId}`
323
+ }}
324
+ </span>
325
+ </button>
326
+ </div>
327
+ </div>
328
+ </div>
329
+
330
+ <div class="space-y-2">
331
+ <label class="block text-sm font-medium text-gray-700">
332
+ {{ activeRole === 'doctor' ? t('Medical.Login.Password') : t('Medical.Login.AppointmentCode') }}
333
+ </label>
334
+ <div class="relative">
335
+ <Lock
336
+ class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"
337
+ />
338
+ <input
339
+ :value="activeRole === 'doctor' ? '••••••••' : '2580'"
340
+ readonly
341
+ class="h-12 w-full rounded-xl border border-gray-200 px-4 pl-11 focus:border-[#0D9488] focus:outline-none focus:ring-2 focus:ring-[#0D9488]/20"
342
+ />
343
+ </div>
344
+ </div>
345
+
346
+ <div
347
+ v-if="activeRole === 'doctor'"
348
+ class="flex items-center justify-between text-sm"
349
+ >
350
+ <label class="flex items-center gap-2 cursor-pointer">
351
+ <input
352
+ type="checkbox"
353
+ checked
354
+ class="w-4 h-4 rounded border-gray-300 text-[#0D9488] focus:ring-[#0D9488]"
355
+ />
356
+ <span class="text-gray-600">{{ t('Medical.Login.RememberLogin') }}</span>
357
+ </label>
358
+ <button type="button" class="text-[#0D9488] hover:text-[#0F766E]">
359
+ {{ t('Medical.Login.ForgotPassword') }}
360
+ </button>
361
+ </div>
362
+
363
+ <div v-else class="bg-[#F1F5F9] rounded-xl p-4">
364
+ <p class="text-sm text-gray-600 text-center">
365
+ <span class="text-[#0D9488] font-medium">{{ t('Medical.Login.Tips') }}</span>
366
+ {{ t('Medical.Login.PatientPhoneTip') }}
367
+ </p>
368
+ </div>
369
+
370
+ <div class="rounded-xl bg-[#F1F5F9] p-3 md:p-4">
371
+ <p class="text-xs text-gray-600 leading-6">
372
+ {{ t('Medical.Login.ConfigTipPrefix') }}
373
+ <code>src/config/basic-info-config.ts</code>
374
+ {{ t('Medical.Login.ConfigTipMiddle') }}
375
+ <code>SDKAPPID</code>{{ t('Medical.Login.ConfigTipSuffix') }}
376
+ <code>UserSig</code>。
377
+ </p>
378
+ </div>
379
+
380
+ <MedicalButton
381
+ type="button"
382
+ block
383
+ size="lg"
384
+ :loading="loading"
385
+ @click="handleLogin"
386
+ >
387
+ {{
388
+ loading
389
+ ? t('Medical.Login.LoginLoading')
390
+ : activeRole === 'doctor'
391
+ ? t('Medical.Login.OpenDashboard')
392
+ : t('Medical.Login.EnterWaitingRoom')
393
+ }}
394
+ </MedicalButton>
395
+
396
+ <p class="text-center text-xs text-gray-500 mt-4">
397
+ {{ t('Medical.Login.EntryDescription') }}
398
+ </p>
399
+
400
+ <p v-if="errorMessage" class="text-sm text-red-500">
401
+ {{ errorMessage }}
402
+ </p>
403
+ </div>
404
+ </div>
405
+
406
+ <div v-else class="space-y-4">
407
+ <div class="rounded-2xl border border-[#0D9488]/20 bg-[#F0FDFA] p-4">
408
+ <p class="text-sm font-medium text-[#0F766E]">{{ t('Medical.Login.IntegrationMode') }}</p>
409
+ <p class="mt-2 text-sm leading-6 text-gray-600">
410
+ {{ t('Medical.Login.IntegrationModeDescription') }}
411
+ </p>
412
+ </div>
413
+
414
+ <div
415
+ class="rounded-2xl bg-[#F8FAFC] p-4 text-xs leading-7 text-gray-600"
416
+ >
417
+ <p>{{ t('Medical.Login.DoctorExample') }}</p>
418
+ <p>
419
+ <code>
420
+ ?role=doctor&amp;userId=doctor_li&amp;appointmentId=APT001
421
+ </code>
422
+ </p>
423
+ <p class="mt-2">{{ t('Medical.Login.PatientExample') }}</p>
424
+ <p>
425
+ <code>
426
+ ?role=patient&amp;userId=patient_zhang&amp;appointmentId=APT001
427
+ </code>
428
+ </p>
429
+ </div>
430
+
431
+ <p v-if="launchHint" class="text-sm text-amber-600">
432
+ {{ launchHint }}
433
+ </p>
434
+ <p v-if="errorMessage" class="text-sm text-red-500">
435
+ {{ errorMessage }}
436
+ </p>
437
+ </div>
438
+ </div>
439
+ </div>
440
+ </div>
441
+ </template>
@@ -0,0 +1,185 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useRoute, useRouter } from 'vue-router';
4
+ import { useUIKit } from '@tencentcloud/uikit-base-component-vue3';
5
+ import { CheckCircle2, ChevronLeft, FileText, House } from '@/shared/icons';
6
+ import { services } from '@/services/adapters';
7
+ import LanguageSwitch from '@/components/LanguageSwitch.vue';
8
+
9
+ const route = useRoute();
10
+ const router = useRouter();
11
+ const { t } = useUIKit();
12
+
13
+ const appointment = computed(() =>
14
+ services.appointment.getAppointmentById(String(route.params.appointmentId))
15
+ );
16
+ const doctor = computed(() =>
17
+ appointment.value
18
+ ? services.user.getDoctorById(appointment.value.doctorId)
19
+ : null
20
+ );
21
+ const durationSeconds = computed(() => Number(route.query.duration || 0));
22
+ const durationMinutes = computed(() =>
23
+ Math.max(1, Math.round(durationSeconds.value / 60))
24
+ );
25
+ const displayDateTime = computed(() => {
26
+ if (!appointment.value) {
27
+ return '';
28
+ }
29
+ const date = new Date(appointment.value.scheduleStartTime * 1000);
30
+ const yyyy = date.getFullYear();
31
+ const mm = `${date.getMonth() + 1}`.padStart(2, '0');
32
+ const dd = `${date.getDate()}`.padStart(2, '0');
33
+ const hh = `${date.getHours()}`.padStart(2, '0');
34
+ const min = `${date.getMinutes()}`.padStart(2, '0');
35
+ return `${yyyy}-${mm}-${dd} ${hh}:${min}`;
36
+ });
37
+ const prescriptionNo = computed(() => {
38
+ if (!appointment.value) {
39
+ return '';
40
+ }
41
+ const date = new Date(appointment.value.scheduleStartTime * 1000);
42
+ const yyyy = date.getFullYear();
43
+ const mm = `${date.getMonth() + 1}`.padStart(2, '0');
44
+ const dd = `${date.getDate()}`.padStart(2, '0');
45
+ return `RX${yyyy}${mm}${dd}001`;
46
+ });
47
+ </script>
48
+
49
+ <template>
50
+ <div
51
+ class="min-h-screen bg-gradient-to-br from-[#F5FAFE] via-[#F3FBF8] to-[#F3F6FD] flex justify-center p-4"
52
+ >
53
+ <div v-if="appointment && doctor" class="w-full max-w-[460px]">
54
+ <div class="h-14 px-2 flex items-center justify-between gap-3">
55
+ <div class="flex items-center gap-3 min-w-0">
56
+ <button
57
+ @click="router.replace('/patient/select-doctor')"
58
+ class="inline-flex items-center gap-1 text-[#1F2937] text-sm font-medium shrink-0"
59
+ >
60
+ <ChevronLeft :size="18" />
61
+ {{ t('Medical.Common.BackHome') }}
62
+ </button>
63
+ <h1 class="text-2xl font-semibold text-[#111827] truncate">
64
+ {{ t('Medical.Finished.Title') }}
65
+ </h1>
66
+ </div>
67
+ <LanguageSwitch />
68
+ </div>
69
+
70
+ <div
71
+ class="bg-[#F3FFF9] border border-[#E6F9F0] rounded-3xl p-4 shadow-sm space-y-4"
72
+ >
73
+ <div class="text-center py-4">
74
+ <div
75
+ class="w-24 h-24 rounded-full bg-gradient-to-br from-[#00C2A8] to-[#0D9488] mx-auto flex items-center justify-center shadow-lg"
76
+ >
77
+ <CheckCircle2 :size="46" class="text-white" />
78
+ </div>
79
+ <h2 class="text-3xl font-semibold text-[#0F172A] mt-3">
80
+ {{ t('Medical.Finished.Completed') }}
81
+ </h2>
82
+ <p class="text-[#6B7280] mt-2">{{ t('Medical.Finished.Thanks') }}</p>
83
+ </div>
84
+
85
+ <div class="bg-white rounded-3xl p-4 shadow-sm border border-[#EEF2F7]">
86
+ <div class="flex items-center justify-between">
87
+ <div class="flex items-center gap-3 min-w-0">
88
+ <div
89
+ class="w-12 h-12 rounded-full bg-gradient-to-br from-[#0D9488] to-[#0F766E] text-white font-semibold flex items-center justify-center shrink-0"
90
+ >
91
+ {{ doctor.userName.charAt(0) }}
92
+ </div>
93
+ <div class="min-w-0">
94
+ <p class="text-xl font-semibold text-[#111827] truncate">
95
+ {{ doctor.userName }}
96
+ </p>
97
+ <p class="text-[#6B7280] text-sm truncate">
98
+ {{ doctor.department }} · {{ doctor.title }}
99
+ </p>
100
+ </div>
101
+ </div>
102
+ <div class="text-right shrink-0 ml-2">
103
+ <p class="text-[#6B7280] text-xs">{{ displayDateTime }}</p>
104
+ <p class="text-[#111827] text-lg font-semibold">
105
+ {{ t('Medical.Finished.Duration', { minutes: durationMinutes }) }}
106
+ </p>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <div
112
+ class="bg-white rounded-3xl p-4 shadow-sm border border-[#EEF2F7] space-y-3"
113
+ >
114
+ <div class="flex items-center gap-2 text-[#334155] font-semibold">
115
+ <FileText :size="18" class="text-[#0D9488]" />
116
+ {{ t('Medical.Finished.ResultExample') }}
117
+ </div>
118
+ <p class="text-xl font-semibold text-[#0F172A]">
119
+ {{ t('Medical.Finished.ResultPlaceholder') }}
120
+ </p>
121
+ </div>
122
+
123
+ <div class="grid grid-cols-2 gap-3">
124
+ <button
125
+ class="h-12 rounded-full bg-white border border-[#E5E7EB] text-[#0D9488] font-semibold"
126
+ >
127
+ {{ t('Medical.Finished.PrescriptionExample') }}
128
+ </button>
129
+ <button
130
+ class="h-12 rounded-full bg-white border border-[#E5E7EB] text-[#334155] font-semibold"
131
+ >
132
+ {{ t('Medical.Finished.RecordExample') }}
133
+ </button>
134
+ </div>
135
+
136
+ <div
137
+ class="bg-white rounded-3xl overflow-hidden shadow-sm border border-[#EEF2F7]"
138
+ >
139
+ <div
140
+ class="bg-gradient-to-r from-[#00C2A8] to-[#0D9488] px-4 py-3 text-white"
141
+ >
142
+ <div class="flex items-center justify-between">
143
+ <h3 class="text-xl font-semibold">
144
+ {{ t('Medical.Finished.PrescriptionDataExample') }}
145
+ </h3>
146
+ <span class="text-sm px-3 py-1 rounded-full bg-white/20">
147
+ {{ t('Medical.Common.Replaceable') }}
148
+ </span>
149
+ </div>
150
+ <p class="text-sm mt-2">
151
+ {{ t('Medical.Finished.SampleNo', { no: prescriptionNo }) }}
152
+ </p>
153
+ </div>
154
+ <div class="p-4">
155
+ <div
156
+ class="border border-[#E5E7EB] rounded-2xl p-4 flex items-center justify-between"
157
+ >
158
+ <div>
159
+ <p class="text-lg font-semibold text-[#111827]">
160
+ {{ t('Medical.Prescription.Amoxicillin') }}
161
+ </p>
162
+ <p class="text-[#6B7280] mt-1">0.5g</p>
163
+ </div>
164
+ <span
165
+ class="px-3 py-1 rounded-full bg-[#F8FAFC] text-[#334155] text-sm"
166
+ >
167
+ {{ t('Medical.Prescription.Quantity21') }}
168
+ </span>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+
174
+ <div class="sticky bottom-3 mt-4">
175
+ <button
176
+ @click="router.replace('/patient/select-doctor')"
177
+ class="w-full h-12 rounded-full bg-gradient-to-r from-[#00C2A8] to-[#0D9488] text-white font-semibold inline-flex items-center justify-center gap-2 shadow-lg"
178
+ >
179
+ <House :size="18" />
180
+ {{ t('Medical.Common.BackHome') }}
181
+ </button>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </template>