manage.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. <template>
  2. <view class="manage" ref="dialog">
  3. <view class="manage-header">
  4. <view class="manage-header-left">
  5. <view>
  6. <h1>{{ TabName }}</h1>
  7. </view>
  8. </view>
  9. <!-- <i class="icon icon-close" @click="toggleShow"></i> -->
  10. </view>
  11. <view class="main" v-if="!currentTab">
  12. <ManageName
  13. class="space-top"
  14. :isAuth="isAuth"
  15. :data="conversation.groupProfile"
  16. @update="updateProfile"
  17. />
  18. <view class="userInfo space-top">
  19. <view class="userInfo-header" @click="setTab('member')">
  20. <view>{{ 群成员 }}</view>
  21. <view style="display: flex">
  22. <view
  23. >{{ conversation.groupProfile?.memberCount?.memberCount }}人</view
  24. >
  25. <image
  26. class="image-right"
  27. src="../../../assets/icon/right-arrow.svg"
  28. ></image>
  29. </view>
  30. </view>
  31. <view style="display: flex">
  32. <view
  33. v-for="(item, index) in userInfo?.list?.slice(0, showUserNum)"
  34. :key="index"
  35. >
  36. <view style="padding: 10px">
  37. <image
  38. class="avatar"
  39. :src="
  40. item?.avatar ||
  41. 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
  42. "
  43. onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
  44. ></image>
  45. </view>
  46. <view class="text-ellipsis">{{ item?.nick || item?.userID }}</view>
  47. </view>
  48. <view style="padding: 10px" v-if="isShowAddMember">
  49. <view class="avatar" @click="handleOperateMember('add')">+</view>
  50. </view>
  51. <view
  52. style="padding: 10px"
  53. v-if="conversation.groupProfile.selfInfo.role === 'Owner'"
  54. >
  55. <view class="avatar" @click="handleOperateMember('remove')">-</view>
  56. </view>
  57. </view>
  58. </view>
  59. <view class="content space-top" @click="editLableName = ''">
  60. <!-- <view class="item-li" @click.stop="setTab('notification')">
  61. <view>
  62. <view> 群公告</view>
  63. <view class="notification">{{ conversation.groupProfile.notification }}</view>
  64. </view>
  65. <image class="image-right" src="/pages/TUIKit/assets/icon/right-arrow.svg"></image>
  66. </view> -->
  67. <view class="item-li" v-if="isAdmin" @click.stop="setTab('admin')">
  68. <view>群管理</view>
  69. <image
  70. class="image-right"
  71. src="../../../assets/icon/right-arrow.svg"
  72. ></image>
  73. </view>
  74. <view class="item-li">
  75. <view>群ID</view>
  76. <view>{{ conversation.groupProfile.groupID }}</view>
  77. </view>
  78. <view class="item-li">
  79. <view>群头像</view>
  80. <image
  81. class="avatar"
  82. :src="
  83. conversation?.groupProfile?.avatar ||
  84. 'https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/im/demo/TUIkit/web/img/constomer.svg'
  85. "
  86. onerror="this.src='https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/im/demo/TUIkit/web/img/constomer.svg'"
  87. >
  88. </image>
  89. </view>
  90. <view class="item-li">
  91. <view>群类型</view>
  92. <view>{{ typeName[conversation.groupProfile.type] }}</view>
  93. </view>
  94. <view class="item-li">
  95. <view>加群方式</view>
  96. <view>{{ typeName[conversation.groupProfile.joinOption] }}</view>
  97. </view>
  98. </view>
  99. <view class="footer space-top">
  100. <view
  101. class="group-btn"
  102. v-if="
  103. conversation.groupProfile.selfInfo.role === 'Owner' &&
  104. userInfo?.list.length > 1
  105. "
  106. >
  107. 转让群组
  108. </view>
  109. <view
  110. class="group-btn"
  111. v-if="!!isDismissGroupAuth"
  112. @click.stop="dismiss(conversation.groupProfile)"
  113. >
  114. 解散群聊
  115. </view>
  116. <view
  117. class="group-btn"
  118. v-else
  119. @click.stop="quit(conversation.groupProfile)"
  120. >
  121. 退出群组
  122. </view>
  123. </view>
  124. </view>
  125. <ManageMember
  126. v-else-if="currentTab === 'member'"
  127. :self="conversation.groupProfile.selfInfo"
  128. :list="userInfo.list"
  129. :total="conversation.groupProfile.memberCount?.memberCount"
  130. :isShowDel="conversation.groupProfile.selfInfo.role === 'Owner'"
  131. @more="getMember('more')"
  132. @del="submit"
  133. />
  134. <ManageNotification
  135. v-else-if="currentTab === 'notification'"
  136. :isAuth="isAuth"
  137. :data="conversation.groupProfile"
  138. @update="updateProfile"
  139. />
  140. <main class="admin" v-else-if="currentTab === 'admin'">
  141. <view class="admin-list" v-if="isAdmin">
  142. <label>群管理员</label>
  143. <view>
  144. <view v-for="(item, index) in member.admin" :key="index">
  145. <view>
  146. <img
  147. class="avatar"
  148. :src="
  149. item?.avatar ||
  150. 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
  151. "
  152. onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
  153. />
  154. </view>
  155. <view>{{ item?.nick || item?.userID }}</view>
  156. </view>
  157. </view>
  158. </view>
  159. </main>
  160. </view>
  161. </template>
  162. <script lang="ts">
  163. import {
  164. defineComponent,
  165. watchEffect,
  166. reactive,
  167. toRefs,
  168. computed,
  169. watch,
  170. ref,
  171. } from "vue";
  172. import ManageName from "./manage-name.vue";
  173. import ManageNotification from "./manage-notification.vue";
  174. import ManageMember from "./manage-member.vue";
  175. import Vuex from "vuex";
  176. import { onBackPress } from "@dcloudio/uni-app";
  177. const TUIGroupManage = defineComponent({
  178. components: {
  179. ManageName,
  180. ManageNotification,
  181. ManageMember,
  182. },
  183. props: {
  184. userInfo: {
  185. type: Object,
  186. default: () => ({
  187. isGroup: false,
  188. list: [],
  189. }),
  190. },
  191. conversation: {
  192. type: Object,
  193. default: () => ({}),
  194. },
  195. },
  196. setup(props: any, ctx: any) {
  197. const types: any = uni.$TIM.TYPES;
  198. const TUIGroupServer: any = uni.$TUIKit.TUIGroupServer;
  199. const data: any = reactive({
  200. conversation: {},
  201. userInfo: {
  202. isGroup: false,
  203. list: [],
  204. },
  205. isShowMuteTimeInput: false,
  206. editLableName: "",
  207. currentTab: "",
  208. transferType: "",
  209. isSearch: true,
  210. isRadio: false,
  211. transferList: [],
  212. selectedList: [],
  213. isMuteTime: false,
  214. show: false,
  215. typeName: {
  216. [types.GRP_WORK]: "好友工作群",
  217. [types.GRP_PUBLIC]: "陌生人社交群",
  218. [types.GRP_MEETING]: "临时会议群",
  219. [types.GRP_AVCHATROOM]: "直播群",
  220. [types.JOIN_OPTIONS_FREE_ACCESS]: "自由加入",
  221. [types.JOIN_OPTIONS_NEED_PERMISSION]: "需要验证",
  222. [types.JOIN_OPTIONS_DISABLE_APPLY]: "禁止加群",
  223. },
  224. delDialogShow: false,
  225. userList: [],
  226. transferTitle: "",
  227. member: {
  228. admin: [],
  229. member: [],
  230. muteMember: [],
  231. },
  232. });
  233. const dialog: any = ref();
  234. watchEffect(() => {
  235. data.conversation = props.conversation;
  236. data.userInfo = props.userInfo;
  237. // data.show = props.show;
  238. });
  239. // 获取vuex 的 store
  240. const VuexStore = (Vuex as any).useStore();
  241. const TabName = computed(() => {
  242. let name = "";
  243. switch (data.currentTab) {
  244. case "notification":
  245. name = "群公告";
  246. break;
  247. case "member":
  248. name = "群成员";
  249. break;
  250. default:
  251. name = "群管理";
  252. break;
  253. }
  254. return name;
  255. });
  256. // 监听用户列表变化
  257. watch(
  258. () => data.userInfo.list,
  259. (newValue: any, oldValue: any) => {
  260. data.member = {
  261. admin: [], // 群管理员列表
  262. member: [], // 群成员列表
  263. muteMember: [], // 已被禁言用户
  264. };
  265. newValue.map((item: any) => {
  266. switch (item?.role) {
  267. case types.GRP_MBR_ROLE_ADMIN:
  268. data.member.admin.push(item);
  269. break;
  270. case types.GRP_MBR_ROLE_MEMBER:
  271. data.member.member.push(item);
  272. break;
  273. default:
  274. break;
  275. }
  276. return item;
  277. });
  278. const time: number = new Date().getTime();
  279. data.member.muteMember = newValue.filter(
  280. (item: any) => item?.muteUntil * 1000 - time > 0
  281. );
  282. },
  283. {
  284. deep: true,
  285. }
  286. );
  287. // 是否有删除群的权限
  288. const isDismissGroupAuth = computed(() => {
  289. const { conversation } = data;
  290. const userRole = conversation?.groupProfile?.selfInfo.role;
  291. const groupType = conversation?.groupProfile?.type;
  292. const isOwner = userRole === types.GRP_MBR_ROLE_OWNER;
  293. const isWork = groupType === types.GRP_WORK;
  294. return isOwner && !isWork;
  295. });
  296. // 是否显示可以添加群成员
  297. const isShowAddMember = computed(() => {
  298. const { conversation } = data;
  299. const groupType = conversation?.groupProfile?.type;
  300. const isWork = groupType === types.GRP_WORK;
  301. if (isWork) {
  302. return true;
  303. }
  304. return false;
  305. });
  306. // 群成员列表外部显示数量
  307. const showUserNum = computed(() => {
  308. let num = 3;
  309. if (!isShowAddMember.value) {
  310. num += 1;
  311. }
  312. if ((data.conversation as any).groupProfile.selfInfo.role !== "Owner") {
  313. num += 1;
  314. }
  315. return num;
  316. });
  317. // 是否为管理员或群主
  318. const isAuth = computed(() => {
  319. const { conversation } = data;
  320. const userRole = conversation?.groupProfile?.selfInfo.role;
  321. const isOwner = userRole === types.GRP_MBR_ROLE_OWNER;
  322. const isAdmin = userRole === types.GRP_MBR_ROLE_ADMIN;
  323. return isOwner || isAdmin;
  324. });
  325. // 是否可以设置管理
  326. const isAdmin = computed(() => {
  327. const { conversation } = data;
  328. const groupType = conversation?.groupProfile?.type;
  329. const userRole = conversation?.groupProfile?.selfInfo.role;
  330. const isOwner = userRole === types.GRP_MBR_ROLE_OWNER;
  331. const isWork = groupType === types.GRP_WORK;
  332. const isAVChatRoom = groupType === types.GRP_AVCHATROOM;
  333. if (!isWork && !isAVChatRoom && isOwner) {
  334. return true;
  335. }
  336. return false;
  337. });
  338. // 获取群用户列表
  339. const getMember = (type?: string) => {
  340. const { conversation } = data;
  341. const options: any = {
  342. groupID: conversation?.groupProfile?.groupID,
  343. count: 100,
  344. offset: type && type === "more" ? data.userInfo.list.length : 0,
  345. };
  346. uni.$TUIKit.getGroupMemberList(options).then((res: any) => {
  347. if (type && type === "more") {
  348. data.userInfo.list = [...data.userInfo.list, ...res.data.memberList];
  349. } else {
  350. data.userInfo.list = res.data.memberList;
  351. }
  352. });
  353. };
  354. // 退出群
  355. const quit = async (group: any) => {
  356. await uni.$TUIKit.quitGroup(group.groupID);
  357. uni.navigateTo({
  358. url: "../../TUIPages/TUIConversation/index",
  359. });
  360. };
  361. // 解散群
  362. const dismiss = async (group: any) => {
  363. await uni.$TUIKit.dismissGroup(group.groupID);
  364. TUIGroupServer.store.conversation = {};
  365. uni.navigateTo({
  366. url: "../../TUIPages/TUIConversation/index",
  367. });
  368. };
  369. // 打开编辑栏
  370. const edit = (labelName: string) => {
  371. data.editLableName = labelName;
  372. };
  373. // 更新群资料
  374. const updateProfile = async (params: any) => {
  375. const { key, value } = params;
  376. const options: any = {
  377. groupID: data.conversation.groupProfile.groupID,
  378. [key]: value,
  379. };
  380. const res = await uni.$TUIKit.updateGroupProfile(options);
  381. const { conversation } = TUIGroupServer.store;
  382. conversation.groupProfile = res.data.group;
  383. TUIGroupServer.store.conversation = {};
  384. TUIGroupServer.store.conversation = conversation;
  385. data.editLableName = "";
  386. };
  387. // 设置当前tab
  388. const setTab = (tabName: string) => {
  389. data.currentTab = tabName;
  390. data.editLableName = "";
  391. if (data.currentTab === "member") {
  392. data.transferType = "remove";
  393. }
  394. if (!data.currentTab) {
  395. data.transferType = "";
  396. }
  397. };
  398. const submit = (userList: any) => {
  399. if (data.transferType === "remove") {
  400. data.userList = userList;
  401. data.delDialogShow = !data.delDialogShow;
  402. } else {
  403. handleManage(userList, data.transferType);
  404. }
  405. };
  406. const handleManage = (userList: any, type: any) => {
  407. const userIDList: any = [];
  408. userList.map((item: any) => {
  409. userIDList.push(item.userID);
  410. return item;
  411. });
  412. };
  413. // 获取要展示的用户列表
  414. getMember();
  415. // 跳转到群成员列表页面
  416. onBackPress((event: any) => {
  417. if (event.from === "backbutton" && data.currentTab) {
  418. setTab("");
  419. return true;
  420. }
  421. return false;
  422. });
  423. // 群管理:添加、删除成员
  424. const handleOperateMember = (type) => {
  425. if (type) {
  426. uni.navigateTo({
  427. url: `../TUIGroup/memberOperate?type=${type}&groupID=${data.conversation.groupProfile.groupID}`,
  428. });
  429. }
  430. };
  431. return {
  432. ...toRefs(data),
  433. isDismissGroupAuth,
  434. isShowAddMember,
  435. isAdmin,
  436. isAuth,
  437. quit,
  438. dismiss,
  439. edit,
  440. updateProfile,
  441. setTab,
  442. TabName,
  443. getMember,
  444. submit,
  445. handleManage,
  446. showUserNum,
  447. dialog,
  448. handleOperateMember,
  449. };
  450. },
  451. });
  452. export default TUIGroupManage;
  453. </script>
  454. <style lang="scss" scoped>
  455. .manage {
  456. display: flex;
  457. flex-direction: column;
  458. background: #ffffff;
  459. box-sizing: border-box;
  460. // width: 360px;
  461. overflow-y: auto;
  462. box-shadow: 0 1px 10px 0 rgba(2, 16, 43, 0.15);
  463. border-radius: 8px 0 0 8px;
  464. // position: absolute;
  465. right: 0;
  466. height: calc(100% - 40px);
  467. z-index: 2;
  468. top: 40px;
  469. .text-ellipsis {
  470. text-align: center;
  471. max-width: 36px;
  472. overflow: hidden;
  473. text-overflow: ellipsis;
  474. white-space: nowrap;
  475. }
  476. .notification {
  477. opacity: 0.6;
  478. width: 246px;
  479. overflow: hidden;
  480. text-overflow: ellipsis;
  481. white-space: nowrap;
  482. }
  483. .item-li {
  484. padding: 14px 0;
  485. display: flex;
  486. justify-content: space-between;
  487. align-items: center;
  488. font-size: 14px;
  489. }
  490. .footer {
  491. padding: 0 20px;
  492. .group-btn {
  493. cursor: pointer;
  494. width: 100%;
  495. font-weight: 400;
  496. font-size: 14px;
  497. color: #dc2113;
  498. padding: 14px 0;
  499. text-align: center;
  500. border-bottom: 1px solid #e8e8e9;
  501. &:last-child {
  502. border: none;
  503. }
  504. }
  505. }
  506. &-header {
  507. padding: 20px;
  508. display: flex;
  509. justify-content: space-between;
  510. align-items: center;
  511. border-bottom: 1px solid #e8e8e9;
  512. h1 {
  513. font-family: PingFangSC-Medium;
  514. font-weight: 500;
  515. font-size: 16px;
  516. color: #000000;
  517. }
  518. &-left {
  519. display: flex;
  520. .icon {
  521. margin-right: 14px;
  522. }
  523. main {
  524. display: flex;
  525. flex-direction: column;
  526. }
  527. }
  528. }
  529. .main {
  530. .userInfo {
  531. padding: 0 20px;
  532. display: flex;
  533. flex-direction: column;
  534. font-size: 14px;
  535. &-header {
  536. display: flex;
  537. justify-content: space-between;
  538. align-items: center;
  539. padding: 14px 0;
  540. }
  541. &:last-child {
  542. padding-right: 0;
  543. }
  544. .more {
  545. padding-top: 10px;
  546. }
  547. .userInfo-mask {
  548. position: absolute;
  549. z-index: 5;
  550. background: #ffffff;
  551. padding: 20px;
  552. box-shadow: 0 11px 20px 0 rgb(0 0 0 / 30%);
  553. left: 100%;
  554. }
  555. }
  556. }
  557. .content {
  558. padding: 0 20px;
  559. }
  560. .admin {
  561. padding: 20px 0;
  562. &-content {
  563. padding: 20px 20px 12px;
  564. display: flex;
  565. align-items: center;
  566. }
  567. &-list {
  568. padding: 0 20px;
  569. label {
  570. display: inline-block;
  571. font-weight: 400;
  572. font-size: 14px;
  573. color: #000000;
  574. padding-bottom: 8px;
  575. }
  576. }
  577. .last {
  578. padding-top: 13px;
  579. position: relative;
  580. &::before {
  581. position: absolute;
  582. content: "";
  583. width: calc(100% - 40px);
  584. height: 1px;
  585. background: #e8e8e9;
  586. top: 0;
  587. left: 0;
  588. right: 0;
  589. margin: 0 auto;
  590. }
  591. }
  592. }
  593. .image-right {
  594. width: 18px;
  595. height: 20px;
  596. }
  597. .avatar {
  598. width: 36px;
  599. height: 36px;
  600. background: #f4f5f9;
  601. border-radius: 4px;
  602. font-size: 12px;
  603. color: #000000;
  604. display: flex;
  605. justify-content: center;
  606. align-items: center;
  607. }
  608. .space-top {
  609. border-top: 10px solid #f4f5f9;
  610. }
  611. // .btn {
  612. // background: #3370ff;
  613. // border: 0 solid #2f80ed;
  614. // padding: 4px 28px;
  615. // font-weight: 400;
  616. // font-size: 12px;
  617. // color: #ffffff;
  618. // line-height: 24px;
  619. // border-radius: 4px;
  620. // &-cancel {
  621. // background: #ffffff;
  622. // border: 1px solid #dddddd;
  623. // color: #828282;
  624. // }
  625. // }
  626. .slider {
  627. &-box {
  628. display: flex;
  629. align-items: center;
  630. width: 34px;
  631. height: 20px;
  632. border-radius: 10px;
  633. background: #e1e1e3;
  634. }
  635. &-block {
  636. display: inline-block;
  637. width: 16px;
  638. height: 16px;
  639. border-radius: 8px;
  640. margin: 0 2px;
  641. background: #ffffff;
  642. border: 0 solid rgba(0, 0, 0, 0.85);
  643. box-shadow: 0 2px 4px 0 #d1d1d1;
  644. }
  645. }
  646. .space-between {
  647. justify-content: space-between;
  648. }
  649. }
  650. </style>