Kaynağa Gözat

提交以下初始版本

xbx 6 ay önce
ebeveyn
işleme
38c754e0e9

+ 3 - 0
package.json

@@ -9,13 +9,16 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "axios": "^1.7.7",
     "copy-webpack-plugin": "^11.0.0",
     "core-js": "^3.8.3",
     "css-loader": "^7.1.2",
     "html-webpack-plugin": "^5.6.0",
+    "js-cookie": "^3.0.5",
     "less": "^4.2.0",
     "less-loader": "^12.2.0",
     "mini-css-extract-plugin": "^2.9.1",
+    "ssestream": "^1.1.0",
     "style-loader": "^4.0.0",
     "vue": "^3.2.13"
   },

+ 46 - 0
pnpm-lock.yaml

@@ -8,6 +8,9 @@ importers:
 
   .:
     dependencies:
+      axios:
+        specifier: ^1.7.7
+        version: 1.7.7
       copy-webpack-plugin:
         specifier: ^11.0.0
         version: 11.0.0(webpack@5.95.0)
@@ -20,6 +23,9 @@ importers:
       html-webpack-plugin:
         specifier: ^5.6.0
         version: 5.6.0(webpack@5.95.0)
+      js-cookie:
+        specifier: ^3.0.5
+        version: 3.0.5
       less:
         specifier: ^4.2.0
         version: 4.2.0
@@ -29,6 +35,9 @@ importers:
       mini-css-extract-plugin:
         specifier: ^2.9.1
         version: 2.9.1(webpack@5.95.0)
+      ssestream:
+        specifier: ^1.1.0
+        version: 1.1.0
       style-loader:
         specifier: ^4.0.0
         version: 4.0.0(webpack@5.95.0)
@@ -1236,6 +1245,9 @@ packages:
   aws4@1.13.2:
     resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
 
+  axios@1.7.7:
+    resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
+
   babel-loader@8.4.1:
     resolution: {integrity: sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==}
     engines: {node: '>= 8.9'}
@@ -2227,6 +2239,10 @@ packages:
     resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
     engines: {node: '>= 0.12'}
 
+  form-data@4.0.1:
+    resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
+    engines: {node: '>= 6'}
+
   forwarded@0.2.0:
     resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
     engines: {node: '>= 0.6'}
@@ -2630,6 +2646,10 @@ packages:
   joi@17.13.3:
     resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
 
+  js-cookie@3.0.5:
+    resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+    engines: {node: '>=14'}
+
   js-message@1.0.7:
     resolution: {integrity: sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==}
     engines: {node: '>=0.6.0'}
@@ -3457,6 +3477,9 @@ packages:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
 
+  proxy-from-env@1.1.0:
+    resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
   prr@1.0.1:
     resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
 
@@ -3764,6 +3787,9 @@ packages:
   sprintf-js@1.0.3:
     resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
 
+  ssestream@1.1.0:
+    resolution: {integrity: sha512-UOS3JTuGqGEOH89mfHFwVOJNH2+JX9ebIWuw6WBQXpkVOxbdoY3RMliSHzshL4XVYJJrcul5NkuvDFCzgYu1Lw==}
+
   sshpk@1.18.0:
     resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==}
     engines: {node: '>=0.10.0'}
@@ -5965,6 +5991,14 @@ snapshots:
 
   aws4@1.13.2: {}
 
+  axios@1.7.7:
+    dependencies:
+      follow-redirects: 1.15.9(debug@4.3.7)
+      form-data: 4.0.1
+      proxy-from-env: 1.1.0
+    transitivePeerDependencies:
+      - debug
+
   babel-loader@8.4.1(@babel/core@7.25.8)(webpack@5.95.0):
     dependencies:
       '@babel/core': 7.25.8
@@ -6928,6 +6962,12 @@ snapshots:
       combined-stream: 1.0.8
       mime-types: 2.1.35
 
+  form-data@4.0.1:
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+
   forwarded@0.2.0: {}
 
   fraction.js@4.3.7: {}
@@ -7298,6 +7338,8 @@ snapshots:
       '@sideway/formula': 3.0.1
       '@sideway/pinpoint': 2.0.0
 
+  js-cookie@3.0.5: {}
+
   js-message@1.0.7: {}
 
   js-tokens@4.0.0: {}
