瀏覽代碼

Merge branch 'dev_1.1.5' into dev_v1.1.4_220707

wenliming 2 年之前
父節點
當前提交
cee23e2f3f
共有 40 個文件被更改,包括 1507 次插入425 次删除
  1. 二進制
      src/assets/subject/icon-clock.png
  2. 3 0
      src/assets/subject/icon-gift-inline.svg
  3. 二進制
      src/assets/subject/icon-gift.gif
  4. 二進制
      src/assets/subject/icon-prize.png
  5. 10 0
      src/assets/subject/icon-uninstall-bg.svg
  6. 二進制
      src/assets/subject/img-custom-common-bg.png
  7. 7 0
      src/assets/subject/img-custom-common-bg.svg
  8. 二進制
      src/assets/subject/img-custom-lottary-bg.png
  9. 17 0
      src/assets/subject/img-custom-lottary-bg.svg
  10. 12 0
      src/assets/subject/success-top-bg-1.svg
  11. 12 0
      src/assets/subject/success-top-bg-2.svg
  12. 4 0
      src/assets/svg/icon-add-white.svg
  13. 3 0
      src/assets/svg/icon-gift.svg
  14. 6 10
      src/entry/background.js
  15. 0 1
      src/entry/content.js
  16. 1 1
      src/http/configAPI.js
  17. 2 1
      src/logic/background/fetch/twitter.js
  18. 11 1
      src/logic/background/help.js
  19. 25 16
      src/logic/background/twitter.js
  20. 2 1
      src/manifest.json
  21. 35 50
      src/uilts/chromeExtension.js
  22. 115 62
      src/uilts/help.js
  23. 44 0
      src/view/components/component-zoom.vue
  24. 42 3
      src/view/components/currency-list.vue
  25. 3 3
      src/view/components/currency-select.vue
  26. 76 27
      src/view/components/custom-card-cover.vue
  27. 132 16
      src/view/components/custom-card-horizontal-cover.vue
  28. 46 0
      src/view/components/font-zoom.vue
  29. 14 4
      src/view/content/message/index.vue
  30. 4 2
      src/view/content/tool-box/full.vue
  31. 4 2
      src/view/content/tool-box/index.vue
  32. 172 0
      src/view/iframe/publish/components/customized-reward-edit.vue
  33. 5 1
      src/view/iframe/publish/components/giveaway-poster.vue
  34. 20 7
      src/view/iframe/publish/components/preview-card.vue
  35. 189 28
      src/view/iframe/publish/give-dialog.vue
  36. 65 60
      src/view/iframe/publish/tool-box/child/editor.vue
  37. 154 51
      src/view/iframe/red-packet/luck-draw.vue
  38. 131 42
      src/view/iframe/red-packet/red-packet.vue
  39. 68 36
      src/view/popup/tabbar-page/message/index.vue
  40. 73 0
      yarn.lock

二進制
src/assets/subject/icon-clock.png


+ 3 - 0
src/assets/subject/icon-gift-inline.svg

@@ -0,0 +1,3 @@
+<svg width="22" height="23" viewBox="0 0 22 23" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2.93203 20.381C2.93203 20.7925 3.25974 21.125 3.66536 21.125H10.2195V12.3835H2.93203V20.381ZM11.7779 21.125H18.332C18.7377 21.125 19.0654 20.7925 19.0654 20.381V12.3835H11.7779V21.125ZM19.432 6.80374H16.0495C16.3612 6.30622 16.5445 5.7157 16.5445 5.08333C16.5445 3.3141 15.126 1.875 13.382 1.875C12.4333 1.875 11.5785 2.30278 10.9987 2.97699C10.4189 2.30278 9.56411 1.875 8.61536 1.875C6.87141 1.875 5.45286 3.3141 5.45286 5.08333C5.45286 5.7157 5.63391 6.30622 5.94786 6.80374H2.56536C2.15974 6.80374 1.83203 7.1362 1.83203 7.54771V10.8025H10.2195V6.80374H11.7779V10.8025H20.1654V7.54771C20.1654 7.1362 19.8377 6.80374 19.432 6.80374ZM10.2195 6.71075H8.61536C7.73078 6.71075 7.0112 5.98074 7.0112 5.08333C7.0112 4.18593 7.73078 3.45592 8.61536 3.45592C9.49995 3.45592 10.2195 4.18593 10.2195 5.08333V6.71075ZM13.382 6.71075H11.7779V5.08333C11.7779 4.18593 12.4974 3.45592 13.382 3.45592C14.2666 3.45592 14.9862 4.18593 14.9862 5.08333C14.9862 5.98074 14.2666 6.71075 13.382 6.71075Z" fill="#F5C03F"/>
+</svg>

二進制
src/assets/subject/icon-gift.gif


二進制
src/assets/subject/icon-prize.png


+ 10 - 0
src/assets/subject/icon-uninstall-bg.svg

@@ -0,0 +1,10 @@
+<svg width="330" height="56" viewBox="0 0 330 56" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect opacity="0.2" width="330" height="56" fill="url(#paint0_linear_22715_225612)"/>
+<defs>
+<linearGradient id="paint0_linear_22715_225612" x1="1.69508e-06" y1="27.3488" x2="330" y2="27.3488" gradientUnits="userSpaceOnUse">
+<stop stop-color="white"/>
+<stop offset="0.36788" stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>

二進制
src/assets/subject/img-custom-common-bg.png


文件差異過大導致無法顯示
+ 7 - 0
src/assets/subject/img-custom-common-bg.svg


二進制
src/assets/subject/img-custom-lottary-bg.png


+ 17 - 0
src/assets/subject/img-custom-lottary-bg.svg

@@ -0,0 +1,17 @@
+<svg width="375" height="375" viewBox="0 0 375 375" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="375" height="375" fill="black"/>
+<rect width="375" height="375" fill="url(#paint0_linear_22038_219600)"/>
+<rect opacity="0.1" y="286" width="375" height="47" fill="url(#paint1_linear_22038_219600)"/>
+<defs>
+<linearGradient id="paint0_linear_22038_219600" x1="187" y1="-1.87221e-08" x2="187.5" y2="375" gradientUnits="userSpaceOnUse">
+<stop offset="0.410609" stop-color="#6E56FF"/>
+<stop offset="1" stop-color="#111214"/>
+</linearGradient>
+<linearGradient id="paint1_linear_22038_219600" x1="1.92623e-06" y1="308.953" x2="375" y2="308.953" gradientUnits="userSpaceOnUse">
+<stop stop-color="white" stop-opacity="0"/>
+<stop offset="0.264" stop-color="white" stop-opacity="0.826667"/>
+<stop offset="0.714667" stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>

+ 12 - 0
src/assets/subject/success-top-bg-1.svg

@@ -0,0 +1,12 @@
+<svg width="367" height="146" viewBox="0 0 367 146" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 0H367V113.935C367 113.935 313.784 146 183.5 146C53.2159 146 0 113.935 0 113.935V0Z" fill="#1D9BF0"/>
+<rect opacity="0.2" y="62.293" width="367" height="43.8" fill="url(#paint0_linear_22038_198334)"/>
+<defs>
+<linearGradient id="paint0_linear_22038_198334" x1="1.88514e-06" y1="83.6836" x2="367" y2="83.6836" gradientUnits="userSpaceOnUse">
+<stop stop-color="white" stop-opacity="0"/>
+<stop offset="0.264" stop-color="white" stop-opacity="0.826667"/>
+<stop offset="0.714667" stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>

+ 12 - 0
src/assets/subject/success-top-bg-2.svg

@@ -0,0 +1,12 @@
+<svg width="367" height="146" viewBox="0 0 367 146" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 0H367V113.935C367 113.935 313.784 146 183.5 146C53.2159 146 0 113.935 0 113.935V0Z" fill="#7D52FD"/>
+<rect opacity="0.2" y="62.293" width="367" height="43.8" fill="url(#paint0_linear_22038_198322)"/>
+<defs>
+<linearGradient id="paint0_linear_22038_198322" x1="1.88514e-06" y1="83.6836" x2="367" y2="83.6836" gradientUnits="userSpaceOnUse">
+<stop stop-color="white" stop-opacity="0"/>
+<stop offset="0.264" stop-color="white" stop-opacity="0.826667"/>
+<stop offset="0.714667" stop-color="white"/>
+<stop offset="1" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>

+ 4 - 0
src/assets/svg/icon-add-white.svg

@@ -0,0 +1,4 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<line x1="3" y1="10" x2="17" y2="10" stroke="white" stroke-width="2"/>
+<line x1="10" y1="17" x2="10" y2="3" stroke="white" stroke-width="2"/>
+</svg>

+ 3 - 0
src/assets/svg/icon-gift.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M3.4 17.3816C3.4 17.7237 3.66813 18 4 18H9.3625V10.7343H3.4V17.3816ZM10.6375 18H16C16.3319 18 16.6 17.7237 16.6 17.3816V10.7343H10.6375V18ZM16.9 6.09662H14.1325C14.3875 5.68309 14.5375 5.19227 14.5375 4.66667C14.5375 3.19614 13.3769 2 11.95 2C11.1737 2 10.4744 2.35556 10 2.91594C9.52563 2.35556 8.82625 2 8.05 2C6.62313 2 5.4625 3.19614 5.4625 4.66667C5.4625 5.19227 5.61063 5.68309 5.8675 6.09662H3.1C2.76812 6.09662 2.5 6.37295 2.5 6.71498V9.42029H9.3625V6.09662H10.6375V9.42029H17.5V6.71498C17.5 6.37295 17.2319 6.09662 16.9 6.09662ZM9.3625 6.01932H8.05C7.32625 6.01932 6.7375 5.41256 6.7375 4.66667C6.7375 3.92077 7.32625 3.31401 8.05 3.31401C8.77375 3.31401 9.3625 3.92077 9.3625 4.66667V6.01932ZM11.95 6.01932H10.6375V4.66667C10.6375 3.92077 11.2263 3.31401 11.95 3.31401C12.6737 3.31401 13.2625 3.92077 13.2625 4.66667C13.2625 5.41256 12.6737 6.01932 11.95 6.01932Z" fill="#1D9BF0"/>
+</svg>

+ 6 - 10
src/entry/background.js

@@ -25,8 +25,7 @@ import {
     checkShowPublishDialog
 } from "@/logic/background/twitter";
 import Report from "@/log-center/log"
-import { PingPong, httpNetWork } from "@/logic/background/help";
-import { commonFetch } from '@/http/fetch'
+import { PingPong, httpNetWork, httpContentToBack } from "@/logic/background/help";
 import { appVersionCode } from '@/http/configAPI.js'
 import { getChromeStorage, setChromeStorage } from '@/uilts/chromeExtension.js'
 
