Selaa lähdekoodia

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

zhangwei 2 vuotta sitten
vanhempi
commit
bdf657cc41
77 muutettua tiedostoa jossa 4119 lisäystä ja 624 poistoa
  1. BIN
      src/assets/img/icon-close.png
  2. BIN
      src/assets/img/icon-default-app-logo.png
  3. BIN
      src/assets/img/icon-fixed-gray.png
  4. BIN
      src/assets/img/icon-fixed.png
  5. BIN
      src/assets/img/icon-full.png
  6. BIN
      src/assets/img/icon-loading-gray.png
  7. BIN
      src/assets/img/icon-page-fail.png
  8. BIN
      src/assets/img/icon-tool-box-01.png
  9. BIN
      src/assets/img/img-tool-box-preview-after.png
  10. BIN
      src/assets/img/img-tool-box-preview-before.png
  11. BIN
      src/assets/subject/icon-clock.png
  12. 3 0
      src/assets/subject/icon-gift-inline.svg
  13. BIN
      src/assets/subject/icon-gift.gif
  14. BIN
      src/assets/subject/icon-prize.png
  15. 10 0
      src/assets/subject/icon-uninstall-bg.svg
  16. BIN
      src/assets/subject/img-custom-common-bg.png
  17. 7 0
      src/assets/subject/img-custom-common-bg.svg
  18. BIN
      src/assets/subject/img-custom-lottary-bg.png
  19. 17 0
      src/assets/subject/img-custom-lottary-bg.svg
  20. 12 0
      src/assets/subject/success-top-bg-1.svg
  21. 12 0
      src/assets/subject/success-top-bg-2.svg
  22. 4 0
      src/assets/svg/icon-add-white.svg
  23. 3 0
      src/assets/svg/icon-gift.svg
  24. 3 0
      src/assets/svg/icon-tool-app-history.svg
  25. 5 0
      src/assets/svg/icon-tool-box-02.svg
  26. 3 0
      src/assets/svg/icon-tool-box-guide-arrow.svg
  27. 3 0
      src/assets/svg/icon-tool-box-search-arrow.svg
  28. 3 0
      src/assets/svg/icon-wallet-success.svg
  29. 6 0
      src/assets/svg/img-default-site-cover.svg
  30. 7 0
      src/assets/svg/img-select-giveaway.svg
  31. 6 0
      src/assets/svg/img-select-tool-box.svg
  32. 32 10
      src/entry/background.js
  33. 12 8
      src/entry/content.js
  34. 1 1
      src/http/configAPI.js
  35. 17 0
      src/http/toolBoxApi.js
  36. 5 0
      src/iframe/test.js
  37. 9 0
      src/iframe/tool-box-guide.js
  38. 2 1
      src/logic/background/fetch/twitter.js
  39. 11 1
      src/logic/background/help.js
  40. 78 53
      src/logic/background/twitter.js
  41. 53 1
      src/logic/content/ParseCard.js
  42. 26 0
      src/logic/content/ToolBox.js
  43. 115 73
      src/logic/content/twitter.js
  44. 19 2
      src/manifest.json
  45. 16 5
      src/rules/rules_1.json
  46. 65 49
      src/uilts/chromeExtension.js
  47. 12 0
      src/uilts/event.js
  48. 126 60
      src/uilts/help.js
  49. 44 0
      src/view/components/component-zoom.vue
  50. 42 3
      src/view/components/currency-list.vue
  51. 3 3
      src/view/components/currency-select.vue
  52. 76 27
      src/view/components/custom-card-cover.vue
  53. 132 16
      src/view/components/custom-card-horizontal-cover.vue
  54. 46 0
      src/view/components/font-zoom.vue
  55. 1 1
      src/view/components/global-tip.vue
  56. 73 0
      src/view/components/icon-svg.vue
  57. 14 4
      src/view/content/message/index.vue
  58. 168 0
      src/view/content/tool-box/full.vue
  59. 446 0
      src/view/content/tool-box/index.vue
  60. 172 0
      src/view/iframe/publish/components/customized-reward-edit.vue
  61. 49 0
      src/view/iframe/publish/components/get-more.vue
  62. 5 1
      src/view/iframe/publish/components/giveaway-poster.vue
  63. 20 7
      src/view/iframe/publish/components/preview-card.vue
  64. 141 0
      src/view/iframe/publish/components/select-publish-content.vue
  65. 302 55
      src/view/iframe/publish/give-dialog.vue
  66. 37 10
      src/view/iframe/publish/publish.vue
  67. 437 0
      src/view/iframe/publish/tool-box/child/editor.vue
  68. 175 0
      src/view/iframe/publish/tool-box/child/guide.vue
  69. 395 0
      src/view/iframe/publish/tool-box/child/preview.vue
  70. 70 0
      src/view/iframe/publish/tool-box/index.vue
  71. 147 51
      src/view/iframe/red-packet/luck-draw.vue
  72. 126 42
      src/view/iframe/red-packet/red-packet.vue
  73. 65 27
      src/view/iframe/tab-group/tab-group.vue
  74. 31 0
      src/view/iframe/test/index.vue
  75. 122 109
      src/view/popup/tabbar-page/message/index.vue
  76. 4 4
      vue.config.js
  77. 73 0
      yarn.lock

BIN
src/assets/img/icon-close.png


BIN
src/assets/img/icon-default-app-logo.png


BIN
src/assets/img/icon-fixed-gray.png


BIN
src/assets/img/icon-fixed.png


BIN
src/assets/img/icon-full.png


BIN
src/assets/img/icon-loading-gray.png


BIN
src/assets/img/icon-page-fail.png


BIN
src/assets/img/icon-tool-box-01.png


BIN
src/assets/img/img-tool-box-preview-after.png


BIN
src/assets/img/img-tool-box-preview-before.png


BIN
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>

BIN
src/assets/subject/icon-gift.gif


BIN
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>

BIN
src/assets/subject/img-custom-common-bg.png


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
src/assets/subject/img-custom-common-bg.svg


BIN
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>

+ 3 - 0
src/assets/svg/icon-tool-app-history.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.9996 20.6663C16.7862 20.6663 20.6663 16.7862 20.6663 11.9996C20.6663 7.21301 16.7862 3.33294 11.9996 3.33294C7.21301 3.33294 3.33294 7.21301 3.33294 11.9996C3.33294 16.7862 7.21301 20.6663 11.9996 20.6663ZM11.9996 22.3996C6.25621 22.3996 1.59961 17.743 1.59961 11.9996C1.59961 6.25621 6.25621 1.59961 11.9996 1.59961C17.743 1.59961 22.3996 6.25621 22.3996 11.9996C22.3996 17.743 17.743 22.3996 11.9996 22.3996ZM12.8663 12.5101V7.66368C12.8663 7.19221 12.478 6.79961 11.9996 6.79961C11.5177 6.79961 11.1329 7.18614 11.1329 7.66368V12.8689C11.1322 12.9813 11.1537 13.0929 11.1963 13.197C11.2388 13.3011 11.3015 13.3958 11.3808 13.4755L13.8439 15.9386C13.9242 16.0185 14.0196 16.0817 14.1244 16.1246C14.2293 16.1676 14.3415 16.1894 14.4548 16.1888C14.5681 16.1882 14.6802 16.1653 14.7846 16.1213C14.889 16.0774 14.9837 16.0132 15.0633 15.9325C15.2247 15.7711 15.3159 15.5524 15.317 15.3241C15.3182 15.0958 15.2292 14.8762 15.0693 14.7131L12.8663 12.5101Z" fill="#AFB3B6"/>
+</svg>

+ 5 - 0
src/assets/svg/icon-tool-box-02.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.8737 13.4889L1.59961 8.13477V17.5585C1.59979 17.7925 1.66091 18.0225 1.77696 18.2258C1.89301 18.4291 2.05999 18.5986 2.26147 18.7177L11.1851 23.9991V13.6143C11.0759 13.5873 10.9711 13.5451 10.8737 13.4889Z" fill="#1D9BF0"/>
+<path d="M22.5015 7.99219V17.5588C22.5014 17.7927 22.4405 18.0226 22.3247 18.2259C22.2089 18.4291 22.0422 18.5987 21.841 18.718L12.916 23.9994V13.5243C12.9379 13.5133 12.9595 13.5016 12.9807 13.4892L22.5015 7.99219Z" fill="#24D37F"/>
+<path d="M1.59961 5.93698C1.71483 5.79133 1.85894 5.67109 2.02287 5.58381L11.4587 0.558553C11.6537 0.454705 11.8713 0.400391 12.0923 0.400391C12.3132 0.400391 12.5308 0.454705 12.7258 0.558553L22.1617 5.58381C22.2897 5.65121 22.4043 5.74018 22.5027 5.84128L12.157 11.8142C12.0899 11.8529 12.0267 11.8981 11.9683 11.949C11.9103 11.8981 11.8475 11.853 11.7809 11.8142L1.59961 5.93698Z" fill="#FFBB56"/>
+</svg>

+ 3 - 0
src/assets/svg/icon-tool-box-guide-arrow.svg

@@ -0,0 +1,3 @@
+<svg width="11" height="17" viewBox="0 0 11 17" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0.972185 1L8.69141 8.5L0.972184 16" stroke="#1D9BF0" stroke-width="2"/>
+</svg>

+ 3 - 0
src/assets/svg/icon-tool-box-search-arrow.svg

@@ -0,0 +1,3 @@
+<svg width="11" height="17" viewBox="0 0 11 17" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0.968279 1L8.6875 8.5L0.968277 16" stroke="white" stroke-width="2"/>
+</svg>

+ 3 - 0
src/assets/svg/icon-wallet-success.svg

@@ -0,0 +1,3 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.9981 4.5H22.0044C23.6547 4.5 25 5.84533 25 7.4981V10.238H17.3117C15.4179 10.238 13.8684 11.7874 13.8684 13.6812V14.5617C13.8684 16.4555 15.4179 18.005 17.3117 18.005H25V20.7448C25 22.4 23.6547 23.7429 22.0019 23.7429H5.9981C4.34287 23.7429 3 22.3976 3 20.7448V7.4981C3 5.84287 4.34533 4.5 5.9981 4.5ZM15.0938 13.6823C15.0938 13.0945 15.3249 12.5411 15.7455 12.1205C16.1636 11.6999 16.7195 11.4688 17.3073 11.4688H24.9956V16.7763H17.3073C16.7195 16.7763 16.1661 16.5451 15.7455 16.1245C15.3249 15.704 15.0938 15.1506 15.0938 14.5628V13.6823ZM19.1962 14.1225C19.1962 13.579 18.7559 13.1387 18.2124 13.1387C17.6688 13.1387 17.2286 13.579 17.2286 14.1225C17.2286 14.6661 17.6688 15.1063 18.2124 15.1063C18.7559 15.1063 19.1962 14.6661 19.1962 14.1225Z" fill="#1D9BF0"/>
+</svg>

+ 6 - 0
src/assets/svg/img-default-site-cover.svg

@@ -0,0 +1,6 @@
+<svg width="505" height="265" viewBox="0 0 505 265" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 5C0 2.23857 2.23858 0 5 0H500C502.761 0 505 2.23858 505 5V260C505 262.761 502.761 265 500 265H5C2.23857 265 0 262.761 0 260V5Z" fill="#1D9BF0"/>
+<rect width="505" height="265" fill="white"/>
+<rect opacity="0.2" width="505" height="265" fill="#1D9BF0"/>
+<path d="M254.377 162.959L250.251 162.377C250.251 162.377 247.501 161.795 246.126 161.213C245.341 161.018 244.358 161.213 243.769 161.795L242.787 162.765C237.09 168.393 227.858 168.975 221.965 163.929C215.286 158.302 215.09 148.21 221.179 142.194L236.108 127.445C238.073 125.505 240.43 124.34 242.787 123.564C245.93 122.788 249.269 122.982 252.216 124.146C253.984 124.922 255.752 125.893 257.323 127.445C258.109 128.222 258.698 128.998 259.287 129.968C260.073 131.327 261.841 131.521 262.823 130.356L268.323 124.922C269.109 124.146 269.109 122.982 268.52 122.011C267.734 120.847 266.752 119.877 265.77 118.906C264.395 117.548 262.823 116.19 261.055 115.219C258.305 113.473 255.162 112.308 251.823 111.726C245.733 110.562 239.055 111.532 233.555 114.443C231.394 115.607 229.233 117.16 227.465 118.906L213.125 133.073C202.714 143.359 201.929 160.048 211.947 170.528C222.358 181.783 240.037 181.977 250.841 171.304L255.752 166.452C257.127 165.482 256.144 163.153 254.377 162.959ZM290.521 93.2899C279.717 83.3926 262.823 84.1689 252.609 94.4543L248.091 98.7237C246.716 100.082 247.698 102.411 249.466 102.605C252.216 102.799 254.966 103.381 257.716 104.158C258.502 104.352 259.484 104.158 260.073 103.575L261.055 102.605C266.752 96.9772 275.984 96.395 281.877 101.441C288.556 107.069 288.753 117.16 282.663 123.176L267.734 137.925C265.77 139.865 263.413 141.03 261.055 141.806C257.912 142.582 254.573 142.388 251.626 141.224C249.859 140.448 248.091 139.477 246.519 137.925C245.733 137.149 245.144 136.372 244.555 135.402C243.769 134.043 242.001 133.849 241.019 135.014L235.519 140.448C234.733 141.224 234.733 142.388 235.322 143.359C236.108 144.523 237.09 145.493 238.073 146.464C239.448 147.822 241.215 149.181 242.787 150.151C245.537 151.897 248.68 153.062 251.823 153.644C257.912 154.808 264.591 153.838 270.091 150.927C272.252 149.763 274.413 148.21 276.181 146.464L291.11 131.715C302.11 121.041 301.717 103.575 290.521 93.2899Z" fill="#1D9BF0"/>
+</svg>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
src/assets/svg/img-select-giveaway.svg


+ 6 - 0
src/assets/svg/img-select-tool-box.svg

@@ -0,0 +1,6 @@
+<svg width="150" height="150" viewBox="0 0 150 150" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="150" height="150" rx="75" fill="#F9F9F9"/>
+<path d="M71.2466 79.9644L40.333 62.1172V93.5296C40.3336 94.3098 40.5374 95.0764 40.9242 95.7539C41.311 96.4315 41.8676 96.9966 42.5392 97.3938L72.2846 114.998V80.3823C71.9207 80.2923 71.5713 80.1517 71.2466 79.9644Z" fill="#1D9BF0"/>
+<path d="M110.006 61.6406V93.5293C110.006 94.309 109.803 95.0754 109.417 95.7529C109.031 96.4304 108.475 96.9958 107.805 97.3935L78.0547 114.998V80.081C78.1277 80.0442 78.1997 80.0052 78.2704 79.9641L110.006 61.6406Z" fill="#24D37F"/>
+<path d="M40.333 54.7912C40.7171 54.3057 41.1975 53.9049 41.7439 53.614L73.1967 36.8631C73.8468 36.517 74.572 36.3359 75.3085 36.3359C76.045 36.3359 76.7703 36.517 77.4204 36.8631L108.873 53.614C109.3 53.8387 109.682 54.1352 110.01 54.4722L75.5242 74.3818C75.3005 74.511 75.0899 74.6615 74.8951 74.8312C74.7018 74.6617 74.4927 74.5113 74.2706 74.3818L40.333 54.7912Z" fill="#FFBB56"/>
+</svg>

+ 32 - 10
src/entry/background.js

@@ -25,7 +25,9 @@ import {
     checkShowPublishDialog
 } from "@/logic/background/twitter";
 import Report from "@/log-center/log"
