JLC_SHOP_SEARCH_TOOL_2.0

JLC_SHOP_SEARCH_TOOL_2.0.

目前為 2025-05-31 提交的版本,檢視 最新版本

// ==UserScript==
// @name         JLC_SHOP_SEARCH_TOOL_2.0
// @namespace    http://tampermonkey.net/
// @version      1.1.3
// @description  JLC_SHOP_SEARCH_TOOL_2.0.
// @author       Lx
// @match        https://so.szlcsc.com/global.html**
// @match        https://list.szlcsc.com/brand**
// @match        https://list.szlcsc.com/catalog**
// @icon         https://www.google.com/s2/favicons?sz=64&domain=szlcsc.com
// @require      https://update.greasyfork.org/scripts/446666/1389793/jQuery%20Core%20minified.js
// @grant        GM_xmlhttpRequest
// @grant        GM_openInTab
// @connect      szlcsc.com
// @license      MIT
// ==/UserScript==
(async function() {
    'use strict';

    const Util = {
        /**
         * 根据value排序Map
         * @param {*} map
         * @returns
         */
        sortMapByValue: function(map) {
            var arrayObj = Array.from(map);
            // 按照value值降序排序
            arrayObj.sort(function(a, b) {
                return b[1] - a[1]; // 修改为降序排序
            });
            return arrayObj;
        },

        /**
         * GET请求封装
         * @param {*} url
         */
        getAjax: function(url) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    url: url,
                    method: 'GET',
                    onload: (r) => {
                        resolve(r.response);
                    },
                    onerror: (err) => {
                        reject(err);
                    }
                });
            });
        },

        /**
         * POST请求封装
         * @param {*} url
         * @param {*} data
         */
        postAjaxJSON: function(url, data) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    url: url,
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify(data), // 确保数据被正确转换为JSON字符串
                    onload: (r) => {
                        resolve(r.response);
                    },
                    onerror: (err) => {
                        reject(err);
                    }
                });
            });
        },

        /**
         * 获取品牌名称
         * 支持列表:
         * 1、XUNDA(讯答)
         * 2、立创开发板
         * 3、50元德立品牌优惠
         * 4、<新人专享>15元芯声品牌优惠
         * @param text
         */
        brandNameProcess: function(text) {
            let replaceText = text;
            try {
                // 取括号里的品牌名称 如:ICEY(冰禹)
                if (replaceText.includes("(")) {
                    const t = replaceText.split(/\(|\)/g).filter((e => e));
                    replaceText = (1 === t.length ? t[0] : t.length > 1 ? t[t.length - 1] : name)
                } else {
                    const t = /<.+>/g.exec(text)
                    if (t != null) {
                        replaceText = t[0].replace(/<|>/g, '')
                        if (replaceText === '新人专享') {
                            replaceText = text.replace(/^.[^元]*元(.*)品牌.*$/, '$1')
                        }
                    } else {
                        replaceText = text.replace(/^.[^元]*元(.*)品牌.*$/, '$1')
                    }
                }
            } catch (e) {
                console.error(e)
            } finally {
                return replaceText
            }
        },

        jsonToUrlParam: function(json, ignoreFields = '') {
            return Object.keys(json)
                .filter(key => ignoreFields.indexOf(key) === -1)
                .map(key => key + '=' + encodeURIComponent(json[key])).join('&'); // 使用 encodeURIComponent 避免URL编码问题
        },

        /**
         * POST请求封装
         * @param {*} url
         * @param {*} jsonData
         */
        postFormAjax: function(url, jsonData) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    url: url,
                    data: this.jsonToUrlParam(jsonData), // 使用 Util.jsonToUrlParam 方法
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
                    },
                    onload: (r) => {
                        resolve(r.response);
                    },
                    onerror: (err) => {
                        reject(err);
                    }
                });
            });
        },

        /**
         * 有进度的等待所有异步任务的执行
         * @param {*} requests
         * @param {*} callback
         * @returns
         */
        allWithProgress: function(requests, callback) {
            let index = 0;
            requests.forEach(item => {
                item.then(() => {
                    index++;
                    const progress = (index / requests.length) * 100;
                    callback({
                        total: requests.length,
                        cur: index,
                        progress: progress
                    });
                }).catch((err) => {
                    console.error(err);
                });
            });
            return Promise.all(requests);
        },

        /**
         * 等待
         * @param {*} timeout
         * @returns
         */
        sleep: function(timeout) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(true);
                }, timeout);
            });
        },

        /**
         * 等待 执行函数
         * @param {*} timeout
         * @param {*} func
         * @returns
         */
        sleepFunc: function(timeout, func) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    func && func();
                }, timeout);
            });
        },

        /**
         * 获取本地缓存
         * @param {*} key
         */
        getLocalData: function(k) {
            return localStorage.getItem(k);
        },

        /**
         * 设置本地缓存
         * @param {*} key
         * @param {*} value
         */
        setLocalData: function(k, v) {
            localStorage.setItem(k, v);
        },

        /**
         * 获取session缓存
         * @param {*} key
         */
        getSessionData: function(k) {
            return sessionStorage.getItem(k);
        },

        /**
         * 设置session缓存
         * @param {*} key
         * @param {*} value
         */
        setSessionData: function(k, v) {
            sessionStorage.setItem(k, v);
        }
    };

    // ========================================================
    // 基础方法
    const Base = {
        // 优惠券只保存1元购的优惠券信息
        allOneCouponMap: new Map(),
        // 搜索页获取每一行的元素
        getSearchRows: () => {
            const rows = $('div.product-group-leader section, div.group section');
            rows.each(function() {
                if (!$(this).hasClass('line-box')) {
                    $(this).addClass('line-box');
                }
            });
            return rows;
        },
        // 获取顶级的行元素
        getParentRow: (that) => $(that).closest('.line-box')
    }


    /**
     * 一键索索淘宝
     */
    class SearchPageHelper {
        constructor() {
            this.someCouponMapping = {
                "MDD": "辰达半导体",
            }
        }

        /**
         * 搜索列表中,对品牌颜色进行上色
         * list.szlcsc.com/catalog
         */
        static catalogBrandColor() {
            for (let [brandName, brandDetail] of Base.allOneCouponMap) {
                // 获取页面元素
                const $brandEle = $(`li[title*="${brandName}"]:not([style*=background-color]), 
                span[title*="${brandName}"]:not([style*=background-color]), 
                a.brand-name[title*="${brandName}"]:not([style*=background-color])`);
                if ($brandEle.length > 0) {
                    $brandEle.css({
                        "background-color": brandDetail.isNew ? '#00bfffb8' : '#7fffd4b8'
                    });
                    $brandEle.addClass(brandDetail.isNew ? 'isNew' : 'isNotNew')
                }
            }
        }

        /**
         * 筛选条件:多选品牌
         * @param {*} isNew 是否新人券 true/false
         */
        async multiFilterBrand(isNew) {
            $('li:contains("品牌"):contains("多选") div:contains("多选")').last().click();
            await Util.sleep(1000);
            const elementStr = isNew ? 'isNew' : 'isNotNew';
            $(`.${elementStr}`).each(function() {
                // 品牌名称
                const brandNameOrigin = Util.brandNameProcess($(this).text().trim().trim());
                if (Base.allOneCouponMap.has(brandNameOrigin)) {
                    if (Base.allOneCouponMap.get(brandNameOrigin).isNew === isNew) {
                        // 多选框选中
                        $(this).find('label').click();
                    }
                }
            })
            await Util.sleep(1000);
            $('button[data-need-query*="lcsc_vid="][data-spm-reset]:contains("确定")').click();
        }

        /**
         * 类目筛选按钮租
         */
        btnsRender() {
            if ($('#_remind').length === 0) {
                $('li:contains("品牌"):contains("多选")').append(`
        <div id='_remind'>
            <span class='row_center get_new_coupon'><p class='new_'></p>新人券</span>
            <span class='row_center get_notnew_coupon'><p class='not_new_'></p>非新人券</span>
        </div>
        <style>
        #_remind {
            display: inline-block;
            position: absolute;
            top: 0px;
            right: 100px;
            width: 100px;
        }
        .row_center {
            display: inline-flex;
            align-items: center;
        }
        .new_ {
            background-color: #00bfffb8;
            margin-right: 10px;
            width: 20px;
            height: 10px;
        }
        .not_new_ {
            background-color: #7fffd4b8;
            margin-right: 10px;
            width: 20px;
            height: 10px;
        }
        .get_new_coupon,
        .get_notnew_coupon {
            cursor: pointer;
        }
        .get_new_coupon:hover,
        .get_notnew_coupon:hover {
            background: #e1e1e1;
        }
        </style>
        `)
                    // 多选新人券
                $('.get_new_coupon').click(() => this.multiFilterBrand(true))
                    // 多选非新人券
                $('.get_notnew_coupon').click(() => this.multiFilterBrand(false))
            }
        }

        /**
         * 获取优惠券列表信息,并暂存在变量集合中
         * 只获取1元购的优惠券
         */
        async getAllCoupon() {

            const buildData = (jsonText) => {
                const json = JSON.parse(jsonText);
                if (json.code === 200) {
                    // 取数据
                    const resultMap = json.result.couponModelVOListMap;
                    const datas = ['1', '2', '3', '4', '5'];
                    let allCouponNotNew = [];
                    for (const key of datas) {
                        // 合并所有类型的优惠券
                        if (resultMap[key]) {
                            allCouponNotNew = [...allCouponNotNew, ...resultMap[key]];
                        }
                        // 优惠券处理
                        processCouponList(allCouponNotNew, this.someCouponMapping);
                    }
                    console.log(Base.allOneCouponMap);
                }
            }

            // 处理单个优惠券
            const processItem = (couponItem, referenceMap, someCouponMapping) => {
                    // 一些优惠券特殊处理
                    for (let key in someCouponMapping) {
                        if (couponItem.couponTypeName == key) {
                            const newBrandName = someCouponMapping[key]
                                // 存到变量Map中
                            referenceMap.set(newBrandName, {
                                couponName: couponItem.couponName, // 优惠券名称
                                isNew: couponItem.couponName.includes("<新人专享>"), // 是否新人专享
                                couponPrice: couponItem.couponAmount, //优惠券金额减免
                                minOrderMoney: couponItem.minOrderMoney, //要求最低金额
                                pay: couponItem.minOrderMoney - couponItem.couponAmount, // 实际支付金额
                                brandName: newBrandName, // 品牌名称
                                couponId: couponItem.uuid, // 优惠券id
                                isHaved: couponItem.isReceive, // 是否已经领取
                                isUsed: couponItem.isUse, // 是否已经使用过
                                brandIndexHref: couponItem.targetUrl, // 对应的品牌主页地址
                                couponLink: `https://www.szlcsc.com/getCoupon/${couponItem.uuid}`, // 领券接口地址
                            });
                        }
                    }
                    // 存到变量Map中
                    referenceMap.set(couponItem.couponTypeName, {
                        couponName: couponItem.couponName, // 优惠券名称
                        isNew: couponItem.couponName.includes("<新人专享>"), // 是否新人专享
                        couponPrice: couponItem.couponAmount, //优惠券金额减免
                        minOrderMoney: couponItem.minOrderMoney, //要求最低金额
                        pay: couponItem.minOrderMoney - couponItem.couponAmount, // 实际支付金额
                        brandName: couponItem.couponTypeName, // 品牌名称
                        couponId: couponItem.uuid, // 优惠券id
                        isHaved: couponItem.isReceive, // 是否已经领取
                        isUsed: couponItem.isUse, // 是否已经使用过
                        brandIndexHref: couponItem.targetUrl, // 对应的品牌主页地址
                        couponLink: `https://www.szlcsc.com/getCoupon/${couponItem.uuid}`, // 领券接口地址
                    });
                }
                // 优惠券简单封装
            const processCouponList = (couponList, someCouponMapping) => {
                // 遍历
                for (let couponItem of couponList) {
                    const {
                        couponAmount,
                        minOrderMoney
                    } = couponItem;
                    // 1元购
                    if ((minOrderMoney - couponAmount) === 1) {
                        processItem(couponItem, Base.allOneCouponMap, someCouponMapping)
                    }
                }
            }

            // 获取缓存的我的优惠券数据
            const couponData = Util.getSessionData('COUPON_DATA');
            if (couponData) {
                if ([...Base.allOneCouponMap.keys()].length == 0) {
                    buildData(couponData);
                }
                return;
            }

            // http获取优惠券信息
            let json = await Util.getAjax(`https://activity.szlcsc.com/activity/coupon`);
            Util.setSessionData('COUPON_DATA', json);
            buildData(json);
        }

        /**
         * 一键搜索淘宝
         */
        appendSearchTbBtn() {
            if ($('.searchTaobao_').length === 0) {
                // 预售拼团 不处理,其他的都追加按钮
                $('button:contains("加入购物车")').after(`
                <button type="button" class="mb-[6px] h-[32px] w-full rounded-[6px] bg-[#0093E6] text-[12px] text-[white] hover:bg-[#47B2ED] searchTaobao_">一键搜淘宝</button>
            `)
            } else if ($('.searchTaobao_:not([addedClickHandler])').length > 0) {
                /**
                 * 非阻容,其他数据处理
                 * @param {*} parents 行级标签
                 * @param {*} resArr  数据存放的数组
                 */
                function other(parents, resArr) {
                    let productName = parents.find('dl dd:eq(0)').text().trim() || '';
                    if (productName.length === 0 || resArr.length > 0) {
                        return;
                    }
                    let footprint = parents.find('dl:contains("封装") dd span').text().trim() || '';
                    resArr.push(productName);
                    resArr.push(footprint);
                }

                /**
                 * 电阻数据处理
                 * @param {*} parents 行级标签
                 * @param {*} resArr  数据存放的数组
                 */
                function R(parents, resArr) {
                    const r = parents.find('dl:contains("阻值") dd span:eq(0)').text().replace('Ω', '').trim() || '';
                    if (r.length === 0 || resArr.length > 0) {
                        return;
                    }
                    const f = parents.find('dl:contains("封装") dd span:eq(0)').text().trim() || '';
                    const j = parents.find('dl:contains("精度") dd span:eq(0)').text().replace('±', '').trim() || '';
                    resArr.push(r);
                    resArr.push(f);
                    resArr.push(j);
                }

                /**
                 * 电容数据处理
                 * @param {*} parents  行级标签
                 * @param {*} resArr  数据存放的数组
                 */
                function C(parents, resArr) {
                    const c = parents.find('dl:contains("容值") dd span:eq(0)').text().trim() || '';
                    if (c.length === 0 || resArr.length > 0) {
                        return;
                    }
                    const v = parents.find('dl:contains("额定电压") dd span:eq(0)').text().trim() || '';
                    const j = parents.find('dl:contains("精度") dd span:eq(0)').text().replace('±', '').trim() || '';
                    const f = parents.find('dl:contains("封装") dd span:eq(0)').text().trim() || '';
                    resArr.push(c);
                    resArr.push(v);
                    resArr.push(j);
                    resArr.push(f);
                }

                $('.searchTaobao_:not([addedClickHandler])').attr('addedClickHandler', true).on('click', function(params) {
                    let searchArrVals = [];
                    const $parents = Base.getParentRow(this);
                    // 阻容处理、其他元件处理
                    R($parents, searchArrVals);
                    C($parents, searchArrVals);
                    other($parents, searchArrVals);
                    GM_openInTab(`https://s.taobao.com/search?q=${searchArrVals.join('/')}`, {
                        active: true,
                        insert: true,
                        setParent: true
                    })
                })
            }
        }

        /**
         * 左侧封装搜索
         */
        appendLeftRowBtns() {
            const rows = this.getSearchRows();
            [...rows.find('button:contains("复制")')].forEach(row => {
                const $btn = $(row);
                const specName = $btn.closest('section').find('dl:contains("封装")').find('dd').text();
                if ($btn.length > 0 && $btn.siblings('button:contains("封装精确匹配")').length === 0) {
                    // $btn.before(`<button spec-name="${specName}" select-type="MHC" style="width: 110px;" class='btn_search_manual mr-[10px] flex h-[26px] items-center justify-center rounded-[13px] bg-[#F5F6F9] text-[#333] hover:bg-[#ECEEF0]'>
                    //        <svg style="margin-right: 3px" t="1748570264460" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6531" width="16" height="16"><path d="M949.76 884.3264a88.68864 88.68864 0 0 1-25.64096 62.67904 87.14752 87.14752 0 0 1-123.76576 0.16896l-164.29568-160.87552a382.4128 382.4128 0 0 1-26.43968 12.6208 382.83776 382.83776 0 0 1-300.032 0 383.38048 383.38048 0 0 1-122.48064-83.39968 391.296 391.296 0 0 1 0-550.36928 384.56832 384.56832 0 0 1 627.55328 123.648 391.00416 391.00416 0 0 1-40.704 376.57088l150.32882 156.56448a88.576 88.576 0 0 1 25.47712 62.39232z m-153.6512-444.04736c0-186.33216-150.41536-337.92-335.30368-337.92s-335.32928 151.6032-335.32928 337.92S275.89632 778.24 460.8 778.24s335.3088-151.64928 335.3088-337.96096z m-503.61344 168.90368a240.45568 240.45568 0 0 1 0-337.73568l34.63168 40.07424a183.46496 183.46496 0 0 0 0 257.50528z" fill="#fa6650" p-id="6532"></path></svg>
                    //        封装模糊匹配
                    //        </button>`);
                    $btn.before(`<button spec-name="${specName}" select-type="JQC" style="width: 110px;" class='btn_search_manual mr-[10px] flex h-[26px] items-center justify-center rounded-[13px] bg-[#F5F6F9] text-[#333] hover:bg-[#ECEEF0]'>
                           <svg style="margin-right: 3px" t="1748569569792" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7704" width="16" height="16"><path d="M945.71 946c-18.67 18.67-49.21 18.67-67.88 0L674.18 742.35c-18.67-18.67-18.67-49.21 0-67.88 18.67-18.67 49.21-18.67 67.88 0l203.65 203.65c18.66 18.66 18.66 49.21 0 67.88z" fill="#CDD8F8" p-id="7705"></path><path d="M447.71 832c-51.82 0-102.11-10.16-149.49-30.2-45.73-19.34-86.79-47.02-122.04-82.27-35.25-35.25-62.93-76.31-82.27-122.04-20.04-47.37-30.2-97.67-30.2-149.49s10.16-102.11 30.2-149.49c19.34-45.73 47.02-86.79 82.27-122.04 35.25-35.25 76.31-62.93 122.04-82.27C345.6 74.16 395.89 64 447.71 64S549.82 74.16 597.2 94.2c45.73 19.34 86.79 47.02 122.04 82.27 35.25 33.25 62.93 76.31 82.27 122.04 20.04 47.37 30.2 97.67 30.2 149.49s-10.16 102.11-30.2 149.49c-19.34 45.73-47.02 86.79-82.27 122.04-35.25 35.25-76.31 62.93-122.04 82.27-47.38 20.04-97.67 30.2-149.49 30.2z m0-667.83c-75.81 0-147.09 29.52-200.7 83.13S163.88 372.18 163.88 448s29.52 147.09 83.13 200.7c53.61 53.61 124.88 83.13 200.7 83.13s147.09-29.52 200.7-83.13c53.61-53.61 83.13-124.88 83.13-200.7s-29.52-147.09-83.13-200.7-124.89-83.13-200.7-83.13z" fill="#5C76F9" p-id="7706"></path></svg>
                           封装精确匹配
                           </button>`);
                }
            });

            $('.btn_search_manual').off('click').on('click', function(e) {
                this._clickSpecFunc($(e.currentTarget), $(e.currentTarget).attr('spec-name'), $(e.currentTarget).attr('select-type'));
            }.bind(this));
        }

        /**
         * 获取搜索结果行
         */
        getSearchRows() {
            return Base.getSearchRows();
        }

        /**
         * 封装模糊匹配
         * @param specName
         * @private
         */
        _MHCEachClick(that, specName) {
            if ($(`.det-screen:contains("封装:") label.fuxuanku-lable:contains("${specName}")`).length > 0) {
                $(`.det-screen:contains("封装:") label.fuxuanku-lable:contains("${specName}")`).click();
            } else {
                if (specName.includes('-')) {
                    this._MHCEachClick(specName.split('-').slice(0, -1).join('-'));
                }
            }
        }

        /**
         * 封装精确匹配
         * @param specName
         * @private
         */
        async _JQCEachClick(that, specName) {
            console.log('Base.getParentRow(that)', Base.getParentRow(that))
            await Util.sleep(200);
            if ($(`.det-screen:contains("封装:") label.fuxuanku-lable[title="${specName}"]`).length > 0) {
                $(`.det-screen:contains("封装:") label.fuxuanku-lable[title="${specName}"]`).click();
            } else {
                if (specName.includes('-')) {
                    this._JQCEachClick(specName.split('-').slice(0, -1).join('-'));
                }
            }
        }

        async _clickSpecFunc(that, specName, selectType) {
            // 封装的筛选条件那一行 展开规格
            $('li:contains("封装"):contains("多选")').find('div:contains("多选")').click();
            switch (selectType) {
                // 模糊查
                case "MHC":
                    this._MHCEachClick(that, specName);
                    break;
                    // 精确查
                case "JQC":
                    this._JQCEachClick(that, specName);
                    break;
            }
            // 查找规格对应的选项
            $(`.det-screen:contains("封装:") input[value="确定"]`).click();
        }
    };

    // 搜索页启动
    function searchStart() {
        // 每行追加到按钮组
        const searchPageHelper = new SearchPageHelper();
        searchPageHelper.appendLeftRowBtns();
        searchPageHelper.appendSearchTbBtn();
        // 优惠券信息获取
        searchPageHelper.getAllCoupon();
        // 搜索页按钮组渲染
        searchPageHelper.btnsRender();
        // 定时上色
        setInterval(SearchPageHelper.catalogBrandColor, 1000);
    }

    // 搜索页判断
    let isSearchPage = () => location.href.includes('so.szlcsc.com/global.html') ||
        location.href.includes('list.szlcsc.com/brand') ||
        location.href.includes('list.szlcsc.com/catalog');

    setInterval(function() {
        if (isSearchPage()) {
            searchStart()
        }
    }, 2000)

})()