@@ -8046,6 +8088,8 @@ snapshots:
       forwarded: 0.2.0
       ipaddr.js: 1.9.1
 
+  proxy-from-env@1.1.0: {}
+
   prr@1.0.1:
     optional: true
 
@@ -8414,6 +8458,8 @@ snapshots:
 
   sprintf-js@1.0.3: {}
 
+  ssestream@1.1.0: {}
+
   sshpk@1.18.0:
     dependencies:
       asn1: 0.2.6

+ 48 - 0
script/reloadServer.js

@@ -0,0 +1,48 @@
+const SSEStream = require('ssestream').default;
+
+
+function ReloadServer(app, compiler) {
+    app.get('/reload', (req, res, next) => {
+        const sseStream = new SSEStream(req);
+        sseStream.pipe(res);
+
+        let closed = false;
+
+        const reloadPlugin = () => {
+            if (!closed) {
+                sseStream.write(
+                    {
+                        event: 'compiled successfully',
+                        data: {
+                            action: '刷新插件和当前页面'
+                        }
+                    },
+                    'utf-8',
+                    (err) => {
+                        if (err) {
+                            console.error(err);
+                        }
+                    },
+                );
+
+                setTimeout(() => {
+                    sseStream.unpipe(res);
+                }, 100);
+            }
+        };
+
+        compiler.hooks.done.tap(
+            'chrome reload plugin',
+            reloadPlugin
+        );
+
+        res.on('close', () => {
+            closed = true;
+            sseStream.unpipe(res);
+        });
+
+        next();
+    });
+}
+
+module.exports = ReloadServer;

+ 43 - 0
src/api/interceptor.js

@@ -0,0 +1,43 @@
+import axios from 'axios'
+import { ElElMessage } from 'element-plus'
+
+import {
+    handleAuthError, handleGeneralError, handleRequestHeader, handleNetworkError
+} from './tool'
+
+axios.interceptors.request.use((config) => {
+    config = handleRequestHeader(config)
+    return config
+})
+
+axios.defaults.baseURL = 'http://www.baidu.com';
+
+axios.interceptors.response.use(
+    async (response) => {
+      const res = response.data;
+      handleAuthError(response.data.error)
+      handleGeneralError(response.data.error, response.data.errmsg)
+      // if the custom code is not 0, it is judged as an error.
+      if (res.code !== 0) {
+        /* ElMessage.error({
+          message: res.msg || 'Error',
+          duration: 5 * 1000,
+        }); */
+        /* if (res.code === 1008) {
+          //const userStore = useUserStore();
+          错误吗来咯
+        } */
+
+        return Promise.reject(new Error(res.msg || 'Error'));
+      }
+      
+      return res;
+    },
+    (error) => {
+      ElMessage.error({
+        message: error.msg || 'Request Error',
+        duration: 5 * 1000,
+      });
+      return Promise.reject(error);
+    }
+  );

+ 87 - 0
src/api/tool.js

@@ -0,0 +1,87 @@
+import { getStorage } from "@/util/storege"
+import { ElMessage } from 'element-plus'
+const handleRequestHeader = (config) => {
+    config.header['token'] = getStorage('token') || ''
+    return config
+}
+
+
+const handleNetworkError = (errStatus) => {
+    let errMessage = '未知错误'
+    if (errStatus) {
+        switch (errStatus) {
+            case 400:
+                errMessage = '错误的请求'
+                break
+            case 401:
+                errMessage = '未授权,请重新登录'
+                break
+            case 403:
+                errMessage = '拒绝访问'
+                break
+            case 404:
+                errMessage = '请求错误,未找到该资源'
+                break
+            case 405:
+                errMessage = '请求方法未允许'
+                break
+            case 408:
+                errMessage = '请求超时'
+                break
+            case 500:
+                errMessage = '服务器端出错'
+                break
+            case 501:
+                errMessage = '网络未实现'
+                break
+            case 502:
+                errMessage = '网络错误'
+                break
+            case 503:
+                errMessage = '服务不可用'
+                break
+            case 504:
+                errMessage = '网络超时'
+                break
+            case 505:
+                errMessage = 'http版本不支持该请求'
+                break
+            default:
+                errMessage = `其他连接错误 --${errStatus}`
+        }
+    } else {
+        errMessage = `无法连接到服务器!`
+    }
+
+    ElMessage.error(errMessage)
+}
+
+
+const handleAuthError = (errno) => {
+    const authErrMap = {
+        '10086': '登录失效,需要重新登录', // token 失效
+    }
+
+    if (authErrMap.hasOwnProperty(errno)) {
+        ElMessage.error(authErrMap[errno])
+        // 授权错误,登出账户
+        // logout()
+        return false
+    }
+
+    return true
+}
+
+
+
+const handleGeneralError = (errno, errmsg) => {
+    if (err.errno !== '0') {
+        ElMessage.error(err.errmsg)
+        return false
+    }
+
+    return true
+}
+
+
+export { handleAuthError, handleGeneralError, handleRequestHeader, handleNetworkError }

BIN
src/assets/img/collection-task.png


+ 98 - 14
src/content/App.vue

@@ -1,6 +1,6 @@
 <template>
   <div data-root="true" class="root1">
-    <el-button type="primary" @click="handleOpen">点我点我2</el-button>
+    <el-button type="primary" @click="handleOpen">点我点我444</el-button>
 
     <el-dialog
       v-model="dialogVisible"
@@ -8,7 +8,7 @@
       width="30%"
       :before-close="handleClose"
     >
-      <span>我纯纯是一个测试{{counter}}</span>
+      <span>我纯纯是一个测试{{ counter }}</span>
       <template #footer>
         <span class="dialog-footer">
           <el-button @click="dialogVisible = false">取消</el-button>
@@ -22,19 +22,103 @@
 </template>
 
 <script setup>
-import {ref} from "vue"
-const counter = ref(0)
+import { onMounted, ref } from "vue";
+import {
+  createCollectElement,
+  isTikTokWeb,
+  createDyElement,
+  throttle
+} from "@/util/index";
+
+const counter = ref(0);
+const dialogVisible = ref(false);
+
+const handleOpen = () => {
+  dialogVisible.value = true;
+};
+
+onMounted(() => {
+  setTimeout(() => {
+    if (isTikTokWeb(location.href)) {
+      observeTitok()
+      createDyElement(() => {
+        openDialog()
+      });
+    } else {
+
+      createCollectElement(() => {
+         openDialog()
+      });
+    }
+  }, 2000);
+});
+
+//节流函数
+const observeTitok = () => {
+  const throttledLogScroll = throttle(createDyElement, 200);
+   const callback = (mutationsList) => {
+        for (let mutation of mutationsList) {
+          console.log(mutation)
+          throttledLogScroll(()=>{
+            openDialog()
+          })
+        }
+      };
+      const observer = new MutationObserver(callback);
+
+      // 配置观察选项
+      const config = {
+        attributes: true,
+        attributeOldValue: true,
+        attributeFilter: ["style"],
+      };
+
+      const targetNode =
+        document.getElementById("slidelist").firstChild.firstChild;
+
+      observer.observe(targetNode, config);
+};
+
+
+//点击弹窗
+const openDialog = () => {
+  dialogVisible.value = true;
+};
 </script>
 
-<style scoped>
-.root1 {
-  position: fixed;
-  top: 68px;
-  right: 36px;
-  z-index: 1000;
-}
-.big-text {
-  font-size: 50px;
-  font-weight: bold;
+<style scoped lang="less">
+.zt-video__content {
+  .zt-video__content-btn {
+    display: block;
+    display: inline-flex;
+    justify-content: center;
+    align-items: center;
+    line-height: 1;
+    height: 28px;
+    white-space: nowrap;
+    cursor: pointer;
+    background: #e6a23c;
+    color: #fff;
+    padding: 0 20px 0 15px;
+    border-radius: 5px;
+    text-align: center;
+    box-sizing: border-box;
+    outline: none;
+    position: absolute;
+    right: 20px;
+    top: 20px;
+    z-index: 9999;
+    &::after {
+      /* position:absolute;
+      right: 15px;
+      top: 5px; */
+      display: block;
+      content: "";
+      width: 20px;
+      height: 20px;
+      transform: translateX(5px);
+      background: url("../assets/logo.png") no-repeat 0 0 / 100% 100%;
+    }
+  }
 }
 </style>

+ 1 - 1
src/content/index.js

@@ -18,7 +18,7 @@ import App from "./App.vue";
    styleEl.setAttribute("rel", "stylesheet");
    let url = chrome.runtime.getURL(cssList[i]);
    styleEl.setAttribute("href", url);
-   console.log(styleEl,'styleElstyleElstyleEl')
+   
    shadowDOM.appendChild(styleEl);
  }
   shadowDOM.appendChild(root);