@@ -89,11 +88,14 @@ chrome.action.onClicked.addListener(function (tab) {
 chrome.tabs.onActivated.addListener(function (activeInfo) {
     setPopupConfig(activeInfo);
 })
+
 function onInstalledMethod() {
     try {
         // 版本更新判断
         getChromeStorage('baseInfo', (info) => {
-            if (!info || !info.appVersionCode || appVersionCode != info.appVersionCode) {
+            if (!info || !info.appVersionCode) {
+                setChromeStorage({ baseInfo: JSON.stringify({ appVersionCode }) })
+            } else if (appVersionCode != info.appVersionCode) {
                 setChromeStorage({ baseInfo: JSON.stringify({ appVersionCode }) }, () => {
                     chrome.runtime.reload()
                 })
@@ -190,13 +192,7 @@ function onMessageMethod(req, sender, sendResponse) {
                     httpNetWork(req.funcName, req.data, sender)
                     break
                 case 'HTTP_CONTENT_TO_BACK':
-                    commonFetch(req.data)
-                    .then((response) => {
-                        chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: response, callback_id: req.callback_id });
-                    })
-                    .catch(() => {
-                        chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: null, callback_id: req.callback_id });
-                    })
+                    httpContentToBack(req, sender)
                     break
                 case 'CONTENT_TWITTER_SHORT_LINK':
                     req.arr_url.forEach(item => {

+ 0 - 1
src/entry/content.js

@@ -148,7 +148,6 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
             setPopupConfByPopupPage();
             break
         case 'IFREME_TAB_GROUP_SET_IFRAME_HEIGHT':
-            console.log('IFREME_TAB_GROUP_SET_IFRAME_HEIGHT', req)
             setTabGroupIframeStyle(req.data);
             break
         case 'IFREME_TAB_GROUP_CONTENT_GET_NAV_TOP':

+ 1 - 1
src/http/configAPI.js

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

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

@@ -125,4 +125,5 @@ export async function fetchGetAllUnReadNotices(params = {}) {
         url: '/notice/getAllUnReadNotices',
         params
     })
-}
+}
+

+ 11 - 1
src/logic/background/help.js

@@ -27,9 +27,19 @@ export const setContentMessage = (obj) => {
     chrome.tabs.query({}, (tabs = []) => {
         if (tabs.length) {
             tabs = tabs.filter((item) => { return item.active && item.selected && item.highlighted }) || []
-            tabs.forEach((item)=>{
+            tabs.forEach((item) => {
                 chrome.tabs.sendMessage(item.id, obj);
             })
         }
     })
+}
+
+export const httpContentToBack = (req, sender) => {
+    commonFetch(req.data)
+        .then((response) => {
+            chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: response, callback_id: req.callback_id });
+        })
+        .catch(() => {
+            chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: null, callback_id: req.callback_id });
+        })
 }

+ 25 - 16
src/logic/background/twitter.js

@@ -1,7 +1,7 @@
 import { fetchTtwitterRequestToken, fetchTwitterLogin, fetchTwitterShortUrl, fetchAllMessageInfo, fetchReadTaskAllMsg, getDiscordUserInfo, fetchGetTwitterNftPostPre, fetchPublish, fetchGetAllUnReadNotices } from '@/logic/background/fetch/twitter.js'
-import { LANDING_PAGE, LANDING_PAGE_MID, setChromeStorage, setChromeCookie, getChromeCookie, getChromeStorage, removeChromeCookie } from '@/uilts/chromeExtension.js'
+import { LANDING_PAGE, LANDING_PAGE_MID, setChromeStorage, setChromeCookie, getChromeCookie, getChromeStorage, removeChromeCookie, LANDING_PAGE_JUMP_INFO } from '@/uilts/chromeExtension.js'
 import { guid } from '@/uilts/help.js'
-import { pageUrl, discordAuthRedirectUri } from '@/http/configAPI'
+import { discordAuthRedirectUri } from '@/http/configAPI'
 import { setContentMessage } from '@/logic/background/help.js'
 
 let authToken = ''
@@ -250,20 +250,20 @@ function sendActivetabMessage(message = {}) {
  */
 
 export function onInstalledCreateTab() {
-    // 落地页参数
-    let cookiesParams = {
-        name: 'jump_info',
-        url: pageUrl
-    }
-    getChromeCookie(cookiesParams, (res) => {
+    getChromeCookie(LANDING_PAGE_JUMP_INFO, (res) => {
         // jump_info
         if (!res || !res.jump_type) {
+            chrome.tabs.create({
+                url: "https://twitter.com",
+            });
             return
         }
+        let created_detail = false
         switch (String(res.jump_type)) {
             // 普通红包
             case 'red_packet':
                 if (res && res.postNickName && res.srcContentId) {
+                    created_detail = true
                     let url = `https://twitter.com/${res.postNickName}/status/${res.srcContentId}`
                     chrome.tabs.create({
                         url
@@ -273,6 +273,7 @@ export function onInstalledCreateTab() {
             // 抽奖红包
             case 'luck_draw':
                 if (res && res.postNickName && res.srcContentId) {
+                    created_detail = true
                     let url = `https://twitter.com/${res.postNickName}/status/${res.srcContentId}`
                     chrome.tabs.create({
                         url
@@ -282,6 +283,7 @@ export function onInstalledCreateTab() {
             // NFT
             case 'ntf_info':
                 if (res && res.twitterAccount && res.nftProjectId) {
+                    created_detail = true
                     let url = `https://twitter.com/${res.twitterAccount}`
                     chrome.tabs.create({
                         url
@@ -291,6 +293,7 @@ export function onInstalledCreateTab() {
             // NFT 组
             case 'nft_group_info':
                 if (res && res.twitterAccount) {
+                    created_detail = true
                     // setChromeStorage({ groupTabData: JSON.stringify({
                     //     deTabVal: 'deGroupTab'
                     // })})
@@ -309,18 +312,22 @@ export function onInstalledCreateTab() {
             // toolbox
             case 'tool_box':
                 if (res && res.postNickName && res.srcContentId) {
+                    created_detail = true
                     let url = `https://twitter.com/${res.postNickName}/status/${res.srcContentId}`
                     chrome.tabs.create({
                         url
                     });
                 }
                 break
-            default:
-                chrome.tabs.create({
-                    url: "https://twitter.com",
-                });
         }
-        removeChromeCookie(cookiesParams)
+
+        if (created_detail == false) {
+            chrome.tabs.create({
+                url: "https://twitter.com",
+            });
+        }
+
+        removeChromeCookie(LANDING_PAGE_JUMP_INFO)
     })
 }
 
@@ -517,9 +524,11 @@ export const checkShowPublishDialog = (params) => {
 }
 
 const createTabShowGiveaway = (params) => {
-    setChromeStorage({showGiveawayData : JSON.stringify({
-        show: true
-    })});
+    setChromeStorage({
+        showGiveawayData: JSON.stringify({
+            show: true
+        })
+    });
     chrome.tabs.create({
         url: params.url,
     });

+ 2 - 1
src/manifest.json

@@ -2,7 +2,7 @@
     "manifest_version": 3,
     "name": "DeNet",
     "description": "Growing more twitter followers with Denet",
-    "version": "1.1.4",
+    "version": "1.1.5",
     "background": {
         "service_worker": "/js/background.js"
     },
@@ -24,6 +24,7 @@
             "js": [
                 "/js/content_help.js"
             ],
+            "all_frames": true,
             "css": [
                 "/css/content_help.css"
             ]

+ 35 - 50
src/uilts/chromeExtension.js

@@ -1,6 +1,6 @@
 import { pageUrl } from "@/http/configAPI.js"
-import { exp } from "mathjs"
 import { guid } from "@/uilts/help";
+
 export const LANDING_PAGE = {
     name: 'received_log',
     url: pageUrl
@@ -11,11 +11,20 @@ export const LANDING_PAGE_MID = {
     url: pageUrl
 }
 
+export const LANDING_PAGE_JUMP_INFO = {
+    name: 'jump_info',
+    url: pageUrl
+}
+
 export function setChromeStorage(params, callback) {
-    if (callback) {
-        chrome.storage.local.set(params, callback)
-    } else {
-        chrome.storage.local.set(params)
+    try {
+        if (callback) {
+            chrome.storage.local.set(params, callback)
+        } else {
+            chrome.storage.local.set(params)
+        }
+    } catch (error) {
+        console.error(error)
     }
 }
 
@@ -44,6 +53,13 @@ export function getChromeStorage(key = '', callback) {
     });
 }
 
+// export function chromeSendMessage() {
+//     try {
+//         // chrome.runtime.sendMessage
+//     } catch {
+//     }
+// }
+
 export function setChromeCookie({
     name,
     url
@@ -56,53 +72,22 @@ export function setChromeCookie({
     })
 }
 
-export function getChromeCookie({
-    name,
-    url
-}, callback) {
-    chrome.cookies.getAll(
-        {
-            name: name || '',
-            url: url || ''
-        }, (e = []) => {
-            let _str = '[]'
-            if (e.length > 0) {
-                _str = e[0].value
-            }
-            let _arr = JSON.parse(decodeURIComponent(_str)) || []
-            callback(_arr)
-        }
-    )
-}
-
-// 累加ChromeCookie
-export function concatChromeCookie({ name, url }, value_obj) {
-    chrome.cookies.getAll(
-        {
-            name: name || '',
-            url: url || ''
-        }, (e = []) => {
-            let _str = '[]'
-            if (e.length > 0) {
-                _str = e[0].value
+export function getChromeCookie({ name = '', url = '' }, callback) {
+    try {
+        chrome.cookies.getAll({ name, url }, (res = []) => {
+            let _str = ''
+            if (Array.isArray(res) && res.length) {
+                _str = res[0].value
             }
-            let _arr = JSON.parse(decodeURIComponent(_str)) || []
-            _arr = _arr.concat(value_obj)
-
-            // 删除cookies
-            chrome.cookies.remove(LANDING_PAGE, () => {
-                chrome.cookies.set({
-                    expirationDate: new Date().getTime() / 10,
-                    name: name,
-                    url: url,
-                    value: encodeURIComponent(JSON.stringify(_arr)) || ''
-                }, (e) => {
-                    console.log(e)
-                })
+            if (_str) {
+                callback(JSON.parse(decodeURIComponent(_str)))
+            } else {
+                callback(null)
             }
-            )
-        }
-    )
+        })
+    } catch (error) {
+        console.error('catch', error)
+    }
 }
 
 export function removeChromeCookie(params, cb) {

+ 115 - 62
src/uilts/help.js

@@ -1,24 +1,25 @@
 export function getQueryString(name) {
-  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i")
-  let r = window.location.search.substr(1).match(reg)
+  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
+  let r = window.location.search.substr(1).match(reg);
   if (r != null) {
-    return window.decodeURIComponent(r[2])
+    return window.decodeURIComponent(r[2]);
   }
-  return null
+  return null;
 }
 
-export function getQueryStringByUrl(url = '', name = '') {
-  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i")
-  let r = url.split('?')[1].match(reg)
+export function getQueryStringByUrl(url = "", name = "") {
+  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
+  let r = url.split("?")[1].match(reg);
   if (r != null) {
-    return window.decodeURIComponent(r[2])
+    return window.decodeURIComponent(r[2]);
   }
-  return null
+  return null;
 }
 
 export function debounce(fn, delay) {
   let timer; // 定时器
-  return function (...args) { // 形成闭包
+  return function (...args) {
+    // 形成闭包
     // args 为函数调用时传的参数。
     let context = this;
     timer && clearTimeout(timer); // 当函数再次执行时,清除定时器,重新开始计时
@@ -26,29 +27,30 @@ export function debounce(fn, delay) {
     timer = setTimeout(function () {
       // 执行传入的指定函数,利用apply更改this绑定和传参
       fn.apply(context, args);
-    }, delay)
-  }
+    }, delay);
+  };
 }
 
 export function throttle(fn, thresh) {
-  var timeout
-  var start = new Date;
-  var threshhold = thresh || 500
+  var timeout;
+  var start = new Date();
+  var threshhold = thresh || 500;
   return function () {
+    var context = this,
+      args = arguments,
+      curr = new Date() - 0;
 
-    var context = this, args = arguments, curr = new Date() - 0
-
-    clearTimeout(timeout)//总是干掉事件回调
+    clearTimeout(timeout); //总是干掉事件回调
     if (curr - start >= threshhold) {
-      fn.apply(context, args) //只执行一部分方法,这些方法是在某个时间段内执行一次
-      start = curr
+      fn.apply(context, args); //只执行一部分方法,这些方法是在某个时间段内执行一次
+      start = curr;
     } else {
       // 让方法在脱离事件后也能执行一次
       timeout = setTimeout(function () {
-        fn.apply(context, args)
+        fn.apply(context, args);
       }, threshhold);
     }
-  }
+  };
 }
 
 export function setStorage(key, value) {
@@ -58,32 +60,33 @@ export function setStorage(key, value) {
 export function getStorage(key) {
   const item = localStorage.getItem(key);
   try {
-    return item ? JSON.parse(item) : '';
+    return item ? JSON.parse(item) : "";
   } catch (e) {
     return item;
   }
 }
 
 export function guid() {
-  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
-    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
+    var r = (Math.random() * 16) | 0,
+      v = c == "x" ? r : (r & 0x3) | 0x8;
     return v.toString(16);
   });
 }
 
 export function scaleNumber(num) {
-  let length = num.toString().split('.')[1].length;
+  let length = num.toString().split(".")[1].length;
 
-  let scale = '1';
+  let scale = "1";
 
   for (let i = 0; i < length; i++) {
-    scale += '0';
+    scale += "0";
   }
 
   let val = num * scale;
   return {
     val,
-    scale
+    scale,
   };
 }
 
@@ -99,8 +102,8 @@ export function getBit(value) {
 }
 
 export function getCookie(name) {
-  var strcookie = document.cookie;//获取cookie字符串
-  var arrcookie = strcookie.split("; ");//分割
+  var strcookie = document.cookie; //获取cookie字符串
+  var arrcookie = strcookie.split("; "); //分割
   //遍历匹配
   for (var i = 0; i < arrcookie.length; i++) {
     var arr = arrcookie[i].split("=");
@@ -116,25 +119,28 @@ export function nextTick(fn, time = 50) {
     setTimeout(() => {
       if (fn) fn();
       resolve();
-    }, time)
-  })
+    }, time);
+  });
 }
 
 export function getBrowser() {
   let browser;
   let UserAgent = navigator.userAgent.toLowerCase();
-  if (UserAgent.indexOf('chrome') > -1 && UserAgent.indexOf('safari') > -1) {
-    browser = `Chrome`
-  } else if (UserAgent.indexOf('firefox') > -1) {
-    browser = `Firefox`
-  } else if (UserAgent.indexOf('opera') > -1) {
-    browser = `Opera`
-  } else if (UserAgent.indexOf('safari') > -1 && UserAgent.indexOf('chrome') == -1) {
-    browser = `Safari`
-  } else if (UserAgent.indexOf('edge') > -1) {
-    browser = `Edge`
+  if (UserAgent.indexOf("chrome") > -1 && UserAgent.indexOf("safari") > -1) {
+    browser = `Chrome`;
+  } else if (UserAgent.indexOf("firefox") > -1) {
+    browser = `Firefox`;
+  } else if (UserAgent.indexOf("opera") > -1) {
+    browser = `Opera`;
+  } else if (
+    UserAgent.indexOf("safari") > -1 &&
+    UserAgent.indexOf("chrome") == -1
+  ) {
+    browser = `Safari`;
+  } else if (UserAgent.indexOf("edge") > -1) {
+    browser = `Edge`;
   } else {
-    browser = `Other`
+    browser = `Other`;
   }
   return browser;
 }
@@ -151,45 +157,64 @@ export function getOffsetRect(element) {
   return {
     top: oTop,
     left: oLeft,
-  }
+  };
 }
 
 export function formatSecondsAsTime(secs) {
   if (secs <= 0) {
-    return '00:00:00'
+    return "00:00:00";
   }
-  var hr = Math.floor(secs / 3600)
-  var min = Math.floor((secs - (hr * 3600)) / 60)
-  var sec = Math.floor(secs - (hr * 3600) - (min * 60))
-  var text
+  var hr = Math.floor(secs / 3600);
+  var min = Math.floor((secs - hr * 3600) / 60);
+  var sec = Math.floor(secs - hr * 3600 - min * 60);
+  var text;
   if (hr < 10) {
-    hr = "0" + hr
+    hr = "0" + hr;
   }
   if (min < 10) {
-    min = "0" + min
+    min = "0" + min;
   }
   if (sec < 10) {
-    sec = "0" + sec
+    sec = "0" + sec;
   }
-  text = hr + ':' + min + ':' + sec
+  text = hr + ":" + min + ":" + sec;
 
-  return text
+  return text;
 }
 
 // 抽奖红包 left
 export function formatSecondsAsDaysOrTime(secs, showLeft = true) {
   if (secs <= 0) {
-    return '00:00:00'
+    return "00:00:00";
   }
-  let text = ''
-  var hr = Math.floor(secs / 3600)
+  let text = "";
+  var hr = Math.floor(secs / 3600);
   if (hr >= 24) {
     let day = parseInt(hr / 24)
     text = showLeft ? `${day} days left` : `${day} days`
   } else {
-    text = formatSecondsAsTime(secs)
+    text = formatSecondsAsTime(secs);
   }
-  return text
+  return text;
+}
+
+// 时间格式化 => *d *h
+export function formatSecondsAsDayHour(secs) {
+  let text = "";
+  if (secs <= 0) {
+    return "0h";
+  }
+
+  var hr = Math.floor(secs / 3600);
+  if (hr >= 24) {
+    let day = parseInt(hr / 24);
+    text += `${day}d `;
+  }
+  let hour = parseInt(hr % 24);
+  if (hour > 0) {
+    text += `${hour}h`;
+  }
+  return text;
 }
 
 export function checkURL(URL) {
@@ -199,8 +224,36 @@ export function checkURL(URL) {
   var Expression = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
   var objExp = new RegExp(Expression);
   if (objExp.test(str) == true) {
-      return true;
+    return true;
   } else {
-      return false;
+    return false;
   }
 }
+
+let $_data = []
+export function $(key, cache = true) {
+  if (!key || typeof(key) != 'string') {
+    return null
+  }
+  // 不使用缓存
+  if (!cache) {
+    return document.querySelector(key)
+  }
+
+  let _dom
+  for (let i in $_data) {
+    if ($_data[i].key == key) {
+      _dom = $_data[i].element
+      break
+    }
+  }
+
+  // 没有缓存
+  if (!_dom) {
+    _dom = document.querySelector(key)
+    if (_dom) {
+      $_data.push({ key, element: _dom })
+    }
+  }
+  return _dom
+}

+ 44 - 0
src/view/components/component-zoom.vue

@@ -0,0 +1,44 @@
+<!-- 组件确定最大宽度时 可等比缩放组件 -->
+<template>
+    <span class="zoom-wrap" ref="zoomDom" :style="{ zoom: zoom, 'font-size': props.fontSize + 'px' }">
+        <slot></slot>
+    </span>
+</template>
+<script setup>
+import { ref, defineProps, onMounted, getCurrentInstance } from 'vue'
+
+let props = defineProps({
+    width: {
+        type: String,
+        default: '375'
+    },
+    fontSize: {
+        type: String,
+        default: '22'
+    }
+})
+
+let zoom = ref(1);
+let currentInstance;
+
+const setFontZoom = () => {
+    const currentInstance = getCurrentInstance()
+    let offsetWidth = currentInstance.ctx.$refs.zoomDom.offsetWidth;
+    zoom.value = offsetWidth > props.width ? +props.width / offsetWidth : 1
+}
+
+onMounted(() => {
+    setFontZoom()
+})
+
+</script>
+<style lang="scss" scoped>
+.zoom-wrap {
+    font-weight: 800;
+    color: #FFFFFF;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    white-space: nowrap;
+}
+</style>

+ 42 - 3
src/view/components/currency-list.vue

@@ -58,7 +58,6 @@
                 <div class="no-data" v-if="show_empty">
                     Not found
                 </div>
-
             </div>
             <!-- 显示搜索结果列表 -->
             <div class="search-list" v-else>
@@ -87,6 +86,11 @@
                     Not found
                 </div>
             </div>
+            <!-- 添加通用奖品 -->
+            <div class="add-general-lottery" v-if="showGeneralLottery" @click="addGeneralLottery">
+                <img class="add-general-lottery-icon" :src="require('@/assets/svg/icon-add-white.svg')" />
+                <span class="add-general-lottery-text">Customize</span>
+            </div>
         </div>
     </div>
 </template>
@@ -109,6 +113,11 @@ const props = defineProps({
     showRefresh: {
         type: Boolean,
         default: true
+    },
+    // 是否显示 通用奖品选项
+    showGeneralLottery: {
+        type: Boolean,
+        default: false
     }
 })
 let keywords = ref('');
@@ -125,8 +134,8 @@ let listReqParams = {
     },
     loadMore: false,
 };
-let show_empty = ref(false)
-const emits = defineEmits(["selectCurrency", "setCurrencyList"]);
+let show_empty = ref(false);
+const emits = defineEmits(["selectCurrency", "setCurrencyList", "addGeneralLottery"]);
 
 const selectCurrency = (params) => {
     emits("selectCurrency", params);
@@ -254,6 +263,13 @@ const listScroll = (e) => {
     }
 }
 
+/**
+ * 添加通用奖品 按钮点击
+ */
+const addGeneralLottery = () => {
+    emits('addGeneralLottery');
+}
+
 onMounted(() => {
     getCurrencyInfoList();
 })
@@ -317,6 +333,7 @@ defineExpose({
     .list-wrapper {
         height: calc(100% - 60px);
         overflow-y: auto;
+        padding-bottom: 50px;
 
         .list-item {
             .item-title {
@@ -402,6 +419,28 @@ defineExpose({
             left: 50%;
             transform: translate(-50%, -50%);
         }
+
+        .add-general-lottery {
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            display: flex;
+            width: 100%;
+            height: 50px;
+            align-items: center;
+            justify-content: center;
+            background-color: #1D9BF0;
+            color: #fff;
+            cursor: pointer;
+
+            &-icon {
+                margin-right: 8px;
+            }
+
+            &-text {
+                font-size: 16px;
+            }
+        }
     }
 }
 </style>

+ 3 - 3
src/view/components/currency-select.vue

@@ -2,12 +2,12 @@
 <template>
     <div class="list-item" v-for="(item, index) in props.list" :key="index">
         <div class="item-title">
-            <img class="icon" :src="item.chainInfo.iconPath" />
-            {{item.chainInfo.chainName}}
+            <img class="icon" :src="item.chainInfo?.iconPath" />
+            {{item.chainInfo?.chainName}}
         </div>
         <div class="item-detail" @click="selectCurrency(item)">
             <div class="left">
-                <img class="icon-currency" :src="item.iconPath" />
+                <img class="icon-currency" :src="item?.iconPath" />
                 <div class="currency-info">
                     <div class="name">{{ item.currencyCode == 'USD' ? 'USD' : item.tokenSymbol }}</div>
                     <div class="desc">{{ item.currencyCode == 'USD' ? 'Paypal' : item.currencyName }}</div>

+ 76 - 27
src/view/components/custom-card-cover.vue

@@ -5,20 +5,22 @@
         <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 v-if="isLottaryCpd" class="time" >{{data.countDown || 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>
+                <component-zoom width="320" fontSize="12">
+                    <span class="count">{{data.totalCount}} Winners</span>
+                    <span>to Share </span>
+                    <span class="prize-name">{{isMoneyRewardCpd ? data.amountValue + ' ' + data.tokenSymbol : data.customizedReward}}</span>
+                </component-zoom>
             </div>
-            <div class="open" @click="open">
+            <div class="open-btn" @click="open">
                 {{isLottaryCpd ? 'Participate Now' : 'Open Now'}}
             </div>
         </div>
     </div>
-    <div class="not-open" v-else>
+    <div class="not-open" v-else-if="isMoneyRewardCpd">
         <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')"  />
 
@@ -50,27 +52,62 @@
                 <img :src="data.currencyIconUrl || imgHeaderCover" />
                 <span id="preview-after-amount"
                     :style="{
-                        fontSize: amountFontSize + 'px'
+                        fontSize: amount_font_size + 'px'
                     }">{{ data.amountValue }}</span>
             </div>
             <div class="time-area" v-if="data.type == 2">
                 <img class="icon-clock" :src="require('@/assets/svg/icon-time.svg')" />
-                {{formatSecondsAsDaysOrTime(data.validityDuration * 3600)}} 
+                {{data.countDown || formatSecondsAsDaysOrTime(data.validityDuration * 3600)}} 
             </div>
             <div class="people" v-else>
                 {{ data.totalCount }} WINNERS TO SHARE
             </div>
         </div>
     </div>
+
+    <!-- 改版之后的卡片 -->
+    <div class="custom-card" v-else>
+        <img class="cover" v-if="isLottaryCpd" :src="require('@/assets/subject/img-custom-lottary-bg.svg')"  />
+        <img class="cover" v-else :src="require('@/assets/subject/img-custom-common-bg.svg')"  />        
+        <img class="gift" :src="require('@/assets/subject/icon-gift.gif')" />        
+        <div class="prize">
+            <component-zoom width="300" fontSize="22">
+                <img class="icon" :src="require('@/assets/subject/icon-gift-inline.svg')"/>
+                <span class="name" id="custom-name" >
+                    {{data.customizedReward}}
+                    <span class="total" >X{{data.totalCount}}</span>
+                </span>
+            </component-zoom>
+            
+        </div>
+        <!-- 底部公共模块 -->
+        <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" >{{data.countDown || formatSecondsAsDaysOrTime(data.validityDuration * 3600)}}</span>
+                <span class="info">{{isLottaryCpd ? 'Left' : 'Instant Giveaway'}}</span>
+            </div>
+            <div class="winner-info">
+                <component-zoom width="300" fontSize="12">
+                    <span class="count">{{data.totalCount}}Winners</span>
+                    <span>to Share </span>
+                    <span class="prize-name">{{isMoneyRewardCpd ? data.amountValue + ' ' + data.tokenSymbol : data.customizedReward}}</span>
+                
+                </component-zoom>
+            </div>
+            <div class="open-btn" @click="open">
+                {{isLottaryCpd ? 'Participate Now' : 'Open Now'}}
+            </div>
+        </div>
+    </div>
 </template>
 
 <script setup>
-import { defineProps, defineEmits, watch, ref, computed } from "vue";
+import { defineProps, defineEmits, ref, computed, onMounted } from "vue";
 import { formatSecondsAsDaysOrTime } from "@/uilts/help";
+import ComponentZoom from "./component-zoom.vue";
 import { RewardType, PlayType } from "@/types";
 
-const imgHeaderCover = require('@/assets/img/icon-header-cover.png');
-
 const props = defineProps({
     data: {
         type: Object,
@@ -81,17 +118,19 @@ const props = defineProps({
                 tokenSymbol: "",
                 type: 1,
                 validityDuration: '',
+                countDown: '',
                 customPosterUrl: '',
                 userInfo: {
                     avatarUrl: "",
                     nickName: "",
                 },
+                rewardType: RewardType.money,
+                customizedReward: "",
             };
         },
     },
 });
 
-let amountFontSize = ref(60);
 
 let isMoneyRewardCpd =computed(() => {
     return props.data.rewardType === RewardType.money
@@ -99,23 +138,30 @@ let isMoneyRewardCpd =computed(() => {
 
 let isLottaryCpd = computed(() => props.data.type === PlayType.lottery);
 
-watch(() => props.data, () => {
+const defaultBaseWidth = isMoneyRewardCpd.value ? 56 : 22;
+const defaultTotalWidth = isMoneyRewardCpd.value ? 360 : 450;
+
+let amount_font_size = ref(defaultBaseWidth);
+
+const setFontSize = () =>{
     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;
+    let lendom = document.querySelector(`#${id}`);
+    if (lendom) {
+        let lenstr = lendom.innerText.length;
+        let num = parseInt(defaultTotalWidth / lenstr);
+        amount_font_size.value = num < defaultBaseWidth ? num : defaultBaseWidth;
     }
-})
-
+}
 
 const emits = defineEmits(["clickOpenRedPacket"]);
 
 const open = () => {
     emits("clickOpenRedPacket", {});
 };
+
+onMounted(() => {
+    setFontSize()
+})
 </script>
 
 <style scoped lang="scss">
@@ -279,17 +325,19 @@ const open = () => {
 .custom-card {
     position: relative;
     width: 100%;
+    height: 100%;
     overflow: hidden;
+    background:#111214;
     position: relative;
-    border-radius: 20px;
+    border-radius: 10px;
     .customImg {
         width: 100%;
-        min-height: 200px;
+        min-height: calc(100% - 125px);
     }
     .cover {
         width: 100%;
         min-height: 350px;
-        border-radius: 20px 20px 0 0;
+        border-radius: 10px 10px 0 0;
     }
     .gift {
         width: 210px;
@@ -301,7 +349,7 @@ const open = () => {
     .prize {
         width: 100%;
         position: absolute;
-        top: 284px;
+        top: 57%;
         left: 0;
         height: 47px;
         display: flex;
@@ -328,7 +376,7 @@ const open = () => {
         width: 100%;
         height: 125px;
         background:#111214;
-        border-radius: 0 0 20px 20px;
+        border-radius: 0 0 10px 10px;
         padding: 10px 16px;
         font-weight: 500;
         font-size: 12px;
@@ -355,6 +403,7 @@ const open = () => {
             align-items: center;
             justify-content: flex-start;
             margin-bottom: 13px;
+            font-size: 12px;
             .count{
                 color: #1D9BF0;
                 margin-right: 4px;
@@ -364,7 +413,7 @@ const open = () => {
                 margin-left: 4px;
             }
         }
-        .open {
+        .open-btn {
             width: 100%;
             height: 45px;
             background: linear-gradient(180deg, #4AB6FF 0%, #1D9BF0 100%, #1D9BF0 100%);

+ 132 - 16
src/view/components/custom-card-horizontal-cover.vue

@@ -1,16 +1,24 @@
 <!-- 自定义卡片红包封面 -->
 <template>
-    <div class="card-wrapper">
+    <div class="card-wrapper" :class="{'custom-card-in-poster': !showBottom}" >
         <template v-if="data.customPosterUrl">
             <img class="customImg" :src="data.customPosterUrl" />
         </template>
-        <template v-else>
+        <template v-else-if="isMoneyRewardCpd">
             <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="bottom-bar">
+                <div class="title">
+                    DeNet.me
+                </div>
+                <div class="desc">
+                    🎁 <template v-if="data.tokenSymbol=='USD'">$</template>{{data.amountValue}} GIVEAWAY
+                </div>
+            </div>
             <div class="user-info">
                 <img :src="data.userInfo.avatarUrl" 
                 class="avatar"/> {{data.userInfo.name}}
@@ -23,7 +31,7 @@
                     :style="{
                         fontSize: amountFontSize + 'px'
                     }">
-                    <img :src="data.currencyIconUrl || imgHeaderCover" class="icon">
+                    <img :src="data.currencyIconUrl" class="icon">
                     <span id="preview-before-amount">
                         {{data.amountValue}}
                     </span>
@@ -41,22 +49,48 @@
                 </div>
             </div>
         </template>
-        <div class="bottom-bar" v-if="showBottom">
-            <div class="title">
-                DeNet.me
+        <template class="custom-card"  v-else>
+            <img class="custom-card-cover" v-if="isLottaryCpd" :src="require('@/assets/subject/img-custom-lottary-bg.png')"  />
+            <img class="custom-card-cover" v-else :src="require('@/assets/subject/img-custom-common-bg.png')"  />  
+            <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 class="desc">
-                🎁 <template v-if="data.tokenSymbol=='USD'">$</template>{{data.amountValue}} GIVEAWAY
+            <div class="custom-card-prize">
+                <component-zoom :width="showBottom ? 210 : 300">
+                    <span class="custom-card-prize-name" id="custom-name" >
+                        <img class="custom-card-prize-gift-inline" :src="require('@/assets/subject/icon-gift-inline.svg')" />
+                        {{data.customizedReward}}
+                        <span class="custom-card-prize-name-total">X{{data.totalCount}}</span>
+                    </span>
+                </component-zoom>
+            </div>
+            <div class="custom-card-desc" :class="{'custom-card-desc-lottary': !isLottaryCpd}">
+                <span class="last-time" v-if="isLottaryCpd">
+                    <img class="custom-card-desc-clock-icon" :src="require('@/assets/subject/icon-clock.png')" />
+                    {{data.validityDuration}} H
+                </span>
+                <span class="trophy-count">
+                    <img class="custom-card-desc-prize-icon" :src="require('@/assets/subject/icon-prize.png')" />
+                    {{data.totalCount}} WINNERS
+                </span>
             </div>
-        </div>
+        </template>
     </div>
 </template>
 
 <script setup>
-import { defineProps, defineEmits, watch, ref } from "vue";
+import { defineProps, defineEmits, watch, ref, computed } from "vue";
+import ComponentZoom from "./component-zoom.vue";
+import { RewardType, PlayType } from "@/types";
 
 const imgHeaderCover = require('@/assets/img/icon-header-cover.png');
 
+
 const props = defineProps({
     data: {
         type: Object,
@@ -72,6 +106,8 @@ const props = defineProps({
                     avatarUrl: "",
                     nickName: "",
                 },
+                rewardType: RewardType.money,
+                customizedReward: ""
             };
         },
     },
@@ -81,15 +117,16 @@ const props = defineProps({
     }
 });
 
+let isMoneyRewardCpd =computed(() => props.data.rewardType === RewardType.money);
+
+let isLottaryCpd = computed(() => props.data.type === PlayType.lottery);
+
 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;
-    }
+    let lenstr = document.querySelector('#preview-before-amount')?.innerHTML?.length;
+    let num = parseInt(360/lenstr);
+    amountFontSize.value = num < 56 ? num : 56;
 })
 </script>
 
@@ -198,4 +235,83 @@ watch(() => props.data, () => {
         }
     }
 }
+.custom-card {
+    width: 100%;
+    height: 100%;
+    &-cover {
+        width: 100%;
+    }
+
+    &-prize {
+        position: absolute;
+        left: 0%;
+        top: 16%;
+        width: 65%;
+        line-height: 42px;
+        display: flex;
+        background-image: url('@/assets/subject/icon-uninstall-bg.svg');
+        background-size: 100% 100%;
+        padding-left: 5px;
+
+        &-name {
+            font-weight: 800;
+            font-size: 16px;
+            // line-height: 20px;
+            letter-spacing: 0.22px;
+            color: #FFFFFF;
+            text-shadow: 0px 1.46341px 0px rgba(0, 0, 0, 0.15);
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            &-total {
+                color: #F5C03F;
+            }
+        }
+
+        &-gift-inline {
+            width: 30px;
+            height: 30px;
+            padding: 0 5px;
+        }
+    }
+    &-desc {
+        font-size: 12px;
+        line-height: 13px;
+        letter-spacing: 0.219512px;
+        color: #FFFFFF;
+        opacity: 0.7;
+        position: absolute;
+        left: 0;
+        top: 37%;
+        display: flex;
+        padding-left: 4%;
+        .last-time {
+            display: flex;
+            align-items: center;
+            margin-right: 12px;
+            img {
+                width: 12px;
+                margin-right: 2px;
+            }
+        }
+        .trophy-count {
+            display: flex;
+            align-items: center;
+
+            img {
+                width: 12px;
+                margin-right: 2px;
+            }
+        }
+    }
+}
+.custom-card-in-poster {
+    .custom-card-prize {
+        top: 24%;
+        line-height: 54px;
+    }
+    .custom-card-desc{
+        top: 50%;
+    }
+}
 </style>

+ 46 - 0
src/view/components/font-zoom.vue

@@ -0,0 +1,46 @@
+<template>
+    <span class="zoom-text" :style="{ fontSize: amount_font_size + 'px' }">{{ amount }}</span>
+</template>
+<script setup>
+import { ref, defineProps, onMounted, watch } from 'vue'
+import { getBit } from "@/uilts/help";
+
+let props = defineProps({
+    amount: {
+        type: String,
+        default: ''
+    },
+    width: {
+        type: Number,
+        default: 360
+    },
+    fontSize: {
+        type: Number,
+        default: 56
+    },
+
+})
+
+let amount_font_size = ref(props.fontSize);
+watch(props, () => {
+    setFontSize()
+})
+function setFontSize(){
+    let amount = getBit(props.amount);
+    let _num = parseInt(props.width / amount.length);
+    amount_font_size.value = _num < props.fontSize ? _num : props.fontSize;
+}
+
+onMounted(() => {
+    setFontSize()
+})
+
+</script>
+<style lang="scss" scoped>
+.zoom-text {
+    word-break: break-all;
+    font-weight: 800;
+    font-size: 22px;
+    color: #FFFFFF;
+}
+</style>

+ 14 - 4
src/view/content/message/index.vue

@@ -1,17 +1,16 @@
 <template>
     <div class="denet-message" v-show="state.list.length > 0">
-        <template v-for="item in state.list">
+        <template v-for="item in state.list" :key="item.createTimestamp">
             <div class="denet-message-area" @click="clickItem(item)" v-if="item.bizType == 2">
                 <img :src="require('@/assets/img/icon-message-fail.png')" alt />
-                <span>You were not selected from the giveaway events... Click to see more giveaways!</span>
+                <span>You were not selected from {{item.bizData.twitterAccount}}'s giveaway events... Click to see more giveaways!</span>
                 <div class="denet-message-close" @click.stop="clickClose(item)">
                     <img :src="require('@/assets/img/icon-message-close.png')" alt />
                 </div>
             </div>
             <div class="denet-message-area" @click="clickItem(item)" v-if="item.bizType == 1">
                 <img :src="require('@/assets/img/icon-message-win.png')" alt />
-                <span>Congratulations! You won <b class="denet-message-money">{{ item.bizData.lotteryMoney }}
-                        {{ item.bizData.lotteryTokenSymbol }}</b> from the giveaway!🎉</span>
+                <span>Congratulations! You won <b class="denet-message-money"> {{getPrize(item.bizData)}}</b> from {{item.bizData.twitterAccount}}'s giveaway!🎉</span>
                 <div class="denet-message-close" @click.stop="clickClose(item)">
                     <img :src="require('@/assets/img/icon-message-close.png')" alt />
                 </div>
@@ -29,11 +28,22 @@
 </template>
 <script setup>
 import { onMounted, reactive } from "vue";
+import { RewardType } from '@/types';
 let state = reactive({
     list: [],
 })
 let timer, now_time
 
+// 获取奖励
+const getPrize = (item) => { 
+    const { lotteryMoney, lotteryTokenSymbol, twitterAccount, rewardType, customizedReward } = item;
+    if (rewardType === RewardType.custom) {
+        return customizedReward;
+    } else {
+        return `${item.bizData.lotteryMoney} ${item.bizData.lotteryTokenSymbol}` 
+    }
+}
+
 // 过5秒消失逻辑
 const overTimeClose = () => {
     if (timer) {

+ 4 - 2
src/view/content/tool-box/full.vue

@@ -10,13 +10,15 @@
             </div>
         </div>
         <div class="content">
-            <iframe :src="state.iframe_url" frameborder="0"></iframe>
+            <iframe :src="state.iframe_url" frameborder="0" allow="camera *;microphone *"></iframe>
         </div>
     </div>
 </template>
 <script setup>
 import { onMounted, reactive, ref } from "vue";
 import { sendEventInfo } from "@/uilts/event";
+import { $ } from "@/uilts/help";
+
 let state = reactive({
     status: '固定', // 全屏
     iframe_url: '',
@@ -50,7 +52,7 @@ const clickFull = () => {
     }
 }
 onMounted(() => {
-    dom_fixed = document.querySelector('#denet-tool-box-fixed')
+    dom_fixed = $('#denet-tool-box-fixed')
 })
 
 const changeFull = () => {

+ 4 - 2
src/view/content/tool-box/index.vue

@@ -11,7 +11,7 @@
             <iframe :src="iframe_url" frameborder="0"></iframe>
         </div>
         <div class="content" v-else>
-            <iframe :src="state.iframe_url" v-show="state.status == ''" ref="dom_iframe" frameborder="0"></iframe>
+            <iframe :src="state.iframe_url" v-show="state.status == ''" ref="dom_iframe" frameborder="0" allow="camera *;microphone *"></iframe>
             <!-- 网页错误 -->
             <div class="state" v-show="state.status == '网页错误'">
                 <img :src="require('@/assets/img/icon-page-fail.png')" alt />
@@ -56,6 +56,7 @@ import { getChromeStorage, setChromeStorage, httpContentToBack, defineProps } fr
 import { guid } from "@/uilts/help";
 import { sendEventInfo } from "@/uilts/event";
 import { onMounted, reactive, ref } from "vue";
+import { $ } from "@/uilts/help";
 let dom_toolbox = ref(null)
 let dom_iframe = ref(null)
 let state = reactive({
@@ -124,7 +125,7 @@ onMounted(() => {
         }
     }
     try {
-        dom.fixed = document.querySelector('#denet-tool-box-fixed')
+        dom.fixed = $('#denet-tool-box-fixed')
         if (dom.fixed && dom.fixed.style.display == 'block') {
             if (dom.fixed.dataset.tweetId == state.tweetId) {
                 state.status = '固定右上角'
@@ -193,6 +194,7 @@ const handleFull = () => {
     }
     // 切换状态
     state.status = '关闭'
+    state.status = '固定右上角'
     // 操作全屏dom
     dom.fixed.style.cssText = `
         width:100%;

+ 172 - 0
src/view/iframe/publish/components/customized-reward-edit.vue

@@ -0,0 +1,172 @@
+<template>
+    <div class="customized-reward-edit">
+        <div class="wrap">
+            <div class="header">
+                <img  
+                    :src="require('@/assets/svg/icon-close.svg')"
+                    class="icon-close"
+                    @click="cancel" />
+            </div>
+            <div class="title">Enter Reward Name</div>
+            <input
+                class="prize-name-input"
+                ref="customNameDom"
+                v-model="customizedReward"
+                maxlength="30"
+                placeholder="Enter Reward Name"
+                @input="onInput"/>
+            <div class="btns">
+                <div class="remove" :class="isUseFul  ? 'use-ful' : ''" @click="remove" >Remove</div>
+                <div class="confirm" :class="isUseFul ? 'use-ful' : ''" @click="submit" >Confirm</div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, defineProps, onMounted, computed, defineEmits, getCurrentInstance } from "vue";
+import { RewardType } from "@/types";
+
+const props = defineProps({
+    customizedReward: {
+        type: String,
+        default: ""
+    },
+    rewardType: {
+        type: Number,
+        default: RewardType.money
+    }
+});
+
+let customizedReward = ref(props.customizedReward);
+
+const emits = defineEmits(['removeReward', 'submitReward', 'closeRewardPopup']);
+
+let isUseFul = computed(() => customizedReward.value.trim() !== '');
+
+let isCheckedCustom = computed(() => props.rewardType === RewardType.custom);
+
+const onInput = (e) => {
+    customizedReward.value = e.target.value;
+}
+
+const cancel = () => {
+    emits('closeRewardPopup');
+}
+
+const submit = () => {
+    isUseFul.value && emits('submitReward', customizedReward.value.trim());
+}
+
+const remove = () => {
+    if(!isUseFul.value) {
+        return
+    }
+    customizedReward.value = '';
+    emits('removeReward');
+}
+
+onMounted(() => { 
+    let instance = getCurrentInstance()
+    instance.ctx.$refs.customNameDom.focus()
+})
+</script>
+
+<style lang="scss" scoped>
+.customized-reward-edit {
+    position: fixed;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    width: 100vw;
+    height: 100vh;
+    background-color: rgba(0,0,0,.6);
+    z-index: 1000000;
+
+    .wrap {
+        width: 630px;
+        height: 270px;
+        position: fixed;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        background: #FFFFFF;
+        border-radius: 20px;
+        padding: 0 0 20px;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+
+        .header {
+            height: 48px;
+            padding: 12px 14px;
+            position: relative;
+        }
+
+        .header::after{
+            content: "";
+            position: absolute;
+            width: 200%;
+            height: 1px;
+            bottom: 0;
+            left: 0;
+            background-color: #D1D9DD;
+            transform: scale(0.5);
+            transform-origin: 0;
+        }
+
+        .title {
+            font-weight: 500;
+            font-size: 16px;
+            line-height: 55px;
+            height: 55px;
+            margin:2px 20px 0;
+            letter-spacing: 0.3px;
+            
+        }
+        .prize-name-input {
+            height: 49px;
+            background: #FFFFFF;
+            border: 1px solid #DDDDDD;
+            border-radius: 100px;
+            margin: 0 20px;
+            padding: 0 20px;
+        }
+        .btns {
+            display: flex;
+            padding: 45px 20px 0;
+            justify-content: space-between;
+
+            div {
+                display: flex;
+                flex-direction: row;
+                justify-content: center;
+                align-items: center;
+                width: 170px;
+                height: 51px;
+                background: rgba(225, 225, 225, 0.01);
+                border: 1px solid #CECECE;
+                border-radius: 51px;
+                color: #cecece;
+                cursor: pointer;
+            }
+            .confirm {
+                background-color: #E1E1E1;
+                color: #fff;
+                cursor: pointer;
+            }
+
+            .use-ful{
+                &.remove {
+                    border: 1px solid #FF0A0A;
+                    color: #FF0A0A;
+                }
+
+                &.confirm {
+                    background: #1D9BF0;
+                }
+            }
+        }
+    }
+}
+</style>

+ 5 - 1
src/view/iframe/publish/components/giveaway-poster.vue

@@ -17,6 +17,8 @@
                                 nickName: userInfo.name,
                                 avatarUrl: userInfo.avatarUrl
                             },
+                            rewardType: baseFormData.rewardType,
+                            customizedReward: baseFormData.customizedReward
                         }"
                         :showBottom="false">
                     </custom-card-horizontal-cover>
@@ -65,7 +67,9 @@
                             userInfo: {
                                 nickName: userInfo.name,
                                 avatarUrl: userInfo.avatarUrl
-                            }
+                            },
+                            rewardType: baseFormData.rewardType,
+                            customizedReward: baseFormData.customizedReward
                         }">
                     </custom-card-cover>
                 </div>

+ 20 - 7
src/view/iframe/publish/components/preview-card.vue

@@ -3,7 +3,7 @@
     <div class="wrapper">
         <div class="card-container">
             <!-- 安装之后的卡片样式 -->
-            <div v-show="installStatus" class="left" :style="{'width': reviewCanvasParams.width+ 'px'}">
+            <div v-if="installStatus" class="left" :style="{'width': reviewCanvasParams.width+ 'px'}">
                 <div class="head" :style="{'zoom': reviewCanvasParams.zoom}">
                     <img :src="userInfo.avatarUrl"
                         class="avatar"/>
@@ -29,7 +29,9 @@
                             userInfo: {
                                 nickName: userInfo.name,
                                 avatarUrl: userInfo.avatarUrl
-                            }
+                            },
+                            rewardType: baseFormData.rewardType,
+                            customizedReward: baseFormData.customizedReward,
                         }"></custom-card-cover>
                     </div>
                 </div>
@@ -37,8 +39,8 @@
 
             <!-- 安装之前的卡片样式 -->
             <div class="content-before"
-                v-show="!installStatus" 
-                :style="{'width': reviewCanvasParams.width+ 'px'}">
+                v-else 
+                :style="{'width': (baseFormData.rewardType === RewardType.money) ? `${reviewCanvasParams.width}px` : '100%'}">
                 <div class="head" 
                     :style="{'zoom': reviewCanvasParams.zoom}">
                     <img :src="userInfo.avatarUrl"
@@ -52,8 +54,10 @@
                         </div>
                     </div>
                 </div>
-                <div :style="{'zoom': reviewCanvasParams.zoom}">
+                <div
+                    :style="{'zoom': baseFormData.rewardType === RewardType.money ? reviewCanvasParams.zoom : 1}">
                     <custom-card-horizontal-cover
+                        :class="baseFormData.rewardType === RewardType.money ? '' : 'custom-prize-card-wrapper'"
                         :data="{
                             totalCount: baseFormData.totalCount,
                             amountValue: baseFormData.amountValue,
@@ -65,7 +69,9 @@
                             userInfo: {
                                 nickName: userInfo.name,
                                 avatarUrl: userInfo.avatarUrl
-                            }
+                            },
+                            rewardType: baseFormData.rewardType,
+                            customizedReward: baseFormData.customizedReward,
                         }">
                     </custom-card-horizontal-cover>
                 </div>
@@ -79,7 +85,8 @@ import { ref, defineProps, onMounted, nextTick, watch, reactive, inject, onUnmou
 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"
+import { getUser } from "@/http/publishApi"
+import { RewardType } from '@/types';
 
 let userInfo = ref({});
 let reviewCanvasParams = reactive({
@@ -305,6 +312,12 @@ onUnmounted(() => {
             background-size: contain;
             background-repeat: no-repeat;
             height: 100%;
+           
+            .custom-prize-card-wrapper {
+                width: 370px;
+                left: 56px;
+                top: 170px;
+            }
         }
 
         .left, .content-before {

+ 189 - 28
src/view/iframe/publish/give-dialog.vue

@@ -82,8 +82,20 @@
                     <div class="currency-pop" v-show="showCurrencyPop">
                         <currency-list 
                             ref="currencyListDom"
+                            :showGeneralLottery="true"
                             @selectCurrency="selectCurrency"
-                            @setCurrencyList="setCurrentCurrencyInfo"></currency-list>
+                            @setCurrencyList="setCurrentCurrencyInfo"
+                            @addGeneralLottery="addCustomizedRewardHandle"></currency-list>
+                    </div>
+                    <!-- 通用奖品编辑弹窗 -->
+                    <div class="customized-reward-edit-popup" v-if="showCustomizedRewardEditPopup">
+                        <customized-reward-edit-popup
+                            ref="rewardEditDom"
+                            :customizedReward="baseFormData.customizedReward"
+                            :rewardType="baseFormData.rewardType"
+                            @closeRewardPopup="closeRewardPopup"
+                            @removeReward="removeReward"
+                            @submitReward="submitReward"></customized-reward-edit-popup>
                     </div>
                     <div class="currency-pop-select" v-show="showCurrencySelect">
                         <currency-select 
@@ -135,17 +147,16 @@
                                         <div class="item currency-select-wrapper">
                                             <div>
                                                 <div class="label currency-select"
-                                                    :class="{'selected': currentCurrencyInfo.currencyCode}"
+                                                    :class="{'selected': currencySelectCpd}"
                                                     @click="selectCurrencyPopHandle">
                                                     <img class="icon"
-                                                        v-if="currentCurrencyInfo.iconPath"
-                                                        :src="currentCurrencyInfo.iconPath"/>
+                                                        v-if="currentIconCpd"
+                                                        :src="currentIconCpd"/>
                                                     <div class="text">
-                                                        {{currentCurrencyInfo.currencyCode == 'USD' ? 'USD' : currentCurrencyInfo.tokenSymbol || 'Select a reward'}}
+                                                        {{currentPrizeCpd}}
                                                     </div>
                                                     <img class="arrow"
-                                                        :src="currentCurrencyInfo.currencyCode ?
-                                                            require('@/assets/svg/icon-form-arrow-down.svg') :  require('@/assets/svg/icon-form-white-arrow-down.svg')"/>
+                                                        :src="currentArrowCpd"/>
                                                 </div>
 
                                                 <!-- 刷新按钮、充值 -->
@@ -183,6 +194,7 @@
                                             </div>
                                             <input v-model="baseFormData.totalCount"
                                                 placeholder="0"
+                                                :disabled="baseFormData.rewardType === RewardType.custom"
                                                 @input="onCountInput"
                                                 @blur="onCountBlur"/>
                                         </div>
@@ -218,6 +230,8 @@
                                                         type: baseFormData.type,
                                                         validityDuration: baseFormData.validityDuration,
                                                         customPosterUrl: customPosterData && customPosterData.after && customPosterData.after.imagePath || '',
+                                                        rewardType: baseFormData.rewardType,
+                                                        customizedReward: baseFormData.customizedReward
                                                     }">
                                                 </custom-card-cover>
                                             </div>
@@ -315,10 +329,10 @@
                         <template v-else-if="showComType == 'preview'">
                             <div class="preview">
                                 <div class="card"
-                                    :class="{ center: Number(baseFormData.amountValue) <= Number(currentCurrencyInfo.balance) }">
+                                    :class="{ center: !isMoneyRewardCpd || Number(baseFormData.amountValue) <= Number(currentCurrencyInfo.balance) }">
                                     <div class="card-title">
                                         <img class="img"
-                                            v-if="Number(baseFormData.amountValue) > Number(currentCurrencyInfo.balance)"
+                                            v-if="isMoneyRewardCpd && Number(baseFormData.amountValue) > Number(currentCurrencyInfo.balance)"
                                             :src=" require('@/assets/subject/top-01.svg') " />
                                         <div class="font">
                                             Preview: <span>{{installStatus ? 'After' : 'Before' }}</span> DeNet Installed
@@ -335,7 +349,7 @@
                                     </div>
                                 </div>
                                 <!-- 需充值 -->
-                                <div class="card-content" v-if="Number(baseFormData.amountValue) > Number(currentCurrencyInfo.balance)">
+                                <div class="card-content" v-if="isMoneyRewardCpd && Number(baseFormData.amountValue) > Number(currentCurrencyInfo.balance)">
                                     <template v-if="currentCurrencyInfo.currencyCode === 'USD'">
                                         <div class="card-title">
                                             <img class="img" :src=" require('@/assets/subject/top-02.svg') " />
@@ -395,6 +409,7 @@
                         <!-- paypal支付按钮 -->
                         <div class="payment" v-show="showComType == 'preview'">
                             <paypal-button
+                                v-if="isMoneyRewardCpd"
                                 :finalAmountData="finalAmountData"
                                 :payConfig="{
                                     paypalClientId,
@@ -419,6 +434,7 @@
                                     </div>
                                 </template>
                             </paypal-button>
+                            <div v-else class="btn-wrap" @click="payStatusHandle(1)"><div class="custom-submit">Confirm</div></div>
                         </div>
                         </template>
                     </div>
@@ -471,7 +487,7 @@
 </template>
 
 <script setup>
-import { ref, watch, reactive, defineProps, defineEmits, onMounted, nextTick, provide, getCurrentInstance } from "vue";
+import { ref, watch, reactive, defineProps, defineEmits, onMounted, nextTick, provide, getCurrentInstance, computed } from "vue";
 import { postPublish, verifyPaypalResult, syncChainTokenRechargeRecord, getCurrencyInfoByCode } from "@/http/publishApi";
 import { getInviteGuildInfo, getInviteGuildInfoByOpenApi, saveInviteGuildInfo } from "@/http/discordApi";
 import { payCalcFee, getPayConfig } from "@/http/pay";
@@ -479,6 +495,7 @@ import { getFrontConfig } from "@/http/account";
 import { uploadSignature, uploadFile } from '@/http/media';
 import {setChromeStorage, getChromeStorage} from "@/uilts/chromeExtension"
 import { debounce, getBit } from "@/uilts/help"
+import { PlayType, RewardType } from '@/types';
 import Report from "@/log-center/log"
 import { ElMessage, ElLoading } from "element-plus";
 import "element-plus/es/components/message/style/css";
@@ -502,6 +519,8 @@ import customCardCover from '@/view/components/custom-card-cover.vue'
 
 const currentInstance = getCurrentInstance();
 
+import CustomizedRewardEditPopup from '@/view/iframe/publish/components/customized-reward-edit';
+
 const config = {
     number: 'BigNumber',
 }
@@ -634,11 +653,12 @@ let baseFormData = reactive({
     amountValue: "",
     totalCount: "",
     validityDuration: "",
-    type: selectModeInfo.type
+    type: selectModeInfo.type,
+    rewardType: RewardType.money,
+    customizedReward: ""
 });
 
-// 当前选择的货币信息
-let currentCurrencyInfo = ref({
+const defaultCurrentCurrencyInfo = {
     currencyCode: "",
     currencyName: "",
     balance: "",
@@ -648,7 +668,10 @@ let currentCurrencyInfo = ref({
     tokenChain: "",
     tokenSymbol: "",
     usdEstimateBalance: ""
-});
+}
+
+// 当前选择的货币信息
+let currentCurrencyInfo = ref(defaultCurrentCurrencyInfo);
 
 const discordIptErrTxt = 'Discord invite link is wrong';
 const discordIptEmptyErrTxt = 'Enter discord invite link';
@@ -724,6 +747,7 @@ let publishModeList = reactive([
 
 let discordInviteInfo = ref({});
 let showDiscordInvitePop = ref(false);
+let showCustomizedRewardEditPopup = ref(false);
 
 let lotteryMaxHourDuration = 168;
 
@@ -756,6 +780,41 @@ const props = defineProps({
     },
 });
 
+//selected prize icon cpd
+let currentIconCpd = computed(() => {
+    if(baseFormData.rewardType === RewardType.custom) {
+        return require("@/assets/svg/icon-gift.svg");
+    } else {
+        return currentCurrencyInfo.value.iconPath;
+    } 
+})
+
+// selected prize cpd
+let currentPrizeCpd = computed(() => {
+    const {currencyCode, tokenSymbol} = currentCurrencyInfo.value
+    if(baseFormData.rewardType === RewardType.custom) {
+        return baseFormData.customizedReward;
+    } else if(currencyCode == "USD") {
+        return "USD";
+    } else {
+        return tokenSymbol || "Select a reward";
+    }
+})
+
+let currencySelectCpd = computed(() => {
+    return baseFormData.customizedReward || currentCurrencyInfo.value.currencyCode;
+})
+
+let currentArrowCpd = computed(() => {
+    if(baseFormData.rewardType === RewardType.custom) {
+        return require("@/assets/svg/icon-cell-arrow-right.svg");
+    } else {
+        return currentCurrencyInfo.value.currencyCode ? require("@/assets/svg/icon-form-arrow-down.svg") :  require("@/assets/svg/icon-form-white-arrow-down.svg");
+    }
+})
+
+let isMoneyRewardCpd =computed(() => baseFormData.rewardType === RewardType.money);
+
 watch(
     () => props.dialogData,
     (newVal) => {
@@ -843,12 +902,16 @@ const selectCurrencyPopHandle = () => {
         pageSource: Report.pageSource.currencySelectorPage,
         businessType: Report.businessType.pageView,
     });
-    showCurrencyPop.value = true;
-    nextTick(() => {
-        if(currencyListDom.value) {
-            currencyListDom.value.getCurrencyInfoList && currencyListDom.value.getCurrencyInfoList();
-        }
-    })
+    if(baseFormData.rewardType === RewardType.custom) {
+        showCustomizedRewardEditPopup.value = true
+    } else {
+        showCurrencyPop.value = true;
+        nextTick(() => {
+            if(currencyListDom.value) {
+                currencyListDom.value.getCurrencyInfoList && currencyListDom.value.getCurrencyInfoList();
+            }
+        })
+    }
 }
 
 /**
@@ -942,12 +1005,16 @@ const selectCurrencyAfter = (params, openWindow = true) => {
         resetFormIpt(false);
         onIptSetErrorTxt();
     }
+    baseFormData.customizedReward = "";
+    baseFormData.rewardType = RewardType.money;
 }
 
 const resetFormIpt = (clearMode = true) => {
     baseFormData.amountValue = "";
     baseFormData.totalCount = "";
     baseFormData.validityDuration = "";
+    baseFormData.rewardType = RewardType.money;
+    baseFormData.customizedReward = "";
 
     if(clearMode) {
         selectModeInfo.index = 0;
@@ -1083,7 +1150,12 @@ const asyncTokenRechRecord = (cb) => {
  * 提交表单请求
  */
 const submitRequest = async () => {
-    let { amountValue = 0, totalCount = 0 } = baseFormData;
+    let {   
+            amountValue = 0,
+            totalCount = 0,
+            rewardType = RewardType.money,
+            customizedReward = "" 
+        } = baseFormData;
     baseFormData.amountCurrencyCode = currentCurrencyInfo.value.currencyCode;
     // 组装提交参数
 
@@ -1110,7 +1182,7 @@ const submitRequest = async () => {
     let receiveConditions = openAntiBot.value ? "" : [];
 
     let validityDuration = '';
-    if(baseFormData.type == 2) {
+    if(baseFormData.type == PlayType.lottery) {
         //小时转毫秒
         let unit = process.env.NODE_ENV != 'production' ? 60 * 1000 : 60 * 60 * 1000;
         // let unit = 60 * 60 * 1000
@@ -1121,16 +1193,24 @@ const submitRequest = async () => {
 
     // 提交参数
     let formData = {
-        amountCurrencyCode: baseFormData.amountCurrencyCode,
         amountValue,
         totalCount,
         finishConditions,
         receiveConditions,
-        payAmountValue: amountValue,
         type: baseFormData.type,
         posterType: 1,
-        validityDuration
+        validityDuration,
+        rewardType
     };
+    if(rewardType === RewardType.custom) {
+        // 通用奖品 类型的活动,添加奖品名称
+        formData.customizedReward = customizedReward;
+        delete formData.amountValue;
+    } else {
+        // 货币类型 添加货币code
+        formData.amountCurrencyCode = baseFormData.amountCurrencyCode;
+        formData.payAmountValue = baseFormData.payAmountValue;
+    }
     submitIng.value = true;
 
     // 自定义封面
@@ -1300,6 +1380,8 @@ const onAmountInput = () => {
     // val = val.replace(/[^\d^\.]+/g, "");
     val = val.replace(/^\D*(\d*(?:\.\d{0,18})?).*$/g, '$1');
 
+    const maxCount = baseFormData.rewardType === RewardType.money ? Number.MAX_SAFE_INTEGER : 100000000;
+
     if(val == '00') {
         val = '0'
     }
@@ -1311,6 +1393,15 @@ const onAmountInput = () => {
         }
     }
 
+    if (baseFormData.rewardType === RewardType.custom) {
+        const maxCount = 100000000;
+        val = val.replace(/^(0)*/, '').replace(/\./, ''); // 通用奖品类型 过滤掉起始位的0和小数点符号
+        if (val > maxCount) { 
+            val = maxCount
+        }
+        baseFormData.totalCount = val;
+    }
+
     baseFormData.amountValue = val;
     setInputErrorMsg({from: 'amount', type:'input'});
 
@@ -1412,13 +1503,14 @@ const setInputErrorMsg = () => {
  * 输入时 检测设置错误信息
  */
 const onIptSetErrorTxt = (params = {}) => {
-    if(!currentCurrencyInfo.value.currencyCode) {
+    if((baseFormData.rewardType === RewardType.money && !currentCurrencyInfo.value.currencyCode)
+    || (baseFormData.rewardType === RewardType.custom && !baseFormData.customizedReward)) {
         iptErrMsgTxt.value = "Select a reward"
     } else if (!baseFormData.amountValue || baseFormData.amountValue == '0') {
         iptErrMsgTxt.value = "Enter an amount";
     } else if (!baseFormData.totalCount || baseFormData.totalCount == '0') {
             iptErrMsgTxt.value = "Enter the number of winners";
-    } else if(+baseFormData.amountValue <= +currentCurrencyInfo.value.balance) {
+    } else if(baseFormData.rewardType === RewardType.money && +baseFormData.amountValue <= +currentCurrencyInfo.value.balance) {
         // 输入金额 小于 余额
         let res = calcIptValue();
         if (!res.flag) {
@@ -1879,6 +1971,37 @@ const confirmData = (data) => {
     close()
     customPosterData.value = customPosterInfo.value;
 }
+/**
+ * 显示通用奖品名称编辑框
+ */
+const addCustomizedRewardHandle = () => {
+    showCurrencyPop.value = false;
+    showCustomizedRewardEditPopup.value = true;
+}
+
+const closeRewardPopup = () => {
+    showCustomizedRewardEditPopup.value = false;
+}
+
+const removeReward = () => {
+    showCustomizedRewardEditPopup.value = false;
+    resetFormIpt(false);
+    onIptSetErrorTxt();
+}
+
+// 提交通用奖品
+const submitReward = (reward) => {
+    if(baseFormData.customizedReward !== reward) {
+        // 有修改时,重置之前已提交的数据
+        resetFormIpt(false);
+    }
+    baseFormData.rewardType = RewardType.custom;
+    baseFormData.customizedReward = reward;
+    showCustomizedRewardEditPopup.value = false;
+    currentCurrencyInfo.value = defaultCurrentCurrencyInfo;
+    setLocalSelectCurrencyInfo(defaultCurrentCurrencyInfo);
+    onIptSetErrorTxt();
+}
 
 onMounted(() => {
     setFrontConfig();
@@ -2189,6 +2312,12 @@ onMounted(() => {
                                     input::placeholder {
                                         color: #c5c5c5;
                                     }
+
+                                    input:disabled {
+                                        color: #c5c5c5;
+                                        background-color: #fff;
+                                    }
+
                                     input {
                                         padding-right: 16px;
                                     }       
@@ -2421,6 +2550,7 @@ onMounted(() => {
                         height: 60px;
                         margin-left: 14px;
                         margin-right: 14px;
+                        position: relative;
                     }
                     .show-font {
                         position: relative;
@@ -2626,6 +2756,37 @@ onMounted(() => {
             margin-left: -5px;
         }
     }
+    .btn-wrap {
+        width: 100%;
+        height: 80px;
+        background-color: #fff;
+        position: absolute;
+        left: 0;
+        bottom: 0;
+        box-shadow: 0px -1px 0px #ececec;
+        border-bottom-right-radius: 16px;
+        padding: 12px 30px;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        border-bottom-left-radius: 16px;
+        z-index: 999;
+        .custom-submit {
+            width: 200px;
+            height: 50px;
+            background: #1D9BF0;
+            border-radius: 50px;
+            font-weight: 700;
+            font-size: 18px;
+            line-height: 50px;
+            text-align: center;
+            letter-spacing: 0.3px;
+            color: #FFFFFF;
+            cursor: pointer;
+        }
+    }
+    
 }
 
 .dialog {

+ 65 - 60
src/view/iframe/publish/tool-box/child/editor.vue

@@ -21,10 +21,9 @@
             <img :src="require('@/assets/svg/icon-tool-app-history.svg')" />
           </div>
           <div class="app-list">
-            <div class="app" v-for="(app, idx) in historyList" :key="idx"
-              @click="clickHistoryAppHandler(app)">
+            <div class="app" v-for="(app, idx) in historyList" :key="idx" @click="clickHistoryAppHandler(app)">
               <div class="img-wrapper">
-                <img class="img" :class="{'small-img': !app.appId}" :src="app.iconPath" :onerror="imgOnError" />
+                <img class="img" :class="{ 'small-img': !app.appId }" :src="app.iconPath" :onerror="imgOnError" />
               </div>
               <div class="name">
                 {{ app.name }}
@@ -59,10 +58,10 @@ import { getChromeStorage } from "@/uilts/chromeExtension"
 import { checkURL } from "@/uilts/help"
 
 const props = defineProps({
-    linkInputDescImage: {
-        type: String,
-        default: '',
-    },
+  linkInputDescImage: {
+    type: String,
+    default: '',
+  },
 });
 
 
@@ -80,39 +79,42 @@ const emits = defineEmits(["changeShowCom"]);
 const searchHandler = async () => {
   let siteTitle = '', favicon = '';
   let timer = null;
-  if(!siteUrl.value) {
+
+
+  if (!siteUrl.value) {
     return;
   }
 
   siteUrl.value = siteUrl.value.trim();
 
-  if(!checkURL(siteUrl.value)) {
+  if (!checkURL(siteUrl.value)) {
     message.info('Incorrect URL entered');
     //提示
     return;
-  } 
+  }
   const loadingHide = message.loading('loading...', 0);
   timer = setTimeout(() => {
     loadingHide();
     message.error('Page loading failed');
   }, 1000 * 15);
+
   let siteRes = await axios.get(siteUrl.value);
 
-  if(siteRes) {
-    if(siteRes.headers['content-type'].indexOf('text/html') < 0 || siteRes.request.status > 399) {
+  if (siteRes) {
+    if (siteRes.headers['content-type'].indexOf('text/html') < 0 || siteRes.request.status > 399) {
       // 提示
       return;
     }
     let urlObj = new URL(siteUrl.value);
-    if(siteRes.data) {
+    if (siteRes.data) {
       siteTitle = getTitleByHtmlStr(siteRes.data);
-      if(!siteTitle) {
+      if (!siteTitle) {
         siteTitle = urlObj.hostname;
-      } 
+      }
       console.log(siteTitle)
     }
     favicon = urlObj.origin + '/favicon.ico';
-  } 
+  }
 
   let currentApp = {
     appId: '',
@@ -126,30 +128,33 @@ const searchHandler = async () => {
     name: siteTitle,
   }
 
-  let convertRes = await convertUrl({params: {originUrl: siteUrl.value}});
+  let convertRes = await convertUrl({ params: { originUrl: siteUrl.value } });
   let params = { convertUrl: siteUrl.value, originUrl: siteUrl.value, appId: '', currentApp };
 
   loadingHide();
   clearTimeout(timer);
 
-  if(convertRes && convertRes.code == 0) {
-    let {convertUrl} = convertRes.data || {};
+  if (convertRes && convertRes.code == 0) {
+    let { convertUrl } = convertRes.data || {};
     params.convertUrl = convertUrl;
   }
   emits('changeShowCom', params)
 }
 
-const getTitleByHtmlStr = (str) => {
-  let index1 = str.indexOf('<title>') + 7;
-  let index2 = str.indexOf('</title>');
-  if(index2 < 0) {
+const getTitleByHtmlStr = (str = '') => {
+  let tag_start = '<title>'
+  let tag_end = '</title>'
+  let index1 = str.indexOf(tag_start) + tag_start.length;
+  let index2 = str.indexOf(tag_end);
+  if (index1 < 0 || index2 < 0) {
     return '';
   }
+
   return str.substring(index1, index2) || '';
 };
 
 const clickHistoryAppHandler = (params) => {
-  if(params.appId) {
+  if (params.appId) {
     clickAppHandler(params);
   } else {
     siteUrl.value = params.defaultUrl;
@@ -171,11 +176,11 @@ const clickAppHandler = (params) => {
 
 const openWindow = (params) => {
   chrome.windows.getCurrent({},
-    function(window) {
-      if(window && window.state == "fullscreen") {
-        chrome.windows.update(window.id,{
+    function (window) {
+      if (window && window.state == "fullscreen") {
+        chrome.windows.update(window.id, {
           state: 'normal'
-        }, function() {
+        }, function () {
           setTimeout(() => {
             createGuideWindow(params, true);
           }, 1000)
@@ -183,7 +188,7 @@ const openWindow = (params) => {
       } else {
         createGuideWindow(params);
       }
-  })
+    })
 };
 
 const createGuideWindow = (params, isUpdate = false) => {
@@ -211,14 +216,14 @@ const createGuideWindow = (params, isUpdate = false) => {
     openWindowList.push(window);
   })
 
-  if(params.guideData) {
+  if (params.guideData) {
     selectAppGuideData = JSON.parse(params.guideData);
   }
 }
 
 const getAppList = () => {
-  getAllPostEditorAppData({params: {}}).then(res => {
-    if(res.code == 0) {
+  getAllPostEditorAppData({ params: {} }).then(res => {
+    if (res.code == 0) {
       appList.value = res.data || [];
     }
   })
@@ -230,13 +235,13 @@ const onRuntimeMsg = () => {
     sendResponse('ok')
     switch (req.actionType) {
       case 'CONTENT_GET_GUIDE_DATA':
-        chrome.runtime.sendMessage({ 
-            actionType: "CONTENT_EDIT_SEND_GUIDE_DATA", 
-            data: {
-              guideData: selectAppGuideData,
-              windowData: openWindowList
-            }
-        },(response) => {});
+        chrome.runtime.sendMessage({
+          actionType: "CONTENT_EDIT_SEND_GUIDE_DATA",
+          data: {
+            guideData: selectAppGuideData,
+            windowData: openWindowList
+          }
+        }, (response) => { });
         break;
       case 'CONTENT_GUIDE_APPLY_APP':
         siteUrl.value = req.data.siteUrl;
@@ -247,7 +252,7 @@ const onRuntimeMsg = () => {
 }
 
 const getHistoryList = async () => {
-  let {list = []} = await getChromeStorage('toolBoxAppHistoryData') || {};
+  let { list = [] } = await getChromeStorage('toolBoxAppHistoryData') || {};
   historyList.value = list;
 };
 
@@ -336,28 +341,28 @@ onMounted(() => {
       .history-wrapper {
 
         .app-list {
-            .img-wrapper {
-              width: 60px;
-              height: 60px;
+          .img-wrapper {
+            width: 60px;
+            height: 60px;
+            border-radius: 10px;
+            margin-bottom: 10px;
+            border: 1px solid #E5E5E5;
+            box-sizing: border-box;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+
+            .img {
+              width: 100%;
+              height: 100%;
               border-radius: 10px;
-              margin-bottom: 10px;
-              border: 1px solid #E5E5E5;
-              box-sizing: border-box;
-              display: flex;
-              align-items: center;
-              justify-content: center;
-
-              .img {
-                width: 100%;
-                height: 100%;
-                border-radius: 10px;
-              }
-
-              .small-img {
-                width: 25px;
-                height: 25px;
-              }
             }
+
+            .small-img {
+              width: 25px;
+              height: 25px;
+            }
+          }
         }
       }
 

+ 154 - 51
src/view/iframe/red-packet/luck-draw.vue

@@ -128,6 +128,7 @@
         <!-- success -->
         <div v-if="state.status == 'success'" class="success">
             <div class="header"
+                v-if="state.detail.rewardType === RewardType.money"
                 :style="{ 'backgroundImage': `url(${require('@/assets/subject/002-back-head-top-180.svg')})` }">
                 <div class="success-title">
                     🎉 Awesome! You are Winner!
@@ -143,10 +144,31 @@
                     <img :src="require('@/assets/svg/icon-right.svg')" alt class="icon-right" />
                 </div>
             </div>
+            <div class="header header-custom-prize"
+                v-else
+                :style="{ 'backgroundImage': `url(${successTopBgCpd})`, 'minHeight': '150px'}">
+                <div class="success-title">
+                    🎉 Awesome! You are the Winner!
+                </div>
+                <div class="custom-prize-show">
+                    
+
+                    <component-zoom width="340" fontSize="22" class="custom-prise-name">
+                        <img :src="require('@/assets/subject/icon-gift-inline.svg')" alt />
+                        <span>{{state.detail.customizedReward}}</span>
+                    </component-zoom>
+                    <!-- <font-zoom :amount="state.detail.customizedReward" width="500" class="custom-prise-name" fontSize="22"></font-zoom> -->
+                </div>
+                <div class="done" @click="clickDone">
+                    <img :src="require('@/assets/subject/001-icon-done.svg')" alt class="icon-done" />
+                    <span>View Rewards In Wallet</span>
+                    <img :src="require('@/assets/svg/icon-right.svg')" alt class="icon-right" />
+                </div>
+            </div>
             <div class="luck-list-title">
                 <div>{{ state.detail.receiveCount || 0 }}/{{ state.detail.totalCount || 0 }} Winners</div>
                 <div class="right">
-                    <span class="text">
+                    <span class="text" v-if="state.detail.rewardType === RewardType.money">
                         <a-tooltip :title="state.detail.receiveAmountValue">
                             {{ getBit(state.detail.receiveAmountValue) }}
                         </a-tooltip>
@@ -172,7 +194,7 @@
                         <div class="luck-title" v-else>Twitter User</div>
                         <div class="luck-time">{{ moment(item.receiveTimestamp).format('MM-DD HH:mm') }}</div>
                     </div>
-                    <div class="luck-money">
+                    <div class="luck-money"  v-if="state.detail.rewardType === RewardType.money">
                         <img :src="state.detail.currencyIconPath" alt />
                         <div class="luck-money-txt">
                             <a-tooltip :title="item.amountValue">
@@ -180,7 +202,8 @@
                             </a-tooltip>
                         </div>
                     </div>
-                    <div class="luck-king" v-if="item.maxAmount">
+                    <div class="luck-custom-prize" v-else>winner</div>
+                    <div class="luck-king" v-if="state.detail.rewardType === RewardType.money && item.maxAmount">
                         <img :src="require('@/assets/svg/icon-king-hat.svg')" alt />
                         <span>Luckiest Draw</span>
                     </div>
@@ -192,7 +215,7 @@
 
         <!-- no-open -->
         <div v-else-if="state.status == 'not-open'" class="not-open">
-            <template v-if="state.detail.posterType === 2 && state.detail.customPosterInstalled">
+            <!-- <template v-if="state.detail.posterType === 2 && state.detail.customPosterInstalled">
                 <img class="customImg" :src="state.detail.customPosterInstalled" />
                 <div class="customBottom">
                     <div class="theme">
@@ -203,35 +226,34 @@
                     <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>
+                        <span class="prize-name">{{state.detail.rewardType === RewardType.money ? state.detail.amountValue + ' ' + state.detail.currencySymbol : state.detail.customizedReward}}</span>
                     </div>
                     <div class="open-red" @click="clickOpenRedPacket">
                         Participate Now
                     </div>
                 </div>
-            </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>
-            </template>
+            </template> -->
+            <!-- <template v-else> -->
+                <custom-card-cover 
+                    :data="{
+                        totalCount: state.detail.totalCount,
+                        amountValue: state.detail.amountValue,
+                        tokenSymbol: state.detail.currencySymbol,
+                        currencyIconUrl: state.detail.currencyIconPath,
+                        type: PlayType.lottery,
+                        validityDuration: state.count_down_time,
+                        countDown: state.count_down_time,
+                        userInfo: {
+                            nickName: state.detail.postUserInfo.name,
+                            avatarUrl: state.detail.postUserInfo.avatarUrl
+                        },
+                        rewardType: state.detail.rewardType,
+                        customizedReward: state.detail.customizedReward,
+                        customPosterUrl: state.detail.customPosterInstalled
+                    }"
+                    @clickOpenRedPacket = "clickOpenRedPacket"
+                ></custom-card-cover>
+            <!-- </template> -->
         </div>
 
 
@@ -242,7 +264,7 @@
             </div>
             <div class="luck-list-title">
                 <div>{{ state.detail.receiveCount || 0 }}/{{ state.detail.totalCount || 0 }} Winners</div>
-                <div class="right">
+                <div class="right" v-if="state.detail.rewardType === RewardType.money">
                     <span class="text">
                         <a-tooltip :title="state.detail.receiveAmountValue">
                             {{ getBit(state.detail.receiveAmountValue) }}
@@ -270,7 +292,7 @@
                         <div class="luck-title" v-else>Twitter User</div>
                         <div class="luck-time">{{ moment(item.receiveTimestamp).format('MM-DD HH:mm:ss') }}</div>
                     </div>
-                    <div class="luck-money">
+                    <div class="luck-money"  v-if="state.detail.rewardType === RewardType.money">
                         <img :src="state.detail.currencyIconPath" alt />
                         <div class="luck-money-txt">
                             <a-tooltip :title="item.amountValue">
@@ -278,8 +300,8 @@
                             </a-tooltip>
                         </div>
                     </div>
-
-                    <div class="luck-king" v-if="item.maxAmount">
+                    <div class="luck-custom-prize" v-else>winner</div>
+                    <div class="luck-king" v-if="state.detail.rewardType === RewardType.money && item.maxAmount">
                         <img :src="require('@/assets/svg/icon-king-hat.svg')" alt />
                         <span>Luckiest Draw</span>
                     </div>
@@ -345,7 +367,7 @@
 
             <div class="luck-list-title" v-show="state.close_status != '等待结果'">
                 <div>{{ state.detail.receiveCount || 0 }}/{{ state.detail.totalCount || 0 }} Winners</div>
-                <div class="right">
+                <div class="right" v-if="state.detail.rewardType === RewardType.money">
                     <span class="text">
                         <a-tooltip :title="state.detail.receiveAmountValue">
                             {{ getBit(state.detail.receiveAmountValue) }}
@@ -374,7 +396,7 @@
                         <div class="luck-title" v-else>Twitter User</div>
                         <div class="luck-time">{{ moment(item.receiveTimestamp).format('MM-DD HH:mm:ss') }}</div>
                     </div>
-                    <div class="luck-money">
+                    <div class="luck-money" v-if="state.detail.rewardType === RewardType.money">
                         <img :src="state.detail.currencyIconPath" alt />
                         <div class="luck-money-txt">
                             <a-tooltip :title="item.amountValue">
@@ -382,7 +404,8 @@
                             </a-tooltip>
                         </div>
                     </div>
-                    <div class="luck-king" v-if="item.maxAmount">
+                    <div class="luck-custom-prize" v-else>winner</div>
+                    <div class="luck-king" v-if="state.detail.rewardType === RewardType.money && item.maxAmount">
                         <img :src="require('@/assets/svg/icon-king-hat.svg')" alt />
                         <span>Luckiest Draw</span>
                     </div>
@@ -429,11 +452,13 @@ export default {
 }
 </script>
 <script setup>
-import { onMounted, reactive, ref } from "vue";
+import { onMounted, reactive, ref, computed } from "vue";
 import { getPostDetail, getRedPacket, finishRedPacket, oneKeyLike, oneKeyReTweet, oneKeyFollow, getTaskDetail, getReceivedList, addFinishEvent } from '@/http/redPacket.js'
 import { getQueryString, guid, getBit, formatSecondsAsDaysOrTime } from '@/uilts/help.js'
 import { message } from 'ant-design-vue';
 import FontAmount from '@/view/components/font-amount.vue'
+import FontZoom from '@/view/components/font-zoom.vue'
+import ComponentZoom from '@/view/components/component-zoom.vue'
 import GetMore from '@/view/iframe/publish/components/get-more.vue'
 import { setChromeStorage, getChromeStorage, sendChromeTabMessage } from '@/uilts/chromeExtension.js'
 import Report from "@/log-center/log"
@@ -443,7 +468,10 @@ import { discordAuthRedirectUri, faceShareRedirectUrl } from '@/http/configAPI'
 import { getSetting, putSetting } from '@/http/user'
 import { getFrontConfig } from "@/http/account";
 import { getInviteGuildInfo } from "@/http/discordApi";
-import GlobalTip from '@/view/components/global-tip.vue'
+import GlobalTip from '@/view/components/global-tip.vue';
+import customCardCover from '@/view/components/custom-card-cover.vue';
+import { RewardType, PlayType } from "@/types";
+
 var moment = require('moment');
 
 let discordAuthorizeRequired = false;
@@ -475,6 +503,8 @@ let state = reactive({
     page_index: 1,
     page_size: 20,
     srcContentId: '',
+    customCover: '',
+    customGiveaway: '',
     error_txt: `oops, new accounts cannot participate in this event,`,
     receiveAmount: 0,
     money: 0,
@@ -495,6 +525,17 @@ let state = reactive({
 
 let fullName = '';
 
+let successTopBgCpd = computed(() => {
+    const { rewardType } = state.detail
+    switch (rewardType) {
+        case RewardType.custom:
+            return require('@/assets/subject/success-top-bg-2.svg');
+        default:
+            return require('@/assets/subject/002-back-head-top-180.svg');
+            break;
+    }
+});
+
 function clickRetry() {
     init()
 }
@@ -560,6 +601,8 @@ async function clickLikeBtn() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 function clickDone() {
@@ -572,7 +615,9 @@ function clickDone() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 function handleScroll(e) {
@@ -673,6 +718,8 @@ async function clickRetweetBtn() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 
@@ -733,7 +780,9 @@ async function clickReply(params) {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 
@@ -775,7 +824,9 @@ async function clickRepostFacebook(params) {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 
@@ -983,7 +1034,9 @@ async function clickFollowAll(item, is_all) {
     let _log_obj = {
         pageSource: Report.pageSource.task_page,
         businessType: Report.businessType.buttonClick,
-        objectType: Report.objectType.follow
+        objectType: Report.objectType.follow,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     }
     if (is_all) {
         _log_obj.objectType = Report.objectType.follow_button
@@ -1107,7 +1160,9 @@ const showSuccessPage = () => {
             senderId: state.userId,
             isOldTwitterFans: reportParams.done.follow,
             isOldDiscordFans: reportParams.done.join_discord,
-            redPacketType: 1
+            redPacketType: 1,
+            customCover: state.customCover,
+            customGiveaway: state.customGiveaway,
         });
     }
 }
@@ -1126,7 +1181,9 @@ const showNotOpenPage = () => {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 const showOpenedPage = () => {
@@ -1149,7 +1206,9 @@ const showOpenedPageReport = () => {
         senderId: state.userId,
         isOldTwitterFans: state.done.follow,
         isOldDiscordFans: state.done.join_discord,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 
@@ -1186,7 +1245,7 @@ const handleStatusPage = () => {
     //    如果 任务完成状态 = 未完成 & 红包状态 = 已结束
     //         显示过期页面
     //    如果 任务完成状态 = 已经完成
-    //        如果 领取到红包金额 = 0 & 红包状态 = 已结束
+    //        如果 (货币奖品类型 领取到红包金额 = 0 || 通用奖品类型 winner = 0) & 红包状态 = 已结束
     //            显示兔子页面
     //        否则如果 红包状态 = 已结束
     //            显示成功页面
@@ -1196,7 +1255,7 @@ const handleStatusPage = () => {
     //    如果 任务完成状态 = 已经过期
     //        如果 红包状态 = 进行中
     //            显示未打开页面
-    //        否则 
+    //        否则
     //            显示已经过期页面
 
     // 如果 我没有领取过 & 红包状态 = 进行中
@@ -1210,6 +1269,7 @@ const handleStatusPage = () => {
 
     // -------- 华丽的分割线 --------
     // 如果 红包状态 = 未开始
+    
     if (state.detail.status == 0) {
         showNotOpenPage()
         return
@@ -1240,7 +1300,7 @@ const handleStatusPage = () => {
             //如果 任务完成状态 = 已经完成
         } else if (state.detail.myReceived.taskFinishStatus == 1) {
             // 领取到空红包 & 红包状态 = 已结束
-            if (state.receiveAmount == 0 && state.detail.status == 2) {
+            if (((state.detail.rewardType === RewardType.money && state.receiveAmount == 0) || (state.detail.rewardType === RewardType.custom && state.detail.myReceived.winner === 0)) && state.detail.status == 2) {
                 showRabbitPage()
                 showRabbitPageReport()
             } else if (state.detail.status == 2) {
@@ -1331,6 +1391,8 @@ function init(initParams) {
             state.tweetId = state.srcContentId;
             state.userId = res.data.srcUserId;
             state.tweet_author = state.detail.postUserInfo && state.detail.postUserInfo.nickName || '';
+            state.customCover = state.detail.posterType == 2 ? 1 : 0;
+            state.customGiveaway = state.detail.posterType == 2 ? 1 : 0;
             // 不要删除这个console
             console.log('postBizData', state.detail)
             checkFacebookReply();
@@ -1572,7 +1634,9 @@ function handleRedPacket() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 
@@ -1634,6 +1698,8 @@ function handleFinishRedPacket() {
                     postId: state.postId,
                     srcContentId: state.tweetId,
                     senderId: state.userId,
+                    customCover: state.customCover,
+                    customGiveaway: state.customGiveaway,
                 }, {
                     get_giveaway_result: Report.extParams.success
                 });
@@ -1714,6 +1780,8 @@ function handleFinishRedPacket() {
                     postId: state.postId,
                     srcContentId: state.tweetId,
                     senderId: state.userId,
+                    customCover: state.customCover,
+                    customGiveaway: state.customGiveaway,
                 }, {
                     get_giveaway_result: Report.extParams.failure,
                 });
@@ -1731,6 +1799,8 @@ function handleFinishRedPacket() {
                 postId: state.postId,
                 srcContentId: state.tweetId,
                 senderId: state.userId,
+                customCover: state.customCover,
+                customGiveaway: state.customGiveaway,
             }, {
                 get_giveaway_result: Report.extParams.failure,
             });
@@ -2037,7 +2107,9 @@ async function joinDiscord() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 
     let url = getInviteUrl();
@@ -2448,8 +2520,25 @@ body {
                     color: #fff;
                 }
             }
+
+            .custom-prize-show {
+                width: 100%;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                height: 44px;
+                margin-top: 20px;
+
+                img {
+                    width: 24px;
+                    height: 24px;
+                    margin-right: 9px;
+                }
+            }
         }
 
+        
+
         .luck-list-title {
             /*      margin-top: 47px;*/
             margin: 0 16px;
@@ -2582,14 +2671,19 @@ body {
                         color: #444444;
                     }
                 }
+
+                .luck-custom-prize {
+                    font-weight: 500;
+                    font-size: 14px;
+                    letter-spacing: 0.3px;
+                    color: #F5B945;
+                }
             }
 
             .luck-item:last-child {
                 border: 0;
             }
         }
-
-
     }
 
     .close {
@@ -2752,6 +2846,15 @@ body {
             margin-top: 10px;
             border-bottom: 1px solid #ECECEC;
         }
+
+        .header-custom-prize {
+            align-items: flex-start;
+            .success-title {
+                line-height: 21px;
+                margin-top: 23px;
+                font-size: 18px;
+            }
+        }
     }
 
     .opened {

+ 131 - 42
src/view/iframe/red-packet/red-packet.vue

@@ -146,7 +146,7 @@
 
     <!-- success -->
     <div v-if="state.status == 'success'" class="success">
-      <div class="header" :style="{ 'backgroundImage': `url(${require('@/assets/subject/001-back-head-top.svg')})` }">
+      <div class="header" v-if="state.detail.rewardType === RewardType.money" :style="{ 'backgroundImage': `url(${require('@/assets/subject/001-back-head-top.svg')})` }">
         <div class="money">
           <img :src="state.detail.currencyIconPath" alt />
           <font-amount :amount="state.receiveAmount" class="big" :fontSize="46"></font-amount>
@@ -158,10 +158,24 @@
           <img :src="require('@/assets/svg/icon-right.svg')" alt class="icon-right" />
         </div>
       </div>
+      <div class="header header-custom-prize" v-else :style="{ 'backgroundImage': `url(${successTopBgCpd})`, 'minHeight': '150px' }">
+        <div class="success-title">
+            🎉 Awesome! You are the Winner!
+        </div>
+        <div class="custom-prize-show">
+            <img :src="require('@/assets/subject/icon-gift-inline.svg')" alt />
+            <font-zoom :amount="state.detail.customizedReward" width="500" class="custom-prise-name" fontSize="22"></font-zoom>
+        </div>
+        <div class="done" @click="clickDone">
+          <img :src="require('@/assets/subject/001-icon-done.svg')" alt class="icon-done" />
+          <span>View Rewards In Wallet</span>
+          <img :src="require('@/assets/svg/icon-right.svg')" alt class="icon-right" />
+        </div>
+      </div>
       <div class="luck-list-title">
         <div>{{ state.detail.receiveCount || 0 }}/{{ state.detail.totalCount || 0 }} Winners</div>
         <div class="right">
-          <span class="text">
+          <span class="text" v-if="state.detail.rewardType === RewardType.money">
             <a-tooltip :title="state.detail.receiveAmountValue">
               {{ getBit(state.detail.receiveAmountValue) }}
             </a-tooltip>
@@ -183,7 +197,7 @@
             <div class="luck-title" v-else>Twitter User</div>
             <div class="luck-time">{{ moment(item.receiveTimestamp).format('MM-DD HH:mm') }}</div>
           </div>
-          <div class="luck-money">
+          <div class="luck-money" v-if="state.detail.rewardType === RewardType.money">
             <img :src="state.detail.currencyIconPath" alt />
             <div class="luck-money-txt">
               <a-tooltip :title="item.amountValue">
@@ -191,7 +205,8 @@
               </a-tooltip>
             </div>
           </div>
-          <div class="luck-king" v-if="item.maxAmount">
+          <div class="luck-custom-prize" v-else>winner</div>
+          <div class="luck-king" v-if="state.detail.rewardType === RewardType.money && item.maxAmount">
             <img :src="require('@/assets/svg/icon-king-hat.svg')" alt />
             <span>Luckiest Draw</span>
           </div>
@@ -203,7 +218,7 @@
 
     <!-- no-open -->
     <div v-else-if="state.status == 'not-open'" class="not-open">
-      <template v-if="state.detail.posterType === 2 && state.detail.customPosterInstalled">
+      <!-- <template v-if="state.detail.posterType === 2 && state.detail.customPosterInstalled">
         <img class="customImg" :src="state.detail.customPosterInstalled" />
         <div class="customBottom">
             <div class="theme">
@@ -219,24 +234,26 @@
             </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>
+      <template v-else> -->
+        <custom-card-cover 
+          :data="{
+              totalCount: state.detail.totalCount,
+              amountValue: state.detail.amountValue,
+              tokenSymbol: state.detail.currencySymbol,
+              currencyIconUrl: state.detail.currencyIconPath,
+              type: PlayType.common,
+              validityDuration: state.detail.validityDuration,
+              userInfo: {
+                  nickName: state.detail.postUserInfo.name,
+                  avatarUrl: state.detail.postUserInfo.avatarUrl
+              },
+              rewardType: state.detail.rewardType,
+              customizedReward: state.detail.customizedReward,
+              customPosterUrl: state.detail.customPosterInstalled
+          }"
+          @clickOpenRedPacket = "clickOpenRedPacket"
+      ></custom-card-cover>
+      <!-- </template> -->
     </div>
 
 
@@ -247,7 +264,7 @@
       </div>
       <div class="luck-list-title">
         <div>{{ state.detail.receiveCount || 0 }}/{{ state.detail.totalCount || 0 }} Winners</div>
-        <div class="right">
+        <div class="right" v-if="state.detail.rewardType === RewardType.money">
           <span class="text">
             <a-tooltip :title="state.detail.receiveAmountValue">
               {{ getBit(state.detail.receiveAmountValue) }}
@@ -271,7 +288,7 @@
             <div class="luck-title" v-else>Twitter User</div>
             <div class="luck-time">{{ moment(item.receiveTimestamp).format('MM-DD HH:mm:ss') }}</div>
           </div>
-          <div class="luck-money">
+          <div class="luck-money" v-if="state.detail.rewardType === RewardType.money">
             <img :src="state.detail.currencyIconPath" alt />
             <div class="luck-money-txt">
               <a-tooltip :title="item.amountValue">
@@ -279,8 +296,8 @@
               </a-tooltip>
             </div>
           </div>
-
-          <div class="luck-king" v-if="item.maxAmount">
+          <div class="luck-custom-prize" v-else>winner</div>
+          <div class="luck-king" v-if="state.detail.rewardType === RewardType.money && item.maxAmount">     
             <img :src="require('@/assets/svg/icon-king-hat.svg')" alt />
             <span>Luckiest Draw</span>
           </div>
@@ -307,7 +324,7 @@
 
       <div class="luck-list-title">
         <div>{{ state.detail.receiveCount || 0 }}/{{ state.detail.totalCount || 0 }} Winners</div>
-        <div class="right">
+        <div class="right" v-if="state.detail.rewardType === RewardType.money">
           <span class="text">
             <a-tooltip :title="state.detail.receiveAmountValue">
               {{ getBit(state.detail.receiveAmountValue) }}
@@ -332,7 +349,7 @@
             <div class="luck-title" v-else>Twitter User</div>
             <div class="luck-time">{{ moment(item.receiveTimestamp).format('MM-DD HH:mm:ss') }}</div>
           </div>
-          <div class="luck-money">
+          <div class="luck-money" v-if="state.detail.rewardType === RewardType.money">
             <img :src="state.detail.currencyIconPath" alt />
             <div class="luck-money-txt">
               <a-tooltip :title="item.amountValue">
@@ -340,7 +357,8 @@
               </a-tooltip>
             </div>
           </div>
-          <div class="luck-king" v-if="item.maxAmount">
+          <div class="luck-custom-prize" v-else>winner</div>
+          <div class="luck-king" v-if="state.detail.rewardType === RewardType.money && item.maxAmount">
             <img :src="require('@/assets/svg/icon-king-hat.svg')" alt />
             <span>Luckiest Draw</span>
           </div>
@@ -386,11 +404,12 @@ export default {
 }
 </script>
 <script setup>
-import { onMounted, reactive, ref } from "vue";
+import { onMounted, reactive, ref, computed } from "vue";
 import { getPostDetail, getRedPacket, finishRedPacket, oneKeyLike, oneKeyReTweet, oneKeyFollow, getTaskDetail, getReceivedList, addFinishEvent } from '@/http/redPacket.js'
 import { getQueryString, guid, getBit } from '@/uilts/help.js'
 import { message } from 'ant-design-vue';
 import FontAmount from '@/view/components/font-amount.vue'
+import FontZoom from '@/view/components/font-zoom.vue'
 import GetMore from '@/view/iframe/publish/components/get-more.vue'
 import { setChromeStorage, getChromeStorage, sendChromeTabMessage } from '@/uilts/chromeExtension.js'
 import Report from "@/log-center/log"
@@ -400,6 +419,8 @@ import { discordAuthRedirectUri, faceShareRedirectUrl } from '@/http/configAPI'
 import { getFrontConfig } from "@/http/account";
 import { getInviteGuildInfo } from "@/http/discordApi";
 import GlobalTip from '@/view/components/global-tip.vue'
+import customCardCover from '@/view/components/custom-card-cover.vue';
+import { RewardType, PlayType } from '@/types';
 
 var moment = require('moment');
 
@@ -432,6 +453,7 @@ let state = reactive({
   page_index: 1,
   page_size: 20,
   srcContentId: '',
+  customCover: '',
   error_txt: `oops, new accounts cannot participate in this event,`,
   receiveAmount: 0,
   money: 0,
@@ -448,6 +470,17 @@ let state = reactive({
 
 let fullName = '';
 
+let successTopBgCpd = computed(() => {
+    const { rewardType } = state.detail
+    switch (rewardType) {
+        case RewardType.custom:
+            return require('@/assets/subject/success-top-bg-2.svg');
+        default:
+            return require('@/assets/subject/002-back-head-top-180.svg');
+            break;
+    }
+});
+
 function clickRetry() {
   init()
 }
@@ -513,6 +546,7 @@ async function clickLikeBtn() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
+    customCover: state.customCover,
   });
 }
 function clickDone() {
@@ -525,7 +559,8 @@ function clickDone() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 function handleScroll(e) {
@@ -626,6 +661,7 @@ async function clickRetweetBtn() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
+    customCover: state.customCover,
   });
 }
 
@@ -664,7 +700,8 @@ async function clickReply(params) {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -706,7 +743,8 @@ async function clickRepostFacebook(params) {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -873,7 +911,8 @@ async function clickFollowAll(item, is_all) {
   let _log_obj = {
     pageSource: Report.pageSource.task_page,
     businessType: Report.businessType.buttonClick,
-    objectType: Report.objectType.follow
+    objectType: Report.objectType.follow,
+    customCover: state.customCover,
   }
   if (is_all) {
     _log_obj.objectType = Report.objectType.follow_button
@@ -989,7 +1028,8 @@ const showSuccessPage = () => {
     senderId: state.userId,
     isOldTwitterFans: reportParams.done.follow,
     isOldDiscordFans: reportParams.done.join_discord,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 const showNotOpenPage = () => {
@@ -1000,7 +1040,8 @@ const showNotOpenPage = () => {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 const showOpenedPage = () => {
@@ -1021,7 +1062,8 @@ const showOpenedPageReport = () => {
     senderId: state.userId,
     isOldTwitterFans: state.done.follow,
     isOldDiscordFans: state.done.join_discord,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -1055,7 +1097,7 @@ const handleStatusPage = () => {
   //    如果 任务完成状态 = 未完成
   //        显示任务未完成页面
   //    如果 任务完成状态 = 已经完成
-  //        如果 领取到红包金额 = 0
+  //        如果 (货币类奖品 && 领取到红包金额 = 0) || (自定义奖品 && winner = 0)
   //            显示兔子页面
   //        否则 
   //            显示成功页面
@@ -1094,7 +1136,7 @@ const handleStatusPage = () => {
       //如果 任务完成状态 = 已经完成
     } else if (state.detail.myReceived.taskFinishStatus == 1) {
       // 领取到空红包
-      if (state.receiveAmount == 0) {
+      if ((state.detail.rewardType === RewardType.money && state.receiveAmount == 0) || (state.detail.rewardType === RewardType.custom && state.detail.myReceived.winner === 0)) {
         showRabbitPage()
         showRabbitPageReport()
       } else {
@@ -1179,6 +1221,7 @@ function init(initParams) {
       state.tweetId = state.srcContentId;
       state.userId = res.data.srcUserId;
       state.tweet_author = state.detail.postUserInfo && state.detail.postUserInfo.nickName || '';
+      state.customCover = state.detail.posterType == 2 ? 1 : 0;
       // 不要删除这个console
       console.log('postBizData', state.detail)
       checkFacebookReply();
@@ -1379,7 +1422,8 @@ function handleRedPacket() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -1447,6 +1491,7 @@ function handleFinishRedPacket() {
           postId: state.postId,
           srcContentId: state.tweetId,
           senderId: state.userId,
+          customCover: state.customCover,
         }, {
           get_giveaway_result: Report.extParams.success
         });
@@ -1526,6 +1571,7 @@ function handleFinishRedPacket() {
           postId: state.postId,
           srcContentId: state.tweetId,
           senderId: state.userId,
+          customCover: state.customCover,
         }, {
           get_giveaway_result: Report.extParams.failure,
         });
@@ -1542,6 +1588,7 @@ function handleFinishRedPacket() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
+        customCover: state.customCover,
       }, {
         get_giveaway_result: Report.extParams.failure,
       });
@@ -1847,7 +1894,8 @@ async function joinDiscord() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 
   let url = getInviteUrl();
@@ -2228,7 +2276,21 @@ body {
           color: #fff;
         }
       }
+      .custom-prize-show {
+        width: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 44px;
+        margin-top: 20px;
+
+        img {
+            width: 24px;
+            height: 24px;
+            margin-right: 9px;
+        }
     }
+}
 
     .luck-list-title {
       /*      margin-top: 47px;*/
@@ -2250,6 +2312,16 @@ body {
       }
     }
 
+    .header-custom-prize {
+        align-items: flex-start;
+        align-content: flex-start;
+        .success-title {
+            line-height: 21px;
+            margin-top: 23px;
+            font-size: 18px;
+        }
+    }
+
     .luck-list {
       background: #fff;
       overflow: auto;
@@ -2356,6 +2428,13 @@ body {
         }
       }
 
+      .luck-custom-prize {
+          font-weight: 500;
+          font-size: 14px;
+          letter-spacing: 0.3px;
+          color: #F5B945;
+      }
+
       .luck-item:last-child {
         border: 0;
       }
@@ -2363,6 +2442,16 @@ body {
   }
 
   .success {
+
+    .success-title {
+        color: #FFFFFF;
+        font-weight: 800;
+        font-size: 21px;
+        line-height: 27px;
+        margin-top: 28px;
+        text-align: center;
+        width: 100%;
+    }
     .luck-list-title {
       margin-top: 17px;
       border-bottom: 1px solid #ECECEC;

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

@@ -63,9 +63,9 @@
                       class="bold"
                       :class="{
                         'align-content':
-                          (item.type == 2 ||
+                          (item.type == 2 || 
                             (item.type == 1 && item.status == 1)) &&
-                          item.amount.length + item.currencySymbol.length > 12,
+                          item?.amount?.length + item?.currencySymbol?.length > 12,
                       }"
                     >
                       <!-- 收到的 -->
@@ -76,10 +76,39 @@
                         </template>
                         <!-- 已完成 -->
                         <template v-else-if="item.status == 1">
+                          <!-- 已中奖-货币型奖品展示 -->
+                          <template v-if="item.rewardType === RewardType.money">
+                            <span class="blance">
+                              <a-tooltip :title="item.amount">
+                                {{ getBit(item.amount) }}</a-tooltip
+                              >
+                            </span>
+                            <div class="coin-type-wrapper">
+                              <span class="coin-type">{{
+                                item.currencySymbol || ""
+                              }}</span>
+                              <img :src="item.currencyIconPath" alt="" />
+                            </div>
+                          </template>
+
+                          <!-- 已中奖-通用型奖品展示 -->
+                          <template v-else>
+                            <span class="blance cuntom-prize">{{item.customizedReward}}</span>
+                          </template>
+                        </template>
+                        <!-- 已过期 -->
+                        <template v-else-if="item.status == 2">
+                          Timeout
+                        </template>
+                      </template>
+                      <!-- 发出去的 -->
+                      <template v-else-if="item.type == 2">
+                        <!-- 已中奖-货币型奖品展示 -->
+                        <template v-if="item.rewardType === RewardType.money">
                           <span class="blance">
-                            <a-tooltip :title="item.amount">
-                              {{ getBit(item.amount) }}</a-tooltip
-                            >
+                            <a-tooltip :title="'-' + item.amount">
+                              -{{ getBit(item.amount) }}
+                            </a-tooltip>
                           </span>
                           <div class="coin-type-wrapper">
                             <span class="coin-type">{{
@@ -88,25 +117,11 @@
                             <img :src="item.currencyIconPath" alt="" />
                           </div>
                         </template>
-                        <!-- 已过期 -->
-                        <template v-else-if="item.status == 2">
-                          Timeout
+                        <!-- 已中奖-通用型奖品展示 -->
+                        <template v-else>
+                          <span class="blance cuntom-prize">{{item.customizedReward}}</span>
                         </template>
                       </template>
-                      <!-- 发出去的 -->
-                      <template v-else-if="item.type == 2">
-                        <span class="blance">
-                          <a-tooltip :title="'-' + item.amount">
-                            -{{ getBit(item.amount) }}
-                          </a-tooltip>
-                        </span>
-                        <div class="coin-type-wrapper">
-                          <span class="coin-type">{{
-                            item.currencySymbol || ""
-                          }}</span>
-                          <img :src="item.currencyIconPath" alt="" />
-                        </div>
-                      </template>
                       <template v-else-if="item.type == 3">
                         <template v-if="item.status == 1">In Progress</template>
                         <template v-else-if="item.status == 2">
@@ -116,17 +131,25 @@
                           Unfinished
                         </template>
                         <template v-else-if="item.status == 4">
-                          <span class="blance">
-                            <a-tooltip :title="item.amount">
-                              +{{ getBit(item.amount) }}
-                            </a-tooltip>
-                          </span>
-                          <div class="coin-type-wrapper">
-                            <span class="coin-type">{{
-                              item.currencySymbol || ""
-                            }}</span>
-                            <img :src="item.currencyIconPath" alt="" />
-                          </div>
+                          <!-- 已中奖-货币型奖品展示 -->
+                          <template v-if="item.rewardType === RewardType.money">
+                            <span class="blance">
+                              <a-tooltip :title="item.amount">
+                                +{{ getBit(item.amount) }}
+                              </a-tooltip>
+                            </span>
+                            <div class="coin-type-wrapper">
+                              <span class="coin-type">{{
+                                item.currencySymbol || ""
+                              }}</span>
+                              <img :src="item.currencyIconPath" alt="" />
+                            </div>
+                          </template>
+                          <!-- 已中奖-通用型奖品展示 -->
+                          <template v-else>
+                            <span class="blance cuntom-prize">{{item.customizedReward}}</span>
+                          </template>
+                          
                         </template>
                         <template v-else-if="item.status == 5">
                           Didn't win
@@ -153,9 +176,12 @@
                         <template v-if="item.postTaskLuckdrop && item.postTaskLuckdrop.luckdropType == 1">
                             ({{
                             item.status == 2 ? "Time expired" : "Termination"
-                            }}) {{ item.postTaskLuckdrop.receivedCount }}/{{
-                            item.postTaskLuckdrop.totalCount
-                            }}
+                            }}) 
+                            <template v-if="item.status == 2 || item.status == 3 && item.srcContentId">
+                              {{ item.postTaskLuckdrop.receivedCount }}/{{
+                              item.postTaskLuckdrop.totalCount
+                              }}
+                            </template>
                         </template>
                         <template v-else>
                             Termination
@@ -239,6 +265,7 @@ import { terminatedLuckdrop } from "@/http/redPacket";
 import { readAllMsgByType, getAllMessageInfo } from "@/http/messageApi"
 import { setBadgeInfo, hideBadge } from "@/logic/background/twitter";
 import { getChromeStorage } from "@/uilts/chromeExtension";
+import { RewardType } from "@/types";
 
 var moment = require("moment");
 
@@ -667,6 +694,11 @@ onBeforeUnmount(() => {
                     color: #e86f00;
                   }
 
+                  .cuntom-prize {
+                    max-width: 130px;
+                    word-break: break-word;
+                  }
+
                   .coin-type-wrapper {
                     display: flex;
                     align-items: center;

+ 73 - 0
yarn.lock

@@ -1105,6 +1105,69 @@
     estree-walker "^2.0.1"
     picomatch "^2.2.2"
 
+"@sentry/browser@7.6.0":
+  version "7.6.0"
+  resolved "https://registry.npmmirror.com/@sentry/browser/-/browser-7.6.0.tgz#54bcd52747c40b2656d62d53541037a5724f3296"
+  integrity sha512-1gdvV8RtTnNFyc790t49MAgFuHAP43NEZvdQOMw5KFnDwSGYFqfBtvJ8tUm125UPbi2fghBryO9M1gfIWboKUg==
+  dependencies:
+    "@sentry/core" "7.6.0"
+    "@sentry/types" "7.6.0"
+    "@sentry/utils" "7.6.0"
+    tslib "^1.9.3"
+
+"@sentry/core@7.6.0":
+  version "7.6.0"
+  resolved "https://registry.npmmirror.com/@sentry/core/-/core-7.6.0.tgz#5e5efd54af7b63957ac4d446fb5a69af33da3e51"
+  integrity sha512-vXIuUZbHVSAXh2xZ3NyXYXqVvVQSbGEpgtQxLutwocvD88JFK6aZqO+WQG69GY1b1fKSeE9faEDDS6WGAi46mQ==
+  dependencies:
+    "@sentry/hub" "7.6.0"
+    "@sentry/types" "7.6.0"
+    "@sentry/utils" "7.6.0"
+    tslib "^1.9.3"
+
+"@sentry/hub@7.6.0":
+  version "7.6.0"
+  resolved "https://registry.npmmirror.com/@sentry/hub/-/hub-7.6.0.tgz#69a0d11e50ee61f3f93665948c4acbe56a9ce676"
+  integrity sha512-TbieNZInpnR5STXykT1zXoKVAsm8ju1RZyzMqYR8nzURbjlMVVEzFRglNY1Ap5MRkbEuYpAc6zUvgLQe8b6Q3w==
+  dependencies:
+    "@sentry/types" "7.6.0"
+    "@sentry/utils" "7.6.0"
+    tslib "^1.9.3"
+
+"@sentry/tracing@^7.5.1":
+  version "7.6.0"
+  resolved "https://registry.npmmirror.com/@sentry/tracing/-/tracing-7.6.0.tgz#2b34992e7a003c40393a4aab4b917db2712a1586"
+  integrity sha512-ydlIk8FpuXiQm3Y0cLwXMOUYv5UtniP8ylWw3ix0sF5sTpJWSaC/g8P8yrzkYV+pm28kde5qfE3nocGhpwxZcA==
+  dependencies:
+    "@sentry/hub" "7.6.0"
+    "@sentry/types" "7.6.0"
+    "@sentry/utils" "7.6.0"
+    tslib "^1.9.3"
+
+"@sentry/types@7.6.0":
+  version "7.6.0"
+  resolved "https://registry.npmmirror.com/@sentry/types/-/types-7.6.0.tgz#7352bcc5621177ceefb18733d0a6b0cdb0307822"
+  integrity sha512-POimbDwr9tmHSKksJTXe5VQpvjkFO4/UWUptigwqf8684rkS7Ie2BT2uyp5GD2EgYFf0BwUOWi98FTYTvUGT+Q==
+
+"@sentry/utils@7.6.0":
+  version "7.6.0"
+  resolved "https://registry.npmmirror.com/@sentry/utils/-/utils-7.6.0.tgz#50b44fd9b06686a358ef2c7c0fd3b80970e1f9ee"
+  integrity sha512-p0Byi6hgawp/sBMY88RY8OmkiAR2jxbjnl8gSo+y3YEu+KeXBUxXMBsI7YeW+1lSb6z8DGhUAOBszTeI4wAr2w==
+  dependencies:
+    "@sentry/types" "7.6.0"
+    tslib "^1.9.3"
+
+"@sentry/vue@^7.5.1":
+  version "7.6.0"
+  resolved "https://registry.npmmirror.com/@sentry/vue/-/vue-7.6.0.tgz#b34acedef9eadc0eaebf27c7463f2e4856645c50"
+  integrity sha512-6ASx/iuE+Ep9wA9IvmdkXk0CZ2pbjU4RaPBHnUSwf7z5kSUEzn/kWG2FiBdz9+Y8N4Vt/+gwZJxmO9gs3QCE6Q==
+  dependencies:
+    "@sentry/browser" "7.6.0"
+    "@sentry/core" "7.6.0"
+    "@sentry/types" "7.6.0"
+    "@sentry/utils" "7.6.0"
+    tslib "^1.9.3"
+
 "@sideway/address@^4.1.3":
   version "4.1.3"
   resolved "https://registry.npmmirror.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27"
@@ -6893,6 +6956,11 @@ trim-newlines@^3.0.0:
   dependencies:
     glob "^7.1.2"
 
+tslib@^1.9.3:
+  version "1.14.1"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
 tslib@^2.0.3:
   version "2.3.1"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
@@ -7097,6 +7165,11 @@ vue-cli-plugin-chrome-extension-cli@~1.1.2:
   resolved "https://registry.npmmirror.com/vue-cli-plugin-chrome-extension-cli/-/vue-cli-plugin-chrome-extension-cli-1.1.2.tgz#3ac05bd3661de73d36807a393f0f306eff3f5687"
   integrity sha512-V6ZN4W3v9tsitsYSO+ukmiKNp0ozyaMJCAAq0kOXBHrtv1R3WQwTQ5WG3gjJDi09trRPiQYnYZRL5YGOhST7rw==
 
+vue-cropper@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-1.0.3.tgz#55323514388149c8914bc53d8e38e22fdd6c6bce"
+  integrity sha512-yDrZkE4H5vOiMA9WQHE+6rmXrZ1S9TMZasEPAZPKg/2I/nySHL4ECD1lNxt7+ofTPKT+9+2sQkCwagPqEqiqJg==
+
 vue-demi@*:
   version "0.12.4"
   resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.12.4.tgz#420dd17628f95f1bbce1102ad3c51074713a8049"

部分文件因文件數量過多而無法顯示