Browse Source

Merge branch 'feature-iframe-7.4' into dev_v1.1.4

zhangwei 2 years ago
parent
commit
0ed2a308db

BIN
src/assets/img/icon-close.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


+ 10 - 0
src/entry/background.js

@@ -25,6 +25,7 @@ import {
 } from "@/logic/background/twitter";
 import Report from "@/log-center/log"
 import { PingPong, httpNetWork } from "@/logic/background/help";
+import { commonFetch } from '@/http/fetch'
 
 import {
     facebookShareSuccess
@@ -159,6 +160,15 @@ function onMessageMethod(req, sender, sendResponse) {
             case 'CONTENT_HTTP_NET_WORK':
                 httpNetWork(req.funcName, req.data, sender)
                 break
+            case 'HTTP_CONTENT_TO_BACK':
+                commonFetch(req.data)
+                    .then((response) => {
+                        chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: response, callback_id: req.callback_id });
+                    })
+                    .catch(() => {
+                        chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: null, callback_id: req.callback_id });
+                    })
+                break
         }
     }
 }

+ 13 - 8
src/entry/content.js

@@ -40,7 +40,8 @@ import {
     TwitterApiUserByScreenName
 } from "@/logic/content/twitter.js";
 
-import { 
+import { httpBackToContentCallBack } from '@/uilts/chromeExtension.js'
+import {
     hideNFTGroupList,
     setNFTGroupContent,
     setJoinedGroupIframeStyle
@@ -57,9 +58,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: {}
     }, () => { });
 };
 
@@ -133,8 +134,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)
@@ -146,7 +147,7 @@ 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)
+            console.log('IFREME_TAB_GROUP_SET_IFRAME_HEIGHT', req)
             setTabGroupIframeStyle(req.data);
             break
         case 'IFREME_TAB_GROUP_CONTENT_GET_NAV_TOP':
@@ -182,9 +183,13 @@ 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
     }
 })

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

+ 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.children[i].id.includes('denet')) {
+                    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()

+ 9 - 1
src/logic/content/twitter.js

@@ -7,6 +7,7 @@ import { fetchAddFinishEvent } from '@/logic/background/fetch/facebook';
 import { showNFTGroupIcon, hideNFTGroupList, checkUserJoinGroup, elemAddEventListener, addJoinedGroupList } from '@/logic/content/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 = {};
@@ -784,7 +785,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)
                     }
@@ -892,6 +897,7 @@ function initParseCard() {
         }
     }, 1000);
 }
+
 let inited = false
 // 初始化
 export function init() {
@@ -927,6 +933,8 @@ export function init() {
         addJoinedGroupList();
         getSysTheme();
         addGroupTab();
+        // 预加载全屏 toobbox
+        ToolBox.initFull()
     }
     // 渲染dom
     initParseCard()

+ 16 - 1
src/manifest.json

@@ -44,7 +44,19 @@
             ]
         }
     ],
+    "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 +70,8 @@
         "tabs",
         "action",
         "cookies",
+        "webNavigation",
+        "declarativeNetRequest",
         "activeTab",
         "scripting",
         "storage",
@@ -81,7 +95,8 @@
                 "/iframe/popup-page.html",
                 "/iframe/tab-group.html",
                 "/iframe/joined-group-list.html",
-                "/iframe/tool-box-guide.html"
+                "/iframe/tool-box-guide.html",
+                "/iframe/test.html"
             ],
             "matches": [
                 "<all_urls>"

+ 9 - 4
src/rules/rules_1.json

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

+ 30 - 3
src/uilts/chromeExtension.js

@@ -1,5 +1,6 @@
 import { pageUrl } from "@/http/configAPI.js"
-
+import { exp } from "mathjs"
+import { guid } from "@/uilts/help";
 export const LANDING_PAGE = {
     name: 'received_log',
     url: pageUrl
@@ -107,7 +108,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 +131,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之间互相传至
+// })

+ 150 - 0
src/view/content/tool_box/full.vue

@@ -0,0 +1,150 @@
+<template>
+    <div class="denet-toolbox">
+        <div class="head">
+            <span>Subway Surfers</span>
+            <div>
+                <!-- 缩放 -->
+                <img :src="require('@/assets/img/icon-full.png')" alt class="fixed" @click="clickFull" />
+                <!-- 关闭 -->
+                <img :src="require('@/assets/img/icon-close.png')" alt class="full" @click="clickClose" />
+            </div>
+        </div>
+        <div class="content">
+            <iframe :src="state.iframe_url" frameborder="0"></iframe>
+        </div>
+    </div>
+</template>
+<script setup>
+import { onMounted, reactive, ref } from "vue";
+import { sendEventInfo } from "@/uilts/event";
+let state = reactive({
+    status: '固定', // 全屏
+    dom_fixed: null,
+    iframe_url: '',
+    tweetId: ''
+})
+
+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
+    }
+});
+
+
+const clickFull = () => {
+    if (state.status == '固定') {
+        state.status = '全屏'
+        changeFull()
+    } else {
+        state.status = '固定'
+        changeFixed()
+    }
+}
+onMounted(() => {
+    state.dom_fixed = document.querySelector('#denet-tool-box-fixed')
+})
+
+const changeFull = () => {
+    state.dom_fixed.style.cssText = `
+    width:100%;
+    height: 100%;
+    position: fixed;
+    right: 0px;
+    top: 0px;`
+}
+
+const changeFixed = () => {
+    state.dom_fixed.style.cssText = `
+    width: 505px;
+    height: 544px;
+    position: fixed;
+    right: 10px;
+    top: 10px;`
+}
+
+const clickClose = () => {
+    state.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;
+        }
+
+        img {
+            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>

+ 392 - 0
src/view/content/tool_box/index.vue

@@ -0,0 +1,392 @@
+<template>
+    <div class="denet-toolbox" @click.stop="clickHead" ref="dom_toolbox" :class="{ 'pre-view': pre_view }">
+        <div class="head" @click.stop="clickHead">
+            <span>Subway Surfers</span>
+            <div v-show="state.show_btn">
+                <img :src="require('@/assets/img/icon-fixed.png')" alt class="fixed" @click.stop="clickFixed" />
+                <img :src="require('@/assets/img/icon-full.png')" alt class="full" @click.stop="clickFull" />
+                <!-- <img :src="require('@/assets/img/icon-close.png')" alt class="full" @click="clickFull" /> -->
+            </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"></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-page-fail.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";
+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: {},
+    dom: {
+        root: null,
+        iframe: null
+    }
+})
+
+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 }) })
+        // 全屏
+
+    }
+}
+onMounted(() => {
+    if (props.pre_view) {
+        return
+    }
+    // twitterid
+    // postid
+    console.log('dom_toolbox', dom_toolbox.value)
+    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 || ''
+        }
+    }
+    getDetail()
+})
+
+// detail函数
+const getDetail = () => {
+    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.originUrl = 'https://www.bilibili.com'
+            iframe.onerror = () => {
+                state.status = '网页错误'
+            }
+            iframe.onload = function () {
+                state.status = ''
+            }
+            state.iframe_url = state.detail.originUrl
+        } else {
+            let iframe = dom_iframe.value
+            state.detail.originUrl = 'https://www.bilibili.com'
+            iframe.onerror = () => {
+                state.status = '网页错误'
+            }
+            iframe.onload = function () {
+                state.status = ''
+            }
+            state.iframe_url = state.detail.originUrl
+        }
+    })
+}
+
+const clickCancel = () => {
+    state.show_alert = false
+}
+
+const clickFixed = () => {
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 固定
+            handleFixed()
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+// 固定
+const handleFixed = () => {
+    if (state.status || !state.iframe_url) {
+        return
+    }
+    // 切换状态
+    state.status = '固定右上角'
+    // 操作全屏dom
+    let dom = document.querySelector('#denet-tool-box-fixed')
+    dom.style.display = 'block'
+    state.show_btn = false
+    sendEventInfo({
+        event_type: 'ToolBox_To_Fixed',
+        data: {
+            iframe_url: state.iframe_url,
+            tweetId: state.tweetId
+        }
+    })
+    // 清除当前iframe src
+    state.iframe_url = ''
+}
+
+const clickFull = () => {
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 全屏
+
+        } 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;
+
+        span {
+            color: #FFFFFF;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 14px;
+            margin-left: 16px;
+        }
+
+        img {
+            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%;
+        }
+
+        .state {
+            img {
+                margin-bottom: 14px;
+            }
+
+            .icon-loading {
+                animation: loading 1s infinite linear;
+            }
+
+            div {
+                color: #8E8E8E;
+                text-align: center;
+                font-weight: 500;
+                font-size: 22px;
+            }
+        }
+
+    }
+}
+
+@keyframes loading {
+    from {
+        transform: rotate(0deg);
+    }
+
+    to {
+        transform: rotate(360deg);
+    }
+}
+</style>

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

@@ -0,0 +1,265 @@
+<template>
+    <div class="denet-toolbox" @click.stop="clickHead">
+        <div class="head" @click.stop="clickHead">
+            <span>Subway Surfers</span>
+            <div>
+                <img :src="require('@/assets/img/icon-fixed.png')" alt class="fixed" @click.stop="clickFixed" />
+                <img :src="require('@/assets/img/icon-full.png')" alt class="full" @click.stop="clickFull" />
+                <!-- <img :src="require('@/assets/img/icon-close.png')" alt class="full" @click="clickFull" /> -->
+            </div>
+        </div>
+        <div class="content">
+            <iframe src="https://www.bilibili.com" v-show="state.status == ''" frameborder="0"></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 />
+            </div>
+
+            <!-- 关闭 -->
+            <div class="state" v-show="state.status == '关闭'">
+
+            </div>
+            <!-- 固定右上角 -->
+            <div class="state" v-show="state.status == '固定右上角'">
+                <img :src="require('@/assets/img/icon-page-fail.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 } from "@/uilts/chromeExtension";
+import { guid } from "@/uilts/help";
+import { onMounted, reactive } from "vue";
+
+let state = reactive({
+    status: '', //
+    show_alert: false,
+    list: [],
+    checkbox: false,
+    checkbox_id: `denet-${guid()}`
+})
+onMounted(() => {
+   
+})
+
+const clickHead = () => {
+    // 
+}
+
+const clickContinue = () => {
+    if (state.checkbox) {
+        setChromeStorage({ fullCheck: JSON.stringify({ fullCheck: 1 }) })
+        // 全屏
+    }
+}
+
+const clickCancel = () => {
+    state.show_alert = false
+}
+
+const clickFixed = () => {
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 固定
+
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+const clickFull = () => {
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 全屏
+
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+
+</script>
+
+<style lang="scss">
+.denet-toolbox {
+    width: 505px;
+    height: 544px;
+    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;
+
+        span {
+            color: #FFFFFF;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 14px;
+            margin-left: 16px;
+        }
+
+        img {
+            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%;
+        }
+
+        .state {
+            img {
+                margin-bottom: 14px;
+            }
+
+            div {
+                color: #8E8E8E;
+                text-align: center;
+                font-weight: 500;
+                font-size: 22px;
+            }
+        }
+    }
+}
+</style>

+ 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`
+          }
         ]
       })
     ],