+ 102 - 0
src/util/index.js

@@ -0,0 +1,102 @@
+const createCollectElement = (fun) => {
+    //找到页面上的所有video节点 然后修改样式插入按钮
+    const videoDoms = Array.from(document.querySelectorAll("video"));
+    console.log(videoDoms, '我是视屏')
+    videoDoms.forEach((ele) => {
+
+        if (ele.dataset.status && "sussess" === ele.dataset.status) return;
+        const parNode = ele.parentNode;
+        if ("body" === parNode.tagName.toLowerCase()) return;
+        if (parNode.tagName.toLowerCase() === "div" && parNode.id === 'app') return;
+        console.log(parNode, 'parNodeparNodeparNode')
+        parNode.style.position = "relative";
+        parNode.classList.add("zt-video__content");
+        const collectBtn = document.createElement('div');
+        collectBtn.classList.add('zt-video__content-btn')
+        collectBtn.innerText = '点我采集'
+        collectBtn.dataset.url = ele.src;
+        parNode.appendChild(collectBtn);
+        ele.dataset.status = 'sussess';
+        collectBtn.addEventListener('click', (e) => {
+            e.stopPropagation();
+            e.preventDefault();
+            fun({
+                url: e.target.dataset.url
+            })
+        })
+    })
+
+
+}
+
+
+const createDyElement = (fun) => {
+    const xg = Array.from(document.querySelectorAll(".xg-video-container"));
+    xg.forEach(e => {
+        e.classList.add('zt-video__content');
+
+        let videoSource;
+        //先去找source 没有找到再去找video
+        videoSource = Array.from(e.getElementsByTagName("source")).find((e) =>
+            e.src.startsWith("//www.douyin.com/")
+        );
+        //
+        if (!videoSource) {
+            videoSource = Array.from(e.getElementsByTagName("video")).find((e) =>
+                e.src.includes("//www.douyin.com")
+            );
+        }
+        console.log(Array.from(e.getElementsByTagName("video")), Array.from(e.getElementsByTagName("source")), 'kkkkkkk')
+
+        console.log(e, 'eeee', videoSource)
+
+        const url = videoSource?.src || ""
+        console.log(url)
+        const source = location.href;
+        const btnElement = Array.from(e.querySelectorAll(".zt-video__content-btn"));
+        if (btnElement.length > 0 || e.dataset.status == "success") return;
+
+        const collectBtn = document.createElement("div");
+        collectBtn.classList.add('zt-video__content-btn')
+        collectBtn.innerText = '点我采集';
+        collectBtn.addEventListener('click', (c) => {
+            c.stopPropagation();
+            c.preventDefault();
+            fun({
+                url: url,
+                source: source
+            })
+        })
+        e.appendChild(collectBtn);
+        e.dataset.status = "success"
+    })
+
+
+
+}
+
+const isTikTokWeb = (url = '') => {
+    const regex = /^https:\/\/www\.douyin\.com/;
+    console.log(url, regex.test(window.location.href || url), '为什么')
+    return regex.test(window.location.href || url)
+}
+
+function throttle(func, wait) {
+    let timeout = null;
+    let context, args;
+
+    return function(...throttledArgs) {
+        context = this;
+        args = throttledArgs;
+
+        if (!timeout) {
+            timeout = setTimeout(() => {
+                func.apply(context, args);
+                timeout = null;
+            }, wait);
+        }
+    };
+}
+
+
+export { createCollectElement, isTikTokWeb, createDyElement,throttle }

+ 92 - 0
src/util/message.js

