소스 검색

[edit] feature 7.2

wenliming 2 년 전
부모
커밋
4eb264e632

+ 2 - 0
package.json

@@ -12,6 +12,7 @@
     "build-watch": "vue-cli-service  --env.NODE_ENV=development build-watch --mode development"
   },
   "dependencies": {
+    "@fingerprintjs/fingerprintjs": "^3.3.5",
     "@sentry/tracing": "^7.5.1",
     "@sentry/vue": "^7.5.1",
     "ant-design-vue": "^2.2.8",
@@ -28,6 +29,7 @@
     "postcss-import": "^14.0.2",
     "postcss-url": "^10.1.3",
     "qrcode": "^1.5.0",
+    "qs": "^6.11.0",
     "sass-loader": "^12.6.0",
     "vue": "^3.2.13",
     "vue-cropper": "^1.0.3",

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 1
src/assets/svg/icon-three-line.svg


+ 5 - 2
src/http/fetch.js

@@ -3,7 +3,7 @@ import { getChromeStorage } from '@/uilts/chromeExtension.js'
 
 export async function commonFetch({ url = '', method = 'POST', params = {}, baseInfo = {} }) {
     try {
-
+        let deviceInfo = await getChromeStorage('deviceInfo') || {};
         let storage_mid = await getChromeStorage('mid').catch((error) => {console.log(error) }) || ''
         const { mid } = storage_mid || {}
         if (!baseInfo.token || !baseInfo.uid) {
@@ -17,6 +17,9 @@ export async function commonFetch({ url = '', method = 'POST', params = {}, base
         baseInfo.appType = 1
         baseInfo.loginUid = baseInfo.uid
 
+        baseInfo.deviceId1 = deviceInfo.deviceId1;
+        baseInfo.deviceId2 = deviceInfo.deviceId2;
+
         return new Promise(function (resolve, reject) {
             let _url = baseAPIUrl + url
             if (url.includes('http')) {
@@ -75,4 +78,4 @@ export async function commonFetch({ url = '', method = 'POST', params = {}, base
     } catch (error) {
         console.log('error', error)
     }
-}
+}

+ 6 - 2
src/http/request.js

@@ -4,6 +4,7 @@ import { baseAPIUrl, appVersionCode } from '@/http/configAPI.js'
 
 let userInfo = '';
 let storage_mid = ''
+let deviceInfo = {};
 
 // 创建axios实例
 export const service = axios.create({
@@ -39,7 +40,8 @@ function checkParams(config) {
         uid,
         appType: 1,
         machineCode: mid,
-        pageSource: pageSource || ''
+        pageSource: pageSource || '',
+        ...deviceInfo
       }
     }
     config['params'] = params;
@@ -64,7 +66,8 @@ function checkParams(config) {
         uid,
         appType: 1,
         machineCode: mid,
-        pageSource: pageSource || ''
+        pageSource: pageSource || '',
+        ...deviceInfo
       }
     }
     config['data'] = data;
@@ -75,6 +78,7 @@ function checkParams(config) {
 // request拦截器
 service.interceptors.request.use(async (config) => {
   userInfo = await getChromeStorage('userInfo') || ''
+  deviceInfo = await getChromeStorage('deviceInfo') || {};
   if (!storage_mid) {
     storage_mid = await getChromeStorage('mid') || ''
   }

+ 2 - 1
src/logic/background/twitter.js

@@ -339,7 +339,8 @@ export function onInstalledCreateTab() {
                 Report.reportLog({
                     objectType: Report.objectType.chrome_extension_installed,
                     funcName: 'onInstalledCreateTab',
-                    postId: res.postId || ''
+                    postId: res.postId || '',
+                    shareLinkId: res.shareLinkId || ''
                 })
             }, 5000)
             let url = 'https://twitter.com/search?q=%23denet'

+ 42 - 2
src/logic/content/help/twitter.js

@@ -1,3 +1,5 @@
+import { getStorage, setStorage, getVisitorId, setCookie, getCookie } from '@/uilts/help'
+import { getChromeStorage, setChromeStorage } from '@/uilts/chromeExtension.js'
 
 // 根据提示dom 跳转到推文详情页面
 export const jumpTwitterDetailByAlert = () => {
@@ -38,6 +40,44 @@ export const showEditTweet = (callback) => {
             clearInterval(timer)
             callback && callback()
         }
-        num-- 
+        num--
     }, 500);
-}
+}
+
+export const setDeviceInfo = async () => {
+  const deviceStorageParams = {
+    name: 'de_net_device_id_1'
+  }
+
+  const deviceCookieParams = {
+    url: 'https://twitter.com/',
+    name: 'de_net_device_id_2'
+  }
+
+  let deviceInfo = {
+    deviceId1: '',
+    deviceId2: ''
+  }
+
+  let storageDeviceInfo = getStorage(deviceStorageParams.name);
+  if(!storageDeviceInfo) {
+    let res = await getVisitorId();
+    let id =  res && res.visitorId || '';
+    setStorage(deviceStorageParams.name, id);
+    deviceInfo.deviceId1 = id;
+  } else {
+    deviceInfo.deviceId1 = storageDeviceInfo;
+  }
+
+  let cookieDeviceInfo = getCookie(deviceCookieParams.name);
+  if(!cookieDeviceInfo) {
+    let res = await getVisitorId();
+    let id = res && res.visitorId || '';
+    setCookie(deviceCookieParams.name, id, 600);
+    deviceInfo.deviceId2 = id;
+  } else {
+    deviceInfo.deviceId2 = cookieDeviceInfo;
+  }
+
+  setChromeStorage({'deviceInfo': JSON.stringify(deviceInfo)});
+}

+ 48 - 2
src/logic/content/twitter.js

@@ -12,7 +12,9 @@ import { toolBox } from '@/logic/content/ToolBox'
 import axios from 'axios';
 import messageCenter from '@/uilts/messageCenter';
 import { PlayType } from '@/types';
-import { reSetBindPostContent } from '@/http/help.js'
+import { reSetBindPostContent } from '@/http/help.js';
+import { setDeviceInfo } from '@/logic/content/help/twitter';
+import qs from 'qs';
 
 let dom = {};
 
@@ -1030,6 +1032,7 @@ export function init() {
         addJoinedGroupList();
         getSysTheme();
         addGroupTab();
+        setDeviceInfo();
         // 预加载全屏 toobbox
         toolBox.initFull()
     }
@@ -1424,7 +1427,8 @@ export function doTaskTwitterAPI({ task_data, task_type, tasks, iframeId }) {
                     case '1':
                         item.relatedUsers.forEach((item) => {
                             if (item.name && item.twitterUserId) {
-                                TwitterFollowAPI(item, task_data.tweet_Id)
+                                TwitterFollowAPI(item, task_data.tweet_Id);
+                                TwitterFriendshipsUpdate({id: task_data.tweet_Id})
                             }
                         })
                         break
@@ -1452,6 +1456,48 @@ export function showJoinDialog(data) {
     iframe.src = chromeExtensionUrl + (`iframe/buy-nft.html#/group?params=${JSON.stringify(data)}&time=${new Date().getTime()}`)
 }
 
+const TwitterFriendshipsUpdate = (params) => {
+  let {id = '', device = true} = params || {};
+  let data = {
+    include_profile_interstitial_type: 1,
+    include_blocking: 1,
+    include_blocked_by: 1,
+    include_followed_by: 1,
+    include_want_retweets: 1,
+    include_mute_edge: 1,
+    include_can_dm: 1,
+    include_can_media_tag: 1,
+    include_ext_has_nft_avatar: 1,
+    skip_status: 1,
+    cursor: -1,
+    id,
+    device
+  }
+  if(!id) {
+    return;
+  }
+  return axios.post(`https://twitter.com/i/api/1.1/friendships/update.json`,
+    qs.stringify(data), {
+    headers: {
+        "accept": "*/*",
+        "accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
+        "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
+        "content-type": "application/x-www-form-urlencoded",
+        "sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Google Chrome\";v=\"101\"",
+        "sec-ch-ua-mobile": "?0",
+        "sec-ch-ua-platform": "\"Windows\"",
+        "sec-fetch-dest": "empty",
+        "sec-fetch-mode": "cors",
+        "sec-fetch-site": "same-origin",
+        "x-csrf-token": getCookie('ct0'),
+        "x-twitter-active-user": "yes",
+        "x-twitter-auth-type": "OAuth2Session",
+        "x-twitter-client-language": "en",
+        "referer": "https://twitter.com"
+    },
+  })
+}
+
 const TwitterFollowAPI = (item, tweet_Id) => {
     fetch("https://twitter.com/i/api/1.1/friendships/create.json", {
         "headers": {

+ 17 - 0
src/uilts/help.js

@@ -1,4 +1,5 @@
 import { appVersionCode } from '@/http/configAPI.js'
+import FingerprintJS from '@fingerprintjs/fingerprintjs'
 
 export function getQueryString(name) {
   let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
@@ -112,6 +113,14 @@ export function getTargetElementWhenClick(e) {
   return result
 }
 
+export function setCookie(cname,cvalue,exdays)
+{
+  var d = new Date();
+  d.setTime(d.getTime()+(exdays*24*60*60*1000));
+  var expires = "expires="+d.toGMTString();
+  document.cookie = cname + "=" + cvalue + "; " + expires;
+}
+
 export function getCookie(name) {
   var strcookie = document.cookie; //获取cookie字符串
   var arrcookie = strcookie.split("; "); //分割
@@ -278,3 +287,11 @@ export const getInnerIframeURL = (url) => {
   }
   return iframeUrl;
 }
+
+export const getVisitorId = async () => {
+  const fpPromise = FingerprintJS.load();
+  let result = {};
+  const fp = await fpPromise
+  result = await fp.get();
+  return result;
+}

+ 18 - 6
src/view/components/custom-card-horizontal-cover.vue

@@ -73,10 +73,12 @@
                         </component-zoom>
                     </div>
                     <div class="treasure-desc-data">
-                        <div class="item">
-                            <img class="icon"
-                                :src="require('@/assets/svg/icon-preview-trophy.svg')" />
-                            {{data.totalCount}} Winners
+                        <div class="item up-gain">
+                            You can get up to $
+                            <component-zoom :width="160"
+                            fontSize="24" :txt="data.upGainAmountUsdValue || 0">
+                              <div class="amount">{{data.upGainAmountUsdValue || 0}}</div>
+                            </component-zoom>
                         </div>
                         <div class="item">
                             <img class="icon" :src="data.currencyIconUrl">
@@ -215,7 +217,7 @@ watch(() => props.data, () => {
     .content-text {
         position: absolute;
         top: 53px;
-        left: 35px;
+        left: 0;
         .title {
             font-weight: 800;
             font-size: 16px;
@@ -272,6 +274,7 @@ watch(() => props.data, () => {
                 font-weight: 900;
                 font-size: 35px;
                 color: #fff;
+                margin-left: 35px;
 
                 .left {
                     margin-right: 7px;
@@ -286,7 +289,15 @@ watch(() => props.data, () => {
             }
 
             .treasure-desc-data {
-                margin-top: 18px;
+                margin-top: 12px;
+
+                .up-gain {
+                  background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 15.54%, rgba(255, 255, 255, 0) 100%);
+                  width: 350px;
+                  color: #fff !important;
+                  padding: 4px 0;
+                  box-sizing: border-box;
+                }
 
                 .item {
                     font-weight: 500;
@@ -295,6 +306,7 @@ watch(() => props.data, () => {
                     margin-bottom: 10px;
                     display: flex;
                     align-items: center;
+                    padding-left: 35px;
 
                     .icon {
                         width: 20px;

+ 1 - 0
src/view/iframe/publish/components/preview-card.vue

@@ -79,6 +79,7 @@
                             },
                             rewardType: baseFormData.rewardType,
                             customizedReward: baseFormData.customizedReward,
+                            upGainAmountUsdValue: upGainAmountUsdValue
                         }">
                     </custom-card-horizontal-cover>
                 </div>

+ 10 - 2
src/view/iframe/publish/give-dialog.vue

@@ -1172,6 +1172,10 @@ const submitRequest = async () => {
 
     let finishConditions = selectModeInfo.type != PlayType.treasure ? getFinishCondition() : getTreasureFinishCondition();
 
+    if(selectModeInfo.type == PlayType.treasure) {
+      customPosterData.value = {};
+    }
+
     let receiveConditions = openAntiBot.value ? "" : [];
 
     let validityDuration = '';
@@ -1693,11 +1697,14 @@ const checkUsdMinNumber = (isInTemplate) => {
         } else {
             let val1 = 0;
             if(currentLuckDropConfig?.minAvgUsdAmount) {
-              val1 = +math.format(math.multiply(math.bignumber(+totalCount), math.bignumber(usdPrice)));
+              val1 = +math.format(math.divide(math.bignumber(+amountValue), math.bignumber(+totalCount)));
             }
 
             const isAmountForbidden = currentLuckDropConfig?.minTotalUsdAmount ? +math.format(math.multiply(math.bignumber(+amountValue), math.bignumber(usdPrice))) < currentLuckDropConfig.minTotalUsdAmount : false;
-            const isAvgForbidden = currentLuckDropConfig?.minAvgUsdAmount ? +math.format(math.divide(math.bignumber(+amountValue), math.bignumber(val1))) < currentLuckDropConfig.minAvgUsdAmount : false;
+
+            const isAvgForbidden = currentLuckDropConfig?.minAvgUsdAmount ? +math.format(math.multiply(math.bignumber(val1), math.bignumber(usdPrice))) < currentLuckDropConfig.minAvgUsdAmount : false;
+
+
             forbiddenText = isAmountForbidden || isAvgForbidden ?
                             `The prize pool must be above ${isInTemplate ? ('<span class="font-color-1D9BF0">$' + currentLuckDropConfig.minTotalUsdAmount + '</span>') : ('$' + currentLuckDropConfig.minTotalUsdAmount)}
                             and the average prize must be above
@@ -2169,6 +2176,7 @@ const selectPublishMode = (params, index) => {
         baseFormData.customizedReward = '';
         showGeneralLottery.value = false;
         previewCustomPosterUrl.value = '';
+        treasureFormData.fansUnitAmount = calcFansUnitAmount();
     } else {
         showGeneralLottery.value = true;
         previewCustomPosterUrl.value = customPosterInfo.value && customPosterInfo.value.after && customPosterInfo.value.after.imagePath || ''

+ 187 - 89
src/view/iframe/treasure-hunt/components/invite-friends.vue

@@ -1,29 +1,50 @@
 <template>
     <div class="invite-friends">
-        <div class="txt">To open the treasure chest you need to share the URL to your friends. Make sure they finish
-            the
-            tasks.</div>
-        <div class="area-url">
-            <div class="url">{{ state.detail.inviteUrl }}</div>
-            <div class="btn copy-btn" @click="clickCopy" v-click-log="state.log_invite_copy_btn_click"
+      <div class="invite-friends-content">
+        <div class="invite-friends-content-head">
+          <div class="title">Invite Friends to Open the Chest!</div>
+          <div class="info">Invitees need to be new fans of {{followUserStr}} to receive rewards</div>
+        </div>
+        <div class="invite-friends-content-body">
+          <img class="tips" v-if="state.active_share_channel" :src="require('@/assets/svg/icon-channel-tips.svg')" />
+
+          <div class="share-list" :class="{'share-list-active': state.active_share_channel}">
+            <div v-for="(item, index) in state.share_list" :key="index" :data-clipboard-text="item.inviteContent"
+                @click="clickShare(item)" class="share-item">
+              <img :src="item.iconPath"  />
+              <div class="name">
+                {{item.name}}
+              </div>
+            </div>
+            <div class="share-item copy-btn" @click="clickCopy" v-click-log="state.log_invite_copy_btn_click"
                 :data-clipboard-text="state.detail.inviteCopyUrl">
-                Copy
+              <img :src="require('@/assets/svg/icon-copy-url-teasure.svg')" alt="">
+              <div class="name">
+                Copy URL
+              </div>
             </div>
         </div>
-        <div class="share-list">
-            <img :src="item.iconPath" alt="" v-for="item in state.share_list" :data-clipboard-text="item.inviteContent"
-                @click="clickShare(item)" class="share-item" />
         </div>
-        <v-btn :txt="state.open_btn.txt" :font-size="'17px'" class="btn" :icon="false"
+      </div>
+      <v-btn :txt="state.open_btn.txt" :font-size="'17px'" class="btn" :icon="false"
             :disabled="state.open_btn.disabled" v-show-log="state.log_invite_btn_show" :loading="state.btn_loading"
             v-click-log="state.log_invite_btn_click" @onClick="clickBtn" font-weight="600"></v-btn>
-
+      <div class="mask" v-show="showShareTips">
+        <div class="content">
+          <img class="icon-loading" :src="require('@/assets/svg/icon-tweet-loading.svg')" />
+          <div class="text">
+            Link copied to clipboard
+             <br/>
+            Opening {{selectShareApp.name }}
+          </div>
+        </div>
+      </div>
     </div>
 </template>
 <script setup>
 import VBtn from '@/view/iframe/treasure-hunt/components/btn.vue'
 import { inviteChannel } from '@/http/treasure'
-import { inject, onMounted } from 'vue'
+import { inject, onMounted, ref } from 'vue'
 import Report from "@/log-center/log"
 import { getFrontConfig } from "@/http/account";
 import { faceShareRedirectUrl } from '@/http/configAPI'
@@ -70,12 +91,28 @@ let facebookAppConfig = {
     faceShareRedirectUrl
 };
 
+let selectShareApp = ref({});
+let showShareTips = ref(false);
+let followUserStr = ref('');
+
 onMounted(() => {
     state.btn_loading = false
     setFrontConfig();
-    initInviteChannel()
+    initInviteChannel();
+    getFollowUserStr();
 })
 
+const getFollowUserStr = () => {
+  let arr = [];
+  if(state.follows && state.follows.length) {
+    for(let i = 0; i < state.follows.length; i++) {
+      let item = state.follows[i];
+      arr.push('@'+item.name);
+    }
+  }
+  followUserStr.value = arr.join(" or ");
+}
+
 chrome.management.onDisabled.addListener(() => {
     initInviteChannel()
 })
@@ -131,14 +168,17 @@ async function clickBtn() {
 const clickShare = (item) => {
     var clipboard = new ClipboardJS('.share-item');
     clipboard.on('success', function (e) {
-        state.toast.txt = 'Copy Successfully'
-        state.toast.has_icon = true
-        state.toast.show = true
-        setTimeout(() => {
-            state.toast.show = false
-        }, 2000)
+        // state.toast.txt = 'Copy Successfully'
+        // state.toast.has_icon = true
+        // state.toast.show = true
+        // setTimeout(() => {
+        //     state.toast.show = false
+        // }, 2000)
         e.clearSelection();
     })
+    showShareTips.value = true;
+    selectShareApp.value = item;
+
     if (item.name == 'facebook') {
         setChromeStorage({
             shareFacebookData: JSON.stringify({
@@ -150,16 +190,23 @@ const clickShare = (item) => {
         }
         let url = `https://www.facebook.com/dialog/share?app_id=${facebookAppConfig.facebookAppId}&display=popup&href=${item.treasureInviteUrl}&redirect_uri=${facebookAppConfig.faceShareRedirectUrl}?params=${JSON.stringify(cbParams)}`;
 
-        chrome.windows.create({
+        setTimeout(() => {
+          showShareTips.value = false;
+          chrome.windows.create({
             width: 800,
-            type: 'normal',
-            url
-        }, function (window) {
-        })
+              type: 'normal',
+              url
+          }, function (window) {
+          })
+        }, 1000)
+
     } else {
-        chrome.tabs.create({
-            url: item.redirectPath
-        });
+        setTimeout(() => {
+          showShareTips.value = false;
+          chrome.tabs.create({
+              url: item.redirectPath
+          });
+        }, 1000)
     }
     Report.reportLog({
         businessType: Report.businessType.buttonClick,
@@ -210,80 +257,131 @@ const clickCopy = () => {
 </script>
 <style lang="scss" scoped>
 .invite-friends {
-    padding: 18px 16px 25px 16px;
+    padding: 9px 16px 16px 16px;
     background: #fff;
+    box-sizing: border-box;
+
+    .invite-friends-content {
+      max-height: 242px;
+      overflow-y: auto;
+      margin-bottom: 10px;
+      padding: 0 6px;
+      box-sizing: border-box;
+
+      .invite-friends-content-head {
+        margin-bottom: 20px;
+        .title {
+          font-weight: 900;
+          font-size: 18px;
+          margin-bottom: 7px;
+        }
+        .info {
+          font-weight: 400;
+          font-size: 12px;
+          color: #7A7A7A;
+        }
 
-    .txt {
-        font-style: normal;
-        font-weight: 500;
-        font-size: 13px;
-        line-height: 18px;
-        /* or 129% */
-        margin-bottom: 18.5px;
-
-        letter-spacing: 0.3px;
+      }
 
-        color: #000000;
-    }
+      .invite-friends-content-body {
+        position: relative;
 
-    .area-url {
-        height: 70px;
-        background: rgba(29, 155, 240, 0.01);
-        border: 1px solid #1D9BF0;
-        border-radius: 5px;
-        display: flex;
-        align-items: center;
-        padding-left: 15px;
-        padding-right: 11px;
-        justify-content: space-between;
-
-        .url {
-            display: -webkit-box;
-            -webkit-box-orient: vertical;
-            -webkit-line-clamp: 3;
-            overflow: hidden;
-            width: 194px;
-
-            color: #737373;
-            font-weight: 400;
-            font-size: 13px;
-            white-space: normal;
-
-            word-wrap: break-word;
-
-            word-break: break-all;
+        .tips {
+          position: absolute;
+          top: -42px;
+          left: 0;
+          width: 146px;
         }
 
-        .btn {
-            user-select: none;
-            background: #1D9BF0;
-            border-radius: 35px;
-            width: 100px;
-            text-align: center;
-            line-height: 37px;
-            height: 37px;
-            font-weight: 700;
-            font-size: 15px;
-            color: #fff;
-            cursor: pointer;
+        .share-list-active {
+          background: rgba(29, 155, 240, 0.1);
+          border: 1.5px solid #1D9BF0 !important;
+          border-radius: 10px;
         }
-    }
+        .share-list {
+          display: flex;
+          flex-wrap: wrap;
+          width: 100%;
+          box-sizing: border-box;
+          border: 1.5px solid #fff;
+
+          .share-item {
+            user-select: none;
+            width: 20%;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            padding: 8px 4px;
+            box-sizing: border-box;
+            border-radius: 12px;
 
-    .share-list {
-        margin-top: 20px;
-        text-align: center;
-        margin-bottom: 14px;
 
-        img {
-            user-select: none;
             cursor: pointer;
-            width: 33px;
-            height: 33px;
-            margin-right: 14px;
-            border-radius: 100px;
+            img {
+                width: 40px;
+                height: 40px;
+                border-radius: 100px;
+                margin-bottom: 8px;
+            }
+            .name {
+              font-weight: 400;
+              font-size: 12px;
+              color: #898989;
+              width: 100%;
+              overflow: hidden;
+              text-overflow: ellipsis; //文本溢出显示省略号
+              white-space: nowrap;
+              text-align: center;
+            }
+          }
+
+          .share-item:hover {
+            background: #E3E3E4;
+          }
+
         }
+      }
+    }
+    .mask {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 375px;
+      height: 100%;
+      background: rgba($color: #000000, $alpha: 0.9);
+      z-index: 1000;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      .content {
+        text-align: center;
+      }
+
+      .icon-loading {
+        width: 60px;
+        height: 60px;
+        animation: loading 1.5 1s linear;
+        margin-bottom: 30px;
+      }
+
+      .text {
+        font-weight: 600;
+        font-size: 17px;
+        color: #FFFFFF;
+      }
     }
 
 
+  @keyframes loading {
+      0% {
+          transform: rotate(0);
+      }
+
+      100% {
+          transform: rotate(360deg);
+      }
+  }
 }
 </style>

+ 231 - 26
src/view/iframe/treasure-hunt/components/invite-list.vue

@@ -1,9 +1,61 @@
 <template>
-    <div class="content" v-show-log="state.log_invite_list_show">
-        <div class="error" v-if="state.invited_list.length == 0">
-            Invite people to hunt treasure with you!
+    <div class="content">
+      <div class="horizontal-invited-wrapper" v-if="state.invited_list.length">
+        invited({{state.inviteCount}})
+        <div class="horizontal-invited-list" @mouseleave="invitedListMouseleave($event)">
+          <template v-for="(item, index) in state.invited_list" :key="index" >
+            <div class="invited-item" v-if="index < 9" @mouseenter="invitedItemMouseenter(item)">
+             <img :src="item.userInfo.avatarUrl" />
+            </div>
+          </template>
+        </div>
+        <img class="more" v-if="state.invited_list.length > 9" :src="require('@/assets/svg/icon-invited-more.svg')"  @mouseenter="moreMouseenter"  @mouseleave="moreMouseleave" />
+      </div>
+
+      <div class="invited-user-info"  @mouseenter="invitedItemMouseenter()"
+       @mouseleave="invitedListMouseleave($event)" v-if="hoverInvitedUserInfo.userInfo">
+        <div class="left">
+            <img class="avatar" :src="hoverInvitedUserInfo.userInfo.avatarUrl" />
         </div>
-        <div class="list" v-else @scroll="handleScroll($event)">
+        <div class="right">
+            <div class="user-info">
+              <div class="name">
+              {{ hoverInvitedUserInfo.userInfo.nickName }}
+              </div>
+              <div class="time">
+                {{ getTime(hoverInvitedUserInfo.timestamp) }}
+              </div>
+            </div>
+            <span class="channel">
+              <img class="app-icon" :src="hoverInvitedUserInfo.userInfo.avatarUrl" />
+              WhatsApp
+            </span>
+        </div>
+      </div>
+
+      <div class="vertical-invited-wrapper" v-if="showVerticalInvitedList" @mouseenter="moreMouseenter"  @mouseleave="moreMouseleave" @scroll="handleScroll($event)">
+        <div class="invited-user-info"  v-for="(item, index) in state.invited_list" :key="index">
+          <div class="left">
+              <img @click="clickItem(item)" class="avatar" :src="item.userInfo.avatarUrl" />
+          </div>
+          <div class="right">
+              <div class="user-info">
+                <div class="name">
+                {{ item.userInfo.nickName }}
+                </div>
+                <div class="time">
+                  {{ getTime(item.timestamp) }}
+                </div>
+              </div>
+              <span class="channel">
+                <img class="app-icon" :src="item.userInfo.avatarUrl" />
+                WhatsApp
+              </span>
+          </div>
+        </div>
+      </div>
+
+        <!-- <div class="list" @scroll="handleScroll($event)">
             <div class="item" v-for="item in state.invited_list" :key="item.userInfo.uid">
                 <div class="left">
                     <img :src="item.userInfo.avatarUrl" alt="" @click="clickItem(item)" />
@@ -13,27 +65,32 @@
                     <div>{{ getTime(item.timestamp) }}</div>
                 </div>
             </div>
-        </div>
-        <div class="footer">
+        </div> -->
+        <!-- <div class="footer">
             <v-btn :txt="state.open_btn.txt" :font-size="'17px'" class="btn" :icon="false" :loading="state.btn_loading"
                 :disabled="state.open_btn.disabled" v-click-log="state.log_invite_btn_click" @onClick="clickBtn"
                 font-weight="600"></v-btn>
-        </div>
+        </div>-->
     </div>
 </template>
 <script setup>
 import VBtn from '@/view/iframe/treasure-hunt/components/btn.vue'
 import { inviteList, inviteListRefresh } from '@/http/treasure'
-import { inject, onMounted } from 'vue'
+import { inject, onMounted, ref } from 'vue'
 import Report from "@/log-center/log"
 
 var moment = require('moment')
 let state = inject('state')
 state.invited_list = []
 let page_num = 1
-let page_size = 10
+let page_size = 100
 let list_end = false
 
+let hoverInvitedUserInfo = ref({});
+let showVerticalInvitedList = ref(false);
+let timer = null;
+let timer1 = null;
+
 state.log_invite_btn_click = {
     businessType: Report.businessType.buttonClick,
     pageSource: Report.pageSource.inviteFriendsPage,
@@ -60,6 +117,30 @@ onMounted(() => {
     list()
 })
 
+const invitedItemMouseenter = (params) => {
+  if(timer) clearTimeout(timer)
+  if(params) {
+    hoverInvitedUserInfo.value = params;
+  }
+}
+
+const invitedListMouseleave = (params) => {
+  timer = setTimeout(function(){
+    hoverInvitedUserInfo.value = {};
+  },600);
+}
+
+const moreMouseenter = () => {
+  if(timer1) clearTimeout(timer1)
+  showVerticalInvitedList.value = true;
+}
+
+const moreMouseleave = () => {
+  timer1 = setTimeout(function(){
+    showVerticalInvitedList.value = false;
+  },600);
+}
+
 const clickItem = (item) => {
     window.open(`https://twitter.com/${item.userInfo.nickName}`)
 }
@@ -93,16 +174,17 @@ state.inviteListRefresh = () => {
         }
     }).then((res) => {
         if (res.code == 0) {
-            handleCommon(res.data)
+          handleCommon(res.data)
         }
     })
 }
 
 const handleCommon = (data) => {
     state.inviteCount = data.inviteCount
-    if (state.inviteCount > 0) {
-        state.tabs[1].txt = `invited(${state.inviteCount})`
-    }
+    // if (state.inviteCount > 0) {
+    //     state.tabs[1].txt = `invited(${state.inviteCount})`
+    // }
+
     if (data.inviteUsers.length > 0) {
         data.inviteUsers.forEach(item => {
             if (state.invited_list.filter((item2) => { return item2.userInfo.uid == item.userInfo.uid }).length == 0) {
@@ -181,25 +263,139 @@ async function clickBtn() {
 </script>
 <style lang="scss" scoped>
 .content {
-    position: relative;
-    height: 292px;
+    position: absolute;
+    bottom: 0px;
+    width: 100%;
 
-    .footer {
-        background: #fff;
-        padding: 10px 16px 25px 16px;
+    .horizontal-invited-wrapper {
+      width: 100%;
+      padding: 12px 14px;
+      box-sizing: border-box;
+      display: flex;
+      align-items: center;
+      font-weight: 400;
+      font-size: 12px;
+      color: #A7A39F;
+
+      .horizontal-invited-list {
+        display: flex;
+        align-items: center;
+        margin-left: 5px;
+        cursor: pointer;
+
+        .invited-item {
+          width: 16px;
+          height: 16px;
+          padding: 0 5px;
+          img {
+            width: 16px;
+            height: 16px;
+            border-radius: 50%;
+          }
+        }
+      }
+
+      .more {
+        margin-left: 5px;
+        cursor: pointer;
+      }
     }
 
-    .error {
-        height: 204px;
-        color: #BABABA;
-        background-color: #fff;
-        font-weight: 500;
-        font-size: 15px;
-        line-height: 204px;
-        text-align: center;
+    .vertical-invited-wrapper {
+      width: 343px;
+      height: 308px;
+      background: #FFFFFF;
+      box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.25);
+      border-radius: 16px;
+      overflow-y: scroll;
+      position: absolute;
+      bottom: -316px;
+      left: 16px;
+      animation: fade-in 0.25s linear forwards;
 
+      .invited-user-info {
+        position: static !important;
+        border-radius: 0px !important;
+        box-shadow: none !important;
+        padding: 0 !important;
+        animation: none !important;
+        .left, .right {
+          padding: 9px 0;
+        }
+        .left {
+          padding-left: 16px;
+        }
+        .right {
+          padding-right: 16px;
+          box-shadow: inset 0px -1px 0px #F2F2F2;
+          box-sizing: border-box;
+        }
+      }
     }
 
+    .invited-user-info {
+      width: 343px;
+      padding: 9px 16px;
+      box-sizing: border-box;
+      background: #FFFFFF;
+      box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.25);
+      border-radius: 16px;
+      position: absolute;
+      bottom: -62px;
+      left: 16px;
+      display: flex;
+      justify-content: space-between;
+      animation: fade-in 0.25s linear forwards;
+
+      .left {
+        display: flex;
+        align-items: center;
+        .avatar {
+          width: 30px;
+          height: 30px;
+          margin-right: 16px;
+          border-radius: 50%;
+        }
+      }
+
+      .right {
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+
+        .user-info {
+          .name {
+            margin-bottom: 5px;
+            font-weight: 500;
+            font-size: 15px;
+          }
+          .time {
+            font-weight: 400;
+            font-size: 12px;
+            color: #A9A9A9;
+          }
+        }
+
+        .channel {
+            height: min-content;
+            display: flex;
+            align-items: center;
+            font-weight: 400;
+            font-size: 12px;
+            color: #A9A9A9;
+
+            .app-icon {
+              width: 14px;
+              height: 14px;
+              margin-right: 4px;
+              border-radius: 50%;
+            }
+        }
+      }
+    }
+
+
+
     .list {
         background: #fff;
         height: 204px;
@@ -247,5 +443,14 @@ async function clickBtn() {
             }
         }
     }
+
+    @keyframes fade-in {
+      from {
+        opacity: 0;
+      }
+      to {
+        opacity: 1;
+      }
+    }
 }
 </style>

+ 5 - 1
src/view/iframe/treasure-hunt/cover.vue

@@ -245,7 +245,7 @@ const logPreRepost = () => {
 
     // rootTwitterName  上报原始发布者的name
     // fatherTwitterName 上报分享者的name or 原始发布者的name
-    // isFatherTwitterFans 在原始链接或分享链接 邀请者非自己 
+    // isFatherTwitterFans 在原始链接或分享链接 邀请者非自己
     // isRootTwitterFans 在原始链接或分享链接 发布者非自己
 
     let params = {}
@@ -555,6 +555,10 @@ const getUsersFollowStatus = () => {
         margin-top: 24px;
         margin-bottom: 15px;
 
+        img {
+          margin-right: 9px;
+        }
+
         span {
             font-weight: 500;
             font-size: 12px;

+ 7 - 6
src/view/iframe/treasure-hunt/invite.vue

@@ -13,7 +13,7 @@
                 <div class="full" ref="line_full"></div>
             </div>
         </div>
-        <div class="area-success-message" @mouseover="mouseOver" @mouseleave="mouseLeave">
+        <!-- <div class="area-success-message" @mouseover="mouseOver" @mouseleave="mouseLeave">
             <div class="content-success-message" ref="content_success_message">
                 <div class="success-message" v-for="item, index in state.success_message_list" :key="index"
                     @click="clickItem(item)">
@@ -22,10 +22,11 @@
                     <span>Opened Treasure Chest</span>
                 </div>
             </div>
-        </div>
+        </div> -->
+         <invite-list></invite-list>
     </div>
 
-    <div class="area-nav">
+    <!-- <div class="area-nav">
         <div class="item" :class="{ active: state.tab_index == i }" @click="state.tab_index = i"
             v-for="item, i in state.tabs">
             <img :src="require('@/assets/svg/icon-invite.svg')" alt=""
@@ -34,10 +35,10 @@
                 :style="{ opacity: state.tab_index == 1 ? '1' : '0.55' }" v-if="i == 1" />
             {{ item.txt }}
         </div>
-    </div>
+    </div> -->
     <div class="area-info">
-        <invite-friends v-show="state.tab_index == 0"></invite-friends>
-        <invite-list v-show="state.tab_index == 1"></invite-list>
+        <invite-friends></invite-friends>
+        <!-- -->
     </div>
     <v-dialog v-show="state.dialog.show"></v-dialog>
 </template>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.