index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <template>
  2. <view @click="pageClick" class="group_members_container">
  3. <group-member-list-header
  4. ref="navHeaderRef"
  5. :checkVisible.sync="showCheck"
  6. :isTransfer="isTransfer"
  7. :isAt="isAt"
  8. :isSetAdmin="isSetAdmin"
  9. :isMute="isMute"
  10. :isNomal="!isOwner && !isAdmin"
  11. :isCall="type === 'CallInvite'"
  12. :groupID="groupID"
  13. @removeMember="showMemberCheck"/>
  14. <view class="search_bar_wrap">
  15. <u-search
  16. bgColor="#F5F7FA"
  17. :disabled="groupMemberList.length === 0"
  18. @clear="searchOrCancel"
  19. @custom="searchOrCancel"
  20. @search="searchMember"
  21. class="search_bar"
  22. shape="round"
  23. placeholder="搜索成员"
  24. :showAction="false"
  25. :actionText="isSearching ? '取消' : '搜索'"
  26. v-model="keyword"/>
  27. </view>
  28. <view v-if="type === 'ChooseAt'" class="at_all_btn" @click="atAll">所有人</view>
  29. <u-list v-if="!(isSearching && !getRenderData.length)" class="member_list" @scrolltolower="loadMore" lowerThreshold="100" height="1">
  30. <u-list-item v-for="member in getRenderData" :key="member.userID">
  31. <user-item
  32. @itemClick="userClick"
  33. @updateCheck="updateCheck"
  34. :checked="isChecked(member.userID)"
  35. :checkVisible="showCheck"
  36. :disabled="!canCheck(member) && showCheck"
  37. :item="member"
  38. ></user-item>
  39. </u-list-item>
  40. <view v-show="loadState.loading || searchState.loading" class="member_loading">
  41. <u-loading-icon></u-loading-icon>
  42. </view>
  43. </u-list>
  44. <u-empty v-else mode="search" />
  45. <choose-index-footer
  46. v-if="showCheck"
  47. :comfirmLoading="comfirmLoading"
  48. @removeItem="updateCheck"
  49. @confirm="confirm"
  50. :choosedData="getChoosedData"
  51. :isRemove="isRemove"
  52. :canConfirmEmpty="isSetAdmin"
  53. :maxLength="groupMemberLength"/>
  54. <u-modal
  55. :show="showConfirmModal"
  56. asyncClose
  57. showCancelButton
  58. @confirm="modalConfirm"
  59. @cancel="() => (showConfirmModal = false)"
  60. :content="isRemove ? '确定移除已选群成员?' : `确定要把群主转移给${choosedTransferMember.nickname}吗?`"/>
  61. <u-toast ref="uToast"></u-toast>
  62. </view>
  63. </template>
  64. <script>
  65. let moreActionArea;
  66. import { GroupMemberListTypes } from '../../../constant';
  67. import IMSDK, { AllowType, GroupMemberRole } from 'openim-uniapp-polyfill';
  68. import UserItem from '../../../components/UserItem/index.vue';
  69. import GroupMemberListHeader from './components/GroupMemberListHeader.vue';
  70. import ChooseIndexFooter from '../../../components/ChooseIndexFooter/index.vue';
  71. export default {
  72. components: {
  73. GroupMemberListHeader,
  74. ChooseIndexFooter,
  75. UserItem
  76. },
  77. data() {
  78. return {
  79. showCheck: false,
  80. groupID: '',
  81. keyword: '',
  82. isSearching: false,
  83. showConfirmModal: false,
  84. comfirmLoading: false,
  85. groupMemberList: [],
  86. searchMemberList: [],
  87. searchChoosedList: [],
  88. choosedMemberIDList: [],
  89. choosedTransferMember: {},
  90. type: GroupMemberListTypes.Preview,
  91. isRightKick: true,
  92. loadState: {
  93. hasMore: true,
  94. loading: false
  95. },
  96. searchState: {
  97. hasMore: true,
  98. loading: false
  99. },
  100. callMediaType: ''
  101. };
  102. },
  103. computed: {
  104. getRenderData() {
  105. return this.isSearching ? this.searchMemberList : this.groupMemberList;
  106. },
  107. getChoosedData() {
  108. const tmpList = [...this.choosedMemberIDList];
  109. return [...this.groupMemberList, ...this.searchChoosedList].filter((member) => {
  110. const idx = tmpList.findIndex((item) => item === member.userID);
  111. if (idx > -1) {
  112. tmpList.splice(idx, 1);
  113. }
  114. return idx > -1;
  115. });
  116. },
  117. isSetAdmin() {
  118. return this.type === GroupMemberListTypes.SetAdmin;
  119. },
  120. isMute() {
  121. return this.type === GroupMemberListTypes.Mute;
  122. },
  123. isRemove() {
  124. return this.type === GroupMemberListTypes.Kickout;
  125. },
  126. isTransfer() {
  127. return this.type === GroupMemberListTypes.Transfer;
  128. },
  129. isAt() {
  130. return this.type === GroupMemberListTypes.ChooseAt;
  131. },
  132. isChecked() {
  133. return (userID) => this.choosedMemberIDList.includes(userID);
  134. },
  135. isOwner() {
  136. return this.$store.getters.storeCurrentMemberInGroup.roleLevel === GroupMemberRole.Owner;
  137. },
  138. isAdmin() {
  139. return this.$store.getters.storeCurrentMemberInGroup.roleLevel === GroupMemberRole.Admin;
  140. },
  141. canCheck() {
  142. return ({ roleLevel, userID }) => {
  143. if (this.type === GroupMemberListTypes.Mute && roleLevel === GroupMemberRole.Admin && !this.isOwner) {
  144. return false;
  145. }
  146. if (this.type === GroupMemberListTypes.SetAdmin && roleLevel === GroupMemberRole.Owner) {
  147. return false;
  148. }
  149. if (this.type === GroupMemberListTypes.Kickout) {
  150. return (this.isOwner || (this.isAdmin && roleLevel !== GroupMemberRole.Owner)) && userID !== this.$store.getters.storeCurrentUserID;
  151. }
  152. if (this.type === GroupMemberListTypes.ChooseAt) {
  153. return true;
  154. }
  155. return userID !== this.$store.getters.storeCurrentUserID;
  156. };
  157. },
  158. groupMemberLength() {
  159. return this.$store.getters.storeCurrentGroup?.memberCount ?? 0;
  160. }
  161. },
  162. onLoad(options) {
  163. const { groupID, type, callMediaType } = options;
  164. this.type = type;
  165. this.groupID = groupID;
  166. this.loadMemberList(groupID);
  167. this.isRightKick = type === GroupMemberListTypes.Kickout;
  168. if (
  169. this.isRightKick ||
  170. type === GroupMemberListTypes.ChooseAt ||
  171. type === GroupMemberListTypes.CallInvite ||
  172. type === GroupMemberListTypes.SetAdmin ||
  173. type === GroupMemberListTypes.Mute
  174. ) {
  175. this.showCheck = true;
  176. }
  177. if (callMediaType) {
  178. this.callMediaType = callMediaType;
  179. }
  180. this.setIMListener();
  181. },
  182. onUnload() {
  183. this.disposeIMListener();
  184. },
  185. methods: {
  186. async pageClick(e) {
  187. if (!moreActionArea) {
  188. moreActionArea = await this.getEl('.more_container');
  189. }
  190. if (!moreActionArea) return;
  191. if (e.target.y < moreActionArea.top || e.target.y > moreActionArea.bottom || e.target.x < moreActionArea.left) {
  192. this.$refs.navHeaderRef.checkMenu();
  193. }
  194. },
  195. searchOrCancel() {
  196. if (this.isSearching) {
  197. this.isSearching = false;
  198. this.keyword = '';
  199. this.searchState.hasMore = true;
  200. this.searchState.loading = false;
  201. } else {
  202. this.searchMember();
  203. }
  204. },
  205. async searchMember() {
  206. this.isSearching = true;
  207. this.searchMemberList = [];
  208. await this.loadSearchList();
  209. },
  210. confirm() {
  211. console.log("qxj confirm type",this.type);
  212. if (this.type === GroupMemberListTypes.SetAdmin) {
  213. const promiseArray = [];
  214. // 普通成员的roleLevel值,兜底使用20
  215. const normalRoleLevel = GroupMemberRole.Normal || 20;
  216. // Promote selected users
  217. this.getChoosedData.forEach(member => {
  218. if (member.roleLevel !== GroupMemberRole.Admin) {
  219. promiseArray.push(IMSDK.asyncApi(IMSDK.IMMethods.SetGroupMemberInfo, IMSDK.uuid(), {
  220. groupID: member.groupID,
  221. userID: member.userID,
  222. roleLevel: GroupMemberRole.Admin
  223. }));
  224. }
  225. });
  226. // Demote unselected admins (from loaded list)
  227. this.groupMemberList.forEach(member => {
  228. if (member.roleLevel === GroupMemberRole.Admin && !this.choosedMemberIDList.includes(member.userID)) {
  229. promiseArray.push(IMSDK.asyncApi(IMSDK.IMMethods.SetGroupMemberInfo, IMSDK.uuid(), {
  230. groupID: member.groupID,
  231. userID: member.userID,
  232. roleLevel: normalRoleLevel
  233. }));
  234. }
  235. });
  236. Promise.all(promiseArray)
  237. .then(() => {
  238. this.$refs.uToast.show({
  239. type: 'success',
  240. message: '设置成功',
  241. complete() {
  242. uni.navigateBack();
  243. }
  244. });
  245. })
  246. .catch((err) => {
  247. console.error('SetAdmin failed', err);
  248. this.$refs.uToast.show({
  249. type: 'error',
  250. message: '操作失败'
  251. });
  252. });
  253. return;
  254. }
  255. if (this.type === GroupMemberListTypes.Mute) {
  256. uni.navigateTo({
  257. url: `/pages_im/pages/common/setMemberMute/index?sourceInfo=${JSON.stringify(this.getChoosedData)}&fromList=1`
  258. });
  259. return;
  260. }
  261. if (this.type === GroupMemberListTypes.ChooseAt || this.type === GroupMemberListTypes.CallInvite) {
  262. let pages = getCurrentPages();
  263. let prevPage = pages[pages.length - 2];
  264. if (this.type === GroupMemberListTypes.ChooseAt) {
  265. prevPage.$vm.getCheckedUsers(this.getChoosedData);
  266. } else {
  267. prevPage.$vm.sendRtcInvite(
  268. this.callMediaType,
  269. this.getChoosedData.map((item) => item.userID)
  270. );
  271. }
  272. uni.navigateBack({
  273. delta: 1
  274. });
  275. return;
  276. }
  277. this.showConfirmModal = true;
  278. },
  279. atAll() {
  280. let pages = getCurrentPages();
  281. let prevPage = pages[pages.length - 2];
  282. prevPage.$vm.getCheckedUsers([
  283. ...this.getChoosedData,
  284. {
  285. userID: 'AtAllTag',
  286. nickname: '所有人'
  287. }
  288. ]);
  289. uni.navigateBack({
  290. delta: 1
  291. });
  292. },
  293. modalConfirm(delUser) {
  294. let func = () => {};
  295. if (this.type === GroupMemberListTypes.Kickout || delUser) {
  296. func = IMSDK.asyncApi(IMSDK.IMMethods.KickGroupMember, IMSDK.uuid(), {
  297. groupID: this.groupID || this.getChoosedData[0].groupID,
  298. reason: '',
  299. userIDList: delUser || this.getChoosedData.map((member) => member.userID)
  300. });
  301. } else {
  302. func = IMSDK.asyncApi(IMSDK.IMMethods.TransferGroupOwner, IMSDK.uuid(), {
  303. groupID: this.choosedTransferMember.groupID,
  304. newOwnerUserID: this.choosedTransferMember.userID
  305. });
  306. }
  307. func.then(() => this.showToast('操作成功', () => uni.navigateBack()))
  308. .catch(() => this.showToast('操作失败'))
  309. .finally(() => (this.showConfirmModal = false));
  310. },
  311. updateChoosedData(userID) {
  312. if (this.choosedMemberIDList.includes(userID)) {
  313. const idx = this.choosedMemberIDList.findIndex((item) => item === userID);
  314. const tmpArr = [...this.choosedMemberIDList];
  315. tmpArr.splice(idx, 1);
  316. this.choosedMemberIDList = tmpArr;
  317. } else {
  318. this.choosedMemberIDList = [...this.choosedMemberIDList, userID];
  319. }
  320. console.log(this.choosedMemberIDList);
  321. },
  322. userClick(member) {
  323. if (this.type === GroupMemberListTypes.Transfer) {
  324. if (member.userID === this.$store.getters.storeCurrentUserID) return;
  325. this.choosedTransferMember = member;
  326. this.showConfirmModal = true;
  327. } else {
  328. if (this.$store.getters.storeCurrentGroup.lookMemberInfo === AllowType.NotAllowed) {
  329. return;
  330. }
  331. uni.$u.route('/pages/common/userCard/index', {
  332. sourceID: member.userID,
  333. memberInfo: JSON.stringify(member),
  334. disableAdd: this.$store.getters.storeCurrentGroup.applyMemberFriend === AllowType.NotAllowed
  335. });
  336. }
  337. },
  338. updateCheck(member) {
  339. if (this.isSearching && !this.choosedMemberIDList.includes(member.userID)) {
  340. this.searchChoosedList = [...this.searchChoosedList, member];
  341. }
  342. this.updateChoosedData(member.userID);
  343. },
  344. showMemberCheck() {
  345. this.type = GroupMemberListTypes.Kickout;
  346. this.showCheck = true;
  347. },
  348. loadMore() {
  349. const stateKey = !this.isSearching ? 'loadState' : 'searchState';
  350. const methodKey = !this.isSearching ? 'loadMemberList' : 'loadSearchList';
  351. if (this[stateKey].hasMore && !this[stateKey].loading) {
  352. this[methodKey]();
  353. }
  354. },
  355. //加载群成员列表
  356. loadMemberList(groupID, isReload = false) {
  357. this.loadState.loading = true;
  358. IMSDK.asyncApi(IMSDK.IMMethods.GetGroupMemberList, IMSDK.uuid(), {
  359. groupID: groupID || this.groupID,
  360. filter: 0,
  361. offset: isReload ? 0 : this.groupMemberList.length,
  362. count: 50
  363. }).then(({ data }) => {
  364. this.groupMemberList = isReload ? [...data] : [...this.groupMemberList, ...data];
  365. if (this.type === GroupMemberListTypes.SetAdmin) {
  366. data.forEach(member => {
  367. if (member.roleLevel === GroupMemberRole.Admin && !this.choosedMemberIDList.includes(member.userID)) {
  368. this.choosedMemberIDList = [...this.choosedMemberIDList, member.userID];
  369. }
  370. });
  371. }
  372. this.loadState.hasMore = data.length === 50;
  373. }).catch((err) => console.log(err))
  374. .finally(() => (this.loadState.loading = false));
  375. },
  376. async loadSearchList() {
  377. this.searchState.loading = true;
  378. const options = {
  379. groupID: this.groupID,
  380. keywordList: [this.keyword],
  381. isSearchUserID: true,
  382. isSearchMemberNickname: true,
  383. offset: this.searchMemberList.length,
  384. count: 20
  385. };
  386. try {
  387. const { data } = await IMSDK.asyncApi(IMSDK.IMMethods.SearchGroupMembers, IMSDK.uuid(), options);
  388. console.log(data);
  389. this.searchMemberList = [...this.searchMemberList, ...data];
  390. this.searchState.hasMore = data.length === 20;
  391. } catch (e) {}
  392. this.searchState.loading = false;
  393. },
  394. showToast(message, complete = null) {
  395. this.$refs.uToast.show({
  396. message,
  397. complete
  398. });
  399. },
  400. getEl(el) {
  401. return new Promise((resolve) => {
  402. const query = uni.createSelectorQuery().in(this);
  403. query.select(el).boundingClientRect((data) => {
  404. // 存在data,且存在宽和高,视为渲染完毕
  405. resolve(data);
  406. }).exec();
  407. });
  408. },
  409. groupMemberInfoChangeHandler({ data }) {
  410. if (data.groupID === this.groupMemberList[0]?.groupID) {
  411. const idx = this.groupMemberList.findIndex((member) => member.userID === data.userID);
  412. if (idx > -1) {
  413. this.loadMemberList(data.groupID, true);
  414. // const tmpArr = [...this.groupMemberList];
  415. // tmpArr[idx] = {
  416. // ...data,
  417. // };
  418. // this.groupMemberList = [...tmpArr];
  419. }
  420. }
  421. },
  422. groupMemberAddedHandler({ data }) {
  423. if (data.groupID === this.groupMemberList[0]?.groupID && this.groupMemberList.length < 20) {
  424. const idx = this.groupMemberList.findIndex((member) => member.userID === data.userID);
  425. if (idx === -1) {
  426. this.groupMemberList = [...this.groupMemberList, data];
  427. }
  428. }
  429. },
  430. groupMemberDeletedHandler({ data }) {
  431. if (data.groupID === this.groupMemberList[0]?.groupID) {
  432. const idx = this.groupMemberList.findIndex((member) => member.userID === data.userID);
  433. if (idx !== -1) {
  434. const tmpArr = [...this.groupMemberList];
  435. tmpArr.splice(idx, 1);
  436. this.groupMemberList = [...tmpArr];
  437. }
  438. }
  439. },
  440. setIMListener() {
  441. uni.$on(IMSDK.IMEvents.OnGroupMemberInfoChanged, this.groupMemberInfoChangeHandler);
  442. IMSDK.subscribe(IMSDK.IMEvents.OnGroupMemberAdded, this.groupMemberAddedHandler);
  443. IMSDK.subscribe(IMSDK.IMEvents.OnGroupMemberDeleted, this.groupMemberDeletedHandler);
  444. },
  445. disposeIMListener() {
  446. uni.$off(IMSDK.IMEvents.OnGroupMemberInfoChanged, this.groupMemberInfoChangeHandler);
  447. IMSDK.unsubscribe(IMSDK.IMEvents.OnGroupMemberAdded, this.groupMemberAddedHandler);
  448. IMSDK.unsubscribe(IMSDK.IMEvents.OnGroupMemberDeleted, this.groupMemberDeletedHandler);
  449. }
  450. },
  451. onBackPress(options) {
  452. if (this.showCheck && this.isRightKick) {
  453. this.showCheck = false;
  454. this.type = GroupMemberListTypes.Preview;
  455. return true;
  456. }
  457. return false;
  458. }
  459. };
  460. </script>
  461. <style lang="scss" scoped>
  462. .group_members_container {
  463. @include colBox(false);
  464. height: 100vh;
  465. overflow: hidden;
  466. .search_bar_wrap {
  467. padding: 8px 24rpx;
  468. background: #fff;
  469. }
  470. .user_del {
  471. height: 60rpx;
  472. border-radius: 60rpx;
  473. font-size: 28rpx;
  474. border: 1px solid #dcdee0;
  475. color: #757575;
  476. width: 104rpx;
  477. text-align: center;
  478. line-height: 60rpx;
  479. }
  480. .at_all_btn {
  481. font-weight: 500;
  482. padding: 24rpx 44rpx;
  483. }
  484. /deep/.u-popup {
  485. flex: none;
  486. }
  487. .member_list {
  488. flex: 1;
  489. }
  490. }
  491. </style>