Просмотр исходного кода

Merge branch 'dev_1.1.3' of DeNet/de-net into master

zhangwei 2 лет назад
Родитель
Сommit
538f33ebbd
54 измененных файлов с 1804 добавлено и 519 удалено
  1. 3 0
      package.json
  2. BIN
      src/assets/img/icon-header-cover.png
  3. 8 0
      src/assets/svg/icon-last-time.svg
  4. 6 0
      src/assets/svg/icon-send-giveaway.svg
  5. 0 46
      src/assets/svg/img-A0.svg
  6. 0 46
      src/assets/svg/img-A1.svg
  7. 30 30
      src/assets/svg/img-B0.svg
  8. 30 30
      src/assets/svg/img-B1.svg
  9. 4 0
      src/assets/svg/img-new.svg
  10. 3 0
      src/assets/svg/poster-after.svg
  11. 2 0
      src/assets/svg/poster-befor.svg
  12. 3 0
      src/assets/svg/poster-loading.svg
  13. 116 83
      src/entry/background.js
  14. 5 1
      src/entry/content.js
  15. 4 1
      src/entry/content_help.js
  16. 1 1
      src/http/configAPI.js
  17. 6 5
      src/http/fetch.js
  18. 7 0
      src/http/logApi.js
  19. 19 0
      src/http/media.js
  20. 3 0
      src/iframe/bind-tweet.js
  21. 3 0
      src/iframe/buy-nft.js
  22. 2 1
      src/iframe/group-card.js
  23. 2 0
      src/iframe/home.js
  24. 2 0
      src/iframe/joined-group-list.js
  25. 2 1
      src/iframe/nft-card.js
  26. 2 1
      src/iframe/nft-group-card.js
  27. 2 1
      src/iframe/nft-group.js
  28. 2 0
      src/iframe/popup-page.js
  29. 2 1
      src/iframe/publish-tips.js
  30. 3 0
      src/iframe/publish.js
  31. 12 7
      src/iframe/red-packet.js
  32. 2 0
      src/iframe/tab-group.js
  33. 5 1
      src/log-center/logEnum.js
  34. 6 1
      src/log-center/logger.js
  35. 4 5
      src/logic/background/help.js
  36. 17 1
      src/logic/background/twitter.js
  37. 5 5
      src/logic/content/ParseCard.js
  38. 43 22
      src/logic/content/twitter.js
  39. 1 1
      src/manifest.json
  40. 23 0
      src/types/global.js
  41. 1 0
      src/types/index.js
  42. 3 3
      src/uilts/help.js
  43. 26 0
      src/uilts/sentry.js
  44. 143 6
      src/view/components/custom-card-cover.vue
  45. 201 0
      src/view/components/custom-card-horizontal-cover.vue
  46. 51 12
      src/view/content/message/index.vue
  47. 353 0
      src/view/iframe/publish/components/giveaway-poster.vue
  48. 26 147
      src/view/iframe/publish/components/preview-card.vue
  49. 339 11
      src/view/iframe/publish/give-dialog.vue
  50. 1 0
      src/view/iframe/red-packet/index.vue
  51. 94 29
      src/view/iframe/red-packet/luck-draw.vue
  52. 93 17
      src/view/iframe/red-packet/red-packet.vue
  53. 82 2
      src/view/popup/currency-detail.vue
  54. 1 1
      src/view/popup/tabbar-page/message/index.vue

+ 3 - 0
package.json

@@ -11,6 +11,8 @@
     "build-watch": "vue-cli-service  --env.NODE_ENV=development build-watch --mode development"
   },
   "dependencies": {
+    "@sentry/tracing": "^7.5.1",
+    "@sentry/vue": "^7.5.1",
     "ant-design-vue": "^2.2.8",
     "axios": "^0.26.1",
     "clipboard": "^2.0.10",
@@ -26,6 +28,7 @@
     "qrcode": "^1.5.0",
     "sass-loader": "^12.6.0",
     "vue": "^3.2.13",
+    "vue-cropper": "^1.0.3",
     "vue-router": "^4.0.14"
   },
   "devDependencies": {

BIN
src/assets/img/icon-header-cover.png


+ 8 - 0
src/assets/svg/icon-last-time.svg

@@ -0,0 +1,8 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<mask id="path-1-outside-1_21276_235944" maskUnits="userSpaceOnUse" x="0" y="0" width="14" height="14" fill="black">
+<rect fill="white" width="14" height="14"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1 7C1 3.69143 3.69143 1 7 1C10.3086 1 13 3.69143 13 7C13 10.3086 10.3086 13 7 13C3.69143 13 1 10.3086 1 7ZM1.861 7C1.861 9.83329 4.16629 12.139 7 12.139C9.83329 12.139 12.139 9.83329 12.139 7C12.139 4.16629 9.83329 1.861 7 1.861C4.16629 1.861 1.861 4.16629 1.861 7ZM7.13644 7.36158H9.01036C9.21841 7.36158 9.38672 7.54844 9.38672 7.7815C9.38672 8.01455 9.21841 8.20141 9.01036 8.20141H6.76158C6.55391 8.20141 6.38672 8.01455 6.38672 7.7815V4.42301C6.38672 4.19038 6.55353 4.00141 6.76158 4.00141C6.96962 4.00141 7.13644 4.18995 7.13644 4.42301V7.36158Z"/>
+</mask>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1 7C1 3.69143 3.69143 1 7 1C10.3086 1 13 3.69143 13 7C13 10.3086 10.3086 13 7 13C3.69143 13 1 10.3086 1 7ZM1.861 7C1.861 9.83329 4.16629 12.139 7 12.139C9.83329 12.139 12.139 9.83329 12.139 7C12.139 4.16629 9.83329 1.861 7 1.861C4.16629 1.861 1.861 4.16629 1.861 7ZM7.13644 7.36158H9.01036C9.21841 7.36158 9.38672 7.54844 9.38672 7.7815C9.38672 8.01455 9.21841 8.20141 9.01036 8.20141H6.76158C6.55391 8.20141 6.38672 8.01455 6.38672 7.7815V4.42301C6.38672 4.19038 6.55353 4.00141 6.76158 4.00141C6.96962 4.00141 7.13644 4.18995 7.13644 4.42301V7.36158Z" fill="#91989C"/>
+<path d="M7.13644 7.36158H7.03644V7.46158H7.13644V7.36158ZM7 0.9C3.6362 0.9 0.9 3.6362 0.9 7H1.1C1.1 3.74666 3.74666 1.1 7 1.1V0.9ZM13.1 7C13.1 3.6362 10.3638 0.9 7 0.9V1.1C10.2533 1.1 12.9 3.74666 12.9 7H13.1ZM7 13.1C10.3638 13.1 13.1 10.3638 13.1 7H12.9C12.9 10.2533 10.2533 12.9 7 12.9V13.1ZM0.9 7C0.9 10.3638 3.6362 13.1 7 13.1V12.9C3.74666 12.9 1.1 10.2533 1.1 7H0.9ZM7 12.039C4.22152 12.039 1.961 9.77806 1.961 7H1.761C1.761 9.88851 4.11105 12.239 7 12.239V12.039ZM12.039 7C12.039 9.77806 9.77806 12.039 7 12.039V12.239C9.88851 12.239 12.239 9.88851 12.239 7H12.039ZM7 1.961C9.77806 1.961 12.039 4.22152 12.039 7H12.239C12.239 4.11105 9.88851 1.761 7 1.761V1.961ZM1.961 7C1.961 4.22151 4.22151 1.961 7 1.961V1.761C4.11106 1.761 1.761 4.11106 1.761 7H1.961ZM9.01036 7.26158H7.13644V7.46158H9.01036V7.26158ZM9.48672 7.7815C9.48672 7.50365 9.28352 7.26158 9.01036 7.26158V7.46158C9.1533 7.46158 9.28672 7.59324 9.28672 7.7815H9.48672ZM9.01036 8.30141C9.28352 8.30141 9.48672 8.05935 9.48672 7.7815H9.28672C9.28672 7.96975 9.1533 8.10141 9.01036 8.10141V8.30141ZM6.76158 8.30141H9.01036V8.10141H6.76158V8.30141ZM6.28672 7.7815C6.28672 8.05887 6.48837 8.30141 6.76158 8.30141V8.10141C6.61944 8.10141 6.48672 7.97023 6.48672 7.7815H6.28672ZM6.28672 4.42301V7.7815H6.48672V4.42301H6.28672ZM6.76158 3.90141C6.48739 3.90141 6.28672 4.14674 6.28672 4.42301H6.48672C6.48672 4.23401 6.61968 4.10141 6.76158 4.10141V3.90141ZM7.23644 4.42301C7.23644 4.14625 7.03571 3.90141 6.76158 3.90141V4.10141C6.90354 4.10141 7.03644 4.23366 7.03644 4.42301H7.23644ZM7.23644 7.36158V4.42301H7.03644V7.36158H7.23644Z" fill="#91989C" mask="url(#path-1-outside-1_21276_235944)"/>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 6 - 0
src/assets/svg/icon-send-giveaway.svg


Разница между файлами не показана из-за своего большого размера
+ 0 - 46
src/assets/svg/img-A0.svg


Разница между файлами не показана из-за своего большого размера
+ 0 - 46
src/assets/svg/img-A1.svg


Разница между файлами не показана из-за своего большого размера
+ 30 - 30
src/assets/svg/img-B0.svg


Разница между файлами не показана из-за своего большого размера
+ 30 - 30
src/assets/svg/img-B1.svg


+ 4 - 0
src/assets/svg/img-new.svg

@@ -0,0 +1,4 @@
+<svg width="40" height="18" viewBox="0 0 40 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="40" height="18" rx="9" fill="#F9B04E"/>
+<path d="M9.50029 13V7.92432H9.54863L13.1741 13H14.5169V5.24951H12.9646V10.2983H12.9217L9.30156 5.24951H7.94805V13H9.50029ZM21.2461 11.6572H17.7334V9.70215H21.0474V8.45605H17.7334V6.58691H21.2461V5.24951H16.1113V13H21.2461V11.6572ZM27.3576 7.7041H27.406L28.8776 13H30.4084L32.4655 5.24951H30.7844L29.5598 10.8623H29.5168L28.0666 5.24951H26.697L25.2468 10.8623H25.2038L23.9846 5.24951H22.3034L24.3552 13H25.8859L27.3576 7.7041Z" fill="white"/>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 3 - 0
src/assets/svg/poster-after.svg


Разница между файлами не показана из-за своего большого размера
+ 2 - 0
src/assets/svg/poster-befor.svg


+ 3 - 0
src/assets/svg/poster-loading.svg

@@ -0,0 +1,3 @@
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.2" d="M20.0026 0V5C23.2952 5.00069 26.4961 6.08476 29.1116 8.08498C31.7271 10.0852 33.6117 12.8904 34.4748 16.0679C35.338 19.2454 35.1316 22.6187 33.8876 25.6673C32.6435 28.7159 30.431 31.2704 27.5912 32.9369C24.7514 34.6034 21.4422 35.2892 18.174 34.8885C14.9058 34.4878 11.8603 33.0229 9.5072 30.7198C7.15411 28.4166 5.6242 25.4032 5.15348 22.1444C4.68277 18.8855 5.2974 15.5624 6.90256 12.6875L2.53757 10.25C0.39731 14.0832 -0.422189 18.5142 0.205487 22.8594C0.833164 27.2045 2.87314 31.2225 6.01072 34.2933C9.1483 37.3641 13.2091 39.3173 17.5668 39.8514C21.9244 40.3855 26.3368 39.471 30.1231 37.2488C33.9095 35.0266 36.8595 31.6203 38.518 27.5554C40.1765 23.4905 40.4514 18.9927 39.3003 14.7561C38.1492 10.5194 35.636 6.77918 32.1485 4.11243C28.6609 1.44568 24.3928 0.000557603 20.0026 0Z" fill="black"/>
+</svg>

+ 116 - 83
src/entry/background.js

@@ -21,7 +21,8 @@ import {
     setActionPopup,
     getTwitterNftPostPre,
     nftTxtPublish,
-    getLuckMessage
+    getSysMessage,
+    checkShowPublishDialog
 } from "@/logic/background/twitter";
 import Report from "@/log-center/log"
 import { PingPong, httpNetWork } from "@/logic/background/help";
@@ -42,7 +43,20 @@ chrome.runtime.onConnect.addListener(function (port) {
 
 // 当有可用更新时触发
 chrome.runtime.onUpdateAvailable.addListener(() => {
-    chrome.runtime.reload()
+    try {
+        Report.reportLog({
+            objectType: Report.objectType.background_function_try,
+            funcName: 'onUpdateAvailable'
+        });
+        chrome.runtime.reload()
+    } catch (error) {
+        Report.reportLog({
+            objectType: Report.objectType.background_function_catch,
+            funcName: 'onUpdateAvailable',
+            errMsg: error.message
+        });
+    }
+
 })
 
 chrome.runtime.onInstalled.addListener(onInstalledMethod);
@@ -60,7 +74,7 @@ chrome.alarms.onAlarm.addListener(function (alarm) {
             PingPong()
             break;
         case 'LuckMessage':
-            getLuckMessage()
+            getSysMessage()
             break
     }
 });
@@ -72,93 +86,112 @@ chrome.action.onClicked.addListener(function (tab) {
 chrome.tabs.onActivated.addListener(function (activeInfo) {
     setPopupConfig(activeInfo);
 })
-
 function onInstalledMethod() {
-    onInstalledCreateTab()
-    onInstalledMid()
-    onInstalledUserSet()
-
-    // pingpang
-    chrome.alarms.create('PingPong', {
-        //1分鐘之後開始(該值不能小於1) 
-        delayInMinutes: 2,
-        //開始後每一分鐘執行一次(該值不能小于1) 
-        periodInMinutes: 4
-    });
-    chrome.alarms.create('LuckMessage', {
-        //1分鐘之後開始(該值不能小於1) 
-        delayInMinutes: 1,
-        //開始後每一分鐘執行一次(該值不能小于1) 
-        periodInMinutes: 1
-    });
-    setTimeout(() => {
-        // 安装成功埋点
+    try {
+        onInstalledCreateTab()
+        onInstalledMid()
+        onInstalledUserSet()
+        // pingpang
+        chrome.alarms.create('PingPong', {
+            //1分鐘之後開始(該值不能小於1) 
+            delayInMinutes: 2,
+            //開始後每一分鐘執行一次(該值不能小于1) 
+            periodInMinutes: 4
+        });
+        chrome.alarms.create('LuckMessage', {
+            //1分鐘之後開始(該值不能小於1) 
+            delayInMinutes: 1,
+            //開始後每一分鐘執行一次(該值不能小于1) 
+            periodInMinutes: 1
+        });
+        setTimeout(() => {
+            // 安装成功埋点
+            Report.reportLog({
+                objectType: Report.objectType.background_function_try,
+                funcName: 'onInstalledMethod'
+            });
+        }, 5000);
+    } catch (error) {
+        // 上报错误信息
         Report.reportLog({
-            objectType: Report.objectType.chrome_extension_installed
+            objectType: Report.objectType.background_function_catch,
+            funcName: 'onInstalledMethod',
+            errMsg: error.message
         });
-    }, 5000);
+    }
 }
 
 function onMessageMethod(req, sender, sendResponse) {
-    sendResponse('ok')
-    if (req) {
-        switch (req.actionType) {
-            case "POPUP_LOGIN":
-                twitterPinLoginToken();
-                break;
-            case "POPUP_PUBLISH_TWITTER_RED_PACK":
-                popupRePublish(req);
-                break;
-            case 'CONTENT_GET_PINED':
-                checkPined();
-                break;
-            case 'CONTENT_SET_BADGE':
-                setBadgeInfo(req);
-                break;
-            case 'CONTENT_HIDE_BADGE':
-                hideBadge();
-                break
-            case 'CONTENT_SEND_CODE':
-                twitterPinLoginCode(sender, req.code);
-            case 'CONTENT_TWITTER_LOGIN':
-                if (req.data) {
-                    twitterPinLoginToken()
-                }
-                break
-            case 'CONTENT_TWITTER_SHORT_LINK':
-                req.arr_url.forEach(item => {
-                    if (item) {
-                        twitterShortUrl(sender, item)
+    try {
+        sendResponse('ok')
+        if (req) {
+            switch (req.actionType) {
+                case "POPUP_LOGIN":
+                    twitterPinLoginToken();
+                    break;
+                case "POPUP_PUBLISH_TWITTER_RED_PACK":
+                    popupRePublish(req);
+                    break;
+                case "POPUP_SHOW_DENET_PUBLISH_DIALOG":
+                    checkShowPublishDialog();
+                    break;
+                case 'CONTENT_GET_PINED':
+                    checkPined();
+                    break;
+                case 'CONTENT_SET_BADGE':
+                    setBadgeInfo(req);
+                    break;
+                case 'CONTENT_HIDE_BADGE':
+                    hideBadge();
+                    break
+                case 'CONTENT_SEND_CODE':
+                    twitterPinLoginCode(sender, req.code);
+                case 'CONTENT_TWITTER_LOGIN':
+                    if (req.data) {
+                        twitterPinLoginToken()
                     }
-                });
-                break
-            case "CONTENT_SEND_DISCORD_AUTH_CODE":
-                discordLoginCode(req, sender);
-                break
-            case 'RED_PACKET_SAVE_DISCORD_AUTH_WINDOW_ID':
-                saveDiscordAuthWindowId(req);
-                break;
-            case 'CONTENT_FACEBOOK_SHARE_SUCCESS':
-                facebookShareSuccess(req, sender);
-                break;
-            case 'CONTENT_PONG':
-                console.log('CONTENT_PONG')
-                break
-            case 'CONTENT_WINDOW_LOADED_SET_POPUP_PAGE':
-                // windwoLoadSetPopupPage(req, sender);
-                break;
-            case 'CONTENT_SET_POPUP_CONFIG':
-                setActionPopup(req, sender);
-                break;
-            case 'CONTENT_GET_TWITTER_NFT_POST_PRE':
-                getTwitterNftPostPre(req.data, sender)
-                break
-            case 'CONTENT_NFT_TXT_PUBLISH':
-                nftTxtPublish(req.data, sender)
-                break
-            case 'CONTENT_HTTP_NET_WORK':
-                httpNetWork(req.funcName, req.data, sender)
-                break
+                    break
+                case 'CONTENT_TWITTER_SHORT_LINK':
+                    req.arr_url.forEach(item => {
+                        if (item) {
+                            twitterShortUrl(sender, item)
+                        }
+                    });
+                    break
+                case "CONTENT_SEND_DISCORD_AUTH_CODE":
+                    discordLoginCode(req, sender);
+                    break
+                case 'RED_PACKET_SAVE_DISCORD_AUTH_WINDOW_ID':
+                    saveDiscordAuthWindowId(req);
+                    break;
+                case 'CONTENT_FACEBOOK_SHARE_SUCCESS':
+                    facebookShareSuccess(req, sender);
+                    break;
+                case 'CONTENT_PONG':
+                    console.log('CONTENT_PONG')
+                    break
+                case 'CONTENT_WINDOW_LOADED_SET_POPUP_PAGE':
+                    // windwoLoadSetPopupPage(req, sender);
+                    break;
+                case 'CONTENT_SET_POPUP_CONFIG':
+                    setActionPopup(req, sender);
+                    break;
+                case 'CONTENT_GET_TWITTER_NFT_POST_PRE':
+                    getTwitterNftPostPre(req.data, sender)
+                    break
+                case 'CONTENT_NFT_TXT_PUBLISH':
+                    nftTxtPublish(req.data, sender)
+                    break
+                case 'CONTENT_HTTP_NET_WORK':
+                    httpNetWork(req.funcName, req.data, sender)
+                    break
+            }
         }
+    } catch (error) {
+        Report.reportLog({
+            objectType: Report.objectType.background_function_catch,
+            funcName: 'onMessageMethod',
+            errMsg: error.message
+        });
     }
 }

+ 5 - 1
src/entry/content.js

@@ -37,7 +37,8 @@ import {
     setGroupInfo,
     refreshTabGroup,
     groupTipsSelectGroupTab,
-    TwitterApiUserByScreenName
+    TwitterApiUserByScreenName,
+    showPublishDialog
 } from "@/logic/content/twitter.js";
 
 import { 
@@ -186,5 +187,8 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
         case 'IFRAME_API_GET_TWEET_USER_INFO_REQ':
             TwitterApiUserByScreenName(req.data)
             break;
+        case 'BG_SHOW_DENET_PUBLISH_DIALOG':
+            showPublishDialog();
+            break;
     }
 })

+ 4 - 1
src/entry/content_help.js

@@ -4,12 +4,15 @@ import {
 } from "@/logic/content/twitter.js";
 import { createApp } from 'vue'
 import ViewMessage from '@/view/content/message/index.vue'
+import CoutomSentry from "@/uilts/sentry.js"
 
 const addDomMessage = (element) => {
     const div = document.createElement('div')
     div.id = 'denet_message'
     document.body.appendChild(div)
-    createApp(element).mount('#denet_message')
+    let app = createApp(element)
+    app.mount('#denet_message')
+    CoutomSentry.initVue(app)
 }
 
 let timer = setInterval(() => {

+ 1 - 1
src/http/configAPI.js

@@ -1,4 +1,4 @@
-export const appVersionCode = 12
+export const appVersionCode = 13
 
 const api = {
 	production: 'https://api.denetme.net',

+ 6 - 5
src/http/fetch.js

@@ -21,16 +21,17 @@ export async function commonFetch({ url = '', method = 'POST' , params = {}, bas
         if(url.includes('http')){
             _url = url
         }
+        let bodyObj = {
+            "baseInfo": baseInfo,
+            "params": params
+        };
         fetch(_url, {
             method: method, // or 'PUT'
             cache: 'no-cache',
             headers: {
                 'Content-Type': 'application/json',
             },
-            body: JSON.stringify({
-                "baseInfo": baseInfo,
-                "params": params
-            }),
+            body: JSON.stringify(bodyObj),
         })
             .then(response => response.json())
             .then(data => {
@@ -60,7 +61,7 @@ export async function commonFetch({ url = '', method = 'POST' , params = {}, bas
                 resolve(data);
             })
             .catch((error) => {
-                reject(error);
+                reject({url: _url, error: error, requestParams: bodyObj});
             });
     })
 }

+ 7 - 0
src/http/logApi.js

@@ -19,4 +19,11 @@ export function logApi(params = {}) {
         },
         params: params.params
     })
+}
+
+export function reportFrontLogApi(params = {}) {
+    return commonFetch({
+        url: `/log/reportFrontLog`,
+        params: params
+    })
 }

+ 19 - 0
src/http/media.js

@@ -0,0 +1,19 @@
+import axios from 'axios'
+import { service } from "./request";
+
+export function uploadSignature(params) {
+    return service({
+        url: `/media/uploadSignature`,
+        method: "post",
+        data: params,
+    });
+}
+
+export function uploadFile({url, data, headers}) {
+    return axios({
+        method: 'PUT',
+        url: url,
+        data: data,
+        headers: headers
+    })
+}

+ 3 - 0
src/iframe/bind-tweet.js

@@ -3,4 +3,7 @@ import App from '@/view/iframe/bind-tweet/bind-tweet.vue'
 
 const app = createApp(App);
 
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
+
 app.mount('#app');

+ 3 - 0
src/iframe/buy-nft.js

@@ -6,6 +6,9 @@ import 'element-plus/dist/index.css'
 import router from '@/router/buy-nft.js'
 
 const app = createApp(App);
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
+
 
 
 app.use(router)

+ 2 - 1
src/iframe/group-card.js

@@ -2,5 +2,6 @@ import { createApp } from 'vue'
 import App from '@/view/iframe/group-card/card.vue'
 
 const app = createApp(App);
-
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app');

+ 2 - 0
src/iframe/home.js

@@ -19,6 +19,8 @@ app.use(Switch)
 app.use(message);
 app.use(router)
 app.mount('#app')
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 
 window.onload = () => {
     document.title = 'DeNet'

+ 2 - 0
src/iframe/joined-group-list.js

@@ -2,4 +2,6 @@ import { createApp } from 'vue'
 import App from '@/view/iframe/tab-group/joined-group-list.vue'
 
 const app = createApp(App);
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app');

+ 2 - 1
src/iframe/nft-card.js

@@ -2,5 +2,6 @@ import { createApp } from 'vue'
 import App from '@/view/iframe/nft/card.vue'
 
 const app = createApp(App);
-
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app');

+ 2 - 1
src/iframe/nft-group-card.js

@@ -2,5 +2,6 @@ import { createApp } from 'vue'
 import App from '@/view/iframe/nft/group-card.vue'
 
 const app = createApp(App);
-
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app');

+ 2 - 1
src/iframe/nft-group.js

@@ -2,5 +2,6 @@ import { createApp } from 'vue'
 import App from '@/view/iframe/nft/group.vue'
 
 const app = createApp(App);
-
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app');

+ 2 - 0
src/iframe/popup-page.js

@@ -17,6 +17,8 @@ app.use(Tooltip);
 app.use(Button);
 app.use(message);
 app.use(router)
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app')
 
 window.onload= () => {

+ 2 - 1
src/iframe/publish-tips.js

@@ -14,6 +14,7 @@ message.config({
 });
 
 const app = createApp(App);
-
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.use(message);
 app.mount('#app');

+ 3 - 0
src/iframe/publish.js

@@ -17,4 +17,7 @@ app.use(Button);
 app.use(Tooltip);
 app.use(message);
 app.use(Switch);
+
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app');

+ 12 - 7
src/iframe/red-packet.js

@@ -5,15 +5,20 @@ const app = createApp(App)
 // 引入路由对象实例
 import "ant-design-vue/dist/antd.css"; // or 'ant-design-vue/dist/antd.less'
 import "@/assets/css/reset.css";
-
 import { message, Tooltip, Switch } from "ant-design-vue";
+
+import CoutomSentry from "@/uilts/sentry.js"
+
+CoutomSentry.initVue(app)
+
 message.config({
-    top: `10px`,
-    duration: 3,
-    maxCount: 1,
-});
-app.use(Tooltip);
-app.use(Switch);
+  top: `10px`,
+  duration: 3,
+  maxCount: 1,
+})
+
+app.use(Tooltip)
+app.use(Switch)
 
 app.use(message)
 app.mount('#app')

+ 2 - 0
src/iframe/tab-group.js

@@ -4,4 +4,6 @@ import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
 
 const app = createApp(App);
+import CoutomSentry from "@/uilts/sentry.js"
+CoutomSentry.initVue(app)
 app.mount('#app');

+ 5 - 1
src/log-center/logEnum.js

@@ -47,7 +47,11 @@ export const objectType = {
     // 安装成功
     chrome_extension_installed: 'chrome-extension-installed',
     // 发送事件异常
-    chrome_extension_sendmessage_error: 'chrome-extension-sendmessage-error'
+    chrome_extension_sendmessage_error: 'chrome-extension-sendmessage-error',
+    // background文件安装catch异常
+    background_function_catch: 'background-function-catch',
+    // background 文件chrome 函数 try
+    background_function_try:'background-function-try',
 }
 
 export const pageSource = {

+ 6 - 1
src/log-center/logger.js

@@ -1,4 +1,4 @@
-import { logApi } from '@/http/logApi'
+import { logApi, reportFrontLogApi } from '@/http/logApi'
 import { getBrowser } from '@/uilts/help.js';
 import { logType } from './logEnum.js';
 import { getChromeStorage } from '@/uilts/chromeExtension'
@@ -55,6 +55,11 @@ function paramsPretreatmentAndRequest(logType, eventData, extParams) {
             pageSource,
             ...obj
         }
+    }).then(res => {
+    }).catch(err => {
+        reportFrontLogApi({
+            logData: JSON.stringify(err)
+        })
     })
 }
 

+ 4 - 5
src/logic/background/help.js

@@ -26,11 +26,10 @@ export function httpNetWork(funcName, data, sender) {
 export const setContentMessage = (obj) => {
     chrome.tabs.query({}, (tabs = []) => {
         if (tabs.length) {
-            let tab = tabs.filter((item) => { return item.active == true }) || []
-            if (tab.length) {
-                // 未读消息
-                chrome.tabs.sendMessage(tab[0].id, obj);
-            }
+            tabs = tabs.filter((item) => { return item.active && item.selected && item.highlighted }) || []
+            tabs.forEach((item)=>{
+                chrome.tabs.sendMessage(item.id, obj);
+            })
         }
     })
 }

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

@@ -431,7 +431,7 @@ export const setPopupConfig = (activeInfo) => {
     })
 }
 
-export const getLuckMessage = () => {
+export const getSysMessage = () => {
     // 请求通知接口
     fetchGetAllUnReadNotices({})
         .then((res) => {
@@ -491,4 +491,20 @@ export const nftTxtPublish = (params, sender) => {
             chrome.tabs.sendMessage(sender.tab.id, { actionType: 'BACK_NFT_PUBLISH_DONE', data: res.data }, (res) => { console.log(res) });
         }
     })
+}
+
+export const checkShowPublishDialog = (params) => {
+    const twitterUrl = 'https://twitter.com';
+    createTabShowGiveaway({
+        url: twitterUrl
+    })
+}
+
+const createTabShowGiveaway = (params) => {
+    setChromeStorage({showGiveawayData : JSON.stringify({
+        show: true
+    })});
+    chrome.tabs.create({
+        url: params.url,
+    });
 }

+ 5 - 5
src/logic/content/ParseCard.js

@@ -286,7 +286,7 @@ class ParseCard {
 
                 if (_iframe.length == 1) {
                     for (let i = 0; i < dom.childNodes.length; i++) {
-                        if (dom.children[i].tagName.toLowerCase() != 'iframe') {
+                        if (dom.childNodes[i].dataset && dom.childNodes[i].dataset.testid && dom.childNodes[i].dataset.testid == 'card.wrapper') {
                             dom.children[i].style.display = 'none'
                         }
 
@@ -339,7 +339,7 @@ class ParseCard {
         if (dom) {
             type = 'card'
             for (let i = 0; i < dom.childNodes.length; i++) {
-                if (dom.children[i].tagName.toLowerCase() != 'iframe') {
+                if (dom.childNodes[i].dataset && dom.childNodes[i].dataset.testid && dom.childNodes[i].dataset.testid == 'card.wrapper') {
                     dom.children[i].style.display = 'none'
                 }
             }
@@ -373,7 +373,7 @@ class ParseCard {
         let dom = dom_card.querySelector('div[aria-labelledby]')
         if (dom) {
             for (let i = 0; i < dom.childNodes.length; i++) {
-                if (dom.children[i].tagName.toLowerCase() != 'iframe') {
+                if (dom.childNodes[i].dataset && dom.childNodes[i].dataset.testid && dom.childNodes[i].dataset.testid == 'card.wrapper') {
                     dom.children[i].style.display = 'none'
                 }
             }
@@ -395,7 +395,7 @@ class ParseCard {
         dom.style = 'min-height:500px'
         if (dom) {
             for (let i = 0; i < dom.childNodes.length; i++) {
-                if (dom.children[i].tagName.toLowerCase() != 'iframe' && (i !== 0)) {
+                if (dom.childNodes[i].dataset && dom.childNodes[i].dataset.testid && dom.childNodes[i].dataset.testid == 'card.wrapper') {
                     dom.children[i].style.display = 'none'
                 }
             }
@@ -421,7 +421,7 @@ class ParseCard {
         let dom = dom_card.querySelector('div[aria-labelledby]')
         if (dom) {
             for (let i = 0; i < dom.childNodes.length; i++) {
-                if (dom.children[i].tagName.toLowerCase() != 'iframe') {
+                if (dom.childNodes[i].dataset && dom.childNodes[i].dataset.testid && dom.childNodes[i].dataset.testid == 'card.wrapper') {
                     dom.children[i].style.display = 'none'
                 }
             }

+ 43 - 22
src/logic/content/twitter.js

@@ -587,29 +587,39 @@ function _createBtnDom() {
     dom.loadingImg = loadingImg;
 }
 
-function addSliderNavDeBtn(isSmall = false) {
-    if (!isSmall) {
-        let bigDom = document.querySelector('a[href="/compose/tweet"]').parentNode.parentNode;
-        let deBtn = document.getElementById('de-btn');
-        if (bigDom && !deBtn) {
-            dom && dom.deBtn && bigDom.appendChild(dom.deBtn);
-            Report.reportLog({
-                pageSource: Report.pageSource.mainPage,
-                businessType: Report.businessType.buttonView,
-                objectType: Report.objectType.buttonMain
-            });
-        }
-    } else {
-        let smallDom = document.querySelector('a[href="/compose/tweet"]').parentNode.parentNode;
-        let deBtn3 = document.getElementById('de-btn3');
-        if (smallDom && !deBtn3) {
-            dom && dom.deBtn3 && smallDom.appendChild(dom.deBtn3);
-            Report.reportLog({
-                pageSource: Report.pageSource.mainPage,
-                businessType: Report.businessType.buttonView,
-                objectType: Report.objectType.buttonMain
-            });
+async function addSliderNavDeBtn(isSmall = false) {
+    try {
+        if (!isSmall) {
+            let bigDom = document.querySelector('a[href="/compose/tweet"]').parentNode.parentNode;
+            let deBtn = document.getElementById('de-btn');
+            if (bigDom && !deBtn) {
+                dom && dom.deBtn && bigDom.appendChild(dom.deBtn);
+                Report.reportLog({
+                    pageSource: Report.pageSource.mainPage,
+                    businessType: Report.businessType.buttonView,
+                    objectType: Report.objectType.buttonMain
+                });
+            }
+        } else {
+            let smallDom = document.querySelector('a[href="/compose/tweet"]').parentNode.parentNode;
+            let deBtn3 = document.getElementById('de-btn3');
+            if (smallDom && !deBtn3) {
+                dom && dom.deBtn3 && smallDom.appendChild(dom.deBtn3);
+                Report.reportLog({
+                    pageSource: Report.pageSource.mainPage,
+                    businessType: Report.businessType.buttonView,
+                    objectType: Report.objectType.buttonMain
+                });
+            }
         }
+    } catch (e) {
+        console.log(e)
+    }
+
+    let { show = false } = await getChromeStorage('showGiveawayData') || {};
+    if(show) {
+        chrome.storage.local.remove("showGiveawayData");
+        showPublishDialog()
     }
 }
 
@@ -2355,3 +2365,14 @@ export const loginSuccessHandle = () => {
         addJoinedGroupList();
     })
 }
+
+export const showPublishDialog = () => {
+    let bigBtn = document.getElementById('de-btn');
+    let smallBtn = document.getElementById('de-btn3');
+
+    if(bigBtn) {
+        bigBtn.click();
+    } else if(smallBtn) {
+        smallBtn.click();
+    }
+}

+ 1 - 1
src/manifest.json

@@ -2,7 +2,7 @@
     "manifest_version": 3,
     "name": "DeNet",
     "description": "Growing more twitter followers with Denet",
-    "version": "1.1.2",
+    "version": "1.1.3",
     "background": {
         "service_worker": "/js/background.js"
     },

+ 23 - 0
src/types/global.js

@@ -0,0 +1,23 @@
+/**
+ * 全局通用字段定义
+ */
+
+/**
+ * 玩法类型
+ * 普通任务:common=1;
+ * 抽奖:lottery=2
+ */
+export const PlayType = {
+  common: 1,
+  lottery: 2,
+};
+
+/**
+ * 奖品类型
+ * 货币:money=1;
+ * 自定义奖品:custom=2
+ */
+export const RewardType = {
+  money: 1,
+  custom: 2,
+};

+ 1 - 0
src/types/index.js

@@ -0,0 +1 @@
+export * from "@/types/global";

+ 3 - 3
src/uilts/help.js

@@ -177,7 +177,7 @@ export function formatSecondsAsTime(secs) {
 }
 
 // 抽奖红包 left
-export function formatSecondsAsDaysOrTime(secs) {
+export function formatSecondsAsDaysOrTime(secs, showLeft = true) {
   if (secs <= 0) {
     return '00:00:00'
   }
@@ -185,9 +185,9 @@ export function formatSecondsAsDaysOrTime(secs) {
   var hr = Math.floor(secs / 3600)
   if (hr >= 24) {
     let day = parseInt(hr / 24)
-    text = `${day} days left`
+    text = showLeft ? `${day} days left` : `${day} days`
   } else {
     text = formatSecondsAsTime(secs)
   }
   return text
-}
+}

+ 26 - 0
src/uilts/sentry.js

@@ -0,0 +1,26 @@
+import * as Sentry from "@sentry/vue";
+import { BrowserTracing } from "@sentry/tracing";
+import { appVersionCode } from '@/http/configAPI.js'
+
+class CoutomSentry {
+    initVue(app) {
+        Sentry.init({
+            app,
+            dsn: "https://529fc1c357b248eda7473c119093f5db@sentry.piaoquantv.com/5",
+            integrations: [
+                new BrowserTracing({
+                    // routingInstrumentation: Sentry.vueRouterInstrumentation(router),
+                    tracingOrigins: ["localhost", "my-site-url.com", /^\//],
+                }),
+            ],
+            // Set tracesSampleRate to 1.0 to capture 100%
+            // of transactions for performance monitoring.
+            // We recommend adjusting this value in production
+            tracesSampleRate: 1.0,
+            release: `${process.env.NODE_ENV}-${appVersionCode}`,
+            logErrors: true
+        });
+    }
+}
+
+export default new CoutomSentry()

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

@@ -1,6 +1,24 @@
 <!-- 自定义卡片红包封面 -->
 <template>
-    <div class="not-open">
+    <div class="custom-card" v-if="data.customPosterUrl">
+        <img class="customImg" :src="data.customPosterUrl" />
+        <div class="common-bottom">
+            <div class="theme">
+                <img v-if="isLottaryCpd" class="icon" :src="require('@/assets/svg/icon-last-time.svg')"/>
+                <span v-if="isLottaryCpd" class="time" >{{formatSecondsAsDaysOrTime(data.validityDuration * 3600)}}</span>
+                <span class="info">{{isLottaryCpd ? 'Left' : 'Instant Giveaway'}}</span>
+            </div>
+            <div class="winner-info">
+                <span class="count">{{data.totalCount}} Winners</span>
+                <span>to Share </span>
+                <span class="prize-name">{{isMoneyRewardCpd ? data.amountValue + ' ' + data.tokenSymbol : data.customizedReward}}</span>
+            </div>
+            <div class="open" @click="open">
+                {{isLottaryCpd ? 'Participate Now' : 'Open Now'}}
+            </div>
+        </div>
+    </div>
+    <div class="not-open" v-else>
         <img class="cover" v-if="data.type == 2" :src="require('@/assets/svg/img-preview-draw-bg.svg')"  />
         <img class="cover" v-else :src="require('@/assets/subject/001-card.png')"  />
 
@@ -29,7 +47,7 @@
         <div class="money-area">
             <div class="txt">{{data.currencyCode == 'USD' ? 'USD' : data.tokenSymbol}} GIVEAWAY</div>
             <div class="coin">
-                <img :src="data.currencyIconUrl" />
+                <img :src="data.currencyIconUrl || imgHeaderCover" />
                 <span id="preview-after-amount"
                     :style="{
                         fontSize: amountFontSize + 'px'
@@ -47,8 +65,11 @@
 </template>
 
 <script setup>
-import { defineProps, defineEmits, watch, ref } from "vue";
+import { defineProps, defineEmits, watch, ref, computed } from "vue";
 import { formatSecondsAsDaysOrTime } from "@/uilts/help";
+import { RewardType, PlayType } from "@/types";
+
+const imgHeaderCover = require('@/assets/img/icon-header-cover.png');
 
 const props = defineProps({
     data: {
@@ -60,6 +81,7 @@ const props = defineProps({
                 tokenSymbol: "",
                 type: 1,
                 validityDuration: '',
+                customPosterUrl: '',
                 userInfo: {
                     avatarUrl: "",
                     nickName: "",
@@ -71,10 +93,21 @@ const props = defineProps({
 
 let amountFontSize = ref(60);
 
+let isMoneyRewardCpd =computed(() => {
+    return props.data.rewardType === RewardType.money
+});
+
+let isLottaryCpd = computed(() => props.data.type === PlayType.lottery);
+
 watch(() => props.data, () => {
-    let lenstr = document.querySelector('#preview-after-amount').innerHTML.length;
-    let num = parseInt(360/lenstr);
-    amountFontSize.value = num < 56 ? num : 56;
+    let id = isMoneyRewardCpd.value ? 'preview-after-amount' : 'custom-name';
+    let baseWidth = isMoneyRewardCpd.value ? 56 : 22;
+    let lendom = document.querySelector(`#${id}`)
+    if(lendom) {
+        let lenstr = lendom.innerHTML.length;
+        let num = parseInt(360/lenstr);
+        amountFontSize.value = num < baseWidth ? num : baseWidth;
+    }
 })
 
 
@@ -242,4 +275,108 @@ const open = () => {
         z-index: 3;
     }
 }
+
+.custom-card {
+    position: relative;
+    width: 100%;
+    overflow: hidden;
+    position: relative;
+    border-radius: 20px;
+    .customImg {
+        width: 100%;
+        min-height: 200px;
+    }
+    .cover {
+        width: 100%;
+        min-height: 350px;
+        border-radius: 20px 20px 0 0;
+    }
+    .gift {
+        width: 210px;
+        position: absolute;
+        left: 50%;
+        top: 83px;
+        transform: translateX(-50%);
+    }
+    .prize {
+        width: 100%;
+        position: absolute;
+        top: 284px;
+        left: 0;
+        height: 47px;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        font-style: normal;
+        font-weight: 800;
+        font-size: 22px;
+        line-height: 47px;
+        letter-spacing: 0.3px;
+
+        .icon {
+            width: 24px;
+        }
+        .name {
+            padding: 0 7px;
+            color: #fff;
+        }
+        .total {
+            color: #F5C03F;
+        }
+    }
+    .common-bottom {
+        width: 100%;
+        height: 125px;
+        background:#111214;
+        border-radius: 0 0 20px 20px;
+        padding: 10px 16px;
+        font-weight: 500;
+        font-size: 12px;
+        line-height: 14px;
+        letter-spacing: 0.3px;
+        color: #838383;
+        line-height: 20px;
+        .theme {
+            display: flex;
+            height: 20px;
+            align-items: center;
+            justify-content: flex-start;
+            .icon {
+                width: 12px;
+            }
+            .time {
+                margin: 0 4px;
+                color: #1D9BF0;
+            }
+        }
+        .winner-info {
+            display: flex;
+            height: 20px;
+            align-items: center;
+            justify-content: flex-start;
+            margin-bottom: 13px;
+            .count{
+                color: #1D9BF0;
+                margin-right: 4px;
+            }
+            .prize-name {
+                color: #1D9BF0;
+                margin-left: 4px;
+            }
+        }
+        .open {
+            width: 100%;
+            height: 45px;
+            background: linear-gradient(180deg, #4AB6FF 0%, #1D9BF0 100%, #1D9BF0 100%);
+            border: 1.5px solid rgba(255, 255, 255, 0.15);
+            border-radius: 52px;
+            line-height: 45px;
+            text-align: center;
+            cursor: pointer;
+            font-weight: 800;
+            font-size: 16px;
+            color: #FFFFFF;
+        }
+    }
+}
 </style>

+ 201 - 0
src/view/components/custom-card-horizontal-cover.vue

@@ -0,0 +1,201 @@
+<!-- 自定义卡片红包封面 -->
+<template>
+    <div class="card-wrapper">
+        <template v-if="data.customPosterUrl">
+            <img class="customImg" :src="data.customPosterUrl" />
+        </template>
+        <template v-else>
+            <img :src="require('@/assets/img/img-preview-draw-after-bg.png')"
+                v-if="data.type == 2"
+                class="card-cover">
+            <img :src="require('@/assets/subject/img-card-cover-blue.png')"
+                v-else
+                class="card-cover"/>
+            <div class="user-info">
+                <img :src="data.userInfo.avatarUrl" 
+                class="avatar"/> {{data.userInfo.name}}
+            </div>
+            <div class="content-text">
+                <div class="title">
+                    {{data.tokenSymbol}} GIVEAWAY
+                </div>
+                <div class="center"
+                    :style="{
+                        fontSize: amountFontSize + 'px'
+                    }">
+                    <img :src="data.currencyIconUrl || imgHeaderCover" class="icon">
+                    <span id="preview-before-amount">
+                        {{data.amountValue}}
+                    </span>
+                </div>
+                <div class="desc">
+                    <template  v-if="data.type == 2">
+                        <img class="icon-clock" 
+                        :src="require('@/assets/svg/icon-preview-clock.svg')" />  {{data.validityDuration}} H
+                        <img class="icon-trophy" 
+                        :src="require('@/assets/svg/icon-preview-trophy.svg')" /> <span class="trophy-count">{{data.totalCount}} WINNERS</span>
+                    </template>
+                    <template v-else>
+                        {{data.totalCount}} WINNERS TO SHARE
+                    </template>
+                </div>
+            </div>
+        </template>
+        <div class="bottom-bar" v-if="showBottom">
+            <div class="title">
+                DeNet.me
+            </div>
+            <div class="desc">
+                🎁 <template v-if="data.tokenSymbol=='USD'">$</template>{{data.amountValue}} GIVEAWAY
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { defineProps, defineEmits, watch, ref } from "vue";
+
+const imgHeaderCover = require('@/assets/img/icon-header-cover.png');
+
+const props = defineProps({
+    data: {
+        type: Object,
+        default: () => {
+            return {
+                totalCount: 0,
+                amountValue: 0,
+                tokenSymbol: "",
+                type: 1,
+                validityDuration: '',
+                customPosterUrl: '',
+                userInfo: {
+                    avatarUrl: "",
+                    nickName: "",
+                },
+            };
+        },
+    },
+    showBottom: {
+        type: Boolean,
+        default: true
+    }
+});
+
+let amountFontSize = ref(60);
+
+watch(() => props.data, () => {
+    let dom = document.querySelector('#preview-after-amount');
+    if (dom) {
+        let lenstr = dom && dom.innerHTML.length;
+        let num = parseInt(360/lenstr);
+        amountFontSize.value = num < 56 ? num : 56;
+    }
+})
+</script>
+
+<style scoped lang="scss">
+.card-wrapper {
+    width: 491px;
+    border: 1px solid #D1D9DD;
+    background: #ffffff;
+    box-sizing: border-box;
+    overflow: hidden;
+    position: relative;
+    box-sizing: border-box;
+    border-radius: 16px;
+    left: 73px;
+    top: 238px;
+
+    .customImg {
+        width: 100%;
+        min-height: 200px;
+    }
+
+    .user-info {
+        position: absolute;
+        left: 8px;
+        top: 8px;
+        z-index: 100;
+        display: flex;
+        align-items: center;
+        font-size: 16px;
+        color: #FFF;
+        width: max-content;
+
+        img {
+            width: 24px;
+            height: 24px;
+            border: 2px solid #FFF4DB;
+            box-sizing: border-box;
+            border-radius: 50%;
+            margin-right: 10px;
+        }
+    }
+    .content-text {
+        position: absolute;
+        top: 53px;
+        left: 35px;
+        .title {
+            font-weight: 800;
+            font-size: 16px;
+            color: #ffffff;
+        }
+        .center {
+            padding: 12px 0;
+            box-sizing: border-box;
+            font-weight: 800;
+            font-size: 56px;
+            color: #fff;
+            display: flex;
+            align-items: center;
+            .icon {
+                width: 46px;
+                height: 46px;
+                margin-right: 10px;
+                border: 3px solid #fff;
+                border-radius: 50%;
+            }
+        }
+        .desc {
+            font-weight: 800;
+            font-size: 13px;
+            color: #ffffff;
+            display: flex;
+            align-items: center;
+
+            .icon-clock {
+                margin-right: 4px;
+            }
+
+            .icon-trophy {
+                margin-left: 8px;
+                margin-right: 4px;
+            }
+
+            .trophy-count {
+                color: #FFCC4D;
+            }
+        }
+    }
+    .card-cover {
+        width: 100%;
+        object-fit: contain;
+    }
+
+    .bottom-bar {
+        padding: 12px;
+        box-sizing: border-box;
+        .title {
+            color: #566370;
+            font-weight: 400;
+            font-size: 14px;
+            margin-bottom: 6px;
+        }
+        .desc {
+            font-weight: 500;
+            font-size: 15px;
+            color: #101419;
+        }
+    }
+}
+</style>

+ 51 - 12
src/view/content/message/index.vue

@@ -16,6 +16,14 @@
                     <img :src="require('@/assets/img/icon-message-close.png')" alt />
                 </div>
             </div>
+            <!-- 自定义系统消息 -->
+            <div class="denet-message-area" @click="clickItem(item)" v-if="item.bizType == 3">
+                <img :src="item.bizData.icon" alt />
+                <span>{{ item.bizData.text }}</span>
+                <div class="denet-message-close" @click.stop="clickClose(item)">
+                    <img :src="require('@/assets/img/icon-message-close.png')" alt />
+                </div>
+            </div>
         </template>
     </div>
 </template>
@@ -24,17 +32,25 @@ import { onMounted, reactive } from "vue";
 let state = reactive({
     list: [],
 })
+let timer, now_time
 
 // 过5秒消失逻辑
 const overTimeClose = () => {
-    setTimeout(() => {
-        let now_time = new Date().getTime()
+    if (timer) {
+        return
+    }
+    timer = setInterval(() => {
+        if (state.list.length == 0) {
+            clearInterval(timer)
+            timer = null
+        }
+        now_time = new Date().getTime()
         for (let i in state.list) {
-            if ((now_time - state.list[i].read_time) > 4500) {
+            if ((now_time - state.list[i].read_time) >= 5000) {
                 state.list.splice(i, 1)
             }
         }
-    }, 5000)
+    }, 1000)
 }
 
 const clickClose = (item) => {
@@ -47,11 +63,18 @@ const clickClose = (item) => {
 }
 
 const clickItem = (item) => {
-    if (item.bizType == 1) {
-        // 跳转详情页
-        window.open(`https://twitter.com/${item.bizData.twitterAccount}/status/${item.bizData.twitterId}`)
-    } else {
-        window.open('https://twitter.com/search?q=%23DeNet&src=typed_query')
+    switch (String(item.bizType)) {
+        case '1':
+            // 跳转详情页
+            window.open(`https://twitter.com/${item.bizData.twitterAccount}/status/${item.bizData.twitterId}`)
+            break;
+        case '2':
+            window.open('https://twitter.com/search?q=%23DeNet&src=typed_query')
+            break;
+        case '3':
+            // 自定义消息
+            window.open(item.bizData.jumpUrl)
+            break
     }
 }
 
@@ -88,7 +111,10 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
 })
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss" >
+#denet_message{
+    text-align: left;
+}
 .denet-message {
     position: fixed;
     max-height: 100%;
@@ -96,7 +122,8 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
     top: 0;
     right: 0;
     width: 500px;
-    z-index: 9999;
+    z-index: 9999;    
+    text-align: left;
 
     &-area {
         width: 344px;
@@ -109,7 +136,9 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
         cursor: pointer;
         filter: drop-shadow(0px 5px 20px rgba(0, 0, 0, 0.22));
         margin-left: 129px;
-
+        animation: right_to_left 1s;
+        text-align: left;
+        
         img:first-child {
             width: 40px;
             height: 40px;
@@ -154,4 +183,14 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
         }
     }
 }
+
+@keyframes right_to_left {
+    0% {
+        right: -400px;
+    }
+
+    100% {
+        right: 0;
+    }
+}
 </style>

+ 353 - 0
src/view/iframe/publish/components/giveaway-poster.vue

@@ -0,0 +1,353 @@
+<template>
+    <div class="show">
+        <div class="row">
+            <img class="tagImg" :src=" require('@/assets/svg/poster-befor.svg') " />
+            <div class="l">
+                <div :style="{'zoom': reviewCanvasParams.zoomL}">
+                    <custom-card-horizontal-cover
+                        :data="{
+                            totalCount: baseFormData.totalCount,
+                            amountValue: baseFormData.amountValue,
+                            tokenSymbol: currentCurrencyInfo.tokenSymbol,
+                            currencyIconUrl: currentCurrencyInfo.iconPath,
+                            type: baseFormData.type,
+                            validityDuration: baseFormData.validityDuration,
+                            customPosterUrl: customPosterInfo && customPosterInfo.before && customPosterInfo.before.imagePath || '',
+                            userInfo: {
+                                nickName: userInfo.name,
+                                avatarUrl: userInfo.avatarUrl
+                            },
+                        }"
+                        :showBottom="false">
+                    </custom-card-horizontal-cover>
+                </div>
+            </div>
+            <div class="r">
+                <div class="desc"><span>1080*567</span> JPG, PNG</div>
+                <div
+                    class="box"
+                    data-type="1"
+                    @drop="dragImg"
+                    @dragenter="dragEnter"
+                    @dragover="dragEnter"
+                    @dragleave="dragLeave">
+                    <span class="drag">Drag Image Here</span>
+                    <div class="upload">
+                        <input
+                            class="file"
+                            type="file"
+                            data-type="1"
+                            @change="fileChange"
+                            accept="image/png,image/jpg,image/jpeg" />
+                        <span>Browse</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <img class="tagImg" :src=" require('@/assets/svg/poster-after.svg') " />
+            <div class="t">
+                <div
+                    :style="{
+                        'zoom': reviewCanvasParams.zoomT,
+                        'position': 'absolute',
+                        'width': '350px',
+                    }">
+                    <custom-card-cover
+                        :data="{
+                            totalCount: baseFormData.totalCount,
+                            amountValue: baseFormData.amountValue,
+                            tokenSymbol: currentCurrencyInfo.tokenSymbol,
+                            currencyIconUrl: currentCurrencyInfo.iconPath,
+                            type: baseFormData.type,
+                            validityDuration: baseFormData.validityDuration,
+                            customPosterUrl: customPosterInfo && customPosterInfo.after && customPosterInfo.after.imagePath || '',
+                            userInfo: {
+                                nickName: userInfo.name,
+                                avatarUrl: userInfo.avatarUrl
+                            }
+                        }">
+                    </custom-card-cover>
+                </div>
+            </div>
+            <div class="r">
+                <div class="desc"><span>1080*1080</span> JPG, PNG</div>
+                <div
+                    class="box"
+                    data-type="2"
+                    @drop="dragImg"
+                    @dragenter="dragEnter"
+                    @dragover="dragEnter"
+                    @dragleave="dragLeave">
+                    <span class="drag">Drag Image Here</span>
+                    <div class="upload">
+                        <input
+                            class="file"
+                            type="file"
+                            data-type="2"
+                            @change="fileChange"
+                            accept="image/png,image/jpg,image/jpeg" />
+                        <span>Browse</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="footer">
+            <button class="confirm" @click="confirmAction">Confirm</button>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, nextTick, defineEmits, defineProps, onMounted } from 'vue';
+import { message } from 'ant-design-vue';
+import customCardCover from '@/view/components/custom-card-cover.vue'
+import customCardHorizontalCover from '@/view/components/custom-card-horizontal-cover.vue'
+import { getChromeStorage } from "@/uilts/chromeExtension"
+import { getUser } from "@/http/publishApi"
+
+const userInfo = ref({});
+const emits = defineEmits(['selectImage', 'confirmData']);
+const reviewCanvasParams = reactive({
+    zoomL: 1,
+    zoomT: 1
+});
+
+defineProps({
+    baseFormData: {
+        type: Object,
+        default: () => {
+            return {
+            }
+        }
+    },
+    currentCurrencyInfo: {
+        type: Object,
+        default: () => {
+            return {
+            }
+        }
+    },
+    customPosterInfo: {
+        type: Object,
+        default: () => {
+            return {
+            }
+        }
+    }
+})
+
+const getUserInfo = (cb) => {
+    getChromeStorage('userInfo', (res) => {
+        if(res) {
+            userInfo.value = res;
+        }
+        cb && cb(res);
+    })
+}
+
+const getUserName = (screenName) => {
+    getUser({
+        params:{
+            screenName
+        }
+    }).then(res => {
+        console.log(res);
+        if(res.code == 0) {
+            userInfo.value.name = res.data.name || ''
+        }
+    });
+}
+
+const fileChange = (e) => {
+    if (e && e.target) {
+        let file = e.target.files[0]
+        let type = e.target.dataset && e.target.dataset.type || '';
+        cropImage(file, type)
+        e.target.value = '';
+    }
+}
+
+const dragImg = (e) => {
+    e.preventDefault();
+    e.target.classList.remove('light');
+
+    // upload
+    if (e && e.dataTransfer) {
+        let file = e.dataTransfer.files[0];
+        let type = e.target.dataset && e.target.dataset.type || '';
+        // 格式判断
+        if (['image/png', 'image/jpg', 'image/jpeg'].indexOf(String(file.type).toLowerCase()) == -1) {
+            message.warning(`This format is not currently supported`);
+        } else {
+            cropImage(file, type)
+        }
+    }
+}
+const dragEnter = (e) => {
+    e.preventDefault();
+    e.target.classList.add('light')
+}
+const dragLeave = (e) => {
+    e.preventDefault();
+    e.target.classList.remove('light')
+}
+
+const cropImage = (file, type) => {
+    emits('selectImage', {
+        file: URL.createObjectURL(file),
+        type,
+    })
+}
+
+const calcPreviewCanvasParams = () => {
+    nextTick(() => {
+        let lH = document.querySelector('div.row').offsetHeight;
+        reviewCanvasParams.zoomL = (lH - 100) / 266;
+        reviewCanvasParams.zoomT = (lH - 20) / 500;
+    });
+}
+
+const confirmAction = () => {
+    emits('confirmData')
+}
+
+onMounted(() => {
+    calcPreviewCanvasParams();
+    getUserInfo((res) => {
+        if(res) {
+            getUserName(res.nickName);
+        }
+    });
+    window.addEventListener('resize',function () {
+        calcPreviewCanvasParams();
+    })
+})
+</script>
+
+<style lang="scss" scoped>
+.show {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: 100%;
+    .row {
+        position: relative;
+        height: calc(50% - 40px);
+        padding: 0 75px;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+
+        &:nth-child(2) {
+            border-top: 1px solid #ececec;
+        }
+        .tagImg {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 177px;
+            height: 32px;
+        }
+        .l {
+            display: flex;
+            width: 50%;
+            align-items: center;
+            justify-content: center;
+            height: calc(100% - 100px);
+        }
+        .t {
+            display: flex;
+            width: 50%;
+            align-items: center;
+            justify-content: center;
+            height: calc(100% - 20px);
+        }
+        .r {
+            width: 50%;
+            margin-left: 63px;
+            height: calc(100% - 100px);
+            .desc {
+                height: 33px;
+                font-size: 16px;
+                font-weight: 600;
+                span {
+                    color: #1D9BF0;
+                }
+            }
+            .box {
+                height: calc(100% - 33px);
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                justify-content: center;
+                border-radius: 10px;
+                border: 1px dashed #B4B4B4;
+
+                &.light {
+                    border: 1px dashed #ff0000;
+                }
+                .drag {
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    color: #AEAEAE;
+                    font-size: 15px;
+                    font-weight: 500;
+                    height: calc(100% - 76px);
+                }
+                .upload {
+                    user-select: none;
+                    position: relative;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    width: calc(100% - 32px);
+                    height: 44px;
+                    margin: 0 16px;
+                    font-size: 15px;
+                    color: #1D9BF0;
+                    border-radius: 22px;
+                    background-color: rgba($color: #1D9BF0, $alpha: .1);
+                    .file {
+                        position: absolute;
+                        z-index: 2;
+                        opacity: 0;
+                        cursor: pointer;
+                        top: 0;
+                        left: 0;
+                        width: 100%;
+                        height: 100%;
+                    }
+                }
+            }
+        }
+    }
+    .footer {
+        display: flex;
+        align-items: center;
+        justify-content: right;
+        height: 80px;
+        text-align: right;
+        padding-right: 30px;
+        border-top: 1px solid #ececec;
+        .confirm {
+            cursor: pointer;
+            border: 0;
+            width: 200px;
+            height: 50px;
+            color: #ffffff;
+            font-size: 18px;
+            font-weight: 700;
+            border-radius: 25px;
+            background: #1D9BF0;
+        }
+    }
+}
+
+:deep() .card-wrapper {
+    top: unset;
+    left: unset;
+}
+</style>

+ 26 - 147
src/view/iframe/publish/components/preview-card.vue

@@ -25,6 +25,7 @@
                             currencyIconUrl: currentCurrencyInfo.iconPath,
                             type: baseFormData.type,
                             validityDuration: baseFormData.validityDuration,
+                            customPosterUrl: customPosterInfo && customPosterInfo.after && customPosterInfo.after.imagePath || '',
                             userInfo: {
                                 nickName: userInfo.name,
                                 avatarUrl: userInfo.avatarUrl
@@ -51,51 +52,22 @@
                         </div>
                     </div>
                 </div>
-                <div class="card-wrapper" 
-                    :style="{'zoom': reviewCanvasParams.zoom}">
-                    <img :src="require('@/assets/img/img-preview-draw-after-bg.png')"
-                        v-if="baseFormData.type == 2"
-                        class="card-cover">
-                    <img :src="require('@/assets/subject/img-card-cover-blue.png')"
-                        v-else
-                        class="card-cover"/>
-                    <div class="bottom-bar">
-                        <div class="title">
-                            DeNet.me
-                        </div>
-                        <div class="desc">
-                            🎁 <template v-if="currentCurrencyInfo.tokenSymbol=='USD'">$</template>{{baseFormData.amountValue}} GIVEAWAY
-                        </div>
-                    </div>
-                    <div class="user-info">
-                        <img :src="userInfo.avatarUrl" 
-                        class="avatar"/> {{userInfo.name}}
-                    </div>
-                    <div class="content-text">
-                        <div class="title">
-                            {{currentCurrencyInfo.tokenSymbol}} GIVEAWAY
-                        </div>
-                        <div class="center"
-                            :style="{
-                                fontSize: amountFontSize + 'px'
-                            }">
-                            <img :src="currentCurrencyInfo.iconPath" class="icon">
-                            <span id="preview-before-amount">
-                                {{baseFormData.amountValue}}
-                            </span>
-                        </div>
-                        <div class="desc">
-                            <template  v-if="baseFormData.type == 2">
-                                <img class="icon-clock" 
-                                :src="require('@/assets/svg/icon-preview-clock.svg')" />  {{baseFormData.validityDuration}} H
-                                <img class="icon-trophy" 
-                                :src="require('@/assets/svg/icon-preview-trophy.svg')" /> <span class="trophy-count">{{baseFormData.totalCount}} WINNERS</span>
-                            </template>
-                            <template v-else>
-                                {{baseFormData.totalCount}} WINNERS TO SHARE
-                            </template>
-                        </div>
-                    </div>
+                <div :style="{'zoom': reviewCanvasParams.zoom}">
+                    <custom-card-horizontal-cover
+                        :data="{
+                            totalCount: baseFormData.totalCount,
+                            amountValue: baseFormData.amountValue,
+                            tokenSymbol: currentCurrencyInfo.tokenSymbol,
+                            currencyIconUrl: currentCurrencyInfo.iconPath,
+                            type: baseFormData.type,
+                            validityDuration: baseFormData.validityDuration,
+                            customPosterUrl: customPosterInfo && customPosterInfo.before && customPosterInfo.before.imagePath || '',
+                            userInfo: {
+                                nickName: userInfo.name,
+                                avatarUrl: userInfo.avatarUrl
+                            }
+                        }">
+                    </custom-card-horizontal-cover>
                 </div>
             </div>
         </div>
@@ -104,9 +76,8 @@
 
 <script setup>
 import { ref, defineProps, onMounted, nextTick, watch, reactive, inject, onUnmounted } from "vue";
-
 import customCardCover from '@/view/components/custom-card-cover.vue'
-
+import customCardHorizontalCover from '@/view/components/custom-card-horizontal-cover.vue'
 import {getChromeStorage} from "@/uilts/chromeExtension"
 import {getUser} from "@/http/publishApi"
 
@@ -146,7 +117,14 @@ defineProps({
     amountFontSize: {
         type: Number,
         default: '56'
-    }
+    },
+    customPosterInfo: {
+        type: Object,
+        default: () => {
+            return {
+            }
+        }
+    },
 })
 
 const getUserInfo = (cb) => {
@@ -327,105 +305,6 @@ onUnmounted(() => {
             background-size: contain;
             background-repeat: no-repeat;
             height: 100%;
-            .card-wrapper {
-                width: 491px;
-                border: 1px solid #D1D9DD;
-                background: #ffffff;
-                box-sizing: border-box;
-                overflow: hidden;
-                position: relative;
-                box-sizing: border-box;
-                border-radius: 16px;
-                left: 73px;
-                top: 238px;
-
-                .user-info {
-                    position: absolute;
-                    left: 8px;
-                    top: 8px;
-                    z-index: 100;
-                    display: flex;
-                    align-items: center;
-                    font-size: 16px;
-                    color: #FFF;
-                    width: max-content;
-
-                    img {
-                        width: 24px;
-                        height: 24px;
-                        border: 2px solid #FFF4DB;
-                        box-sizing: border-box;
-                        border-radius: 50%;
-                        margin-right: 10px;
-                    }
-                }
-                .content-text {
-                    position: absolute;
-                    top: 53px;
-                    left: 35px;
-                    .title {
-                        font-weight: 800;
-                        font-size: 16px;
-                        color: #ffffff;
-                    }
-                    .center {
-                        padding: 12px 0;
-                        box-sizing: border-box;
-                        font-weight: 800;
-                        font-size: 56px;
-                        color: #fff;
-                        display: flex;
-                        align-items: center;
-                        .icon {
-                            width: 46px;
-                            height: 46px;
-                            margin-right: 10px;
-                            border: 3px solid #fff;
-                            border-radius: 50%;
-                        }
-                    }
-                    .desc {
-                        font-weight: 800;
-                        font-size: 13px;
-                        color: #ffffff;
-                        display: flex;
-                        align-items: center;
-
-                        .icon-clock {
-                            margin-right: 4px;
-                        }
-
-                        .icon-trophy {
-                            margin-left: 8px;
-                            margin-right: 4px;
-                        }
-
-                        .trophy-count {
-                            color: #FFCC4D;
-                        }
-                    }
-                }
-                .card-cover {
-                    width: 100%;
-                    object-fit: contain;
-                }
-
-                .bottom-bar {
-                    padding: 12px;
-                    box-sizing: border-box;
-                    .title {
-                        color: #566370;
-                        font-weight: 400;
-                        font-size: 14px;
-                        margin-bottom: 6px;
-                    }
-                    .desc {
-                        font-weight: 500;
-                        font-size: 15px;
-                        color: #101419;
-                    }
-                }
-            }
         }
 
         .left, .content-before {

+ 339 - 11
src/view/iframe/publish/give-dialog.vue

@@ -48,13 +48,25 @@
             <!-- 内容 -->
             <div class="body">
                 <!-- 充值组件 -->
-                <top-up v-if="showComType == 'topUp'" 
+                <top-up
+                    v-if="showComType == 'topUp'" 
                     :asyncIng="asyncIng"
                     :currentCurrencyInfo="tempCurrentCurrencyInfo"
-                    @topUpDone="topUpDone"></top-up>
+                    @topUpDone="topUpDone">
+                </top-up>
+
+                <!-- 自定义红包封面 -->
+                <giveaway-poster
+                    v-else-if="showComType == 'poster'"
+                    :baseFormData="baseFormData"
+                    :currentCurrencyInfo="currentCurrencyInfo"
+                    :customPosterInfo="customPosterInfo"
+                    @selectImage="selectImage"
+                    @confirmData="confirmData">
+                </giveaway-poster>
 
                 <!-- 表单填写容器 -->
-                <div class="body-content" v-if="showComType != 'topUp'">
+                <div class="body-content" v-else>
 
                     <!-- 货币列表  -->
                     <div class="currency-pop" v-show="showCurrencyPop">
@@ -165,9 +177,39 @@
                                             </div>
                                         </div>
                                     </div>
+                                    <div class="giveaway-poster" @click="customCoverImg">
+                                        <div class="show-img">
+                                            <div
+                                                :style="{
+                                                    'zoom': 55 / 500,
+                                                    'position': 'absolute',
+                                                    'width': '345px',
+                                                }">
+                                                <custom-card-cover
+                                                    :data="{
+                                                        totalCount: baseFormData.totalCount,
+                                                        amountValue: baseFormData.amountValue,
+                                                        tokenSymbol: currentCurrencyInfo.tokenSymbol,
+                                                        currencyIconUrl: currentCurrencyInfo.iconPath,
+                                                        type: baseFormData.type,
+                                                        validityDuration: baseFormData.validityDuration,
+                                                        customPosterUrl: customPosterData && customPosterData.after && customPosterData.after.imagePath || '',
+                                                    }">
+                                                </custom-card-cover>
+                                            </div>
+                                        </div>
+                                        <div class="show-font">
+                                            <span>Giveaway Poster</span>
+                                            <img class="new" v-if="customShowNewImage" :src=" require('@/assets/svg/img-new.svg') " />
+                                        </div>
+                                        <div class="show-placeholder">Replace</div>
+                                        <div class="arrow">
+                                            <img :src=" require('@/assets/svg/icon-cell-arrow-right.svg') " />
+                                        </div>
+                                    </div>
                                     <!--  提示 -->
                                     <ul class="tips-wrapper">
-                                        <li class="row">
+                                        <li class="row" style="white-space:nowrap;">
                                             Rewards can only be claimed after the target user completes all tasks you set. 
                                         </li>
                                         <li class="row">
@@ -264,6 +306,7 @@
                                             :postData="publishRes"
                                             :baseFormData="baseFormData"
                                             :amountFontSize="previewFontSize"
+                                            :customPosterInfo="customPosterData"
                                         ></preview-card>
                                     </div>
                                 </div>
@@ -366,24 +409,58 @@
             @cancel="messageBoxCancel"
             @confirm="messageBoxConfirm"
         ></message-box>
+
+        <!-- 裁剪 -->
+        <div class="dialog" v-if="cropperDialog">
+            <div class="corp-title">
+                <img class="back" :src="require('@/assets/svg/icon-back.svg')" @click="hiddenDialog" />
+                <span>Crop</span>
+            </div>
+            <div class="corp-content">
+                <vue-cropper
+                    ref="refCropper"
+                    :img="cropperOption.img"
+                    :output-type="cropperOption.outputType"
+                    :infoTrue="cropperOption.infoTrue"
+                    :full="cropperOption.full"
+                    :fixed="cropperOption.fixed"
+                    :fixed-number="cropperOption.fixedNumber"
+                    :auto-crop="cropperOption.autoCrop"
+                    :auto-crop-width="cropperOption.autoCropWidth"
+                    :auto-crop-height="cropperOption.autoCropHeight"
+                    :center-box="cropperOption.centerBox"
+                    :high="cropperOption.high"
+                    :max-img-size="cropperOption.max">
+                </vue-cropper>
+            </div>
+            <div class="corp-footer">
+                <button v-if="cropperLoading" class="confirm disable">
+                    <img :src=" require('@/assets/svg/icon-btn-loading.svg') " />
+                    <span>Confirm</span>
+                </button>
+                <button v-else class="confirm" @click="confirmImage">Confirm</button>
+            </div>
+        </div>
+        <div class="dialog-mask" v-if="cropperDialog"></div>
     </div>
 </template>
 
 <script setup>
-import { ref, watch, reactive, defineProps, defineEmits, onMounted, nextTick, provide } from "vue";
+import { ref, watch, reactive, defineProps, defineEmits, onMounted, nextTick, provide, getCurrentInstance } from "vue";
 import { postPublish, verifyPaypalResult, syncChainTokenRechargeRecord, getCurrencyInfoByCode } from "@/http/publishApi";
 import { getInviteGuildInfo, getInviteGuildInfoByOpenApi, saveInviteGuildInfo } from "@/http/discordApi";
 import { payCalcFee, getPayConfig } from "@/http/pay";
 import { getFrontConfig } from "@/http/account";
+import { uploadSignature, uploadFile } from '@/http/media';
 import {setChromeStorage, getChromeStorage} from "@/uilts/chromeExtension"
 import { debounce, getBit } from "@/uilts/help"
 import Report from "@/log-center/log"
 import { ElMessage, ElLoading } from "element-plus";
 import "element-plus/es/components/message/style/css";
 import "element-plus/es/components/loading/style/css";
-
+import 'vue-cropper/dist/index.css'
+import { VueCropper }  from "vue-cropper";
 import {create, all} from "mathjs";
-
 import messageBox from "@/view/components/message-box.vue";
 import currencyList from "@/view/components/currency-list.vue";
 import currencySelect from "@/view/components/currency-select.vue";
@@ -392,7 +469,11 @@ import followInput from "@/view/iframe/publish/components/follow-input";
 import paypalButton from "@/view/iframe/publish/components/paypal-button";
 import topUp from "@/view/iframe/publish/components/top-up.vue";
 import topUp2 from "@/view/iframe/publish/components/top-up2.vue";
-import  GlobalTip  from '@/view/components/global-tip.vue'
+import giveawayPoster from '@/view/iframe/publish/components/giveaway-poster.vue';
+import GlobalTip from '@/view/components/global-tip.vue'
+import customCardCover from '@/view/components/custom-card-cover.vue'
+
+const currentInstance = getCurrentInstance();
 
 const config = {
     number: 'BigNumber',
@@ -422,6 +503,28 @@ let dialogStyle = reactive({
     dialogContentWidth: 1100
 })
 
+let cropperOption = ref({
+    img: '',
+    full: true,
+    infoTrue: true,
+    fixed: true,
+    fixedNumber: [16, 8.396],
+    outputType: 'png',
+    autoCrop: true,
+    autoCropWidth: 99999,
+    autoCropHeight: 99999,
+    centerBox: true,
+    high: true,
+    max: 99999,
+})
+let cropperDialog = ref(false)
+let cropperLoading = ref(false)
+let cropperType = ref('before')
+let customPosterInfo = ref({})
+let customPosterData = ref({})
+let customShowNewImage = ref(false)
+let refCropper = ref('')
+
 // 当前展示组件内容 default(表单)  preview(预览)  topUp(充值)
 let showComType = ref("default"); 
 let currentComData = {
@@ -434,6 +537,9 @@ let currentComData = {
     topUp: {
         title: "Deposit",
     },
+    poster: {
+        title: "Giveaway Poster",
+    }
 };
 
 // 机器人开关
@@ -837,6 +943,19 @@ const goTopUp = () => {
     showComType.value = 'topUp';
 }
 
+/*
+ * 自定义封面
+ */
+const customCoverImg = () => {
+    customPosterInfo.value = {}
+    if (Object.keys(customPosterData.value).length > 0) {
+        customPosterInfo.value = customPosterData.value;
+    }
+    showComType.value = 'poster';
+    customShowNewImage.value = false;
+    setChromeStorage({ custom_poster_guide: Date.now() });
+}
+
 /**
  * 充值done事件
  */
@@ -942,10 +1061,22 @@ const submitRequest = async () => {
         receiveConditions,
         payAmountValue: amountValue,
         type: baseFormData.type,
+        posterType: 1,
         validityDuration
     };
     submitIng.value = true;
 
+    // 自定义封面
+    if (Object.keys(customPosterData.value).length > 0) {
+        formData['posterType'] = 2;
+        if (customPosterData.value && customPosterData.value.after) {
+            formData['customPosterInstalled'] = customPosterData.value.after.objectKey || ''
+        }
+        if (customPosterData.value && customPosterData.value.before) {
+            formData['customPosterUninstalled'] = customPosterData.value.before.objectKey || ''
+        }
+    }
+
     // 法币支付需要计算费率
     if(formData.amountCurrencyCode == "USD") { 
         let payAmountRes = await getPayAmount(amountValue);
@@ -1008,6 +1139,8 @@ const initParams = () => {
     setDiscordIptTxt({text: ''});
 
     discordInviteInfo.value = {};
+    customPosterInfo.value = {};
+    customPosterData.value = {};
 };
 
 const setDiscordIptTxt = ({text}) => {
@@ -1589,6 +1722,81 @@ const selectPublishMode = (params, index) => {
     setInputErrorMsg();
 }
 
+// 截图相关
+const showDialog = () => {
+    cropperDialog.value = true;
+}
+const hiddenDialog = () => {
+    cropperDialog.value = false;
+    cropperLoading.value = false;
+}
+const selectImage = (option) => {
+    // 设置图片
+    cropperOption.value.img = option.file
+    // 选取比例
+    if (option && option.type && option.type == 2) {
+        cropperType.value = 'after';
+        cropperOption.value.fixedNumber = [1, 1];
+    } else {
+        cropperType.value = 'before';
+        cropperOption.value.fixedNumber = [16, 8.396];
+    }
+    nextTick(() => {
+        showDialog()
+    })
+}
+const confirmImage = () => {
+    let contentType = 'image/png';
+    cropperLoading.value = true;
+    
+    if (refCropper.value) {
+        refCropper.value.getCropBlob(imgData => {
+            uploadSignature({
+                params: {
+                    bizType: 1,
+                    fileType: 1,
+                    contentType: contentType,
+                    fileSuffix: 'png',
+                }
+            }).then(res => {
+                let { code, data } = res;
+                if (code === 0) {
+                    let reader = new FileReader()
+                        reader.readAsArrayBuffer(imgData)
+                        reader.onload = function(e) {
+                            let execFile = e.target.result;
+                            uploadFile({
+                                url: data.url,
+                                data: new Blob([execFile]),
+                                headers: {
+                                    'Authorization': data.authorization,
+                                    'x-amz-date': data.date,
+                                    'Content-Type': contentType
+                                }
+                            }).then(res => {
+                                let { status } = res
+                                if (status == 200) {
+                                    successImage(data)
+                                }
+                            }).finally(() => {
+                                cropperLoading.value = false;
+                            })
+                        }
+                }
+            })
+        })
+    }
+}
+const successImage = (data) => {
+    hiddenDialog()
+    // setPosterInfo
+    customPosterInfo.value[cropperType.value] = data;
+}
+const confirmData = (data) => {
+    close()
+    customPosterData.value = customPosterInfo.value;
+}
+
 onMounted(() => {
     setFrontConfig();
     setPayConfig();
@@ -1596,6 +1804,12 @@ onMounted(() => {
     window.addEventListener('resize', function () {
         setDialogStyle(true);
     })
+    // showNewImage
+    getChromeStorage('custom_poster_guide', (info) => {
+        if (!info) {
+            customShowNewImage.value = true
+        }
+    })
 });
 </script>
 
@@ -1859,11 +2073,11 @@ onMounted(() => {
                                 img {
                                     -webkit-user-drag: none;
                                     width: 220px;
-                                    height: 160px;
+                                    height: 90px;
                                 }
                             }
                             .form-base {
-                                margin-top: 14px;
+                                margin-top: 20px;
                                 border: 1px solid #D1D9DD;
                                 border-radius: 14px;
                                 box-sizing: border-box;
@@ -2104,7 +2318,48 @@ onMounted(() => {
                     }
                 }
 
-
+                .giveaway-poster {
+                    display: flex;
+                    align-items: center;
+                    flex-direction: row;
+                    cursor: pointer;
+                    height: 84px;
+                    margin-top: 20px;
+                    border-radius: 14px;
+                    border: 1px solid #D1D9DD;
+                    .show-img {
+                        display: flex;
+                        align-items: center;
+                        justify-content: center;
+                        width: 45px;
+                        height: 60px;
+                        margin-left: 14px;
+                        margin-right: 14px;
+                    }
+                    .show-font {
+                        position: relative;
+                        font-size: 15px;
+                        font-weight: 500;
+                        .new {
+                            width: 40px;
+                            height: 18px;
+                            margin-left: 10px;
+                        }
+                    }
+                    .show-placeholder {
+                        flex: 1;
+                        color: #1D9BF0;
+                        font-size: 15px;
+                        font-weight: 500;
+                        text-align: right;
+                    }
+                    .arrow {
+                        width: 18px;
+                        height: 24px;
+                        margin-left: 2px;
+                        margin-right: 12px;
+                    }
+                }
                 .tips-wrapper {
                     margin: 16px 0 0 12px !important;
                     padding: 0px !important;
@@ -2286,4 +2541,77 @@ onMounted(() => {
         }
     }
 }
+
+.dialog {
+    position: absolute;
+    z-index: 2002;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 800px;
+    height: calc(100% - 100px);
+    border-radius: 20px;
+    background-color: #ffffff;
+    .corp-title {
+        display: flex;
+        height: 48px;
+        align-items: center;
+        .back {
+            cursor: pointer;
+            width: 24px;
+            height: 24px;
+            margin: 0 12px;
+        }
+        span {
+            font-size: 16px;
+            font-weight: 500;
+        }
+    }
+    .corp-content {
+        width: 472px;
+        margin: auto;
+        height: calc(100% - 130px);
+    }
+    .corp-footer {
+        display: flex;
+        align-items: center;
+        justify-content: right;
+        height: 80px;
+        text-align: right;
+        padding-right: 30px;
+        .confirm {
+            cursor: pointer;
+            border: 0;
+            width: 200px;
+            height: 50px;
+            color: #ffffff;
+            font-size: 18px;
+            font-weight: 700;
+            border-radius: 25px;
+            background: #1D9BF0;
+            &.disable {
+                background: #D9D9D9;
+                img {
+                    width: 20px;
+                    margin-right: 10px;
+                }
+            }
+        }
+    }
+}
+.dialog-mask {
+    position: absolute;
+    z-index: 2001;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+}
+:deep() .vue-cropper {
+    background-image: none;
+}
+:deep() .cropper-modal {
+    background: rgba(0, 0, 0, .05);
+}
 </style>

+ 1 - 0
src/view/iframe/red-packet/index.vue

@@ -9,4 +9,5 @@ import LuckDraw from '@/view/iframe/red-packet/luck-draw.vue'
 import { getQueryString } from '@/uilts/help.js'
 let state = reactive({})
 state.page_type = getQueryString('page_type') || '红包'
+
 </script>

+ 94 - 29
src/view/iframe/red-packet/luck-draw.vue

@@ -192,38 +192,46 @@
 
         <!-- no-open -->
         <div v-else-if="state.status == 'not-open'" class="not-open">
-            <img :src="require('@/assets/subject/002-card.svg')" alt="">
-            <img class="open-gif" :src="require('@/assets/gif/002.png')" />
-
-            <img :src="require('@/assets/svg/icon-participate.svg')" alt="" class="open" @click="clickOpenRedPacket">
-            <div class="title" v-if="state.detail.postUserInfo">
-                <img :src="state.detail.postUserInfo.avatarUrl" alt />
-                <span>{{ state.detail.postUserInfo.nickName || 'FutureDoctor' }}</span>
-            </div>
-            <div class="money-area">
-                <div class="txt">{{ state.detail.currencySymbol }} GIVEAWAY</div>
-                <div class="coin">
-                    <img :src="state.detail.currencyIconPath" alt />
-                    <font-amount :amount="state.detail.amountValue" style="color:#fff;"></font-amount>
+            <template v-if="state.detail.posterType === 2 && state.detail.customPosterInstalled">
+                <img class="customImg" :src="state.detail.customPosterInstalled" />
+                <div class="customBottom">
+                    <div class="theme">
+                        <img class="icon" :src="require('@/assets/svg/icon-last-time.svg')"/>
+                        <span class="time">{{ state.count_down_time }}</span>
+                        <span class="info">Left</span>
+                    </div>
+                    <div class="winner-info">
+                        <span class="count">{{state.detail.totalCount}} Winners</span>
+                        <span>to Share </span>
+                        <span class="prize-name">{{state.detail.amountValue + ' ' + state.detail.currencySymbol}}</span>
+                    </div>
+                    <div class="open-red" @click="clickOpenRedPacket">
+                        Participate Now
+                    </div>
                 </div>
-                <!-- <div class="people">{{ state.detail.totalCount }} WINNERS TO SHARE</div> -->
-                <!-- <div class="mark-area">
-                    <div class="time">
+            </template>
+            <template v-else>
+                <img :src="require('@/assets/subject/002-card.svg')" alt="">
+                <img class="open-gif" :src="require('@/assets/gif/002.png')" />
+
+                <img :src="require('@/assets/svg/icon-participate.svg')" alt="" class="open" @click="clickOpenRedPacket">
+                <div class="title" v-if="state.detail.postUserInfo">
+                    <img :src="state.detail.postUserInfo.avatarUrl" alt />
+                    <span>{{ state.detail.postUserInfo.nickName || 'FutureDoctor' }}</span>
+                </div>
+                <div class="money-area">
+                    <div class="txt">{{ state.detail.currencySymbol }} GIVEAWAY</div>
+                    <div class="coin">
+                        <img :src="state.detail.currencyIconPath" alt />
+                        <font-amount :amount="state.detail.amountValue" style="color:#fff;"></font-amount>
+                    </div>
+                    <div class="time-area">
+                        <div></div>
                         <img :src="require('@/assets/svg/icon-time.svg')" />
                         <span>{{ state.count_down_time }}</span>
                     </div>
-                    <div class="win">
-                        <img :src="require('@/assets/svg/icon-win.svg')" />
-                        <span>{{ state.detail.totalCount }} WINNERS</span>
-                    </div>
-                </div> -->
-                <div class="time-area">
-                    <div></div>
-                    <img :src="require('@/assets/svg/icon-time.svg')" />
-                    <span>{{ state.count_down_time }}</span>
                 </div>
-
-            </div>
+            </template>
         </div>
 
 
@@ -994,8 +1002,9 @@ const reSetBindTwtterId = (_params) => {
     let { taskCondition } = postBizData;
     let discordTask = JSON.parse(taskCondition).find(item => item.type == 7);
 
-    getChromeStorage('userInfo', (_userInfo) => {
-        if (_userInfo && _userInfo.uid == _params.uid) {
+    getChromeStorage('userInfo', (_userInfo = {}) => {
+        // if (_userInfo && _userInfo.uid == _params.uid) {
+        if (_userInfo.uid) {
             srcPublishSuccess({
                 params: {
                     postId: state.postId,
@@ -2964,6 +2973,62 @@ body {
         overflow: hidden;
         border-radius: 11px;
 
+        .customImg {
+            width: 100%;
+            min-height: 373px;
+        }
+        .customBottom {
+            width: 100%;
+            height: 125px;
+            background:#111214;
+            padding: 10px 16px;
+            font-weight: 500;
+            font-size: 12px;
+            line-height: 14px;
+            letter-spacing: 0.3px;
+            color: #838383;
+            line-height: 20px;
+            .theme {
+                display: flex;
+                height: 20px;
+                align-items: center;
+                justify-content: flex-start;
+                .icon {
+                    width: 12px;
+                }
+                .time {
+                    margin: 0 4px;
+                    color: #1D9BF0;
+                }
+            }
+            .winner-info {
+                display: flex;
+                height: 20px;
+                align-items: center;
+                justify-content: flex-start;
+                margin-bottom: 13px;
+                .count{
+                    color: #1D9BF0;
+                    margin-right: 4px;
+                }
+                .prize-name {
+                    color: #1D9BF0;
+                    margin-left: 4px;
+                }
+            }
+            .open-red {
+                height: 45px;
+                background: linear-gradient(180deg, #4AB6FF 0%, #1D9BF0 100%, #1D9BF0 100%);
+                border: 1.5px solid rgba(255, 255, 255, 0.15);
+                border-radius: 52px;
+                line-height: 45px;
+                text-align: center;
+                cursor: pointer;
+                font-weight: 800;
+                font-size: 16px;
+                color: #FFFFFF;
+            }
+        }
         .money-area {
             width: 100%;
             position: absolute;

+ 93 - 17
src/view/iframe/red-packet/red-packet.vue

@@ -203,22 +203,40 @@
 
     <!-- no-open -->
     <div v-else-if="state.status == 'not-open'" class="not-open">
-      <img :src="require('@/assets/subject/001-card.png')" alt="">
-      <img class="open-gif" :src="require('@/assets/gif/001.gif')" />
-
-      <img :src="require('@/assets/svg/icon-open.svg')" alt="" class="open" @click="clickOpenRedPacket">
-      <div class="title" v-if="state.detail.postUserInfo">
-        <img :src="state.detail.postUserInfo.avatarUrl" alt />
-        <span>{{ state.detail.postUserInfo.nickName || 'FutureDoctor' }}</span>
-      </div>
-      <div class="money-area">
-        <div class="txt">{{ state.detail.currencySymbol }} GIVEAWAY</div>
-        <div class="coin">
-          <img :src="state.detail.currencyIconPath" alt />
-          <font-amount :amount="state.detail.amountValue" style="color:#fff;"></font-amount>
+      <template v-if="state.detail.posterType === 2 && state.detail.customPosterInstalled">
+        <img class="customImg" :src="state.detail.customPosterInstalled" />
+        <div class="customBottom">
+            <div class="theme">
+                <span class="info">Instant Giveaway</span>
+            </div>
+            <div class="winner-info">
+                <span class="count">{{state.detail.totalCount}} Winners</span>
+                <span>to Share </span>
+                <span class="prize-name">{{state.detail.amountValue + ' ' + state.detail.currencySymbol}}</span>
+            </div>
+            <div class="open-red" @click="clickOpenRedPacket">
+                Open Now
+            </div>
         </div>
-        <div class="people">{{ state.detail.totalCount }} WINNERS TO SHARE</div>
-      </div>
+      </template>
+      <template v-else>
+        <img :src="require('@/assets/subject/001-card.png')" alt="">
+        <img class="open-gif" :src="require('@/assets/gif/001.gif')" />
+
+        <img :src="require('@/assets/svg/icon-open.svg')" alt="" class="open" @click="clickOpenRedPacket">
+        <div class="title" v-if="state.detail.postUserInfo">
+          <img :src="state.detail.postUserInfo.avatarUrl" alt />
+          <span>{{ state.detail.postUserInfo.nickName || 'FutureDoctor' }}</span>
+        </div>
+        <div class="money-area">
+          <div class="txt">{{ state.detail.currencySymbol }} GIVEAWAY</div>
+          <div class="coin">
+            <img :src="state.detail.currencyIconPath" alt />
+            <font-amount :amount="state.detail.amountValue" style="color:#fff;"></font-amount>
+          </div>
+          <div class="people">{{ state.detail.totalCount }} WINNERS TO SHARE</div>
+        </div>
+      </template>
     </div>
 
 
@@ -874,8 +892,9 @@ const reSetBindTwtterId = (_params) => {
   let { taskCondition } = postBizData;
   let discordTask = JSON.parse(taskCondition).find(item => item.type == 7);
 
-  getChromeStorage('userInfo', (_userInfo) => {
-    if (_userInfo.uid == _params.uid) {
+  getChromeStorage('userInfo', (_userInfo = {}) => {
+    // if (_userInfo.uid == _params.uid) {
+    if (_userInfo.uid) {
       srcPublishSuccess({
         params: {
           postId: state.postId,
@@ -2601,6 +2620,63 @@ body {
     overflow: hidden;
     border-radius: 11px;
 
+    .customImg {
+        width: 100%;
+        min-height: 373px;
+    }
+    .customBottom {
+        width: 100%;
+        height: 125px;
+        background:#111214;
+        padding: 10px 16px;
+        font-weight: 500;
+        font-size: 12px;
+        line-height: 14px;
+        letter-spacing: 0.3px;
+        color: #838383;
+        line-height: 20px;
+        .theme {
+            display: flex;
+            height: 20px;
+            align-items: center;
+            justify-content: flex-start;
+            .icon {
+                width: 12px;
+            }
+            .time {
+                margin: 0 4px;
+                color: #1D9BF0;
+            }
+        }
+        .winner-info {
+            display: flex;
+            height: 20px;
+            align-items: center;
+            justify-content: flex-start;
+            margin-bottom: 13px;
+            .count{
+                color: #1D9BF0;
+                margin-right: 4px;
+            }
+            .prize-name {
+                color: #1D9BF0;
+                margin-left: 4px;
+            }
+        }
+        .open-red {
+            height: 45px;
+            background: linear-gradient(180deg, #4AB6FF 0%, #1D9BF0 100%, #1D9BF0 100%);
+            border: 1.5px solid rgba(255, 255, 255, 0.15);
+            border-radius: 52px;
+            line-height: 45px;
+            text-align: center;
+            cursor: pointer;
+            font-weight: 800;
+            font-size: 16px;
+            color: #FFFFFF;
+        }
+    }
+
     .money-area {
       width: 100%;
       position: absolute;

+ 82 - 2
src/view/popup/currency-detail.vue

@@ -33,10 +33,24 @@
       </div>
     </div>
     <div class="bottom">
-      <div class="btn deposit-btn"
+      <template v-if="showSendBtn">
+        <div class="btn send-btn" @click="showSendGiveawayDialog(currencyInfo)">
+          <img :src="require('@/assets/svg/icon-send-giveaway.svg')" /> 
+          Send Giveaway
+        </div>
+        <div class="btn-wrapper">
+          <div class="button "
+            v-if="currencyInfo.currencyCode != 'USD'"
+            @click="clickDeposit">Deposit</div>
+          <div class="button" @click="clickWithdraw">Withdrawal</div>
+        </div>
+      </template>
+      <template v-else>
+        <div class="btn deposit-btn"
           v-if="currencyInfo.currencyCode != 'USD'"
           @click="clickDeposit">Deposit</div>
-      <div class="btn withdrawal-btn" @click="clickWithdraw">Withdrawal</div>
+        <div class="btn withdrawal-btn" @click="clickWithdraw">Withdrawal</div>
+      </template>
     </div>
 
     <template v-if="showCurrencySelect">
@@ -59,6 +73,7 @@ import Report from "@/log-center/log";
 import { getStorage } from "@/uilts/help";
 
 import { getCurrencyInfoBySymbol } from "@/http/publishApi";
+import {setChromeStorage} from "@/uilts/chromeExtension"
 
 import VHead from '@/view/popup/components/head.vue'
 import currencySelect from "@/view/components/currency-select.vue";
@@ -73,6 +88,8 @@ let showCurrencySelect = ref(false);
 
 let currencyOpertionType = '';
 
+let showSendBtn = ref(true);
+
 
 
 const selectCurrency = (params) => {
@@ -81,6 +98,8 @@ const selectCurrency = (params) => {
       withdrawHandle(params);
     } else if(currencyOpertionType == 'DEPOSIT') {
       depositHandle(params);
+    } else if(currencyOpertionType == 'SEND') {
+      showSendGiveawayDialog(params);
     }
 }
 
@@ -172,6 +191,27 @@ const onRefresh = () => {
   })
 };
 
+const showSendGiveawayDialog = (params = {}) => {
+  if(currenciesData.value.length > 1 && currencyOpertionType != 'SEND') {
+      showCurrencySelect.value = true;
+      currencyOpertionType = "SEND";
+  } else {
+    console.log(params,'params')
+      setLocalSelectCurrencyInfo(params)
+      setTimeout(() => {
+        chrome.runtime.sendMessage({ 
+          actionType: "POPUP_SHOW_DENET_PUBLISH_DIALOG", 
+          data: { } 
+      }, () => { });
+      }, 600)
+      currencyOpertionType = '';
+  }
+};
+
+const setLocalSelectCurrencyInfo = (params = {}) => {
+    setChromeStorage({ selectCurrencyInfo : JSON.stringify(params)})    
+}
+
 onMounted(() => {
     let {params = '{}'} = router.currentRoute.value.query;
 
@@ -186,6 +226,11 @@ onMounted(() => {
         totalUsdEstimateBalance
       };
       console.log(currencyInfo.value )
+    } 
+    if(window.location.pathname.indexOf('/home.html') > -1) {
+      showSendBtn.value = false;
+    } else {
+      showSendBtn.value = true;
     }
 })
 </script>
@@ -274,6 +319,41 @@ onMounted(() => {
       box-sizing: border-box;
       color: #1d9bf0;
     }
+
+    .send-btn {
+      font-weight: 700;
+      font-size: 18px;
+      margin-bottom: 18px;
+      background: #1d9bf0;
+      color: #fff;
+      img {
+        width: 19px;
+        height: 19px;
+        margin-right: 8px;
+      }
+    }
+
+    .btn-wrapper {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+
+      .button {
+        width: 163px;
+        height: 50px;
+        border: 1px solid #d7e8f4;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-weight: 600;
+        font-size: 16px;
+        border-radius: 100px;
+        box-sizing: border-box;
+        color: #1D9BF0;
+        cursor: pointer;
+      }
+    }
   }
 
 

+ 1 - 1
src/view/popup/tabbar-page/message/index.vue

@@ -499,7 +499,7 @@ const downTimeBegin = () => {
             let endTime = moment(item.endTimestamp + 5000)
             let downTime = (endTime - time) || 0
             if (downTime > 0) {
-                item.downTime = formatSecondsAsDaysOrTime(downTime / 1000);
+                item.downTime = formatSecondsAsDaysOrTime(downTime / 1000, false);
                 ifDown = true;
             }
             if (item && item.downTime && item.downTime == '00:00:00') {

Некоторые файлы не были показаны из-за большого количества измененных файлов