فهرست منبع

【MiniProgram】update Miniprogram

yuanfazheng 1 سال پیش
والد
کامیت
d403afa8b6
100فایلهای تغییر یافته به همراه2364 افزوده شده و 1233 حذف شده
  1. 21 0
      MiniProgram/.gitignore
  2. 16 0
      MiniProgram/.hbuilderx/launch.json
  3. 2 2
      MiniProgram/package.json
  4. 86 31
      MiniProgram/src/locales/en-US.ts
  5. 91 32
      MiniProgram/src/locales/zh-CN.ts
  6. 13 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/AllMembersShareScreenIcon.svg
  7. 5 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/ArrowStrokeRightIcon.svg
  8. 3 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/CalendarIcon.svg
  9. 9 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/EditNameCardIcon.svg
  10. 5 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/EllipsisIcon.svg
  11. 11 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/HostShareScreenIcon.svg
  12. 8 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/LinkIcon.svg
  13. 3 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/LoadingScheduleIcon.svg
  14. 0 8
      MiniProgram/src/roomkit/TUIRoom/assets/icons/MailIcon.svg
  15. 0 8
      MiniProgram/src/roomkit/TUIRoom/assets/icons/PhoneIcon.svg
  16. 11 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/ScheduleAttendees.svg
  17. 3 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/ScheduleRoomIcon.svg
  18. 6 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/SuccessIcon.svg
  19. 0 9
      MiniProgram/src/roomkit/TUIRoom/assets/icons/VerifyIcon.svg
  20. 10 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/VirtualBackgroundIcon.svg
  21. 11 0
      MiniProgram/src/roomkit/TUIRoom/assets/icons/WarningIcon.svg
  22. BIN
      MiniProgram/src/roomkit/TUIRoom/assets/imgs/background-white.png
  23. BIN
      MiniProgram/src/roomkit/TUIRoom/assets/imgs/blurred-background.png
  24. BIN
      MiniProgram/src/roomkit/TUIRoom/assets/imgs/close-virtual-background.png
  25. 4 2
      MiniProgram/src/roomkit/TUIRoom/assets/style/black-theme.scss
  26. 4 2
      MiniProgram/src/roomkit/TUIRoom/assets/style/white-theme.scss
  27. 5 5
      MiniProgram/src/roomkit/TUIRoom/components/Chat/ChatEditor/index.vue
  28. 5 16
      MiniProgram/src/roomkit/TUIRoom/components/Chat/ChatEditor/useChatEditor.ts
  29. 6 1
      MiniProgram/src/roomkit/TUIRoom/components/Chat/MessageList/index.vue
  30. 2 1
      MiniProgram/src/roomkit/TUIRoom/components/Chat/MessageList/useMessageListHook.ts
  31. 0 6
      MiniProgram/src/roomkit/TUIRoom/components/Chat/util.ts
  32. 83 4
      MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberControl/index.vue
  33. 128 36
      MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberControl/useMemberControlHooks.ts
  34. 12 5
      MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberItem/useMemberItemHooks.ts
  35. 4 12
      MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberItemCommon/MemberInfo.vue
  36. 71 0
      MiniProgram/src/roomkit/TUIRoom/components/ManageMember/index.vue
  37. 60 11
      MiniProgram/src/roomkit/TUIRoom/components/ManageMember/useIndexHooks.ts
  38. 8 27
      MiniProgram/src/roomkit/TUIRoom/components/RoomContent/StreamContainer/index.vue
  39. 91 43
      MiniProgram/src/roomkit/TUIRoom/components/RoomContent/StreamContainer/useStreamContainerHooks.ts
  40. 8 6
      MiniProgram/src/roomkit/TUIRoom/components/RoomContent/StreamRegion/index.vue
  41. 1 0
      MiniProgram/src/roomkit/TUIRoom/components/RoomContent/index.vue
  42. 2 1
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ApplyControl/MasterApplyControl/index.vue
  43. 6 20
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ApplyControl/MemberApplyControl.vue
  44. 6 13
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/AudioControl.vue
  45. 3 2
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ChatControl.vue
  46. 6 54
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/EndControl/index.vue
  47. 11 4
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/EndControl/useEndControlHooks.ts
  48. 2 3
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/InviteControl.vue
  49. 3 1
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ManageMemberControl.vue
  50. 3 3
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/MoreControl/index.vue
  51. 6 21
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/VideoControl.vue
  52. 203 0
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/VirtualBackground.vue
  53. 0 3
      MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/index/index.vue
  54. 1 1
      MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/RoomInfo/useRoomInfoHooks.ts
  55. 6 2
      MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/UserInfo/index.vue
  56. 2 61
      MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/UserInfo/useUserInfoHooks.ts
  57. 1 17
      MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/index/index.vue
  58. 10 2
      MiniProgram/src/roomkit/TUIRoom/components/RoomHome/RoomControl/index.vue
  59. 0 79
      MiniProgram/src/roomkit/TUIRoom/components/RoomLogin/MailLogin.vue
  60. 0 82
      MiniProgram/src/roomkit/TUIRoom/components/RoomLogin/PhoneLogin.vue
  61. 0 131
      MiniProgram/src/roomkit/TUIRoom/components/RoomLogin/VerifyCode.vue
  62. 0 8
      MiniProgram/src/roomkit/TUIRoom/components/RoomSetting/index.vue
  63. 4 3
      MiniProgram/src/roomkit/TUIRoom/components/RoomSidebar/useSideBarHooks.ts
  64. 0 3
      MiniProgram/src/roomkit/TUIRoom/components/common/ArrowStroke.vue
  65. 0 7
      MiniProgram/src/roomkit/TUIRoom/components/common/AudioMediaControl.vue
  66. 0 10
      MiniProgram/src/roomkit/TUIRoom/components/common/AudioSettingTab.vue
  67. 62 26
      MiniProgram/src/roomkit/TUIRoom/components/common/DeviceSelect.vue
  68. 3 0
      MiniProgram/src/roomkit/TUIRoom/components/common/Language.vue
  69. 7 7
      MiniProgram/src/roomkit/TUIRoom/components/common/Logo.vue
  70. 1 4
      MiniProgram/src/roomkit/TUIRoom/components/common/SwitchTheme.vue
  71. 0 7
      MiniProgram/src/roomkit/TUIRoom/components/common/VideoMediaControl.vue
  72. 0 5
      MiniProgram/src/roomkit/TUIRoom/components/common/VideoProfile.vue
  73. 1 9
      MiniProgram/src/roomkit/TUIRoom/components/common/VideoSettingTab.vue
  74. 19 11
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Checkbox.vue
  75. 252 0
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Datepicker/Datepicker.vue
  76. 54 0
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Datepicker/Timepicker.vue
  77. 0 11
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Drawer.vue
  78. 31 33
      MiniProgram/src/roomkit/TUIRoom/components/common/base/IconButton.vue
  79. 0 61
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Input.vue
  80. 76 0
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Input/index.vue
  81. 1 0
      MiniProgram/src/roomkit/TUIRoom/components/common/base/MessageBox/index.ts
  82. 14 4
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Option.vue
  83. 19 14
      MiniProgram/src/roomkit/TUIRoom/components/common/base/Select.vue
  84. 0 7
      MiniProgram/src/roomkit/TUIRoom/components/common/base/SvgIcon.vue
  85. 155 0
      MiniProgram/src/roomkit/TUIRoom/conference.ts
  86. 106 64
      MiniProgram/src/roomkit/TUIRoom/conference.vue
  87. 2 2
      MiniProgram/src/roomkit/TUIRoom/constants/room.ts
  88. 1 5
      MiniProgram/src/roomkit/TUIRoom/extension/RoomMessageCard/handleRoomMessage.ts
  89. 3 0
      MiniProgram/src/roomkit/TUIRoom/extension/RoomMessageCard/roomMessageCard.scss
  90. 40 42
      MiniProgram/src/roomkit/TUIRoom/extension/chatExtension.ts
  91. 0 20
      MiniProgram/src/roomkit/TUIRoom/extension/index.scss
  92. 2 2
      MiniProgram/src/roomkit/TUIRoom/extension/utils/interact.ts
  93. 0 2
      MiniProgram/src/roomkit/TUIRoom/hooks/useDeviceManager.ts
  94. 28 30
      MiniProgram/src/roomkit/TUIRoom/hooks/useMasterApplyControl.ts
  95. 1 1
      MiniProgram/src/roomkit/TUIRoom/hooks/useMitt.ts
  96. 11 0
      MiniProgram/src/roomkit/TUIRoom/index.ts
  97. 86 31
      MiniProgram/src/roomkit/TUIRoom/locales/en-US.ts
  98. 4 5
      MiniProgram/src/roomkit/TUIRoom/locales/index.ts
  99. 91 32
      MiniProgram/src/roomkit/TUIRoom/locales/zh-CN.ts
  100. 213 0
      MiniProgram/src/roomkit/TUIRoom/preConference.vue

+ 21 - 0
MiniProgram/.gitignore

@@ -0,0 +1,21 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+*.local
+
+# Editor directories and files
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 16 - 0
MiniProgram/.hbuilderx/launch.json

@@ -0,0 +1,16 @@
+{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
+  // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
+    "version": "0.0",
+    "configurations": [{
+     	"app-plus" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"default" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"type" : "uniCloud"
+     }
+    ]
+}

+ 2 - 2
MiniProgram/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@tencentcloud/tui-roomkit-uniapp",
-  "version": "2.4.0",
+  "version": "2.5.0",
   "bin": {
     "configureProject": "./configureProject_bin.js"
   },
@@ -60,7 +60,7 @@
     "@tencentcloud/chat": "latest",
     "@tencentcloud/trtc-component-wx": "^0.0.4",
     "@tencentcloud/tui-core": "latest",
-    "@tencentcloud/tuiroom-engine-wx": "^2.4.0",
+    "@tencentcloud/tuiroom-engine-wx": "^2.5.0",
     "@tencentcloud/universal-api": "^2.0.9",
     "axios": "^0.27.2",
     "inquirer": "^9.2.11",

+ 86 - 31
MiniProgram/src/locales/en-US.ts

@@ -1,28 +1,4 @@
 export default {
-  '': '',
-  'Sign in': 'Sign in',
-  'Phone Login': 'Phone Login',
-  'Email Login': 'Email Login',
-  'I have read and agree to the': 'I have read and agree to the',
-  'Privacy Policy': 'Privacy Policy',
-  and: 'and',
-  'Terms of Use': 'Terms of Use',
-  Login: 'Login',
-  'Mobile number': 'Mobile number',
-  'Verification code': 'Verification code',
-  'Email address': 'Email address',
-  'Please enter a valid phone number!': 'Please enter a valid phone number!',
-  'Please enter a valid email address!': 'Please enter a valid email address!',
-  'Please enter your phone number!': 'Please enter your phone number!',
-  'Please enter your email address!': 'Please enter your email address!',
-  'Please enter the verification code!': 'Please enter the verification code!',
-  'Please accept the privacy policy and user agreement!': 'Please accept the privacy policy and user agreement!',
-  'Incorrect verification code, please check the code!': 'Incorrect verification code, please check the code!',
-  'The verification code has expired, please retrieve a new one!': 'The verification code has expired, please retrieve a new one!',
-  'The verification code has been used, please retrieve a new one!': 'The verification code has been used, please retrieve a new one!',
-  'Login failed, please try again.': 'Login failed, please try again.',
-  SEND: 'SEND',
-  ' ': ' ',
   'The room does not exist, please confirm the room number or create a room!': 'The room does not exist, please confirm the room number or create a room!',
   'Log out': 'Log out',
   'Edit profile': 'Edit profile',
@@ -105,6 +81,9 @@ export default {
   'You have been invited by the administrator to step down, please raise your hand if you need to speak': 'You have been invited by the administrator to step down, please raise your hand if you need to speak',
   Invite: 'Invite',
   Settings: 'Settings',
+  VirtualBackground: 'VirtualBackground',
+  Close: 'Close',
+  BlurredBackground: 'Blur',
   EndPC: 'End',
   EndH5: 'End',
   'You are currently the host of the room, please choose the corresponding operation. If you choose "End Room", the current room will be disbanded and all members will be removed. If you choose "Leave Room", the current room will not be disbanded, and your hosting privileges will be transferred to other members.': 'You are currently the host of the room, please choose the corresponding operation. If you choose "End Room", the current room will be disbanded and all members will be removed. If you choose "Leave Room", the current room will not be disbanded, and your hosting privileges will be transferred to other members.',
@@ -220,6 +199,8 @@ export default {
   'Camera settings': 'Camera settings',
   Copy: 'Copy',
   'Copied successfully': 'Copied successfully',
+  'Copied failure': 'Copied failure',
+  'This model does not support copying at this time': 'This model does not support copying at this time',
   'accepted the invitation to the stage': 'accepted the invitation to the stage',
   'declined the invitation to the stage': 'declined the invitation to the stage',
   'All audios disabled': 'All audios disabled',
@@ -229,8 +210,6 @@ export default {
   'Sb invites you to turn on the microphone': ({ named }) => `${named('role')} invites you to turn on the microphone`,
   'All videos disabled': 'All videos disabled',
   'All videos enabled': 'All videos enabled',
-  'Disabling text chat for all is enabled': 'Disabling text chat for all is enabled',
-  'Unblocked all text chat': 'Unblocked all text chat',
   'Your camera has been turned off': 'Your camera has been turned off',
   // @ts-ignore
   'Sb invites you to turn on the camera': ({ named }) => `${named('role')} invites you to turn on the camera`,
@@ -302,8 +281,6 @@ export default {
   'Stage management': 'Stage management',
   'Already on stage': 'Already on stage',
   'Not on stage': 'Not on stage',
-  'The stage is full, please contact the host': 'The stage is full, please contact the host',
-  'The stage is full': 'The stage is full',
   'To go on stage again, you need to reapply and wait for the roomOwner/administrator to approve': 'To go on stage again, you need to reapply and wait for the roomOwner/administrator to approve',
   'To go on stage again, a new application needs to be initiated': 'To go on stage again, a new application needs to be initiated',
   'The request to go on stage has been sent out, please wait for the roomOwner/administrator to approve it': 'The request to go on stage has been sent out, please wait for the roomOwner/administrator to approve it',
@@ -313,14 +290,17 @@ export default {
   'people applying to stage': 'people applying to stage',
   // @ts-ignore
   'and so on number people applying to stage': ({ named }) => `and so on ${named('number')} people applying to stage`,
-  // Room Chat 融合卡片翻译
+  'The stage is full, please contact the host': 'The stage is full, please contact the host',
+  'The stage is full': 'The stage is full',
+  'change name': 'change name',
+  // Room Chat Fusion Card Translation
   'quick conference': 'quick conference',
   Meeting: 'Meeting',
   'Meeting in progress': 'Meeting in progress',
   Initiating: 'Initiating',
-  'X people have joined': ({ values }: any) => `${values?.number} people have joined`,
+  'X people have joined': ({ named }: any) => `${named('number')} people have joined`,
   'Waiting for members to join the meeting': 'Waiting for members to join the meeting',
-  'X people are in the meeting': ({ values }: any) => `${values?.number} people are in the meeting`,
+  'X people are in the meeting': ({ named }: any) => `${named('number')} people are in the meeting`,
   'Already joined': 'Already joined',
   'Enter the meeting': 'Enter the meeting',
   'Ending meeting': 'Ending meeting',
@@ -328,4 +308,79 @@ export default {
   'Currently in a meeting, please exit the current meeting before proceeding.': 'Currently in a meeting, please exit the current meeting before proceeding.',
   'Failed to initiate meeting': 'Failed to initiate meeting',
   'Failed to enter the meeting': 'Failed to enter the meeting',
+  'Failed to disable chat': 'Failed to disable chat',
+  'All members can share screen': 'All members can share screen',
+  'Screen sharing for host/admin only': 'Screen sharing for host/admin only',
+  'Failed to initiate screen sharing, currently only host/admin can share screen.': 'Failed to initiate screen sharing, currently only host/admin can share screen.',
+  'Is it turned on that only the host/admin can share the screen?': 'Is it turned on that only the host/admin can share the screen?',
+  'Other member is sharing the screen is now, the member\'s sharing will be terminated after you turning on': 'Other member is sharing the screen now, the member\'s sharing will be terminated after you turning on',
+  'Your screen sharing has been stopped': 'Your screen sharing has been stopped',
+  'Your screen sharing has been stopped, Now only the host/admin can share the screen': 'Your screen sharing has been stopped, Now only the host/admin can share the screen',
+  'I got it': 'I got it',
+  Attention: 'Attention',
+  'Audio playback failed. Click the "Confirm" to resume playback': 'Audio playback failed. Click the "Confirm" to resume playback.',
+  // Schedule Room
+  'view details': 'view details',
+  'modify room': 'modify room',
+  'cancel room': 'cancel room',
+  join: 'join',
+  'No room available for booking': 'No room available for booking',
+  'Ongoing': 'Ongoing',
+  finished: 'finished',
+  'History room': 'History room',
+  'sb temporary room': ({ named }: any) => `${named('name')}'s temporary room`,
+  year: 'year',
+  month: 'month',
+  day: 'day',
+  Schedule: 'Schedule',
+  'Modify Room': 'Modify Room',
+  'Room Name': 'Room Name',
+  'please enter the room name': 'please enter the room name',
+  'Room type': 'Room type',
+  'Starting time': 'Starting time',
+  'Room duration': 'Room duration',
+  'Time zone': 'Time zone',
+  Attendees: 'Attendees',
+  'Please enter the member name': 'Please enter the member name',
+  people: 'people',
+  'Scheduled meetings': 'scheduled meetings',
+  minute: 'minute',
+  hour: 'hour',
+  minutes: 'minutes',
+  hours: 'hours',
+  'The start time cannot be earlier than the current time': 'The start time cannot be earlier than the current time',
+  'Room Time': 'Room Time',
+  'Creator': 'Creator',
+  'Room Details': 'Room Details',
+  'Enter Now': 'Enter Now',
+  'Invited members': 'Invited members',
+  'The room is closed': 'The room is closed',
+  'Entry Time': 'Entry Time',
+  'Duration of participation': 'Duration of participation',
+  'No cancellation': 'No cancellation',
+  'Other members will not be able to join after cancellation': 'Other members will not be able to join after cancellation',
+  'Inviting members to join': 'Inviting members to join',
+  'Invitation by room ID': 'Invitation by room ID',
+  'Invitation via room link': 'Invitation via room link',
+  'Copy the conference number and link': 'Copy the conference number and link',
+  'Schedule successful, invite members to join': 'Schedule successful, invite members to join',
+  'Name changed successfully': 'Name changed successfully',
+  'schedule year': '/',
+  'schedule month': '/',
+  'schedule day': ' ',
+  'The room name cannot exceed 100 characters': 'The room name cannot exceed 100 characters',
+  'The meeting is in progress and any meeting information cannot be modified': 'The meeting is in progress and any meeting information cannot be modified',
+  'Schedule room loading': 'Schedule room loading...',
+  'The user name cannot exceed 32 characters': 'The user name cannot exceed 32 characters',
+  'sb invites you to join the conference': ({ named }: any) => `${named('name')} invites you to join the conference`,
+  // media privilege
+  microphone: 'microphone',
+  camera: 'camera',
+  'Unable to use the device': ({ named }: any) => `Unable to use the ${named('deviceType')}`,
+  'media device failed to open, please check the media device and try again': ({ named }: any) => `${named('deviceType')} failed to open, please check the ${named('deviceType')} and try again`,
+  'The current device is not authorized, please allow access to the device permissions': ({ named }: any) => `The ${named('deviceType')} is not authorized, please allow access to the ${named('deviceType')} permissions`,
+  'The current device is occupied by other apps, try to close other apps or change the device': ({ named }: any) => `The ${named('deviceType')} is occupied by other apps, try to close other apps or change the device`,
+  'Turn on device privileges': ({ named }: any) => `Turn on ${named('deviceType')} privileges`,
+  'You can go to "System Preferences - Security & Privacy - Device" to enable device permissions': ({ named }: any) => `You can go to "System Preferences - Security & Privacy - ${named('deviceType')}" to enable device permissions.`,
+  'Go to Settings': 'Go to Settings',
 };

+ 91 - 32
MiniProgram/src/locales/zh-CN.ts

@@ -1,33 +1,9 @@
 export default {
-  '': '',
-  'Sign in': '请登录',
-  'Phone Login': '手机登录',
-  'Email Login': '邮箱登录',
-  'I have read and agree to the': '我已阅读并同意',
-  'Privacy Policy': '隐私协议',
-  and: '和',
-  'Terms of Use': '用户协议',
-  Login: '登录',
-  'Mobile number': '请输入您的手机号',
-  'Verification code': '请输入验证码',
-  'Email address': '请输入邮箱',
-  'Please enter a valid phone number!': '请输入正确的手机号!',
-  'Please enter a valid email address!': '请输入正确的邮箱地址!',
-  'Please enter your phone number!': '请输入手机号!',
-  'Please enter your email address!': '请输入邮箱地址!',
-  'Please enter the verification code!': '请输入验证码!',
-  'Please accept the privacy policy and user agreement!': '请同意隐私协议及用户协议!',
-  'Incorrect verification code, please check the code!': '验证码错误,请检查验证码!',
-  'The verification code has expired, please retrieve a new one!': '验证码已过期,请重新获取验证码!',
-  'The verification code has been used, please retrieve a new one!': '验证码已使用,请重新获取验证码!',
-  'Login failed, please try again.': '登录失败,请重试',
-  SEND: '获取验证码',
-  ' ': '倒计时',
   'The room does not exist, please confirm the room number or create a room!': '房间不存在,请确认房间号或创建房间!',
   'Log out': '退出登录',
   'Edit profile': '编辑资料',
   'User Name': '用户名',
-  'Please input user name': '请输入用户名',
+  'Please input user name': '请输入名称',
   'Username length should be greater than 0': '昵称长度应该大于0',
   Save: '保存',
   Camera: '摄像头',
@@ -102,6 +78,9 @@ export default {
   'You have been invited by the administrator to step down, please raise your hand if you need to speak': '您已被管理员邀请下台,需要发言请先举手',
   Invite: '邀请',
   Settings: '设置',
+  VirtualBackground: '虚拟背景',
+  Close: '关闭',
+  BlurredBackground: '背景模糊',
   EndPC: '结束房间',
   EndH5: '结束',
   Tips: '提示',
@@ -220,6 +199,8 @@ export default {
   'Camera settings': '摄像头设置',
   Copy: '复制',
   'Copied successfully': '复制成功',
+  'Copied failure': '复制失败',
+  'This model does not support copying at this time': '此机型暂不支持复制',
   'accepted the invitation to the stage': '接受了上台邀请',
   'declined the invitation to the stage': '拒绝了上台邀请',
   'All audios disabled': '已开启全体静音',
@@ -229,8 +210,6 @@ export default {
   'Sb invites you to turn on the microphone': ({ named }) => `${named('role')}邀请你打开麦克风`,
   'All videos disabled': '已开启全体禁画',
   'All videos enabled': '已解除全体禁画',
-  'Disabling text chat for all is enabled': '已开启全体禁止文字聊天',
-  'Unblocked all text chat': '已解除全体禁止文字聊天',
   'Your camera has been turned off': '已关闭您的摄像头',
   // @ts-ignore
   'Sb invites you to turn on the camera': ({ named }) => `${named('role')}邀请你打开摄像头`,
@@ -302,8 +281,6 @@ export default {
   'Stage management': '上台管理',
   'Already on stage': '已上台',
   'Not on stage': '未上台',
-  'The stage is full, please contact the host': '台上人数已满,请联系主持人',
-  'The stage is full': '台上人数已满',
   'To go on stage again, you need to reapply and wait for the roomOwner/administrator to approve': '再次上台需重新发起申请,并等待房主/管理员通过',
   'To go on stage again, a new application needs to be initiated': '再次上台需重新发起申请',
   'The request to go on stage has been sent out, please wait for the roomOwner/administrator to approve it': '上台申请已发出,请等待房主/管理员通过',
@@ -313,14 +290,17 @@ export default {
   'people applying to stage': '人正在申请上台',
   // @ts-ignore
   'and so on number people applying to stage': ({ named }) => `等 ${named('number')} 人正在申请上台`,
-  // Room Chat 融合卡片翻译
+  'The stage is full, please contact the host': '台上人数已满,请联系主持人',
+  'The stage is full': '台上人数已满',
+  'change name': '修改名称',
+  // Room Chat Fusion Card Translation
   'quick conference': '快速会议',
   Meeting: '会议',
   'Meeting in progress': '会议 进行中',
   Initiating: '正在发起',
-  'X people have joined': ({ values }: any) => `${values?.number} 人已入会`,
+  'X people have joined': ({ named }: any) => `${named('number')} 人已入会`,
   'Waiting for members to join the meeting': '等待成员入会',
-  'X people are in the meeting': ({ values }: any) => `${values?.number} 人在会议中`,
+  'X people are in the meeting': ({ named }: any) => `${named('number')} 人在会议中`,
   'Already joined': '已入会',
   'Enter the meeting': '进入会议',
   'Ending meeting': '正在结束会议',
@@ -328,4 +308,83 @@ export default {
   'Currently in a meeting, please exit the current meeting before proceeding.': '正在会议中,请先退出当前会议后再进行操作',
   'Failed to initiate meeting': '发起会议失败',
   'Failed to enter the meeting': '进入会议失败',
+  'Failed to disable chat': '禁言失败',
+  'All members can share screen': '全体成员可共享屏幕',
+  'Screen sharing for host/admin only': '仅房主/管理员可共享屏幕',
+  'Failed to initiate screen sharing, currently only host/admin can share screen.': '发起共享失败,当前仅房主/管理员可以共享',
+  'Is it turned on that only the host/admin can share the screen?': '是否开启仅房主/管理员可共享屏幕?',
+  'Other member is sharing the screen is now, the member\'s sharing will be terminated after you turning on': '当前有成员正在共享屏幕, 开启后该成员的共享将会被终止',
+  'Your screen sharing has been stopped': '共享屏幕停止',
+  'Your screen sharing has been stopped, Now only the host/admin can share the screen': '已停止您的共享,当前仅房主/管理员可以共享屏幕',
+  'I got it': '我知道了',
+  Attention: '注意',
+  'Audio playback failed. Click the "Confirm" to resume playback': '音频播放失败。单击 “确认 ”恢复播放。',
+  // Schedule Room
+  'view details': '查看详情',
+  'modify room': '修改房间',
+  'cancel room': '取消房间',
+  join: '加入',
+  'No room available for booking': '暂无预订房间',
+  'Ongoing': '进行中',
+  finished: '已结束',
+  'History room': '历史房间',
+  'sb temporary room': ({ named }: any) => `${named('name')}的临时房间`,
+  year: '年',
+  month: '月',
+  day: '日',
+  Schedule: '预订房间',
+  'Modify Room': '修改房间',
+  'Room Name': '房间名称',
+  'please enter the room name': '请输入房间名称',
+  'Room type': '房间类型',
+  'Starting time': '开始时间',
+  'Room duration': '房间时长',
+  'Time zone': '时区',
+  Attendees: '参与成员',
+  'Please enter the member name': '请输入成员名称',
+  people: '人',
+  Contacts: '联系人',
+  'No relevant members found': '暂无相关成员',
+  'Selected Contact': '已选联系人',
+  'Scheduled meetings': '预订的会议',
+  minute: '分钟',
+  hour: '小时',
+  minutes: '分钟',
+  hours: '小时',
+  'The start time cannot be earlier than the current time': '开始时间不能早于当前时间',
+  'Room Time': '房间时间',
+  'Creator': '发起人',
+  'Room Details': '房间详情',
+  'Enter Now': '立即进入',
+  'Invited members': '邀请成员',
+  'The room is closed': '房间已结束',
+  'Entry Time': '进房时间',
+  'Duration of participation': '参会时长',
+  'No cancellation': '暂不取消',
+  'Cancellation of scheduled rooms': '取消已预订的房间',
+  'Other members will not be able to join after cancellation': '取消后其他成员将无法加入',
+  'Inviting members to join': '邀请成员加入',
+  'Invitation by room ID': '通过房间号邀请',
+  'Invitation via room link': '通过房间链接邀请',
+  'Copy the conference number and link': '复制会议号与链接',
+  'Schedule successful, invite members to join': '预订成功,邀请成员加入',
+  'Name changed successfully': '名称修改成功',
+  'schedule year': '年',
+  'schedule month': '月',
+  'schedule day': '日',
+  'The room name cannot exceed 100 characters': '房间名称不能超过100个字节',
+  'The meeting is in progress and any meeting information cannot be modified': '进行中的会议,不能修改任何会议信息',
+  'Schedule room loading': '预定房间加载中...',
+  'The user name cannot exceed 32 characters': '用户名称不能超过32个字节',
+  'sb invites you to join the conference': ({ named }: any) => `${named('name')} 邀请您加入会议`,
+  // media privilege
+  microphone: '麦克风',
+  camera: '摄像头',
+  'Unable to use the device': ({ named }: any) => `无法使用${named('deviceType')}`,
+  'media device failed to open, please check the media device and try again': ({ named }: any) => `打开${named('deviceType')}失败, 请检查${named('deviceType')}设备并重试`,
+  'The current device is not authorized, please allow access to the device permissions': ({ named }: any) => `${named('deviceType')}设备未授权, 请允许访问${named('deviceType')}权限`,
+  'The current device is occupied by other apps, try to close other apps or change the device': ({ named }: any) => `当前${named('deviceType')}被其他应用程序占用,请尝试关闭其他应用程序或更换${named('deviceType')}`,
+  'Turn on device privileges': ({ named }: any) => `打开${named('deviceType')}权限`,
+  'You can go to "System Preferences - Security & Privacy - Device" to enable device permissions': ({ named }: any) => `你可前往"系统设置 - 隐私与安全性 - ${named('deviceType')}"开启设备权限。`,
+  'Go to Settings': '前往设置',
 };

+ 13 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/AllMembersShareScreenIcon.svg

@@ -0,0 +1,13 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="design-iconfont">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M1.66669 14.9998C1.66669 12.3681 3.69998 10.2112 6.28134 10.0145C6.40852 10.0048 6.53703 9.99985 6.66669 9.99985H10C10.1297 9.99985 10.2582 10.0048 10.3854 10.0145C12.9667 10.2112 15 12.3681 15 14.9998V18.3332H1.66669V14.9998ZM13.3334 14.9998V16.6665H3.33335V14.9998C3.33335 13.1589 4.82574 11.6665 6.66669 11.6665H10C11.841 11.6665 13.3334 13.1589 13.3334 14.9998Z" fill="#4E5461" style="fill:#4E5461;fill:color(display-p3 0.3040 0.3284 0.3800);fill-opacity:1;" />
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M6.66669 8.3602C6.64897 8.3514 6.63135 8.34246 6.61379 8.33339C5.40776 7.70998 4.58335 6.45126 4.58335 5C4.58335 2.92893 6.26229 1.25 8.33335 1.25C10.4044 1.25 12.0834 2.92893 12.0834 5C12.0834 6.45126 11.259 7.70998 10.0529 8.33339C10.0354 8.34246 10.0177 8.3514 10 8.3602C9.49795 8.60971 8.93204 8.75 8.33335 8.75C7.73467 8.75 7.16875 8.60971 6.66669 8.3602ZM10.4167 5C10.4167 6.15059 9.48395 7.08333 8.33335 7.08333C7.18276 7.08333 6.25002 6.15059 6.25002 5C6.25002 3.84941 7.18276 2.91667 8.33335 2.91667C9.48395 2.91667 10.4167 3.84941 10.4167 5Z" fill="#4E5461" style="fill:#4E5461;fill:color(display-p3 0.3040 0.3284 0.3800);fill-opacity:1;" />
+    <path d="M12.5651 8.67685L13.2006 7.38123C13.5522 6.66449 13.7504 5.85772 13.7504 4.99992C13.7504 4.13294 13.5467 3.31354 13.1846 2.58695L14.6269 1.74561C15.1318 2.72004 15.417 3.82667 15.417 4.99992C15.417 6.11771 15.1581 7.17503 14.697 8.11518C16.8927 9.61592 18.3337 12.1396 18.3337 14.9999V18.3333L16.667 18.3333V14.9999C16.667 12.7132 15.5172 10.6946 13.7565 9.49115L12.5651 8.67685Z" fill="#4E5461" style="fill:#4E5461;fill:color(display-p3 0.3040 0.3284 0.3800);fill-opacity:1;" />
+  <defs>
+    <filter id="filter0_b_10685_2618" x="-4.99998" y="-5.41667" width="30.0003" height="30.4166" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+      <feFlood flood-opacity="0" result="BackgroundImageFix" />
+      <feGaussianBlur in="BackgroundImageFix" stdDeviation="3.33333" />
+      <feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_10685_2618" />
+      <feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_10685_2618" result="shape" />
+    </filter>
+  </defs>
+</svg>

+ 5 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/ArrowStrokeRightIcon.svg

@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <g id="Group 7815">
+    <path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M12.3935 8L8.64345 3.5H10.8564L14.1529 7.45584L14.6064 8L14.1529 8.54416L10.8564 12.5H8.64345L12.3935 8ZM8 7.15H1V8.85H8V7.15Z" fill="currentColor" />
+  </g>
+</svg>

+ 3 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/CalendarIcon.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M10 3H6L6 1.5H5L5 3H3C2.44772 3 2 3.44771 2 4V13C2 13.5523 2.44772 14 3 14H13C13.5523 14 14 13.5523 14 13V4C14 3.44772 13.5523 3 13 3H11V1.5H10L10 3ZM5 5L6 5L6 4H10L10 5L11 5V4H13V6H3V4H5L5 5ZM3 7H13V13H3L3 7Z" fill="currentColor" />
+</svg>

+ 9 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/EditNameCardIcon.svg

@@ -0,0 +1,9 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="&#231;&#148;&#187;&#231;&#172;&#148;">
+<g id="Union">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5791 4.48698L7.10505 17.4325L2.40326 18.4929L0.970703 13.8908L8.4448 0.945312L14.5791 4.48698ZM9.05484 3.22202L2.77894 14.0922L3.53735 16.5286L6.02653 15.9672L12.3024 5.09702L9.05484 3.22202Z" fill="currentColor"/>
+<path d="M9.5978 17.5003L18.3329 17.5003V15.8337H10.5601L9.5978 17.5003Z" fill="currentColor"/>
+<path d="M18.3331 13.7503L11.7633 13.7503L12.7256 12.0837H18.3331V13.7503Z" fill="currentColor"/>
+</g>
+</g>
+</svg>  

+ 5 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/EllipsisIcon.svg

@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M3 9C2.44775 9 2 8.55228 2 8C2 7.44772 2.44775 7 3 7C3.55225 7 4 7.44772 4 8C4 8.55228 3.55225 9 3 9Z" fill="currentColor" />
+  <path d="M7 8C7 8.55228 7.44775 9 8 9C8.55225 9 9 8.55228 9 8C9 7.44772 8.55225 7 8 7C7.44775 7 7 7.44772 7 8Z" fill="currentColor" />
+  <path d="M12 8C12 8.55228 12.4478 9 13 9C13.5522 9 14 8.55228 14 8C14 7.44772 13.5522 7 13 7C12.4478 7 12 7.44772 12 8Z" fill="currentColor" />
+</svg>

+ 11 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/HostShareScreenIcon.svg

@@ -0,0 +1,11 @@
+<svg width="20" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="design-iconfont">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M5.84823 10.4236C2.98532 10.5677 0.708374 12.9346 0.708374 15.8333V19.5833H15.2917V15.8333C15.2917 12.9346 13.0148 10.5677 10.1519 10.4236C10.0602 10.419 9.96788 10.4167 9.87504 10.4167H6.12504C6.03221 10.4167 5.93992 10.419 5.84823 10.4236ZM6.18166 8.75001C6.73114 9.01695 7.3481 9.16668 8.00004 9.16668C8.65198 9.16668 9.26894 9.01695 9.81843 8.75001C11.2085 8.07471 12.1667 6.64926 12.1667 5.00001C12.1667 2.69882 10.3012 0.833344 8.00004 0.833344C5.69885 0.833344 3.83337 2.69882 3.83337 5.00001C3.83337 6.64926 4.79158 8.07471 6.18166 8.75001ZM13.625 17.9167V15.8333C13.625 13.7623 11.9461 12.0833 9.87504 12.0833H6.12504C4.05397 12.0833 2.37504 13.7623 2.37504 15.8333V17.9167H13.625ZM8.00004 7.50001C9.38075 7.50001 10.5 6.38072 10.5 5.00001C10.5 3.6193 9.38075 2.50001 8.00004 2.50001C6.61933 2.50001 5.50004 3.6193 5.50004 5.00001C5.50004 6.38072 6.61933 7.50001 8.00004 7.50001Z" fill="#4E5461" style="fill:#4E5461;fill:color(display-p3 0.3040 0.3284 0.3800);fill-opacity:1;" />
+  <defs>
+    <filter id="filter0_b_10687_41" x="-5.95829" y="-5.83332" width="27.9167" height="32.0833" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+      <feFlood flood-opacity="0" result="BackgroundImageFix" />
+      <feGaussianBlur in="BackgroundImageFix" stdDeviation="3.33333" />
+      <feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_10687_41" />
+      <feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_10687_41" result="shape" />
+    </filter>
+  </defs>
+</svg>

+ 8 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/LinkIcon.svg

@@ -0,0 +1,8 @@
+<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <g id="Group 1792">
+    <g id="Group 7870">
+      <path id="Vector 88" d="M5.35 1H2C1.72386 1 1.5 1.22386 1.5 1.5V11.5C1.5 11.7761 1.72386 12 2 12H12C12.2761 12 12.5 11.7761 12.5 11.5V8.15" stroke="currentColor" stroke-width="1.5" />
+      <path id="Vector 87" d="M7.5 1H12.5M12.5 1V6M12.5 1L7.5 6" stroke="currentColor" stroke-width="1.5" />
+    </g>
+  </g>
+</svg>

+ 3 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/LoadingScheduleIcon.svg

@@ -0,0 +1,3 @@
+<svg width="36" height="36" fill="#1C66EF" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+<path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" class="spinner_aj0A"/>
+</svg>

+ 0 - 8
MiniProgram/src/roomkit/TUIRoom/assets/icons/MailIcon.svg

@@ -1,8 +0,0 @@
-<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path
-    fill-rule="evenodd"
-    clip-rule="evenodd"
-    d="M4.96094 8.0358V11.5992L7.55254 9.81752L4.96094 8.0358ZM16.9609 8.0358L14.3697 9.81725L16.9609 11.5987V8.0358ZM13.1194 10.6768L11.6219 11.7064C11.2238 11.9801 10.6981 11.9801 10.3 11.7064L8.80284 10.6771L4.96094 13.3184V15.1673H16.9609V13.3179L13.1194 10.6768ZM4.29427 3.33398C3.83403 3.33398 3.46094 3.70708 3.46094 4.16732V15.834C3.46094 16.2942 3.83403 16.6673 4.29427 16.6673H17.6276C18.0878 16.6673 18.4609 16.2942 18.4609 15.834V4.16732C18.4609 3.70708 18.0878 3.33398 17.6276 3.33398H4.29427ZM5.04427 6.2728V4.91732H16.8776V6.2728L10.9609 10.3405L5.04427 6.2728Z"
-    fill="#D5E0F2"
-  />
-</svg>

+ 0 - 8
MiniProgram/src/roomkit/TUIRoom/assets/icons/PhoneIcon.svg

@@ -1,8 +0,0 @@
-<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path
-    d="M5 1.75H15C15.1381 1.75 15.25 1.86193 15.25 2V18C15.25 18.1381 15.1381 18.25 15 18.25H5C4.86193 18.25 4.75 18.1381 4.75 18V2C4.75 1.86193 4.86193 1.75 5 1.75Z"
-    stroke="#D5E0F2"
-    stroke-width="1.5"
-  />
-  <circle cx="10" cy="14.5" r="0.5" fill="#D5E0F2" stroke="#D5E0F2" />
-</svg>

+ 11 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/ScheduleAttendees.svg

@@ -0,0 +1,11 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" @click="emit('click')">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M0.666748 12C0.666748 9.8945 2.29352 8.16896 4.35874 8.01169C4.46039 8.00395 4.56311 8.00001 4.66675 8.00001H7.33341C7.43705 8.00001 7.53977 8.00395 7.64142 8.01169C9.70664 8.16896 11.3334 9.89451 11.3334 12V14.633H0.666748V12ZM10.0001 12V13.2997H2.00008V12C2.00008 10.5272 3.19399 9.33334 4.66675 9.33334H7.33341C8.80617 9.33334 10.0001 10.5272 10.0001 12Z" fill="currentColor" />
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M4.66675 6.68816C4.65266 6.68116 4.63864 6.67405 4.62468 6.66684C3.65972 6.16815 3.00008 5.16111 3.00008 4C3.00008 2.34315 4.34323 1 6.00008 1C7.65693 1 9.00008 2.34315 9.00008 4C9.00008 5.16111 8.34045 6.16815 7.37548 6.66684C7.36152 6.67405 7.3475 6.68116 7.33341 6.68816C6.93176 6.88776 6.47903 7 6.00008 7C5.52113 7 5.0684 6.88776 4.66675 6.68816ZM7.66675 4C7.66675 4.92047 6.92056 5.66667 6.00008 5.66667C5.07961 5.66667 4.33341 4.92047 4.33341 4C4.33341 3.07953 5.07961 2.33333 6.00008 2.33333C6.92056 2.33333 7.66675 3.07953 7.66675 4Z" fill="currentColor" />
+  <path d="M10.6667 5H15.3334V3.66667H10.6667V5Z" fill="currentColor" />
+  <path d="M11.6667 8.66667H15.3334V7.33333H11.6667V8.66667Z" fill="currentColor" />
+  <path d="M15.3334 12.3333H12.6667V11H15.3334V12.3333Z" fill="currentColor" />
+</svg>
+<script setup lang="ts">
+import { defineEmits } from 'vue';
+const emit = defineEmits(['click']);
+</script>

+ 3 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/ScheduleRoomIcon.svg

@@ -0,0 +1,3 @@
+<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M12 2.5H6L6 0.25H4.5L4.5 2.5H1.5C0.671573 2.5 0 3.17157 0 4V17.5C0 18.3284 0.671573 19 1.5 19H16.5C17.3284 19 18 18.3284 18 17.5V4C18 3.17157 17.3284 2.5 16.5 2.5H13.5V0.25H12L12 2.5ZM4.5 5.5L6 5.5V4H12L12 5.5L13.5 5.5V4H16.5V7H1.5V4H4.5L4.5 5.5ZM1.5 8.5H16.5V17.5H1.5L1.5 8.5Z" fill="currentColor" />
+</svg>

+ 6 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/SuccessIcon.svg

@@ -0,0 +1,6 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="Union">
+<path d="M5.58366 10.0765L9.18 13.7056L14.6337 7.55301L13.5737 6.66671L8.97813 10.9392L6.42526 9.16785L5.58366 10.0765Z" fill="#00B89C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.1668 10C19.1668 15.0626 15.0628 19.1667 10.0002 19.1667C4.93755 19.1667 0.833496 15.0626 0.833496 10C0.833496 4.93743 4.93755 0.833374 10.0002 0.833374C15.0628 0.833374 19.1668 4.93743 19.1668 10ZM17.5002 10C17.5002 14.1422 14.1423 17.5 10.0002 17.5C5.85803 17.5 2.50016 14.1422 2.50016 10C2.50016 5.8579 5.85803 2.50004 10.0002 2.50004C14.1423 2.50004 17.5002 5.8579 17.5002 10Z" fill="#00B89C"/>
+</g>
+</svg>

+ 0 - 9
MiniProgram/src/roomkit/TUIRoom/assets/icons/VerifyIcon.svg

@@ -1,9 +0,0 @@
-<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path
-    d="M3.48047 4.05096L10.4838 2L17.4805 4.05096V8.25698C17.4805 12.6778 14.6611 16.6025 10.4815 18C6.30073 16.6026 3.48047 12.6769 3.48047 8.25503V4.05096Z"
-    stroke="#D5E0F2"
-    stroke-width="1.5"
-    stroke-linejoin="round"
-  />
-  <path d="M6.74023 9.58333L9.6876 12.5L14.7402 7.5" stroke="#D5E0F2" stroke-width="1.5" stroke-linejoin="round" />
-</svg>

+ 10 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/VirtualBackgroundIcon.svg

@@ -0,0 +1,10 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <mask id="path-1-inside-1_9286_2150" fill="white">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M12 10C14.2091 10 16 8.20914 16 6C16 3.79086 14.2091 2 12 2C9.79086 2 8 3.79086 8 6C8 8.20914 9.79086 10 12 10ZM10 12C6.68629 12 4 14.6863 4 18V21H20V18C20 14.6863 17.3137 12 14 12H10Z" />
+  </mask>
+  <path d="M4 21H2.3V22.7H4V21ZM20 21V22.7H21.7V21H20ZM14.3 6C14.3 7.27025 13.2703 8.3 12 8.3V11.7C15.148 11.7 17.7 9.14802 17.7 6H14.3ZM12 3.7C13.2703 3.7 14.3 4.72975 14.3 6H17.7C17.7 2.85198 15.148 0.3 12 0.3V3.7ZM9.7 6C9.7 4.72975 10.7297 3.7 12 3.7V0.3C8.85198 0.3 6.3 2.85198 6.3 6H9.7ZM12 8.3C10.7297 8.3 9.7 7.27025 9.7 6H6.3C6.3 9.14802 8.85198 11.7 12 11.7V8.3ZM5.7 18C5.7 15.6252 7.62518 13.7 10 13.7V10.3C5.74741 10.3 2.3 13.7474 2.3 18H5.7ZM5.7 21V18H2.3V21H5.7ZM20 19.3H4V22.7H20V19.3ZM18.3 18V21H21.7V18H18.3ZM14 13.7C16.3748 13.7 18.3 15.6252 18.3 18H21.7C21.7 13.7474 18.2526 10.3 14 10.3V13.7ZM10 13.7H14V10.3H10V13.7Z" fill="currentColor" mask="url(#path-1-inside-1_9286_2150)" />
+  <path d="M3 12L7 8" stroke="var(--active-color-2)" stroke-width="1.7" />
+  <path d="M18 12L22 8" stroke="var(--active-color-2)" stroke-width="1.7" />
+  <path d="M2.00098 7L6.00098 3" stroke="var(--active-color-2)" stroke-width="1.7" />
+  <path d="M17.001 7L21.001 3" stroke="var(--active-color-2)" stroke-width="1.7" />
+</svg>

+ 11 - 0
MiniProgram/src/roomkit/TUIRoom/assets/icons/WarningIcon.svg

@@ -0,0 +1,11 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <g id="&#230;&#179;&#168;&#230;&#132;&#143;">
+    <path
+      id="Vector"
+      fill-rule="evenodd"
+      clip-rule="evenodd"
+      d="M10.0007 2.49919C14.1428 2.49919 17.5006 5.85702 17.5006 9.99919C17.5006 14.1413 14.1428 17.4992 10.0007 17.4992C5.85852 17.4992 2.50065 14.1413 2.50065 9.99919C2.50065 5.85702 5.85852 2.49919 10.0007 2.49919ZM19.1673 9.99919C19.1673 4.9366 15.0632 0.83252 10.0007 0.83252C4.93804 0.83252 0.833984 4.9366 0.833984 9.99919C0.833984 15.0618 4.93804 19.1659 10.0007 19.1659C15.0632 19.1659 19.1673 15.0618 19.1673 9.99919ZM9.16732 5.41585V11.6659H10.834V5.41585H9.16732ZM10.834 12.9159H9.16407V14.5858H10.834V12.9159Z"
+      fill="#FFAD66"
+    />
+  </g>
+</svg>

BIN
MiniProgram/src/roomkit/TUIRoom/assets/imgs/background-white.png


BIN
MiniProgram/src/roomkit/TUIRoom/assets/imgs/blurred-background.png


BIN
MiniProgram/src/roomkit/TUIRoom/assets/imgs/close-virtual-background.png


+ 4 - 2
MiniProgram/src/roomkit/TUIRoom/assets/style/black-theme.scss

@@ -11,6 +11,7 @@
   --background-color-10: #2B2F38;
   --background-color-11: #2B303A;
   --background-color-12: #4F586B;
+  --hover-background-colo-1: rgba(213, 224, 242, 0.5);
   --font-color-1: #D5E0F2;
   --font-color-2: #6B758A;
   --font-color-3: #B3B8C8;
@@ -19,6 +20,8 @@
   --font-color-6: #CFD5E6;
   --font-color-7: #FFFFFF;
   --font-color-8: #8F9AB2;
+  --font-color-9: #969EB4;
+  --font-color-10: #FF643D;
   --title-color: #000000;
   --stroke-color: rgba(79, 88, 107, 0.30);
   --stroke-color-2: #434956;
@@ -124,7 +127,7 @@
   --el-message-box-title: #CFD4E6;
   --el-button-text-color: #7C85A6;
   --selected-screen-share-dialog: #006EFF;
-  /* H5新增 */
+  /* H5 New Content */
   --log-out-mobile: rgba(0, 0, 0, 0.7);
   --log-out: #2C2C2E;
   --log-out-cancel: #2C2C2E;
@@ -156,5 +159,4 @@
   --user-extra-info-color-h5: #FFFFFF;
   --apply-container-background-outline-h5: #292D38;
   --apply-container-outline-h5: #CFD5E6;
-  --mute-all-h5: #FF2E2E
 }

+ 4 - 2
MiniProgram/src/roomkit/TUIRoom/assets/style/white-theme.scss

@@ -11,6 +11,7 @@
   --background-color-10: #E7EAF7;
   --background-color-11: #F0F3FA;
   --background-color-12: #FFFFFF;
+  --hover-background-color-1: rgba(213, 224, 242, 0.5);
   --font-color-1: #4F586B;
   --font-color-2: #B2BBD1;
   --font-color-3: #000000;
@@ -19,6 +20,8 @@
   --font-color-6: #0F1014;
   --font-color-7: #FFFFFF;
   --font-color-8: #4F586B;
+  --font-color-9: #969EB4;
+  --font-color-10: #FF643D;
   --title-color: #0F1014;
   --stroke-color: #E4EAF7;
   --stroke-color-2: #EAEFF8;
@@ -122,7 +125,7 @@
   --el-message-box-title: #000000;
   --el-button-text-color: #646366;
   --selected-screen-share-dialog: #006EFF;
-  /* H5新增 */
+  /* H5 New Content */
   --log-out-mobile: rgba(0, 0, 0, 0.7);
   --log-out: #F9F9F9C7;
   --log-out-cancel: #FFFFFF;
@@ -154,5 +157,4 @@
   --user-extra-info-color-h5: #FFFFFF;
   --apply-container-background-outline-h5: #006EFF;
   --apply-container-outline-h5: #FFFFFF;
-  --mute-all-h5: #FFFFFF
 }

+ 5 - 5
MiniProgram/src/roomkit/TUIRoom/components/Chat/ChatEditor/index.vue

@@ -1,18 +1,18 @@
 <template>
-  <div :class="['chat-editor', cannotSendMessage ? 'disable-editor' : '']">
+  <div :class="['chat-editor', isMessageDisabled ? 'disable-editor' : '']">
     <div class="chat-input-container">
       <div class="input-content">
         <svg-icon
           style="display: flex"
-          @tap="togglePopover" :icon="EmojiIcon" :class="['emoji-icon', { 'disable-emoji': cannotSendMessage }]"
+          @tap="togglePopover" :icon="EmojiIcon" :class="['emoji-icon', { 'disable-emoji': isMessageDisabled }]"
         />
         <input
           ref="editorInputEle"
           v-model="sendMsg"
           type="text"
-          :disabled="cannotSendMessage"
+          :disabled="isMessageDisabled"
           class="content-bottom-input"
-          :placeholder="cannotSendMessage ? t('Muted by the moderator') : t('Type a message')"
+          :placeholder="isMessageDisabled ? t('Muted by the moderator') : t('Type a message')"
           enterkeyhint="send"
           @keyup.enter="sendMessage"
         />
@@ -33,7 +33,7 @@ const {
   editorInputEle,
   sendMsg,
   isEmojiToolbarVisible,
-  cannotSendMessage,
+  isMessageDisabled,
   sendMessage,
   handleChooseEmoji,
   togglePopover,

+ 5 - 16
MiniProgram/src/roomkit/TUIRoom/components/Chat/ChatEditor/useChatEditor.ts

@@ -1,4 +1,4 @@
-import { computed, ref, watch } from 'vue';
+import { ref, watch } from 'vue';
 import { storeToRefs } from 'pinia';
 import TUIMessage from '../../common/base/Message/index';
 
@@ -18,23 +18,15 @@ export default function useChatEditor() {
   const roomStore = useRoomStore();
 
   const { roomId } = storeToRefs(basicStore);
-  const { isMessageDisableByAdmin } = storeToRefs(chatStore);
-  const { isMessageDisableForAllUser } = storeToRefs(roomStore);
+  const { isMessageDisabled } = storeToRefs(chatStore);
   const editorInputEle = ref();
   const sendMsg = ref('');
   const isEmojiToolbarVisible = ref(false);
-  watch(isMessageDisableByAdmin, (value) => {
+  watch(isMessageDisabled, (value) => {
     if (value) {
       sendMsg.value = '';
     }
   });
-
-  watch(isMessageDisableForAllUser, (value) => {
-    if (value) {
-      sendMsg.value = '';
-    }
-  });
-  const cannotSendMessage = computed(() => Boolean(isMessageDisableByAdmin.value || isMessageDisableForAllUser.value));
   const sendMessage = async () => {
     const result = decodeSendTextMsg(sendMsg.value);
     if (result === '') {
@@ -61,7 +53,7 @@ export default function useChatEditor() {
         payload: {
           text: result,
         },
-        nick: roomStore.localUser.userName || roomStore.localUser.userId,
+        nick: roomStore.localUser.nameCard || roomStore.localUser.userName || roomStore.localUser.userId,
         from: roomStore.localUser.userId,
         flow: 'out',
         sequence: Math.random(),
@@ -69,8 +61,6 @@ export default function useChatEditor() {
     } catch (e) {
     /**
      * Message delivery failure
-     *
-     * 消息发送失败
     **/
       TUIMessage({ type: 'error', message: t('Failed to send the message') });
     }
@@ -87,8 +77,7 @@ export default function useChatEditor() {
     t,
     editorInputEle,
     sendMsg,
-    isMessageDisableByAdmin,
-    cannotSendMessage,
+    isMessageDisabled,
     sendMessage,
     handleChooseEmoji,
     isEmojiToolbarVisible,

+ 6 - 1
MiniProgram/src/roomkit/TUIRoom/components/Chat/MessageList/index.vue

@@ -10,7 +10,7 @@
         :class="['message-item', `${'out' === item.flow ? 'is-me' : ''}`]"
       >
         <div v-if="getDisplaySenderName(index)" class="message-header" :title="item.nick || item.from">
-          {{ item.nick || item.from }}
+          {{ getDisplayName(item.from) }}
         </div>
         <div class="message-body">
           <message-text :data="item.payload.text" />
@@ -22,12 +22,17 @@
 
 <script setup lang="ts">
 import { getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue';
+import { storeToRefs } from 'pinia';
 import MessageText from '../MessageTypes/MessageText.vue';
 import useMessageList from './useMessageListHook';
 import { getBoundingClientRect, getScrollInfo, instanceMapping } from '../../../utils/domOperation';
 import { throttle } from '../../../utils/utils';
+import { useRoomStore } from '../../../stores/room';
+
 const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance();
 const scrollTop = ref();
+const roomStore = useRoomStore();
+const { getDisplayName } = storeToRefs(roomStore)
 let isScrollAtBottom = true;
 const {
   setMessageListInfo,

+ 2 - 1
MiniProgram/src/roomkit/TUIRoom/components/Chat/MessageList/useMessageListHook.ts

@@ -80,8 +80,9 @@ export default function useMessageList() {
     if (!options || !options.data) {
       return;
     }
+    const currentConversationId = `GROUP${roomId.value}`
     options.data.forEach((message: any) => {
-      if (message.type !== TencentCloudChat.TYPES.MSG_TEXT) {
+      if (message.conversationID !== currentConversationId || message.type !== TencentCloudChat.TYPES.MSG_TEXT) {
         return;
       }
       const { ID, payload: { text }, nick: userName, from: userId } = message;

+ 0 - 6
MiniProgram/src/roomkit/TUIRoom/components/Chat/util.ts

@@ -1,8 +1,4 @@
 /**
- * 聊天界面表情输入界面
- * 需要注意的是, TUIRoomKit 里面的表情包都是有版权限制的,购买的 IM 服务不包括表情包的使用权,请在上线的时候替换成自己的表情包,否则会面临法律风险
- * 黄脸表情为腾讯云版权所有,如要使用需获得授权,请通过以下链接联系我们。
- *
  * Emoji input interface in the chat screen.
  * It should be noted that the emoticons in TUIRoomKit are copyrighted. The purchased IM service does not include the
  * right to use the emoticons. Please replace them with your own emoticons when you go online, otherwise you will
@@ -372,7 +368,6 @@ export function decodeMessageText(payload: string) {
   /**
    * Text Message
    *
-   * 文本消息
   **/
   let temp = payload;
   let left = -1;
@@ -418,7 +413,6 @@ export function decodeSendTextMsg(payload: string) {
   /**
    * Text Message
    *
-   * 文本消息
   **/
   let temp = payload;
   let left = -1;

+ 83 - 4
MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberControl/index.vue

@@ -1,9 +1,8 @@
 <template>
-  <div v-if="!isGeneralUser" class="member-control-container">
+  <div class="member-control-container">
     <div class="member-title">
       <Avatar class="avatar-url" :img-src="userInfo.avatarUrl"></Avatar>
-      <div class="member-title-content">{{ userInfo.userName || userInfo.userId }}</div>
-      <!-- TODO: 完善 @tap 的 .stop 修饰符 -->
+      <div class="member-title-content">{{ roomService.getDisplayName(userInfo) }}</div>
       <span v-if="isWeChat" @tap.stop="handleCloseControl" class="tab-cancel">{{ t('Cancel') }}</span>
     </div>
     <div
@@ -28,6 +27,20 @@
     >
       <span>{{ dialogData.content }}</span>
     </Dialog>
+    <div class="input-content-container" ref="editorInputEleContainer" @tap.stop="handleCloseInput" v-show="isShowInput">
+      <div class="input-content" >
+        <div class="input">
+          <tui-input ref="editorInputEle"
+            :theme="roomService.basicStore.defaultTheme"
+            :model-value="tempUserName"
+            type="text"
+            enterkeyhint="done"
+            @input="tempUserName = $event"
+            @done="handleAction(props.userInfo)">
+          </tui-input>
+      </div>
+    </div>
+  </div>
   </div>
 </template>
 
@@ -39,6 +52,8 @@ import Dialog from '../../common/base/Dialog/index.vue';
 import useMemberControlHooks from './useMemberControlHooks';
 import { useI18n } from '../../../locales';
 import { UserInfo } from '../../../stores/room';
+import { roomService } from '../../../services';
+import TuiInput from '../../common/base/Input/index.vue';
 
 interface Props {
   userInfo: UserInfo,
@@ -49,19 +64,33 @@ const props = defineProps<Props>();
 
 const { t } = useI18n();
 const {
-  isGeneralUser,
   controlList,
   handleCancelDialog,
   handleAction,
   isDialogVisible,
   dialogData,
+  tempUserName,
+  isShowInput,
+  editorInputEleContainer,
+  editorInputEle,
 } = useMemberControlHooks(props);
 
 const emit = defineEmits(['on-close-control']);
 
 function handleCloseControl() {
+  isShowInput.value = false;
   emit('on-close-control');
 }
+
+function handleCloseInput(event: any) {
+  if(isWeChat) {
+    isShowInput.value = false;
+  } else if (event.target !== event.currentTarget) {
+    return;
+  } else {
+    isShowInput.value = false;
+  }
+}
 </script>
 
 <style lang="scss" scoped>
@@ -131,4 +160,54 @@ function handleCloseControl() {
   text-align: end;
   padding-right: 30px
 }
+.input-content-container{
+  position: fixed;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 100vw;
+  height: auto;
+  box-sizing: border-box;
+  background-color: var(--log-out-mobile);
+  z-index: 9999;
+  .input-content {
+    display: flex;
+    width: 100%;
+    align-items: center;
+    justify-content: center;
+    color: #676c80;
+    background: var(--background-color-1);
+    border: none;
+    box-sizing: border-box;
+    font-family: "PingFang SC";
+    font-style: normal;
+    font-weight: 450;
+    font-size: 16px;
+    line-height: 4vh;
+    resize: none;
+    padding: 10px;
+    position: absolute;
+    bottom: 0;
+  }
+  .input{
+    width: 100%;
+  }
+  .content-bottom-input {
+    color: #676c80;
+    width: 100%;
+    height: 35px;
+    border: none;
+    box-sizing: border-box;
+    font-family: 'PingFang SC';
+    font-style: normal;
+    font-weight: 450;
+    font-size: 16px;
+    line-height: 4vh;
+    background-color: var(--chat-editor-input-color-h5);
+    caret-color: var(--caret-color);
+    border-radius: 45px;
+    resize: none;
+    padding-left: 12px;
+  }
+}
 </style>

+ 128 - 36
MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberControl/useMemberControlHooks.ts

@@ -4,7 +4,7 @@ import { UserInfo, useRoomStore } from '../../../stores/room';
 import useGetRoomEngine from '../../../hooks/useRoomEngine';
 import { useBasicStore } from '../../../stores/basic';
 import useMasterApplyControl from '../../../hooks/useMasterApplyControl';
-import { TUIMediaDevice, TUIRole, TUIRequestCallbackType, TUIErrorCode } from '@tencentcloud/tuiroom-engine-wx';
+import  { TUIMediaDevice, TUIRole, TUIRequestCallbackType, TUIErrorCode } from '@tencentcloud/tuiroom-engine-wx';
 import AudioOpenIcon from '../../../assets/icons/AudioOpenIcon.svg';
 import VideoOpenIcon from '../../../assets/icons/VideoOpenIcon.svg';
 import ChatForbiddenIcon from '../../../assets/icons/ChatForbiddenIcon.svg';
@@ -16,15 +16,20 @@ import OffStageIcon from '../../../assets/icons/OffStageIcon.svg';
 import TransferOwnerIcon from '../../../assets/icons/TransferOwnerIcon.svg';
 import SetAdminIcon from '../../../assets/icons/SetAdminIcon.svg';
 import RevokeAdminIcon from '../../../assets/icons/RevokeAdminIcon.svg';
+import EditNameCardIcon from '../../../assets/icons/EditNameCardIcon.svg';
 import { storeToRefs } from 'pinia';
 import TUIMessage from '../../common/base/Message';
 import { MESSAGE_DURATION } from '../../../constants/message';
-
+import eventBus from '../../../hooks/useMitt';
+import useMemberItemHooks from '../MemberItem/useMemberItemHooks';
+import { roomService } from '../../../services';
+import { isMobile } from '../../../utils/environment';
+import { calculateByteLength } from '../../../utils/utils';
 interface ObjectType {
   [key: string]: any;
 }
 
-type ActionType = 'transferOwner' | 'kickUser' | '';
+type ActionType = 'transferOwner' | 'kickUser' | 'changeUserNameCard' | '';
 export default function useMemberControl(props?: any) {
   const roomEngine = useGetRoomEngine();
   const { t } = useI18n();
@@ -32,14 +37,19 @@ export default function useMemberControl(props?: any) {
   const basicStore = useBasicStore();
   const roomStore = useRoomStore();
   const isDialogVisible: Ref<boolean> = ref(false);
-  const dialogData: Ref<{ title: string; content: string; confirmText: string; actionType: ActionType }> = ref({
+  const isShowInput: Ref<boolean> = ref(false);
+  const editorInputEle = ref();
+  const editorInputEleContainer = ref();
+  const tempUserName = ref(props.userInfo.nameCard || props.userInfo.userName);
+  const dialogData: Ref<{ title: string; content: string; confirmText: string; actionType: ActionType, showInput: boolean }> = ref({
     title: '',
     content: '',
     confirmText: '',
     actionType: '' as ActionType,
+    showInput: false,
   });
-  const kickOffDialogContent = computed(() => t('whether to kick sb off the room', { name: props.userInfo.userName || props.userInfo.userId }));
-  const transferOwnerTitle = computed(() => t('Transfer the roomOwner to sb', { name: props.userInfo.userName || props.userInfo.userId }));
+  const kickOffDialogContent = computed(() => t('whether to kick sb off the room', { name: roomService.getDisplayName(props.userInfo) }));
+  const transferOwnerTitle = computed(() => t('Transfer the roomOwner to sb', { name: roomService.getDisplayName(props.userInfo) }));
   const {
     isFreeSpeakMode,
     isSpeakAfterTakingSeatMode,
@@ -50,8 +60,6 @@ export default function useMemberControl(props?: any) {
   } = storeToRefs(roomStore);
   /**
    * Functions related to the Raise Your Hand function
-   *
-   * 举手发言功能相关函数
   **/
   const {
     agreeUserOnStage,
@@ -61,11 +69,16 @@ export default function useMemberControl(props?: any) {
     kickUserOffStage,
   } = useMasterApplyControl();
 
+  const { isCanOperateMySelf } = useMemberItemHooks(props.userInfo);
+
   const isMe = computed(() => basicStore.userId === props.userInfo.userId);
   const isTargetUserAnchor = computed(() => props.userInfo.onSeat === true);
   const isTargetUserAudience = computed(() => props.userInfo.onSeat !== true);
 
   const controlList = computed(() => {
+    if (isCanOperateMySelf.value) {
+      return [changeUserNameCard.value];
+    }
     const agreeOrDenyStageList = props.userInfo.isUserApplyingToAnchor ? [agreeOnStage.value, denyOnStage.value] : [];
     const inviteStageList = isTargetUserAudience.value && !props.userInfo.isUserApplyingToAnchor
       ? [inviteOnStage.value] : [];
@@ -77,20 +90,22 @@ export default function useMemberControl(props?: any) {
         [TUIRole.kRoomOwner]: [
           audioControl.value, videoControl.value, chatControl.value,
           setOrRevokeAdmin.value, transferOwner.value, kickUser.value,
+          changeUserNameCard.value,
         ],
         [TUIRole.kAdministrator]: [
           audioControl.value, videoControl.value, chatControl.value,
+          changeUserNameCard.value,
         ],
       },
       speakAfterTakeSeat: {
         [TUIRole.kRoomOwner]: [
           ...inviteStageList, ...onStageControlList, ...agreeOrDenyStageList,
           setOrRevokeAdmin.value, transferOwner.value, chatControl.value,
-          kickUser.value,
+          kickUser.value, changeUserNameCard.value,
         ],
         [TUIRole.kAdministrator]: [
           ...inviteStageList, ...onStageControlList, ...agreeOrDenyStageList,
-          chatControl.value,
+          chatControl.value, changeUserNameCard.value,
         ],
       },
     };
@@ -119,7 +134,7 @@ export default function useMemberControl(props?: any) {
   const chatControl = computed(() => ({
     key: 'chatControl',
     icon: ChatForbiddenIcon,
-    title: props.userInfo.isChatMutedByMasterOrAdmin ? t('Enable chat') : t('Disable chat'),
+    title: props.userInfo.isMessageDisabled ? t('Enable chat') : t('Disable chat'),
     func: disableUserChat,
   }));
 
@@ -160,10 +175,15 @@ export default function useMemberControl(props?: any) {
 
   const denyOnStage = computed(() => ({ key: 'denyOnStage', icon: DenyOnStageIcon, title: t('Refuse stage'), func: denyUserOnStage }));
   const makeOffStage = computed(() => ({ key: 'makeOffStage', icon: OffStageIcon, title: t('Step down'), func: kickUserOffStage }));
+
+  const changeUserNameCard = computed(() => ({
+    key: 'changeUserNameCard',
+    icon: EditNameCardIcon,
+    title: t('change name'),
+    func: () => handleOpenDialog('changeUserNameCard'),
+  }));
   /**
    * Invitation to the stage/uninvitation to the stage
-   *
-   * 邀请上台/取消邀请上台
   **/
   async function toggleInviteUserOnStage(userInfo: UserInfo) {
     const { isInvitingUserToAnchor } = userInfo;
@@ -184,8 +204,6 @@ export default function useMemberControl(props?: any) {
 
   /**
    * Banning/Unbanning
-   *
-   * 禁麦/邀请打开麦克风
   **/
   async function muteUserAudio(userInfo: UserInfo) {
     if (userInfo.hasAudioStream) {
@@ -197,7 +215,7 @@ export default function useMemberControl(props?: any) {
       if (userInfo.isRequestingUserOpenMic) {
         TUIMessage({
           type: 'info',
-          message: `${t('An invitation to open the microphone has been sent to sb.', { name: userInfo.userName || userInfo.userId })}`,
+          message: `${t('An invitation to open the microphone has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`,
           duration: MESSAGE_DURATION.NORMAL,
         });
         return;
@@ -224,7 +242,7 @@ export default function useMemberControl(props?: any) {
       });
       TUIMessage({
         type: 'info',
-        message: `${t('An invitation to open the microphone has been sent to sb.', { name: userInfo.userName || userInfo.userId })}`,
+        message: `${t('An invitation to open the microphone has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`,
         duration: MESSAGE_DURATION.NORMAL,
       });
       if (request && request.requestId) {
@@ -235,8 +253,6 @@ export default function useMemberControl(props?: any) {
 
   /**
    * Banned painting/unbanned painting
-   *
-   * 禁画/取消禁画
   **/
   async function muteUserVideo(userInfo: UserInfo) {
     if (userInfo.hasVideoStream) {
@@ -248,7 +264,7 @@ export default function useMemberControl(props?: any) {
       if (userInfo.isRequestingUserOpenCamera) {
         TUIMessage({
           type: 'info',
-          message: `${t('An invitation to open the camera has been sent to sb.', { name: userInfo.userName || userInfo.userId })}`,
+          message: `${t('An invitation to open the camera has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`,
           duration: MESSAGE_DURATION.NORMAL,
         });
         return;
@@ -275,7 +291,7 @@ export default function useMemberControl(props?: any) {
       });
       TUIMessage({
         type: 'info',
-        message: `${t('An invitation to open the camera has been sent to sb.', { name: userInfo.userName || userInfo.userId })}`,
+        message: `${t('An invitation to open the camera has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`,
         duration: MESSAGE_DURATION.NORMAL,
       });
       if (request && request.requestId) {
@@ -285,36 +301,44 @@ export default function useMemberControl(props?: any) {
   }
   /**
    * Allow text chat / Cancel text chat
-   *
-   * 允许文字聊天/取消文字聊天
   **/
-  function disableUserChat(userInfo: UserInfo) {
-    const currentState = userInfo.isChatMutedByMasterOrAdmin;
-    roomStore.setMuteUserChat(userInfo.userId, !currentState);
-    roomEngine.instance?.disableSendingMessageByAdmin({
-      userId: userInfo.userId,
-      isDisable: !currentState,
-    });
+  async function disableUserChat(userInfo: UserInfo) {
+    const { isMessageDisabled } = userInfo;
+    try {
+      await roomEngine.instance?.disableSendingMessageByAdmin({
+        userId: userInfo.userId,
+        isDisable: !isMessageDisabled,
+      });
+      roomStore.setMuteUserChat(userInfo.userId, !isMessageDisabled);
+    } catch (error) {
+      TUIMessage({
+        type: 'error',
+        message: t('Failed to disable chat'),
+        duration: MESSAGE_DURATION.NORMAL,
+      });
+    }
   }
 
   /**
    * Kick the user out of the room
-   *
-   * 将用户踢出房间
   **/
   async function kickOffUser(userInfo: UserInfo) {
     await roomEngine.instance?.kickRemoteUserOutOfRoom({
       userId: userInfo.userId,
     });
+    roomStore.removeRemoteUser(userInfo.userId);
   }
 
   /**
-   * 转移房主给用户
+   * Transfer host to user
    */
   async function handleTransferOwner(userInfo: UserInfo) {
     const roomInfo = await roomEngine.instance?.fetchRoomInfo();
     if (roomInfo?.roomOwner === roomStore.localUser.userId) {
       try {
+        if (roomStore.localUser.hasScreenStream) {
+          eventBus.emit('ScreenShare:stopScreenShare');
+        }
         await roomEngine.instance?.changeUserRole({
           userId: userInfo.userId,
           userRole: TUIRole.kRoomOwner,
@@ -322,7 +346,7 @@ export default function useMemberControl(props?: any) {
         roomStore.setMasterUserId(userInfo.userId);
         TUIMessage({
           type: 'success',
-          message: t('The room owner has been transferred to sb', { name: userInfo.userName || userInfo.userId }),
+          message: t('The room owner has been transferred to sb', { name: roomService.getDisplayName(userInfo) }),
           duration: MESSAGE_DURATION.NORMAL,
         });
       } catch (error) {
@@ -336,7 +360,7 @@ export default function useMemberControl(props?: any) {
   }
 
   /**
-   * 设置/撤销管理员权限
+   * Set/Remove administrator permissions
    */
   async function handleSetOrRevokeAdmin(userInfo: UserInfo) {
     const newRole = userInfo.userRole === TUIRole.kGeneralUser ? TUIRole.kAdministrator : TUIRole.kGeneralUser;
@@ -350,30 +374,89 @@ export default function useMemberControl(props?: any) {
       : `${t('The administrator status of sb has been withdrawn', { name: updatedUserName })}`;
     TUIMessage({ type: 'success', message: tipMessage });
     roomStore.setRemoteUserRole(userInfo.userId, newRole);
+    if (newRole === TUIRole.kGeneralUser && userInfo.hasScreenStream) {
+      await roomEngine.instance?.closeRemoteDeviceByAdmin({
+        userId: userInfo.userId,
+        device: TUIMediaDevice.kScreen,
+      });
+    }
   }
 
   function handleOpenDialog(action: string) {
-    isDialogVisible.value = true;
     switch (action) {
       case 'kickUser':
+        isDialogVisible.value = true;
         dialogData.value = {
           title: t('Note'),
           content: kickOffDialogContent.value,
           confirmText: t('Confirm'),
           actionType: action,
+          showInput: false,
         };
         break;
       case 'transferOwner':
+        isDialogVisible.value = true;
         dialogData.value = {
           title: transferOwnerTitle.value,
           content: t('After transfer the room owner, you will become a general user'),
           confirmText: t('Confirm transfer'),
           actionType: action,
+          showInput: false,
+        };
+        break;
+      case 'changeUserNameCard':
+        if (isMobile) {
+          isShowInput.value = true;
+          document?.body?.appendChild(editorInputEleContainer.value);
+        } else {
+          isDialogVisible.value = true;
+        }
+        dialogData.value = {
+          title: t('change name'),
+          content: '',
+          confirmText: t('Confirm'),
+          actionType: action,
+          showInput: true,
         };
         break;
     }
   }
 
+
+  const nameCardCheck = () => {
+    const result  = calculateByteLength(tempUserName.value) <= 32;
+    !result && TUIMessage({
+      type: 'warning',
+      message: t('The user name cannot exceed 32 characters'),
+      duration: MESSAGE_DURATION.NORMAL,
+    });
+    return result;
+  };
+
+  /**
+   * change UserNameCard
+   */
+  async function handleChangeUserNameCard(userInfo: UserInfo) {
+    if (!nameCardCheck()) return;
+    try {
+      await roomEngine.instance?.changeUserNameCard({
+        userId: userInfo.userId,
+        nameCard: tempUserName.value,
+      });
+      TUIMessage({
+        type: 'success',
+        message: t('Name changed successfully'),
+        duration: MESSAGE_DURATION.NORMAL,
+      });
+    } catch (error) {
+      TUIMessage({
+        type: 'error',
+        message: t('change name failed, please try again.'),
+        duration: MESSAGE_DURATION.NORMAL,
+      });
+    }
+  }
+
   function handleAction(userInfo: UserInfo) {
     switch (dialogData.value.actionType) {
       case 'kickUser':
@@ -382,13 +465,18 @@ export default function useMemberControl(props?: any) {
       case 'transferOwner':
         handleTransferOwner(userInfo);
         break;
+      case 'changeUserNameCard':
+        handleChangeUserNameCard(userInfo);
+        isShowInput.value = false;
     } isDialogVisible.value = false;
   }
 
   function handleCancelDialog() {
+    tempUserName.value = props.userInfo.nameCard || props.userInfo.userName;
     isDialogVisible.value = false;
   }
 
+
   return {
     props,
     isMe,
@@ -399,5 +487,9 @@ export default function useMemberControl(props?: any) {
     handleAction,
     isDialogVisible,
     dialogData,
+    tempUserName,
+    isShowInput,
+    editorInputEle,
+    editorInputEleContainer,
   };
 };

+ 12 - 5
MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberItem/useMemberItemHooks.ts

@@ -3,20 +3,26 @@ import { UserInfo, useRoomStore } from '../../../stores/room';
 import { storeToRefs } from 'pinia';
 import { TUIRole } from '@tencentcloud/tuiroom-engine-wx';
 
-const showUserId = ref(''); // 需要展示操作面板的用户id
+// The user ID that needs to display the operation panel
+const showUserId = ref('');
 
 export default function useMemberItem(userInfo: UserInfo) {
   const roomStore = useRoomStore();
   const { isMaster, isAdmin } = storeToRefs(roomStore);
-  // 是否有权限操作当前用户
+  // Does the current user have permission to operate
   const isCanOperateCurrentMember = computed(() => {
     const isTargetUserRoomOwner = userInfo.userRole === TUIRole.kRoomOwner;
     const isTargetUserGeneral = userInfo.userRole === TUIRole.kGeneralUser;
-    return (isMaster.value && !isTargetUserRoomOwner) || (isAdmin.value && isTargetUserGeneral);
+    const isTargetUserMySelf = userInfo.userId === roomStore.localUser.userId;
+    return (isMaster.value && !isTargetUserRoomOwner) || (isAdmin.value && isTargetUserGeneral) || isTargetUserMySelf;
   });
-  const isMemberControlAccessible = computed(() => (
-    userInfo.userId === showUserId.value) && isCanOperateCurrentMember.value); // 只有房主或管理员才打开操作面板
 
+  const isCanOperateMySelf = computed(() => {
+    return userInfo.userId === roomStore.localUser.userId;
+  })
+  const isMemberControlAccessible = computed(() => (
+    userInfo.userId === showUserId.value) && (isCanOperateMySelf.value || isCanOperateCurrentMember.value));
+  
   function openMemberControl() {
     showUserId.value = userInfo.userId;
   }
@@ -29,6 +35,7 @@ export default function useMemberItem(userInfo: UserInfo) {
     isMemberControlAccessible,
     openMemberControl,
     closeMemberControl,
+    isCanOperateMySelf,
   };
 }
 

+ 4 - 12
MiniProgram/src/roomkit/TUIRoom/components/ManageMember/MemberItemCommon/MemberInfo.vue

@@ -1,14 +1,9 @@
 <template>
-  <!--
-      *User base information
-      *
-      *用户基础信息
-    -->
+  <!-- User base information -->
   <div :class="[isMobile ? 'member-info-mobile' : 'member-info']">
-    <!-- 用户基础信息 -->
     <div :class="!showStateIcon && isTargetUserAdmin ? 'member-basic-info-admin' : 'member-basic-info'">
       <Avatar class="avatar-url" :img-src="userInfo.avatarUrl"></Avatar>
-      <div class="user-name">{{ userInfo.userName || userInfo.userId }}</div>
+      <div class="user-name">{{ roomService.getDisplayName(userInfo) }}</div>
       <div class="role-info">
         <svg-icon
           style="display: flex"
@@ -20,11 +15,7 @@
         <div :class="`user-extra-info ${isTargetUserAdmin ? 'user-extra-info-admin' : ''}`">{{ extraInfo }}</div>
       </div>
     </div>
-    <!--
-      *User audio and video status information
-      *
-      *用户音视频状态信息
-    -->
+    <!-- User audio and video status information -->
     <div v-if="showStateIcon" class="member-av-state">
       <svg-icon
         style="display: flex"
@@ -54,6 +45,7 @@ import { useI18n } from '../../../locales';
 import { isMobile } from '../../../utils/environment';
 import UserIcon from '../../../assets/icons/UserIcon.svg';
 import { TUIRole } from '@tencentcloud/tuiroom-engine-wx';
+import { roomService } from '../../../services';
 
 const { t } = useI18n();
 

+ 71 - 0
MiniProgram/src/roomkit/TUIRoom/components/ManageMember/index.vue

@@ -35,6 +35,25 @@
       >
         {{ videoManageInfo }}
       </div>
+      <div
+        class="manage-member-button"
+        @touchstart="toggleClickMoreBtn"
+      >
+        {{ t('More') }}
+        <div v-show="showMoreControl" class="moreControl-container">
+          <div class="moreControl-container-main">
+            <div
+              v-for="item in moreControlList"
+              :key="item.type"
+              class="user-operate-item"
+              @touchstart="item.func(item.type)"
+            >
+              <svg-icon style="display: flex" :icon="item.icon"></svg-icon>
+              <span class="operate-text">{{ item.title }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
     <Dialog
       v-model="showManageAllUserDialog"
@@ -88,6 +107,9 @@ const {
   handleToggleStaged,
   applyToAnchorUserContent,
   showApplyUserList,
+  showMoreControl,
+  moreControlList,
+  toggleClickMoreBtn
 } = useIndex();
 
 </script>
@@ -272,4 +294,53 @@ const {
   .cancel{
     color: var(--font-color-4);
   }
+  .moreControl-container{
+    position: fixed;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 100vw;
+    height: auto;
+    box-sizing: border-box;
+    background-color: var(--log-out-mobile);
+    .moreControl-container-main{
+      width: 100%;
+      background: var(--background-color-1);
+      border-radius: 15px 15px 0px 0px;
+      position: fixed;
+      bottom: 0;
+      display: flex;
+      flex-direction: column;
+      animation-duration: 200ms;
+      animation-name: popup;
+      padding-bottom: 4vh;
+      gap: 5px;
+      padding: 50px 25px;
+      @keyframes popup {
+        from {
+          transform-origin: bottom;
+          transform: scaleY(0);
+        }
+        to {
+          transform-origin: bottom;
+          transform: scaleY(1);
+        }
+      }
+    }
+    .user-operate-item {
+      cursor: pointer;
+      color: var(--popup-title-color-h5);
+      height: 20px;
+      display: flex;
+      align-items: center;
+      .operate-text {
+        font-family: PingFang SC;
+        margin-left: 8px;
+        font-size: 14px;
+        white-space: nowrap;
+        line-height: 22px;
+        font-weight: 400;
+      }
+    }
+  }
 </style>

+ 60 - 11
MiniProgram/src/roomkit/TUIRoom/components/ManageMember/useIndexHooks.ts

@@ -8,6 +8,8 @@ import { TUIMediaDevice } from '@tencentcloud/tuiroom-engine-wx';
 import TUIMessage from '../common/base/Message/index';
 import { MESSAGE_DURATION } from '../../constants/message';
 import { isMobile } from '../../utils/environment';
+import AllMembersShareScreenIcon from '../../assets/icons/AllMembersShareScreenIcon.svg';
+import HostShareScreenIcon from '../../assets/icons/HostShareScreenIcon.svg';
 
 export default function useIndex() {
   const roomEngine = useGetRoomEngine();
@@ -21,11 +23,19 @@ export default function useIndex() {
     anchorUserList,
     applyToAnchorList,
     isOnStateTabActive,
+    generalUserScreenStreamList,
   } = storeToRefs(roomStore);
 
+  enum ManageControlType {
+    AUDIO = 'audio',
+    VIDEO = 'video',
+    SCREEN = 'screen',
+  }
+
   const audienceUserList = computed(() => userList.value.filter(user => !anchorUserList.value.includes(user)));
 
   const searchText = ref('');
+  const showMoreControl = ref(false);
 
   function handleToggleStaged() {
     isOnStateTabActive.value = !isOnStateTabActive.value;
@@ -42,7 +52,7 @@ export default function useIndex() {
     if (!searchText.value) {
       return list;
     }
-    return list.filter((item: UserInfo) => item.userName?.includes(searchText.value) || item.userId.includes(searchText.value));
+    return list.filter((item: UserInfo) => item.nameCard?.includes(searchText.value) || item.userName?.includes(searchText.value) || item.userId.includes(searchText.value));
   });
   const alreadyStaged = computed(() => `${t('Already on stage')} (${(anchorUserList.value.length)})`);
   const notStaged = computed(() => `${t('Not on stage')} (${(audienceUserList.value.length)})`);
@@ -54,45 +64,62 @@ export default function useIndex() {
   const audioManageInfo = computed(() => (roomStore.isMicrophoneDisableForAllUser ? t('Lift all mute') : t('All mute')));
   const videoManageInfo = computed(() => (roomStore.isCameraDisableForAllUser ? t('Lift stop all video') : t('All stop video')));
 
+  const moreControlList = computed(() => ([
+    {
+      title: roomStore.isScreenShareDisableForAllUser ?  t('All members can share screen') :  t('Screen sharing for host/admin only'),
+      icon: roomStore.isScreenShareDisableForAllUser ? AllMembersShareScreenIcon : HostShareScreenIcon,
+      func: toggleManageAllMember,
+      type: ManageControlType.SCREEN,
+    },
+  ]));
+
   const showManageAllUserDialog: Ref<boolean> = ref(false);
   const dialogContent: Ref<string> = ref('');
   const dialogTitle: Ref<string> = ref('');
   const dialogActionInfo: Ref<string> = ref('');
   let stateForAllAudio: boolean = false;
   let stateForAllVideo: boolean = false;
+  let stateForScreenShare: boolean = false;
 
-  enum ManageControlType {
-    AUDIO = 'audio',
-    VIDEO = 'video',
-  }
   const currentControlType: Ref<ManageControlType> = ref(ManageControlType.AUDIO);
 
   async function toggleManageAllMember(type: ManageControlType) {
-    showManageAllUserDialog.value = true;
     currentControlType.value = type;
     switch (type) {
       case ManageControlType.AUDIO:
+        showManageAllUserDialog.value = true;
         dialogTitle.value = roomStore.isMicrophoneDisableForAllUser
           ? t('Enable all audios') : t('All current and new members will be muted');
         dialogContent.value = roomStore.isMicrophoneDisableForAllUser
           ? t('After unlocking, users can freely turn on the microphone')
           : t('Members will not be able to open the microphone');
         stateForAllAudio =  !roomStore.isMicrophoneDisableForAllUser;
-        // 小程序更新视图
+        // Mini program update view
         await nextTick();
         dialogActionInfo.value = audioManageInfo.value;
         break;
       case ManageControlType.VIDEO:
+        showManageAllUserDialog.value = true;
         dialogTitle.value = roomStore.isCameraDisableForAllUser
           ? t('Enable all videos') : t('All and new members will be banned from the camera');
         dialogContent.value = roomStore.isCameraDisableForAllUser
           ? t('After unlocking, users can freely turn on the camera')
           : t('Members will not be able to open the camera');
         stateForAllVideo = !roomStore.isCameraDisableForAllUser;
-        // 小程序更新视图
+        // Mini program update view
         await nextTick();
         dialogActionInfo.value = videoManageInfo.value;
         break;
+      case ManageControlType.SCREEN:
+        stateForScreenShare = !roomStore.isScreenShareDisableForAllUser;
+        if (generalUserScreenStreamList.value.length === 0) {
+          toggleAllScreenShare();
+          break;
+        }
+        showManageAllUserDialog.value = true;
+        dialogTitle.value = t('Is it turned on that only the host/admin can share the screen?');
+        dialogContent.value = t("Other member is sharing the screen is now, the member's sharing will be terminated after you turning on");
+        break;
       default:
         break;
     }
@@ -106,11 +133,26 @@ export default function useIndex() {
       case ManageControlType.VIDEO:
         toggleAllVideo();
         break;
+      case ManageControlType.SCREEN:
+        await roomEngine.instance?.closeRemoteDeviceByAdmin({
+          userId: generalUserScreenStreamList.value[0].userId,
+          device: TUIMediaDevice.kScreen,
+        });
+        toggleAllScreenShare();
+        break;
       default:
         break;
     }
     showManageAllUserDialog.value = false;
   }
+  async function toggleAllScreenShare() {
+    await roomEngine.instance?.disableDeviceForAllUserByAdmin({
+      isDisable: stateForScreenShare,
+      device: TUIMediaDevice.kScreen,
+    });
+    roomStore.setDisableScreenShareForAllUserByAdmin(stateForScreenShare);
+    showMoreControl.value = false;
+  }
   function showApplyUserList() {
     if (isMobile) {
       basicStore.setSidebarOpenStatus(true);
@@ -134,7 +176,7 @@ export default function useIndex() {
       isDisable: stateForAllAudio,
       device: TUIMediaDevice.kMicrophone,
     });
-    roomStore.setMicrophoneDisableState(stateForAllAudio);
+    roomStore.setDisableMicrophoneForAllUserByAdmin(stateForAllAudio);
   }
 
   async function toggleAllVideo() {
@@ -151,18 +193,22 @@ export default function useIndex() {
       isDisable: stateForAllVideo,
       device: TUIMediaDevice.kCamera,
     });
-    roomStore.setCameraDisableState(stateForAllVideo);
+    roomStore.setDisableCameraForAllUserByAdmin(stateForAllVideo);
   }
 
   const applyToAnchorUserContent = computed(() => {
     const lastIndex = applyToAnchorList.value.length - 1;
-    const userName = applyToAnchorList.value[lastIndex]?.userName || applyToAnchorList.value[lastIndex]?.userId;
+    const userName = applyToAnchorList.value[lastIndex]?.nameCard || applyToAnchorList.value[lastIndex]?.userName || applyToAnchorList.value[lastIndex]?.userId;
     if (applyToAnchorList.value.length === 1) {
       return `${userName} ${t('Applying for the stage')}`;
     }
     return `${userName} ${t('and so on number people applying to stage', { number: applyToAnchorList.value.length })}`;
   });
 
+  function toggleClickMoreBtn() {
+    showMoreControl.value = !showMoreControl.value;
+  }
+
   return {
     showApplyUserList,
     searchText,
@@ -183,5 +229,8 @@ export default function useIndex() {
     isOnStateTabActive,
     handleToggleStaged,
     applyToAnchorUserContent,
+    toggleClickMoreBtn,
+    showMoreControl,
+    moreControlList,
   };
 }

+ 8 - 27
MiniProgram/src/roomkit/TUIRoom/components/RoomContent/StreamContainer/index.vue

@@ -45,7 +45,7 @@
         </div>
       </div>
     </div>
-    <!--左右滑动控制栏 -->
+    <!-- Slide the control bar left or right -->
     <div v-if="totalPageNumber > 1" class="swipe">
       <div
         v-for="(item, index) in totalPageNumber"
@@ -149,8 +149,6 @@ const isFirstPageInSixPointLayout: Ref<Boolean> = computed(() => {
 
 /**
  * ----- The following handles the nine-pane page flip logic -----
- *
- * ----- 以下处理六宫格翻页逻辑 -----
 **/
 const showStreamList: ComputedRef<StreamInfo[]> = computed(() => {
   if (layout.value === LAYOUT.SIX_EQUAL_POINTS) {
@@ -174,8 +172,6 @@ const showStreamList: ComputedRef<StreamInfo[]> = computed(() => {
 
 /**
  * Show left and right page flip icons
- *
- * 显示左右翻页图标
 **/
 const totalPageNumber = computed(() => {
   const videoStreamNumber = onlyVideoStreamList.value.length;
@@ -195,8 +191,6 @@ function isActiveDot(index: number) {
 
 /**
  * Swipe left to turn the page
- *
- * 向左滑动翻页
 **/
 async function handleTurnPageLeft() {
   if (currentPageIndex.value === 0) {
@@ -210,8 +204,6 @@ async function handleTurnPageLeft() {
 
 /**
  * Swipe right to turn the page
- *
- * 向右滑动翻页
 **/
 async function handleTurnPageRight() {
   if (currentPageIndex.value === totalPageNumber.value - 1) {
@@ -225,8 +217,6 @@ async function handleTurnPageRight() {
 
 /**
  * ----- The following processing stream layout ---------
- *
- * ----- 以下处理流布局 ---------
 **/
 const enlargedContainerRef = ref();
 const streamListRef = ref();
@@ -258,22 +248,16 @@ function handleTouchEnd(event:any) {
     return;
   }
   if (moveDirectionX < 0) {
-    // 右滑
     handleTurnPageRight();
   }
   if (moveDirectionX > 0) {
-    // 左滑
     handleTurnPageLeft();
   }
 }
 
 /**
  * --- The following processing stream events ----
- *
- * --- 以下处理流事件 ----
 **/
-
-
 const onUserVideoStateChanged = async (eventInfo: {
   userId: string,
   streamType: TUIVideoStreamType,
@@ -281,12 +265,12 @@ const onUserVideoStateChanged = async (eventInfo: {
   reason: TUIChangeReason,
 }) => {
   const { userId, streamType, hasVideo, reason } = eventInfo;
-  // 更新 roomStore 流状态数据
+  // Update roomStore flow state data
   roomStore.updateUserVideoState(userId, streamType, hasVideo);
 
-  // 处理状态变更
+  // Handle status changes
   if (userId === basicStore.userId && !hasVideo && reason === TUIChangeReason.kChangedByAdmin) {
-    // 主持人关闭摄像头
+    // The host turns off the camera
     if (streamType === TUIVideoStreamType.kCameraStream) {
       TUIMessage({
         type: 'warning',
@@ -296,10 +280,9 @@ const onUserVideoStateChanged = async (eventInfo: {
       // When the moderator opens the whole staff forbidden to draw,
       // open and then close the single person's camera alone, at this time
       // the corresponding user's camera status for inoperable
-      // 主持人开启全员禁画时,单独打开再关闭单人的摄像头,此时对应用户的摄像头状态为无法操作
       roomStore.setCanControlSelfVideo(!roomStore.isCameraDisableForAllUser);
     }
-    // 主持人关闭屏幕分享
+    // Host turns off screen sharing
     if (streamType === TUIVideoStreamType.kScreenStream) {
       TUIMessage({
         type: 'warning',
@@ -309,7 +292,7 @@ const onUserVideoStateChanged = async (eventInfo: {
     }
   }
 
-  // 当远端屏幕分享变化的时候,处理流布局
+  // Handle flow layout when screen sharing flow changes
   if (userId !== basicStore.userId && streamType === TUIVideoStreamType.kScreenStream) {
     if (hasVideo) {
       const largeStream = roomStore.remoteStreamObj[`${userId}_${streamType}`] as StreamInfo;
@@ -322,8 +305,6 @@ const onUserVideoStateChanged = async (eventInfo: {
       if (userId === enlargeStream.value?.userId) {
         /**
          * Reset the stream playback layout when the remote screen sharing stream is stopped
-         *
-         * 远端屏幕分享流停止的时候,重新设置流播放布局
         **/
         logger.debug(`${logPrefix} onUserVideoStateChanged: stop`, userId, streamType);
         roomEngine.instance?.stopPlayRemoteVideo({
@@ -344,7 +325,7 @@ const onUserVideoStateChanged = async (eventInfo: {
   }
 };
 
-// 计算音量最大的 userId
+// Calculate the userId with the loudest volume
 function handleLargestVoice(userVolumeList: Array<TRTCVolumeInfo>) {
   if (currentSpeakerUserId.value) {
     const lastSpeakerUserVolumeInfo = userVolumeList.find((item: TRTCVolumeInfo) => (
@@ -372,7 +353,7 @@ function handleLargestVoice(userVolumeList: Array<TRTCVolumeInfo>) {
 
 const handleLargestVoiceThrottle = throttle(handleLargestVoice, 1000);
 
-// 音量变化
+// volume change
 const onUserVoiceVolumeChanged = (eventInfo: {
   userVolumeList: any[],
 }) => {

+ 91 - 43
MiniProgram/src/roomkit/TUIRoom/components/RoomContent/StreamContainer/useStreamContainerHooks.ts

@@ -1,7 +1,8 @@
-import { watch } from 'vue';
+import { watch, ref } from 'vue';
+import type { Ref } from 'vue';
 import useGetRoomEngine from '../../../hooks/useRoomEngine';
-import {  useRoomStore } from '../../../stores/room';
-import  { TUIChangeReason, TUIMediaDeviceType,  TUIUserInfo } from '@tencentcloud/tuiroom-engine-wx';
+import {  useRoomStore, StreamInfo } from '../../../stores/room';
+import  { TUIChangeReason, TUIMediaDeviceType, TUIUserInfo, TRTCVolumeInfo } from '@tencentcloud/tuiroom-engine-wx';
 import TUIMessage from '../../common/base/Message/index';
 import { useI18n } from '../../../locales';
 import { useBasicStore } from '../../../stores/basic';
@@ -11,38 +12,37 @@ import { isMobile, isWeChat } from '../../../utils/environment';
 import logger from '../../../utils/common/logger';
 import { SMALL_VIDEO_ENC_PARAM } from '../../../constants/room';
 import useDeviceManager from '../../../hooks/useDeviceManager';
+import { throttle } from '../../../utils/utils';
+import { LAYOUT } from '../../../constants/render';
 
 const logPrefix = '[StreamContainer]';
 
 export default function useStreamContainer() {
-  /**
-   * --- The following processing stream events ----
-   *
-   * --- 以下处理流事件 ----
-   **/
+  /** --- The following processing stream events ---- **/
   const roomEngine = useGetRoomEngine();
   const basicStore = useBasicStore();
   const roomStore = useRoomStore();
+  const { layout } = storeToRefs(basicStore);
+  const { localStream } = storeToRefs(roomStore);
   const { t } = useI18n();
   const { deviceManager } = useDeviceManager();
 
-  // 远端用户进入
+  const currentRemoteSpeakerUserId: Ref<string> = ref('');
+  const currentSpeakerUserId: Ref<string> = ref('');
+
   const onRemoteUserEnterRoom = (eventInfo: { userInfo: TUIUserInfo }) => {
     roomStore.addRemoteUser(eventInfo.userInfo);
   };
 
-  // 远端用户离开
   const onRemoteUserLeaveRoom = (eventInfo: { userInfo: TUIUserInfo }) => {
     roomStore.removeRemoteUser(eventInfo.userInfo.userId);
   };
 
-  // 麦位变化
   const onSeatListChanged = (eventInfo: { seatList: any[], seatedList: any[], leftList: any[] }) => {
     const { seatedList, leftList } = eventInfo;
     roomStore.updateOnSeatList(seatedList, leftList);
   };
 
-  // 用户音频状态发生改变
   const onUserAudioStateChanged = (eventInfo: {
     userId: string,
     hasAudio: boolean,
@@ -50,9 +50,7 @@ export default function useStreamContainer() {
   }) => {
     const { userId, hasAudio, reason } = eventInfo;
     roomStore.updateUserAudioState(userId, hasAudio);
-    // 处理状态变更
     if (userId === basicStore.userId && !hasAudio && reason === TUIChangeReason.kChangedByAdmin) {
-      // 主持人关闭麦克风
       TUIMessage({
         type: 'warning',
         message: t('Your microphone has been turned off'),
@@ -61,12 +59,80 @@ export default function useStreamContainer() {
       /**
        * When the host turns on a full ban, the microphone of a single person is turned on
        * and off separately, and the microphone status of the corresponding user is inoperable at this time
-       *
-       * 主持人开启全员禁言时,单独打开再关闭单人的麦克风,此时对应用户的麦克风状态为无法操作
        **/
       roomStore.setCanControlSelfAudio(!roomStore.isMicrophoneDisableForAllUser);
     }
   };
+
+  // Calculate the userId of the loudest speaker in the room
+  // Calculate the userId of the remote user who speaks the loudest in the current room.
+  function handleUserVoiceVolume(userVolumeList: Array<TRTCVolumeInfo>) {
+    const localUserVolume = {
+      userId: basicStore.userId,
+      volume: 0,
+    };
+    const largestRemoteUserVolume = {
+      userId: '',
+      volume: 0,
+    };
+    userVolumeList.forEach((item: TRTCVolumeInfo) => {
+      if (item.userId === basicStore.userId && localStream.value.hasAudioStream) {
+        localUserVolume.volume = item.volume;
+      } else if (item.userId !== basicStore.userId && roomStore.remoteUserObj[item.userId]?.hasAudioStream) {
+        const { userId, volume } = item;
+        if (volume > largestRemoteUserVolume.volume) {
+          largestRemoteUserVolume.userId = userId;
+          largestRemoteUserVolume.volume = volume;
+        }
+      }
+    });
+
+    const largestUserVolume = localUserVolume.volume > largestRemoteUserVolume.volume
+      ? localUserVolume : largestRemoteUserVolume;
+
+    if (currentRemoteSpeakerUserId.value) {
+      const lastRemoteSpeakerUserVolumeInfo = userVolumeList.find((item: TRTCVolumeInfo) => (
+        item.userId === currentRemoteSpeakerUserId.value
+      ));
+      if (!lastRemoteSpeakerUserVolumeInfo || lastRemoteSpeakerUserVolumeInfo.volume === 0) {
+        if (largestRemoteUserVolume.volume > 0) {
+          currentRemoteSpeakerUserId.value = largestRemoteUserVolume.userId;
+        }
+      }
+    } else {
+      if (largestRemoteUserVolume.volume > 0) {
+        currentRemoteSpeakerUserId.value = largestRemoteUserVolume.userId;
+      }
+    }
+
+    if (currentSpeakerUserId.value) {
+      const lastSpeakerUserVolumeInfo: TRTCVolumeInfo | undefined = userVolumeList.find((item: TRTCVolumeInfo) => (
+        item.userId === currentSpeakerUserId.value
+      ));
+      if (!lastSpeakerUserVolumeInfo || lastSpeakerUserVolumeInfo.volume === 0) {
+        if (largestUserVolume.volume > 0) {
+          currentSpeakerUserId.value = largestUserVolume.userId;
+        }
+      }
+    } else {
+      if (largestUserVolume.volume > 0) {
+        currentSpeakerUserId.value = largestUserVolume.userId;
+      }
+    }
+  }
+
+  const handleUserVoiceVolumeThrottle = throttle(handleUserVoiceVolume, 1000);
+
+  // volume change
+  const onUserVoiceVolumeChanged = (eventInfo: {
+    userVolumeList: any[],
+  }) => {
+    const { userVolumeList } = eventInfo;
+    if (layout.value === LAYOUT.LARGE_SMALL_WINDOW) {
+      handleUserVoiceVolumeThrottle(userVolumeList);
+    }
+  };
+
   const {
     isDefaultOpenCamera,
     isDefaultOpenMicrophone,
@@ -77,39 +143,18 @@ export default function useStreamContainer() {
       /**
        * Turn on the local camera
        *
-       * 开启本地摄像头
        **/
-
       if (isWeChat) {
-        await roomEngine.instance?.setLocalVideoView({
-          view: `${roomStore.localStream.userId}_${roomStore.localStream.streamType}`,
-        });
-        // @ts-ignore
         await roomEngine.instance?.openLocalCamera({ isFrontCamera: basicStore.isFrontCamera });
       } else if (isMobile) {
-        const previewDom = document?.getElementById(`${roomStore.localStream.userId}_${roomStore.localStream.streamType}`);
-        if (!previewDom) {
-          logger.error(`${logPrefix}watch isDefaultOpenCamera:`, isDefaultOpenCamera, previewDom);
-          return;
-        }
-        await roomEngine.instance?.setLocalVideoView({
-          view: `${roomStore.localStream.userId}_${roomStore.localStream.streamType}`,
-        });
         const trtcCloud = roomEngine.instance?.getTRTCCloud();
         trtcCloud?.enableSmallVideoStream(false, SMALL_VIDEO_ENC_PARAM);
-        // @ts-ignore
         await roomEngine.instance?.openLocalCamera({ isFrontCamera: basicStore.isFrontCamera });
         return;
       } else {
-        const previewDom = document?.getElementById(`${roomStore.localStream.userId}_${roomStore.localStream.streamType}`);
-        if (!previewDom) {
-          logger.error(`${logPrefix}watch isDefaultOpenCamera:`, isDefaultOpenCamera, previewDom);
-          return;
-        }
         /**
          * Set device id
          *
-         * 设置设备id
         **/
         if (!roomStore.currentCameraId) {
           const cameraList = await deviceManager.instance?.getDevicesList({
@@ -126,15 +171,11 @@ export default function useStreamContainer() {
         /**
          * Turn on the local camera
          *
-         * 开启本地摄像头
         **/
-        await roomEngine.instance?.setLocalVideoView({
-          view: `${roomStore.localStream.userId}_${roomStore.localStream.streamType}`,
-        });
         await roomEngine.instance?.openLocalCamera();
       }
     }
-  }, {immediate: true});
+  }, { immediate: true });
 
   watch(isDefaultOpenMicrophone, async (val) => {
     if (val) {
@@ -158,12 +199,19 @@ export default function useStreamContainer() {
     } else {
       await roomEngine.instance?.muteLocalAudio();
     }
-  }, {immediate: true});
+  }, { immediate: true });
+
+  const isSameStream = (stream1: StreamInfo | null, stream2: StreamInfo | null) => `${stream1?.userId}_${stream1?.streamType}` === `${stream2?.userId}_${stream2?.streamType}`;
+
   return {
+    currentRemoteSpeakerUserId,
+    currentSpeakerUserId,
     onRemoteUserEnterRoom,
     onRemoteUserLeaveRoom,
     onSeatListChanged,
     onUserAudioStateChanged,
+    onUserVoiceVolumeChanged,
+    isSameStream,
     t,
   };
 }

+ 8 - 6
MiniProgram/src/roomkit/TUIRoom/components/RoomContent/StreamRegion/index.vue

@@ -104,9 +104,9 @@ const isScreenStream = computed(() => props.stream.streamType === TUIVideoStream
 
 const userInfo = computed(() => {
   if (isInnerScene) {
-    return `${props.stream.userName} | ${props.stream.userId}` || props.stream.userId;
+    return `${props.stream.nameCard || props.stream.userName} | ${props.stream.userId}`;
   }
-  return props.stream.userName || props.stream.userId;
+  return props.stream.nameCard || props.stream.userName || props.stream.userId;
 });
 
 onMounted(() => {
@@ -142,6 +142,12 @@ onMounted(() => {
             });
           }
         }
+      } else {
+        if (basicStore.userId === props.stream.userId && props.stream.streamType === TUIVideoStreamType.kCameraStream) {
+          await roomEngine.instance?.setLocalVideoView({
+            view: null,
+          });
+        }
       }
     },
     { immediate: true },
@@ -182,8 +188,6 @@ onMounted(() => {
 /**
  * enlargeUserId The switch requires that both the small window
  * corresponding to the previously played stream and the stream that needs to be newly played be replayed.
- *
- * enlargeUserId 切换的时候需要让之前播放流对应的小窗口和需要新播放的流都重新播放
 **/
 onMounted(() => {
   watch(
@@ -193,8 +197,6 @@ onMounted(() => {
         if (basicStore.userId === props.stream.userId) {
           /**
            * Replay local video streams only when they are open
-           *
-           * 只有当本地视频流是打开状态的时候,才重新播放本地流
           **/
           if (props.stream.hasVideoStream) {
             await roomEngine.instance?.setLocalVideoView({

+ 1 - 0
MiniProgram/src/roomkit/TUIRoom/components/RoomContent/index.vue

@@ -16,6 +16,7 @@ defineProps<{
 .content-container {
   width: 100%;
   height: 100%;
+  overflow: hidden;
 }
 
 </style>

+ 2 - 1
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ApplyControl/MasterApplyControl/index.vue

@@ -5,7 +5,7 @@
         <div class="user-info">
           <Avatar class="avatar-url" :img-src="item.avatarUrl"></Avatar>
           <div class="stage-info">
-            <span class="user-name" :title="item.userName || item.userId">{{ item.userName || item.userId }}</span>
+            <span class="user-name" :title="roomService.getDisplayName(item)">{{ roomService.getDisplayName(item) }}</span>
             <span class="apply-tip">{{ t('Apply for the stage') }}</span>
           </div>
         </div>
@@ -43,6 +43,7 @@ import Avatar from '../../../common/Avatar.vue';
 import ApplyStageLabelIcon from '../../../../assets/icons/ApplyStageLabelIcon.svg';
 import useMasterApplyControl from '../../../../hooks/useMasterApplyControl';
 import SvgIcon from '../../../common/base/SvgIcon.vue';
+import { roomService } from '../../../../services';
 
 const {
   t,

+ 6 - 20
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ApplyControl/MemberApplyControl.vue

@@ -122,10 +122,9 @@ const leaveSeatDialogInfo = computed(() => ({
 const currentDialogInfo = computed(() => (currentDialogType.value === 'inviteDialog'
   ? inviteDialogInfo
   : leaveSeatDialogInfo.value));
+
 /**
- * Send a request to be on the mike
- *
- * 发送上麦申请
+ * Send a request to be on the stage
 **/
 async function sendSeatApplication() {
   if (isAdmin.value) {
@@ -166,11 +165,8 @@ async function sendSeatApplication() {
 }
 
 /**
- * Cancellation of on-mike application
- *
- * 处理点击【创建房间】
+ * Cancellation of application stage
 **/
-// 取消上麦申请
 async function cancelSeatApplication() {
   TUIMessage({
     type: 'info',
@@ -186,9 +182,7 @@ async function cancelSeatApplication() {
 }
 
 /**
- * User Down Mack
- *
- * 用户下麦
+ * User Down stage
 **/
 function handleStepDownDialogVisible() {
   showDialog.value = !showDialog.value;
@@ -213,8 +207,6 @@ function hideApplyAttention() {
 
 /**
  * Handling host or administrator invitation to on-stage signalling
- *
- * 处理主持人或管理员邀请上台信令
 **/
 async function onRequestReceived(eventInfo: { request: TUIRequest }) {
   const { request: { userId, requestId, requestAction } } = eventInfo;
@@ -229,9 +221,7 @@ async function onRequestReceived(eventInfo: { request: TUIRequest }) {
 }
 
 /**
-   * The host canceled the invitation to the microphone
-   *
-   * 主持人取消邀请上麦
+   * The host canceled the invitation to the stage
   **/
 function onRequestCancelled(eventInfo: { requestId: string; userId: string }) {
   const { requestId } = eventInfo;
@@ -243,8 +233,6 @@ function onRequestCancelled(eventInfo: { requestId: string; userId: string }) {
 
 /**
  * User accepts/rejects the presenter's invitation
- *
- * 用户接受/拒绝主讲人的邀请
 **/
 async function handleInvite(agree: boolean) {
   try {
@@ -264,11 +252,9 @@ async function handleInvite(agree: boolean) {
 }
 
 /**
- * Kicked off the seat by the host
- * 被主持人踢下麦
+ * Kicked off the stage by the host
  */
 async function onKickedOffSeat() {
-  // 被主持人踢下麦
   TUIMessage({
     type: 'warning',
     message: t('You have been invited by the host to step down, please raise your hand if you need to speak'),

+ 6 - 13
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/AudioControl.vue

@@ -1,15 +1,10 @@
 <!--
-  * 名称: IconButton
+  * Name: IconButton
   * @param name String required
   * @param size String 'large'|'medium'|'small'
   * Usage:
   * Use <audio-control /> in the template
   *
-  * Name: IconButton
-  * @param name String required
-  * @param size String 'large'|'medium'|'small'
-  * 使用方式:
-  * 在 template 中使用 <audio-control />
 -->
 <template>
   <div>
@@ -107,7 +102,7 @@ async function toggleMuteAudio() {
   }
   if (localStream.value.hasAudioStream) {
     await roomEngine.instance?.muteLocalAudio();
-    // 如果是全员禁言状态下,用户主动关闭麦克风之后不能再自己打开
+    // If everyone is muted, the user will not be able to turn the microphone back on after voluntarily turning it off.
     if (roomStore.isMicrophoneDisableForAllUser) {
       roomStore.setCanControlSelfAudio(false);
     }
@@ -122,7 +117,7 @@ async function toggleMuteAudio() {
       });
       return;
     }
-    // 有麦克风列表且有权限
+    // There is a microphone list and permissions
     await roomEngine.instance?.unmuteLocalAudio();
     if (!basicStore.isOpenMic) {
       roomEngine.instance?.openLocalMicrophone();
@@ -133,8 +128,6 @@ async function toggleMuteAudio() {
 
 /**
  * Handling host or administrator turn on/off microphone signalling
- *
- * 处理主持人或管理员打开/关闭麦克风信令
 **/
 const showRequestOpenMicDialog: Ref<boolean> = ref(false);
 const requestOpenMicRequestId: Ref<string> = ref('');
@@ -147,7 +140,7 @@ async function onRequestReceived(eventInfo: { request: TUIRequest }) {
     showRequestOpenMicDialog.value = true;
   }
 }
-// 接受主持人邀请,打开麦克风
+// Accept the host invitation and turn on the microphone
 async function handleAccept() {
   roomStore.setCanControlSelfAudio(true);
   await roomEngine.instance?.responseRemoteRequest({
@@ -158,7 +151,7 @@ async function handleAccept() {
   showRequestOpenMicDialog.value = false;
 }
 
-// 保持静音
+// keep mute
 async function handleReject() {
   await roomEngine.instance?.responseRemoteRequest({
     requestId: requestOpenMicRequestId.value,
@@ -168,7 +161,7 @@ async function handleReject() {
   showRequestOpenMicDialog.value = false;
 }
 
-// 请求被取消
+// Request canceled
 async function onRequestCancelled(eventInfo: { requestId: string }) {
   const { requestId } = eventInfo;
   if (requestOpenMicRequestId.value === requestId) {

+ 3 - 2
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ChatControl.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="chat-control-container">
+  <div v-if="chatControlConfig.visible" class="chat-control-container">
     <tui-badge :hidden="chatStore.unReadCount === 0" :value="chatStore.unReadCount" :max="10">
       <icon-button
         :title="t('Chat')"
@@ -19,8 +19,9 @@ import { useChatStore } from '../../stores/chat';
 import { storeToRefs } from 'pinia';
 import { useI18n } from '../../locales';
 import TuiBadge from '../common/base/Badge.vue';
+import { roomService } from '../../services';
 const { t } = useI18n();
-
+const chatControlConfig = roomService.getComponentConfig('ChatControl');
 const basicStore = useBasicStore();
 const chatStore = useChatStore();
 const { sidebarName } = storeToRefs(basicStore);

+ 6 - 54
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/EndControl/index.vue

@@ -64,7 +64,7 @@
               >
                 <div class="member-basic-info">
                   <Avatar class="avatar-url" :img-src="user.avatarUrl"></Avatar>
-                  <div class="user-name">{{ user.userName || user.userId }}</div>
+                  <div class="user-name">{{ roomService.getDisplayName(user) }}</div>
                   <svg-icon style="display: flex" v-if="selectedUser === user.userId" :icon="CorrectIcon" class="correct"> </svg-icon>
                 </div>
               </div>
@@ -83,8 +83,7 @@
 </template>
 
 <script setup lang="ts">
-import { onUnmounted } from 'vue';
-import { TUIRoomEngine, TUIRole, TUIRoomEvents } from '@tencentcloud/tuiroom-engine-wx';
+import { TUIRole } from '@tencentcloud/tuiroom-engine-wx';
 import useEndControl from './useEndControlHooks';
 import logger from '../../../utils/common/logger';
 import popup from '../../common/base/PopUpH5.vue';
@@ -93,13 +92,12 @@ import CorrectIcon from '../../../assets/icons/CorrectIcon.svg';
 import SearchIcon from '../../../assets/icons/SearchIcon.svg';
 import Avatar from '../../common/Avatar.vue';
 import EndRoomIcon from '../../../assets/icons/EndRoomIcon.svg';
-import TUIMessageBox from '../../common/base/MessageBox/index';
+import { roomService } from '../../../services';
 
 const {
   t,
   isShowLeaveRoomDialog,
   roomStore,
-  basicStore,
   roomEngine,
   stopMeeting,
   cancel,
@@ -107,7 +105,6 @@ const {
   logPrefix,
   currentDialogType,
   visible,
-  closeMediaBeforeLeave,
   resetState,
   toggleMangeMemberSidebar,
   searchName,
@@ -121,7 +118,6 @@ const {
   isMasterWithRemoteUser,
 } = useEndControl();
 
-const emit = defineEmits(['on-exit-room', 'on-destroy-room']);
 function handleEndBtnClick() {
   stopMeeting();
 }
@@ -145,16 +141,12 @@ function handleEndLeaveClick() {
 
 /**
  * Active room dismissal
- *
- * 主动解散房间
  **/
 async function dismissRoom() {
   try {
     logger.log(`${logPrefix}dismissRoom: enter`);
-    closeMediaBeforeLeave();
-    await roomEngine.instance?.destroyRoom();
     resetState();
-    emit('on-destroy-room', { code: 0, message: '' });
+    await roomService.dismissRoom();
   } catch (error) {
     logger.error(`${logPrefix}dismissRoom error:`, error);
   }
@@ -162,17 +154,12 @@ async function dismissRoom() {
 
 /**
  * Leave the room voluntarily
- *
- * 主动离开房间
  **/
 async function leaveRoom() {
   // eslint-disable-line
   try {
-    closeMediaBeforeLeave();
-    const response = await roomEngine.instance?.exitRoom();
-    logger.log(`${logPrefix}leaveRoom:`, response);
     resetState();
-    emit('on-exit-room', { code: 0, message: '' });
+    await roomService.leaveRoom();
   } catch (error) {
     logger.error(`${logPrefix}leaveRoom error:`, error);
   }
@@ -186,48 +173,13 @@ async function transferAndLeave() {
     const userId = selectedUser.value;
     const changeUserRoleResponse = await roomEngine.instance?.changeUserRole({ userId, userRole: TUIRole.kRoomOwner });
     logger.log(`${logPrefix}transferAndLeave:`, changeUserRoleResponse);
-    closeMediaBeforeLeave();
-    const exitRoomResponse = await roomEngine.instance?.exitRoom();
-    logger.log(`${logPrefix}exitRoom:`, exitRoomResponse);
-    basicStore.setSidebarOpenStatus(false);
-    basicStore.setSidebarName('');
     resetState();
-    emit('on-exit-room', { code: 0, message: '' });
+    await roomService.leaveRoom();
   } catch (error) {
     logger.error(`${logPrefix}transferAndLeave error:`, error);
   }
 }
 
-/**
- * notification of room dismissal from the host
- *
- * 收到主持人解散房间通知
- **/
-const onRoomDismissed = async (eventInfo: { roomId: string }) => {
-  try {
-    const { roomId } = eventInfo;
-    logger.log(`${logPrefix}onRoomDismissed:`, roomId);
-    TUIMessageBox({
-      title: t('Note'),
-      message: t('The host closed the room.'),
-      confirmButtonText: t('Sure'),
-      callback: async () => {
-        resetState();
-        emit('on-destroy-room', { code: 0, message: '' });
-      },
-    });
-  } catch (error) {
-    logger.error(`${logPrefix}onRoomDestroyed error:`, error);
-  }
-};
-
-TUIRoomEngine.once('ready', () => {
-  roomEngine.instance?.on(TUIRoomEvents.onRoomDismissed, onRoomDismissed);
-});
-
-onUnmounted(() => {
-  roomEngine.instance?.off(TUIRoomEvents.onRoomDismissed, onRoomDismissed);
-});
 </script>
 <style lang="scss" scoped>
 .end-control-container {

+ 11 - 4
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/EndControl/useEndControlHooks.ts

@@ -8,6 +8,7 @@ import useGetRoomEngine from '../../../hooks/useRoomEngine';
 import { TUIRoomEngine, TUIRole, TUIRoomEvents } from '@tencentcloud/tuiroom-engine-wx';
 import logger from '../../../utils/common/logger';
 import TUIMessage from '../../common/base/Message/index';
+import { roomService } from '../../../services';
 
 export default function useEndControl() {
   const { t } = useI18n();
@@ -35,8 +36,7 @@ export default function useEndControl() {
   const selectedUser: Ref<string> = ref('');
   const showTransfer = ref(false);
   const searchName = ref('');
-  const filteredList = computed(() => remoteUserList.value.filter(searchUser => (
-    searchUser.userId.includes(searchName.value)) || (searchUser.userName?.includes(searchName.value))));
+  const filteredList = computed(() => remoteUserList.value.filter(searchUser => (searchUser.nameCard?.includes(searchName.value)) || (searchUser.userId.includes(searchName.value)) || (searchUser.userName?.includes(searchName.value))));
   const hasNoData = computed(() => filteredList.value.length === 0);
   const isMasterWithOneRemoteUser = computed(() => remoteUserList.value.length === 1);
   const isMasterWithRemoteUser = computed(() => remoteUserList.value.length > 0);
@@ -154,7 +154,7 @@ export default function useEndControl() {
             }
             handleUpdateSeatApplicationList();
           }
-          if (chatStore.isMessageDisableByAdmin) {
+          if (chatStore.isMessageDisabled) {
             roomEngine.instance?.disableSendingMessageByAdmin({
               userId,
               isDisable: false,
@@ -169,9 +169,16 @@ export default function useEndControl() {
   TUIRoomEngine.once('ready', () => {
     roomEngine.instance?.on(TUIRoomEvents.onUserRoleChanged, onUserRoleChanged);
   });
-
+  const handleMount = () => {
+    const { userRole } = roomService.roomStore.localUser;
+    if (userRole === TUIRole.kRoomOwner || userRole === TUIRole.kAdministrator) {
+      handleUpdateSeatApplicationList();
+    }
+  };
+  roomService.lifeCycleManager.on('mount', handleMount);
   onUnmounted(() => {
     roomEngine.instance?.off(TUIRoomEvents.onUserRoleChanged, onUserRoleChanged);
+    roomService.lifeCycleManager.off('mount', handleMount);
   });
   return {
     t,

+ 2 - 3
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/InviteControl.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="invite-control-container" v-if="inviteControlConfig.visible">
+  <div v-if="inviteControlConfig.visible" class="invite-control-container">
     <icon-button
       :is-active="sidebarName === 'invite'"
       :title="t('Invite')"
@@ -29,8 +29,7 @@ const { sidebarName } = storeToRefs(basicStore);
 const { t } = useI18n();
 const isShowInviteTab = ref(false);
 const inviteRef = ref();
-const [inviteControlConfig] = roomService.getComponentConfig(['InviteControl']);
-
+const inviteControlConfig = roomService.getComponentConfig('InviteControl');
 function toggleInviteSidebar() {
   if (isMobile) {
     isShowInviteTab.value = true;

+ 3 - 1
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/ManageMemberControl.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="manage-member-control-container">
+  <div v-if="manageMemberControlConfig.visible" class="manage-member-control-container">
     <icon-button
       :is-active="sidebarName === 'manage-member'"
       :title="memberTitle"
@@ -18,7 +18,9 @@ import { useBasicStore } from '../../stores/basic';
 import { useRoomStore } from '../../stores/room';
 import { useI18n } from '../../locales';
 import { computed } from 'vue';
+import { roomService } from '../../services';
 
+const manageMemberControlConfig = roomService.getComponentConfig('ManageMemberControl');
 const { t } = useI18n();
 
 const basicStore = useBasicStore();

+ 3 - 3
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/MoreControl/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="more-control-container">
+    <div v-if="moreControlConfig.visible" class="more-control-container">
       <icon-button
         @tap="showMore"
         :is-active="sidebarName === 'more'"
@@ -31,8 +31,9 @@ import { useRoomStore } from '../../../stores/room';
 import { ref, onMounted, onUnmounted } from 'vue';
 import ExtensionIcon from '../../../assets/icons/ExtensionIcon.svg';
 import bus from '../../../hooks/useMitt';
-import TUIRoomAegis from '../../../utils/aegis';
+import { roomService } from '../../../services';
 
+const moreControlConfig = roomService.getComponentConfig('MoreControl');
 const showMoreContent = ref(false);
 const moreContentRef = ref();
 
@@ -50,7 +51,6 @@ function handleCancelControl() {
   showMoreContent.value = false;
 }
 function handleControlClick(name: string) {
-  TUIRoomAegis.reportEvent({ name, ext1: name });
   bus.emit('experience-communication', name);
 }
 

+ 6 - 21
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/VideoControl.vue

@@ -1,15 +1,9 @@
 <!--
-  * 名称: IconButton
+  * Name: IconButton
   * @param name String required
   * @param size String 'large'|'medium'|'small'
   * Usage:
   * Use <audio-control /> in the template
-  *
-  * Name: IconButton
-  * @param name String required
-  * @param size String 'large'|'medium'|'small'
-  * 使用方式:
-  * 在 template 中使用 <audio-control />
 -->
 <template>
   <div>
@@ -104,7 +98,7 @@ async function toggleMuteVideo() {
 
   if (localStream.value.hasVideoStream) {
     await roomEngine.instance?.closeLocalCamera();
-    // 如果是全员禁画状态下,用户主动关闭摄像头之后不能再自己打开
+    // If the picture is banned for all members, the user cannot turn it on again after voluntarily turning off the camera.
     if (roomStore.isCameraDisableForAllUser) {
       roomStore.setCanControlSelfVideo(false);
     }
@@ -113,7 +107,7 @@ async function toggleMuteVideo() {
       type: TUIMediaDeviceType.kMediaDeviceTypeVideoCamera,
     });
     const hasCameraDevice = cameraList && cameraList.length > 0;
-    // 无摄像头列表
+    // No camera list
     if (!hasCameraDevice && !isWeChat) {
       TUIMessageBox({
         title: t('Note'),
@@ -122,10 +116,6 @@ async function toggleMuteVideo() {
       });
       return;
     }
-    // 有摄像头列表
-    roomEngine.instance?.setLocalVideoView({
-      view: `${roomStore.localStream.userId}_${roomStore.localStream.streamType}`,
-    });
     if (isMobile) {
       if (isH5) {
         const trtcCloud = roomEngine.instance?.getTRTCCloud();
@@ -142,8 +132,6 @@ async function toggleMuteVideo() {
 
 /**
  * Handling host or administrator turn on/off camera signalling
- *
- * 处理主持人或管理员打开/关闭摄像头信令
 **/
 const showRequestOpenCameraDialog: Ref<boolean> = ref(false);
 const requestOpenCameraRequestId: Ref<string> = ref('');
@@ -157,12 +145,9 @@ async function onRequestReceived(eventInfo: { request: TUIRequest }) {
   }
 }
 
-// 接受主持人邀请,打开摄像头
+// Accept the host invitation and turn on the camera
 async function handleAccept() {
   roomStore.setCanControlSelfVideo(true);
-  roomEngine.instance?.setLocalVideoView({
-    view: `${roomStore.localStream.userId}_${roomStore.localStream.streamType}`,
-  });
   await roomEngine.instance?.responseRemoteRequest({
     requestId: requestOpenCameraRequestId.value,
     agree: true,
@@ -171,7 +156,7 @@ async function handleAccept() {
   showRequestOpenCameraDialog.value = false;
 }
 
-// 保持静音
+// keep mute
 async function handleReject() {
   await roomEngine.instance?.responseRemoteRequest({
     requestId: requestOpenCameraRequestId.value,
@@ -181,7 +166,7 @@ async function handleReject() {
   showRequestOpenCameraDialog.value = false;
 }
 
-// 请求被取消
+// Request canceled
 async function onRequestCancelled(eventInfo: { requestId: string }) {
   const { requestId } = eventInfo;
   if (requestOpenCameraRequestId.value === requestId) {

+ 203 - 0
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/VirtualBackground.vue

@@ -0,0 +1,203 @@
+<template>
+  <div v-if="componentConfig.visible" class="virtualBackground-control-container">
+    <icon-button :title="t('VirtualBackground')" @click-icon="openSettingPanel">
+      <virtual-background-icon></virtual-background-icon>
+    </icon-button>
+
+    <Dialog
+      v-model="isDialogVisible" :title="t('VirtualBackground')" width="600px" :modal="true"
+      :append-to-room-container="true" @close="closeSettingPanel"
+    >
+      <div id="stream-preview" class="stream-preview">
+        <div v-if="isLoading" class="mask"></div>
+        <div v-if="isLoading" class="spinner"></div>
+      </div>
+      <div class="setting">
+        <div
+          :class="[
+            'setting-item', selectedBackground === 'close' ? 'active' : ''
+          ]"
+          @click="applyVirtualBackground('close')"
+        >
+          <i class="setting-item-icon">
+            <img :src="CloseVirtualBackground" alt="close" style="width: 32px;" />
+          </i>
+          <span>{{ t('Close') }}</span>
+        </div>
+        <div
+          :class="[
+            'setting-item', selectedBackground === 'blur' ? 'active' : ''
+          ]"
+          @click="applyVirtualBackground('blur')"
+        >
+          <i class="setting-item-icon">
+            <img :src="BlurredBackground" alt="blurred" />
+          </i>
+          <span>{{ t('BlurredBackground') }}</span>
+        </div>
+      </div>
+      <div class="footer">
+        <TuiButton
+          class="button" :disabled="!isAllowed" @click="confirmVirtualBackground"
+        >
+          {{ t('Save') }}
+        </TuiButton>
+        <TuiButton class="button" type="primary" @click="closeSettingPanel">{{ t('Cancel') }}</TuiButton>
+      </div>
+    </Dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, nextTick, ref } from 'vue';
+import IconButton from '../common/base/IconButton.vue';
+import VirtualBackgroundIcon from '../../assets/icons/VirtualBackgroundIcon.svg';
+import { useI18n } from '../../locales';
+import { roomService } from '../../services';
+import Dialog from '../common/base/Dialog/index.vue';
+import CloseVirtualBackground from '../../assets/imgs/close-virtual-background.png';
+import BlurredBackground from '../../assets/imgs/blurred-background.png';
+import TuiButton from '../common/base/Button.vue';
+
+const { t } = useI18n();
+const componentConfig = roomService.componentManager.getComponentConfig('VirtualBackground');
+const isAllowed = computed(() => roomService.roomStore.localStream.hasVideoStream);
+const appliedBackground = ref<'close' | 'blur'>('close');
+const selectedBackground = ref<'close' | 'blur'>('close');
+const isDialogVisible = ref(false);
+const isLoading = ref(false);
+const openSettingPanel = async () => {
+  roomService.virtualBackground.initVirtualBackground();
+  isDialogVisible.value = true;
+  isLoading.value = true;
+  await nextTick();
+  await roomService.roomEngine.instance?.startCameraDeviceTest({ view: 'stream-preview' });
+  await applyVirtualBackground(appliedBackground.value);
+  isLoading.value = false;
+};
+
+const closeSettingPanel = async () => {
+  isDialogVisible.value = false;
+  roomService.roomEngine.instance?.stopCameraDeviceTest();
+  selectedBackground.value = appliedBackground.value;
+};
+
+const confirmVirtualBackground = async () => {
+  if (!isAllowed.value) return;
+  appliedBackground.value = selectedBackground.value;
+  closeSettingPanel();
+  if (selectedBackground.value === 'blur') {
+    await roomService.virtualBackground.toggleVirtualBackground(true);
+  }
+  if (selectedBackground.value === 'close') {
+    await roomService.virtualBackground.toggleVirtualBackground(false);
+  }
+};
+
+const applyVirtualBackground = async (type: 'close' | 'blur') => {
+  isLoading.value = true;
+  try {
+    selectedBackground.value = type;
+    await roomService.virtualBackground.toggleTestVirtualBackground(type === 'blur');
+  } finally {
+    isLoading.value = false;
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.stream-preview {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-sizing: border-box;
+  border-radius: 8px;
+  overflow: hidden;
+  min-height: 310px;
+  background-color: #000;
+  position: relative;
+}
+
+.setting {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  margin-top: 10px;
+  padding: 1rem;
+  border: 1px solid #E4E8EE;
+  border-radius: 8px;
+
+  &-item {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    text-align: center;
+    border-radius: 8px;
+    color: #4F586B;
+    font-size: 12px;
+    border: 1px solid transparent;
+    cursor: pointer;
+
+    &-icon {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background-color: #f0f3fa;
+      border-radius: 8px;
+      width: 54px;
+      height: 54px;
+      overflow: hidden;
+    }
+  }
+
+  &-item.active {
+    background-color: #1C66E5;
+    border: 1px solid #1C66E5;
+    color: #fff;
+  }
+}
+
+.spinner {
+  z-index: 3;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 40px;
+  height: 40px;
+  border: 4px solid #f3f3f3;
+  border-top: 4px solid #1C66E5;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+.mask {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  z-index: 2;
+}
+
+@keyframes spin {
+  0% {
+    transform: translate(-50%, -50%) rotate(0deg);
+  }
+  100% {
+    transform: translate(-50%, -50%) rotate(360deg);
+  }
+}
+
+.footer {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 1rem;
+  margin-top: 10px;
+  padding: 1rem;
+  border-radius: 8px;
+  .button {
+    width: 84px;
+    height: 32px;
+  }
+}
+</style>

+ 0 - 3
MiniProgram/src/roomkit/TUIRoom/components/RoomFooter/index/index.vue

@@ -36,8 +36,6 @@ import MemberApplyControl from '../ApplyControl/MemberApplyControl.vue';
 import MoreControl from '../MoreControl/index.vue';
 import bus from '../../../hooks/useMitt';
 
-import TUIRoomAegis from '../../../utils/aegis';
-
 import useRoomFooter from './useRoomFooterHooks';
 
 const {
@@ -49,7 +47,6 @@ const {
 
 
 function handleControlClick(name: string) {
-  TUIRoomAegis.reportEvent({ name, ext1: name });
   bus.emit('experience-communication', name);
 }
 </script>

+ 1 - 1
MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/RoomInfo/useRoomInfoHooks.ts

@@ -19,7 +19,7 @@ export default function useRoomInfo() {
   const isShowRoomInfo = ref(false);
   const roomType = computed(() => (roomStore.isFreeSpeakMode ? t('Free Speech Room') : t('On-stage Speaking Room')));
   const arrowDirection = ref(false);
-  const [roomLinkConfig] = roomService.getComponentConfig(['RoomLink']);
+  const roomLinkConfig = roomService.getComponentConfig('RoomLink');
 
   const masterUserName = computed(() => roomStore.getUserName(masterUserId.value) || masterUserId.value);
 

+ 6 - 2
MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/UserInfo/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div ref="userInfoRef" class="user-info-container">
+  <div v-if="userInfoConfig.visible" ref="userInfoRef" class="user-info-container">
     <div @tap="handleUserControl" class="user-info-content">
       <Avatar class="avatar" :img-src="avatarUrl"></Avatar>
       <div class="name">{{ userName || userId }}</div>
@@ -8,7 +8,7 @@
     <div v-if="showUserControl" class="user-control-container">
       <div class="logout-mobile">
         <div class="logout-mobile-main">
-          <div class="logout" @tap="() => $emit('log-out')">
+          <div @tap="() => $emit('log-out')" class="logout">
             <i> {{ t('Log out') }}</i>
           </div>
           <div class="close" @click.stop="showUserControl = false">
@@ -22,6 +22,10 @@
 <script setup lang="ts">
 import Avatar from '../../common/Avatar.vue';
 import useUserInfo from './useUserInfoHooks';
+import { roomService } from '../../../services';
+
+const userInfoConfig = roomService.getComponentConfig('UserInfo');
+
 const {
   userInfoRef,
   showUserControl,

+ 2 - 61
MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/UserInfo/useUserInfoHooks.ts

@@ -1,27 +1,14 @@
-import { ref, onMounted, onUnmounted, Ref } from 'vue';
+import { ref, onMounted, onUnmounted } from 'vue';
 import { useI18n } from '../../../locales';
-import { useBasicStore } from '../../../stores/basic';
-import { storeToRefs } from 'pinia';
-import TUIMessage from '../../common/base/Message/index';
-import { MESSAGE_DURATION } from '../../../constants/message';
-import { TUIRoomEngine } from '@tencentcloud/tuiroom-engine-wx';
-import { useRoomStore } from '../../../stores/room';
 export default function useUserInfo() {
   const { t } = useI18n();
-  const basicStore = useBasicStore();
-  const { userName } = storeToRefs(basicStore);
 
   const userInfoRef = ref();
   const showUserControl = ref(false);
-  const showUserNameEdit: Ref<boolean> = ref(false);
-
-  const tempUserName = ref('');
-  const roomStore = useRoomStore();
 
   /**
      * Whether to display the user information operation box
      *
-     * 是否显示用户信息操作框
     **/
   function handleUserControl() {
     showUserControl.value = !showUserControl.value;
@@ -30,54 +17,13 @@ export default function useUserInfo() {
   /**
      * Hide the user information action box
      *
-     * 隐藏用户信息操作框
     **/
   function hideUserControl(event: Event) {
-    if (!userInfoRef.value.contains(event.target)) {
+    if (!userInfoRef.value?.contains(event.target)) {
       showUserControl.value = false;
     }
   }
 
-  /**
-     * Show change name dialog
-     *
-     * 展示修改名字 dialog
-    **/
-  function showEditUserNameDialog() {
-    showUserNameEdit.value = true;
-    tempUserName.value = userName.value;
-  }
-
-  /**
-     * Close the modify name dialog
-     *
-     * 关闭修改名字的 dialog
-    **/
-  function closeEditUserNameDialog() {
-    showUserNameEdit.value = false;
-  }
-
-  /**
-     * Save the new userName
-     *
-     * 保存新的 userName
-    **/
-  async function handleSaveUserName(userName: string) {
-    if (userName.length === 0) {
-      TUIMessage({
-        type: 'warning',
-        message: t('Username length should be greater than 0'),
-        duration: MESSAGE_DURATION.NORMAL,
-      });
-      return;
-    }
-    basicStore.setUserName(userName);
-    TUIRoomEngine.setSelfInfo({ userName, avatarUrl: roomStore.localUser.avatarUrl || '' });
-    roomStore.setLocalUser({ userName });
-    closeEditUserNameDialog();
-  }
-
-
   onMounted(() => {
     window?.addEventListener('click', hideUserControl);
   });
@@ -88,12 +34,7 @@ export default function useUserInfo() {
   return {
     t,
     showUserControl,
-    showUserNameEdit,
     userInfoRef,
-    tempUserName,
     handleUserControl,
-    showEditUserNameDialog,
-    closeEditUserNameDialog,
-    handleSaveUserName,
   };
 }

+ 1 - 17
MiniProgram/src/roomkit/TUIRoom/components/RoomHeader/index/index.vue

@@ -6,10 +6,7 @@
         <switch-mirror />
       </div>
       <room-info />
-      <end-control
-        @on-destroy-room="onDestroyRoom"
-        @on-exit-room="onExitRoom"
-      />
+      <end-control />
     </div>
     <switch-theme :visible="false"></switch-theme>
   </div>
@@ -19,21 +16,8 @@ import EndControl from '../../RoomFooter/EndControl/index.vue';
 import SwitchCamera from './SwitchCamera.vue';
 import SwitchMirror from './SwitchMirror.vue';
 import RoomInfo from '../RoomInfo/index.vue';
-import TUIRoomAegis from '../../../utils/aegis';
 import SwitchTheme from '../../common/SwitchTheme.vue';
 
-const emit = defineEmits(['log-out', 'on-destroy-room', 'on-exit-room']);
-
-const onDestroyRoom = (info: { code: number; message: string }) => {
-  emit('on-destroy-room', info);
-  TUIRoomAegis.reportEvent({ name: 'destroyRoom', ext1: 'destroyRoom-success' });
-};
-
-const onExitRoom = (info: { code: number; message: string }) => {
-  emit('on-exit-room', info);
-  TUIRoomAegis.reportEvent({ name: 'exitRoom', ext1: 'exitRoom-success' });
-};
-
 </script>
 <style scoped>
 .header{

+ 10 - 2
MiniProgram/src/roomkit/TUIRoom/components/RoomHome/RoomControl/index.vue

@@ -208,16 +208,24 @@ function handleDocumentClick(event: MouseEvent) {
 
 function handleRoomOption(type:string) {
   emit('update-user-name', currentUserName.value);
+  const roomParam = getRoomParam();
   switch (type) {
     case 'Join':
       if (!roomId.value) {
         TUIMessage({ type: 'error', message: t('Please enter the room number') });
         return;
       }
-      emit('enter-room', String(roomId.value));
+      emit('enter-room', {
+        roomId: String(roomId.value),
+        roomParam,
+      });
       break;
     case 'New':
-      emit('create-room', mode.value);
+      emit('create-room', {
+        roomMode: mode.value,
+        roomParam,
+        isSeatEnabled: Boolean(mode.value === 'SpeakAfterTakingSeat'),
+      });
       break;
     default:
       break;

+ 0 - 79
MiniProgram/src/roomkit/TUIRoom/components/RoomLogin/MailLogin.vue

@@ -1,79 +0,0 @@
-<template>
-  <div class="mail-login">
-    <input
-      v-model="mailAddress" :class="[isMobile ? 'input-mobile' : 'input']" :placeholder="t('Email address')"
-      enterkeyhint="complete"
-    >
-    <div class="area-container">
-      <svg-icon style="display: flex" :icon="MailIcon"></svg-icon>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref, watch } from 'vue';
-import SvgIcon from '../common/base/SvgIcon.vue';
-import { useI18n } from '../../locales';
-import { isMobile }  from '../../utils/environment';
-import MailIcon from '../../assets/icons/MailIcon.svg';
-
-const { t } = useI18n();
-
-const emit = defineEmits(['update-mail-address']);
-const mailAddress = ref('');
-watch(() => mailAddress.value, (val) => {
-  emit('update-mail-address', val);
-});
-</script>
-
-<style lang="scss" scoped>
-.mail-login{
-  position:relative;
-  width:100%;
-  height:100%;
-  .input{
-    -webkit-appearance:none;
-    background-color:#292D38;
-    background-image:none;
-    border-radius:8px;
-    border:1px solid #292D38;
-    box-sizing:border-box;
-    color: #B3B8C8;
-    display:inline-block;
-    font-size:inherit;
-    height:60px;
-    line-height:60px;
-    outline:none;
-    padding:0 15px 0 40px;
-    transition:border-color .2s cubic-bezier(.645,.045,.355,1);
-    width:100%;
-  }
-  .input-mobile{
-    -webkit-appearance:none;
-    background-color:rgba(207, 213, 230, 0.2);
-    background-image:none;
-    border-radius:8px;
-    box-sizing:border-box;
-    color: #B3B8C8;
-    display:inline-block;
-    font-size:inherit;
-    height:60px;
-    line-height:60px;
-    outline:none;
-    border: 0px;
-    padding:0 15px 0 40px;
-    transition:border-color .2s cubic-bezier(.645,.045,.355,1);
-    width:100%;
-    }
-  .area-container{
-    width:20px;
-    height:20px;
-    position:absolute;
-    top:32%;
-    left:4%;
-  }
-  .verify-code{
-    margin-top:30px;
-  }
-}
-</style>

+ 0 - 82
MiniProgram/src/roomkit/TUIRoom/components/RoomLogin/PhoneLogin.vue

@@ -1,82 +0,0 @@
-<template>
-  <div class="phone-login">
-    <input
-      v-model="phoneNumber"
-      :class="[isMobile ? 'input-mobile' : 'input']"
-      :placeholder="t('Mobile number')" auto-complete="true"
-      enterkeyhint="complete"
-    >
-    <div class="area-container">
-      <svg-icon style="display: flex" :icon="PhoneIcon"></svg-icon>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import SvgIcon from '../common/base/SvgIcon.vue';
-import PhoneIcon from '../../assets/icons/PhoneIcon.svg';
-import { ref, watch } from 'vue';
-import { useI18n } from '../../locales';
-import { isMobile }  from '../../utils/environment';
-
-const { t } = useI18n();
-const emit = defineEmits(['update-phone-number']);
-const phoneNumber = ref('');
-watch(() => phoneNumber.value, (val) => {
-  phoneNumber.value = val.replace(/\D/g, '');
-  emit('update-phone-number', phoneNumber.value);
-});
-</script>
-
-<style lang="scss" scoped>
-.phone-login{
-  position:relative;
-  width:100%;
-  height:100%;
-  .input{
-    -webkit-appearance:none;
-    background-color:#292D38;
-    background-image:none;
-    border-radius:8px;
-    border:1px solid #292D38;
-    box-sizing:border-box;
-    color: #B3B8C8;
-    display:inline-block;
-    font-size:inherit;
-    height:60px;
-    line-height:60px;
-    outline:none;
-    padding:0 15px 0 40px;
-    transition:border-color .2s cubic-bezier(.645,.045,.355,1);
-    width:100%;
-  }
-  .input-mobile{
-    -webkit-appearance:none;
-    background-color:rgba(207, 213, 230, 0.2);
-    background-image:none;
-    border-radius:8px;
-    box-sizing:border-box;
-    color: #B3B8C8;
-    display:inline-block;
-    font-size:inherit;
-    height:60px;
-    line-height:60px;
-    outline:none;
-    border: 0px;
-    padding:0 15px 0 40px;
-    transition:border-color .2s cubic-bezier(.645,.045,.355,1);
-    width:100%;
-    }
-  .area-container{
-    width:20px;
-    height:20px;
-    // border: 1px solid pink;
-    position:absolute;
-    top:32%;
-    left:4%;
-  }
-  .verify-code{
-    margin-top:30px;
-  }
-}
-</style>

+ 0 - 131
MiniProgram/src/roomkit/TUIRoom/components/RoomLogin/VerifyCode.vue

@@ -1,131 +0,0 @@
-<template>
-  <div class="verify-input">
-    <input
-      v-model="verifyStates.verifyCode"
-      :class="[isMobile ? 'input-mobile': 'input']"
-      :placeholder="t('Verification code')"
-      enterkeyhint="complete"
-    >
-    <div class="area-container">
-      <svg-icon style="display: flex" :icon="VerifyIcon"></svg-icon>
-    </div>
-    <span v-if="verifyStates.countdown <= 0" class="text send-btn" @click="sendVerifyCode">{{ t('SEND') }}</span>
-    <span v-else class="text static"> {{ t(' ') }} {{ `${verifyStates.countdown} s` }}</span>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { reactive, watch } from 'vue';
-import SvgIcon from '../common/base/SvgIcon.vue';
-import VerifyIcon from '../../assets/icons/VerifyIcon.svg';
-import { useI18n } from '../../locales';
-import { isMobile }  from '../../utils/environment';
-const { t } = useI18n();
-const emit = defineEmits(['update-verify-code', 'send-verify-code']);
-interface VerifyStates{
-  countdown: number,
-  timer: number,
-  verifyCode: string,
-}
-const verifyStates:VerifyStates = reactive({
-  countdown: 0,
-  timer: 0,
-  verifyCode: '',
-});
-watch(() => verifyStates.verifyCode, (val) => {
-  verifyStates.verifyCode = val.replace(/\D/g, '');
-  /**
-   * Get the verification code
-   *
-   * 获取到验证码
-  **/
-  emit('update-verify-code', verifyStates.verifyCode);
-});
-function startCountDown() {
-  verifyStates.countdown = 60;
-  verifyStates.timer = window?.setInterval(() => {
-    verifyStates.countdown = verifyStates.countdown - 1;
-    if (verifyStates.countdown <= 0) {
-      clearInterval(verifyStates.timer);
-    }
-  }, 1000);
-}
-function sendVerifyCode() {
-  emit('send-verify-code');
-}
-
-function clear() {
-  verifyStates.countdown = 0;
-}
-
-defineExpose({ startCountDown, clear });
-
-</script>
-
-<style lang="scss" scoped>
-.verify-input{
-    position:relative;
-    width:100%;
-    height:100%;
-    .input{
-        -webkit-appearance:none;
-        background-color:#292D38;
-        background-image:none;
-        border-radius:8px;
-        border:1px solid #292D38;
-        box-sizing:border-box;
-        color: #B3B8C8;
-        display:inline-block;
-        font-size:inherit;
-        height:60px;
-        line-height:60px;
-        outline:none;
-        padding:0 15px 0 40px;
-        transition:border-color .2s cubic-bezier(.645,.045,.355,1);
-        width:100%;
-    }
-    .input-mobile{
-        -webkit-appearance:none;
-        background-color:rgba(207, 213, 230, 0.2);
-        background-image:none;
-        border-radius:8px;
-        box-sizing:border-box;
-        color: #B3B8C8;
-        display:inline-block;
-        font-size:inherit;
-        height:60px;
-        line-height:60px;
-        outline:none;
-        border: 0px;
-        padding:0 15px 0 40px;
-        transition:border-color .2s cubic-bezier(.645,.045,.355,1);
-        width:100%;
-    }
-    .area-container {
-        width:20px;
-        height:20px;
-        // border: 1px solid pink;
-        position:absolute;
-        top:32%;
-        left:4%;
-    }
-    .text{
-      height: 60px;
-      line-height: 60px;
-      position: absolute;
-      right: 12px;
-      font-size: 18px;
-      // border:1px solid #000;
-        &.send-btn{
-            cursor:pointer;
-            color: #1C66E5;
-            font-size: 16px;
-        }
-        &.static{
-            color:#ccc;
-            opacity:0.5;
-        }
-    }
-
-}
-</style>

+ 0 - 8
MiniProgram/src/roomkit/TUIRoom/components/RoomSetting/index.vue

@@ -54,17 +54,9 @@ const basicStore = useBasicStore();
 
 const { showSettingDialog, activeSettingTab } = storeToRefs(basicStore);
 
-/**
- * TODO: Refine the rest of the settings Tab
- *
- * TODO: 完善其余设置 Tab
- **/
 const settingTabsTitleList = computed(() => [
   { label: t('Audio settings'), value: 'audio' },
   { label: t('Camera settings'), value: 'video' },
-  // { label: '美颜和虚拟设置', value: 'beauty' },
-  // { label: '统计功能', value: 'static' },
-  // { label: '录制', value: 'record' },
 ]);
 
 function handleUpdateActiveTab(tabTitle: string) {

+ 4 - 3
MiniProgram/src/roomkit/TUIRoom/components/RoomSidebar/useSideBarHooks.ts

@@ -13,7 +13,7 @@ export default function useSideBar() {
 
   const chatStore = useChatStore();
   const basicStore = useBasicStore();
-  const { sdkAppId, isSidebarOpen, sidebarName } = storeToRefs(basicStore);
+  const { sdkAppId, isSidebarOpen, sidebarName, roomId } = storeToRefs(basicStore);
   const roomStore = useRoomStore();
   const { userNumber } = storeToRefs(roomStore);
 
@@ -48,13 +48,14 @@ export default function useSideBar() {
     done();
   }
 
-  /** 监听消息接收,放在这里是为了打开 chat 之前只记录消息未读数 */
+  /** Monitor message reception, placed here to only record unread messages before opening chat */
   const onReceiveMessage = (options: { data: any }) => {
     if (!options || !options.data) {
       return;
     }
+    const currentConversationId = `GROUP${roomId.value}`
     options.data.forEach((message: any) => {
-      if (message.type === TencentCloudChat.TYPES.MSG_TEXT) {
+      if (message.conversationID === currentConversationId && message.type === TencentCloudChat.TYPES.MSG_TEXT) {
         if (!basicStore.isSidebarOpen || basicStore.sidebarName !== 'chat') {
           // eslint-disable-next-line no-plusplus
           chatStore.updateUnReadCount(++chatStore.unReadCount);

+ 0 - 3
MiniProgram/src/roomkit/TUIRoom/components/common/ArrowStroke.vue

@@ -16,11 +16,8 @@ import SvgIcon from './base/SvgIcon.vue';
 import ArrowStrokeLeftIcon from '../../assets/icons/ArrowStrokeLeftIcon.svg';
 
 interface Props {
-  // 'bottom' | 'left'
   strokePosition: string;
-  // 'up' | 'down' | 'left' | 'right'
   arrowDirection: string;
-  // 是否展示线条
   hasStroke: boolean,
 }
 

+ 0 - 7
MiniProgram/src/roomkit/TUIRoom/components/common/AudioMediaControl.vue

@@ -5,13 +5,6 @@
   * @param isDisabled boolean Whether the audio is disabled or not
   * Usage:
   * Use <audio-media-control /> in the template
-  *
-  * 名称: AudioMediaControl 音频媒体操作组件(开关麦克风)
-  * @param hasMore boolean 是否展示【更多】icon, 可以切换麦克风和扬声器
-  * @param isMuted boolean 音频是否被静音状态
-  * @param isDisabled boolean 音频是否 disabled 状态
-  * 使用方式:
-  * 在 template 中使用 <audio-media-control />
 -->
 <template>
   <div>

+ 0 - 10
MiniProgram/src/roomkit/TUIRoom/components/common/AudioSettingTab.vue

@@ -4,12 +4,6 @@
   * @param size String 'large'|'medium'|'small'
   * Usage:
   * Use <video-tab></video-tab> in the template
-  *
-  * 名称: VideoTab
-  * @param name String required
-  * @param size String 'large'|'medium'|'small'
-  * 使用方式:
-  * 在 template 中使用 <video-tab></video-tab>
 -->
 <template>
   <div :class="['audio-setting-tab', themeClass]">
@@ -107,8 +101,6 @@ const isTestingMicrophone = ref(false);
 
 /**
  * Click on the microphone [Test] button
- *
- * 点击麦克风【测试】按钮
 **/
 function handleMicrophoneTest() {
   isTestingMicrophone.value = !isTestingMicrophone.value;
@@ -120,8 +112,6 @@ const { t } = useI18n();
 
 /**
  * Click on the speaker [Test] button
- *
- * 点击扬声器【测试】按钮
 **/
 async function handleSpeakerTest() {
   const SPEAKER_TEST_URL = 'https://web.sdk.qcloud.com/trtc/electron/download/resources/media/TestSpeaker.mp3';

+ 62 - 26
MiniProgram/src/roomkit/TUIRoom/components/common/DeviceSelect.vue

@@ -4,12 +4,6 @@
   * @param size String 'large'|'medium'|'small'
   * Usage:
   * Use <device-select></device-select> in template
-  *
-  * 名称: DeviceSelect
-  * @param deviceType String required
-  * @param size String 'large'|'medium'|'small'
-  * 使用方式:
-  * 在 template 中使用 <device-select></device-select>
 -->
 <template>
   <tui-select
@@ -31,13 +25,13 @@
 </template>
 
 <script setup lang="ts">
-import { ref, Ref } from 'vue';
+import { ref, Ref, watch } from 'vue';
 import { useRoomStore } from '../../stores/room';
 import { storeToRefs } from 'pinia';
 import TuiSelect from './base/Select.vue';
 import TuiOption from './base/Option.vue';
 
-import { TRTCDeviceInfo, TUIMediaDeviceType } from '@tencentcloud/tuiroom-engine-wx';
+import { TRTCDeviceInfo, TUIDeviceInfo, TUIMediaDeviceType } from '@tencentcloud/tuiroom-engine-wx';
 import useDeviceManager from '../../hooks/useDeviceManager';
 const { deviceManager } = useDeviceManager();
 
@@ -65,17 +59,41 @@ const currentDeviceId = ref(getInitDeviceId());
 
 function getInitDeviceId() {
   if (deviceType === 'camera') {
-    return currentCameraId;
+    return currentCameraId.value;
   }
   if (deviceType === 'microphone') {
-    return currentMicrophoneId;
+    return currentMicrophoneId.value;
   }
   if (deviceType === 'speaker') {
-    return currentSpeakerId;
+    return currentSpeakerId.value;
   }
   return '';
 }
 
+if (deviceType === 'camera') {
+  watch(currentCameraId, (val) => {
+    if (currentDeviceId.value !== val) {
+      currentDeviceId.value = val;
+    }
+  }, { immediate: true });
+}
+
+if (deviceType === 'microphone') {
+  watch(currentMicrophoneId, (val) => {
+    if (currentDeviceId.value !== val) {
+      currentDeviceId.value = val;
+    }
+  }, { immediate: true });
+}
+
+if (deviceType === 'speaker') {
+  watch(currentSpeakerId, (val) => {
+    if (currentDeviceId.value !== val) {
+      currentDeviceId.value = val;
+    }
+  }, { immediate: true });
+}
+
 function getDeviceList() {
   if (deviceType === 'camera') {
     return cameraList;
@@ -93,25 +111,43 @@ async function handleChange(deviceId: string) {
   onChange && onChange(deviceId);
   switch (deviceType) {
     case 'camera':
-      await deviceManager.instance?.setCurrentDevice({
-        type: TUIMediaDeviceType.kMediaDeviceTypeVideoCamera,
-        deviceId,
-      });
-      roomStore.setCurrentCameraId(deviceId);
+      try {
+        await deviceManager.instance?.setCurrentDevice({
+          type: TUIMediaDeviceType.kMediaDeviceTypeVideoCamera,
+          deviceId,
+        });
+        roomStore.setCurrentCameraId(deviceId);
+      } catch (error) {
+        if (cameraList.value.map((item: TUIDeviceInfo) => item.deviceId).includes(currentCameraId.value)) {
+          currentDeviceId.value = currentCameraId.value;
+        }
+      }
       break;
     case 'microphone':
-      await deviceManager.instance?.setCurrentDevice({
-        type: TUIMediaDeviceType.kMediaDeviceTypeAudioInput,
-        deviceId,
-      });
-      roomStore.setCurrentMicrophoneId(deviceId);
+      try {
+        await deviceManager.instance?.setCurrentDevice({
+          type: TUIMediaDeviceType.kMediaDeviceTypeAudioInput,
+          deviceId,
+        });
+        roomStore.setCurrentMicrophoneId(deviceId);
+      } catch (error) {
+        if (microphoneList.value.map((item: TUIDeviceInfo) => item.deviceId).includes(currentMicrophoneId.value)) {
+          currentDeviceId.value = currentMicrophoneId.value;
+        }
+      }
       break;
     case 'speaker':
-      await deviceManager.instance?.setCurrentDevice({
-        type: TUIMediaDeviceType.kMediaDeviceTypeAudioOutput,
-        deviceId,
-      });
-      roomStore.setCurrentSpeakerId(deviceId);
+      try {
+        await deviceManager.instance?.setCurrentDevice({
+          type: TUIMediaDeviceType.kMediaDeviceTypeAudioOutput,
+          deviceId,
+        });
+        roomStore.setCurrentSpeakerId(deviceId);
+      } catch (error) {
+        if (speakerList.value.map((item: TUIDeviceInfo) => item.deviceId).includes(currentSpeakerId.value)) {
+          currentDeviceId.value = currentSpeakerId.value;
+        }
+      }
       break;
     default:
       break;

+ 3 - 0
MiniProgram/src/roomkit/TUIRoom/components/common/Language.vue

@@ -1,5 +1,6 @@
 <template>
   <icon-button
+    v-if="languageConfig.visible"
     :title="title"
     :layout="IconButtonLayout.HORIZONTAL"
     :icon="LanguageIcon"
@@ -15,10 +16,12 @@ import LanguageIcon from '../../assets/icons/LanguageIcon.svg';
 import { useBasicStore } from '../../stores/basic';
 import i18n from '../../locales/index';
 import { computed } from 'vue';
+import { roomService } from '../../services';
 
 const basicStore = useBasicStore();
 
 const title = computed(() => (basicStore.lang === 'en-US' ? 'English' : '中文'));
+const languageConfig = roomService.getComponentConfig('Language');
 
 const handleChange = (): void => {
   switch (i18n.global.locale.value) {

+ 7 - 7
MiniProgram/src/roomkit/TUIRoom/components/common/Logo.vue

@@ -1,15 +1,15 @@
 <!-- eslint-disable max-len -->
 <template>
   <div class="logo-container">
-    <!-- PC 端中文黑色主题下 logo -->
+    <!-- Logo under Chinese black theme on PC -->
     <div v-if="!isMobile && isZH && isBlackTheme">
       <svg-icon style="display: flex" :icon="LogoOfPCInChineseBlackIcon"></svg-icon>
     </div>
-    <!-- PC 端中文白色主题下 logo -->
+    <!-- Logo under Chinese white theme on PC -->
     <div v-if="!isMobile && isZH && isWhiteTheme">
       <svg-icon style="display: flex" :icon="LogoOfPCInChineseWhiteIcon"></svg-icon>
     </div>
-    <!-- 移动端中文黑白主题 logo -->
+    <!-- Mobile Chinese black and white theme logo -->
     <div v-if="isMobile && isZH" class="mobile-zh-logo">
       <span class="logo" :class="isWhiteTheme ? 'white' : 'black'">
         <svg-icon style="display: flex" :icon="LogoOfMobileInChinese"></svg-icon>
@@ -18,7 +18,7 @@
         <svg-icon style="display: flex" :icon="LogoTitleOfMobileInChinese"></svg-icon>
       </span>
     </div>
-    <!-- 英文黑白主题 logo -->
+    <!-- English black and white theme logo -->
     <div v-if="isEN" :class="['pc-en-logo', { 'mobile': isMobile }]">
       <span class="logo">
         <svg-icon style="display: flex" :icon="LogoInEnglish"></svg-icon>
@@ -32,7 +32,7 @@
 
 <script setup lang="ts">
 import i18n from '../../locales/index';
-import { isMobile, isWeChat } from '../../utils/environment';
+import { isMobile } from '../../utils/environment';
 import { computed } from 'vue';
 import { useBasicStore } from '../../stores/basic';
 import { storeToRefs } from 'pinia';
@@ -48,8 +48,8 @@ const basicStore = useBasicStore();
 
 const { defaultTheme } = storeToRefs(basicStore);
 
-const isEN = computed(() => !isWeChat && i18n.global.locale.value === 'en-US');
-const isZH = computed(() => isWeChat || i18n.global.locale.value === 'zh-CN');
+const isEN = computed(() => i18n.global.locale.value === 'en-US');
+const isZH = computed(() => i18n.global.locale.value === 'zh-CN');
 const isBlackTheme = computed(() => defaultTheme.value === 'black');
 const isWhiteTheme = computed(() => defaultTheme.value === 'white');
 

+ 1 - 4
MiniProgram/src/roomkit/TUIRoom/components/common/SwitchTheme.vue

@@ -3,9 +3,6 @@
   * Usage:
   * Use <switch-theme /> in template
   *
-  * 名称: Switchtheme
-  * 使用方式:
-  * 在 template 中使用 <switch-theme />
 -->
 <template>
   <icon-button
@@ -30,7 +27,7 @@ import { roomService } from '../../services';
 const basicStore = useBasicStore();
 const { defaultTheme } = storeToRefs(basicStore);
 const { t } = useI18n();
-const [switchThemeConfig] = roomService.getComponentConfig(['SwitchTheme']);
+const switchThemeConfig = roomService.getComponentConfig('SwitchTheme');
 
 interface Props {
   visible?: boolean,

+ 0 - 7
MiniProgram/src/roomkit/TUIRoom/components/common/VideoMediaControl.vue

@@ -5,13 +5,6 @@
   * @param isDisabled boolean Whether the video is disabled or not
   * Usage:
   * Use <video-media-control :isMuted="isMuted" /> in the template
-  *
-  * 名称: VideoMediaControl 视频媒体操作组件(开关摄像头)
-  * @param hasMore boolean 是否展示【更多】icon, 可以切换摄像头
-  * @param isMuted boolean 视频是否被静画状态
-  * @param isDisabled boolean 视频是否 disabled 状态
-  * 使用方式:
-  * 在 template 中使用 <video-media-control :isMuted="isMuted" />
 -->
 <template>
   <div>

+ 0 - 5
MiniProgram/src/roomkit/TUIRoom/components/common/VideoProfile.vue

@@ -5,11 +5,6 @@
   * Usage:
   * Use <device-select></device-select> in template
   *
-  * 名称: DeviceSelect
-  * @param deviceType String required
-  * @param size String 'large'|'medium'|'small'
-  * 使用方式:
-  * 在 template 中使用 <device-select></device-select>
 -->
 <template>
   <tui-select

+ 1 - 9
MiniProgram/src/roomkit/TUIRoom/components/common/VideoSettingTab.vue

@@ -5,11 +5,6 @@
   * Usage:
   * Use <video-tab></video-tab> in the template
   *
-  * 名称: VideoTab
-  * @param name String required
-  * @param size String 'large'|'medium'|'small'
-  * 使用方式:
-  * 在 template 中使用 <video-tab></video-tab>
 -->
 <template>
   <div :class="['video-tab', themeClass]">
@@ -32,7 +27,6 @@
       <tui-switch v-model="isLocalStreamMirror"></tui-switch>
     </div>
     <div v-if="withMore" class="item-setting">
-      <!-- TODO: <div class="item">美颜与虚拟背景</div> -->
       <div class="item" @click="handleMoreCameraSetting">{{ t('More Camera Settings') }}</div>
     </div>
   </div>
@@ -81,8 +75,6 @@ const { t } = useI18n();
 
 /**
  * Click [More Camera Settings].
- *
- * 点击【更多摄像头设置】
 **/
 function handleMoreCameraSetting() {
   basicStore.setShowSettingDialog(true);
@@ -93,7 +85,7 @@ if (props.withPreview) {
   onMounted(async () => {
     roomEngine.instance?.startCameraDeviceTest({ view: 'test-camera-preview' });
     if (isElectron) {
-      // Electron 需要首次设置 mirrorType
+      // Electron requires mirrorType to be set for the first time
       const trtcCloud = roomEngine.instance?.getTRTCCloud();
       await trtcCloud?.setLocalRenderParams({
         mirrorType: isLocalStreamMirror.value

+ 19 - 11
MiniProgram/src/roomkit/TUIRoom/components/common/base/Checkbox.vue

@@ -1,45 +1,53 @@
 <template>
   <div class="tui-checkbox">
-    <input
-      v-model="checked"
-      type="checkbox"
-      @change="handleValueChange"
-    />
-    <span @click="handleCheckBoxClick">
+    <input v-model="checked" type="checkbox" @change="handleValueChange" />
+    <span class="tui-checkbox-slot-container" @click="handleCheckBoxClick">
       <slot></slot>
     </span>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, Ref } from 'vue';
+import { ref, Ref, watch } from 'vue';
 interface Props {
   modelValue: boolean;
 }
 
 const props = withDefaults(defineProps<Props>(), {
   modelValue: false,
+  slotCustomStyle: () => ({}),
 });
-
 const checked: Ref<boolean> = ref(props.modelValue);
+const emit = defineEmits(['input']);
+
+
+watch(() => props.modelValue, (value) => {
+  checked.value = value;
+});
+
 
 function handleCheckBoxClick() {
   checked.value = !checked.value;
+  emit('input', checked.value);
 }
 
-const emit = defineEmits(['update:modelValue']);
 
 function handleValueChange(event: any) {
   checked.value = event.target.checked;
-  emit('update:modelValue', event.target.checked);
+  emit('input', event.target.checked);
 }
 </script>
 
 <style lang="scss" scoped>
 .tui-checkbox {
   position: relative;
-  display: inline-block;
+  display: flex;
+  align-items: center;
   cursor: pointer;
+  .tui-checkbox-slot-container {
+    flex: 1;
+    overflow: auto;
+  }
 }
 
 input {

+ 252 - 0
MiniProgram/src/roomkit/TUIRoom/components/common/base/Datepicker/Datepicker.vue

@@ -0,0 +1,252 @@
+<template>
+  <div ref="pickerRef" class="container">
+    <TuiInput
+      v-model="formattedDate" readonly class="datepicker-input" @focus="togglePicker"
+    />
+    <div v-if="showPicker" class="picker">
+      <div class="picker-header">
+        <span class="current-time">{{ currentYear }}/{{ currentMonth < 10 ? `0${currentMonth}` : currentMonth }}</span>
+        <button class="arrow" @click="changeMonth(-1)">&lt;</button>
+        <button class="arrow" @click="changeMonth(1)">&gt;</button>
+      </div>
+      <div class="picker-weekdays">
+        <div v-for="weekday in weekdays" :key="weekday" class="weekday">{{ weekday }}</div>
+      </div>
+      <div class="picker-body">
+        <div
+          v-for="day in days" :key="day.id" class="day"
+          :class="{
+            'other-month': day.otherMonth,
+            'selected': isSelected(day.date),
+            'today': isToday(day.date),
+            'past': isPast(day.date)
+          }"
+          @click="selectDate(day.date)"
+        >
+          {{ day.label }}
+          <span v-if="isToday(day.date)" class="today-dot"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, defineProps, defineEmits, watch, watchEffect, onMounted, onUnmounted } from 'vue';
+import TuiInput from '../Input/index.vue';
+
+interface Props {
+  modelValue: Date;
+}
+const props = defineProps<Props>();
+const emit = defineEmits(['input']);
+const showPicker = ref(false);
+const selectedDate = ref(props.modelValue || new Date());
+const currentYear = ref(selectedDate.value?.getFullYear());
+const currentMonth = ref(selectedDate.value?.getMonth() + 1);
+
+const pickerRef = ref<any>(null);
+const closeOnOutsideClick = (event: any) => {
+  if (!pickerRef.value.contains(event.target)) {
+    showPicker.value = false;
+  }
+};
+const togglePicker = () => {
+  showPicker.value = !showPicker.value;
+};
+onMounted(() => {
+  window?.addEventListener('click', closeOnOutsideClick);
+});
+
+onUnmounted(() => {
+  window?.removeEventListener('click', closeOnOutsideClick);
+});
+
+watch(selectedDate, (newValue) => {
+  emit('input', newValue);
+}, { immediate: true });
+
+watch(() => props.modelValue, (newValue) => {
+  if (!newValue) return;
+  selectedDate.value = newValue;
+  currentMonth.value = selectedDate.value.getMonth() + 1;
+  currentYear.value = selectedDate.value.getFullYear();
+}, { immediate: true });
+
+const changeMonth = (value: any) => {
+  currentMonth.value += value;
+  if (currentMonth.value === 0) {
+    currentMonth.value = 12;
+    currentYear.value -= 1;
+  } else if (currentMonth.value === 13) {
+    currentMonth.value = 1;
+    currentYear.value += 1;
+  }
+};
+
+const selectDate = (date: any) => {
+  if (isPast(date)) {
+    return;
+  }
+  selectedDate.value = date;
+  showPicker.value = false;
+};
+
+const days = ref<any[]>([]);
+
+const updateDays = () => {
+  const daysArray = [];
+  const firstDay = new Date(currentYear.value, currentMonth.value - 1, 1).getDay();
+  const daysInMonth = new Date(currentYear.value, currentMonth.value, 0).getDate();
+  const daysInPrevMonth = new Date(currentYear.value, currentMonth.value - 1, 0).getDate();
+
+  for (let i = daysInPrevMonth - firstDay + 1; i <= daysInPrevMonth; i++) {
+    const date = new Date(currentYear.value, currentMonth.value - 2, i);
+    daysArray.push({ id: date.toISOString(), label: i, date, otherMonth: true });
+  }
+
+  for (let i = 1; i <= daysInMonth; i++) {
+    const date = new Date(currentYear.value, currentMonth.value - 1, i);
+    daysArray.push({ id: date.toISOString(), label: i, date, otherMonth: false });
+  }
+
+  const remainingDays = 35 - daysArray.length;
+  if (remainingDays > 0) {
+    for (let i = 1; i <= remainingDays; i++) {
+      const date = new Date(currentYear.value, currentMonth.value, i);
+      daysArray.push({ id: date.toISOString(), label: i, date, otherMonth: true });
+    }
+  }
+
+  days.value = daysArray.slice(0, 35);
+};
+
+watchEffect(() => {
+  updateDays();
+});
+
+const formattedDate = computed(() => `${selectedDate.value.getFullYear()}-${selectedDate.value.getMonth() + 1}-${selectedDate.value.getDate()}`);
+
+const isSelected = (date: any) => date.toISOString() === selectedDate.value.toISOString();
+
+const isToday = (date: any) => {
+  const today = new Date();
+  return date.getFullYear() === today.getFullYear()
+  && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
+};
+
+const isPast = (date: any) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  return date < today;
+};
+
+const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+</script>
+
+<style lang="scss" scoped>
+.container {
+  position: relative;
+  display: inline-block;
+  width: 100%;
+}
+
+.datepicker-input {
+  width: 100%;
+  cursor: pointer;
+}
+
+.picker {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: inline-block;
+  padding: 5px;
+  border-radius: 4px;
+  background-color: #fff;
+  color: #000;
+  font-size: 12px;
+  width: 100%;
+  border: 1px solid var(--stroke-color);
+}
+
+.picker-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 5px;
+  font-size: 14px;
+  line-height: normal;
+
+  .current-time {
+    font-weight: bold;
+    color: black;
+    width: 80%;
+  }
+}
+
+.arrow {
+  display: inline-block;
+  padding: 0 5px;
+  border: none;
+  background-color: transparent;
+  cursor: pointer;
+}
+
+.arrow:hover {
+  color: #409eff;
+}
+
+.picker-weekdays {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+}
+
+.weekday {
+  padding: 5px;
+  background-color: #fff;
+  text-align: center;
+  line-height: initial;
+}
+
+.picker-body {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+}
+
+.day {
+  padding: 5px;
+  background-color: #fff;
+  text-align: center;
+  cursor: pointer;
+  line-height: initial;
+  position: relative;
+}
+
+.day:hover {
+  background-color: #ecf5ff;
+}
+
+.day.selected {
+  background-color: #409eff;
+  color: white;
+}
+
+.day.today .today-dot {
+  position: absolute;
+  bottom: 0px;
+  left: 50%;
+  transform: translateX(-50%);
+  display: inline-block;
+  width: 5px;
+  height: 5px;
+  background-color: #409eff;
+  border-radius: 50%;
+}
+
+.day.past {
+  color: #ccc;
+  pointer-events: none;
+}
+</style>

+ 54 - 0
MiniProgram/src/roomkit/TUIRoom/components/common/base/Datepicker/Timepicker.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="container">
+    <TuiSelect
+      v-model="selectedTime"
+      theme="white" class="timepicker-select" :teleported="false" :custom-select-content-style="{ 'font-weight': 400 }"
+    >
+      <TuiOption
+        v-for="time in timeOptions"
+        :key="time"
+        theme="white"
+        :value="time"
+        :label="time"
+        :custom-option-content-style="{ 'font-weight': 400 }"
+      ></TuiOption>
+    </TuiSelect>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, defineProps, defineEmits, watch } from 'vue';
+import TuiSelect from '../Select.vue';
+import TuiOption from '../Option.vue';
+interface Props {
+  modelValue: string;
+}
+const props = defineProps<Props>();
+const emit = defineEmits(['input']);
+const selectedTime = ref(props.modelValue);
+
+const timeOptions = computed(() => {
+  const options = [];
+  for (let i = 0; i < 24; i++) {
+    for (let j = 0; j < 60; j += 15) {
+      const hour = i < 10 ? `0${i}` : `${i}`;
+      const minute = j < 10 ? `0${j}` : `${j}`;
+      options.push(`${hour}:${minute}`);
+    }
+  }
+  return options;
+});
+
+const updateTime = () => {
+  emit('input', selectedTime.value);
+};
+
+watch(selectedTime, () => {
+  updateTime();
+}, {
+  immediate: true,
+});
+watch(() => props.modelValue, (newValue) => {
+  selectedTime.value = newValue;
+}, { immediate: true });
+</script>

+ 0 - 11
MiniProgram/src/roomkit/TUIRoom/components/common/base/Drawer.vue

@@ -10,17 +10,6 @@
   * Usage:
   * Use <Drawer title="there is title" v-model="showDrawer"></Drawer> in template
   *
-  * 名称: Drawer
-  * @param title String required [Drawer 的标题]
-  * @param modelValue Boolean [控制是否显示 Drawer]
-  * @param modal Boolean [Drawer 是否有遮罩层]
-  * @param size number | string [Drawer 的宽度]
-  * @param beforeClose (done: DoneFn) => void; [Drawer 关闭前的回调函数]
-  * @param closeOnClickModal Boolean [是否支持点击遮罩层关闭 Drawer]
-  * @param showClose Boolean [是否展示关闭按钮]
-  * @param appendToBody Boolean [是否插入到 body 中]
-  * 使用方式:
-  * 在 template 中使用 <Drawer title="there is title" v-model="showDrawer"></Drawer>
 -->
 <template>
   <div

+ 31 - 33
MiniProgram/src/roomkit/TUIRoom/components/common/base/IconButton.vue

@@ -6,41 +6,35 @@
   * @param layout IconButtonLayout.VERTICAl | IconButtonLayout.HORIZONTAL
   * Usage:
   * Use <icon-button :icon="chatIcon"><icon-button> in template
-  *
-  * 名称: IconButton
-  * @param title String required
-  * @param hasMore Boolean
-  * @param hideHoverEffect Boolean
-  * @param layout IconButtonLayout.VERTICAl | IconButtonLayout.HORIZONTAL
-  * 使用方式:
-  * 在 template 中使用 <icon-button :icon="chatIcon"><icon-button>
 -->
 <template>
-  <div class="icon-button-container">
-    <div
-      v-if="isMobile"
-      @tap="handleClickEvent"
-      :class="['icon-content', iconContentClass, `${disabled && 'disabled'}`]"
-    >
-      <slot></slot>
-      <svg-icon style="display: flex" v-if="icon" :icon="icon"></svg-icon>
-      <span class="title">{{ title }}</span>
-    </div>
-    <div
-      v-else
-      :class="['icon-content', iconContentClass, `${disabled && 'disabled'}`]"
-      @click="handleClickEvent"
-    >
-      <slot></slot>
-      <svg-icon style="display: flex" v-if="icon" :icon="icon"></svg-icon>
-      <svg-icon style="display: flex" v-if="isNotSupport" class="unsupport-icon" :icon="UnSupportIcon"></svg-icon>
-      <span class="title">
-        {{ title }}
-        <slot name="title"></slot>
-      </span>
-    </div>
-    <div v-if="hasMore" ref="moreSpanRef" class="icon-arrow" @click="$emit('click-more')">
-      <svg-icon style="display: flex" :icon="ArrowUp"></svg-icon>
+  <div :class="[themeClass]">
+    <div class="icon-button-container">
+      <div
+        v-if="isMobile"
+        @tap="handleClickEvent"
+        :class="['icon-content', iconContentClass, `${disabled && 'disabled'}`]"
+      >
+        <slot></slot>
+        <svg-icon style="display: flex" v-if="icon" :icon="icon"></svg-icon>
+        <span class="title">{{ title }}</span>
+      </div>
+      <div
+        v-else
+        :class="['icon-content', iconContentClass, `${disabled && 'disabled'}`]"
+        @click="handleClickEvent"
+      >
+        <slot></slot>
+        <svg-icon style="display: flex" v-if="icon" :icon="icon"></svg-icon>
+        <svg-icon style="display: flex" v-if="isNotSupport" class="unsupport-icon" :icon="UnSupportIcon"></svg-icon>
+        <span class="title">
+          {{ title }}
+          <slot name="title"></slot>
+        </span>
+      </div>
+      <div v-if="hasMore" ref="moreSpanRef" class="icon-arrow" @click="$emit('click-more')">
+        <svg-icon style="display: flex" :icon="ArrowUp"></svg-icon>
+      </div>
     </div>
   </div>
 </template>
@@ -63,6 +57,7 @@ interface Props {
   layout?: IconButtonLayout,
   icon?: Component | null,
   isNotSupport?: boolean,
+  theme?: 'white' | 'black',
 }
 
 const props = withDefaults(defineProps<Props>(), {
@@ -74,9 +69,12 @@ const props = withDefaults(defineProps<Props>(), {
   isActive: false,
   layout: IconButtonLayout.VERTICAl,
   isNotSupport: false,
+  theme: undefined,
 });
 const emit = defineEmits(['click-icon', 'click-more']);
 
+const themeClass = computed(() => (props.theme ? `tui-theme-${props.theme}` : ''));
+
 const iconContentClass = computed(() => {
   if (props.layout === IconButtonLayout.HORIZONTAL) {
     return 'icon-content-horizontal';

+ 0 - 61
MiniProgram/src/roomkit/TUIRoom/components/common/base/Input.vue

@@ -1,61 +0,0 @@
-<template>
-  <div class="tui-input">
-    <input
-      :value="modelValue"
-      :placeholder="placeholder"
-      :disabled="disabled"
-      maxlength="80"
-      :type="type"
-      @input="updateValue"
-    />
-  </div>
-</template>
-
-<script setup lang="ts">
-interface Props {
-  modelValue: string;
-  placeholder: string;
-  disabled?: boolean;
-  type?: string;
-}
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const props = withDefaults(defineProps<Props>(), {
-  modelValue: '',
-  placeholder: '',
-  disabled: false,
-  type: 'text',
-});
-
-const emit = defineEmits(['update:modelValue']);
-
-function updateValue(event: any) {
-  emit('update:modelValue', event.target.value);
-}
-</script>
-
-<style lang="scss" scoped>
-.tui-input {
-  position: relative;
-  display: inline-block;
-}
-
-input {
-  width: 100%;
-  padding: 10px 0px 10px 16px;
-  font-size: 14px;
-  color: var(--title-color);
-  background-color: #f9fafc;
-  border: 1px solid var(--stroke-color);
-  border-radius: 4px;
-}
-
-input:focus {
-  border-color: var(--active-color-1);
-  outline: 0;
-}
-
-input:disabled {
-  background-color: var(--background-color-9);
-}
-</style>

+ 76 - 0
MiniProgram/src/roomkit/TUIRoom/components/common/base/Input/index.vue

@@ -0,0 +1,76 @@
+<template>
+  <div class="input">
+    <input
+      @click.stop
+      ref="editorInputEle"
+      :value="modelValue"
+      :type="type"
+      class="content-bottom-input"
+      :confirm-type="enterkeyhint"
+      always-embed="true"
+      adjust-position="true"
+      @confirm="done"
+      @input="handleInput"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+interface Props {
+  modelValue: string;
+  placeholder?: string;
+  type?: string;
+  readonly?: boolean;
+  enterkeyhint?: string;
+  theme?: string;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const props = withDefaults(defineProps<Props>(), {
+  placeholder: '',
+  disabled: false,
+  type: 'text',
+  enterkeyhint: '',
+  modelValue: '',
+  theme: '',
+});
+
+const emit = defineEmits(['input','done']);
+
+const done = () => {
+  emit('done');
+}
+
+function handleInput(event: any) {
+  const { value } = event.target;
+  const trimmedValue = value.trimStart().trimEnd();
+  
+  if (value !== trimmedValue) {
+    event.target.value = trimmedValue;
+  }
+  emit('input', trimmedValue);
+}
+</script>
+
+<style>
+.input{
+  width: 100%;
+}
+.content-bottom-input {
+  color: #676c80;
+  width: 100%;
+  height: 35px;
+  border: none;
+  box-sizing: border-box;
+  font-family: 'PingFang SC';
+  font-style: normal;
+  font-weight: 450;
+  font-size: 16px;
+  line-height: 4vh;
+  background: var(--chat-editor-input-color-h5);
+  caret-color: var(--caret-color);
+  border-radius: 45px;
+  resize: none;
+  padding-left: 12px;
+}
+</style>

+ 1 - 0
MiniProgram/src/roomkit/TUIRoom/components/common/base/MessageBox/index.ts

@@ -3,6 +3,7 @@ function TUIMessageBox(params ?: {
   title?: string,
   confirmButtonText?: string;
   cancelButtonText?: string;
+  appendToRoomContainer?: boolean
   callback?: any;
 }) {
   const { confirmButtonText = '确定', cancelButtonText = '取消', callback } = params || {};

+ 14 - 4
MiniProgram/src/roomkit/TUIRoom/components/common/base/Option.vue

@@ -1,15 +1,21 @@
 <template>
-  <div :class="['option-container', { 'active': isSelected }]" @click="handleChooseOption">
-    <span class="option-content">{{ label || value }}</span>
+  <div ref="optionRef" :class="['option-container', { 'active': isSelected }]" @click="handleChooseOption">
+    <template v-if="$slots.customOptionContent">
+      <slot name="customOptionContent"></slot>
+    </template>
+    <template v-else>
+      <span :style="props.customOptionContentStyle" class="option-content">{{ label || value }}</span>
+    </template>
   </div>
 </template>
 
 <script setup lang="ts">
-import { inject, watch, computed, onBeforeUnmount } from 'vue';
+import { ref, inject, watch, computed, onBeforeUnmount, StyleValue, onMounted } from 'vue';
 
 interface OptionData {
   label: string,
   value: string | number | boolean | object,
+  customOptionContentStyle?: StyleValue,
 }
 
 interface SelectData {
@@ -21,6 +27,7 @@ interface SelectData {
   onOptionSelected: (optionData: OptionData) => void,
 }
 
+const optionRef = ref(null);
 const props = defineProps<OptionData>();
 
 const select: SelectData | undefined = inject('select');
@@ -30,9 +37,12 @@ const isSelected = computed(() => select && select.selectedValue === props.value
 const optionData = computed(() => ({
   label: props.label,
   value: props.value,
+  ref: optionRef,
 }));
 
-select?.onOptionCreated(optionData.value);
+onMounted(() => {
+  select?.onOptionCreated(optionData.value);
+});
 
 onBeforeUnmount(() => {
   select?.onOptionDestroyed(props.value);

+ 19 - 14
MiniProgram/src/roomkit/TUIRoom/components/common/base/Select.vue

@@ -7,9 +7,14 @@
       :class="['select-content', { 'disabled': disabled }]"
       @click="handleClickSelect"
     >
-      <span class="select-text">
-        {{ selectedLabel || selectedValue }}
-      </span>
+      <template v-if="$slots.customSelectContent">
+        <slot name="customSelectContent"></slot>
+      </template>
+      <template v-else>
+        <span class="select-text" :style="props.customSelectContentStyle">
+          {{ selectedLabel || selectedValue }}
+        </span>
+      </template>
       <svg-icon
         style="display: flex"
         :class="['arrow-icon', { 'reverse': showSelectDropdown }]"
@@ -30,7 +35,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, provide, computed, reactive, watch } from 'vue';
+import { ref, provide, computed, reactive, watch, StyleValue } from 'vue';
 import SvgIcon from './SvgIcon.vue';
 import ArrowStrokeSelectDownIcon from '../../../assets/icons/ArrowStrokeSelectDownIcon.svg';
 import useZIndex from '../../../hooks/useZIndex';
@@ -42,6 +47,7 @@ interface Props {
   modelValue: string | number | boolean | object,
   disabled?: boolean,
   theme?: 'white' | 'black',
+  customSelectContentStyle?: StyleValue,
 }
 
 const props = defineProps<Props>();
@@ -52,18 +58,16 @@ const showSelectDropdown = ref(false);
 const optionObj = ref(new Map());
 const optionDataList = computed(() => Array.from(optionObj.value.values()));
 const selectedLabel = ref('');
-const selectedValue = ref(props.modelValue);
 const selectContainerRef = ref();
 const selectDropDownRef = ref();
 const dropDirection = ref('down');
 const themeClass = computed(() => (props.theme ? `tui-theme-${props.theme}` : ''));
 
 watch(() => props.modelValue, (val) => {
-  selectedValue.value = val;
   if (optionObj.value.get(val)) {
     selectedLabel.value = optionObj.value.get(props.modelValue).label;
   }
-});
+}, { immediate: true });
 
 watch(optionDataList, () => {
   if (optionObj.value.get(props.modelValue)) {
@@ -87,14 +91,12 @@ function onOptionDestroyed(value: string | number | boolean | object) {
 }
 
 function onOptionSelected(option: OptionData) {
-  selectedValue.value = option.value;
   showSelectDropdown.value = false;
   emits('update:modelValue', option.value);
+  emits('change', option.value);
 }
 
-watch(selectedValue, (val) => {
-  emits('change', val);
-});
+const selectedValue = computed(() => props.modelValue);
 
 provide('select', reactive({
   selectedValue,
@@ -115,10 +117,11 @@ function handleClickSelect() {
     handleDropDownPosition();
     dropDownStyle.value = { zIndex: nextZIndex() };
     showSelectDropdown.value = true;
+    optionObj.value.get(props.modelValue).ref.scrollIntoView({ block: 'center' });
   }
 }
 
-// 根据页面位置确定下拉框的定位
+// Determine the positioning of the drop-down box based on the page position
 function handleDropDownPosition() {
   const { top, bottom } = selectContainerRef.value?.getBoundingClientRect();
   const container = roomService.getRoomContainer();
@@ -148,6 +151,7 @@ function handleClickOutside() {
 
 .select-container {
   position: relative;
+  height: 100%;
   .select-content {
     box-sizing: border-box;
     position: relative;
@@ -155,10 +159,11 @@ function handleClickOutside() {
     background-color: var(--background-color-7);
     color: var(--font-color-3);
     border-radius: 8px;
-    height: 42px;
-    padding: 10px 16px;
+    padding: 0px 16px;
     cursor: pointer;
     display: flex;
+    align-items: center;
+    height: 42px;
     &.disabled {
       background-color: rgba(255, 255, 255, 0.50);
       color: #8F9AB2;

+ 0 - 7
MiniProgram/src/roomkit/TUIRoom/components/common/base/SvgIcon.vue

@@ -29,7 +29,6 @@ interface Props {
   responseSize?: string | number,
   customClass?: string,
   icon?: string,
-  color?: string,
 }
 
 const props = defineProps<Props>();
@@ -70,12 +69,6 @@ watch(() => props.size, (val) => {
   }
 });
 
-watch(() => props.color, (val) => {
-  if (val) {
-    currentColor.value = val;
-  }
-}, { immediate: true });
-
 onMounted(() => {
   watch(defaultTheme, async () => {
     await nextTick();

+ 155 - 0
MiniProgram/src/roomkit/TUIRoom/conference.ts

@@ -0,0 +1,155 @@
+import { ChatSDK } from '@tencentcloud/chat';
+import { roomService, StartParams, JoinParams, LanguageOption, ThemeOption, EventType } from './services';
+import { TUIRoomEngine } from './index';
+import logger from './utils/common/logger';
+
+export enum RoomEvent {
+  ROOM_START = 'RoomStart',
+  ROOM_JOIN = 'RoomJoin',
+  ROOM_LEAVE = 'RoomLeave',
+  ROOM_DISMISS = 'RoomDestroy',
+  ROOM_ERROR = 'RoomError',
+  KICKED_OUT = 'KickedOut',
+  KICKED_OFFLINE = 'KickedOffline',
+  USER_SIG_EXPIRED = 'UserSigExpired',
+  USER_LOGOUT = 'UserLogout',
+}
+export enum FeatureButton {
+  SwitchTheme = 'SwitchTheme',
+  SwitchLayout = 'LayoutControl',
+  SwitchLanguage = 'Language',
+  FullScreen = 'FullScreen',
+  Invitation = 'InviteControl',
+}
+interface IConference {
+  getRoomEngine(): TUIRoomEngine | null;
+
+  on: (eventType: RoomEvent, callback: () => void) => void;
+
+  off: (eventType: RoomEvent, callback: () => void) => void;
+
+  login: (params: {
+    sdkAppId: number;
+    userId: string;
+    userSig: string;
+    tim?: ChatSDK;
+  }) => Promise<void>;
+
+  logout: () => Promise<void>;
+
+  start: (roomId: string, params: StartParams) => Promise<void>;
+
+  join: (roomId: string, params: JoinParams) => Promise<void>;
+
+  leave: () => Promise<void>;
+
+  dismiss: () => Promise<void>;
+
+  setSelfInfo: (options: {
+    userName: string;
+    avatarUrl: string;
+  }) => Promise<void>;
+
+  setLanguage: (language: LanguageOption) => void;
+
+  setTheme: (theme: ThemeOption) => void;
+
+  disableTextMessaging: () => void;
+
+  disableScreenSharing: () => void;
+
+  enableWatermark: () => void;
+
+  enableVirtualBackground: () => void;
+
+  hideFeatureButton: (name: FeatureButton) => void;
+
+  replaceFriendList: (userList: Array<any>) => void;
+}
+class Conference implements IConference {
+  public login(params: {
+    sdkAppId: number;
+    userId: string;
+    userSig: string;
+    tim?: ChatSDK;
+  }) {
+    return roomService.initRoomKit(params);
+  }
+
+  public async logout() {
+    return roomService.logOut();
+  }
+
+  public getRoomEngine() {
+    const roomEngine = roomService.roomEngine.instance;
+    if (!roomEngine) {
+      logger.warn('getRoomEngine failed, roomEngine is not exist');
+    }
+    return roomService.roomEngine.instance;
+  }
+
+  public on(eventType: RoomEvent, callback: () => any) {
+    roomService.on(eventType as unknown as EventType, callback);
+  }
+
+  public off(eventType: RoomEvent, callback: () => void) {
+    roomService.off(eventType as unknown as EventType, callback);
+  }
+
+  public async start(roomId: string, params?: StartParams) {
+    return await roomService.start(roomId, params);
+  }
+
+  public async join(roomId: string, params?: JoinParams) {
+    return await roomService.join(roomId, params);
+  }
+
+  public async leave() {
+    return await roomService.leaveRoom();
+  }
+
+  public async dismiss() {
+    return await roomService.dismissRoom();
+  }
+
+  public setSelfInfo(options: {
+    userName: string;
+    avatarUrl: string;
+  }) {
+    return roomService.setSelfInfo(options);
+  }
+
+  public setLanguage(language: LanguageOption) {
+    return roomService.setLanguage(language);
+  }
+
+  public setTheme(theme: 'LIGHT' | 'DARK') {
+    return roomService.setTheme(theme);
+  }
+
+  public disableTextMessaging() {
+    roomService.setComponentConfig({ ChatControl: { visible: false } });
+  }
+
+  public disableScreenSharing() {
+    roomService.setComponentConfig({ ScreenShare: { visible: false } });
+  }
+
+  public enableWatermark() {
+    roomService.waterMark.toggleWatermark(true);
+  }
+
+  public enableVirtualBackground() {
+    roomService.setComponentConfig({ VirtualBackground: { visible: true } });
+  }
+
+  public hideFeatureButton(name: FeatureButton) {
+    roomService.setComponentConfig({ [name]: { visible: false } });
+  }
+
+  public replaceFriendList(userList: Array<any>) {
+    return roomService.scheduleConferenceManager.replaceFriendList(userList);
+  }
+}
+
+export const conference = new Conference();

+ 106 - 64
MiniProgram/src/roomkit/TUIRoom/index.vue → MiniProgram/src/roomkit/TUIRoom/conference.vue

@@ -1,26 +1,24 @@
 <template>
-  <div v-if="basicStore.roomId" id="roomContainer" ref="roomRef" :class="tuiRoomClass">
+  <div v-if="conferenceShow" id="roomContainer" ref="roomRef" :class="tuiRoomClass">
     <room-header
       v-show="showRoomTool && showHeaderTool"
       class="header"
       @log-out="logOut"
-      @on-destroy-room="onDestroyRoom"
-      @on-exit-room="onExitRoom"
     ></room-header>
     <room-content
       ref="roomContentRef"
+      @tap="handleRoomContentTap"
       :show-room-tool="showRoomTool"
       class="content"
-      @tap="handleRoomContentTap"
     ></room-content>
-    <room-footer v-show="showRoomTool" class="footer" @on-destroy-room="onDestroyRoom" @on-exit-room="onExitRoom" />
+    <room-footer v-show="showRoomTool" class="footer" />
     <room-sidebar></room-sidebar>
     <room-setting></room-setting>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, onUnmounted, Ref, watch, computed } from 'vue';
+import { ref, onMounted, onUnmounted, Ref, watch, computed, withDefaults, defineProps } from 'vue';
 import RoomHeader from './components/RoomHeader/index/index.vue';
 import RoomFooter from './components/RoomFooter/index/index.vue';
 import RoomSidebar from './components/RoomSidebar/index.vue';
@@ -31,7 +29,6 @@ import { useBasicStore } from './stores/basic';
 import { isMobile, isWeChat } from './utils/environment';
 import { TUIKickedOutOfRoomReason } from '@tencentcloud/tuiroom-engine-wx';
 
-import TUIRoomAegis from './utils/aegis';
 import { MESSAGE_DURATION } from './constants/message';
 
 import TUIMessageBox from './components/common/base/MessageBox/index';
@@ -39,6 +36,14 @@ import TUIMessage from './components/common/base/Message/index';
 import { roomService, EventType, RoomParam, RoomInitData } from './services/index';
 import useDeviceManager from './hooks/useDeviceManager';
 
+const props = withDefaults(defineProps<{
+  displayMode: 'permanent' | 'wake-up'
+}>(), {
+  displayMode: 'permanent',
+});
+
+const conferenceShow = computed(() => (props.displayMode === 'permanent' || !!basicStore.roomId));
+
 useDeviceManager({ listenForDeviceChange: true });
 
 const { t } = roomService;
@@ -58,11 +63,8 @@ const emit = defineEmits([
   'on-enter-room',
   'on-exit-room',
   'on-destroy-room',
-  // 用户被踢出房间
   'on-kicked-out-of-room',
-  // 用户被踢下线
   'on-kicked-off-line',
-  // 用户 userSig 过期
   'on-userSig-expired',
 ]);
 
@@ -72,22 +74,23 @@ const showMessageBox = (data: {
   code?: number;
   message: string;
   title: string;
+  cancelButtonText: string,
   confirmButtonText: string;
   callback?: () => void;
 }) => {
   const {
     message,
     title = roomService.t('Note'),
+    cancelButtonText,
     confirmButtonText = roomService.t('Sure'),
-    callback = () => { },
+    callback = () => {},
   } = data;
   TUIMessageBox({
     title,
     message,
+    cancelButtonText,
     confirmButtonText,
-    callback: async () => {
-      callback && callback();
-    },
+    callback,
   });
 };
 const showMessage = (data: {
@@ -106,37 +109,44 @@ const showMessage = (data: {
 onMounted(() => {
   roomService.on(EventType.ROOM_NOTICE_MESSAGE, showMessage);
   roomService.on(EventType.ROOM_NOTICE_MESSAGE_BOX, showMessageBox);
-  roomService.on(EventType.ROOM_KICKED_OUT, onKickedOutOfRoom);
-  roomService.on(EventType.ROOM_USER_SIG_EXPIRED, onUserSigExpired);
-  roomService.on(EventType.ROOM_KICKED_OFFLINE, onKickedOffLine);
+  roomService.on(EventType.KICKED_OUT, onKickedOutOfRoom);
+  roomService.on(EventType.USER_SIG_EXPIRED, onUserSigExpired);
+  roomService.on(EventType.KICKED_OFFLINE, onKickedOffLine);
+  roomService.on(EventType.ROOM_START, onStartRoom);
+  roomService.on(EventType.ROOM_JOIN, onJoinRoom);
+  roomService.on(EventType.ROOM_LEAVE, onLeaveRoom);
+  roomService.on(EventType.ROOM_DISMISS, onDismissRoom);
+  roomService.on(EventType.USER_LOGOUT, onLogout);
 });
 onUnmounted(() => {
   roomService.off(EventType.ROOM_NOTICE_MESSAGE, showMessage);
   roomService.off(EventType.ROOM_NOTICE_MESSAGE_BOX, showMessageBox);
-  roomService.off(EventType.ROOM_KICKED_OUT, onKickedOutOfRoom);
-  roomService.off(EventType.ROOM_USER_SIG_EXPIRED, onUserSigExpired);
-  roomService.off(EventType.ROOM_KICKED_OFFLINE, onKickedOffLine);
+  roomService.off(EventType.KICKED_OUT, onKickedOutOfRoom);
+  roomService.off(EventType.USER_SIG_EXPIRED, onUserSigExpired);
+  roomService.off(EventType.KICKED_OFFLINE, onKickedOffLine);
+  roomService.off(EventType.ROOM_START, onStartRoom);
+  roomService.off(EventType.ROOM_JOIN, onJoinRoom);
+  roomService.off(EventType.ROOM_LEAVE, onLeaveRoom);
+  roomService.off(EventType.ROOM_DISMISS, onDismissRoom);
+  roomService.off(EventType.USER_LOGOUT, onLogout);
   roomService.resetStore();
 });
 
 const { sdkAppId, showHeaderTool } = roomService.basicStore;
-watch(
-  () => sdkAppId,
-  (val: number) => {
-    if (val) {
-      TUIRoomAegis.setSdkAppId(val);
-      TUIRoomAegis.reportEvent({
-        name: 'loaded',
-        ext1: 'loaded-success',
-      });
-    }
-  },
-);
-const tuiRoomClass = computed(() => (isMobile ? ['tui-room', `tui-theme-${roomService.basicStore.defaultTheme}`, 'tui-room-h5'] : ['tui-room', `tui-theme-${roomService.basicStore.defaultTheme}`]));
+const tuiRoomClass = computed(() => {
+  const roomClassList = ['tui-room', `tui-theme-${roomService.basicStore.defaultTheme}`];
+  if (isMobile) {
+    roomClassList.push('tui-room-h5');
+  }
+  if (basicStore.scene === 'chat') {
+    roomClassList.push('chat-room');
+  }
+  return roomClassList;
+});
+
 /**
  * Handle page mouse hover display toolbar logic
  *
- * 处理页面鼠标悬浮显示工具栏逻辑
  **/
 const roomContentRef = ref<InstanceType<typeof RoomContent>>();
 const showRoomTool: Ref<boolean> = ref(true);
@@ -146,7 +156,6 @@ function handleHideRoomTool() {
 }
 
 watch(() => roomRef.value, (newValue, oldValue) => {
-  // PC 端处理 room 控制栏交互
   if (!isWeChat && !isMobile) {
     if (newValue) {
       addRoomContainerEvent(newValue);
@@ -181,7 +190,7 @@ const removeRoomContainerEvent = (container: Node) => {
   container.removeEventListener('mousemove', showToolThrottle);
   container.removeEventListener('mouseleave', hideTool);
 };
-// H5 及小程序端处理 room 控制栏交互
+
 function handleRoomContentTap() {
   showRoomTool.value = !showRoomTool.value;
   if (showRoomTool.value) {
@@ -191,12 +200,10 @@ function handleRoomContentTap() {
 
 async function dismissRoom() {
   await roomService.dismissRoom();
-  emit('on-destroy-room');
 }
 
 async function leaveRoom() {
   await roomService.leaveRoom();
-  emit('on-exit-room');
 }
 
 async function init(option: RoomInitData) {
@@ -209,49 +216,46 @@ async function createRoom(options: {
   roomMode: 'FreeToSpeak' | 'SpeakAfterTakingSeat';
   roomParam?: RoomParam;
 }) {
-  await roomService.createRoom(options);
-  emit('on-create-room', {
-    code: 0,
-    message: 'create room success',
-  });
-  await roomService.enterRoom(options);
-  emit('on-enter-room', {
-    code: 0,
-    message: 'enter room success',
+  const { roomId, roomName, roomMode, roomParam } = options;
+  roomService.createRoom({
+    roomId,
+    roomName,
+    roomMode,
+    roomParam,
   });
 }
 
 async function enterRoom(options: { roomId: string; roomParam?: RoomParam }) {
   await roomService.enterRoom(options);
-  emit('on-enter-room', {
-    code: 0,
-    message: 'enter room success',
-  });
 }
 
-// To do 临时注释,待放开
-// const onStatistics = (statistics: TRTCStatistics) => {
-//   basicStore.setStatistics(statistics);
-// };
-
 function resetStore() {
   roomService.resetStore();
 }
 
 const logOut = () => {
   roomService.logOut();
-  emit('on-log-out');
 };
 
-const onDestroyRoom = (info: { code: number; message: string }) => {
-  roomService.emit(EventType.ROOM_DESTROY);
-  roomService.resetStore();
-  emit('on-destroy-room', info);
+const onStartRoom = () => {
+  emit('on-create-room',  { code: 0, message: 'create room' });
 };
 
-const onExitRoom = (info: { code: number; message: string }) => {
-  roomService.resetStore();
-  emit('on-exit-room', info);
+const onJoinRoom = () => {
+  emit('on-enter-room',  { code: 0, message: 'enter room' });
+};
+
+
+const onLeaveRoom = () => {
+  emit('on-exit-room',  { code: 0, message: 'exit room' });
+};
+
+const onDismissRoom = () => {
+  emit('on-destroy-room', { code: 0, message: 'destroy room' });
+};
+
+const onLogout = () => {
+  emit('on-log-out', { code: 0, message: 'user logout' });
 };
 
 const onKickedOutOfRoom = async (eventInfo: { roomId: string; reason: TUIKickedOutOfRoomReason; message: string }) => {
@@ -290,9 +294,21 @@ const onKickedOffLine = (eventInfo: { message: string }) => {
   --footer-shadow-color: rgba(34, 38, 46, 0.3);
 }
 
+.tui-theme-white.tui-room {
+  --header-shadow-color: #e3eaf7;
+  --footer-shadow-color: rgba(197, 210, 229, 0.2);
+}
+
+.tui-theme-black.tui-room {
+  --header-shadow-color: rgba(34, 38, 46, 0.3);
+  --footer-shadow-color: rgba(34, 38, 46, 0.3);
+}
+
 .tui-room {
   width: 100%;
   height: 100%;
+  min-width: 850px;
+  min-height: 400px;
   position: relative;
   color: var(--font-color-1);
   background-color: var(--background-color-1);
@@ -318,5 +334,31 @@ const onKickedOffLine = (eventInfo: { message: string }) => {
     position: absolute;
     top: 0;
   }
+
+  &.tui-room-h5 {
+    width: 100%;
+    height: 100%;
+    min-width: initial;
+    min-height: initial;
+  }
+}
+
+#roomContainer {
+  &.chat-room {
+    position: absolute;
+    margin: auto;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 999;
+    height: 80%;
+    width: 80%;
+    border-radius: 10px;
+  }
+  &.tui-room-h5,.chat-room {
+    width: 100%;
+    height: 100%;
+  }
 }
 </style>

+ 2 - 2
MiniProgram/src/roomkit/TUIRoom/constants/room.ts

@@ -1,8 +1,8 @@
 import { TRTCVideoResolution } from '@tencentcloud/tuiroom-engine-wx';
 
 export enum SpeechMode {
-  FREE_SPEECH = 'FreeSpeech', // 自由发言模式
-  APPLY_SPEECH = 'ApplySpeech', // 申请发言模式
+  FREE_SPEECH = 'FreeSpeech',
+  APPLY_SPEECH = 'ApplySpeech',
 }
 
 export enum IconButtonLayout {

+ 1 - 5
MiniProgram/src/roomkit/TUIRoom/extension/RoomMessageCard/handleRoomMessage.ts

@@ -3,10 +3,6 @@ import { Message, Profile } from '@tencentcloud/chat';
 import { getIsRoomCardMessage } from '../utils/judgeRoomMessage';
 
 /**
- * 渲染卡片需要用到 isRoomMessage、isRoomCreateByMe、userList、isEnterRoom
- * ChatExtension 提供基础功能 编辑消息 发送消息 消息上屏等
- * HandleRoomMessage 实现业务功能 发起会议、加入会议、离开会议等
- *
  * Rendering cards requires isRoomMessage, isRoomCreateByMe, userList, isEnterRoom
  * ChatExtension provides basic functions such as editing messages, sending messages, displaying messages on the screen.
  * HandleRoomMessage implements business functions such as initiating meetings, joining meetings, leaving meetings.
@@ -35,7 +31,7 @@ export class HandleRoomMessage {
     chatExtension.setHistoryMeetingMessageList('delete', { ID: this.message.ID, messageData: this.messageData });
   }
 
-  // 获取卡片信息
+  // Get card information
   private handleMessage(message: Message) {
     this.message = message;
     const currentUser = chatExtension.chatContext?.userID;

+ 3 - 0
MiniProgram/src/roomkit/TUIRoom/extension/RoomMessageCard/roomMessageCard.scss

@@ -46,6 +46,9 @@
             .title {
                 font-size: 16px;
                 font-weight: 500;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
             }
 
             .users {

+ 40 - 42
MiniProgram/src/roomkit/TUIRoom/extension/chatExtension.ts

@@ -28,10 +28,6 @@ export interface MessageData {
   ownerName: string
   owner: string
 }
-const loadStyle = () => {
-  import('./index.scss');
-};
-
 const getRoomOptions = () => ({
   roomId: String(Math.ceil(Math.random() * 1000000)),
   roomMode: 'FreeToSpeak',
@@ -47,15 +43,15 @@ const getRoomOptions = () => ({
 });
 export interface CustomMessagePayload {
   version: number
-  businessID: string // 固定值,用于在IM上区分当前消息是哪类自定义消息。
-  groupId: string // 邀请群成员入会时,需要用到groupId来获取群成员列表。
-  messageId: string // 用于观众成为房主后,通过messageId 来查找并更新指定消息。
-  roomId: string // 房间的id,enterRoom 必须的参数
-  owner: string // 房主的userId
-  ownerName: string // 房主的userName
-  roomState: RoomState // 当前的房间状态,有creating/created/destroying/destroyed 四种状态
-  memberCount: 1 // 当前房间内有多少人,需要在ui上展示有多少人在会议中。
-  userList: Array<{ faceUrl: string; nickName: string; userId: string }> // 包括房主在内的被邀请用户的列表,最多展示5个,防止消息长度超出限制。
+  businessID: string // Fixed value, used to distinguish the type of custom message on IM.
+  groupId: string // When inviting group members to join, groupId is needed to get the list of group members.
+  messageId: string // Used to find and update a specific message after the audience becomes the host.
+  roomId: string // The id of the room, a required parameter for enterRoom.
+  owner: string // The userId of the room owner.
+  ownerName: string // The userName of the room owner.
+  roomState: RoomState // The current room state, there are four states: creating/created/destroying/destroyed.
+  memberCount: 1 // The number of people in the current room, it needs to be displayed on the UI how many people are in the meeting.
+  userList: Array<{ faceUrl: string; nickName: string; userId: string }> // The list of invited users including the room owner, display up to 5 to prevent the message length from exceeding the limit.
 }
 export enum RoomState {
   CREATING = 'creating',
@@ -69,7 +65,7 @@ export enum ChatType {
   CUSTOM_SERVICE = 'customerService',
   ROOM = 'room'
 }
-// message 的编辑都交给房主处理,因此需要拿到每条message 然后对比 userid 相同的时候将其赋给message
+// The editing of messages is left to the homeowner, so you need to get each message and compare it with the userid and assign it to the message if it is the same.
 export class ChatExtension {
   static instance?: ChatExtension;
   private message = {} as Message;
@@ -177,7 +173,7 @@ export class ChatExtension {
     this.onUserRoleChanged = this.onUserRoleChanged.bind(this);
   }
   private bindRoomServiceEvent() {
-    this.service?.on(EventType.ROOM_DESTROY, this.onRoomDestroy);
+    this.service?.on(EventType.ROOM_DISMISS, this.onRoomDestroy);
   }
   private bindRoomEngineEvent() {
     roomEngine.instance?.on(TUIRoomEvents.onRemoteUserEnterRoom, this.onRemoteUserEnterRoom);
@@ -185,7 +181,7 @@ export class ChatExtension {
     roomEngine.instance?.on(TUIRoomEvents.onUserRoleChanged, this.onUserRoleChanged);
   }
   private unBindRoomServiceEvent() {
-    this.service?.off(EventType.ROOM_DESTROY, this.onRoomDestroy);
+    this.service?.off(EventType.ROOM_DISMISS, this.onRoomDestroy);
   }
   private unBindRoomEngineEvent() {
     roomEngine.instance?.off(TUIRoomEvents.onRemoteUserEnterRoom, this.onRemoteUserEnterRoom);
@@ -200,7 +196,7 @@ export class ChatExtension {
       ...userList,
       {
         faceUrl: userInfo.avatarUrl,
-        nickName: userInfo.userName,
+        nickName: userInfo.nameCard || userInfo.userName,
         userId: userInfo.userId,
       },
     ];
@@ -226,8 +222,8 @@ export class ChatExtension {
       const [profile] = profileResult.data;
       const { userID, nick } = profile;
       await this.modifyMessage(this.message.ID, {
-        owner: userID, // 房主的userId
-        ownerName: nick, // 房主的userName
+        owner: userID, // Homeowner’s userId
+        ownerName: nick, // Homeowner’s userName
       });
     }
   }
@@ -249,7 +245,7 @@ export class ChatExtension {
         },
       },
     };
-    if (!chatType) return extension; // 老版本 chatType === undefined 忽略配置直接 return
+    if (!chatType) return extension; // Old version chatType === undefined ignores configuration and returns directly
     if (!this.chatExtensionSetting[chatType]) return;
     return extension;
   }
@@ -294,7 +290,7 @@ export class ChatExtension {
       this.messagePayload = this.parseMessageData(message);
       await this.sendMessage(message);
       await this.modifyMessage(message.ID, { messageId: message.ID, roomState: RoomState.CREATED });
-    } catch(error) {
+    } catch (error) {
       this.service?.emit(EventType.ROOM_NOTICE_MESSAGE, {
         code: -1,
         type: 'error',
@@ -309,7 +305,7 @@ export class ChatExtension {
   public async onNotifyEvent(eventName: string, subKey: string, params?: Record<string, any>) {
     if (eventName === TUIConstants.TUILogin.EVENT.LOGIN_STATE_CHANGED) {
       if (subKey === TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGIN_SUCCESS) {
-        // 收到登录成功时执行自己的业务逻辑处理
+        // Execute your own business logic processing when receiving successful login
         !isMobile && setDragAndResize('#roomContainer');
         TUIRoomEngine?.callExperimentalAPI(JSON.stringify({
           api: 'setFramework',
@@ -320,11 +316,13 @@ export class ChatExtension {
         }));
         this.bindRoomEngineEvent();
         this.bindRoomServiceEvent();
-        loadStyle();
-        roomService.setComponentConfig({
-          SwitchTheme: {visible: false},
-          InviteControl: {visible: false},
-          RoomLink: {visible: false},
+        roomService.basicStore.setScene('chat');
+        roomService.componentManager.setComponentConfig({
+          SwitchTheme: { visible: false },
+          Language: { visible: false },
+          InviteControl: { visible: false },
+          RoomLink: { visible: false },
+          UserInfo: { visible: false },
         });
         this.chatContext = TUILogin.getContext();
         this.myProfile =  await this.getMyProfile();
@@ -346,17 +344,17 @@ export class ChatExtension {
     const { SDKAppID, userID, userSig } = this.chatContext;
     const { nick = '', avatar = defaultAvatarUrl } = this.myProfile;
     this.service && this.service[deep ? 'initRoomKit' : 'storeInit']({
-      // 获取 sdkAppId 请您参考 步骤一
+      // To get sdkAppId, please refer to Step One
       sdkAppId: SDKAppID,
-      // 用户在您业务中的唯一标示 Id
+      // The unique Id of the user in your business
       userId: userID,
-      // 本地开发调试可在 https://console.cloud.tencent.com/trtc/usersigtool 页面快速生成 userSig, 注意 userSig 与 userId 为一一对应关系
+      // For local development and debugging, you can quickly generate userSig on the page https://console.cloud.tencent.com/trtc/usersigtool. Note that userSig and userId have a one-to-one correspondence
       userSig,
-      // 用户在您业务中使用的昵称
+      // The nickname used by the user in your business
       userName: nick,
-      // 用户在您业务中使用的头像链接
+      // The avatar link used by the user in your business
       avatarUrl: avatar,
-      // 用户在您业务中需要的皮肤主题颜色及是否支持切换皮肤主题
+      // The skin theme color needed by the user in your business and whether to support switching skin themes
       theme: {
         isSupportSwitchTheme: false,
       },
@@ -436,20 +434,20 @@ export class ChatExtension {
       data: JSON.stringify({
         version: 1,
         businessID: 'group_room_message',
-        groupId: conversationID, // todo 当前版本暂不修改,等待im方案
-        messageId: '', // 用于观众成为房主后,通过 messageId 来查找并更新指定消息。
-        roomId, // 房间的id,enterRoom 必须的参数
-        owner: userID, // 房主的userId
-        ownerName: nick, // 房主的userName
-        roomState, // 当前的房间状态,有creating/created/destroying/destroyed 四种状态
-        memberCount: 1, // 当前房间内有多少人,需要在ui上展示有多少人在会议中。
+        groupId: conversationID, // todo The current version does not modify this, waiting for the IM solution
+        messageId: '', // Used to find and update a specific message after the audience becomes the host.
+        roomId, // The id of the room, a required parameter for enterRoom
+        owner: userID, // The userId of the room owner
+        ownerName: nick, // The userName of the room owner
+        roomState, // The current room state, there are four states: creating/created/destroying/destroyed
+        memberCount: 1, // The number of people in the current room, it needs to be displayed on the UI how many people are in the meeting.
         userList: [
           {
             faceUrl: avatar,
             nickName: nick,
             userId: userID,
           },
-        ], // 包括房主在内的被邀请用户的列表,最多展示5个,防止消息长度超出限制。
+        ], // A list of invited users, including the host, can be displayed at most 5 to prevent the message length from exceeding the limit.
       }),
     };
     return payload;
@@ -467,7 +465,7 @@ export class ChatExtension {
   private getUserProfile(userIDList: Array<string>) {
     const { chat } = this.chatContext;
     return chat.getUserProfile({
-      userIDList, // 请注意:即使只拉取一个用户的资料,也需要用数组类型,例如:userIDList: ['user1']
+      userIDList, // Please note: Even if you only pull the information of one user, you still need to use the array type, for example: userIDList: ['user1']
     });
   }
 

+ 0 - 20
MiniProgram/src/roomkit/TUIRoom/extension/index.scss

@@ -1,20 +0,0 @@
-#roomContainer {
-    position: absolute;
-    margin: auto;
-    top: 0;
-    right: 0;
-    bottom: 0;
-    left: 0;
-    z-index: 999;
-    height: 80%;
-    width: 80%;
-    border-radius: 10px;
-    .header-container>.right-container {
-        display: none;
-    }
-}
-
-#roomContainer.tui-room-h5 {
-    width: 100%;
-    height: 100%;
-}

+ 2 - 2
MiniProgram/src/roomkit/TUIRoom/extension/utils/interact.ts

@@ -18,7 +18,7 @@ export const setDragAndResize = (domSelect: string) => {
         let x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
         let y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
 
-        // 限制拖拽边界不超过屏幕
+        // Limit the dragging boundary to not exceed the screen
         x = Math.min(screenWidth - target.offsetWidth + minWidth - 100, Math.max(-minWidth + 100, x));
         y = Math.min(screenHeight - target.offsetHeight + minHeight - 100, Math.max(-minHeight + 100, y));
 
@@ -36,7 +36,7 @@ export const setDragAndResize = (domSelect: string) => {
           x = (parseFloat(x) || 0) + event.deltaRect.left;
           y = (parseFloat(y) || 0) + event.deltaRect.top;
 
-          // 限制缩放最小值
+          // Limit zoom minimum
           const width = Math.max(minWidth, event.rect.width);
           const height = Math.max(minHeight, event.rect.height);
 

+ 0 - 2
MiniProgram/src/roomkit/TUIRoom/hooks/useDeviceManager.ts

@@ -82,8 +82,6 @@ export default function (options?: { listenForDeviceChange: boolean }) {
 
   /**
    * Device changes: device switching, device plugging and unplugging events
-   *
-   * 设备变化:设备切换、设备插拔事件
   **/
   async function onDeviceChanged(eventInfo: {deviceId: string, type: TUIMediaDeviceType, state: TUIMediaDeviceState}) {
     if (!deviceManager.instance) {

+ 28 - 30
MiniProgram/src/roomkit/TUIRoom/hooks/useMasterApplyControl.ts

@@ -1,6 +1,4 @@
-// 举手发言逻辑
-// 主持人:同意/拒绝用户的申请发言,踢人下麦,邀请用户上麦,取消邀请用户上麦
-
+// Raise hands to speak logic
 import { onBeforeUnmount, computed, watch } from 'vue';
 import { TUIRoomEngine, TUIRoomEvents, TUIRequestAction, TUIRequest, TUIRequestCallbackType, TUIErrorCode } from '@tencentcloud/tuiroom-engine-wx';
 import useGetRoomEngine from './useRoomEngine';
@@ -26,30 +24,30 @@ export default function () {
   const applyToAnchorUserIdList = computed(() => applyToAnchorList.value.map(item => item.userId));
   const applyToAnchorUserCount = computed(() => applyToAnchorList.value.length);
 
-  // ------ 以下处理普通用户操作 ---------
+  // ------ The following handles common user operations ---------
 
-  // new: 收到来自用户的上麦申请
+  // new: Receive an application from a user
   function onRequestReceived(eventInfo: { request: TUIRequest }) {
     const { requestAction, requestId, userId, timestamp } = eventInfo.request;
     if (requestAction === TUIRequestAction.kRequestToTakeSeat) {
-      // 用户申请上麦
+      // User application for stage
       userId && roomStore.addApplyToAnchorUser({ userId, requestId, timestamp });
     }
   }
 
-  // 远端用户取消上麦申请
+  // The remote user cancels the application to connect to the stage
   function onRequestCancelled(eventInfo: { requestId: string, userId: string }) {
     const { requestId } = eventInfo;
     roomStore.removeApplyToAnchorUser(requestId);
   }
 
-  // 远端用户的请求被其他 管理员/房主 处理事件
+  // The remote user's request is handled by other administrators/hosts.
   function onRequestProcessed(eventInfo: { requestId: string, userId: string }) {
     const { requestId } = eventInfo;
     roomStore.removeApplyToAnchorUser(requestId);
   }
 
-  // 处理用户请求
+  // Handle user requests
   async function handleUserApply(applyUserId: string, agree: boolean) {
     try {
       // TUIRoomCore.replySpeechApplication(applyUserId, agree);
@@ -62,7 +60,7 @@ export default function () {
         });
         roomStore.removeApplyToAnchorUser(requestId);
       } else {
-        logger.warn('处理上台申请失败,数据异常,请重试!', userInfo);
+        logger.warn('Failed to process the stage application. The data is abnormal. Please try again!', userInfo);
       }
     } catch (error: any) {
       if (error.code === TUIErrorCode.ERR_ALL_SEAT_OCCUPIED) {
@@ -73,7 +71,7 @@ export default function () {
     }
   }
 
-  // 同意用户上台
+  // agree user to stage
   async function agreeUserOnStage(userInfo: UserInfo) {
     try {
       const requestId = userInfo.applyToAnchorRequestId;
@@ -81,7 +79,7 @@ export default function () {
         await roomEngine.instance?.responseRemoteRequest({ requestId, agree: true });
         roomStore.removeApplyToAnchorUser(requestId);
       } else {
-        logger.warn('同意上台申请失败,数据异常,请重试!', userInfo);
+        logger.warn('Failed to process the stage application. The data is abnormal. Please try again!', userInfo);
       }
     } catch (error: any) {
       if (error.code === TUIErrorCode.ERR_ALL_SEAT_OCCUPIED) {
@@ -92,7 +90,7 @@ export default function () {
     }
   }
 
-  // 拒绝用户上台
+  // reject user to stage
   async function denyUserOnStage(userInfo: UserInfo) {
     const requestId = userInfo.applyToAnchorRequestId;
     if (requestId) {
@@ -102,21 +100,21 @@ export default function () {
       });
       roomStore.removeApplyToAnchorUser(requestId);
     } else {
-      logger.warn('拒绝上台申请失败,数据异常,请重试!', userInfo);
+      logger.warn('Failed to process the stage application. The data is abnormal. Please try again!', userInfo);
     }
   }
 
-  // 处理全部用户上麦请求
+  // Process all users’ requests to access the microphone
   async function handleAllUserApply(isAgreeOrRejectAllUserApply: boolean) {
     let hasErrorOccurred = false;
     const applyUserList = applyToAnchorList.value.map(item => ({
       userId: item.userId,
-      userName: item.userName,
+      userName: item.nameCard || item.userName,
       applyToAnchorRequestId: item.applyToAnchorRequestId,
     }));
-    for (const { userId, userName, applyToAnchorRequestId } of applyUserList) {
+    for (const { applyToAnchorRequestId } of applyUserList) {
       const action = isAgreeOrRejectAllUserApply ? 'Agree' : 'Reject';
-      const actionFailedMessage = `${action} ${userName || userId} 上台申请失败,请重试!`;
+      const actionFailedMessage = `${action} sb on stage failed, please retry`;
       try {
         if (applyToAnchorRequestId) {
           await roomEngine.instance?.responseRemoteRequest({
@@ -151,9 +149,9 @@ export default function () {
     roomEngine.instance?.off(TUIRoomEvents.onRequestProcessed, onRequestProcessed);
   });
 
-  // --------- 以下处理主持人主动操作 ----------
+  // --------- The following handles the moderator’s active operations ----------
 
-  // 邀请用户上台
+  // Invite users to the stage
   async function inviteUserOnStage(userInfo: UserInfo) {
     const { userId } = userInfo;
     const request = await roomEngine.instance?.takeUserOnSeatByAdmin({
@@ -207,7 +205,7 @@ export default function () {
     }
   }
 
-  // 取消邀请用户上台
+  // Cancel invite users to the stage
   function cancelInviteUserOnStage(userInfo: UserInfo) {
     const { userId, inviteToAnchorRequestId } = userInfo;
     roomStore.removeInviteToAnchorUser(userId);
@@ -216,7 +214,7 @@ export default function () {
     }
   }
 
-  // 邀请下台
+  // Invite to step down
   function kickUserOffStage(userInfo: UserInfo) {
     roomEngine.instance?.kickUserOffSeatByAdmin({
       seatIndex: -1,
@@ -260,7 +258,7 @@ export default function () {
       const onlyOneUserTakeStage = newVal.length === 1;
       const firstUser = applyToAnchorList.value[0];
       const lastIndex = applyToAnchorList.value.length - 1;
-      const userName = applyToAnchorList.value[lastIndex]?.userName || applyToAnchorList.value[lastIndex]?.userId;
+      const userName = applyToAnchorList.value[lastIndex]?.nameCard || applyToAnchorList.value[lastIndex]?.userName || applyToAnchorList.value[lastIndex]?.userId;
       const message = onlyOneUserTakeStage
         ? `${userName} ${t('Applying for the stage')}`
         : `${userName} ${t('and so on number people applying to stage', { number: applyToAnchorList.value.length })}`;
@@ -279,19 +277,19 @@ export default function () {
     hideApplyList,
     applyToAnchorUserCount,
     applyToAnchorList,
-    // 处理用户上麦申请(同意/拒绝)
+    // Process the user's application for accessing the stage (agree/reject)
     handleUserApply,
-    // 同意普通用户上台
+    // Allow ordinary users to take the stage
     agreeUserOnStage,
-    // 拒绝普通用户上台
+    // Reject ordinary users to take the stage
     denyUserOnStage,
-    // 邀请用户上台
+    // Invite users to the stage
     inviteUserOnStage,
-    // 取消邀请用户上台
+    // Cancel invite users to the stage
     cancelInviteUserOnStage,
-    // 将用户踢下麦
+    // Kick the user off the stage
     kickUserOffStage,
-    // 处理所有用户请求
+    // Handle all user requests
     handleAllUserApply,
     handleShowNotification,
   };

+ 1 - 1
MiniProgram/src/roomkit/TUIRoom/hooks/useMitt.ts

@@ -1,4 +1,4 @@
-// 事件总线第三方库
+// Event bus third-party library
 import mitt from 'mitt';
 const bus = mitt();
 export default bus;

+ 11 - 0
MiniProgram/src/roomkit/TUIRoom/index.ts

@@ -0,0 +1,11 @@
+import { TUIRoomEngine } from '@tencentcloud/tuiroom-engine-wx';
+import { conference, RoomEvent, FeatureButton } from './conference';
+import { roomService } from './services';
+export {
+  TUIRoomEngine,
+  roomService,
+  conference,
+  RoomEvent,
+  FeatureButton,
+};
+

+ 86 - 31
MiniProgram/src/roomkit/TUIRoom/locales/en-US.ts

@@ -1,28 +1,4 @@
 export default {
-  '': '',
-  'Sign in': 'Sign in',
-  'Phone Login': 'Phone Login',
-  'Email Login': 'Email Login',
-  'I have read and agree to the': 'I have read and agree to the',
-  'Privacy Policy': 'Privacy Policy',
-  and: 'and',
-  'Terms of Use': 'Terms of Use',
-  Login: 'Login',
-  'Mobile number': 'Mobile number',
-  'Verification code': 'Verification code',
-  'Email address': 'Email address',
-  'Please enter a valid phone number!': 'Please enter a valid phone number!',
-  'Please enter a valid email address!': 'Please enter a valid email address!',
-  'Please enter your phone number!': 'Please enter your phone number!',
-  'Please enter your email address!': 'Please enter your email address!',
-  'Please enter the verification code!': 'Please enter the verification code!',
-  'Please accept the privacy policy and user agreement!': 'Please accept the privacy policy and user agreement!',
-  'Incorrect verification code, please check the code!': 'Incorrect verification code, please check the code!',
-  'The verification code has expired, please retrieve a new one!': 'The verification code has expired, please retrieve a new one!',
-  'The verification code has been used, please retrieve a new one!': 'The verification code has been used, please retrieve a new one!',
-  'Login failed, please try again.': 'Login failed, please try again.',
-  SEND: 'SEND',
-  ' ': ' ',
   'The room does not exist, please confirm the room number or create a room!': 'The room does not exist, please confirm the room number or create a room!',
   'Log out': 'Log out',
   'Edit profile': 'Edit profile',
@@ -105,6 +81,9 @@ export default {
   'You have been invited by the administrator to step down, please raise your hand if you need to speak': 'You have been invited by the administrator to step down, please raise your hand if you need to speak',
   Invite: 'Invite',
   Settings: 'Settings',
+  VirtualBackground: 'VirtualBackground',
+  Close: 'Close',
+  BlurredBackground: 'Blur',
   EndPC: 'End',
   EndH5: 'End',
   'You are currently the host of the room, please choose the corresponding operation. If you choose "End Room", the current room will be disbanded and all members will be removed. If you choose "Leave Room", the current room will not be disbanded, and your hosting privileges will be transferred to other members.': 'You are currently the host of the room, please choose the corresponding operation. If you choose "End Room", the current room will be disbanded and all members will be removed. If you choose "Leave Room", the current room will not be disbanded, and your hosting privileges will be transferred to other members.',
@@ -220,6 +199,8 @@ export default {
   'Camera settings': 'Camera settings',
   Copy: 'Copy',
   'Copied successfully': 'Copied successfully',
+  'Copied failure': 'Copied failure',
+  'This model does not support copying at this time': 'This model does not support copying at this time',
   'accepted the invitation to the stage': 'accepted the invitation to the stage',
   'declined the invitation to the stage': 'declined the invitation to the stage',
   'All audios disabled': 'All audios disabled',
@@ -229,8 +210,6 @@ export default {
   'Sb invites you to turn on the microphone': ({ named }) => `${named('role')} invites you to turn on the microphone`,
   'All videos disabled': 'All videos disabled',
   'All videos enabled': 'All videos enabled',
-  'Disabling text chat for all is enabled': 'Disabling text chat for all is enabled',
-  'Unblocked all text chat': 'Unblocked all text chat',
   'Your camera has been turned off': 'Your camera has been turned off',
   // @ts-ignore
   'Sb invites you to turn on the camera': ({ named }) => `${named('role')} invites you to turn on the camera`,
@@ -302,8 +281,6 @@ export default {
   'Stage management': 'Stage management',
   'Already on stage': 'Already on stage',
   'Not on stage': 'Not on stage',
-  'The stage is full, please contact the host': 'The stage is full, please contact the host',
-  'The stage is full': 'The stage is full',
   'To go on stage again, you need to reapply and wait for the roomOwner/administrator to approve': 'To go on stage again, you need to reapply and wait for the roomOwner/administrator to approve',
   'To go on stage again, a new application needs to be initiated': 'To go on stage again, a new application needs to be initiated',
   'The request to go on stage has been sent out, please wait for the roomOwner/administrator to approve it': 'The request to go on stage has been sent out, please wait for the roomOwner/administrator to approve it',
@@ -313,14 +290,17 @@ export default {
   'people applying to stage': 'people applying to stage',
   // @ts-ignore
   'and so on number people applying to stage': ({ named }) => `and so on ${named('number')} people applying to stage`,
-  // Room Chat 融合卡片翻译
+  'The stage is full, please contact the host': 'The stage is full, please contact the host',
+  'The stage is full': 'The stage is full',
+  'change name': 'change name',
+  // Room Chat Fusion Card Translation
   'quick conference': 'quick conference',
   Meeting: 'Meeting',
   'Meeting in progress': 'Meeting in progress',
   Initiating: 'Initiating',
-  'X people have joined': ({ values }: any) => `${values?.number} people have joined`,
+  'X people have joined': ({ named }: any) => `${named('number')} people have joined`,
   'Waiting for members to join the meeting': 'Waiting for members to join the meeting',
-  'X people are in the meeting': ({ values }: any) => `${values?.number} people are in the meeting`,
+  'X people are in the meeting': ({ named }: any) => `${named('number')} people are in the meeting`,
   'Already joined': 'Already joined',
   'Enter the meeting': 'Enter the meeting',
   'Ending meeting': 'Ending meeting',
@@ -328,4 +308,79 @@ export default {
   'Currently in a meeting, please exit the current meeting before proceeding.': 'Currently in a meeting, please exit the current meeting before proceeding.',
   'Failed to initiate meeting': 'Failed to initiate meeting',
   'Failed to enter the meeting': 'Failed to enter the meeting',
+  'Failed to disable chat': 'Failed to disable chat',
+  'All members can share screen': 'All members can share screen',
+  'Screen sharing for host/admin only': 'Screen sharing for host/admin only',
+  'Failed to initiate screen sharing, currently only host/admin can share screen.': 'Failed to initiate screen sharing, currently only host/admin can share screen.',
+  'Is it turned on that only the host/admin can share the screen?': 'Is it turned on that only the host/admin can share the screen?',
+  'Other member is sharing the screen is now, the member\'s sharing will be terminated after you turning on': 'Other member is sharing the screen now, the member\'s sharing will be terminated after you turning on',
+  'Your screen sharing has been stopped': 'Your screen sharing has been stopped',
+  'Your screen sharing has been stopped, Now only the host/admin can share the screen': 'Your screen sharing has been stopped, Now only the host/admin can share the screen',
+  'I got it': 'I got it',
+  Attention: 'Attention',
+  'Audio playback failed. Click the "Confirm" to resume playback': 'Audio playback failed. Click the "Confirm" to resume playback.',
+  // Schedule Room
+  'view details': 'view details',
+  'modify room': 'modify room',
+  'cancel room': 'cancel room',
+  join: 'join',
+  'No room available for booking': 'No room available for booking',
+  'Ongoing': 'Ongoing',
+  finished: 'finished',
+  'History room': 'History room',
+  'sb temporary room': ({ named }: any) => `${named('name')}'s temporary room`,
+  year: 'year',
+  month: 'month',
+  day: 'day',
+  Schedule: 'Schedule',
+  'Modify Room': 'Modify Room',
+  'Room Name': 'Room Name',
+  'please enter the room name': 'please enter the room name',
+  'Room type': 'Room type',
+  'Starting time': 'Starting time',
+  'Room duration': 'Room duration',
+  'Time zone': 'Time zone',
+  Attendees: 'Attendees',
+  'Please enter the member name': 'Please enter the member name',
+  people: 'people',
+  'Scheduled meetings': 'scheduled meetings',
+  minute: 'minute',
+  hour: 'hour',
+  minutes: 'minutes',
+  hours: 'hours',
+  'The start time cannot be earlier than the current time': 'The start time cannot be earlier than the current time',
+  'Room Time': 'Room Time',
+  'Creator': 'Creator',
+  'Room Details': 'Room Details',
+  'Enter Now': 'Enter Now',
+  'Invited members': 'Invited members',
+  'The room is closed': 'The room is closed',
+  'Entry Time': 'Entry Time',
+  'Duration of participation': 'Duration of participation',
+  'No cancellation': 'No cancellation',
+  'Other members will not be able to join after cancellation': 'Other members will not be able to join after cancellation',
+  'Inviting members to join': 'Inviting members to join',
+  'Invitation by room ID': 'Invitation by room ID',
+  'Invitation via room link': 'Invitation via room link',
+  'Copy the conference number and link': 'Copy the conference number and link',
+  'Schedule successful, invite members to join': 'Schedule successful, invite members to join',
+  'Name changed successfully': 'Name changed successfully',
+  'schedule year': '/',
+  'schedule month': '/',
+  'schedule day': ' ',
+  'The room name cannot exceed 100 characters': 'The room name cannot exceed 100 characters',
+  'The meeting is in progress and any meeting information cannot be modified': 'The meeting is in progress and any meeting information cannot be modified',
+  'Schedule room loading': 'Schedule room loading...',
+  'The user name cannot exceed 32 characters': 'The user name cannot exceed 32 characters',
+  'sb invites you to join the conference': ({ named }: any) => `${named('name')} invites you to join the conference`,
+  // media privilege
+  microphone: 'microphone',
+  camera: 'camera',
+  'Unable to use the device': ({ named }: any) => `Unable to use the ${named('deviceType')}`,
+  'media device failed to open, please check the media device and try again': ({ named }: any) => `${named('deviceType')} failed to open, please check the ${named('deviceType')} and try again`,
+  'The current device is not authorized, please allow access to the device permissions': ({ named }: any) => `The ${named('deviceType')} is not authorized, please allow access to the ${named('deviceType')} permissions`,
+  'The current device is occupied by other apps, try to close other apps or change the device': ({ named }: any) => `The ${named('deviceType')} is occupied by other apps, try to close other apps or change the device`,
+  'Turn on device privileges': ({ named }: any) => `Turn on ${named('deviceType')} privileges`,
+  'You can go to "System Preferences - Security & Privacy - Device" to enable device permissions': ({ named }: any) => `You can go to "System Preferences - Security & Privacy - ${named('deviceType')}" to enable device permissions.`,
+  'Go to Settings': 'Go to Settings',
 };

+ 4 - 5
MiniProgram/src/roomkit/TUIRoom/locales/index.ts

@@ -1,18 +1,18 @@
 /**
- * i18n 使用说明
+ * i18n Instructions for use
  *
  * <script>
  * import i18n, { useI18n } from '../locale';
  * const { t } = useI18n();
  *
- * // case 1: 翻译文本中没有变量
+ * // case 1: There are no variables in the translation text
  * t('happy');
  * i18n.t('happy');
- * // case 2: 翻译文本中存在变量
+ * // case 2: Variable exists in translation text
  * t('kick sb. out of room', { someOneName: 'xiaoming' });
  * i18n.t('kick sb. out of room', { someOneName: 'xiaoming' });
  *
- * // 切换语言
+ * // switch language
  * switch (i18n.global.locale.value) {
  *  case 'en-US':
  *    i18n.global.locale.value = 'zh-CN';
@@ -58,7 +58,6 @@ class TUIKitI18n {
     return message[key];
   }
 
-  // 兼容 App.use 不报错
   public install() {
   }
 }

+ 91 - 32
MiniProgram/src/roomkit/TUIRoom/locales/zh-CN.ts

@@ -1,33 +1,9 @@
 export default {
-  '': '',
-  'Sign in': '请登录',
-  'Phone Login': '手机登录',
-  'Email Login': '邮箱登录',
-  'I have read and agree to the': '我已阅读并同意',
-  'Privacy Policy': '隐私协议',
-  and: '和',
-  'Terms of Use': '用户协议',
-  Login: '登录',
-  'Mobile number': '请输入您的手机号',
-  'Verification code': '请输入验证码',
-  'Email address': '请输入邮箱',
-  'Please enter a valid phone number!': '请输入正确的手机号!',
-  'Please enter a valid email address!': '请输入正确的邮箱地址!',
-  'Please enter your phone number!': '请输入手机号!',
-  'Please enter your email address!': '请输入邮箱地址!',
-  'Please enter the verification code!': '请输入验证码!',
-  'Please accept the privacy policy and user agreement!': '请同意隐私协议及用户协议!',
-  'Incorrect verification code, please check the code!': '验证码错误,请检查验证码!',
-  'The verification code has expired, please retrieve a new one!': '验证码已过期,请重新获取验证码!',
-  'The verification code has been used, please retrieve a new one!': '验证码已使用,请重新获取验证码!',
-  'Login failed, please try again.': '登录失败,请重试',
-  SEND: '获取验证码',
-  ' ': '倒计时',
   'The room does not exist, please confirm the room number or create a room!': '房间不存在,请确认房间号或创建房间!',
   'Log out': '退出登录',
   'Edit profile': '编辑资料',
   'User Name': '用户名',
-  'Please input user name': '请输入用户名',
+  'Please input user name': '请输入名称',
   'Username length should be greater than 0': '昵称长度应该大于0',
   Save: '保存',
   Camera: '摄像头',
@@ -102,6 +78,9 @@ export default {
   'You have been invited by the administrator to step down, please raise your hand if you need to speak': '您已被管理员邀请下台,需要发言请先举手',
   Invite: '邀请',
   Settings: '设置',
+  VirtualBackground: '虚拟背景',
+  Close: '关闭',
+  BlurredBackground: '背景模糊',
   EndPC: '结束房间',
   EndH5: '结束',
   Tips: '提示',
@@ -220,6 +199,8 @@ export default {
   'Camera settings': '摄像头设置',
   Copy: '复制',
   'Copied successfully': '复制成功',
+  'Copied failure': '复制失败',
+  'This model does not support copying at this time': '此机型暂不支持复制',
   'accepted the invitation to the stage': '接受了上台邀请',
   'declined the invitation to the stage': '拒绝了上台邀请',
   'All audios disabled': '已开启全体静音',
@@ -229,8 +210,6 @@ export default {
   'Sb invites you to turn on the microphone': ({ named }) => `${named('role')}邀请你打开麦克风`,
   'All videos disabled': '已开启全体禁画',
   'All videos enabled': '已解除全体禁画',
-  'Disabling text chat for all is enabled': '已开启全体禁止文字聊天',
-  'Unblocked all text chat': '已解除全体禁止文字聊天',
   'Your camera has been turned off': '已关闭您的摄像头',
   // @ts-ignore
   'Sb invites you to turn on the camera': ({ named }) => `${named('role')}邀请你打开摄像头`,
@@ -302,8 +281,6 @@ export default {
   'Stage management': '上台管理',
   'Already on stage': '已上台',
   'Not on stage': '未上台',
-  'The stage is full, please contact the host': '台上人数已满,请联系主持人',
-  'The stage is full': '台上人数已满',
   'To go on stage again, you need to reapply and wait for the roomOwner/administrator to approve': '再次上台需重新发起申请,并等待房主/管理员通过',
   'To go on stage again, a new application needs to be initiated': '再次上台需重新发起申请',
   'The request to go on stage has been sent out, please wait for the roomOwner/administrator to approve it': '上台申请已发出,请等待房主/管理员通过',
@@ -313,14 +290,17 @@ export default {
   'people applying to stage': '人正在申请上台',
   // @ts-ignore
   'and so on number people applying to stage': ({ named }) => `等 ${named('number')} 人正在申请上台`,
-  // Room Chat 融合卡片翻译
+  'The stage is full, please contact the host': '台上人数已满,请联系主持人',
+  'The stage is full': '台上人数已满',
+  'change name': '修改名称',
+  // Room Chat Fusion Card Translation
   'quick conference': '快速会议',
   Meeting: '会议',
   'Meeting in progress': '会议 进行中',
   Initiating: '正在发起',
-  'X people have joined': ({ values }: any) => `${values?.number} 人已入会`,
+  'X people have joined': ({ named }: any) => `${named('number')} 人已入会`,
   'Waiting for members to join the meeting': '等待成员入会',
-  'X people are in the meeting': ({ values }: any) => `${values?.number} 人在会议中`,
+  'X people are in the meeting': ({ named }: any) => `${named('number')} 人在会议中`,
   'Already joined': '已入会',
   'Enter the meeting': '进入会议',
   'Ending meeting': '正在结束会议',
@@ -328,4 +308,83 @@ export default {
   'Currently in a meeting, please exit the current meeting before proceeding.': '正在会议中,请先退出当前会议后再进行操作',
   'Failed to initiate meeting': '发起会议失败',
   'Failed to enter the meeting': '进入会议失败',
+  'Failed to disable chat': '禁言失败',
+  'All members can share screen': '全体成员可共享屏幕',
+  'Screen sharing for host/admin only': '仅房主/管理员可共享屏幕',
+  'Failed to initiate screen sharing, currently only host/admin can share screen.': '发起共享失败,当前仅房主/管理员可以共享',
+  'Is it turned on that only the host/admin can share the screen?': '是否开启仅房主/管理员可共享屏幕?',
+  'Other member is sharing the screen is now, the member\'s sharing will be terminated after you turning on': '当前有成员正在共享屏幕, 开启后该成员的共享将会被终止',
+  'Your screen sharing has been stopped': '共享屏幕停止',
+  'Your screen sharing has been stopped, Now only the host/admin can share the screen': '已停止您的共享,当前仅房主/管理员可以共享屏幕',
+  'I got it': '我知道了',
+  Attention: '注意',
+  'Audio playback failed. Click the "Confirm" to resume playback': '音频播放失败。单击 “确认 ”恢复播放。',
+  // Schedule Room
+  'view details': '查看详情',
+  'modify room': '修改房间',
+  'cancel room': '取消房间',
+  join: '加入',
+  'No room available for booking': '暂无预订房间',
+  'Ongoing': '进行中',
+  finished: '已结束',
+  'History room': '历史房间',
+  'sb temporary room': ({ named }: any) => `${named('name')}的临时房间`,
+  year: '年',
+  month: '月',
+  day: '日',
+  Schedule: '预订房间',
+  'Modify Room': '修改房间',
+  'Room Name': '房间名称',
+  'please enter the room name': '请输入房间名称',
+  'Room type': '房间类型',
+  'Starting time': '开始时间',
+  'Room duration': '房间时长',
+  'Time zone': '时区',
+  Attendees: '参与成员',
+  'Please enter the member name': '请输入成员名称',
+  people: '人',
+  Contacts: '联系人',
+  'No relevant members found': '暂无相关成员',
+  'Selected Contact': '已选联系人',
+  'Scheduled meetings': '预订的会议',
+  minute: '分钟',
+  hour: '小时',
+  minutes: '分钟',
+  hours: '小时',
+  'The start time cannot be earlier than the current time': '开始时间不能早于当前时间',
+  'Room Time': '房间时间',
+  'Creator': '发起人',
+  'Room Details': '房间详情',
+  'Enter Now': '立即进入',
+  'Invited members': '邀请成员',
+  'The room is closed': '房间已结束',
+  'Entry Time': '进房时间',
+  'Duration of participation': '参会时长',
+  'No cancellation': '暂不取消',
+  'Cancellation of scheduled rooms': '取消已预订的房间',
+  'Other members will not be able to join after cancellation': '取消后其他成员将无法加入',
+  'Inviting members to join': '邀请成员加入',
+  'Invitation by room ID': '通过房间号邀请',
+  'Invitation via room link': '通过房间链接邀请',
+  'Copy the conference number and link': '复制会议号与链接',
+  'Schedule successful, invite members to join': '预订成功,邀请成员加入',
+  'Name changed successfully': '名称修改成功',
+  'schedule year': '年',
+  'schedule month': '月',
+  'schedule day': '日',
+  'The room name cannot exceed 100 characters': '房间名称不能超过100个字节',
+  'The meeting is in progress and any meeting information cannot be modified': '进行中的会议,不能修改任何会议信息',
+  'Schedule room loading': '预定房间加载中...',
+  'The user name cannot exceed 32 characters': '用户名称不能超过32个字节',
+  'sb invites you to join the conference': ({ named }: any) => `${named('name')} 邀请您加入会议`,
+  // media privilege
+  microphone: '麦克风',
+  camera: '摄像头',
+  'Unable to use the device': ({ named }: any) => `无法使用${named('deviceType')}`,
+  'media device failed to open, please check the media device and try again': ({ named }: any) => `打开${named('deviceType')}失败, 请检查${named('deviceType')}设备并重试`,
+  'The current device is not authorized, please allow access to the device permissions': ({ named }: any) => `${named('deviceType')}设备未授权, 请允许访问${named('deviceType')}权限`,
+  'The current device is occupied by other apps, try to close other apps or change the device': ({ named }: any) => `当前${named('deviceType')}被其他应用程序占用,请尝试关闭其他应用程序或更换${named('deviceType')}`,
+  'Turn on device privileges': ({ named }: any) => `打开${named('deviceType')}权限`,
+  'You can go to "System Preferences - Security & Privacy - Device" to enable device permissions': ({ named }: any) => `你可前往"系统设置 - 隐私与安全性 - ${named('deviceType')}"开启设备权限。`,
+  'Go to Settings': '前往设置',
 };

+ 213 - 0
MiniProgram/src/roomkit/TUIRoom/preConference.vue

@@ -0,0 +1,213 @@
+<template>
+  <div id="pre-conference-container" :class="['pre-conference-container', tuiRoomThemeClass]">
+    <div class="header">
+      <div class="left-header">
+        <switch-theme class="header-item"></switch-theme>
+      </div>
+      <div class="right-header">
+        <language-icon class="header-item language"></language-icon>
+        <user-info
+          class="header-item user-info"
+          :user-id="props.userInfo.userId"
+          :user-name="props.userInfo.userName"
+          :avatar-url="props.userInfo.avatarUrl"
+          :is-show-edit-name="props.showEditNameInPc"
+          @update-user-name="handleUpdateUserName"
+          @log-out="handleLogOut"
+        ></user-info>
+      </div>
+    </div>
+    <room-home-control
+      v-if="isMobile"
+      ref="roomControlRef"
+      :given-room-id="props.roomId"
+      :user-name="props.userInfo.userName"
+      @create-room="handleCreateRoom"
+      @enter-room="handleEnterRoom"
+      @update-user-name="handleUpdateUserName"
+    ></room-home-control>
+    <div class="pre-home-control" v-else>
+      <Logo v-show="props.isShowLogo" class="logo" />
+      <div class="pre-home-control-container">
+        <room-home-control
+          ref="roomControlRef"
+          :given-room-id="props.roomId"
+          :user-name="props.userInfo.userName"
+          :enable-scheduled-conference="props.enableScheduledConference"
+          @create-room="handleCreateRoom"
+          @enter-room="handleEnterRoom"
+          @update-user-name="handleUpdateUserName"
+        ></room-home-control>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted, onUnmounted } from 'vue';
+import UserInfo from './components/RoomHeader/UserInfo/index.vue';
+import RoomHomeControl from './components/RoomHome/RoomControl/index.vue';
+import LanguageIcon from './components/common/Language.vue';
+import SwitchTheme from './components/common/SwitchTheme.vue';
+import { EventType, roomService } from './services/index';
+import Logo from './components/common/Logo.vue';
+import TUIMessageBox from './components/common/base/MessageBox/index';
+import TUIMessage from './components/common/base/Message/index';
+import { MESSAGE_DURATION } from './constants/message';
+import { isMobile } from './utils/environment';
+
+const roomControlRef = ref();
+
+const props = withDefaults(defineProps<{
+  userInfo: {
+    userId: string,
+    userName: string,
+    avatarUrl: string,
+  },
+  showEditNameInPc: boolean,
+  roomId: string,
+  enableScheduledConference: boolean,
+  isShowLogo?: boolean
+}>(), {
+  userInfo: () => ({
+    userId: '',
+    userName: '',
+    avatarUrl: '',
+  }),
+  showEditNameInPc: false,
+  roomId: '',
+  enableScheduledConference: true,
+  isShowLogo: true,
+});
+
+const emits = defineEmits(['on-create-room', 'on-enter-room', 'on-update-user-name', 'on-logout']);
+
+const tuiRoomThemeClass = computed(() => `tui-theme-${roomService.basicStore.defaultTheme}`);
+
+async function handleCreateRoom(roomOption: Record<string, any>) {
+  emits('on-create-room', roomOption);
+}
+
+async function handleEnterRoom(roomOption: Record<string, any>) {
+  emits('on-enter-room', roomOption);
+}
+
+function handleUpdateUserName(userName: string) {
+  emits('on-update-user-name', userName);
+}
+
+async function handleLogOut() {
+  emits('on-logout');
+}
+
+const showMessageBox = (data: {
+  code?: number;
+  message: string;
+  title: string;
+  cancelButtonText: string,
+  confirmButtonText: string;
+  callback?: () => void;
+}) => {
+  const {
+    message,
+    title = roomService.t('Note'),
+    cancelButtonText,
+    confirmButtonText = roomService.t('Sure'),
+    callback = () => {},
+  } = data;
+  TUIMessageBox({
+    title,
+    message,
+    cancelButtonText,
+    confirmButtonText,
+    callback,
+  });
+};
+const showMessage = (data: {
+  type: 'warning' | 'success' | 'error' | 'info';
+  message: string;
+  duration: MESSAGE_DURATION;
+}) => {
+  const { type, message, duration } = data;
+  TUIMessage({
+    type,
+    message,
+    duration,
+  });
+};
+
+onMounted(() => {
+  roomService.on(EventType.ROOM_NOTICE_MESSAGE, showMessage);
+  roomService.on(EventType.ROOM_NOTICE_MESSAGE_BOX, showMessageBox);
+});
+onUnmounted(() => {
+  roomService.off(EventType.ROOM_NOTICE_MESSAGE, showMessage);
+  roomService.off(EventType.ROOM_NOTICE_MESSAGE_BOX, showMessageBox);
+});
+</script>
+
+<style>
+@import './assets/style/global.scss';
+@import './assets/style/black-theme.scss';
+@import './assets/style/white-theme.scss';
+</style>
+
+<style lang="scss" scoped>
+
+.tui-theme-black .pre-conference-container {
+  --background: var(--background-color-1);
+}
+.tui-theme-white .pre-conference-container {
+  --background: var(--background-color-1);
+}
+
+.tui-theme-black.pre-conference-container {
+  --background: var(--background-color-1);
+}
+
+.pre-conference-container {
+  width: 100%;
+  height: 100%;
+  background: var(--background);
+  background-size: cover;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: PingFang SC;
+  color: var(--font-color-1);
+  .header {
+    box-sizing: border-box;
+    width: 100%;
+    position: absolute;
+    top: 0;
+    padding: 22px 24px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .left-header, .right-header {
+      display: flex;
+      align-items: center;
+      .header-item {
+        &:not(:first-child) {
+          margin-left: 16px;
+        }
+      }
+    }
+  }
+  .pre-home-control {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    display: flex;
+    align-items: center;
+    flex-direction: column;
+    .logo {
+      margin-bottom: 56px;
+    }
+    .pre-home-control-container {
+      display: flex;
+    }
+  }
+}
+
+</style>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است