-import { PingPong, httpNetWork } from "@/logic/background/help";
+import { PingPong, httpNetWork, httpContentToBack } from "@/logic/background/help";
+import { appVersionCode } from '@/http/configAPI.js'
+import { getChromeStorage, setChromeStorage } from '@/uilts/chromeExtension.js'
 
 import {
     facebookShareSuccess
@@ -86,8 +88,10 @@ chrome.action.onClicked.addListener(function (tab) {
 chrome.tabs.onActivated.addListener(function (activeInfo) {
     setPopupConfig(activeInfo);
 })
-function onInstalledMethod() {
+
+function thenInstalledMethod() {
     try {
+
         onInstalledCreateTab()
         onInstalledMid()
         onInstalledUserSet()
@@ -112,7 +116,6 @@ function onInstalledMethod() {
             });
         }, 5000);
     } catch (error) {
-        // 上报错误信息
         Report.reportLog({
             objectType: Report.objectType.background_function_catch,
             funcName: 'onInstalledMethod',
@@ -120,6 +123,21 @@ function onInstalledMethod() {
         });
     }
 }
+function onInstalledMethod() {
+    // 版本更新判断
+    getChromeStorage('baseInfo', (info) => {
+        if (!info || !info.appVersionCode) {
+            setChromeStorage({ baseInfo: JSON.stringify({ appVersionCode }) })
+            thenInstalledMethod()
+        } else if (appVersionCode != info.appVersionCode) {
+            setChromeStorage({ baseInfo: JSON.stringify({ appVersionCode }) }, () => {
+                chrome.runtime.reload()
+            })
+        } else {
+            thenInstalledMethod()
+        }
+    })
+}
 
 function onMessageMethod(req, sender, sendResponse) {
     try {
@@ -146,18 +164,12 @@ function onMessageMethod(req, sender, sendResponse) {
                     break
                 case 'CONTENT_SEND_CODE':
                     twitterPinLoginCode(sender, req.code);
+                    break;
                 case 'CONTENT_TWITTER_LOGIN':
                     if (req.data) {
                         twitterPinLoginToken()
                     }
                     break
-                case 'CONTENT_TWITTER_SHORT_LINK':
-                    req.arr_url.forEach(item => {
-                        if (item) {
-                            twitterShortUrl(sender, item)
-                        }
-                    });
-                    break
                 case "CONTENT_SEND_DISCORD_AUTH_CODE":
                     discordLoginCode(req, sender);
                     break
@@ -185,6 +197,16 @@ function onMessageMethod(req, sender, sendResponse) {
                 case 'CONTENT_HTTP_NET_WORK':
                     httpNetWork(req.funcName, req.data, sender)
                     break
+                case 'HTTP_CONTENT_TO_BACK':
+                    httpContentToBack(req, sender)
+                    break
+                case 'CONTENT_TWITTER_SHORT_LINK':
+                    req.arr_url.forEach(item => {
+                        if (item) {
+                            twitterShortUrl(sender, item)
+                        }
+                    });
+                    break
             }
         }
     } catch (error) {

+ 12 - 8
src/entry/content.js

@@ -41,7 +41,8 @@ import {
     showPublishDialog
 } from "@/logic/content/twitter.js";
 
-import { 
+import { httpBackToContentCallBack } from '@/uilts/chromeExtension.js'
+import {
     hideNFTGroupList,
     setNFTGroupContent,
     setJoinedGroupIframeStyle
@@ -58,9 +59,9 @@ chrome.storage.onChanged.addListener(changes => {
 window.onload = () => {
     init();
     initFacebookContent();
-    chrome.runtime.sendMessage({ 
-        actionType: "CONTENT_WINDOW_LOADED_SET_POPUP_PAGE", 
-        data: { } 
+    chrome.runtime.sendMessage({
+        actionType: "CONTENT_WINDOW_LOADED_SET_POPUP_PAGE",
+        data: {}
     }, () => { });
 };
 
@@ -134,8 +135,8 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
             twitterPublishHandler(req.publishRes);
             break;
         case 'IFRAME_TWITTER_SHOW_POPUP_PAGE':
-            let {from = '' ,showJoinGroupFinish} = req.data || {};
-            showPopupPage({path: '/NFT', from,showJoinGroupFinish }); 
+            let { from = '', showJoinGroupFinish } = req.data || {};
+            showPopupPage({ path: '/NFT', from, showJoinGroupFinish });
             break
         case "IFRAME_TWITTER_SHOW_BUY_NFT":
             showBuyNFT(req.data)
@@ -147,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':
@@ -183,10 +183,14 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
             break
         case 'BACK_NFT_PUBLISH_DONE':
             refreshTabGroup()
-            break 
+            break
         case 'IFRAME_API_GET_TWEET_USER_INFO_REQ':
             TwitterApiUserByScreenName(req.data)
             break;
+        // 回掉参数
+        case 'HTTP_BACK_TO_CONTENT':
+            httpBackToContentCallBack(req)
+            break
         case 'BG_SHOW_DENET_PUBLISH_DIALOG':
             showPublishDialog();
             break;

+ 1 - 1
src/http/configAPI.js

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

+ 17 - 0
src/http/toolBoxApi.js

@@ -0,0 +1,17 @@
+import { service } from "./request";
+
+export function convertUrl(params) {
+    return service({
+        url: `/post/editor/convertUrl`,
+        method: 'post',
+        data: params
+    })
+}
+
+export function getAllPostEditorAppData(params) {
+    return service({
+        url: `/post/editor/getAllPostEditorAppData`,
+        method: 'post',
+        data: params
+    })
+}

+ 5 - 0
src/iframe/test.js

@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from '@/view/iframe/test/index.vue'
+
+const app = createApp(App);
+app.mount('#app');

+ 9 - 0
src/iframe/tool-box-guide.js

@@ -0,0 +1,9 @@
+import { createApp } from 'vue'
+import App from '@/view/iframe/publish/tool-box/child/guide.vue'
+
+const app = createApp(App);
+app.mount('#app');
+
+window.onload= () => {
+    document.title = 'DeNet'
+}

+ 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 });
+        })
 }

+ 78 - 53
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 = ''
@@ -248,63 +248,86 @@ function sendActivetabMessage(message = {}) {
 /**
  * 安装后打开新标签页
  */
+
 export function onInstalledCreateTab() {
-    let cookiesParams = {
-        name: 'pickup_info',
-        url: pageUrl
-    }
-    getChromeCookie(cookiesParams, (res) => {
-        let { postNickName, srcContentId } = res;
-        if (res && postNickName && srcContentId) {
-            let url = `https://twitter.com/${postNickName}/status/${srcContentId}`
+    getChromeCookie(LANDING_PAGE_JUMP_INFO, (res) => {
+        // jump_info
+        if (!res || !res.jump_type) {
             chrome.tabs.create({
-                url
+                url: "https://twitter.com",
             });
-            removeChromeCookie(cookiesParams)
-        } else {
-            let nftParams = {
-                name: 'nft_info',
-                url: pageUrl
-            }
-            getChromeCookie(nftParams, (res) => {
-                let { twitterAccount, nftProjectId } = res;
-                if (res && twitterAccount && nftProjectId) {
-                    let url = `https://twitter.com/${twitterAccount}`
+            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
                     });
-                    removeChromeCookie(nftParams)
-                } else {
-                    let nftGroupParams = {
-                        name: 'nft_group_info',
-                        url: pageUrl
-                    }
-                    getChromeCookie(nftGroupParams, (res) => {
-                        let { twitterAccount } = res;
-                        if (res && twitterAccount) {
-                            // setChromeStorage({ groupTabData: JSON.stringify({
-                            //     deTabVal: 'deGroupTab'
-                            // })})
-                            chrome.storage.local.set({
-                                groupTabData: JSON.stringify({
-                                    deTabVal: 'deGroupTab'
-                                })
-                            }, (res) => {
-                                let url = `https://twitter.com/${twitterAccount}`
-                                chrome.tabs.create({
-                                    url
-                                });
-                            })
-                            removeChromeCookie(nftGroupParams)
-                        } else {
-                            chrome.tabs.create({
-                                url: "https://twitter.com",
-                            });
-                        }
+                }
+                break
+            // 抽奖红包
+            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
+                    });
+                }
+                break
+            // NFT
+            case 'ntf_info':
+                if (res && res.twitterAccount && res.nftProjectId) {
+                    created_detail = true
+                    let url = `https://twitter.com/${res.twitterAccount}`
+                    chrome.tabs.create({
+                        url
+                    });
+                }
+                break
+            // NFT 组
+            case 'nft_group_info':
+                if (res && res.twitterAccount) {
+                    created_detail = true
+                    // setChromeStorage({ groupTabData: JSON.stringify({
+                    //     deTabVal: 'deGroupTab'
+                    // })})
+                    chrome.storage.local.set({
+                        groupTabData: JSON.stringify({
+                            deTabVal: 'deGroupTab'
+                        })
+                    }, (res) => {
+                        let url = `https://twitter.com/${res.twitterAccount}`
+                        chrome.tabs.create({
+                            url
+                        });
                     })
                 }
-            })
+                break
+            // 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
         }
+
+        if (created_detail == false) {
+            chrome.tabs.create({
+                url: "https://twitter.com",
+            });
+        }
+
+        removeChromeCookie(LANDING_PAGE_JUMP_INFO)
     })
 }
 
@@ -501,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,
     });

+ 53 - 1
src/logic/content/ParseCard.js

@@ -1,4 +1,7 @@
 import { getChromeStorage, setChromeStorage } from '@/uilts/chromeExtension.js'
+import ToolBox from '@/view/content/tool-box/index.vue'
+import { createApp } from 'vue'
+
 // 解析卡片类
 // 1.dom匹配
 // 2.找出网页匹配 获取twitterid
@@ -330,6 +333,55 @@ class ParseCard {
         }
         return false
     }
+    addDomView({ tweet_Id, element, parentElement, post_Id }) {
+        const div = document.createElement('div')
+        div.id = 'denet-' + tweet_Id
+        div.style.cssText = `
+        width: 505px;
+        height: 544px;`
+        div.dataset.tweetId = tweet_Id || ''
+        div.dataset.postId = post_Id || ''
+        parentElement.appendChild(div)
+        createApp(element).mount(`#${div.id}`)
+    }
+    replaceDomView({ dom_card, tweet_Id, post_Id, time, short_url, page_type = '' }) {
+        if (!dom_card || !dom_card.parentElement) {
+            return
+        }
+        // class == denet-toolbox
+        let type
+        let dom = dom_card.querySelector('div[aria-labelledby]')
+        if (dom) {
+            type = 'card'
+            for (let i = 0; i < dom.childNodes.length; i++) {
+                if (dom.childNodes[i].dataset && dom.childNodes[i].dataset.testid && dom.childNodes[i].dataset.testid == 'card.wrapper') {
+                    dom.children[i].style.display = 'none'
+                }
+            }
+        } else {
+            type = 'txt'
+            dom = dom_card.querySelector('div[lang][dir=auto]').parentElement
+        }
+
+        dom.style = 'min-height:500px'
+        if (dom) {
+            // debugger mode
+            if (window.location.href.includes('denet_debugger')) {
+                let div = document.createElement('div')
+                div.style.color = 'red'
+                div.innerText = `
+                tweet_Id:${tweet_Id} , 
+                post_Id:${post_Id}
+                获取dom时间:${time}
+                短链接:${short_url}
+                渲染时长:${(new Date().getTime() - time) / 1000}s
+                `
+                dom.parentElement.appendChild(div)
+            }
+            // this.createIframe({ post_Id, tweet_Id, page_type })
+            this.addDomView({ tweet_Id, post_Id, element: ToolBox, parentElement: dom })
+        }
+    }
     replaceDOMRedPacket({ dom_card, tweet_Id, post_Id, time, short_url, page_type = '' }) {
         if (!dom_card || !dom_card.parentElement) {
             return
@@ -408,7 +460,7 @@ class ParseCard {
 
             if (post_Id.indexOf('luckdraw/') >= 0) {
                 post_Id = post_Id.replace('luckdraw/', '');
-                dom.appendChild(this.createIframe({ post_Id, tweet_author, page_type:'抽奖' }, true))
+                dom.appendChild(this.createIframe({ post_Id, tweet_author, page_type: '抽奖' }, true))
             } else {
                 dom.appendChild(this.createIframe({ post_Id, tweet_author }, true))
             }

+ 26 - 0
src/logic/content/ToolBox.js

@@ -0,0 +1,26 @@
+import ToolBoxFull from '@/view/content/tool-box/full.vue'
+import { createApp } from 'vue'
+
+
+export class ToolBox {
+    constructor() {
+
+    }
+    // 加载组件
+    initFull() {
+        const div = document.createElement('div')
+        div.id = 'denet-tool-box-fixed'
+        div.style.cssText = `
+            width: 505px;
+            height: 544px;
+            position: fixed;
+            right: 10px;
+            top: 10px;
+            display:none;`
+        document.body.appendChild(div)
+        createApp(ToolBoxFull).mount(`#${div.id}`)
+    }
+}
+
+
+export default new ToolBox()

+ 115 - 73
src/logic/content/twitter.js

@@ -5,9 +5,9 @@ import { reportSrcPublishEvent } from '@/http/publishApi'
 import Report from "@/log-center/log"
 import { fetchAddFinishEvent } from '@/logic/background/fetch/facebook';
 import { showNFTGroupIcon, hideNFTGroupList, checkUserJoinGroup, elemAddEventListener, addJoinedGroupList } from '@/logic/content/nft';
-import { getTwitterNftGroupInfo } from '@/http/nft'
 import { jumpTwitterDetailByAlert, showEditTweet } from '@/logic/content/help/twitter.js'
 import { clearPostContent, setGroupIconStatus } from '@/logic/content/nft.js'
+import ToolBox from '@/logic/content/ToolBox'
 import axios from 'axios';
 
 let dom = {};
@@ -73,14 +73,14 @@ function renderDom() {
 /**
  * 展示give弹窗
  */
-export function showGiveDialogHandler(userInfo) {
+export function showGiveDialogHandler(params) {
     let iframe = document.getElementById('iframe-content');
     if (iframe) {
-        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', userInfo }, '*');
+        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', data: params }, '*');
     } else {
         _addIframe();
         let iframe = document.getElementById('iframe-content');
-        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', userInfo }, '*');
+        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', data: params }, '*');
     }
 }
 
@@ -233,6 +233,10 @@ function _publishTweetEvent(params, cb) {
 }
 
 function onClosePublishDialogHandle(dom, params) {
+    // 如果是 Tool box
+    if (params.postType == 3) {
+        return;
+    }
     dom.querySelector('div[role="group"]').addEventListener('click', function () {
         setTimeout(() => {
             let parent = document.querySelector('div[data-testid="confirmationSheetDialog"]');
@@ -273,9 +277,10 @@ function checkIsShowReSend(dom, params) {
  * @param isClick
  * @private
  */
-function _addDeNetEditBtn(parent, dom, isClick = false) {
+function _addDeNetEditBtn(params = {}) {
     setTimeout(() => {
-        if (parent && parent.parentNode) {
+        let toolElem = document.querySelector('div[data-testid="toolBar"]');
+        if (toolElem) {
             Report.reportLog({
                 pageSource: Report.pageSource.mainPage,
                 businessType: Report.businessType.buttonView,
@@ -283,12 +288,17 @@ function _addDeNetEditBtn(parent, dom, isClick = false) {
             });
             let innerDeIcon = document.getElementById('de-btn1');
             if (!innerDeIcon) {
-                parent.parentNode.insertBefore(dom, parent.nextElementSibling);
+                toolElem.firstChild.appendChild(createTweetToolbarDenet())
+            }
+
+            let innerToolBoxIcon = document.getElementById('de-tool-box-btn-01');
+            if (!innerToolBoxIcon) {
+                toolElem.firstChild.appendChild(createTweetToolbarToolBox())
             }
         } else {
             setTimeout(() => {
-                parent = _getScheduleDom(isClick);
-                if (parent && parent.parentNode) {
+                let toolElem = document.querySelector('div[data-testid="toolBar"]');
+                if (toolElem) {
                     Report.reportLog({
                         pageSource: Report.pageSource.mainPage,
                         businessType: Report.businessType.buttonView,
@@ -296,7 +306,12 @@ function _addDeNetEditBtn(parent, dom, isClick = false) {
                     });
                     let innerDeIcon = document.getElementById('de-btn1');
                     if (!innerDeIcon) {
-                        parent.parentNode.insertBefore(dom, parent.nextElementSibling);
+                        toolElem.firstChild.appendChild(createTweetToolbarDenet())
+                    }
+
+                    let innerToolBoxIcon = document.getElementById('de-tool-box-btn-01');
+                    if (!innerToolBoxIcon) {
+                        toolElem.firstChild.appendChild(createTweetToolbarToolBox())
                     }
                 }
             }, 1000)
@@ -304,16 +319,6 @@ function _addDeNetEditBtn(parent, dom, isClick = false) {
     })
 }
 
-/**
- * 在dialog插入deNet按钮
- * @private
- */
-// function _addDeNetBtnToDialog() {
-//     setTimeout(() => {
-//         let dialogScheduleBtn = _getScheduleDom(true);
-//         _addDeNetEditBtn(dialogScheduleBtn, dom.deBtn2);
-//     }, 800)
-// }
 
 /**
  * 获取左侧twitter按钮
@@ -335,9 +340,9 @@ function _addDeNetBtn() {
         let navWidth = document.querySelector('nav[role="navigation"]').offsetWidth;
         addSliderNavDeBtn(navWidth < 245);
         let innerDeIcon = document.getElementById('de-btn1');
-        if (!innerDeIcon) {
-            let dialogScheduleBtn = _getScheduleDom(false);
-            dom && dom.deBtn1 && _addDeNetEditBtn(dialogScheduleBtn, dom.deBtn1);
+        let innerToolBoxIcon = document.getElementById('de-tool-box-btn-01');
+        if (!innerDeIcon || !innerToolBoxIcon) {
+            _addDeNetEditBtn();
         }
     }, 800)
 }
@@ -430,7 +435,7 @@ export function hideNoticeBindTweet() {
  * 点击deNet按钮处理
  * @private
  */
-function _deNetBtnClick() {
+function _deNetBtnClick(params = {}) {
     getUserInfo((res) => {
         if (res) {
             if (window.location.pathname != '/home') {
@@ -439,7 +444,7 @@ function _deNetBtnClick() {
                 }
                 dom.homeBtn.click();
             }
-            showGiveDialogHandler(res);
+            showGiveDialogHandler(params);
         } else {
             let loadIcon = document.getElementById('de-btn-loading');
             if (loadIcon) {
@@ -526,17 +531,13 @@ function _createBtnDom() {
     deBtn.style.cssText = 'width:90%;height: 52px;text-align:center;line-height:52px;margin-bottom: 4px;margin-top: 4px;background: linear-gradient(274.8deg, #FF9900 -3.69%, #BD00FF 69.71%, #00F0FF 122.65%);color:#fff;font-size:17px;font-weight:700;border-radius:100px;cursor: pointer;display: flex;align-items: center;justify-content: center;';
 
     // 编辑框内按钮
-    const deBtn1 = document.createElement('img');
     let src = require("@/assets/img/icon-gift-pack.png");
     const smallDeBtnStyle = 'width:20px;height: 20px;cursor: pointer;padding: 0px 8px';
-    deBtn1.id = 'de-btn1';
-    deBtn1.style.cssText = smallDeBtnStyle;
-    deBtn1.src = src
 
     const deBtn2 = document.createElement('div');
     deBtn2.id = 'de-btn2';
     deBtn2.style.cssText = smallDeBtnStyle;
-    deBtn2.src = "@/assets/img/icon-gift-pack.png"
+    deBtn2.src = src
 
     // 小屏按钮
     const deBtn3 = document.createElement('img');
@@ -545,29 +546,14 @@ function _createBtnDom() {
     deBtn3.style.cssText = 'width:52px;height: 52px;margin-top:20px;cursor: pointer;';
 
     deBtn.addEventListener('click', () => {
-        // chrome.runtime.sendMessage({
-        //     actionType: 'CONTENT_SET_BADGE',
-        //     data: {
-        //         text: '2'
-        //     }
-        // }, res => {
-        //     console.log(res);
-        // })
-
         Report.reportLog({
             pageSource: Report.pageSource.mainPage,
             businessType: Report.businessType.buttonClick,
             objectType: Report.objectType.buttonMain
         });
-        _deNetBtnClick();
-    })
-    deBtn1.addEventListener('click', () => {
-        Report.reportLog({
-            pageSource: Report.pageSource.mainPage,
-            businessType: Report.businessType.buttonClick,
-            objectType: Report.objectType.buttonSecond
+        _deNetBtnClick({
+            type: 'SHOW_SELECT'
         });
-        _deNetBtnClick();
     })
     deBtn2.addEventListener('click', () => {
         _deNetBtnClick();
@@ -578,15 +564,54 @@ function _createBtnDom() {
             businessType: Report.businessType.buttonClick,
             objectType: Report.objectType.buttonMain
         });
-        _deNetBtnClick();
+        _deNetBtnClick({
+            type: 'SHOW_SELECT'
+        });
     })
     dom.deBtn = deBtn;
-    dom.deBtn1 = deBtn1;
     dom.deBtn2 = deBtn2;
     dom.deBtn3 = deBtn3;
     dom.loadingImg = loadingImg;
 }
 
+function createTweetToolbarDenet() {
+    let src = require("@/assets/img/icon-gift-pack.png");
+    const smallDeBtnStyle = 'width:20px;height: 20px;cursor: pointer;padding: 0px 8px';
+
+    const deBtn1 = document.createElement('img');
+    deBtn1.id = 'de-btn1';
+    deBtn1.style.cssText = smallDeBtnStyle;
+    deBtn1.src = src;
+
+    deBtn1.addEventListener('click', () => {
+        Report.reportLog({
+            pageSource: Report.pageSource.mainPage,
+            businessType: Report.businessType.buttonClick,
+            objectType: Report.objectType.buttonSecond
+        });
+        _deNetBtnClick();
+    })
+
+    return deBtn1;
+}
+
+function createTweetToolbarToolBox() {
+    let src = require("@/assets/img/icon-tool-box-01.png");
+    const smallDeBtnStyle = 'width:20px;height: 20px;cursor: pointer;padding: 0px 8px';
+
+    const deToolBoxBtn = document.createElement('img');
+    deToolBoxBtn.id = 'de-tool-box-btn-01';
+    deToolBoxBtn.style.cssText = smallDeBtnStyle;
+    deToolBoxBtn.src = src;
+
+    deToolBoxBtn.addEventListener('click', () => {
+        _deNetBtnClick({
+            type: 'TOOL_BOX'
+        })
+    })
+
+    return deToolBoxBtn;
+}
 async function addSliderNavDeBtn(isSmall = false) {
     try {
         if (!isSmall) {
@@ -625,7 +650,7 @@ async function addSliderNavDeBtn(isSmall = false) {
 
 function onWindowResize() {
     window.onresize = throttle(function () {
-        setTabGroupIframeStyle();
+        setTabGroupIframeStyle({}, true);
         try {
             if (tweetPublishStore.showPublishDialog) {
                 let dialog = document.querySelector('div[role="dialog"]');
@@ -665,8 +690,7 @@ function checkHasDeBtn() {
         let toolBar = document.querySelector('div[data-testid="toolBar"]');
         let innerDeIcon = document.getElementById('de-btn1');
         if (toolBar && !innerDeIcon) {
-            let dialogScheduleBtn = _getScheduleDom(false);
-            dom && dom.deBtn1 && _addDeNetEditBtn(dialogScheduleBtn, dom.deBtn1);
+            _addDeNetEditBtn();
         }
     } catch (e) {
         console.log(e)
@@ -724,6 +748,10 @@ export const changeQueueNum = (num = 0) => {
 
 let main_observer = null
 function onChangePageMain(targetNode) {
+    if (main_observer) {
+        return
+    }
+    changeQueueNum(1)
     try {
         const config = { attributes: false, childList: true, subtree: true };
         const callback = (mutationsList, observer) => {
@@ -764,7 +792,11 @@ function setIframeRedPacket(type = 'twitter') {
                         item.post_Id = item.post_Id.split('luckdraw/')[1] || ''
                         item.page_type = '抽奖'
                         parseCard.replaceDOMRedPacket(item)
-                    } else {
+                    } else if (item && item.post_Id && item.post_Id.indexOf('toolbox/') >= 0) {
+                        item.page_type = 'toolbox'
+                        item.post_Id = item.post_Id.split('toolbox/')[1] || ''
+                        parseCard.replaceDomView(item)
+                    } else if (item && item.post_Id && !item.post_Id.includes('/')) {
                         item.page_type = '红包'
                         parseCard.replaceDOMRedPacket(item)
                     }
@@ -838,10 +870,7 @@ function initParseCard() {
         if (inTwitter && inTwitterNode) {
             clearInterval(timer)
             setInterval(() => {
-                if (!main_observer) {
-                    onChangePageMain(inTwitterNode)
-                    changeQueueNum(1)
-                }
+                onChangePageMain(inTwitterNode)
                 twitterPinLogin()
                 showNFTGroupIcon()
                 if (queue_num <= 0) {
@@ -859,10 +888,7 @@ function initParseCard() {
         } else if (inFacebook && inFacebookNode) {
             clearInterval(timer)
             setInterval(() => {
-                if (!main_observer) {
-                    onChangePageMain(inFacebookNode)
-                    changeQueueNum(1)
-                }
+                onChangePageMain(inTwitterNode)
                 if (queue_num <= 0) {
                     return
                 }
@@ -872,6 +898,7 @@ function initParseCard() {
         }
     }, 1000);
 }
+
 let inited = false
 // 初始化
 export function init() {
@@ -897,16 +924,18 @@ export function init() {
     }
 
     if (window.location.host.includes('twitter.com')) {
+        renderDom();
         showNFTCard()
         showNFTGroupIcon()
         addEventAction();
         checkUserJoinGroup();
-        renderDom();
         checkTwitterTaskState();
         initBuyNFT();
         addJoinedGroupList();
         getSysTheme();
         addGroupTab();
+        // 预加载全屏 toobbox
+        ToolBox.initFull()
     }
     // 渲染dom
     initParseCard()
@@ -1762,15 +1791,6 @@ export const tiggerInjectPopupPage = () => {
     }
 }
 
-const onBodyClick = () => {
-    if (window.location.href.indexOf('api.twitter.com') < 0) {
-        document.querySelector('body').addEventListener('click', function () {
-            console.log('click')
-            // hidePopupPage();
-        })
-    }
-}
-
 export const setPopupConfByPopupPage = () => {
     let iframe = document.getElementById('de-popup-page');
     if (iframe) {
@@ -1980,7 +2000,12 @@ export const groupTipsSelectGroupTab = (params = {}) => {
 const addGroupTabEventListener = () => {
     let groupTab = getGroupTabNode();
     groupTab.addEventListener('click', function () {
-        let groupColor = systemInfo.theme == 'light' ? 'rgb(15, 20, 25)' : '#fff';
+        let bgColor = document.querySelector('body').style.backgroundColor;
+
+        let groupColor = systemInfo.theme == 'dark' && bgColor == 'rgb(0, 0, 0)' ? '#fff' : 'rgb(15, 20, 25)';
+
+        // let groupColor = systemInfo.theme == 'light' ? 'rgb(15, 20, 25)' : '#fff';
+
         setGroupTabSelfStyle({
             groupColor: groupColor,
             groupFontWeight: '700',
@@ -2265,7 +2290,10 @@ const fixProfileTabAutoSwitch = () => {
  * 
  * 设置Tab Group Iframe 样式
  */
-export const setTabGroupIframeStyle = (params) => {
+export const setTabGroupIframeStyle = (params, isReSize = false) => {
+    if (!isReSize) {
+        getSysTheme();
+    }
     let iframeContent = getGroupTabContentNode();
     if (iframeContent) {
         let htmlHeight = document.querySelector('html').offsetHeight;
@@ -2339,6 +2367,7 @@ const getSysTheme = () => {
     } else {
         systemInfo.theme = 'dark'
     }
+    sysThemeChange();
     themeMedia.addListener(e => {
         addGroupTab()
         if (e.matches) {
@@ -2346,9 +2375,22 @@ const getSysTheme = () => {
         } else {
             systemInfo.theme = 'dark'
         }
+        sysThemeChange();
     });
 }
 
+const sysThemeChange = () => {
+    setTimeout(() => {
+        let bgColor = document.querySelector('body').style.backgroundColor;
+
+        chrome.runtime.sendMessage({
+            actionType: "CONTENT_SYS_THEME_CHANGE", data: {
+                theme: systemInfo.theme,
+                twitterTheme: bgColor == 'rgb(0, 0, 0)' ? 'dark' : 'light'
+            }
+        }, () => { })
+    }, 800)
+}
 
 /** 
  * 

+ 19 - 2
src/manifest.json

@@ -2,7 +2,7 @@
     "manifest_version": 3,
     "name": "DeNet",
     "description": "Growing more twitter followers with Denet",
-    "version": "1.1.3",
+    "version": "1.1.5",
     "background": {
         "service_worker": "/js/background.js"
     },
@@ -44,7 +44,20 @@
             ]
         }
     ],
+    "optional_permissions": [
+        "declarativeNetRequest"
+    ],
+    "declarative_net_request": {
+        "rule_resources": [
+            {
+                "id": "ruleset_1",
+                "enabled": true,
+                "path": "/rules/rules_1.json"
+            }
+        ]
+    },
     "host_permissions": [
+        "*://*/*",
         "<all_urls>",
         "*://*.twitter.com/*",
         "*://twitter.com/*",
@@ -58,6 +71,8 @@
         "tabs",
         "action",
         "cookies",
+        "webNavigation",
+        "declarativeNetRequest",
         "activeTab",
         "scripting",
         "storage",
@@ -80,7 +95,9 @@
                 "/iframe/popup-page.html",
                 "/iframe/popup-page.html",
                 "/iframe/tab-group.html",
-                "/iframe/joined-group-list.html"
+                "/iframe/joined-group-list.html",
+                "/iframe/tool-box-guide.html",
+                "/iframe/test.html"
             ],
             "matches": [
                 "<all_urls>"

+ 16 - 5
src/rules/rules_1.json

@@ -1,17 +1,28 @@
 [
     {
         "id": 1,
-        "priority":1,
-        "condition": {
-            "urlFilter": "twitter.com"
-        },
+        "condition": {},
         "action": {
             "type": "modifyHeaders",
             "responseHeaders": [
                 {
                     "header": "X-Frame-Options",
+                    "operation": "remove"
+                },
+                {
+                    "header": "Frame-Options",
+                    "operation": "remove"
+                },
+                {
+                    "header": "content-security-policy",
+                    "operation": "remove"
+                }
+            ],
+            "requestHeaders": [
+                {
+                    "header": "sec-fetch-dest",
                     "operation": "set",
-                    "value":"ALLOW-FROM  https://twitter.com"
+                    "value": "document"
                 }
             ]
         }

+ 65 - 49
src/uilts/chromeExtension.js

@@ -1,4 +1,5 @@
 import { pageUrl } from "@/http/configAPI.js"
+import { guid } from "@/uilts/help";
 
 export const LANDING_PAGE = {
     name: 'received_log',
@@ -10,8 +11,21 @@ export const LANDING_PAGE_MID = {
     url: pageUrl
 }
 
-export function setChromeStorage(params) {
-    chrome.storage.local.set(params)
+export const LANDING_PAGE_JUMP_INFO = {
+    name: 'jump_info',
+    url: pageUrl
+}
+
+export function setChromeStorage(params, callback) {
+    try {
+        if (callback) {
+            chrome.storage.local.set(params, callback)
+        } else {
+            chrome.storage.local.set(params)
+        }
+    } catch (error) {
+        console.error(error)
+    }
 }
 
 export function getChromeStorage(key = '', callback) {
@@ -39,6 +53,13 @@ export function getChromeStorage(key = '', callback) {
     });
 }
 
+// export function chromeSendMessage() {
+//     try {
+//         // chrome.runtime.sendMessage
+//     } catch {
+//     }
+// }
+
 export function setChromeCookie({
     name,
     url
@@ -51,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
+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)) || []
-            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
-            }
-            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) {
@@ -107,7 +97,7 @@ export function removeChromeCookie(params, cb) {
     })
 }
 
-export function sendChromeTabMessage(params,callback) {
+export function sendChromeTabMessage(params, callback) {
     chrome.tabs.getCurrent((tab) => {
         chrome.tabs.sendMessage(tab.id, params, callback);
     })
@@ -130,4 +120,30 @@ export function checkIsLogin(callback) {
             }
         })
     })
-}
+}
+
+let callback_arr = []
+export function httpContentToBack(data, callback) {
+    let callback_id = guid()
+    chrome.runtime.sendMessage({ actionType: "HTTP_CONTENT_TO_BACK", data, callback_id }, (res) => {
+        if (res && callback) {
+            callback_arr.push({
+                callback_id,
+                callback,
+            })
+        }
+    })
+}
+
+// 再找到它执行
+export function httpBackToContentCallBack(req) {
+    for (let i in callback_arr) {
+        if (callback_arr[i].callback_id == req.callback_id) {
+            // 执行
+            callback_arr[i].callback(req.data)
+            // 删除
+            callback_arr.splice(i, 1)
+            break
+        }
+    }
+}

+ 12 - 0
src/uilts/event.js

@@ -0,0 +1,12 @@
+export const sendEventInfo = (data) => {
+    // 创建事件
+    let onEvent = new CustomEvent("onEvent", {
+        detail: data
+    });
+    window.dispatchEvent(onEvent);
+}
+
+// window.addEventListener("onEvent", e => {
+//     alert(`pingan事件触发,是 ${e.info.name} 触发。`);
+//     // dom之间互相传至
+// })

+ 126 - 60
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,43 +157,103 @@ 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) {
+  var str = URL;
+  //判断URL地址的正则表达式为:http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
+  // /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/
+  var Expression = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
+  var objExp = new RegExp(Expression);
+  if (objExp.test(str) == true) {
+    return true;
+  } else {
+    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>

+ 1 - 1
src/view/components/global-tip.vue

@@ -33,7 +33,7 @@ onMounted(() => {
 <style lang="scss" scoped>
 .global-tip {
     position: absolute;
-    z-index: 999;
+    z-index: 3000;
     display: flex;
     height: 40px;
     background: #000000;

+ 73 - 0
src/view/components/icon-svg.vue

@@ -0,0 +1,73 @@
+<template>
+    <!-- 固定 -->
+    <template v-if="icon == '固定'">
+        <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" :class="class"
+            @click.stop="clickStop">
+            <mask id="path-1-inside-1_22849_40056" fill="white">
+                <rect x="1" y="2" width="18" height="16" rx="1" />
+            </mask>
+            <rect x="1" y="2" width="18" height="16" rx="1" fill="#D9D9D9" fill-opacity="0.01" stroke="white"
+                stroke-width="3" mask="url(#path-1-inside-1_22849_40056)" />
+            <rect x="9" y="5" width="7" height="5" fill="white" />
+        </svg>
+    </template>
+
+    <!-- 放大 -->
+    <template v-if="icon == '放大'">
+        <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" :class="class"
+            @click.stop="clickStop">
+            <path
+                d="M11.3054 3.39557H15.4646L11.8944 6.8807L11.8944 6.88068L11.8923 6.88281C11.7377 7.03738 11.6759 7.2515 11.6759 7.45338C11.6759 7.65758 11.7381 7.87294 11.8611 8.05744L11.8888 8.09904L11.9304 8.12678C12.1149 8.24978 12.3303 8.31196 12.5345 8.31196C12.7363 8.31196 12.9505 8.25011 13.105 8.09554L16.5923 4.6083V8.77794C16.5923 9.00244 16.702 9.20602 16.8445 9.34851C16.987 9.49099 17.1905 9.60072 17.4151 9.60072C17.6396 9.60072 17.8431 9.49099 17.9856 9.34851C18.1281 9.20602 18.2378 9.00244 18.2378 8.77794V3.5396C18.2871 3.02389 18.1855 2.58065 17.9514 2.25868C17.7118 1.92926 17.3491 1.75 16.9377 1.75H11.3054C11.0809 1.75 10.8773 1.85973 10.7348 2.00222C10.5923 2.1447 10.4826 2.34828 10.4826 2.57278C10.4826 2.79729 10.5923 3.00087 10.7348 3.14335C10.8773 3.28583 11.0809 3.39557 11.3054 3.39557ZM9.77794 15.6877H5.6083L9.09554 12.2005C9.2485 12.0475 9.31196 11.8347 9.31196 11.6419C9.31196 11.4566 9.25337 11.2528 9.11311 11.1015C8.97067 10.9011 8.75564 10.7952 8.52497 10.7952C8.29228 10.7952 8.06427 10.9018 7.88281 11.0832L4.39557 14.5705V10.019C4.39557 9.79447 4.28583 9.59089 4.14335 9.44841C4.00087 9.30592 3.79729 9.19619 3.57278 9.19619C3.34828 9.19619 3.1447 9.30592 3.00222 9.44841C2.85973 9.59089 2.75 9.79447 2.75 10.019V15.4604C2.75 15.8987 2.91219 16.3805 3.19379 16.756C3.47667 17.1332 3.90175 17.4288 4.43196 17.4288H9.77794C10.0024 17.4288 10.206 17.319 10.3485 17.1765C10.491 17.0341 10.6007 16.8305 10.6007 16.606C10.6007 16.1115 10.2265 15.6877 9.77794 15.6877Z"
+                fill="white" stroke="white" stroke-width="0.5" />
+        </svg>
+    </template>
+
+    <!-- 缩小 -->
+    <svg v-if="icon == '缩小'" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"
+        :class="class" @click.stop="clickStop">
+        <g clip-path="url(#clip0_22829_232305)">
+            <path
+                d="M2.57362 11.9381H6.74327L3.25602 15.4254C3.10307 15.5783 3.0396 15.7912 3.0396 15.984C3.0396 16.1693 3.09819 16.373 3.23846 16.5244C3.38089 16.7248 3.59592 16.8307 3.82659 16.8307C4.05929 16.8307 4.28729 16.7241 4.46875 16.5426L7.956 13.0554V17.6069C7.956 17.8314 8.06573 18.035 8.20821 18.1775C8.3507 18.32 8.55428 18.4297 8.77878 18.4297C9.00328 18.4297 9.20686 18.32 9.34934 18.1775C9.49183 18.035 9.60156 17.8314 9.60156 17.6069V12.1655C9.60156 11.7271 9.43937 11.2454 9.15777 10.8699C8.87489 10.4927 8.44981 10.1971 7.9196 10.1971H2.57362C2.34912 10.1971 2.14554 10.3068 2.00306 10.4493C1.86057 10.5918 1.75084 10.7954 1.75084 11.0199C1.75084 11.5144 2.12504 11.9381 2.57362 11.9381Z"
+                fill="white" stroke="white" stroke-width="0.5" />
+            <path
+                d="M16.6276 2.25602L13.1403 5.74327V1.57362C13.1403 1.34912 13.0306 1.14554 12.8881 1.00306C12.7456 0.860574 12.542 0.750839 12.3175 0.750839C12.093 0.750839 11.8894 0.860574 11.747 1.00306C11.6045 1.14554 11.4947 1.34912 11.4947 1.57362V6.81196C11.4455 7.32767 11.5471 7.77091 11.7812 8.09288C12.0208 8.4223 12.3835 8.60156 12.7948 8.60156H18.4272C18.6517 8.60156 18.8553 8.49183 18.9978 8.34934C19.1403 8.20686 19.25 8.00328 19.25 7.77878C19.25 7.55427 19.1403 7.3507 18.9978 7.20821C18.8553 7.06573 18.6517 6.956 18.4272 6.956H14.268L17.8381 3.47087L17.8382 3.47088L17.8403 3.46875C17.9949 3.31418 18.0567 3.10006 18.0567 2.89818C18.0567 2.69398 17.9945 2.47862 17.8715 2.29412L17.8438 2.25252L17.8022 2.22479C17.6177 2.10179 17.4023 2.0396 17.1981 2.0396C16.9962 2.0396 16.7821 2.10145 16.6276 2.25602Z"
+                fill="white" stroke="white" stroke-width="0.5" />
+        </g>
+        <defs>
+            <clipPath id="clip0_22829_232305">
+                <rect width="18" height="18" fill="white" transform="translate(1 1)" />
+            </clipPath>
+        </defs>
+    </svg>
+
+    <!-- 关闭 -->
+    <svg v-if="icon == '关闭'" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"
+        :class="class" @click.stop="clickStop">
+        <line y1="-1" x2="16.7592" y2="-1" transform="matrix(-0.698077 0.716023 -0.698077 -0.716023 15.1914 4)"
+            stroke="white" stroke-width="2" />
+        <line y1="-1" x2="16.7592" y2="-1" transform="matrix(0.698077 0.716022 0.698077 -0.716022 4.79297 4)"
+            stroke="white" stroke-width="2" />
+    </svg>
+
+
+</template>
+<script setup>
+import { defineProps, defineEmits } from 'vue'
+const emits = defineEmits("back");
+let props = defineProps({
+    icon: {
+        type: String,
+        default: ''
+    },
+    class: {
+        type: String,
+        default: ''
+    }
+})
+const clickStop = () => {
+    emits("clickStop");
+}
+
+</script>
+<style>
+</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) {

+ 168 - 0
src/view/content/tool-box/full.vue

@@ -0,0 +1,168 @@
+<template>
+    <div class="denet-toolbox">
+        <div class="head">
+            <span></span>
+            <div>
+                <!-- 缩小 -->
+                <icon-svg v-if="state.status == '全屏'" :icon="'缩小'" @clickStop="clickFull" :class="'full'"></icon-svg>
+                <icon-svg v-else :icon="'放大'" @clickStop="clickFull" :class="'full'"></icon-svg>
+
+                <!-- 关闭 -->
+                <icon-svg :icon="'关闭'" @clickStop="clickClose" :class="'fixed'"></icon-svg>
+            </div>
+        </div>
+        <div class="content">
+            <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";
+import IconSvg from '@/view/components/icon-svg.vue'
+
+let state = reactive({
+    status: '固定', // 全屏
+    iframe_url: '',
+    tweetId: ''
+})
+let dom_fixed = null
+
+window.addEventListener("onEvent", e => {
+    let info = e.detail
+    switch (info.event_type) {
+        // 固定
+        case 'ToolBox_To_Fixed':
+            // 替换
+            if (state.iframe_url || state.tweetId) {
+                sendClose()
+            }
+            state.iframe_url = info.data.iframe_url
+            state.tweetId = info.data.tweetId
+            break
+        case 'ToolBox_To_Full':
+            if (state.iframe_url || state.tweetId) {
+                sendClose()
+            }
+            state.status = '全屏'
+            state.iframe_url = info.data.iframe_url
+            state.tweetId = info.data.tweetId
+            break
+    }
+});
+
+
+const clickFull = () => {
+    if (state.status == '固定') {
+        state.status = '全屏'
+        changeFull()
+    } else {
+        state.status = '固定'
+        changeFixed()
+    }
+}
+onMounted(() => {
+    dom_fixed = $('#denet-tool-box-fixed')
+})
+
+const changeFull = () => {
+    dom_fixed.style.cssText = `
+    width:100%;
+    height: 100%;
+    position: fixed;
+    right: 0px;
+    top: 0px;`
+}
+
+const changeFixed = () => {
+    dom_fixed.style.cssText = `
+    width: 505px;
+    height: 544px;
+    position: fixed;
+    right: 10px;
+    top: 10px;`
+}
+
+const clickClose = () => {
+    dom_fixed.style.display = 'none'
+    sendClose()
+
+}
+const sendClose = () => {
+    let url = state.iframe_url
+    let tweetId = state.tweetId
+    sendEventInfo({
+        event_type: 'ToolBox_Fixed_Close',
+        data: {
+            url,
+            tweetId,
+        }
+    })
+    state.iframe_url = ''
+    state.tweetId = ''
+}
+
+
+</script>
+
+<style lang="scss">
+.denet-toolbox {
+    width: 100%;
+    height: 100%;
+    filter: drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.2));
+    border-radius: 12px;
+    overflow: hidden;
+
+    .head {
+        width: 100%;
+        height: 40px;
+        background: #373737;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        span {
+            color: #FFFFFF;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 14px;
+            margin-left: 16px;
+        }
+
+        div {
+            display: flex;
+            justify-content: center;
+        }
+
+        svg {
+            width: 20px;
+            height: 20px;
+            cursor: pointer;
+
+        }
+
+        .full {
+            margin-right: 16px;
+        }
+
+        .fixed {
+            margin-right: 20px;
+        }
+    }
+
+    .content {
+        width: 100%;
+        height: calc(100% - 40px);
+        background: #686868;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        iframe {
+            width: 100%;
+            height: 100%;
+        }
+    }
+}
+</style>

+ 446 - 0
src/view/content/tool-box/index.vue

@@ -0,0 +1,446 @@
+<template>
+    <div class="denet-toolbox" @click.stop="clickHead" ref="dom_toolbox" :class="{ 'pre-view': pre_view }">
+        <div class="head" @click.stop="clickHead">
+            <span></span>
+            <div v-show="state.show_btn && state.status == ''">
+                <icon-svg :icon="'固定'" @clickStop="clickFixed" :class="'fixed'"></icon-svg>
+                <icon-svg :icon="'放大'" @clickStop="clickFull" :class="'full'"></icon-svg>
+            </div>
+        </div>
+        <div class="content" v-if="pre_view">
+            <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"
+                allow="camera *;microphone *"></iframe>
+            <!-- 网页错误 -->
+            <div class="state" v-show="state.status == '网页错误'">
+                <img :src="require('@/assets/img/icon-page-fail.png')" alt />
+                <div>Oops, this link is invalid</div>
+            </div>
+
+            <!-- 加载 -->
+            <div class="state" v-show="state.status == '加载'">
+                <img :src="require('@/assets/img/icon-loading-gray.png')" alt class="icon-loading" />
+            </div>
+
+            <!-- 关闭 -->
+            <div class="state" v-show="state.status == '关闭'">
+
+            </div>
+            <!-- 固定右上角 -->
+            <div class="state" v-show="state.status == '固定右上角'">
+                <img :src="require('@/assets/img/icon-fixed-gray.png')" alt />
+                <div>Pinned to the top right</div>
+            </div>
+        </div>
+        <!-- alert -->
+        <div class="alert" v-show="state.show_alert">
+            <div class="back" @click.stop="clickCancel"></div>
+            <div class="confirm">
+                <div class="check">
+                    <input :id="state.checkbox_id" type='checkbox' v-model="state.checkbox" />
+                    <label :for="state.checkbox_id">Don't remind</label>
+                </div>
+
+                <div class="title">Web Page Progress May Reset</div>
+                <div class="handle">
+                    <div class="cancel" @click.stop="clickCancel">Cancel</div>
+                    <div class="continue" @click.stop="clickContinue">Continue</div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+<script setup>
+import { getChromeStorage, setChromeStorage, httpContentToBack, defineProps } from "@/uilts/chromeExtension";
+import { guid } from "@/uilts/help";
+import { sendEventInfo } from "@/uilts/event";
+import { onMounted, reactive, ref } from "vue";
+import { $ } from "@/uilts/help";
+import IconSvg from '@/view/components/icon-svg.vue'
+let dom_toolbox = ref(null)
+let dom_iframe = ref(null)
+let state = reactive({
+    status: '', //
+    show_alert: false,
+    show_btn: true,
+    list: [],
+    checkbox: false,
+    checkbox_id: `denet-${guid()}`,
+    postId: '',
+    tweetId: '',
+    detail: {},
+    handle_type: ''
+})
+
+let dom = {}
+
+let props = defineProps({
+    pre_view: {
+        type: Boolean,
+        default: false,
+    },
+    iframe_url: {
+        type: String,
+        default: ''
+    }
+})
+
+window.addEventListener("onEvent", e => {
+    let info = e.detail
+    switch (info.event_type) {
+        // 事件传输
+        case 'ToolBox_Fixed_Close':
+            if (info.data.tweetId == state.tweetId) {
+                state.show_btn = true
+                state.status = ''
+                state.iframe_url = info.data.url
+            }
+            break
+    }
+});
+const clickHead = () => {
+    // 
+}
+
+const clickContinue = () => {
+    if (state.checkbox) {
+        setChromeStorage({ fullCheck: JSON.stringify({ fullCheck: 1 }) })
+    }
+    if (state.handle_type == '全屏') {
+        handleFull()
+    } else {
+        handleFixed()
+    }
+    state.show_alert = false
+}
+onMounted(() => {
+    if (props.pre_view) {
+        return
+    }
+    if (dom_toolbox.value) {
+        state.dom_root = dom_toolbox.value.closest('div[data-tweet-id]')
+        if (state.dom_root) {
+            state.postId = state.dom_root.dataset.postId || ''
+            state.tweetId = state.dom_root.dataset.tweetId || ''
+        }
+    }
+    try {
+        dom.fixed = $('#denet-tool-box-fixed')
+        if (dom.fixed && dom.fixed.style.display == 'block') {
+            if (dom.fixed.dataset.tweetId == state.tweetId) {
+                state.status = '固定右上角'
+                return
+            }
+        }
+    } catch (error) {
+        console.log(error)
+    }
+    getDetail()
+})
+
+// detail函数
+const getDetail = () => {
+    state.status = '加载'
+    httpContentToBack({
+        url: `/post/getDetail`,
+        params: {
+            postId: state.postId
+        }
+    }, (res) => {
+        if (res && res.code == 0) {
+            state.detail = JSON.parse(res.data.postBizData)
+            console.log('postBizData', state.detail)
+            // 加载iframe
+            let iframe = dom_iframe.value
+            // state.detail.convertUrl = 'https://www.bilibili.com'
+            // iframe.onerror = () => {
+            //     state.status = '网页错误'
+            // }
+            // iframe.onload = function () {
+            //     if (state.status != '固定右上角') {
+            //         state.status = ''
+            //     }
+            // }
+            setTimeout(() => {
+                state.iframe_url = state.detail.convertUrl
+                state.status = ''
+            }, 1000)
+        } else {
+            state.status = '网页错误'
+        }
+    })
+}
+
+const clickCancel = () => {
+    state.show_alert = false
+}
+
+const clickFixed = () => {
+    state.handle_type = '固定'
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 固定
+            handleFixed()
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+// 固定
+const handleFull = () => {
+    if (state.status || !state.iframe_url) {
+        return
+    }
+    // 切换状态
+    state.status = '关闭'
+    state.status = '固定右上角'
+    // 操作全屏dom
+    dom.fixed.style.cssText = `
+        width:100%;
+        height: 100%;
+        position: fixed;
+        right: 0px;
+        top: 0px;
+        display:block;`
+    dom.fixed.dataset.tweetId = state.tweetId
+    state.show_btn = false
+    sendEventInfo({
+        event_type: 'ToolBox_To_Full',
+        data: {
+            iframe_url: state.iframe_url,
+            tweetId: state.tweetId,
+        }
+    })
+    // 清除当前iframe src
+    state.iframe_url = ''
+}
+
+// 全屏
+const handleFixed = () => {
+    // 切换状态
+    state.show_btn = false
+    state.status = '固定右上角'
+    // 操作全屏dom
+    dom.fixed.style.cssText = `
+        display:block;
+        width: 505px;
+        height: 544px;
+        position: fixed;
+        right: 10px;
+        top: 10px;`
+    dom.fixed.dataset.tweetId = state.tweetId
+    sendEventInfo({
+        event_type: 'ToolBox_To_Fixed',
+        data: {
+            iframe_url: state.iframe_url,
+            tweetId: state.tweetId
+        }
+    })
+    // 清除当前iframe src
+    state.iframe_url = ''
+}
+
+const clickFull = () => {
+    state.handle_type = '全屏'
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 全屏
+            handleFull()
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+</script>
+
+<style lang="scss">
+.pre-view {
+    pointer-events: none;
+    cursor: default;
+}
+
+.denet-toolbox {
+    width: 100%;
+    height: 100%;
+    filter: drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.2));
+    border-radius: 12px;
+    overflow: hidden;
+    position: relative;
+
+    .alert {
+        text-align: center;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+
+        .back {
+            background: #000000;
+            opacity: 0.8;
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            cursor: auto;
+        }
+
+        .confirm {
+            position: absolute;
+            width: 355px;
+            height: 180px;
+            background: #FFFFFF;
+            border-radius: 20px;
+            top: 173px;
+            left: 50%;
+            margin-left: -180px;
+
+            .title {
+                font-weight: 600;
+                font-size: 18px;
+                color: #000000;
+                margin-bottom: 34px;
+            }
+
+            .check {
+                color: #899099;
+                font-weight: 400;
+                font-size: 14px;
+                margin: 12px 15px 32px 0;
+                text-align: right;
+                align-content: center;
+                justify-content: flex-end;
+                display: flex;
+                line-height: 17px;
+
+                input {
+                    margin-right: 8px;
+                }
+
+                label {
+                    line-height: 19px;
+                }
+
+            }
+
+            .handle {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+
+                div {
+                    font-weight: 600;
+                    font-size: 16px;
+                    width: 156px;
+                    height: 47px;
+                    line-height: 47px;
+                    cursor: pointer;
+                    border-radius: 1000px;
+                    user-select: none;
+                }
+
+                .cancel {
+                    color: #000000;
+                    background: rgba(56, 154, 255, 0.01);
+                    border: 1px solid #E6E6E6;
+                }
+
+                .continue {
+                    background: #1D9BF0;
+                    font-weight: 600;
+                    margin-left: 11px;
+                    color: #FFFFFF;
+                }
+            }
+        }
+    }
+
+    .head {
+        width: 100%;
+        height: 40px;
+        background: #373737;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        div {
+            display: flex;
+            justify-content: center;
+        }
+
+        span {
+            color: #FFFFFF;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 14px;
+            margin-left: 16px;
+        }
+
+        svg {
+            width: 20px;
+            height: 20px;
+            cursor: pointer;
+
+        }
+
+        .full {
+            margin-right: 16px;
+        }
+
+        .fixed {
+            margin-right: 20px;
+        }
+    }
+
+    .content {
+        width: 100%;
+        height: calc(100% - 40px);
+        background: #686868;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        iframe {
+            background: #fff;
+            width: 100%;
+            height: 100%;
+        }
+
+        .state {
+            text-align: center;
+
+            img {
+                margin-bottom: 14px;
+            }
+
+            .icon-loading {
+                animation: loading 1s infinite linear;
+            }
+
+            div {
+                margin-bottom: 40px;
+                color: #8E8E8E;
+                text-align: center;
+                font-weight: 500;
+                font-size: 22px;
+            }
+        }
+
+    }
+}
+
+@keyframes loading {
+    from {
+        transform: rotate(0deg);
+    }
+
+    to {
+        transform: rotate(360deg);
+    }
+}
+</style>

+ 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>

+ 49 - 0
src/view/iframe/publish/components/get-more.vue

@@ -1,4 +1,14 @@
 <template>
+    <div class="get-more-btns"  v-if="props.style_type == 3">
+        <div class="get-more-btn" @click="jumpMore">
+            <img width="22" :src="require('@/assets/svg/icon-wallet-success.svg')" />
+            <font>View wallet</font>
+        </div>
+        <div class="get-more-btn" @click="jumpMore">
+            <img width="22" :src="require('@/assets/svg/icon-big-give.svg')" />
+            <font>Get More Giveaway</font>
+        </div>
+    </div>
     <div class="getMore" @click="jumpMore" v-if="props.style_type == 1">
         <img width="20" :src="require('@/assets/svg/icon-big-give.svg')" />
         <font>Get More Giveaway</font>
@@ -78,4 +88,43 @@ const jumpMore = () => {
         margin-right: 8px;
     }
 }
+.get-more-btns  {
+    display: flex;
+    cursor: pointer;
+    user-select: none;
+    align-items: center;
+    justify-content: center;
+    padding: 13px 0;
+    box-shadow: 0px -2px 10px rgba(0, 0, 0, 0.06);
+    position: relative;
+    .get-more-btn {
+        flex: 1;
+        display: flex;
+        cursor: pointer;
+        user-select: none;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        height: 44px;
+        font {
+            font-weight: 600;
+            font-size: 14px;
+            line-height: 17px;
+            text-align: center;
+            letter-spacing: 0.3px;
+            color: #000000;
+        }
+    }
+    &::after {
+        content: "";
+        width: 1px;
+        height: 88px;
+        position: absolute;
+        left: 50%;
+        top: 13px;
+        background-color: rgba(0,0,0,.2);
+        transform: scale(0.5);
+        transform-origin: 0 0;
+    }
+}
 </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 {

+ 141 - 0
src/view/iframe/publish/components/select-publish-content.vue

@@ -0,0 +1,141 @@
+<template>
+    <div class="overlay" v-if="visible">
+        <div class="content">
+            <!-- 头部 -->
+            <div class="head">
+                <div class="left">
+                    <!-- 关闭按钮 -->
+                    <div class="close-btn" @click="close">
+                        <img  class="icon-close"
+                            :src="require('@/assets/svg/icon-close.svg')"/>
+                    </div>
+                    <!-- 标题 -->
+                    <div class="title">
+                        DeNet
+                    </div>
+                </div>
+            </div>
+            <div class="body">
+                <div v-for="(item, index) in list"
+                    :key="index"
+                    class="item"
+                    @click="clickItem(item, index)">
+                    <img :src="item.icon" alt="">
+                    {{item.txt}}
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, watch, reactive, defineProps, onMounted } from "vue";
+let list = reactive([
+    {
+        icon: require('@/assets/svg/img-select-giveaway.svg'),
+        type: 'REDPACKET',
+        txt: 'Giveaway'
+    },
+    {
+        icon: require('@/assets/svg/img-select-tool-box.svg'),
+        type: 'TOOL_BOX',
+        txt: 'Tool Box'
+    }
+])
+
+const props = defineProps({
+    visible: {
+        type: Boolean,
+        default: false
+    },
+});
+
+
+const emits = defineEmits(["close", "select"]);
+
+const close = () => {
+    emits('close', {});
+}
+
+const clickItem = (params, index) => {
+    emits('select', params);
+}
+
+</script>
+
+<style lang="scss" scoped>
+.overlay {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1000;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    overflow: auto;
+
+    .content {
+        width: 580px;
+        height: 400px;
+        background: #ffffff;
+        border-radius: 20px;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        box-sizing: border-box;
+        z-index: 2000;
+
+        .head {
+            border-bottom: 1px solid #ececec;
+            height: 48px;
+            box-sizing: border-box;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 0 14px;
+
+            .left {
+                display: flex;
+                align-items: center;
+                .title {
+                    font-size: 16px;
+                    font-weight: 500;
+                }
+
+                .close-btn {
+                    display: flex;
+                    align-items: center;
+                    width: max-content;
+                    margin-right: 12px;
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .body {
+            display: flex;
+            justify-content: space-between;
+            height: calc(100% - 48px);
+            width: 350px;
+            margin: 0 auto;
+
+            .item {
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                font-weight: 500;
+                font-size: 18px;
+                margin-top: 70px;
+                cursor: pointer;
+
+                img {
+                    margin-bottom: 20px;
+                }
+            }
+        }
+    }
+}
+</style>

+ 302 - 55
src/view/iframe/publish/give-dialog.vue

@@ -15,16 +15,26 @@
                 <div class="left">
                     <!-- 关闭按钮 -->
                     <div class="close-btn" @click="close">
-                        <img  class="icon-close"
+                        <template v-if="publishType == 'TOOL_BOX'">
+                            <img  class="icon-close"
+                            :src="require('@/assets/svg/icon-close.svg')"
+                            v-if="toolBoxPageData.activePage == 'EDITOR'"/>
+                            <img class="icon-close"
+                                :src="require('@/assets/svg/icon-back.svg')"
+                                v-else/>
+                        </template>
+                        <template v-else>
+                            <img  class="icon-close"
                             :src="require('@/assets/svg/icon-close.svg')"
                             v-if="showComType == 'default'"/>
-                        <img class="icon-close"
-                            :src="require('@/assets/svg/icon-back.svg')"
-                            v-else/>
+                            <img class="icon-close"
+                                :src="require('@/assets/svg/icon-back.svg')"
+                                v-else/>
+                        </template>
                     </div>
                     <!-- 标题 -->
                     <div class="title">
-                        {{ currentComData[showComType]["title"] }}
+                        {{publishType == 'REDPACKET' ? currentComData[showComType]["title"] : 'Tool Box' }}
                     </div>
                 </div>
                 <div class="right">
@@ -72,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 
@@ -82,17 +104,31 @@
                             @selectCurrency="selectCurrencyAfter"></currency-select>
                     </div>
 
-                    <div class="left" v-if="showComType != 'preview'">
-                        <div class="gift-pack-wrapper">
-                            <img class="icon" :src="require('@/assets/svg/icon-gift-pack.svg')"/>
-                        </div>
-                        <div class="bottom">
+                    <div class="left" v-if="showComType != 'preview' && toolBoxPageData.activePage != 'PREVIEW'">
+                        <div class="tab-item" 
+                            :class="{'active-tab': item.type == publishType}"
+                            v-for="(item, index) in leftTabList" 
+                            :key="index"
+                            @click="clickLeftTab(item, index)"
+                            >
+                            <img class="icon" :src="item.icon"/>
                         </div>
                     </div>
 
                     <div class="right"  
-                        :class="{'fill-right': showComType == 'preview'}">
+                        :class="{'fill-right': showComType == 'preview' || toolBoxPageData.activePage == 'PREVIEW'}">
                         <global-tip :type="'2'"></global-tip>
+
+                        
+                        <template v-if="publishType == 'TOOL_BOX'">
+                            <tool-box 
+                                :pageData="{
+                                    'linkInputDescImage': toolBoxPageData.postEditorLinkInputDescImage
+                                }"
+                                :activePage="toolBoxPageData.activePage" @onPageChange="onToolBoxPageChange"
+                                @toolBoxPublishFinish="toolBoxPublishFinish"></tool-box>
+                        </template>
+                        <template v-else>
                         <div class="form-wrapper"  v-if="showComType == 'default'">
                             <div class="form-cell-item base-form-wrapper">
                                 <div class="title">
@@ -111,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>
 
                                                 <!-- 刷新按钮、充值 -->
@@ -159,6 +194,7 @@
                                             </div>
                                             <input v-model="baseFormData.totalCount"
                                                 placeholder="0"
+                                                :disabled="baseFormData.rewardType === RewardType.custom"
                                                 @input="onCountInput"
                                                 @blur="onCountBlur"/>
                                         </div>
@@ -194,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>
@@ -291,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
@@ -311,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') " />
@@ -371,6 +409,7 @@
                         <!-- paypal支付按钮 -->
                         <div class="payment" v-show="showComType == 'preview'">
                             <paypal-button
+                                v-if="isMoneyRewardCpd"
                                 :finalAmountData="finalAmountData"
                                 :payConfig="{
                                     paypalClientId,
@@ -395,7 +434,9 @@
                                     </div>
                                 </template>
                             </paypal-button>
+                            <div v-else class="btn-wrap" @click="payStatusHandle(1)"><div class="custom-submit">Confirm</div></div>
                         </div>
+                        </template>
                     </div>
                 </div>
             </div>
@@ -446,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";
@@ -454,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";
@@ -461,6 +503,7 @@ import "element-plus/es/components/loading/style/css";
 import 'vue-cropper/dist/index.css'
 import { VueCropper }  from "vue-cropper";
 import {create, all} from "mathjs";
+
 import messageBox from "@/view/components/message-box.vue";
 import currencyList from "@/view/components/currency-list.vue";
 import currencySelect from "@/view/components/currency-select.vue";
@@ -469,12 +512,15 @@ import followInput from "@/view/iframe/publish/components/follow-input";
 import paypalButton from "@/view/iframe/publish/components/paypal-button";
 import topUp from "@/view/iframe/publish/components/top-up.vue";
 import topUp2 from "@/view/iframe/publish/components/top-up2.vue";
+import toolBox from '@/view/iframe/publish/tool-box/index.vue'
 import giveawayPoster from '@/view/iframe/publish/components/giveaway-poster.vue';
 import GlobalTip from '@/view/components/global-tip.vue'
 import customCardCover from '@/view/components/custom-card-cover.vue'
 
 const currentInstance = getCurrentInstance();
 
+import CustomizedRewardEditPopup from '@/view/iframe/publish/components/customized-reward-edit';
+
 const config = {
     number: 'BigNumber',
 }
@@ -496,6 +542,8 @@ let publishRes = reactive({});
 
 //弹窗是否展示
 let visible = ref(false);
+let publishType = ref('REDPACKET');
+
 
 //弹窗高度
 let dialogStyle = reactive({
@@ -605,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: "",
@@ -619,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';
@@ -695,22 +747,81 @@ let publishModeList = reactive([
 
 let discordInviteInfo = ref({});
 let showDiscordInvitePop = ref(false);
+let showCustomizedRewardEditPopup = ref(false);
 
 let lotteryMaxHourDuration = 168;
 
+let leftTabList = reactive([
+    {
+        icon: require('@/assets/svg/icon-gift-pack.svg'),
+        type: 'REDPACKET'
+    },
+    {
+        icon: require('@/assets/svg/icon-tool-box-02.svg'),
+        type: 'TOOL_BOX'
+    }
+])
+
+let toolBoxPageData = reactive({
+    activePage: 'EDITOR', //EDITOR PREVIEW
+    postEditorLinkInputDescImage: ''
+})
+
+
 const props = defineProps({
-    dialogVisible: {
-        type: Boolean,
-        default: false,
+    dialogData: {
+        type: Object,
+        default: () => {
+            return {
+                visible: false,
+                type: 'REDPACKET'
+            }
+        },
     },
 });
 
+//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.dialogVisible,
+    () => props.dialogData,
     (newVal) => {
-        console.log("watch", newVal);
-        visible.value = newVal;
-        if (newVal) {
+        visible.value = newVal.visible;
+
+        if (newVal.visible) {
+            publishType.value = newVal.type;
             Report.reportLog({
                 pageSource: Report.pageSource.publisherDialog,
                 businessType: Report.businessType.pageView,
@@ -728,18 +839,33 @@ watch(
         } else {
             clearInterval(timer.value);
         }
+    },
+    {
+        deep: true
     }
 );
 
-const emits = defineEmits(["close", "confirm", "payPalFinsh"]);
+const emits = defineEmits(["close", "confirm", "postPublishFinish"]);
 
 const close = () => {
-    if (showComType.value != "default") {
-        showComType.value = "default";
-        isBack.value = true;
+    if(publishType.value == 'TOOL_BOX') {
+        closeToolBoxPage()
     } else {
-        initParams();
+        if (showComType.value != "default") {
+            showComType.value = "default";
+            isBack.value = true;
+        } else {
+            initParams();
+            emits("close", false);
+        }
+    }
+};
+
+const closeToolBoxPage = () => {
+    if(toolBoxPageData.activePage == "EDITOR") {
         emits("close", false);
+    } else if(toolBoxPageData.activePage == "PREVIEW") {
+        toolBoxPageData.activePage = "EDITOR";
     }
 };
 
@@ -776,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();
+            }
+        })
+    }
 }
 
 /**
@@ -875,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;
@@ -1016,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;
     // 组装提交参数
 
@@ -1043,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
@@ -1054,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;
 
     // 自定义封面
@@ -1135,6 +1282,8 @@ const initParams = () => {
     tempCurrentCurrencyInfo.value = {};
     currentCurrencyInfo.value = {};
 
+    publishType.value = 'REDPACKET';
+
     // clear discord value
     setDiscordIptTxt({text: ''});
 
@@ -1186,7 +1335,7 @@ const payStatusHandle = (payStatus) => {
     //支付状态 0:未支付,1:支付成功,2:支付失败,3:已关闭,4:已退款
     switch (payStatus) {
         case 1:
-            emits("payPalFinsh", { publishRes });
+            emits("postPublishFinish", { publishRes });
             showComType.value = "default";
             initParams();
             break;
@@ -1231,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'
     }
@@ -1242,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'});
 
@@ -1343,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) {
@@ -1666,6 +1827,7 @@ const setFrontConfig = () => {
         if (res.code == 0) {
             paypalHtml.value = res.data.paypalHtml;
             lotteryMaxHourDuration = res.data.lotteryMaxHourDuration;
+            toolBoxPageData.postEditorLinkInputDescImage = res.data.postEditorLinkInputDescImage;
         }
     });
 };
@@ -1722,6 +1884,19 @@ const selectPublishMode = (params, index) => {
     setInputErrorMsg();
 }
 
+const clickLeftTab = (params, index) => {
+    publishType.value = params.type;
+}
+
+const onToolBoxPageChange = (params) => {
+    toolBoxPageData.activePage = params.page;
+};
+
+const toolBoxPublishFinish = (params) => {
+    toolBoxPageData.activePage = 'EDITOR';
+    emits("postPublishFinish", { publishRes: params.publishRes });
+}
+
 // 截图相关
 const showDialog = () => {
     cropperDialog.value = true;
@@ -1796,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();
@@ -1997,16 +2203,19 @@ onMounted(() => {
                 width: 50px;
                 display: flex;
                 flex-direction: column;
-                justify-content: space-between;
                 align-items: center;
 
-                .gift-pack-wrapper {
+                .tab-item {
                     width: 100%;
                     height: 55px;
-                    background: #f5f5f5;
                     display: flex;
                     align-items: center;
                     justify-content: center;
+                    cursor: pointer;
+                }
+
+                .active-tab {
+                    background-color: #D2EAFC;
                 }
 
                 .bottom {
@@ -2103,6 +2312,12 @@ onMounted(() => {
                                     input::placeholder {
                                         color: #c5c5c5;
                                     }
+
+                                    input:disabled {
+                                        color: #c5c5c5;
+                                        background-color: #fff;
+                                    }
+
                                     input {
                                         padding-right: 16px;
                                     }       
@@ -2335,6 +2550,7 @@ onMounted(() => {
                         height: 60px;
                         margin-left: 14px;
                         margin-right: 14px;
+                        position: relative;
                     }
                     .show-font {
                         position: relative;
@@ -2540,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 {

+ 37 - 10
src/view/iframe/publish/publish.vue

@@ -2,25 +2,34 @@
 <template>
     <div class="main_app">
         <give-dialog
-            :dialogVisible="dialogVisible"
+            :dialogData="dialogData"
             @close="close"
-            @payPalFinsh="payPalFinsh"
-        ></give-dialog>
+            @postPublishFinish="finish" ></give-dialog>
+        <select-publish-content 
+            :visible="selectVisible" 
+            @close="hideSelectDialog"
+            @select="selectPublishType"></select-publish-content>
     </div>
 </template>
 
 <script setup>
-import { ref } from "vue";
+import { ref, reactive } from "vue";
 import giveDialog from "@/view/iframe/publish/give-dialog.vue";
+import selectPublishContent from "@/view/iframe/publish/components/select-publish-content.vue";
 
-let dialogVisible = ref(false);
+let dialogData = reactive({
+    visible: false,
+    type: 'REDPACKET'
+});
+
+let selectVisible = ref(false);
 
 const close = () => {
-    dialogVisible.value = false;
+    dialogData.visible = false;
     hideIframe();
 };
 
-const payPalFinsh = (params) => {
+const finish = (params) => {
     close();
     window.parent.postMessage({ actionType: "IFRAME_SHOW_TWITTER_PUBLISH_DIALOG", publishRes: params.publishRes }, "*");
 };
@@ -29,11 +38,29 @@ const hideIframe = () => {
     window.parent.postMessage({ actionType: "IFRAME_HIDE_IFREME" }, "*");
 };
 
+const hideSelectDialog = () => {
+    selectVisible.value = false;
+    close();
+}
+
+const selectPublishType = (params) => {
+    selectVisible.value = false;
+    dialogData.visible = true;
+    dialogData.type = params.type;
+}
+
 window.addEventListener("message", function (event) {
-    console.log("addEventListener", event);
-    if (event.data && event.data.actionType == "CONTENT_SHOW_GIVE_DIALOG") {
+    let eventData = event.data;
+    if (eventData && eventData.actionType == "CONTENT_SHOW_GIVE_DIALOG") {
         window.parent.postMessage({ actionType: "IFRAME_SHOW_IFREME" }, "*");
-        dialogVisible.value = true;
+
+        let {type = 'REDPACKET'} = eventData.data || {};
+        if(type != 'SHOW_SELECT') {
+            dialogData.visible = true;
+            dialogData.type = type;
+        } else {
+            selectVisible.value = true;
+        }
     }
 });
 </script>

+ 437 - 0
src/view/iframe/publish/tool-box/child/editor.vue

@@ -0,0 +1,437 @@
+<template>
+  <div class="editor-wrapper">
+    <div class="top">
+      <div class="title">
+        Enter Link to Embed in Tweet
+      </div>
+      <div class="search-wrapper">
+        <input class="input" type="text" v-model="siteUrl" placeholder="Enter link">
+        <div class="btn" @click="searchHandler()">
+          <img :src="require('@/assets/svg/icon-tool-box-search-arrow.svg')" />
+        </div>
+      </div>
+      <div class="desc">
+        <img :src="linkInputDescImage" alt="">
+      </div>
+    </div>
+    <div class="bottom">
+      <div class="content">
+        <div class="cate-item history-wrapper" v-if="historyList.length">
+          <div class="cate">
+            <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="img-wrapper">
+                <img class="img" :class="{ 'small-img': !app.appId }" :src="app.iconPath" :onerror="imgOnError" />
+              </div>
+              <div class="name">
+                {{ app.name }}
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="cate-item" v-for="(item) in appList" :key="item.cateId">
+          <div class="cate">
+            <img :src="item.iconPath">
+          </div>
+          <div class="app-list">
+            <div class="app" v-for="(app, idx) in item.apps" :key="idx" @click="clickAppHandler(app)">
+              <img class="app-img" :src="app.iconPath" alt="">
+              <div class="name">
+                {{ app.name }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits, onMounted } from "vue";
+import axios from 'axios';
+import { message } from "ant-design-vue";
+import { convertUrl, getAllPostEditorAppData } from "@/http/toolBoxApi";
+import { getChromeStorage } from "@/uilts/chromeExtension"
+import { checkURL, debounce } from "@/uilts/help"
+
+const props = defineProps({
+  linkInputDescImage: {
+    type: String,
+    default: '',
+  },
+});
+
+
+let siteUrl = ref('');
+
+let selectAppGuideData = {};
+let selectGuideApp = {};
+let openWindowList = [];
+
+let historyList = ref([])
+
+let appList = ref();
+
+const emits = defineEmits(["changeShowCom"]);
+
+const searchHandler = async (_params) => {
+  let siteTitle = '', favicon = '';
+  let timer = null;
+
+
+  if (!siteUrl.value) {
+    return;
+  }
+
+  siteUrl.value = siteUrl.value.trim();
+
+  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) {
+      // 提示
+      return;
+    }
+    let urlObj = new URL(siteUrl.value);
+    if (siteRes.data) {
+      siteTitle = getTitleByHtmlStr(siteRes.data);
+      if (!siteTitle) {
+        siteTitle = urlObj.hostname;
+      }
+      console.log(siteTitle)
+    }
+    favicon = urlObj.origin + '/favicon.ico';
+  }
+
+  let currentApp = {
+    appId: '',
+    cateId: '',
+    createType: '',
+    defaultUrl: siteUrl.value,
+    guideData: '',
+    iconPath: favicon,
+    interactType: '',
+    linkImagePath: "",
+    name: siteTitle,
+  }
+  if(_params) {
+    currentApp = _params;
+  }
+
+  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 || {};
+    params.convertUrl = convertUrl;
+  }
+  emits('changeShowCom', params)
+}
+
+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 < tag_start.length || index2 < 0) {
+    return '';
+  }
+
+  return str.substring(index1, index2) || '';
+};
+
+const clickHistoryAppHandler =  debounce(function(params) {
+  if (params.appId) {
+    clickAppHandler(params);
+  } else {
+    siteUrl.value = params.defaultUrl;
+    searchHandler(params);
+  }
+}, 800);
+
+const clickAppHandler =  debounce(function(params) {
+  let { createType, defaultUrl, appId, linkImagePath } = params;
+  switch (createType) {
+    case 1:
+      emits('changeShowCom', { convertUrl: defaultUrl, originUrl: defaultUrl, appId, linkImagePath, currentApp: params })
+      break;
+    case 2:
+      openWindow(params);
+      break;
+  }
+}, 800);
+
+const openWindow = (params) => {
+  chrome.windows.getCurrent({},
+    function (window) {
+      if (window && window.state == "fullscreen") {
+        chrome.windows.update(window.id, {
+          state: 'normal'
+        }, function () {
+          setTimeout(() => {
+            createGuideWindow(params, true);
+          }, 1000)
+        })
+      } else {
+        createGuideWindow(params);
+      }
+    })
+};
+
+const createGuideWindow = (params, isUpdate = false) => {
+  openWindowList = [];
+  selectAppGuideData = {};
+  selectGuideApp = {};
+
+  let windowWith = window.screen.width - 500;
+  let guideUrl = chrome.runtime.getURL('/iframe/tool-box-guide.html');
+
+  chrome.windows.create({
+    width: windowWith,
+    type: 'normal',
+    url: params.defaultUrl,
+    state: 'normal'
+  }, function (window) {
+    openWindowList.push(window);
+  })
+  chrome.windows.create({
+    width: 500,
+    type: 'popup',
+    url: guideUrl,
+    left: windowWith,
+    state: 'normal'
+  }, function (window) {
+    openWindowList.push(window);
+  })
+  selectGuideApp = params;
+  if (params.guideData) {
+    selectAppGuideData = JSON.parse(params.guideData);
+  }
+}
+
+const getAppList = () => {
+  getAllPostEditorAppData({ params: {} }).then(res => {
+    if (res.code == 0) {
+      appList.value = res.data || [];
+    }
+  })
+}
+
+
+const onRuntimeMsg = () => {
+  chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
+    sendResponse('ok')
+    switch (req.actionType) {
+      case 'CONTENT_GET_GUIDE_DATA':
+        chrome.runtime.sendMessage({
+          actionType: "CONTENT_EDIT_SEND_GUIDE_DATA",
+          data: {
+            guideData: selectAppGuideData,
+            windowData: openWindowList,
+            selectGuideApp
+          }
+        }, (response) => { });
+        break;
+      case 'CONTENT_GUIDE_APPLY_APP':
+        siteUrl.value = req.data.siteUrl;
+        searchHandler(req.data.selectGuideApp);
+        break;
+    }
+  })
+}
+
+const getHistoryList = async () => {
+  let { list = [] } = await getChromeStorage('toolBoxAppHistoryData') || {};
+  historyList.value = list;
+};
+
+const imgOnError = (e) => {
+  let img = e.srcElement;
+  img.src = require('@/assets/img/icon-default-app-logo.png');
+  img.onerror = null;
+}
+
+onMounted(() => {
+  getHistoryList();
+  getAppList();
+  onRuntimeMsg();
+})
+</script>
+
+<style lang="scss" scoped>
+.editor-wrapper {
+  width: 100%;
+  height: 100%;
+
+  .top {
+    padding: 25px 40px 30px 40px;
+    border-bottom: 0.5px solid #D1D9DD;
+    box-sizing: border-box;
+
+    .title {
+      font-weight: 500;
+      font-size: 20px;
+    }
+
+    .search-wrapper {
+      margin: 20px 0;
+      border-radius: 8px;
+      width: 100%;
+      height: 49px;
+      display: flex;
+      align-items: center;
+
+      .input {
+        background: #F1F3F4;
+        border: none;
+        outline: none;
+        font-weight: 400;
+        font-size: 16px;
+        color: #636363;
+        height: 100%;
+        width: calc(100% - 49px);
+        padding-left: 20px;
+        border-bottom-left-radius: 8px;
+        border-top-left-radius: 8px;
+      }
+
+      .btn {
+        width: 49px;
+        height: 49px;
+        background: #1D9BF0;
+        border-bottom-right-radius: 8px;
+        border-top-right-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+      }
+    }
+
+    .desc {
+      img {
+        width: 315px;
+        height: 14px;
+        object-fit: cover;
+      }
+    }
+  }
+
+  .bottom {
+    width: 100%;
+    height: calc(100% - 185px);
+    overflow-y: auto;
+
+    .content {
+      width: 100%;
+      padding: 36px 30px 20px 50px;
+      box-sizing: border-box;
+
+      .history-wrapper {
+
+        .app-list {
+          .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;
+            }
+
+            .small-img {
+              width: 25px;
+              height: 25px;
+            }
+          }
+        }
+      }
+
+      .cate-item {
+        min-height: 110px;
+        display: flex;
+        margin-bottom: 12px;
+
+        .cate {
+          width: 20px;
+          height: 110px;
+          margin-right: 26px;
+          display: flex;
+          align-items: center;
+          margin-top: -10px;
+
+          img {
+            width: 20px;
+            height: 20px;
+          }
+        }
+
+        .app-list {
+          display: flex;
+          align-content: center;
+          flex-wrap: wrap;
+
+          .app {
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            width: 110px;
+            height: 110px;
+            cursor: pointer;
+
+            .app-img {
+              width: 60px;
+              height: 60px;
+              border-radius: 10px;
+              margin-bottom: 10px;
+              border: 1px solid #E5E5E5;
+              box-sizing: border-box;
+            }
+
+            .name {
+              font-weight: 500;
+              font-size: 12px;
+              color: #636363;
+              width: 100%;
+              height: 15px;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+              overflow: hidden;
+              text-align: center;
+              box-sizing: border-box;
+              padding: 0 5px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 175 - 0
src/view/iframe/publish/tool-box/child/guide.vue

@@ -0,0 +1,175 @@
+<template>
+    <div class="guide-wrapper">
+        <div class="top">
+            <div class="title">Guided Tutorial</div>
+            <div class="content">
+                <div class="img-list" 
+                    v-if="pageData.guideData.guideType == 'image'">
+                    <div class="img-item" 
+                        v-for="(item, index) in pageData.guideData.guideData"
+                        :key="index">
+                        <img class="img" :src="item" >
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="bottom">
+            <div class="title">
+                Enter Link
+            </div>
+            <div class="search-input-wrapper">
+                <input class="input" v-model="siteUrl" placeholder="Enter link" />
+                <div class="btn" @click="confirm">
+                    <img :src="require('@/assets/svg/icon-tool-box-guide-arrow.svg')" alt="">
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { checkURL } from "@/uilts/help"
+
+let siteUrl = ref('');
+
+let pageData = reactive({
+    guideType: '',
+    guideData: [],
+    selectGuideApp: {}
+});
+
+const confirm = () => {
+    siteUrl.value = siteUrl.value.trim();
+
+    if(!checkURL(siteUrl.value)) {
+        return;
+    } 
+
+    chrome.runtime.sendMessage({ 
+        actionType: "CONTENT_GUIDE_APPLY_APP", 
+        data: {
+            siteUrl: siteUrl.value,
+            selectGuideApp: pageData.selectGuideApp
+        }
+    },(response) => {});
+
+    setTimeout(() => {
+        for(let i = 0; i < pageData.windowData.length; i++) {
+            let item = pageData.windowData[i];
+            chrome.windows.remove(
+                item.id,
+                function() {}
+            )
+        };
+    }, 600)
+};
+
+const onRuntimeMsg = () => {
+    chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
+        sendResponse('ok')
+        switch (req.actionType) {
+            case 'CONTENT_EDIT_SEND_GUIDE_DATA':
+                let {guideData, windowData, selectGuideApp} = req.data;
+                pageData.guideData = guideData;
+                pageData.windowData = windowData;
+                pageData.selectGuideApp = selectGuideApp;
+                break;
+        }
+    })
+}
+
+onMounted(() => {
+    setTimeout(() => {
+        chrome.runtime.sendMessage({ 
+            actionType: "CONTENT_GET_GUIDE_DATA", 
+            data:{}
+        },(response) => {});
+    }, 600)
+
+    onRuntimeMsg();
+})
+
+</script>
+
+<style lang="scss">
+
+html, body, #app {
+    margin: 0 !important;
+    width: 100%;
+    height: 100%;
+}
+.guide-wrapper {
+    width: 100%;
+    height: 100%;
+
+    .top {
+        height: calc(100% - 158px);
+        padding: 30px 43px;
+        box-sizing: border-box;
+        overflow-y: auto;
+
+        .title {
+            font-weight: 500;
+            font-size: 30px;
+            margin-top: 10px;
+            margin-bottom: 30px;
+        }
+
+        .img-item {
+            margin-bottom: 18px;
+
+            .img {
+                width: 100%;
+            }
+        }
+    }
+
+    .bottom {
+        height: 158px;
+        background: #1D9BF0;
+        padding: 20px;
+        box-sizing: border-box;
+
+        .title {
+            font-weight: 500;
+            font-size: 20px;
+            color: #fff;
+            margin-top: 7px;
+            margin-bottom: 18px;
+        }
+
+        .search-input-wrapper {
+            width: 100%;
+            height: 49px;
+            background: #fff;
+            border-radius: 100px;
+            box-sizing: border-box;
+            display: flex;
+
+            .input {
+                border: none;
+                outline: none;
+                border-radius: 100px;
+                width: calc(100% - 49px);
+                height: 100%;
+                box-sizing: border-box;
+                color: #636363;
+                padding: 15px 20px 15px 20px;
+            }
+
+            .btn {
+                width: 49px;
+                height: 100%;
+                background: #D2EAFC;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                border-top-right-radius: 100px;
+                border-bottom-right-radius: 100px;
+                cursor: pointer;
+            }
+        }
+    }
+}
+</style>

+ 395 - 0
src/view/iframe/publish/tool-box/child/preview.vue

@@ -0,0 +1,395 @@
+<template>
+    <div class="editor-preview-wrapper">
+        <div class="top">
+            <div class="card-container">
+                <!-- 安装之后的卡片样式 -->
+                <div class="content-after" v-show="installStatus" :style="{ 'width': reviewCanvasParams.width + 'px' }">
+                    <div class="head" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <img :src="userInfo.avatarUrl" class="avatar" />
+                        <div class="article-wrapper">
+                            <div class="nickname">
+                                {{ userInfo.name }}
+                            </div>
+                            <div class="name">
+                                @{{ userInfo.nickName }}
+                            </div>
+                        </div>
+                    </div>
+                    <div class="after-cover-wrapper" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <install-card :pre_view="true" :iframe_url="previewData.convertUrl"></install-card>
+                    </div>
+                </div>
+
+                <!-- 安装之前的卡片样式 -->
+                <div class="content-before" v-show="!installStatus"
+                    :style="{ 'width': reviewCanvasParams.width + 'px' }">
+                    <div class="head" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <img :src="userInfo.avatarUrl" class="avatar" />
+                        <div class="article-wrapper">
+                            <div class="nickname">
+                                {{ userInfo.name }}
+                            </div>
+                            <div class="name">
+                                @{{ userInfo.nickName }}
+                            </div>
+                        </div>
+                    </div>
+                    <div class="card-wrapper" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <img class="cover" :src="previewData.linkImagePath || require('@/assets/svg/img-default-site-cover.svg')" />
+                        <!-- v-if="previewData.linkImagePath && previewData.appId" <iframe class="iframe" 
+                            :src="previewData.convertUrl"
+                            v-else></iframe>  -->
+
+                        <div class="bottom-bar">
+                            <div class="site-url">DeNet.me</div>
+                            <div class="desc">Install DeNet Plugin to Participate</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="font">
+                Preview: <span>{{ installStatus ? 'After' : 'Before' }}</span> DeNet Installed
+            </div>
+        </div>
+        <div class="bottom">
+            <div class="btn" @click="publishHandler">NEXT</div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, defineEmits, reactive, defineProps, onMounted, nextTick, onUnmounted } from "vue";
+import { postPublish } from "@/http/publishApi";
+import installCard from '@/view/content/tool-box/index.vue'
+
+import { getChromeStorage, setChromeStorage } from "@/uilts/chromeExtension"
+import { getUser } from "@/http/publishApi"
+
+let installStatus = ref(false);
+
+let userInfo = ref({});
+let submitIng = ref(false);
+
+let reviewCanvasParams = reactive({
+    width: 620,
+    zoom: 1
+});
+let timer = ref(null);
+
+const props = defineProps({
+    previewData: {
+        type: Object,
+        default: {
+            convertUrl: '',
+            originUrl: '',
+            appId: ''
+        }
+    }
+})
+
+const emits = defineEmits(["publishFinish"]);
+
+const getUserInfo = (cb) => {
+    getChromeStorage('userInfo', (res) => {
+        if (res) {
+            userInfo.value = res;
+        }
+        cb && cb(res);
+    })
+}
+
+const getUserName = (screenName) => {
+    getUser({
+        params: {
+            screenName
+        }
+    }).then(res => {
+        console.log(res);
+        if (res.code == 0) {
+            userInfo.value.name = res.data.name || ''
+        }
+    });
+}
+
+const calcPreviewCanvasParams = () => {
+    nextTick(() => {
+        let containerDom = document.querySelector('.card-container');
+        let domHeight = containerDom && containerDom.offsetHeight || 500;
+        const canvasHeight = 780, canvasWidth = 620;
+        if (domHeight < canvasHeight) {
+            //比例: 高 / 宽
+            let hWRatio = canvasHeight / canvasWidth;
+            //缩小宽度 = 高度 / 比例  
+            let width = domHeight / hWRatio;
+            if (width > canvasWidth) {
+                width = canvasWidth;
+            }
+            //缩小比例 
+            let zoom = width / canvasWidth;
+            if (zoom > 1) {
+                zoom = 1;
+            }
+            reviewCanvasParams.width = width;
+            reviewCanvasParams.zoom = zoom;
+        } else {
+            reviewCanvasParams.width = canvasWidth;
+            reviewCanvasParams.zoom = 1;
+        }
+    });
+}
+
+const publishHandler = () => {
+    if(submitIng.value) {
+        return;
+    }
+    let {convertUrl, originUrl, appId, currentApp} = props.previewData;
+
+    setHistoryData(currentApp);
+
+    let postBizData = {
+        convertUrl,
+        originUrl,
+        appId
+    };
+    let data = {
+        params: {
+            postBizData: JSON.stringify(postBizData),
+            postSrc: 1, // 1 twitter
+            postType: 3, //3 Tool box
+        },
+    };
+
+    submitIng.value = true;
+    
+    postPublish(data).then((res) => {
+        submitIng.value = false;
+        if (res.code == 0) {
+            let publishRes = res.data;
+            emits("publishFinish", { publishRes });
+        } else {
+        }
+    })
+    .catch((err) => {
+        console.log(err);
+    });
+}
+
+const setHistoryData = async (params) => {
+    const maxLength = 9;
+    let {list = []} = await getChromeStorage('toolBoxAppHistoryData') || {};
+    if(list.length) {
+        let hasSite = list.find(item => item.defaultUrl == params.defaultUrl);
+        if(hasSite) {
+            return;
+        }
+        list.unshift(params);
+        if(list.length > maxLength) {
+            list.length = maxLength;
+        }
+        setChromeStorage({ toolBoxAppHistoryData : JSON.stringify({
+            list: list
+        })}) 
+    } else {
+        setChromeStorage({ toolBoxAppHistoryData : JSON.stringify({
+            list: [params]
+        })})    
+    }
+};
+
+onMounted(() => {
+    calcPreviewCanvasParams();
+    getUserInfo((res) => {
+        if (res) {
+            getUserName(res.nickName);
+        }
+        clearInterval(timer.value);
+        timer.value = setInterval(() => {
+            installStatus.value = !installStatus.value;
+        }, 3000)
+    });
+    window.addEventListener('resize', function () {
+        calcPreviewCanvasParams();
+    })
+})
+
+onUnmounted(() => {
+    clearInterval(timer.value);
+})
+
+</script>
+
+<style lang="scss" scoped>
+.editor-preview-wrapper {
+    width: 100%;
+    height: 100%;
+
+    .top {
+        width: 100%;
+        height: calc(100% - 80px);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        overflow-y: auto;
+        padding: 20px 0;
+        box-sizing: border-box;
+
+        .card-container {
+            height: 100%;
+            margin-right: 50px;
+
+            .content-after,
+            .content-before {
+                position: relative;
+            }
+
+            .head {
+                position: absolute;
+                z-index: 1100;
+                top: 20px;
+                left: 17px;
+                display: flex;
+
+                .avatar {
+                    width: 47px;
+                    height: 47px;
+                    border-radius: 50%;
+                    object-fit: cover;
+                    margin-right: 13px;
+                }
+
+                .article-wrapper {
+                    display: flex;
+
+                    .nickname {
+                        font-weight: 500;
+                    }
+
+                    .nickname,
+                    .name {
+                        font-size: 15px;
+                    }
+                    .name {
+                        color: #566370;
+                        margin-left: 7px;
+                    }
+                }
+            }
+
+            .content-after {
+                background: url('@/assets/img/img-tool-box-preview-after.png');
+                width: 387px;
+                height: 100%;
+                background-size: contain;
+                background-repeat: no-repeat;
+                border: 1px solid #D1D9DD;
+                border-radius: 13px;
+                box-sizing: border-box;
+
+                .after-cover-wrapper {
+                    position: absolute;
+                    z-index: 100;
+                    top: 108px;
+                    left: 78px;
+                    width: 425px;
+                    height: 458px;
+                    border-radius: 10px;
+                    // border: 1px solid #D1D9DD;
+                }
+
+            }
+
+            .content-before {
+                background: url('@/assets/img/img-tool-box-preview-before.png');
+                background-size: contain;
+                background-repeat: no-repeat;
+                height: 100%;
+                border: 1px solid #D1D9DD;
+                border-radius: 13px;
+                box-sizing: border-box;
+
+                .card-wrapper {
+                    width: 505px;
+                    height: 338px;
+                    border: 1px solid #D1D9DD;
+                    background: #ffffff;
+                    box-sizing: border-box;
+                    overflow: hidden;
+                    position: relative;
+                    box-sizing: border-box;
+                    border-radius: 16px;
+                    left: 73px;
+                    top: 90px;
+
+                    .iframe {
+                        height: calc(100% - 73px);
+                        width: 100%;
+                        border: none;    
+                        pointer-events: none;
+                        cursor: default;
+                    }
+
+                    .cover {
+                        height: calc(100% - 73px);
+                        width: 100%;
+                        object-fit: contain;
+                    }
+
+                    .bottom-bar {
+                        width: 100%;
+                        height: 73px;
+                        padding: 10px 10px 0 13px;
+                        border-top: 1px solid rgba(0, 0, 0, 0.3);
+                        .site-url {
+                            color: #566370;
+                            font-size: 14px;
+                            line-height: 20px;
+                        }
+                        .desc {
+                            font-weight: 500;
+                            font-size: 15px;
+                            line-height: 21px;
+                            font-weight: 500;
+                        }
+                    }
+                }
+            }
+        }
+
+        .font {
+            width: 300px;
+            font-weight: 600;
+            font-size: 20px;
+
+            span {
+                color: #1D9BF0;
+            }
+        }
+    }
+
+    .bottom {
+        width: 100%;
+        height: 80px;
+        box-shadow: 0px -1px 0px #ECECEC;
+        box-sizing: border-box;
+        border-bottom-left-radius: 16px;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        .btn {
+            width: 200px;
+            height: 50px;
+            background: #1D9BF0;
+            border-radius: 10000px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-weight: 700;
+            font-size: 18px;
+            color: #fff;
+            margin-right: 30px;
+            cursor: pointer;
+        }
+    }
+
+}
+</style>

+ 70 - 0
src/view/iframe/publish/tool-box/index.vue

@@ -0,0 +1,70 @@
+<template>
+    <div class="page-wrapper">
+        <editor v-show="showCom == 'EDITOR'" :linkInputDescImage="pageData.linkInputDescImage" @changeShowCom="changeShowCom" />  
+        <preview v-show="showCom == 'PREVIEW'" :previewData="previewData" 
+        @publishFinish="publishFinish" />
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, defineProps, defineEmits } from "vue";
+
+import editor from '@/view/iframe/publish/tool-box/child/editor.vue'
+import preview from '@/view/iframe/publish/tool-box/child/preview.vue'
+
+const props = defineProps({
+    pageData: {
+        type: Object,
+        default: {
+            linkInputDescImage: ''
+        }
+    },
+    activePage: {
+        type: String,
+        default: 'EDITOR', // EDITOR PREVIEW
+    },
+});
+
+const emits = defineEmits(["onToolBoxPageChange"]);
+
+watch(
+    () => props.activePage,
+    (newVal) => {
+        showCom.value = newVal;
+    },
+    {
+        deep: true
+    }
+);
+
+let showCom = ref('EDITOR'); 
+let previewData = reactive({
+    convertUrl: '',
+    originUrl: '',
+    appId: '',
+    currentApp: {}
+})
+
+const changeShowCom = (params) => {
+    showCom.value = 'PREVIEW';
+    previewData.convertUrl = params.convertUrl;
+    previewData.originUrl = params.originUrl;
+    previewData.appId = params.appId
+    previewData.linkImagePath = params.linkImagePath || '';
+    previewData.currentApp = params.currentApp || {};
+
+    emits("onPageChange", {page: showCom.value});
+}
+
+const publishFinish = (params) => {
+    emits("toolBoxPublishFinish", params);
+}
+
+</script>
+
+<style lang="scss" scoped>
+    .page-wrapper {
+        width: 100%;
+        height: 100%;
+    }
+</style>

+ 147 - 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,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">
+                    <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>
             <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 +187,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 +195,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 +208,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 +219,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 +257,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 +285,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 +293,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 +360,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 +389,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 +397,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 +445,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 +461,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 +496,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 +518,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 +594,8 @@ async function clickLikeBtn() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 function clickDone() {
@@ -572,7 +608,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 +711,8 @@ async function clickRetweetBtn() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 
@@ -733,7 +773,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 +817,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 +1027,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 +1153,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 +1174,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 +1199,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 +1238,7 @@ const handleStatusPage = () => {
     //    如果 任务完成状态 = 未完成 & 红包状态 = 已结束
     //         显示过期页面
     //    如果 任务完成状态 = 已经完成
-    //        如果 领取到红包金额 = 0 & 红包状态 = 已结束
+    //        如果 (货币奖品类型 领取到红包金额 = 0 || 通用奖品类型 winner = 0) & 红包状态 = 已结束
     //            显示兔子页面
     //        否则如果 红包状态 = 已结束
     //            显示成功页面
@@ -1196,7 +1248,7 @@ const handleStatusPage = () => {
     //    如果 任务完成状态 = 已经过期
     //        如果 红包状态 = 进行中
     //            显示未打开页面
-    //        否则 
+    //        否则
     //            显示已经过期页面
 
     // 如果 我没有领取过 & 红包状态 = 进行中
@@ -1210,6 +1262,7 @@ const handleStatusPage = () => {
 
     // -------- 华丽的分割线 --------
     // 如果 红包状态 = 未开始
+    
     if (state.detail.status == 0) {
         showNotOpenPage()
         return
@@ -1240,7 +1293,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 +1384,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.rewardType == 2 ? 1 : 0;
             // 不要删除这个console
             console.log('postBizData', state.detail)
             checkFacebookReply();
@@ -1572,7 +1627,9 @@ function handleRedPacket() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
-        redPacketType: 1
+        redPacketType: 1,
+        customCover: state.customCover,
+        customGiveaway: state.customGiveaway,
     });
 }
 
@@ -1634,6 +1691,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 +1773,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 +1792,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 +2100,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 +2513,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 +2664,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 +2839,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 {

+ 126 - 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,19 @@
           <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>
       <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 +192,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 +200,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 +213,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 +229,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 +259,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 +283,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 +291,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 +319,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 +344,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 +352,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 +399,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 +414,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 +448,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 +465,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 +541,7 @@ async function clickLikeBtn() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
+    customCover: state.customCover,
   });
 }
 function clickDone() {
@@ -525,7 +554,8 @@ function clickDone() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 function handleScroll(e) {
@@ -626,6 +656,7 @@ async function clickRetweetBtn() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
+    customCover: state.customCover,
   });
 }
 
@@ -664,7 +695,8 @@ async function clickReply(params) {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -706,7 +738,8 @@ async function clickRepostFacebook(params) {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -873,7 +906,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 +1023,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 +1035,8 @@ const showNotOpenPage = () => {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 const showOpenedPage = () => {
@@ -1021,7 +1057,8 @@ const showOpenedPageReport = () => {
     senderId: state.userId,
     isOldTwitterFans: state.done.follow,
     isOldDiscordFans: state.done.join_discord,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -1055,7 +1092,7 @@ const handleStatusPage = () => {
   //    如果 任务完成状态 = 未完成
   //        显示任务未完成页面
   //    如果 任务完成状态 = 已经完成
-  //        如果 领取到红包金额 = 0
+  //        如果 (货币类奖品 && 领取到红包金额 = 0) || (自定义奖品 && winner = 0)
   //            显示兔子页面
   //        否则 
   //            显示成功页面
@@ -1094,7 +1131,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 +1216,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 +1417,8 @@ function handleRedPacket() {
     postId: state.postId,
     srcContentId: state.tweetId,
     senderId: state.userId,
-    redPacketType: 0
+    redPacketType: 0,
+    customCover: state.customCover,
   });
 }
 
@@ -1447,6 +1486,7 @@ function handleFinishRedPacket() {
           postId: state.postId,
           srcContentId: state.tweetId,
           senderId: state.userId,
+          customCover: state.customCover,
         }, {
           get_giveaway_result: Report.extParams.success
         });
@@ -1526,6 +1566,7 @@ function handleFinishRedPacket() {
           postId: state.postId,
           srcContentId: state.tweetId,
           senderId: state.userId,
+          customCover: state.customCover,
         }, {
           get_giveaway_result: Report.extParams.failure,
         });
@@ -1542,6 +1583,7 @@ function handleFinishRedPacket() {
         postId: state.postId,
         srcContentId: state.tweetId,
         senderId: state.userId,
+        customCover: state.customCover,
       }, {
         get_giveaway_result: Report.extParams.failure,
       });
@@ -1847,7 +1889,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 +2271,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 +2307,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 +2423,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 +2437,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;

+ 65 - 27
src/view/iframe/tab-group/tab-group.vue

@@ -41,14 +41,24 @@
                                     </template>
                                 </el-popover>
                             </div>
-                            <div class="nick-name">
+                            <div class="nick-name"
+                                :style="{
+                                    color: eleThemeStyle.color
+                                }">
                                 {{item.nickName}}
                             </div>
-                            <div class="screen-name">
+                            <div class="screen-name"
+                                :style="{
+                                    color: eleThemeStyle.screenName
+                                }">
                                 @{{item.screenName}}
                             </div>
                         </div>
-                        <div class="post-content" v-html="item.textContent"></div>
+                        <div class="post-content"
+                            :style="{
+                                color: eleThemeStyle.color
+                            }"
+                            v-html="item.textContent"></div>
                     </div>
                 </div>
             </template>
@@ -61,7 +71,7 @@
 </template>
 
 <script setup>
-import { onMounted, ref } from "vue";
+import { onMounted, reactive, ref } from "vue";
 import { getGroupPostList, getTwitterNftGroupInfo } from '@/http/nft'
 import { getQueryString } from '@/uilts/help.js'
 import { ElPopover } from "element-plus";
@@ -75,6 +85,12 @@ let listWrapperDom = ref(null);
 let pageWrapperDom = ref(null);
 let loading = ref(false);
 
+let eleThemeStyle = reactive({
+    color: '#000',
+    screenName: '#566370',
+    borderColor: '#F0F3F4',
+})
+
 let listReqParams = {
     params: {
         pageSize: 100,
@@ -110,6 +126,9 @@ function onRuntimeMsg() {
             case 'CONTENT_SEND_GROUP_NAV_TOP':
                 styleHandler(req.data);
                 break;
+            case 'CONTENT_SYS_THEME_CHANGE':
+                setPageThemeStyle(req.data);
+                break;
         }
     })
 }
@@ -234,6 +253,22 @@ const initData = () => {
     }
 }
 
+const setPageThemeStyle = (params) => {
+    let {twitterTheme, theme} = params;
+
+    if(twitterTheme == 'light') {
+        eleThemeStyle.color = '#000';
+        eleThemeStyle.screenName = '#566370';
+        eleThemeStyle.borderColor = '#F0F3F4';
+        document.querySelector('body').style.backgroundColor = '#fff'
+    } else if (twitterTheme == 'dark'){
+        eleThemeStyle.color = '#fff';
+        eleThemeStyle.screenName = '#fff';
+        eleThemeStyle.borderColor = '#000';
+        document.querySelector('body').style.backgroundColor = '#000'
+    }
+};
+
 onMounted(() => {
     onRuntimeMsg();
     initData();
@@ -259,28 +294,31 @@ html, body, #app {
     display: none !important;
 }
 
-@media (prefers-color-scheme: light) {
-    body {
-        background: #fff;
-    }
-}
-
-@media (prefers-color-scheme: dark) {
-    .list-item {
-        border-bottom: 1px solid #000 !important;
-    }
-    .nick-name {
-        color: #fff !important;
-    }
-
-    .screen-name {
-        color: #fff !important;
-    }
-
-    .post-content {
-        color: #fff !important;
-    }
-}
+// @media (prefers-color-scheme: light) {
+//     body {
+//         background: #fff;
+//     }
+// }
+
+// @media (prefers-color-scheme: dark) {
+//     body {
+//         background: #000 !important;
+//     }
+//     .list-item {
+//         border-bottom: 1px solid #000 !important;
+//     }
+//     .nick-name {
+//         color: #fff !important;
+//     }
+
+//     .screen-name {
+//         color: #fff !important;
+//     }
+
+//     .post-content {
+//         color: #fff !important;
+//     }
+// }
 
 .preview-nft {
     box-sizing: border-box;
@@ -399,7 +437,7 @@ html, body, #app {
                     font-weight: 400;
                     font-size: 16px;
                     line-height: 24px;
-                    color: rgb(15, 20, 25);
+                    color: #000;
                     font-family: TwitterChirp, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                     word-break: break-all;
                     white-space: pre-line;

+ 31 - 0
src/view/iframe/test/index.vue

@@ -0,0 +1,31 @@
+<template>
+    <!-- <div class="denet-toolbox" @click.stop="clickHead">
+        
+    </div> -->
+    <input type="button" value="点击" @click="state.show = !state.show">
+    <p v-if="state.show" :data-view="1" :data-click="'按钮'" @click.stop="clickTest">1</p>
+    <p v-else :data-view="2">2</p>
+
+</template>
+<script setup>
+import { onMounted, reactive } from "vue";
+
+let state = reactive({
+    show: false
+})
+onMounted(() => {
+    var io = new IntersectionObserver((e) => {
+        console.log(e[0].target)
+    });
+    io.observe(document.querySelector('p[data-view]'))
+
+})
+
+function clickTest() {
+    console.log('clickTest')
+}
+
+</script>
+
+<style lang="scss">
+</style>

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

@@ -4,53 +4,33 @@
       <div class="list-wrapper" ref="pageGiveListDom">
         <div class="give-list" v-if="currentTabIndex == 0">
           <template v-if="giveList.length">
-            <div
-              class="cell"
-              :class="{ 'cell-center': item.type == 1 || item.type == 3 }"
-              v-for="(item, index) in giveList"
-              :key="index"
-              @click="clickListItem(item, index)"
-            >
-              <red-dot
-                class="red-dots"
-                v-if="item.unReadMsgCount > 0 && isReadMsg"
-              ></red-dot>
+            <div class="cell" :class="{ 'cell-center': item.type == 1 || item.type == 3 }"
+              v-for="(item, index) in giveList" :key="index" @click="clickListItem(item, index)">
+              <red-dot class="red-dots" v-if="item.unReadMsgCount > 0 && isReadMsg"></red-dot>
 
               <div class="img-wrapper">
                 <!-- 收到红包 -->
                 <template v-if="item.type == 1 || item.type == 3">
                   <img class="icon-avatar" :src="item.userInfo.avatarUrl" />
-                  <img
-                    class="icon-give"
-                    :src="require('@/assets/svg/icon-get-giveaways-s.svg')"
-                  />
+                  <img class="icon-give" :src="require('@/assets/svg/icon-get-giveaways-s.svg')" />
                 </template>
                 <!-- 发出去红包 -->
                 <template v-else-if="2">
-                  <img
-                    class="icon-big-give"
-                    :src="require('@/assets/svg/icon-send-giveaways-s.svg')"
-                  />
-                  <img
-                    class="icon-mark-give"
-                    :src="require('@/assets/svg/icon-send-giveaways-mark.svg')"
-                  />
+                  <img class="icon-big-give" :src="require('@/assets/svg/icon-send-giveaways-s.svg')" />
+                  <img class="icon-mark-give" :src="require('@/assets/svg/icon-send-giveaways-mark.svg')" />
                 </template>
               </div>
-              <div
-                class="info-wrapper"
-                :class="{ 'info-center': item.type == 1 }"
-              >
+              <div class="info-wrapper" :class="{ 'info-center': item.type == 1 }">
                 <div class="left">
                   <div class="nickname">
                     <template v-if="item.type == 1">
-                        Get Giveaway
+                      Get Giveaway
                     </template>
                     <template v-else-if="item.type == 2">
-                        {{ item.postTaskLuckdrop.luckdropType === 1 ? 'Send Giveaway' : 'Lottery Giveaway' }}
+                      {{ item.postTaskLuckdrop.luckdropType === 1 ? 'Send Giveaway' : 'Lottery Giveaway' }}
                     </template>
                     <template v-else-if="item.type == 3">
-                        Lottery
+                      Lottery
                     </template>
                   </div>
                   <div class="time">
@@ -59,74 +39,99 @@
                 </div>
                 <div class="right">
                   <div class="msg">
-                    <div
-                      class="bold"
-                      :class="{
+                    <div 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,
+                        'custom-bold': item.rewardType === RewardType.custom
                       }"
                     >
                       <!-- 收到的 -->
                       <template v-if="item.type == 1">
                         <!-- 进行中-->
                         <template v-if="item.status == 0">
-                          in progress
+                          In Progress
                         </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">{{
                               item.currencySymbol || ""
-                            }}</span>
+                              }}</span>
                             <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 class="cuntom-prize-icon">X</span>
+                            <span class="cuntom-prize-total">{{item?.postTaskLuckdrop?.totalCount || ''}}</span>
+                          </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">
-                        Open in {{ item.downTime || '' }}
+                          Open in {{ item.downTime || '' }}
                         </template>
                         <template v-else-if="item.status == 3">
                           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
@@ -144,86 +149,78 @@
                       </template>
                       <!-- 进行中 -->
                       <template v-else-if="item.status == 1">
-                        {{ item.postTaskLuckdrop.receivedCount }}/{{
+                        <template v-if="item.postTaskLuckdrop && item.postTaskLuckdrop.luckdropType == 2">
+                          {{ item.downTime || '' }}
+                        </template>
+                        <template v-else>
+                          {{ item.postTaskLuckdrop.receivedCount }}/{{
                           item.postTaskLuckdrop.totalCount
-                        }}
+                          }}
+                        </template>
                       </template>
                       <!-- 2:已结束; 3:提前终止-->
                       <template v-else-if="item.status == 2 || item.status == 3">
+                        <!-- 普通红包 -->
                         <template v-if="item.postTaskLuckdrop && item.postTaskLuckdrop.luckdropType == 1">
-                            ({{
-                            item.status == 2 ? "Time expired" : "Termination"
-                            }}) {{ item.postTaskLuckdrop.receivedCount }}/{{
+                          <template v-if="item.status == 2">
+                            (Time expired)
+                          </template>
+                          <template v-if="item.status == 3">
+                            {{item.srcContentId ? '(Termination)' : 'Termination'}}
+                          </template>
+                          <template v-if="item.status == 2 || item.status == 3 && item.srcContentId">
+                            {{ item.postTaskLuckdrop.receivedCount }}/{{
                             item.postTaskLuckdrop.totalCount
                             }}
+                          </template>
                         </template>
+                        <!-- 抽奖红包 -->
                         <template v-else>
-                            Termination
+                            <!-- 自定义奖品类型 结束时显示 Complete -->
+                            {{ item.rewardType === RewardType.custom && item.status == 2 ? 'Complete' : 'Termination' }}
                         </template>
                       </template>
                       <!-- 红包提前终止/退款(进行中)显示-->
                       <template v-if="item.status == 4">
-                        {{ item.postTaskLuckdrop && item.postTaskLuckdrop.luckdropType == 1 ? 'Terminating' : 'Open in ' + item.downTime || '' }}
+                        {{ item.postTaskLuckdrop && item.postTaskLuckdrop.luckdropType == 1 ? 'Terminating' : 'Open in '
+                        + item.downTime || '' }}
                       </template>
 
                       <!-- 进行中或者未发送成功时显示 
                                                 v-if="item.status == 1 || item.postTaskLuckdrop.reSendAvailable"-->
                       <div class="desc-bottom-bar">
                         <!-- 没有终止红包时显示 -->
-                        <div
-                          v-if="item.postTaskLuckdrop.terminatedAvailable"
-                          class="btn"
-                          @click.stop="terminaHandler(item, index)"
-                        >
+                        <div v-if="item.postTaskLuckdrop.terminatedAvailable" class="btn"
+                          @click.stop="terminaHandler(item, index)">
                           Termination
                         </div>
 
                         <!-- 红包未发出显示 -->
-                        <div
-                          class="btn send-btn"
-                          v-if="item.postTaskLuckdrop.reSendAvailable"
-                          @click.stop="sendTwitter(item)"
-                        >
+                        <div class="btn send-btn" v-if="item.postTaskLuckdrop.reSendAvailable"
+                          @click.stop="sendTwitter(item)">
                           Send
                         </div>
-                        <div
-                          v-else-if="item.srcContentId"
-                          class="btn detail-btn"
-                          @click.stop="clickListItem(item, index)"
-                        >
+                        <div v-else-if="item.srcContentId" class="btn detail-btn"
+                          @click.stop="clickListItem(item, index)">
                           details
                         </div>
                       </div>
                     </div>
                   </div>
                   <!-- 发红包—— 未发出、进行中 隐藏 -->
-                  <img
-                    v-if="item.type != 2"
-                    class="icon"
-                    :src="require('@/assets/svg/icon-cell-arrow-right.svg')"
-                  />
+                  <img v-if="item.type != 2" class="icon" :src="require('@/assets/svg/icon-cell-arrow-right.svg')" />
                 </div>
               </div>
             </div>
           </template>
           <template v-else>
-            <img
-              class="icon-empty"
-              :src="require('@/assets/svg/icon-empty-list.svg')"
-            />
+            <img class="icon-empty" :src="require('@/assets/svg/icon-empty-list.svg')" />
           </template>
         </div>
       </div>
     </div>
-    <modal
-      :visible="modalVisible"
-      :title="modalTitle"
-      :content="modalContent"
-      cancelText="Termination"
-      confirmText="Cancel"
-      @cancel="modalCancel"
-      @confirm="modalConfirm"
-    />
+    <modal :visible="modalVisible" :title="modalTitle" :content="modalContent" cancelText="Termination"
+      confirmText="Cancel" @cancel="modalCancel" @confirm="modalConfirm" />
   </div>
 </template>
 
@@ -239,6 +236,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 +665,21 @@ onBeforeUnmount(() => {
                     color: #e86f00;
                   }
 
+                  .cuntom-prize {
+                    max-width: 130px;
+                    word-break: break-word;
+                    text-align: left;
+                  }
+
+                  .cuntom-prize-icon {
+                    color: #000;
+                  }
+
+                  .cuntom-prize-total {
+                    color: #000;
+                    word-break: break-word;
+                  }
+
                   .coin-type-wrapper {
                     display: flex;
                     align-items: center;

+ 4 - 4
vue.config.js

@@ -100,10 +100,10 @@ module.exports = {
             from: path.resolve(`src/manifest.json`),
             to: `${path.resolve('dist')}/manifest.json`
           },
-          // {
-          //   from: path.resolve(`src/rules`),
-          //   to: `${path.resolve('dist')}/rules`
-          // }
+          {
+            from: path.resolve(`src/rules`),
+            to: `${path.resolve('dist')}/rules`
+          }
         ]
       })
     ],

+ 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"

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä