index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <div>
  3. <div
  4. v-if="groupApplicationCount > 0"
  5. class="application-tips"
  6. >
  7. <div>
  8. {{ groupApplicationCount }}{{ TUITranslateService.t("TUIChat.条入群申请") }}
  9. </div>
  10. <div
  11. class="application-tips-btn"
  12. @click="toggleGroupApplicationDrawerShow"
  13. >
  14. {{ TUITranslateService.t("TUIChat.点击处理") }}
  15. </div>
  16. </div>
  17. <Drawer
  18. ref="drawerDomInstanceRef"
  19. :visible="isGroupApplicationDrawerShow"
  20. :zIndex="998"
  21. :popDirection="isMobile ? 'bottom' : 'right'"
  22. :isFullScreen="isMobile"
  23. :overlayColor="isMobile ? undefined : 'transparent'"
  24. :drawerStyle="{
  25. bottom: {
  26. minHeight: '60vh',
  27. maxHeight: '80vh',
  28. borderRadius: '12px 12px 0 0',
  29. },
  30. right: {
  31. width: '360px',
  32. borderRadius: '12px 0 0 12px',
  33. boxShadow: '0 0 10px 0 #d0d0d0',
  34. }
  35. }"
  36. @onOverlayClick="toggleGroupApplicationDrawerShow"
  37. >
  38. <div
  39. :class="{
  40. 'application-contaienr': true
  41. }"
  42. >
  43. <header class="application-header">
  44. <div
  45. @click="toggleGroupApplicationDrawerShow"
  46. >
  47. <Icon
  48. v-if="isPC"
  49. :file="closeIcon"
  50. :size="'16px'"
  51. />
  52. <div v-else>
  53. {{
  54. TUITranslateService.t('关闭')
  55. }}
  56. </div>
  57. </div>
  58. </header>
  59. <main>
  60. <div
  61. v-for="(item, index) in customGroupApplicationList"
  62. :key="item.nick"
  63. :class="{
  64. 'application-item': true,
  65. 'removed': item.isRemoved,
  66. }"
  67. >
  68. <Avatar
  69. :style="{
  70. flex: '0 0 auto',
  71. }"
  72. :url="item.avatar"
  73. :useSkeletonAnimation="true"
  74. />
  75. <div class="application-item-info">
  76. <div class="application-item-nick">
  77. {{ item.nick }}
  78. </div>
  79. <div class="application-item-note">
  80. {{ TUITranslateService.t("TUIChat.申请加入") }}
  81. </div>
  82. </div>
  83. <div
  84. class="application-item-operation"
  85. >
  86. <div
  87. class="agree"
  88. @click="handleApplication(item, 'Agree', index)"
  89. >
  90. {{ TUITranslateService.t("TUIChat.同意") }}
  91. </div>
  92. <div
  93. class="reject"
  94. @click="handleApplication(item, 'Reject', index)"
  95. >
  96. {{ TUITranslateService.t("TUIChat.拒绝") }}
  97. </div>
  98. </div>
  99. </div>
  100. </main>
  101. </div>
  102. </Drawer>
  103. </div>
  104. </template>
  105. <script setup lang="ts">
  106. import { ref, onMounted, onUnmounted, watch } from '../../../../adapter-vue';
  107. import {
  108. TUIStore,
  109. StoreName,
  110. TUITranslateService,
  111. TUIUserService,
  112. TUIGroupService,
  113. } from '@tencentcloud/chat-uikit-engine';
  114. import Icon from '../../../common/Icon.vue';
  115. import Avatar from '../../../common/Avatar/index.vue';
  116. import Drawer from '../../../common/Drawer/index.vue';
  117. import closeIcon from '../../../../assets/icon/close-dark.svg';
  118. import { isPC, isMobile } from '../../../../utils/env';
  119. import { IGroupApplication, IUserProfile, IChatResponese } from '../../../../interface';
  120. interface IProps {
  121. groupID: string;
  122. }
  123. interface ICustomGroupApplication {
  124. nick: string;
  125. avatar: string;
  126. isRemoved: boolean;
  127. application: IGroupApplication;
  128. }
  129. const props = withDefaults(defineProps<IProps>(), {
  130. groupID: '',
  131. });
  132. const drawerDomInstanceRef = ref<InstanceType<typeof Drawer>>();
  133. const groupApplicationCount = ref(0);
  134. const isGroupApplicationDrawerShow = ref(false);
  135. const customGroupApplicationList = ref<ICustomGroupApplication[]>([]);
  136. watch(isGroupApplicationDrawerShow, (newVal) => {
  137. if (newVal) {
  138. generateCustomGroupApplicationList().then((list) => {
  139. customGroupApplicationList.value = list;
  140. groupApplicationCount.value = list.length;
  141. });
  142. }
  143. });
  144. watch(() => customGroupApplicationList.value.length, (newVal, oldVal) => {
  145. if (oldVal > 0 && newVal === 0) {
  146. isGroupApplicationDrawerShow.value = false;
  147. }
  148. });
  149. /**
  150. * Retrieves the current group application list based on the provided groupID.
  151. *
  152. * @return {Promise<IGroupApplication[]>} The list of group applications for the current group.
  153. */
  154. async function getCurrentGroupApplicationList(): Promise<IGroupApplication[]> {
  155. const result: IChatResponese<{ applicationList: IGroupApplication[] }> = await TUIGroupService.getGroupApplicationList();
  156. const currentGroupApplicationList = result.data.applicationList.filter(application => application.groupID === props.groupID);
  157. return currentGroupApplicationList;
  158. }
  159. function toggleGroupApplicationDrawerShow() {
  160. isGroupApplicationDrawerShow.value = !isGroupApplicationDrawerShow.value;
  161. }
  162. async function generateCustomGroupApplicationList(): Promise<ICustomGroupApplication[]> {
  163. const applicationList = await getCurrentGroupApplicationList();
  164. if (applicationList.length === 0) {
  165. return [];
  166. }
  167. const userIDList = applicationList.map(application => application.applicationType === 0 ? application.applicant : application.userID);
  168. const { data: userProfileList } = await TUIUserService.getUserProfile({ userIDList }) as IChatResponese<IUserProfile[]>;
  169. const mappingFromUserID2Profile: Record<string, IUserProfile> = {};
  170. userProfileList.forEach((profile: IUserProfile) => {
  171. mappingFromUserID2Profile[profile.userID] = profile;
  172. });
  173. const groupApplicationList: ICustomGroupApplication[] = applicationList.map((application) => {
  174. const profile = mappingFromUserID2Profile[application.applicationType === 0 ? application.applicant : application.userID];
  175. return {
  176. nick: profile.nick || profile.userID || 'anonymous',
  177. avatar: profile.avatar || '',
  178. isRemoved: false,
  179. application: application,
  180. };
  181. });
  182. return groupApplicationList;
  183. }
  184. function handleApplication(customApplication: ICustomGroupApplication, action: 'Agree' | 'Reject', index: number) {
  185. TUIGroupService.handleGroupApplication({
  186. handleAction: action,
  187. application: customApplication.application,
  188. }).then(() => {
  189. customGroupApplicationList.value[index].isRemoved = true;
  190. setTimeout(() => {
  191. customGroupApplicationList.value.splice(index, 1);
  192. groupApplicationCount.value -= 1;
  193. }, 150);
  194. }).catch(() => {
  195. // TODO: handle error
  196. });
  197. }
  198. // --------------- mounted function ---------------
  199. onMounted(() => {
  200. // get current group application number on the first time entering the group
  201. getCurrentGroupApplicationList().then((applicationList) => {
  202. groupApplicationCount.value = applicationList.length;
  203. });
  204. TUIStore.watch(StoreName.GRP, {
  205. groupSystemNoticeList: onGroupSystemNoticeListUpdated,
  206. });
  207. });
  208. onUnmounted(() => {
  209. TUIStore.unwatch(StoreName.GRP, {
  210. groupSystemNoticeList: onGroupSystemNoticeListUpdated,
  211. });
  212. });
  213. function onGroupSystemNoticeListUpdated() {
  214. // Approving or rejecting existing applications will not trigger this callback, but new applications can trigger it.
  215. generateCustomGroupApplicationList().then((list) => {
  216. customGroupApplicationList.value = list;
  217. groupApplicationCount.value = list.length;
  218. });
  219. }
  220. </script>
  221. <style scoped lang="scss">
  222. :not(not) {
  223. display: flex;
  224. flex-direction: column;
  225. box-sizing: border-box;
  226. min-width: 0;
  227. }
  228. .flex-row {
  229. flex-direction: row;
  230. }
  231. .application-tips {
  232. display: flex;
  233. flex-direction: row;
  234. justify-content: center;
  235. width: 100%;
  236. padding: 5px 0;
  237. font-size: 14px;
  238. background-color: #fce4d3;
  239. .application-tips-btn {
  240. color: #006eff;
  241. cursor: pointer;
  242. margin-left: 12px;
  243. }
  244. }
  245. .application-contaienr {
  246. padding: 50px 18px 10px;
  247. background-color: #fff;
  248. height: 100%;
  249. overflow: hidden auto;
  250. font-size: 14px;
  251. .application-header {
  252. position: absolute;
  253. top: 0;
  254. left: 0;
  255. right: 0;
  256. padding: 10px 20px;
  257. flex-direction: row-reverse;
  258. color: #679ce1;
  259. font-size: 14px;
  260. }
  261. .application-item {
  262. display: flex;
  263. flex-direction: row;
  264. align-items: center;
  265. padding: 10px 0;
  266. transition: transform 0.15s ease-out;
  267. & + .application-item {
  268. border-top: 0.5px solid #d0d0d0;
  269. }
  270. .application-item-info {
  271. margin-left: 8px;
  272. margin-right: 8px;
  273. font-size: 14px;
  274. .application-item-nick {
  275. display: block;
  276. overflow: hidden;
  277. text-overflow: ellipsis;
  278. white-space: nowrap;
  279. }
  280. .application-item-note {
  281. color: #989191;
  282. font-size: 12px;
  283. }
  284. }
  285. .application-item-operation {
  286. flex-direction: row;
  287. margin-left: auto;
  288. padding: 8px;
  289. flex: 0 0 auto;
  290. font-size: 14px;
  291. .agree{
  292. color: #679ce1;
  293. cursor: pointer
  294. }
  295. .reject{
  296. margin-left: 12px;
  297. color: #fb355d;
  298. cursor: pointer
  299. }
  300. }
  301. }
  302. .removed {
  303. transform: translateX(-100%);
  304. }
  305. }
  306. </style>