invite-list.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <template>
  2. <div class="content">
  3. <div class="horizontal-invited-wrapper" v-if="state.invited_list.length">
  4. invited({{state.inviteCount}})
  5. <div class="horizontal-invited-list" @mouseleave="invitedListMouseleave($event)">
  6. <template v-for="(item, index) in state.invited_list" :key="index" >
  7. <div class="invited-item" v-if="index < 9" @mouseenter="invitedItemMouseenter(item)">
  8. <img :src="item.userInfo.avatarUrl" />
  9. </div>
  10. </template>
  11. </div>
  12. <img class="more" v-if="state.invited_list.length > 9" :src="require('@/assets/svg/icon-invited-more.svg')" @mouseenter="moreMouseenter" @mouseleave="moreMouseleave" />
  13. </div>
  14. <div class="invited-user-info" @mouseenter="invitedItemMouseenter()"
  15. @mouseleave="invitedListMouseleave($event)" v-if="hoverInvitedUserInfo.userInfo">
  16. <div class="left">
  17. <img class="avatar" :src="hoverInvitedUserInfo.userInfo.avatarUrl" />
  18. </div>
  19. <div class="right">
  20. <div class="user-info">
  21. <div class="name">
  22. {{ hoverInvitedUserInfo.userInfo.nickName }}
  23. </div>
  24. <div class="time">
  25. {{ getTime(hoverInvitedUserInfo.timestamp) }}
  26. </div>
  27. </div>
  28. <span class="channel">
  29. <img class="app-icon" :src="hoverInvitedUserInfo.userInfo.avatarUrl" />
  30. WhatsApp
  31. </span>
  32. </div>
  33. </div>
  34. <div class="vertical-invited-wrapper" v-if="showVerticalInvitedList" @mouseenter="moreMouseenter" @mouseleave="moreMouseleave" @scroll="handleScroll($event)">
  35. <div class="invited-user-info" v-for="(item, index) in state.invited_list" :key="index">
  36. <div class="left">
  37. <img @click="clickItem(item)" class="avatar" :src="item.userInfo.avatarUrl" />
  38. </div>
  39. <div class="right">
  40. <div class="user-info">
  41. <div class="name">
  42. {{ item.userInfo.nickName }}
  43. </div>
  44. <div class="time">
  45. {{ getTime(item.timestamp) }}
  46. </div>
  47. </div>
  48. <span class="channel">
  49. <img class="app-icon" :src="item.userInfo.avatarUrl" />
  50. WhatsApp
  51. </span>
  52. </div>
  53. </div>
  54. </div>
  55. <!-- <div class="list" @scroll="handleScroll($event)">
  56. <div class="item" v-for="item in state.invited_list" :key="item.userInfo.uid">
  57. <div class="left">
  58. <img :src="item.userInfo.avatarUrl" alt="" @click="clickItem(item)" />
  59. </div>
  60. <div class="right">
  61. <div>{{ item.userInfo.nickName }}</div>
  62. <div>{{ getTime(item.timestamp) }}</div>
  63. </div>
  64. </div>
  65. </div> -->
  66. <!-- <div class="footer">
  67. <v-btn :txt="state.open_btn.txt" :font-size="'17px'" class="btn" :icon="false" :loading="state.btn_loading"
  68. :disabled="state.open_btn.disabled" v-click-log="state.log_invite_btn_click" @onClick="clickBtn"
  69. font-weight="600"></v-btn>
  70. </div>-->
  71. </div>
  72. </template>
  73. <script setup>
  74. import VBtn from '@/view/iframe/treasure-hunt/components/btn.vue'
  75. import { inviteList, inviteListRefresh } from '@/http/treasure'
  76. import { inject, onMounted, ref } from 'vue'
  77. import Report from "@/log-center/log"
  78. var moment = require('moment')
  79. let state = inject('state')
  80. state.invited_list = []
  81. let page_num = 1
  82. let page_size = 100
  83. let list_end = false
  84. let hoverInvitedUserInfo = ref({});
  85. let showVerticalInvitedList = ref(false);
  86. let timer = null;
  87. let timer1 = null;
  88. state.log_invite_btn_click = {
  89. businessType: Report.businessType.buttonClick,
  90. pageSource: Report.pageSource.inviteFriendsPage,
  91. objectType: Report.objectType.openChestButton,
  92. redPacketType: Report.redPacketType.treasure,
  93. shareLinkId: state.invite_code,
  94. myShareLinkId: state.detail.inviteCopyUrl,
  95. currentInvitedNum: state.inviteCount,
  96. postId: state.postId
  97. }
  98. state.log_invite_list_show = {
  99. businessType: Report.businessType.pageView,
  100. pageSource: Report.pageSource.beenInvitedPage,
  101. redPacketType: Report.redPacketType.treasure,
  102. shareLinkId: state.invite_code,
  103. myShareLinkId: state.detail.inviteCopyUrl,
  104. currentInvitedNum: state.inviteCount,
  105. postId: state.postId
  106. }
  107. onMounted(() => {
  108. state.btn_loading = false
  109. list()
  110. })
  111. const invitedItemMouseenter = (params) => {
  112. if(timer) clearTimeout(timer)
  113. if(params) {
  114. hoverInvitedUserInfo.value = params;
  115. }
  116. }
  117. const invitedListMouseleave = (params) => {
  118. timer = setTimeout(function(){
  119. hoverInvitedUserInfo.value = {};
  120. },600);
  121. }
  122. const moreMouseenter = () => {
  123. if(timer1) clearTimeout(timer1)
  124. showVerticalInvitedList.value = true;
  125. }
  126. const moreMouseleave = () => {
  127. timer1 = setTimeout(function(){
  128. showVerticalInvitedList.value = false;
  129. },600);
  130. }
  131. const clickItem = (item) => {
  132. window.open(`https://twitter.com/${item.userInfo.nickName}`)
  133. }
  134. function handleScroll(e) {
  135. if (list_end) {
  136. return
  137. }
  138. e = e.target
  139. if ((e.clientHeight + e.scrollTop) / e.scrollHeight > .8) {
  140. list_end = true
  141. inviteListScroll()
  142. }
  143. }
  144. const list = () => {
  145. state.inviteListRefresh()
  146. }
  147. // 刷新时调用
  148. state.inviteListRefresh = () => {
  149. let last_timestamp = 0
  150. if (state.invited_list.length > 0) {
  151. last_timestamp = state.invited_list[0].timestamp
  152. }
  153. inviteListRefresh({
  154. params: {
  155. postId: state.postId,
  156. lastTimestamp: last_timestamp,
  157. }
  158. }).then((res) => {
  159. if (res.code == 0) {
  160. handleCommon(res.data)
  161. }
  162. })
  163. }
  164. const handleCommon = (data) => {
  165. state.inviteCount = data.inviteCount
  166. // if (state.inviteCount > 0) {
  167. // state.tabs[1].txt = `invited(${state.inviteCount})`
  168. // }
  169. if (data.inviteUsers.length > 0) {
  170. data.inviteUsers.forEach(item => {
  171. if (state.invited_list.filter((item2) => { return item2.userInfo.uid == item.userInfo.uid }).length == 0) {
  172. state.invited_list.push(item)
  173. }
  174. })
  175. state.invited_list = state.invited_list.sort((a, b) => {
  176. return b.timestamp - a.timestamp
  177. })
  178. list_end = false
  179. } else {
  180. list_end = false
  181. }
  182. }
  183. // 滚动
  184. let inviteListScroll = () => {
  185. // state.invited_list
  186. let last_timestamp = 0
  187. let len = state.invited_list.length
  188. if (len > 0) {
  189. last_timestamp = state.invited_list[len - 1].timestamp
  190. }
  191. inviteList({
  192. params: {
  193. inviteCode: state.invite_code,
  194. postId: state.postId,
  195. lastTimestamp: last_timestamp,
  196. pageSize: page_size
  197. }
  198. }).then((res) => {
  199. if (res.code == 0) {
  200. handleCommon(res.data)
  201. }
  202. })
  203. }
  204. const getTime = (timestamp) => {
  205. let _d1 = moment(new Date().getTime())
  206. let _d2 = moment(timestamp)
  207. const plural = (n, s) => {
  208. let _str = `${n} ${s} ago`
  209. if (n > 1) {
  210. _str = `${n} ${s}s ago`
  211. }
  212. return _str
  213. }
  214. let _d = moment.duration(_d1.diff(_d2)).days()
  215. if (_d) {
  216. return plural(_d, 'day')
  217. }
  218. let _h = moment.duration(_d1.diff(_d2)).hours()
  219. if (_h) {
  220. return plural(_h, 'hour')
  221. }
  222. let _m = moment.duration(_d1.diff(_d2)).minutes()
  223. if (_m) {
  224. return plural(_m, 'min')
  225. }
  226. let _s = moment.duration(_d1.diff(_d2)).seconds()
  227. return plural(_s, 'sec')
  228. }
  229. async function clickBtn() {
  230. let _userInfo = await state.checkIsLogin()
  231. if (!_userInfo) {
  232. return
  233. }
  234. state.btn_loading = true
  235. state.treasureOpen()
  236. }
  237. </script>
  238. <style lang="scss" scoped>
  239. .content {
  240. position: absolute;
  241. bottom: 0px;
  242. width: 100%;
  243. .horizontal-invited-wrapper {
  244. width: 100%;
  245. padding: 12px 14px;
  246. box-sizing: border-box;
  247. display: flex;
  248. align-items: center;
  249. font-weight: 400;
  250. font-size: 12px;
  251. color: #A7A39F;
  252. .horizontal-invited-list {
  253. display: flex;
  254. align-items: center;
  255. margin-left: 5px;
  256. cursor: pointer;
  257. .invited-item {
  258. width: 16px;
  259. height: 16px;
  260. padding: 0 5px;
  261. img {
  262. width: 16px;
  263. height: 16px;
  264. border-radius: 50%;
  265. }
  266. }
  267. }
  268. .more {
  269. margin-left: 5px;
  270. cursor: pointer;
  271. }
  272. }
  273. .vertical-invited-wrapper {
  274. width: 343px;
  275. height: 308px;
  276. background: #FFFFFF;
  277. box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.25);
  278. border-radius: 16px;
  279. overflow-y: scroll;
  280. position: absolute;
  281. bottom: -316px;
  282. left: 16px;
  283. animation: fade-in 0.25s linear forwards;
  284. .invited-user-info {
  285. position: static !important;
  286. border-radius: 0px !important;
  287. box-shadow: none !important;
  288. padding: 0 !important;
  289. animation: none !important;
  290. .left, .right {
  291. padding: 9px 0;
  292. }
  293. .left {
  294. padding-left: 16px;
  295. }
  296. .right {
  297. padding-right: 16px;
  298. box-shadow: inset 0px -1px 0px #F2F2F2;
  299. box-sizing: border-box;
  300. }
  301. }
  302. }
  303. .invited-user-info {
  304. width: 343px;
  305. padding: 9px 16px;
  306. box-sizing: border-box;
  307. background: #FFFFFF;
  308. box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.25);
  309. border-radius: 16px;
  310. position: absolute;
  311. bottom: -62px;
  312. left: 16px;
  313. display: flex;
  314. justify-content: space-between;
  315. animation: fade-in 0.25s linear forwards;
  316. .left {
  317. display: flex;
  318. align-items: center;
  319. .avatar {
  320. width: 30px;
  321. height: 30px;
  322. margin-right: 16px;
  323. border-radius: 50%;
  324. }
  325. }
  326. .right {
  327. display: flex;
  328. justify-content: space-between;
  329. width: 100%;
  330. .user-info {
  331. .name {
  332. margin-bottom: 5px;
  333. font-weight: 500;
  334. font-size: 15px;
  335. }
  336. .time {
  337. font-weight: 400;
  338. font-size: 12px;
  339. color: #A9A9A9;
  340. }
  341. }
  342. .channel {
  343. height: min-content;
  344. display: flex;
  345. align-items: center;
  346. font-weight: 400;
  347. font-size: 12px;
  348. color: #A9A9A9;
  349. .app-icon {
  350. width: 14px;
  351. height: 14px;
  352. margin-right: 4px;
  353. border-radius: 50%;
  354. }
  355. }
  356. }
  357. }
  358. .list {
  359. background: #fff;
  360. height: 204px;
  361. overflow-y: auto;
  362. .item {
  363. height: 60px;
  364. display: flex;
  365. align-items: center;
  366. .left {
  367. width: 58px;
  368. text-align: center;
  369. img {
  370. cursor: pointer;
  371. border-radius: 50px;
  372. width: 30px;
  373. height: 30px;
  374. }
  375. }
  376. .right {
  377. flex: 1;
  378. border-bottom: 1px solid #D9D9D9;
  379. display: flex;
  380. align-items: center;
  381. height: 100%;
  382. justify-content: space-between;
  383. div:nth-child(1) {
  384. color: #000000;
  385. font-weight: 500;
  386. font-size: 15px;
  387. cursor: pointer;
  388. }
  389. div:nth-child(2) {
  390. color: #A6A6A6;
  391. font-weight: 400;
  392. font-size: 12px;
  393. margin-right: 17px;
  394. }
  395. }
  396. }
  397. }
  398. @keyframes fade-in {
  399. from {
  400. opacity: 0;
  401. }
  402. to {
  403. opacity: 1;
  404. }
  405. }
  406. }
  407. </style>