@@ -0,0 +1,92 @@
+/* eslint-disable no-undef */
+// 统一消息格式
+class ChromeMessage {
+    constructor(msg, params) {
+        this.msg = msg;
+        this.params = params;
+    }
+}
+// 获取函数的形参个数
+function getFuncParameters(func) {
+    if (typeof func === 'function') {
+        const match = /[^(]+\(([^)]*)?\)/gm.exec(Function.prototype.toString.call(func));
+        if (match[1]) {
+            const args = match[1].replace(/[^,\w]*/g, '').split(',');
+            return args.length;
+        }
+    }
+
+    return 0;
+}
+
+// 监听回调函数
+const listeners = {};
+
+// 事件分发
+function dispatchEvent(request, sendResponse) {
+    const { msg } = request;
+    let callBack;
+
+    Object.keys(listeners).forEach((key) => {
+        if (key === msg) {
+            callBack = listeners[key];
+        }
+    });
+
+    if (callBack) {
+        const paramSize = getFuncParameters(callBack);
+
+        callBack(request, sendResponse);
+
+        return paramSize === 2;
+    }
+
+    return false;
+}
+
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+    const success = dispatchEvent(request, sendResponse);
+
+    if (!success) {
+        sendResponse(new ChromeMessage('Default Response'));
+    }
+
+    return true;
+});
+
+// content scripts 发送和监听消息
+class ContentClient {
+    listen(msg, callBack) {
+        listeners[msg] = callBack;
+    }
+
+    seedMessage(message) {
+        return new Promise((resolve) => {
+            chrome.runtime.sendMessage(message, (res) => {
+                resolve(res);
+            });
+        });
+    }
+}
+
+// background 发送和监听消息
+class BackgroundClient {
+    listen(msg, callBack) {
+        listeners[msg] = callBack;
+    }
+
+    seedMessage(message) {
+        return new Promise((resolve) => {
+            chrome.tabs.query({ active: true }, (tabs) => {
+                chrome.tabs.sendMessage(tabs[0].id, message, (response) => {
+                    resolve(response);
+                });
+            });
+        });
+    }
+}
+
+const contentClient = new ContentClient();
+const backgroundClient = new BackgroundClient();
+
+export { contentClient, backgroundClient, ChromeMessage };

+ 18 - 0
src/util/storege.js

@@ -0,0 +1,18 @@
+/* eslint-disable no-undef */
+function setStorage(key, value) {
+    return new Promise((resolve) => {
+        chrome.storage.sync.set({ [key]: value }, () => {
+            resolve(value);
+        });
+    });
+}
+
+function getStorage(key) {
+    return new Promise((resolve) => {
+        chrome.storage.sync.get(key, (result) => {
+            resolve(result[key]);
+        });
+    });
+}
+
+export { setStorage, getStorage };

+ 17 - 20
vue.config.js

@@ -37,7 +37,7 @@ const plugins = [
         extensionPage: 'popup',
       },
   }), */
-  new  ReplaceCssPlugin(),
+  new ReplaceCssPlugin(),
   AutoImport({
     include: [
       /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
@@ -86,7 +86,7 @@ chromeName.forEach((name) => {
 });
 
 module.exports = {
-  
+
   pages,
   // 生产环境是否生成 sourceMap 文件
   productionSourceMap: false,
@@ -102,16 +102,16 @@ module.exports = {
       content: "./src/content/index.js",
       background: "./src/background/index.js",
     },
-    resolve:{
-      alias:{
+    resolve: {
+      alias: {
         '@': path.resolve('src'),
       }
     },
-    
+
     output: {
       filename: "js/[name].js",
     },
-    
+
     plugins,
     optimization: {
       splitChunks: {
@@ -143,16 +143,16 @@ module.exports = {
     config.module.rule('less')
       .test(/\.less$/)
       .use('style-loader')
-        .loader('style-loader')
+      .loader('style-loader')
       .end()
       .use('css-loader')
-        .loader('css-loader')
+      .loader('css-loader')
       .end()
       .use('postcss-loader')
       .loader('postcss-loader')
-    .end()
+      .end()
       .use('less-loader')
-        .loader('less-loader')
+      .loader('less-loader')
       .end()
   },
   css: {
@@ -160,15 +160,12 @@ module.exports = {
       filename: "css/[name].css",
     },
   },
-  /* devServer:{
-
-    host: '0.0.0.0', // 让你的服务器可以被外部访问
-    port: 8080, // 端口
-    https: false, // 不使用 HTTP 提供服务
-    hot: true, 
-    onBeforeSetupMiddleware: function(app, server,compiler) {
-      // 在服务器启动之前执行的函数
-      console.log(compiler,'compilercompilercompiler')
-    },
+  /* devServer: {
+    lazy: false,
+    // 将 bundle 写到磁盘而不是内存
+    writeToDisk: true,
+    before(app, serve, compiler) {
+      reloadServer(app, compiler);
+    }
   } */
 };