├── .gitignore ├── src ├── main.js ├── utils │ ├── DomHook │ │ ├── index.js │ │ └── DomHook.js │ ├── index.js │ └── Notice │ │ └── Notice.css ├── pages │ ├── www │ │ ├── packages │ │ │ ├── TestA │ │ │ │ ├── views │ │ │ │ │ ├── index.js │ │ │ │ │ └── TestA.vue │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── index.js │ ├── buff │ │ ├── packages │ │ │ ├── CompareList │ │ │ │ ├── views │ │ │ │ │ ├── index.js │ │ │ │ │ └── CompareList.vue │ │ │ │ ├── styles │ │ │ │ │ └── index.css │ │ │ │ ├── index.js │ │ │ │ └── components │ │ │ │ │ └── SkinInfo.vue │ │ │ ├── AddButton │ │ │ │ ├── apis │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── ShowTime │ │ │ │ ├── styles │ │ │ │ │ └── index.css │ │ │ │ └── index.js │ │ │ ├── Compare3D │ │ │ │ ├── styles │ │ │ │ │ └── index.css │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── Inspect3D │ │ │ │ └── index.js │ │ └── index.js │ ├── spect │ │ ├── packages │ │ │ ├── Compare2D │ │ │ │ ├── views │ │ │ │ │ ├── index.js │ │ │ │ │ ├── Compare.vue │ │ │ │ │ └── Diff.vue │ │ │ │ ├── styles │ │ │ │ │ └── index.css │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── index.js │ └── index.js ├── router.js └── global │ └── styles │ └── index.css ├── index.html ├── jsconfig.json ├── package.json ├── webpack.config.js ├── header.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .DS_Store 3 | node_modules 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import {initRouter} from "./router.js" 2 | initRouter(); 3 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | -------------------------------------------------------------------------------- /src/utils/DomHook/index.js: -------------------------------------------------------------------------------- 1 | import {DomHook} from "./DomHook" 2 | 3 | export { 4 | DomHook 5 | } -------------------------------------------------------------------------------- /src/pages/www/packages/TestA/views/index.js: -------------------------------------------------------------------------------- 1 | import TestA from "./TestA.vue" 2 | export { 3 | TestA 4 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/CompareList/views/index.js: -------------------------------------------------------------------------------- 1 | import CompareList from "./CompareList.vue" 2 | 3 | export { 4 | CompareList 5 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./*"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/pages/www/packages/index.js: -------------------------------------------------------------------------------- 1 | import TestA from "./TestA" 2 | function initPkg() { 3 | TestA.init(); 4 | } 5 | 6 | export { 7 | initPkg 8 | } -------------------------------------------------------------------------------- /src/pages/spect/packages/Compare2D/views/index.js: -------------------------------------------------------------------------------- 1 | import Compare from "./Compare.vue" 2 | import Diff from "./Diff.vue" 3 | 4 | export { 5 | Compare, 6 | Diff 7 | } -------------------------------------------------------------------------------- /src/pages/spect/packages/index.js: -------------------------------------------------------------------------------- 1 | import Compare2D from "./Compare2D" 2 | 3 | function initPkg() { 4 | Compare2D.init(); 5 | } 6 | 7 | export { 8 | initPkg 9 | } -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | // import www from "./www" 2 | import buff from "./buff" 3 | import spect from "./spect" 4 | export default { 5 | // www 6 | buff, 7 | spect 8 | } -------------------------------------------------------------------------------- /src/pages/www/index.js: -------------------------------------------------------------------------------- 1 | import {initPkg} from "./packages" 2 | 3 | function beforeInit() { 4 | 5 | } 6 | 7 | function init() { 8 | beforeInit(); 9 | initPkg(); 10 | } 11 | 12 | export default { 13 | init 14 | } -------------------------------------------------------------------------------- /src/pages/buff/index.js: -------------------------------------------------------------------------------- 1 | import {initPkg} from "./packages" 2 | 3 | 4 | function beforeInit() { 5 | } 6 | 7 | function init() { 8 | beforeInit(); 9 | initPkg(); 10 | } 11 | 12 | export default { 13 | init 14 | } -------------------------------------------------------------------------------- /src/pages/www/packages/TestA/index.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue" 2 | import {TestA} from "./views" 3 | 4 | function init() { 5 | createApp(TestA).mount("#asset_tag-filter"); 6 | } 7 | 8 | export default { 9 | init 10 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/AddButton/apis/index.js: -------------------------------------------------------------------------------- 1 | export function getAssetIdInfo(assetid) { 2 | return fetch('https://buff.163.com/api/market/csgo_inspect_3d?assetid=' + assetid,{ 3 | method: 'GET', 4 | mode: 'no-cors', 5 | // cache: 'default', 6 | // credentials: 'include', 7 | }) 8 | } -------------------------------------------------------------------------------- /src/pages/spect/index.js: -------------------------------------------------------------------------------- 1 | import {initPkg} from "./packages" 2 | 3 | function beforeInit() { 4 | // 清除页面默认数据 5 | document.title = "CSGO饰品对比 - 2D"; 6 | document.body.innerHTML = ""; 7 | } 8 | 9 | function init() { 10 | beforeInit(); 11 | initPkg(); 12 | } 13 | 14 | export default { 15 | init 16 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/ShowTime/styles/index.css: -------------------------------------------------------------------------------- 1 | .t_Left { 2 | position: relative; 3 | } 4 | 5 | .ex-time { 6 | position: absolute; 7 | margin-top: -35px; 8 | width: 100%; 9 | text-align: right; 10 | color: gray; 11 | font-size: 12px; 12 | } 13 | 14 | .ex-time span{ 15 | margin-right: 10px; 16 | } -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import pages from "./pages" 2 | 3 | function initRouter() { 4 | // 根据需求判断location对象的值,来选择使用哪个page 5 | if (location.href.indexOf("buff") !== -1) { 6 | pages.buff.init(); 7 | } 8 | 9 | if (location.href.indexOf("spect") !== -1) { 10 | if (location.href.indexOf("compare2d") !== -1) { 11 | pages.spect.init(); 12 | } 13 | } 14 | } 15 | export { 16 | initRouter 17 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/CompareList/styles/index.css: -------------------------------------------------------------------------------- 1 | .compare__wrap { 2 | position: absolute; 3 | z-index: 1015; 4 | } 5 | 6 | .compare__mask { 7 | width: 100%; 8 | height: 100%; 9 | background-color: rgba(0, 0, 0, 0.6); 10 | } 11 | 12 | .compare__dialog { 13 | padding: 10px; 14 | width: 800px; 15 | height: 500px; 16 | background-color: white; 17 | position: fixed; 18 | left: 0; 19 | right: 0; 20 | top: 250px; 21 | margin: auto; 22 | box-shadow: 0px 0px 10px 0px #888888; 23 | border-radius: 10px; 24 | } -------------------------------------------------------------------------------- /src/pages/www/packages/TestA/views/TestA.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 25 | 26 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "webpack-dev-server", 5 | "build": "webpack --env.prod", 6 | "start": "webpack --watch" 7 | }, 8 | "dependencies": { 9 | "@vueform/slider": "^1.0.5", 10 | "style-loader": "^1.2.1", 11 | "vue": "^3.0.11" 12 | }, 13 | "devDependencies": { 14 | "@vue/compiler-sfc": "^3.0.11", 15 | "css-loader": "^3.4.2", 16 | "file-loader": "^6.2.0", 17 | "mini-css-extract-plugin": "^0.9.0", 18 | "url-loader": "^4.1.1", 19 | "vue-loader": "^16.2.0", 20 | "webpack": "^4.42.1", 21 | "webpack-cli": "^3.3.11", 22 | "webpack-dev-server": "^3.11.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/buff/packages/Compare3D/styles/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | .compare3d__iframe { 7 | width: 100%; 8 | height: 100vh; 9 | border: 0px solid white; 10 | } 11 | 12 | .compare3d__tips { 13 | position: fixed; 14 | color: rgba(255, 255, 255, 0.6); 15 | font-size: 14px; 16 | top: 70px; 17 | left: 10px; 18 | cursor: default; 19 | user-select: none; 20 | } 21 | 22 | .compare3d__watermark { 23 | position: fixed; 24 | right: 10px; 25 | top: 70px; 26 | color: rgba(255, 255, 255, 0.6); 27 | z-index: 1; 28 | cursor: pointer; 29 | font-size: 24px; 30 | user-select: none; 31 | } -------------------------------------------------------------------------------- /src/utils/DomHook/DomHook.js: -------------------------------------------------------------------------------- 1 | class DomHook { 2 | constructor(selector, isSubtree, callback) { 3 | this.selector = selector; 4 | this.isSubtree = isSubtree; 5 | let targetNode = document.querySelector(this.selector); 6 | if (targetNode == null) { 7 | return; 8 | } 9 | let observer = new MutationObserver(function(mutations) { 10 | callback(mutations); 11 | }); 12 | this.observer = observer; 13 | this.observer.observe(targetNode, { attributes: true, childList: true, subtree: this.isSubtree }); 14 | } 15 | closeHook() { 16 | this.observer.disconnect(); 17 | } 18 | } 19 | 20 | export { 21 | DomHook 22 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/index.js: -------------------------------------------------------------------------------- 1 | import AddButton from "./AddButton" 2 | import CompareList from "./CompareList" 3 | import Compare3D from "./Compare3D" 4 | import Inspect3D from "./Inspect3D" 5 | import ShowTime from "./ShowTime" 6 | 7 | function initPkg() { 8 | if (location.href.indexOf("goods") !== -1) { 9 | let timer = setInterval(() => { 10 | if (document.getElementsByClassName("j_shoptip_handler").length > 0) { 11 | if (unsafeWindow.hookList.length > 0) { 12 | clearInterval(timer); 13 | initMarket(); 14 | } else { 15 | // 调用网页自身的请求实现拦截 16 | let marketShow = new unsafeWindow.marketShow(); 17 | marketShow.init(); 18 | } 19 | } 20 | }, 300); 21 | } else if (location.href.indexOf("compare3d") !== -1) { 22 | initCompare3D(); 23 | } else if (location.href.indexOf("3d_inspect/cs2") !== -1) { 24 | initInspect3D(); 25 | } 26 | } 27 | 28 | function initMarket() { 29 | AddButton.init(); 30 | CompareList.init(); 31 | ShowTime.init(); 32 | } 33 | 34 | function initCompare3D() { 35 | Compare3D.init(); 36 | } 37 | 38 | function initInspect3D() { 39 | Inspect3D.init(); 40 | } 41 | 42 | export { 43 | initPkg 44 | } -------------------------------------------------------------------------------- /src/global/styles/index.css: -------------------------------------------------------------------------------- 1 | .el-button { 2 | display: inline-block; 3 | line-height: 1; 4 | white-space: nowrap; 5 | cursor: pointer; 6 | background: #fff; 7 | border: 1px solid #dcdfe6; 8 | color: #606266; 9 | -webkit-appearance: none; 10 | text-align: center; 11 | box-sizing: border-box; 12 | outline: none; 13 | margin: 0; 14 | transition: .1s; 15 | font-weight: 500; 16 | -moz-user-select: none; 17 | -webkit-user-select: none; 18 | -ms-user-select: none; 19 | padding: 12px 20px; 20 | font-size: 14px; 21 | border-radius: 4px; 22 | } 23 | 24 | .el-button--primary { 25 | color: #fff; 26 | background-color: #409eff; 27 | border-color: #409eff; 28 | } 29 | 30 | .el-button--primary:hover { 31 | background: #66b1ff; 32 | border-color: #66b1ff; 33 | color: #fff; 34 | } 35 | 36 | .el-button.is-disabled, .el-button.is-disabled:focus, .el-button.is-disabled:hover { 37 | color: #c0c4cc; 38 | cursor: not-allowed; 39 | background-image: none; 40 | background-color: #fff; 41 | border-color: #ebeef5; 42 | } 43 | 44 | .el-button--success { 45 | color: #fff; 46 | background-color: #67c23a; 47 | border-color: #67c23a; 48 | } 49 | .el-button--success:hover { 50 | background: #85ce61; 51 | border-color: #85ce61; 52 | color: #fff; 53 | } 54 | 55 | .el-button--danger { 56 | color: #fff; 57 | background-color: #f56c6c; 58 | border-color: #f56c6c; 59 | } 60 | 61 | .el-button--danger:hover { 62 | background: #f78989; 63 | border-color: #f78989; 64 | color: #fff; 65 | } 66 | 67 | .el-button--warning { 68 | color: #fff; 69 | background-color: #e6a23c; 70 | border-color: #e6a23c; 71 | } 72 | 73 | .el-button--warning:focus, .el-button--warning:hover { 74 | background: #ebb563; 75 | border-color: #ebb563; 76 | color: #fff; 77 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/CompareList/index.js: -------------------------------------------------------------------------------- 1 | import {CompareList} from "./views" 2 | import {createApp} from "vue" 3 | import "./styles/index.css" 4 | 5 | function init() { 6 | initDom(); 7 | initFunc(); 8 | // createApp(CompareList).mount("#j_mybackpack"); 9 | } 10 | 11 | function initDom() { 12 | let a = document.createElement("li"); 13 | a.className = "j_drop-handler"; 14 | a.id = "comparelist" 15 | a.innerHTML = `对比列表 ` 16 | let b = document.querySelector(".nav > ul"); 17 | b.appendChild(a); 18 | } 19 | 20 | function initFunc() { 21 | let body = document.body; 22 | document.getElementById("comparelist").addEventListener("click", () => { 23 | onClickCloseCompareList(); 24 | let a = document.createElement("div"); 25 | a.className = "compare__wrap"; 26 | a.style.width = `${body.offsetWidth}px`; 27 | a.style.height = `${body.offsetHeight}px`; 28 | a.innerHTML = ` 29 |
30 | 31 |
32 |
33 | ` 34 | body.insertBefore(a, body.childNodes[0]); 35 | 36 | document.getElementsByClassName("compare__mask")[0].addEventListener("click", () => { 37 | onClickCloseCompareList(); 38 | }); 39 | let app = createApp(CompareList); 40 | app.mount("#compare__app"); 41 | 42 | // createApp(CompareList).mount("#compare__app"); 43 | // createApp(CompareList).mount("#compare__app"); 44 | // console.log(JSON.parse(GM_getValue("CompareList") || "[]")) 45 | }); 46 | } 47 | 48 | function onClickCloseCompareList() { 49 | let lastDom = document.getElementsByClassName("compare__wrap")[0]; 50 | if (lastDom) { 51 | lastDom.remove(); 52 | } 53 | } 54 | 55 | export default { 56 | init 57 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { VueLoaderPlugin } = require('vue-loader') 3 | const { resolve } = require('path') 4 | const webpack = require('webpack') 5 | const fs = require("fs") 6 | // const MiniCssExtractPlugin = require('mini-css-extract-plugin') 7 | 8 | module.exports = (env = {}) => ({ 9 | mode: env.prod ? 'production' : 'development', 10 | devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map', 11 | entry: path.resolve(__dirname, './src/main.js'), 12 | output: { 13 | path: path.resolve(__dirname, './dist'), 14 | publicPath: '/dist/' 15 | }, 16 | resolve: { 17 | alias: { 18 | // this isn't technically needed, since the default `vue` entry for bundlers 19 | // is a simple `export * from '@vue/runtime-dom`. However having this 20 | // extra re-export somehow causes webpack to always invalidate the module 21 | // on the first HMR update and causes the page to reload. 22 | 'vue': '@vue/runtime-dom', 23 | '@': resolve('./') 24 | } 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.vue$/, 30 | use: 'vue-loader' 31 | }, 32 | { 33 | test: /\.png$/, 34 | use: { 35 | loader: 'url-loader', 36 | options: { limit: 8192 } 37 | } 38 | }, 39 | { 40 | test: /\.css$/, 41 | use: ['style-loader', 'css-loader'] 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new VueLoaderPlugin(), 47 | // new MiniCssExtractPlugin({ 48 | // filename: '[name].css' 49 | // }) 50 | new webpack.BannerPlugin({ 51 | entryOnly: true, // 是否仅在入口包中输出 banner 信息 52 | raw: true, 53 | banner: () => { 54 | return String(fs.readFileSync("./header.js")) 55 | } 56 | }), 57 | ], 58 | devServer: { 59 | inline: true, 60 | hot: true, 61 | stats: 'minimal', 62 | contentBase: __dirname, 63 | overlay: true 64 | }, 65 | externals: { 66 | vue: 'Vue', 67 | '@vueform/slider': 'VueformSlider', 68 | }, 69 | optimization: { 70 | minimize: false 71 | } 72 | }) 73 | -------------------------------------------------------------------------------- /header.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name CSGO饰品2D/3D对比 3 | // @namespace https://github.com/qianjiachun 4 | // @version 2024.05.31.02 5 | // @description 使用图像处理技术对CSGO饰品网站上的皮肤进行对比,可以快速分辨出饰品细微的差异,不用再手动来回切换对比了。同时显示饰品上架时间和修改时间。 6 | // @author 小淳 7 | // @match *://buff.163.com/goods* 8 | // @match *://buff.163.com/3d_inspect/cs2?compare=true* 9 | // @match *://spect.fp.ps.netease.com/* 10 | // @match *://buff.163.com/compare3d* 11 | // @grant GM_setValue 12 | // @grant GM_getValue 13 | // @grant GM_deleteValue 14 | // @grant GM_listValues 15 | // @grant GM_setClipboard 16 | // @grant unsafeWindow 17 | // @run-at document-start 18 | // @require https://fastly.jsdelivr.net/npm/notice.js@0.4.0/dist/notice.js 19 | // @require https://lib.baomitu.com/vue/3.0.11/vue.global.prod.js 20 | // @require https://fastly.jsdelivr.net/npm/comparison-slider@1.1.0/dist/comparison-slider.min.js 21 | // @require https://fastly.jsdelivr.net/npm/canvas-compare@3.0.0/src/canvas-compare.min.js 22 | // @require https://fastly.jsdelivr.net/npm/@vueform/slider@2.0.4/dist/slider.global.js 23 | // ==/UserScript== 24 | 25 | unsafeWindow.hookList = []; 26 | unsafeWindow.hookCallback = function (xhr) { 27 | // console.log(xhr); 28 | } 29 | function addXMLRequestCallback(callback){ 30 | var oldSend, i; 31 | if( XMLHttpRequest.callbacks ) { 32 | XMLHttpRequest.callbacks.push( callback ); 33 | } else { 34 | XMLHttpRequest.callbacks = [callback]; 35 | oldSend = XMLHttpRequest.prototype.send; 36 | XMLHttpRequest.prototype.send = function(){ 37 | for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) { 38 | XMLHttpRequest.callbacks[i]( this ); 39 | } 40 | oldSend.apply(this, arguments); 41 | } 42 | } 43 | } 44 | if (location.href.indexOf("goods") !== -1) { 45 | addXMLRequestCallback( function( xhr ) { 46 | xhr.addEventListener("load", function(){ 47 | if ( xhr.readyState == 4 && xhr.status == 200 ) { 48 | unsafeWindow.hookList.push(xhr); 49 | unsafeWindow.hookCallback(xhr); 50 | } 51 | }); 52 | }); 53 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/ShowTime/index.js: -------------------------------------------------------------------------------- 1 | import { dateFormat } from "@/src/utils"; 2 | import "./styles/index.css" 3 | 4 | function init() { 5 | let hasValidRes = false; 6 | let timer = setInterval(() => { 7 | for (let i = unsafeWindow.hookList.length - 1; i >= 0; i--) { 8 | let item = unsafeWindow.hookList[i]; 9 | if (item.responseURL.includes("goods/sell_order")) { 10 | clearInterval(timer); 11 | hasValidRes = true; 12 | break; 13 | } 14 | } 15 | if (!hasValidRes) { 16 | let marketShow = new unsafeWindow.marketShow(); 17 | marketShow.init(); 18 | return; 19 | } 20 | for (let i = unsafeWindow.hookList.length - 1; i >= 0; i--) { 21 | let item = unsafeWindow.hookList[i]; 22 | if (item.responseURL.includes("goods/sell_order")) { 23 | let data = JSON.parse(item.responseText); 24 | insertDom(data.data.items) 25 | break; 26 | } 27 | } 28 | unsafeWindow.hookCallback = function (xhr) { 29 | if (xhr.responseURL.includes("goods/sell_order")) { 30 | let data = JSON.parse(xhr.responseText); 31 | insertDom(data.data.items) 32 | } 33 | } 34 | }, 500); 35 | } 36 | 37 | function insertDom(items) { 38 | let sellings = document.querySelectorAll(".list_tb_csgo .selling"); 39 | 40 | for (let i = 0; i < sellings.length; i++) { 41 | let itemData = items[i]; 42 | let id = sellings[i].id; 43 | let t_Lefts = sellings[i].querySelectorAll(".t_Left"); 44 | let target = t_Lefts[t_Lefts.length - 1]; 45 | if (!target) continue; 46 | if (!id.includes(itemData.id)) continue; 47 | let div = document.createElement("div"); 48 | div.className = "ex-time"; 49 | div.innerHTML = `${itemData.created_at ? "上架: " + dateFormat("yyyy-MM-dd hh:mm:ss",new Date(itemData.created_at * 1000)) + "" : ""} 50 | ${itemData.updated_at && itemData.created_at !== itemData.updated_at ? "
更新: " + dateFormat("yyyy-MM-dd hh:mm:ss",new Date(itemData.updated_at * 1000)) + "" : ""}`; 51 | target.insertBefore(div, target.childNodes[0]); 52 | } 53 | } 54 | 55 | export default { 56 | init 57 | } -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | // 公共 2 | import "./Notice/Notice.css" 3 | 4 | export function getStrMiddle(str, before, after) { 5 | // 取中间文本 6 | let m = str.match(new RegExp(before + '(.*?)' + after)); 7 | return m ? m[1] : false; 8 | } 9 | 10 | export function showMessage(msg, type) { 11 | // type: success[green] error[red] warning[orange] info[blue] 12 | new NoticeJs({ 13 | text: msg, 14 | type: type, 15 | position: 'bottomRight', 16 | }).show(); 17 | } 18 | 19 | export function getBase64(imgUrl, callback) { 20 | window.URL = window.URL || window.webkitURL; 21 | var xhr = new XMLHttpRequest(); 22 | xhr.open("get", imgUrl, true); 23 | xhr.responseType = "blob"; 24 | xhr.onload = function () { 25 | if (this.status == 200) { 26 | var blob = this.response; 27 | let oFileReader = new FileReader(); 28 | oFileReader.onloadend = function (e) { 29 | let base64 = e.target.result; 30 | callback(base64); 31 | }; 32 | oFileReader.readAsDataURL(blob); 33 | } 34 | } 35 | xhr.send(); 36 | } 37 | 38 | // 模拟鼠标按住拖动 39 | function createMouseEvent(eventName, ofsx, ofsy) { 40 | let evt = document.createEvent('MouseEvents'); 41 | evt = new MouseEvent(eventName, { 42 | clientX: ofsx, 43 | clientY: ofsy, 44 | bubbles: true 45 | }) 46 | evt.isMessage = true; 47 | return evt 48 | }; 49 | 50 | export function setMouseMove(dom, x, y) { 51 | dom.dispatchEvent(createMouseEvent("mousedown")); 52 | dom.dispatchEvent(createMouseEvent("mousemove", x, y)); 53 | dom.dispatchEvent(createMouseEvent("mouseup")); 54 | } 55 | 56 | // 获取URL参数 57 | export function getQueryString(name) { 58 | let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 59 | let r = window.location.search.substr(1).match(reg); 60 | if (r != null) return unescape(r[2]); return null; 61 | } 62 | 63 | export function dateFormat(fmt, date) { 64 | let o = { 65 | "M+": date.getMonth() + 1, 66 | "d+": date.getDate(), 67 | "h+": date.getHours(), 68 | "m+": date.getMinutes(), 69 | "s+": date.getSeconds(), 70 | "q+": Math.floor((date.getMonth() + 3) / 3), 71 | "S": date.getMilliseconds() 72 | }; 73 | if (/(y+)/.test(fmt)) 74 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); 75 | for (let k in o) 76 | if (new RegExp("(" + k + ")").test(fmt)) 77 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 78 | return fmt; 79 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/CompareList/components/SkinInfo.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | -------------------------------------------------------------------------------- /src/pages/spect/packages/Compare2D/styles/index.css: -------------------------------------------------------------------------------- 1 | *,body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | html, body { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | .pdl-10 { 12 | padding-left: 10px; 13 | } 14 | 15 | .pdl-20 { 16 | padding-left: 20px; 17 | } 18 | 19 | .wrap { 20 | display: flex; 21 | width: 100%; 22 | height: 100%; 23 | } 24 | 25 | .compare2d__watermark { 26 | position: fixed; 27 | right: 10px; 28 | top: 10px; 29 | color: rgba(255, 255, 255, 0.6); 30 | z-index: 1; 31 | cursor: pointer; 32 | font-size: 24px; 33 | user-select: none; 34 | } 35 | 36 | .skin1-info { 37 | position: fixed; 38 | left: 260px; 39 | bottom: 10px; 40 | color: rgba(255, 255, 255, 0.5); 41 | z-index: 1; 42 | cursor: pointer; 43 | font-size: 16px; 44 | } 45 | 46 | .skin2-info { 47 | position: fixed; 48 | right: 10px; 49 | bottom: 10px; 50 | color: rgba(255, 255, 255, 0.5); 51 | z-index: 1; 52 | cursor: pointer; 53 | font-size: 16px; 54 | } 55 | 56 | .menu { 57 | background-color: rgb(29,30,35); 58 | cursor: default; 59 | height: 100%; 60 | flex: 0 0 250px; 61 | color: rgba(255, 255, 255, 0.7); 62 | font-size: 16px; 63 | font-family: "微软雅黑"; 64 | user-select:none; 65 | } 66 | 67 | .view { 68 | width: 100%; 69 | height: 100%; 70 | flex: 1; 71 | } 72 | 73 | .menu__title { 74 | text-align: center; 75 | font-size: 30px; 76 | font-weight: 600; 77 | margin-top: 15px; 78 | margin-bottom: 30px; 79 | color: #f6ca9d; 80 | } 81 | 82 | .sub__view { 83 | margin-bottom: 30px; 84 | } 85 | 86 | .view__title { 87 | font-size: 26px; 88 | font-weight: 600; 89 | margin-bottom: 10px; 90 | color: rgba(255, 255, 255, 0.9); 91 | } 92 | 93 | .view__item { 94 | cursor: pointer; 95 | height: 56px; 96 | line-height: 56px; 97 | } 98 | 99 | .view__item:hover { 100 | background-color: rgb(23,24,28); 101 | } 102 | 103 | .is-active { 104 | background-color: rgb(23,24,28); 105 | color: white; 106 | } 107 | 108 | .texture__title { 109 | font-size: 26px; 110 | font-weight: 600; 111 | margin-bottom: 10px; 112 | color: rgba(255, 255, 255, 0.9); 113 | } 114 | 115 | .texture__item { 116 | cursor: pointer; 117 | height: 56px; 118 | line-height: 56px; 119 | } 120 | 121 | .texture__item:hover { 122 | background-color: rgb(23,24,28); 123 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CSGO饰品对比 2 | 3 | ### 发布地址(Release & Install) 4 | [https://greasyfork.org/zh-CN/scripts/427277](https://greasyfork.org/zh-CN/scripts/427277) 5 | 6 | ### 介绍 7 | 1. 使用图像处理技术对CSGO饰品网站上的皮肤进行对比,可以快速分辨出饰品细微的差异,不用再手动来回切换对比了 8 | 2. 支持复制检视链接(同手机端分享),可粘贴至部分社区服(如ONET4P社区服)进行检视 9 | 3. 目前只适用于网易buff,其余平台后续版本可能会加上 10 | 4. 基于Webpack + Vue3.0 + TamperMonkey开发 11 | 12 | 13 | ### 安装 & 使用方法 14 | 1. [安装油猴脚本(官方网站)](https://www.tampermonkey.net/),或者[安装油猴脚本(谷歌扩展)](https://www.crx4chrome.com/crx/1429/),选择【Download crx file from crx4chrome】,将下载后的文件拖入浏览器进行安装,已经安装过的跳过此步 15 | 2. 实在不会安装的请[百度如何安装油猴脚本](https://www.baidu.com/s?wd=%E5%A6%82%E4%BD%95%E5%AE%89%E8%A3%85tampermonkey) 16 | 3. 点击上方绿色的[安装按钮](https://greasyfork.org/zh-CN/scripts/427277),在弹出的页面中选择安装即可 17 | 4. 打开网易BUFF,在饰品市场列表页面中,原先的3D监视/社区服监视多了一个【加入对比】 18 | 5. 加入想要对比的饰品后,在页面顶部【游戏资讯】右侧多了一个对比列表 19 | 6. 选择两项即可进行对比 20 | 21 | 22 | ### 功能 23 | #### 2D对比 24 | 1. 对比:将两个饰品的检视图进行对比,鼠标左右拖动可以快速查看两者的不同 25 | 2. 差异:通过计算,显示出两个饰品像素点的不同,可以精确地发现饰品的差别,特别适用于宝石刀等。 26 | 27 | #### 3D对比 28 | 1. 最多支持9项模型的对比 29 | 2. 所有模型同步拖动 30 | 3. 按键盘1~9键可以快速切换模型 31 | 4. 按回车键可让所有模型归位 32 | 33 | #### 显示饰品上架和修改时间 34 | 1. 显示饰品上架时间 35 | 2. 显示饰品修改价格时间 36 | 37 | [![11516475ea9e26e03b88369dbbef7b97.png](https://s1.imagehub.cc/images/2023/12/04/11516475ea9e26e03b88369dbbef7b97.png)](https://www.imagehub.cc/image/1P03mU) 38 | 39 | 40 | ### 声明 41 | 1. 本项目由作者个人兴趣开发,代码开源,请勿将本项目用于商业用途 42 | 2. 引用本项目代码或借鉴还请标明一下出处,在此感谢 43 | 3. 作者:小淳 44 | 4. 联系方式:189964430@qq.com 45 | 5. [项目开源地址](https://github.com/qianjiachun/csgo-skin-compare) 46 | 47 | ### 效果预览 48 | ![buff](https://z3.ax1x.com/2021/06/03/23YKQe.png) 49 | ![检视图对比](https://z3.ax1x.com/2021/06/03/23Y6YV.png) 50 | ![检视图差异](https://z3.ax1x.com/2021/06/03/23YtW8.png) 51 | ![纹理图差异](https://z3.ax1x.com/2021/06/03/23YWy4.png) 52 | ![3D对比](https://z3.ax1x.com/2021/06/03/23YzTI.png) 53 | 54 | 55 | ## 更新内容 56 | 57 | ### 2024年5月31日 58 | 1. 【修复】修复了脚本无法生效的BUG 59 | 60 | ### 2024年5月4日 61 | 1. 【修复】修复了2D对比失效的BUG 62 | 2. 【修复】修复了3D对比失效的BUG 63 | - 3D对比目前拖动同步尚未修复,如果需要请手动进行视角调整 64 | 65 | ### 2024年5月2日 66 | 1. 【修复】修复了首次加载页面时无法显示上架时间的BUG 67 | 68 | ### 2023年12月4日 69 | 1. 【修复】修复了脚本无法生效的BUG 70 | 2. 【删除】删除了复制检视链接的功能 71 | 72 | ### 2023年5月5日 73 | 1. 【修复】修复了与[CSGO饰品自由议价needrun插件](https://greasyfork.org/zh-CN/scripts/464061)冲突导致不显示的问题 74 | 75 | ### 2022年9月19日 76 | 1. 【修复】修复了2D对比失效的BUG 77 | 78 | ### 2022年4月7日 79 | 1. 【修复】修复了3D对比拖动不同步的BUG 80 | 81 | ### 2022年4月6日 82 | 1. 【新增】新增显示饰品上架时间和饰品更新时间 83 | 84 | ### 2022年2月13日 85 | 1. 【修复】修复了复制检视链接失效的BUG 86 | - 检视链接可以去[ONET4P社区服BUFF/IGXE饰品检视](http://www.onet4p.net/server-list/9)使用 87 | 88 | ### 2021年12月18日 89 | 1. 【新增】新增复制检视链接功能,可粘贴至部分社区服(如ONET4P社区服)检视(内容与手机端分享一样) 90 | ### 2021年6月10日 91 | 1. 【修复】修复了饰品列表页面有自己上架的饰品导致脚本错误的BUG 92 | 2. 【新增】对比列表新增了全部删除功能 93 | -------------------------------------------------------------------------------- /src/pages/buff/packages/Inspect3D/index.js: -------------------------------------------------------------------------------- 1 | // 每个iframe的子页面 2 | import {getQueryString, setMouseMove} from "@/src/utils/index" 3 | 4 | let index; 5 | 6 | let isMouseDown = false; 7 | let mouseDownInfo = {}; 8 | let mouseMoveInfo = {}; 9 | 10 | function init() { 11 | let t = setInterval(() => { 12 | let canvas = document.querySelector("canvas"); 13 | if (canvas) { 14 | clearInterval(t); 15 | index = getQueryString("index"); 16 | initMessage(handleMessage); 17 | initFunc(); 18 | } 19 | }, 500); 20 | } 21 | 22 | function initMessage(callback) { 23 | window.addEventListener("message", e => { 24 | callback(e.data); 25 | }) 26 | } 27 | 28 | function handleMessage(msg) { 29 | let dom = document.querySelector("canvas"); 30 | switch (msg.cmd) { 31 | case "rotate": 32 | setMouseMove(dom, msg.value.x, msg.value.y); 33 | break; 34 | case "reset": 35 | buffManager.resetScene(); 36 | break; 37 | default: 38 | break; 39 | } 40 | } 41 | 42 | function initFunc() { 43 | let dom = document.querySelector("canvas"); 44 | 45 | // 锚点切换 46 | window.addEventListener("keydown", e => { 47 | postMessage({ 48 | cmd: "keydown", 49 | value: e.key 50 | }) 51 | }) 52 | 53 | // 鼠标拖拽 54 | dom.addEventListener("mousedown", (e) => { 55 | if (e.isMessage) { 56 | return; 57 | } 58 | isMouseDown = true; 59 | mouseDownInfo = { 60 | x: e.pageX, 61 | y: e.pageY 62 | } 63 | }) 64 | 65 | dom.addEventListener("mousemove", (e) => { 66 | if (e.isMessage) { 67 | return; 68 | } 69 | if (isMouseDown) { 70 | mouseMoveInfo = { 71 | x: e.pageX - mouseDownInfo.x, 72 | y: e.pageY - mouseDownInfo.y 73 | } 74 | } 75 | }) 76 | 77 | document.body.addEventListener("mouseup", (e) => { 78 | if (e.isMessage) { 79 | return; 80 | } 81 | console.log(mouseMoveInfo) 82 | isMouseDown = false; 83 | postMessage({ 84 | cmd: "rotate", 85 | index: index, 86 | value: mouseMoveInfo 87 | }); 88 | }) 89 | 90 | document.getElementById("pc").onclick = (e) => { 91 | // 阻止操作面板影响 92 | e.stopPropagation(); 93 | } 94 | } 95 | 96 | function postMessage(msg) { 97 | window.parent.postMessage(msg); 98 | } 99 | 100 | export default { 101 | init 102 | } -------------------------------------------------------------------------------- /src/pages/spect/packages/Compare2D/views/Compare.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 66 | 67 | -------------------------------------------------------------------------------- /src/pages/buff/packages/Compare3D/index.js: -------------------------------------------------------------------------------- 1 | import "./styles/index.css" 2 | 3 | // 1. 收集所有的模型iframe 4 | // 2. 给每个iframe设置拖拽事件 5 | // 3. 将事件传给父页面 6 | // 4. 父页面将事件传递给其他的子页面 7 | 8 | function init() { 9 | clearDefaultHtml(); 10 | initDom(); 11 | initMessage(handleMessage); 12 | initFunc(); 13 | } 14 | 15 | function clearDefaultHtml() { 16 | document.title = "CSGO饰品对比 - 3D"; 17 | document.body.innerHTML = ""; 18 | document.body.style.background = ""; 19 | document.querySelectorAll("link").forEach(item => item.remove()) 20 | } 21 | 22 | function getModelLinks() { 23 | let ret = []; 24 | let data = JSON.parse(GM_getValue("CompareList_3D") || "[]") || []; 25 | for (let i = 0; i < data.length; i++) { 26 | let item = data[i]; 27 | ret.push("https://buff.163.com/3d_inspect/cs2?compare=true&assetid=" + item.assetid); 28 | } 29 | return ret; 30 | } 31 | 32 | function initDom() { 33 | let links = getModelLinks(); 34 | let html = ` 35 |
36 | 使用说明: 37 |
38 | 1. 尽量以直线轨迹拖动,请勿按住来回拖动否则会不同步 39 |
40 | 2. 按键盘1~9键可以快速切换模型 41 |
42 | 3. 按回车键可让所有模型归位 43 |
44 | 4. 请勿在F12下操作,否则会不同步 45 |
46 | 5. 模型加载需要时间,请全部加载完再操作 47 |
48 |
49 | --By 小淳 50 |
51 | `; 52 | for (let i = 0; i < links.length; i++) { 53 | // id用于锚点跳转 54 | html += `` 55 | } 56 | let a = document.createElement("div"); 57 | a.className = "compare3d__wrap"; 58 | a.innerHTML = html; 59 | document.body.appendChild(a); 60 | } 61 | 62 | function initFunc() { 63 | // 父页面锚点快捷键实现 64 | document.addEventListener("keydown", e => { 65 | location.href = "https://buff.163.com/compare3d#model" + e.key; 66 | }) 67 | 68 | document.getElementsByClassName("compare3d__watermark")[0].addEventListener("click", (e) => { 69 | e.stopPropagation(); 70 | window.open("https://github.com/qianjiachun/csgo-skin-compare"); 71 | }) 72 | 73 | document.getElementsByClassName("compare3d__tips")[0].onclick = (e) => { 74 | e.stopPropagation(); 75 | } 76 | } 77 | 78 | function initMessage(callback) { 79 | window.addEventListener("message", (e) => { 80 | callback(e.data); 81 | }) 82 | } 83 | 84 | function handleMessage(msg) { 85 | let models = document.getElementsByClassName("compare3d__iframe"); 86 | switch (msg.cmd) { 87 | case "keydown": 88 | // iframe按下键盘,用于锚点跳转 89 | if (msg.value == "Enter") { 90 | // 重置 91 | for (let i = 0; i < models.length; i++) { 92 | postMessage(models[i].contentWindow, { 93 | cmd: "reset", 94 | value: 0 95 | }); 96 | } 97 | } else if (!isNaN(msg.value)) { 98 | location.href = "https://buff.163.com/compare3d#model" + msg.value; 99 | } 100 | break; 101 | case "rotate": 102 | // 得到旋转信息,旋转其他的model 103 | for (let i = 0; i < models.length; i++) { 104 | if (Number(msg.index) !== i+1) { 105 | // 排除自己 106 | postMessage(models[i].contentWindow, { 107 | cmd: "rotate", 108 | value: msg.value 109 | }); 110 | } 111 | } 112 | break; 113 | default: 114 | break; 115 | } 116 | } 117 | 118 | function postMessage(dom, msg) { 119 | dom.postMessage(msg); 120 | } 121 | 122 | export default { 123 | init 124 | } -------------------------------------------------------------------------------- /src/utils/Notice/Notice.css: -------------------------------------------------------------------------------- 1 | .noticejs-top{top:0;width:100%!important}.noticejs-top .item{border-radius:0!important;margin:0!important}.noticejs-topRight{top:10px;right:10px}.noticejs-topLeft{top:10px;left:10px}.noticejs-topCenter{top:10px;left:50%;transform:translate(-50%)}.noticejs-middleLeft,.noticejs-middleRight{right:10px;top:50%;transform:translateY(-50%)}.noticejs-middleLeft{left:10px}.noticejs-middleCenter{top:50%;left:50%;transform:translate(-50%,-50%)}.noticejs-bottom{bottom:0;width:100%!important}.noticejs-bottom .item{border-radius:0!important;margin:0!important}.noticejs-bottomRight{bottom:10px;right:10px}.noticejs-bottomLeft{bottom:10px;left:10px}.noticejs-bottomCenter{bottom:10px;left:50%;transform:translate(-50%)}.noticejs{font-family:Helvetica Neue,Helvetica,Arial,sans-serif}.noticejs .item{margin:0 0 10px;border-radius:3px;overflow:hidden}.noticejs .item .close{float:right;font-size:18px;font-weight:700;line-height:1;color:#fff;text-shadow:0 1px 0 #fff;opacity:1;margin-right:7px}.noticejs .item .close:hover{opacity:.5;color:#000}.noticejs .item a{color:#fff;border-bottom:1px dashed #fff}.noticejs .item a,.noticejs .item a:hover{text-decoration:none}.noticejs .success{background-color:#64ce83}.noticejs .success .noticejs-heading{background-color:#3da95c;color:#fff;padding:10px}.noticejs .success .noticejs-body{color:#fff;padding:10px}.noticejs .success .noticejs-body:hover{visibility:visible!important}.noticejs .success .noticejs-content{visibility:visible}.noticejs .info{background-color:#3ea2ff}.noticejs .info .noticejs-heading{background-color:#067cea;color:#fff;padding:10px}.noticejs .info .noticejs-body{color:#fff;padding:10px}.noticejs .info .noticejs-body:hover{visibility:visible!important}.noticejs .info .noticejs-content{visibility:visible}.noticejs .warning{background-color:#ff7f48}.noticejs .warning .noticejs-heading{background-color:#f44e06;color:#fff;padding:10px}.noticejs .warning .noticejs-body{color:#fff;padding:10px}.noticejs .warning .noticejs-body:hover{visibility:visible!important}.noticejs .warning .noticejs-content{visibility:visible}.noticejs .error{background-color:#e74c3c}.noticejs .error .noticejs-heading{background-color:#ba2c1d;color:#fff;padding:10px}.noticejs .error .noticejs-body{color:#fff;padding:10px}.noticejs .error .noticejs-body:hover{visibility:visible!important}.noticejs .error .noticejs-content{visibility:visible}.noticejs .progressbar{width:100%}.noticejs .progressbar .bar{width:1%;height:30px;background-color:#4caf50}.noticejs .success .noticejs-progressbar{width:100%;background-color:#64ce83;margin-top:-1px}.noticejs .success .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#3da95c}.noticejs .info .noticejs-progressbar{width:100%;background-color:#3ea2ff;margin-top:-1px}.noticejs .info .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#067cea}.noticejs .warning .noticejs-progressbar{width:100%;background-color:#ff7f48;margin-top:-1px}.noticejs .warning .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#f44e06}.noticejs .error .noticejs-progressbar{width:100%;background-color:#e74c3c;margin-top:-1px}.noticejs .error .noticejs-progressbar .noticejs-bar{width:100%;height:5px;background:#ba2c1d}@keyframes noticejs-fadeOut{0%{opacity:1}to{opacity:0}}.noticejs-fadeOut{animation-name:noticejs-fadeOut}@keyframes noticejs-modal-in{to{opacity:.3}}@keyframes noticejs-modal-out{to{opacity:0}}.noticejs-rtl .noticejs-heading{direction:rtl}.noticejs-rtl .close{float:left!important;margin-left:7px;margin-right:0!important}.noticejs-rtl .noticejs-content{direction:rtl}.noticejs{position:fixed;z-index:10050;width:320px}.noticejs ::-webkit-scrollbar{width:8px}.noticejs ::-webkit-scrollbar-button{width:8px;height:5px}.noticejs ::-webkit-scrollbar-track{border-radius:10px}.noticejs ::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.5);border-radius:10px}.noticejs ::-webkit-scrollbar-thumb:hover{background:#fff}.noticejs-modal{position:fixed;width:100%;height:100%;background-color:#000;z-index:10000;opacity:.3;left:0;top:0}.noticejs-modal-open{opacity:0;animation:noticejs-modal-in .3s ease-out}.noticejs-modal-close{animation:noticejs-modal-out .3s ease-out;animation-fill-mode:forwards} -------------------------------------------------------------------------------- /src/pages/spect/packages/Compare2D/views/Diff.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/pages/spect/packages/Compare2D/index.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import "./styles/index.css" 3 | import { Compare, Diff } from "./views" 4 | 5 | 6 | let compareList = JSON.parse(GM_getValue("CompareList_2D") || "[]") || []; 7 | let app; 8 | 9 | function init() { 10 | initDom(); 11 | initDom_Menu(); 12 | initFunc(); 13 | } 14 | 15 | function initDom() { 16 | let a = document.createElement("div"); 17 | a.className = "wrap"; 18 | a.innerHTML = ` 19 |
20 | --By 小淳 21 |
22 |
23 | ${compareList[0].name} 24 |
25 | 模板:${compareList[0].asset_info.paintseed} 26 |
27 | 磨损:${compareList[0].asset_info.paintwear} 28 |
29 |
30 | ${compareList[1].name} 31 |
32 | 模板:${compareList[1].asset_info.paintseed} 33 |
34 | 磨损:${compareList[1].asset_info.paintwear} 35 |
36 | 55 | 56 |
57 | 58 |
59 | ` 60 | let b = document.body; 61 | b.appendChild(a); 62 | } 63 | 64 | function initDom_Menu() { 65 | let html_texture = ""; // 纹理图html 66 | let dom = document.getElementsByClassName("texture__content")[0]; 67 | 68 | for (let i = 0; i < compareList[0].textures.length; i++) { 69 | // 纹理图 70 | html_texture += ` 71 |
【纹理图${i+1}】对比
72 |
【纹理图${i+1}】差异
73 | ` 74 | } 75 | dom.innerHTML = html_texture; 76 | } 77 | 78 | function initFunc() { 79 | let views = document.getElementsByClassName("view__item"); 80 | let textures = document.getElementsByClassName("texture__item"); 81 | // active事件 82 | for (let i = 0; i < views.length; i++) { 83 | views[i].addEventListener("click", () => { 84 | for (let j = 0; j < views.length; j++) { 85 | views[j].className = views[j].className.replace(" is-active", ""); 86 | } 87 | for (let j = 0; j < textures.length; j++) { 88 | textures[j].className = textures[j].className.replace(" is-active", ""); 89 | } 90 | views[i].className += " is-active"; 91 | }) 92 | } 93 | 94 | for (let i = 0; i < textures.length; i++) { 95 | textures[i].addEventListener("click", () => { 96 | for (let j = 0; j < views.length; j++) { 97 | views[j].className = views[j].className.replace(" is-active", ""); 98 | } 99 | for (let j = 0; j < textures.length; j++) { 100 | textures[j].className = textures[j].className.replace(" is-active", ""); 101 | } 102 | textures[i].className += " is-active"; 103 | }) 104 | } 105 | 106 | // 业务事件 107 | document.getElementById("view__compare").addEventListener("click", () => { 108 | // 检视图对比 109 | if (app) app.unmount(); 110 | let img1 = compareList[0].inspectUrl; 111 | let img2 = compareList[1].inspectUrl; 112 | app = createApp(Compare); 113 | app.config.globalProperties.img1 = img1; 114 | app.config.globalProperties.img2 = img2; 115 | app.mount("#app"); 116 | }) 117 | document.getElementById("view__diff").addEventListener("click", () => { 118 | // 检视图差异 119 | if (app) app.unmount(); 120 | let img1 = compareList[0].inspectUrl; 121 | let img2 = compareList[1].inspectUrl; 122 | app = createApp(Diff); 123 | app.config.globalProperties.img1 = img1; 124 | app.config.globalProperties.img2 = img2; 125 | app.mount("#app"); 126 | }) 127 | 128 | let texture_compare = document.getElementsByClassName("texture__compare"); 129 | let texture_diff = document.getElementsByClassName("texture__diff"); 130 | 131 | for (let i = 0; i < texture_compare.length; i++) { 132 | let item = texture_compare[i]; 133 | item.addEventListener("click", () => { 134 | // 纹理图对比 135 | if (app) app.unmount(); 136 | let img1 = compareList[0].textures[i].url; 137 | let img2 = compareList[1].textures[i].url; 138 | app = createApp(Compare); 139 | app.config.globalProperties.img1 = img1; 140 | app.config.globalProperties.img2 = img2; 141 | app.mount("#app"); 142 | }) 143 | } 144 | 145 | for (let i = 0; i < texture_diff.length; i++) { 146 | let item = texture_diff[i]; 147 | item.addEventListener("click", () => { 148 | // 纹理图差异 149 | if (app) app.unmount(); 150 | let img1 = compareList[0].textures[i].url; 151 | let img2 = compareList[1].textures[i].url; 152 | app = createApp(Diff); 153 | app.config.globalProperties.img1 = img1; 154 | app.config.globalProperties.img2 = img2; 155 | app.mount("#app"); 156 | }) 157 | } 158 | 159 | document.getElementsByClassName("compare2d__watermark")[0].addEventListener("click", () => { 160 | window.open("https://github.com/qianjiachun/csgo-skin-compare"); 161 | }) 162 | } 163 | 164 | 165 | export default { 166 | init 167 | } -------------------------------------------------------------------------------- /src/pages/buff/packages/CompareList/views/CompareList.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 155 | 156 | -------------------------------------------------------------------------------- /src/pages/buff/packages/AddButton/index.js: -------------------------------------------------------------------------------- 1 | import {getAssetIdInfo} from "./apis" 2 | import {getStrMiddle, showMessage} from "@/src/utils" 3 | import {DomHook} from "@/src/utils/DomHook" 4 | 5 | 6 | let skinImg // 皮肤图片地址 7 | let skinName // 皮肤名字 8 | const LIST_MAX = 50 // 对比列表最多存放数 9 | 10 | function init() { 11 | initDom(); 12 | initFunc(); 13 | let hook = new DomHook(".detail-tab-cont", false, (m => { 14 | for (let i = 0; i < m[0].addedNodes.length; i++) { 15 | let item = m[0].addedNodes[i]; 16 | if (item.id == "market-selling-list") { 17 | initDom(); 18 | initFunc(); 19 | break; 20 | } 21 | } 22 | })) 23 | } 24 | 25 | function initDom() { 26 | initDom_addBtn(); 27 | // initDom_CopyBtn(); 28 | } 29 | 30 | function initDom_addBtn() { 31 | skinImg = getSkinImg(); 32 | skinName = getSkinName(); 33 | let domList = document.getElementsByClassName("ctag btn_action_link"); 34 | for (let i = 0; i < domList.length; i++) { 35 | let parentDom = domList[i].parentNode; 36 | let trDom = domList[i].parentNode.parentNode.parentNode.parentNode; 37 | let assetid = domList[i].getAttribute("data-assetid"); 38 | let inspectUrl = trDom.getElementsByClassName("csgo_inspect_img_btn")[0].getAttribute("data-inspecturl"); 39 | if (!trDom.getElementsByClassName("btn-buy-order")[0]) { 40 | // 有自己上架的饰品 41 | continue; 42 | } 43 | let price = trDom.getElementsByClassName("btn-buy-order")[0].getAttribute("data-price"); 44 | let shopDom = trDom.getElementsByClassName("j_shoptip_handler")[0]; 45 | let shopHref = shopDom.href; 46 | let shopImg = shopDom.getElementsByClassName("user-avatar")[0].src; 47 | let shopName = shopDom.innerText; 48 | 49 | if (assetid) { 50 | let dom = document.createElement("a"); 51 | dom.className = "ctag compare-btn"; 52 | dom.setAttribute("assetid", assetid); 53 | dom.setAttribute("inspecturl", inspectUrl); 54 | dom.setAttribute("price", price); 55 | dom.setAttribute("shop_href", shopHref); 56 | dom.setAttribute("shop_img", shopImg); 57 | dom.setAttribute("shop_name", shopName); 58 | dom.innerHTML = `加入对比`; 59 | parentDom.appendChild(dom); 60 | } 61 | } 62 | } 63 | 64 | function initDom_CopyBtn() { 65 | let domList = document.getElementsByClassName("pic-cont item-detail-img"); 66 | for (let i = 0; i < domList.length; i++) { 67 | let trDom = domList[i].parentNode.parentNode; 68 | let parentDom = trDom.getElementsByClassName("csgo_value")[0]; 69 | let sell_order_id = domList[i].getAttribute("data-orderid"); 70 | let assetid = domList[i].getAttribute("data-assetid"); 71 | let appid = domList[i].getAttribute("data-appid"); 72 | let classid = domList[i].getAttribute("data-classid"); 73 | let instanceid = domList[i].getAttribute("data-instanceid"); 74 | 75 | let dom = document.createElement("a"); 76 | dom.style.marginLeft = "5px"; 77 | dom.className = "ctag copylink-btn"; 78 | dom.setAttribute("assetid", assetid); 79 | dom.setAttribute("instanceid", instanceid); 80 | dom.setAttribute("appid", appid); 81 | dom.setAttribute("classid", classid); 82 | dom.setAttribute("sell_order_id", sell_order_id); 83 | dom.innerHTML = ` 84 | 85 | 检视链接`; 86 | parentDom.appendChild(dom); 87 | } 88 | } 89 | 90 | function initFunc() { 91 | let domList = document.getElementsByClassName("compare-btn"); 92 | for (let i = 0; i < domList.length; i++) { 93 | domList[i].addEventListener("click", () => { 94 | let assetid = domList[i].getAttribute("assetid"); 95 | let inspectUrl = domList[i].getAttribute("inspecturl"); 96 | let price = domList[i].getAttribute("price"); 97 | let shopHref = domList[i].getAttribute("shop_href"); 98 | let shopImg = domList[i].getAttribute("shop_img"); 99 | let shopName = domList[i].getAttribute("shop_name"); 100 | onClickAddButton({ 101 | assetid: assetid, 102 | inspectUrl: inspectUrl, 103 | price: price, 104 | shopHref: shopHref, 105 | shopImg: shopImg, 106 | shopName: shopName 107 | }); 108 | }) 109 | } 110 | 111 | let domListCopy = document.getElementsByClassName("copylink-btn"); 112 | for (let i = 0; i < domListCopy.length; i++) { 113 | domListCopy[i].addEventListener("click", () => { 114 | let assetid = domListCopy[i].getAttribute("assetid"); 115 | let instanceid = domListCopy[i].getAttribute("instanceid"); 116 | let appid = domListCopy[i].getAttribute("appid"); 117 | let classid = domListCopy[i].getAttribute("classid"); 118 | let sell_order_id = domListCopy[i].getAttribute("sell_order_id"); 119 | let text = `https://buff.163.com/market/m/item_detail?game=csgo&assetid=${assetid}&classid=${classid}&instanceid=${instanceid}&sell_order_id=${sell_order_id}` 120 | GM_setClipboard(text); 121 | showMessage("复制成功,可粘贴至社区服检视", "success"); 122 | }) 123 | } 124 | } 125 | 126 | function onClickAddButton(info) { 127 | // GM_deleteValue("CompareList"); 128 | // return 129 | getAssetIdInfo(info.assetid).then(res => { 130 | return res.json(); 131 | }).then(ret => { 132 | if (ret.code === "Error") { 133 | showMessage("解析该饰品失败,暂不支持3D", "error"); 134 | return; 135 | } 136 | let obj = getSkinData(ret.data); 137 | obj.assetid = info.assetid; 138 | obj.price = info.price; 139 | obj.inspectUrl = info.inspectUrl; 140 | obj.shopHref = info.shopHref; 141 | obj.shopImg = info.shopImg; 142 | obj.shopName = info.shopName; 143 | if (obj) { 144 | if (saveData2CompareList(obj)) { 145 | showMessage("加入对比列表成功", "success"); 146 | } else { 147 | showMessage("该饰品已存在于对比列表", "error"); 148 | } 149 | } else { 150 | showMessage("加入对比列表失败", "error"); 151 | } 152 | 153 | }).catch(err => { 154 | console.log(err); 155 | }) 156 | } 157 | 158 | function getSkinData(data) { 159 | // 构造对比列表里的饰品对象信息 160 | let ret = data; 161 | if (data) { 162 | let i = 1; 163 | let textureList = []; 164 | for (const key in data.texture_url) { 165 | textureList.push({id: key, url: data.texture_url[key]}); 166 | } 167 | // while (`texture_${i}` in data.texture_url) { 168 | // if (i > 1000) { 169 | // // 熔断 170 | // break; 171 | // } 172 | // textureList.push({id: `texture_${i}`, url: data.texture_url[`texture_${i}`]}); 173 | // i++; 174 | // } 175 | ret.textures = textureList; 176 | ret.name = skinName; 177 | ret.img_url = skinImg; 178 | ret.update_time = new Date().getTime(); 179 | } 180 | return ret; 181 | } 182 | 183 | function getSkinImg() { 184 | return document.getElementsByClassName("t_Center")[1].getElementsByTagName("img")[0].src; 185 | } 186 | 187 | function getSkinName() { 188 | let ret = ""; 189 | let parent = document.getElementsByClassName("cru-goods"); 190 | for (let i = 0; i < parent.length; i++) { 191 | let item = parent[i]; 192 | ret += item.innerText; 193 | } 194 | return ret; 195 | } 196 | 197 | function saveData2CompareList(data) { 198 | let value = GM_getValue("CompareList") || "[]"; 199 | let compareList = JSON.parse(value); 200 | 201 | let isExist = false; 202 | for (let i = 0; i < compareList.length; i++) { 203 | if (data.assetid == compareList[i].assetid) { 204 | isExist = true; 205 | break; 206 | } 207 | } 208 | if (isExist) { 209 | return false; 210 | } 211 | if (compareList.length > LIST_MAX) { 212 | compareList.pop(); // 删除最后一个 213 | } 214 | compareList.unshift(data); // 插入到第一个 215 | 216 | let text = JSON.stringify(compareList); 217 | GM_setValue("CompareList", text); 218 | return true; 219 | } 220 | 221 | export default { 222 | init 223 | } --------------------------------------------------------------------------------