├── .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 |
2 | view1内容
3 |
4 |
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 |
2 |
3 |
4 |
![]()
5 |
检视图
6 |
7 |
8 |
{{skinName}}
9 |
模板:{{skinSeed}}
10 |
磨损:{{skinWear}}
11 |
15 |
16 |
17 |
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 | [](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 | 
49 | 
50 | 
51 | 
52 | 
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 |
2 |
12 |
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 |
2 |
3 |
4 |
5 | 明亮度:
6 |
7 |
8 |
9 | 分辨率:
10 |
11 |
12 |
13 | 阈值:
14 |
15 |
16 |
17 |
18 |
19 |
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 |
2 |
45 |
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 | }
--------------------------------------------------------------------------------