From 0948413460c975927a4dfd8a3898acc08111c5d2 Mon Sep 17 00:00:00 2001 From: johnathan <952508490@qq.com> Date: Fri, 13 Jun 2025 18:35:20 +0800 Subject: [PATCH] Update --- package.json | 5 +- pnpm-lock.yaml | 422 +++++++++++++++++- shim.d.ts | 4 + src/composables/useAppContext.ts | 4 + src/composables/useCurrentUrl.ts | 6 +- src/composables/usePageContext.ts | 4 - src/composables/useWebExtensionStorage.ts | 4 +- src/logic/common-setup.ts | 7 +- src/logic/data-io.ts | 214 +++++---- src/logic/execute-script.ts | 17 +- src/logic/page-worker/homedepot.ts | 18 +- src/logic/page-worker/types.d.ts | 6 +- src/logic/web-injectors.ts | 66 ++- src/options/Options.vue | 13 +- .../views/AmazonResultTable.vue} | 27 +- src/router/index.ts | 27 ++ src/sidepanel/App.vue | 24 +- .../AmazonEntries}/DetailPageEntry.vue | 0 .../AmazonEntries}/ReviewPageEntry.vue | 0 .../AmazonEntries}/SearchPageEntry.vue | 0 .../AmazonSidepanel.vue} | 7 +- src/sidepanel/views/HomedepotSidepanel.vue | 32 ++ 22 files changed, 734 insertions(+), 173 deletions(-) create mode 100644 src/composables/useAppContext.ts delete mode 100644 src/composables/usePageContext.ts rename src/{components/ResultTable.vue => options/views/AmazonResultTable.vue} (92%) create mode 100644 src/router/index.ts rename src/sidepanel/{ => views/AmazonEntries}/DetailPageEntry.vue (100%) rename src/sidepanel/{ => views/AmazonEntries}/ReviewPageEntry.vue (100%) rename src/sidepanel/{ => views/AmazonEntries}/SearchPageEntry.vue (100%) rename src/sidepanel/{Sidepanel.vue => views/AmazonSidepanel.vue} (89%) create mode 100644 src/sidepanel/views/HomedepotSidepanel.vue diff --git a/package.json b/package.json index 3b5f8ac..8b6d08c 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "dayjs": "^1.11.13", "emittery": "^1.1.0", "esno": "^4.8.0", + "exceljs": "^4.4.0", "fs-extra": "^11.2.0", "husky": "^9.1.7", "jsdom": "^26.0.0", @@ -61,14 +62,12 @@ "vitest": "^3.1.1", "vue": "^3.5.13", "vue-demi": "^0.14.10", + "vue-router": "^4.5.1", "web-ext": "^8.5.0", "webext-bridge": "^6.0.1", "webextension-polyfill": "^0.12.0" }, "lint-staged": { "**/*": "prettier --write --ignore-unknown" - }, - "dependencies": { - "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eb2cca..32084dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,10 +6,6 @@ settings: importers: .: - dependencies: - xlsx: - specifier: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz - version: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz devDependencies: '@ffflorian/jszip-cli': specifier: ^3.8.2 @@ -53,6 +49,9 @@ importers: esno: specifier: ^4.8.0 version: 4.8.0 + exceljs: + specifier: ^4.4.0 + version: 4.4.0 fs-extra: specifier: ^11.2.0 version: 11.2.0 @@ -107,6 +106,9 @@ importers: vue-demi: specifier: ^0.14.10 version: 0.14.10(vue@3.5.13(typescript@5.8.2)) + vue-router: + specifier: ^4.5.1 + version: 4.5.1(vue@3.5.13(typescript@5.8.2)) web-ext: specifier: ^8.5.0 version: 8.5.0 @@ -743,6 +745,18 @@ packages: } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + '@fast-csv/format@4.3.5': + resolution: + { + integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==, + } + + '@fast-csv/parse@4.3.6': + resolution: + { + integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==, + } + '@ffflorian/jszip-cli@3.8.2': resolution: { @@ -1104,6 +1118,12 @@ packages: integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==, } + '@types/node@14.18.63': + resolution: + { + integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==, + } + '@types/node@22.10.5': resolution: { @@ -1224,6 +1244,12 @@ packages: integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==, } + '@vue/devtools-api@6.6.4': + resolution: + { + integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==, + } + '@vue/reactivity@3.5.13': resolution: { @@ -1432,6 +1458,13 @@ packages: } engines: { node: '>= 6' } + archiver-utils@3.0.4: + resolution: + { + integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==, + } + engines: { node: '>= 10' } + archiver@3.1.1: resolution: { @@ -1439,6 +1472,13 @@ packages: } engines: { node: '>= 6' } + archiver@5.3.2: + resolution: + { + integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==, + } + engines: { node: '>= 10' } + argparse@2.0.1: resolution: { @@ -1556,12 +1596,24 @@ packages: } engines: { node: '>=8' } + binary@0.3.0: + resolution: + { + integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==, + } + bl@4.1.0: resolution: { integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, } + bluebird@3.4.7: + resolution: + { + integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==, + } + bluebird@3.7.2: resolution: { @@ -1625,6 +1677,13 @@ packages: integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, } + buffer-indexof-polyfill@1.0.2: + resolution: + { + integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==, + } + engines: { node: '>=0.10' } + buffer@5.7.1: resolution: { @@ -1637,6 +1696,13 @@ packages: integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==, } + buffers@0.1.1: + resolution: + { + integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==, + } + engines: { node: '>=0.2.0' } + bundle-name@3.0.0: resolution: { @@ -1693,6 +1759,12 @@ packages: } engines: { node: '>=12' } + chainsaw@0.1.0: + resolution: + { + integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==, + } + chalk@2.4.2: resolution: { @@ -1897,6 +1969,13 @@ packages: } engines: { node: '>= 6' } + compress-commons@4.1.2: + resolution: + { + integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==, + } + engines: { node: '>= 10' } + concat-map@0.0.1: resolution: { @@ -1953,6 +2032,14 @@ packages: typescript: optional: true + crc-32@1.2.2: + resolution: + { + integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==, + } + engines: { node: '>=0.8' } + hasBin: true + crc32-stream@3.0.1: resolution: { @@ -1960,6 +2047,13 @@ packages: } engines: { node: '>= 6.9.0' } + crc32-stream@4.0.3: + resolution: + { + integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==, + } + engines: { node: '>= 10' } + crc@3.8.0: resolution: { @@ -2257,6 +2351,12 @@ packages: } engines: { node: '>= 0.4' } + duplexer2@0.1.4: + resolution: + { + integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==, + } + eastasianwidth@0.2.0: resolution: { @@ -2567,6 +2667,13 @@ packages: integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==, } + exceljs@4.4.0: + resolution: + { + integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==, + } + engines: { node: '>=8.3.0' } + execa@5.1.1: resolution: { @@ -2601,6 +2708,13 @@ packages: integrity: sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==, } + fast-csv@4.3.6: + resolution: + { + integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==, + } + engines: { node: '>=10.0.0' } + fast-deep-equal@3.1.3: resolution: { @@ -2757,6 +2871,14 @@ packages: engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] + fstream@1.0.12: + resolution: + { + integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==, + } + engines: { node: '>=0.6' } + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: { @@ -3710,6 +3832,12 @@ packages: engines: { node: '>=18.12.0' } hasBin: true + listenercount@1.0.1: + resolution: + { + integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==, + } + listr2@8.2.5: resolution: { @@ -3756,18 +3884,61 @@ packages: integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==, } + lodash.escaperegexp@4.1.2: + resolution: + { + integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==, + } + lodash.flatten@4.4.0: resolution: { integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==, } + lodash.groupby@4.6.0: + resolution: + { + integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==, + } + + lodash.isboolean@3.0.3: + resolution: + { + integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==, + } + + lodash.isequal@4.5.0: + resolution: + { + integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==, + } + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isfunction@3.0.9: + resolution: + { + integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==, + } + + lodash.isnil@4.0.0: + resolution: + { + integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==, + } + lodash.isplainobject@4.0.6: resolution: { integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==, } + lodash.isundefined@3.0.1: + resolution: + { + integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==, + } + lodash.merge@4.6.2: resolution: { @@ -3780,6 +3951,12 @@ packages: integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==, } + lodash.uniq@4.5.0: + resolution: + { + integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==, + } + lodash@4.17.21: resolution: { @@ -3917,6 +4094,13 @@ packages: integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, } + minimatch@5.1.6: + resolution: + { + integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, + } + engines: { node: '>=10' } + minimatch@9.0.1: resolution: { @@ -3944,6 +4128,13 @@ packages: } engines: { node: '>=16 || 14 >=14.17' } + mkdirp@0.5.6: + resolution: + { + integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==, + } + hasBin: true + mlly@1.7.4: resolution: { @@ -4567,6 +4758,12 @@ packages: } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + readdir-glob@1.1.3: + resolution: + { + integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==, + } + readdirp@3.6.0: resolution: { @@ -4683,6 +4880,14 @@ packages: integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, } + rimraf@2.7.1: + resolution: + { + integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==, + } + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@3.0.2: resolution: { @@ -4972,6 +5177,13 @@ packages: integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==, } + saxes@5.0.1: + resolution: + { + integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==, + } + engines: { node: '>=10' } + saxes@6.0.0: resolution: { @@ -5573,6 +5785,12 @@ packages: } engines: { node: '>=18' } + traverse@0.3.9: + resolution: + { + integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==, + } + treemate@0.3.11: resolution: { @@ -5781,6 +5999,12 @@ packages: } engines: { node: '>=8' } + unzipper@0.10.14: + resolution: + { + integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==, + } + upath@2.0.1: resolution: { @@ -5944,6 +6168,14 @@ packages: '@vue/composition-api': optional: true + vue-router@4.5.1: + resolution: + { + integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==, + } + peerDependencies: + vue: ^3.2.0 + vue@3.5.13: resolution: { @@ -6197,12 +6429,6 @@ packages: } engines: { node: '>=12' } - xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz: - resolution: { tarball: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz } - version: 0.20.3 - engines: { node: '>=0.8' } - hasBin: true - xml-name-validator@5.0.0: resolution: { @@ -6285,6 +6511,13 @@ packages: } engines: { node: '>= 6' } + zip-stream@4.1.1: + resolution: + { + integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==, + } + engines: { node: '>= 10' } + snapshots: '@antfu/install-pkg@1.0.0': dependencies: @@ -6542,6 +6775,25 @@ snapshots: '@eslint/js@8.57.1': {} + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@ffflorian/jszip-cli@3.8.2(typescript@5.8.2)': dependencies: commander: 12.1.0 @@ -6717,6 +6969,8 @@ snapshots: '@types/minimatch@3.0.5': {} + '@types/node@14.18.63': {} + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 @@ -6808,6 +7062,8 @@ snapshots: '@vue/compiler-dom': 3.5.13 '@vue/shared': 3.5.13 + '@vue/devtools-api@6.6.4': {} + '@vue/reactivity@3.5.13': dependencies: '@vue/shared': 3.5.13 @@ -6969,6 +7225,19 @@ snapshots: normalize-path: 3.0.0 readable-stream: 2.3.8 + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + archiver@3.1.1: dependencies: archiver-utils: 2.1.0 @@ -6979,6 +7248,16 @@ snapshots: tar-stream: 2.2.0 zip-stream: 2.1.3 + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + argparse@2.0.1: {} array-buffer-byte-length@1.0.2: @@ -7035,12 +7314,19 @@ snapshots: binary-extensions@2.3.0: {} + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + bluebird@3.4.7: {} + bluebird@3.7.2: {} boolbase@1.0.0: {} @@ -7079,6 +7365,8 @@ snapshots: buffer-from@1.1.2: {} + buffer-indexof-polyfill@1.0.2: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -7089,6 +7377,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffers@0.1.1: {} + bundle-name@3.0.0: dependencies: run-applescript: 5.0.0 @@ -7124,6 +7414,10 @@ snapshots: loupe: 3.1.2 pathval: 2.0.0 + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -7250,6 +7544,13 @@ snapshots: normalize-path: 3.0.0 readable-stream: 2.3.8 + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + concat-map@0.0.1: {} concat-stream@1.6.2: @@ -7286,11 +7587,18 @@ snapshots: optionalDependencies: typescript: 5.8.2 + crc-32@1.2.2: {} + crc32-stream@3.0.1: dependencies: crc: 3.8.0 readable-stream: 3.6.2 + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + crc@3.8.0: dependencies: buffer: 5.7.1 @@ -7465,6 +7773,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + eastasianwidth@0.2.0: {} editorconfig@1.0.4: @@ -7740,6 +8052,18 @@ snapshots: evtd@0.2.4: {} + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.13 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.3 + unzipper: 0.10.14 + uuid: 8.3.2 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -7780,6 +8104,11 @@ snapshots: exsolve@1.0.4: {} + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + fast-deep-equal@3.1.3: {} fast-json-patch@3.1.1: {} @@ -7863,6 +8192,13 @@ snapshots: fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -8402,6 +8738,8 @@ snapshots: transitivePeerDependencies: - supports-color + listenercount@1.0.1: {} + listr2@8.2.5: dependencies: cli-truncate: 4.0.0 @@ -8434,14 +8772,30 @@ snapshots: lodash.difference@4.5.0: {} + lodash.escaperegexp@4.1.2: {} + lodash.flatten@4.4.0: {} + lodash.groupby@4.6.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isequal@4.5.0: {} + + lodash.isfunction@3.0.9: {} + + lodash.isnil@4.0.0: {} + lodash.isplainobject@4.0.6: {} + lodash.isundefined@3.0.1: {} + lodash.merge@4.6.2: {} lodash.union@4.6.0: {} + lodash.uniq@4.5.0: {} + lodash@4.17.21: {} log-update@6.1.0: @@ -8503,6 +8857,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.1: dependencies: brace-expansion: 2.0.1 @@ -8515,6 +8873,10 @@ snapshots: minipass@7.1.2: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mlly@1.7.4: dependencies: acorn: 8.14.1 @@ -8908,6 +9270,10 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -8973,6 +9339,10 @@ snapshots: rfdc@1.4.1: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -9143,6 +9513,10 @@ snapshots: sax@1.4.1: {} + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -9469,6 +9843,8 @@ snapshots: dependencies: punycode: 2.3.1 + traverse@0.3.9: {} + treemate@0.3.11: {} tslib@2.8.1: {} @@ -9610,6 +9986,19 @@ snapshots: untildify@4.0.0: {} + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + upath@2.0.1: {} update-notifier@7.3.1: @@ -9728,6 +10117,11 @@ snapshots: dependencies: vue: 3.5.13(typescript@5.8.2) + vue-router@4.5.1(vue@3.5.13(typescript@5.8.2)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.8.2) + vue@3.5.13(typescript@5.8.2): dependencies: '@vue/compiler-dom': 3.5.13 @@ -9924,8 +10318,6 @@ snapshots: xdg-basedir@5.1.0: {} - xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz: {} - xml-name-validator@5.0.0: {} xml2js@0.6.2: @@ -9970,3 +10362,9 @@ snapshots: archiver-utils: 2.1.0 compress-commons: 2.1.1 readable-stream: 3.6.2 + + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 diff --git a/shim.d.ts b/shim.d.ts index 5019d15..e96ad6d 100644 --- a/shim.d.ts +++ b/shim.d.ts @@ -6,3 +6,7 @@ declare module 'webext-bridge' { // see https://github.com/antfu/webext-bridge#type-safe-protocols } } + +declare global { + type AppContext = 'options' | 'sidepanel'; +} diff --git a/src/composables/useAppContext.ts b/src/composables/useAppContext.ts new file mode 100644 index 0000000..068fc30 --- /dev/null +++ b/src/composables/useAppContext.ts @@ -0,0 +1,4 @@ +export function useAppContext() { + const appContext = document.location.pathname.split('/')[2] as AppContext; + return { appContext }; +} diff --git a/src/composables/useCurrentUrl.ts b/src/composables/useCurrentUrl.ts index 5c90301..15146d0 100644 --- a/src/composables/useCurrentUrl.ts +++ b/src/composables/useCurrentUrl.ts @@ -1,9 +1,9 @@ export function useCurrentUrl() { - const currentUrl = ref(''); + const currentUrl = ref(undefined); const updateUrl = async () => { const tab = await browser.tabs.query({ active: true, currentWindow: true }).then((ts) => ts[0]); - currentUrl.value = tab.url || ''; + currentUrl.value = tab.url || undefined; }; onMounted(() => { @@ -17,5 +17,5 @@ export function useCurrentUrl() { browser.tabs.onHighlighted.removeListener(updateUrl); }); - return { currentUrl }; + return currentUrl; } diff --git a/src/composables/usePageContext.ts b/src/composables/usePageContext.ts deleted file mode 100644 index 6202dfc..0000000 --- a/src/composables/usePageContext.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function usePageContext() { - const pageContext = document.location.pathname.split('/')[2] as 'sidepanel' | 'options'; - return { pageContext }; -} diff --git a/src/composables/useWebExtensionStorage.ts b/src/composables/useWebExtensionStorage.ts index 61db945..6f28c7e 100644 --- a/src/composables/useWebExtensionStorage.ts +++ b/src/composables/useWebExtensionStorage.ts @@ -6,7 +6,7 @@ import { storage } from 'webextension-polyfill'; import type { RemovableRef, StorageLikeAsync, UseStorageAsyncOptions } from '@vueuse/core'; import type { Ref } from 'vue-demi'; import type { Storage } from 'webextension-polyfill'; -import { usePageContext } from './usePageContext'; +import { useAppContext } from './useAppContext'; export type WebExtensionStorageOptions = UseStorageAsyncOptions; @@ -121,7 +121,7 @@ export function useWebExtensionStorage( return; } if (typeof listenToStorageChanges === 'string') { - const { pageContext: context } = usePageContext(); + const { appContext: context } = useAppContext(); if (listenToStorageChanges !== context) { return; } diff --git a/src/logic/common-setup.ts b/src/logic/common-setup.ts index 58acae3..cbd5a60 100644 --- a/src/logic/common-setup.ts +++ b/src/logic/common-setup.ts @@ -1,12 +1,13 @@ import type { App } from 'vue'; -import { usePageContext } from '~/composables/usePageContext'; +import { useAppContext } from '~/composables/useAppContext'; +import { router } from '~/router'; /** * Setup Vue app * @param app Vue app */ export function setupApp(app: App) { - const { pageContext: context } = usePageContext(); + const { appContext: context } = useAppContext(); // Inject a globally available `$app` object in template app.config.globalProperties.$app = { @@ -19,4 +20,6 @@ export function setupApp(app: App) { // Here you can install additional plugins for all contexts: popup, options page and content-script. // example: app.use(i18n) // example excluding content-script context: if (context !== 'content-script') app.use(i18n) + app.use(router); + return app; } diff --git a/src/logic/data-io.ts b/src/logic/data-io.ts index b06be1e..16b3a3b 100644 --- a/src/logic/data-io.ts +++ b/src/logic/data-io.ts @@ -1,97 +1,144 @@ -import { utils, read, writeFileXLSX, WorkSheet, WorkBook } from 'xlsx'; +import excel from 'exceljs'; class Worksheet { - readonly _raw: WorkSheet; + readonly _ws: excel.Worksheet; + readonly workbook: Workbook; - constructor(ws: WorkSheet) { - this._raw = ws; + constructor(ws: excel.Worksheet, wb: Workbook) { + this._ws = ws; + this.workbook = wb; } - static fromJson(data: Record[], options: { headers?: Header[] } = {}) { + async readJson(data: Record[], options: { headers?: Header[] } = {}) { const { headers = data.length > 0 ? Object.keys(data[0]).map((k) => ({ label: k, prop: k }) as Header) : [], } = options; - const rows = data.map((item) => { - const row: Record = {}; - headers.forEach((header) => { - const value = getAttribute(item, header.prop); - if (header.formatOutputValue) { - row[header.label] = header.formatOutputValue(value); - } else if (['string', 'number', 'bigint', 'boolean'].includes(typeof value)) { - row[header.label] = value; - } else { - row[header.label] = JSON.stringify(value); + const rows = await Promise.all( + data.map(async (item, i) => { + const record: Record = {}; + const cols = headers.filter((h) => h.ignore?.out !== true); + for (let j = 0; j < cols.length; j++) { + const header = cols[j]; + if (header.ignore?.out) { + continue; + } + const value = getAttribute(item, header.prop); + if (header.formatOutputValue) { + record[header.label] = await header.formatOutputValue(value, i); + } else if (['string', 'number', 'bigint', 'boolean'].includes(typeof value)) { + record[header.label] = value; + } else { + record[header.label] = JSON.stringify(value); + } } - }); - return row; + return record; + }), + ); + + this._ws.columns = headers.map((e) => { + return { header: e.label, key: e.label }; }); - const ws = utils.json_to_sheet(rows, { - header: headers.map((h) => h.label), - }); - ws['!autofilter'] = { - ref: utils.encode_range({ c: 0, r: 0 }, { c: headers.length - 1, r: rows.length }), - }; // Use Auto Filter: https://github.com/SheetJS/sheetjs/issues/472#issuecomment-292852308 - return new Worksheet(ws); + this._ws.addRows(rows); + this._ws.autoFilter = { + from: { + row: 1, + column: 1, + }, + to: { + row: rows.length + 1, + column: headers.length, + }, + }; } - toJson(options: { headers?: Header[] } = {}) { + async toJson(options: { headers?: Header[] } = {}) { const { headers } = options; - let jsonData = utils.sheet_to_json>(this._raw); - if (headers) { - jsonData = jsonData.map((item) => { - const mappedItem: Record = {}; - headers.forEach((header) => { - const value = header.parseImportValue - ? header.parseImportValue(item[header.label]) - : item[header.label]; - setAttribute(mappedItem, header.prop, value); - }); - return mappedItem; + let jsonData: Record[] = []; + this._ws.eachRow((row) => { + const rowData: Record = {}; + row.eachCell((cell, colNumber) => { + const header = this._ws.getRow(1).getCell(colNumber).value?.toString()!; + rowData[header] = cell.value; }); + jsonData.push(rowData); + }); + jsonData = jsonData.slice(1); // Remove Headers + if (headers) { + jsonData = await Promise.all( + jsonData.map(async (item, i) => { + const mappedItem: Record = {}; + for (const header of headers) { + if (header.ignore?.in) { + continue; + } + const value = header.parseImportValue + ? await header.parseImportValue(item[header.label], i) + : item[header.label]; + setAttribute(mappedItem, header.prop, value); + } + return mappedItem; + }), + ); } return jsonData as T[]; } - toWorkbook(sheetName?: string) { - const wb = new Workbook(utils.book_new()); - wb.addSheet(sheetName || 'Sheet1', this); - return wb; + async addImage(img: { data: ArrayBuffer; ext: 'jpeg' | 'png' | 'gif' }) { + const imgId = this.workbook._wb.addImage({ + buffer: img.data, + extension: img.ext, + }); + return imgId; } } class Workbook { - readonly _raw: WorkBook; + _wb: excel.Workbook; - constructor(wb: WorkBook) { - this._raw = wb; + constructor(wb: excel.Workbook) { + this._wb = wb; } get sheetCount() { - return this._raw.SheetNames.length; + return this._wb.worksheets.length; } - static fromArrayBuffer(bf: ArrayBuffer) { - const data = new Uint8Array(bf); - const wb = read(data, { type: 'array' }); - return new Workbook(wb); + static createWorkbook() { + return new Workbook(new excel.Workbook()); } - getSheet(index: number) { - const sheetName = this._raw.SheetNames[index]; - return new Worksheet(this._raw.Sheets[sheetName]); + async loadArrayBuffer(bf: ArrayBuffer) { + this._wb = await this._wb.xlsx.load(bf); } - addSheet(name: string, sheet: Worksheet) { - utils.book_append_sheet(this._raw, sheet._raw, name); + getSheet(index: number): Worksheet | undefined { + const ws = this._wb.getWorksheet(index + 1); // Align the index + return ws ? new Worksheet(ws, this) : undefined; } - exportFile(fileName: string) { - writeFileXLSX(this._raw, fileName, { bookType: 'xlsx', type: 'binary', compression: true }); + addSheet(name?: string) { + const ws = this._wb.addWorksheet(name); + return new Worksheet(ws, this); + } + + async exportFile(fileName: string) { + const bf = await this._wb.xlsx.writeBuffer(); + const blob = new Blob([bf], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); } } @@ -131,12 +178,17 @@ function setAttribute(obj: Record, path: string, value: unknown export type Header = { label: string; prop: string; - parseImportValue?: (val: any) => any; - formatOutputValue?: (val: any) => any; + parseImportValue?: (val: any, index: number) => any; + formatOutputValue?: (val: any, index: number) => any; + ignore?: { + in?: boolean; + out?: boolean; + }; }; export type ExportBaseOptions = { fileName?: string; + sheetName?: string; headers?: Header[]; }; @@ -149,31 +201,20 @@ export type ImportBaseOptions = { * @param data 数据数组 * @param options 导出选项 */ -export function exportToXLSX( +export async function exportToXLSX( data: Record[], - options?: ExportBaseOptions & { asWorkSheet?: false }, -): void; -export function exportToXLSX( - data: Record[], - options: Omit & { asWorkSheet: true }, -): Worksheet; -export function exportToXLSX( - data: Record[], - options: ExportBaseOptions & { asWorkSheet?: boolean } = {}, + options: ExportBaseOptions = {}, ) { const { headers, + sheetName, fileName = `export_${new Date().toISOString().slice(0, 10)}.xlsx`, - asWorkSheet, } = options; - const worksheet = Worksheet.fromJson(data, { headers: headers }); - - if (asWorkSheet) { - return worksheet; - } - const workbook = worksheet.toWorkbook(); - workbook.exportFile(fileName); + const workbook = Workbook.createWorkbook(); + const worksheet = workbook.addSheet(sheetName); + await worksheet.readJson(data, { headers }); + await workbook.exportFile(fileName); } /** @@ -198,13 +239,15 @@ export async function importFromXLSX>( reader.onload = (event) => { try { - const wb = Workbook.fromArrayBuffer(event.target?.result as ArrayBuffer); - if (asWorkBook) { - resolve(wb); - } - const ws = wb.getSheet(0); // 默认读取第一个工作表 - const jsonData = ws.toJson({ headers }); - resolve(jsonData); + const wb = Workbook.createWorkbook(); + wb.loadArrayBuffer(event.target?.result as ArrayBuffer).then(() => { + if (asWorkBook) { + resolve(wb); + } else { + const ws = wb.getSheet(0)!; // 默认读取第一个工作表 + resolve(ws.toJson({ headers })); + } + }); } catch (error) { reject(error); } @@ -215,3 +258,10 @@ export async function importFromXLSX>( reader.readAsArrayBuffer(file); }); } + +/** + * 创建Excel文件对象 + */ +export function createWorkbook() { + return Workbook.createWorkbook(); +} diff --git a/src/logic/execute-script.ts b/src/logic/execute-script.ts index eccb9df..0710cc6 100644 --- a/src/logic/execute-script.ts +++ b/src/logic/execute-script.ts @@ -21,20 +21,29 @@ * console.log(result); // Outputs: 42 * ``` */ -export async function exec(tabId: number, func: () => Promise): Promise; +type ExecOptions = { + timeout?: number; +}; + +export async function exec( + tabId: number, + func: () => Promise, + payload?: undefined, + options?: ExecOptions, +): Promise; export async function exec>( tabId: number, func: (payload: P) => Promise, payload: P, + options?: ExecOptions, ): Promise; export async function exec>( tabId: number, func: (payload?: P) => Promise, payload?: P, + options: ExecOptions = {}, ): Promise { - const { timeout } = { - timeout: 30000, - }; + const { timeout = 30000 } = options; return new Promise(async (resolve, reject) => { setTimeout(() => reject('脚本运行超时'), timeout); try { diff --git a/src/logic/page-worker/homedepot.ts b/src/logic/page-worker/homedepot.ts index 8dbf648..d4e65f7 100644 --- a/src/logic/page-worker/homedepot.ts +++ b/src/logic/page-worker/homedepot.ts @@ -1,7 +1,6 @@ import Emittery from 'emittery'; import { HomedepotEvents, HomedepotWorker } from './types'; import { Tabs } from 'webextension-polyfill'; -import { isForbiddenUrl } from '~/env'; import { withErrorHandling } from '../error-handler'; import { HomedepotDetailPageInjector } from '../web-injectors'; @@ -19,13 +18,6 @@ class HomedepotWorkerImpl implements HomedepotWorker { private readonly _controlChannel = new Emittery<{ interrupt: undefined }>(); - private async getCurrentTab(): Promise { - const tab = await browser.tabs - .query({ active: true, currentWindow: true }) - .then((tabs) => tabs[0]); - return tab; - } - private async createNewTab(url?: string): Promise { const tab = await browser.tabs.create({ url, active: true }); return tab; @@ -34,16 +26,14 @@ class HomedepotWorkerImpl implements HomedepotWorker { @withErrorHandling private async wanderingDetailPage(OSMID: string) { const url = `https://www.homedepot.com/p/${OSMID}`; - let tab = await this.getCurrentTab(); - if (!tab.url || isForbiddenUrl(tab.url)) { - tab = await this.createNewTab(url); - } else { - await browser.tabs.update(tab.id!, { url }); - } + const tab = await this.createNewTab(url); const injector = new HomedepotDetailPageInjector(tab); await injector.waitForPageLoad(); const info = await injector.getInfo(); this.channel.emit('detail-item-collected', { item: { OSMID, ...info } }); + setTimeout(() => { + browser.tabs.remove(tab.id!); + }, 1000); } async runDetailPageTask( diff --git a/src/logic/page-worker/types.d.ts b/src/logic/page-worker/types.d.ts index 0359acc..4d68c7f 100644 --- a/src/logic/page-worker/types.d.ts +++ b/src/logic/page-worker/types.d.ts @@ -42,12 +42,12 @@ type AmazonItem = Pick & type HomedepotDetailItem = { OSMID: string; link: string; - brandName: string; + brandName?: string; title: string; price: string; - rate: string; + rate?: string; innerText: string; - reviewCount: number; + reviewCount?: number; mainImageUrl: string; }; diff --git a/src/logic/web-injectors.ts b/src/logic/web-injectors.ts index a9d77da..4cd6909 100644 --- a/src/logic/web-injectors.ts +++ b/src/logic/web-injectors.ts @@ -5,15 +5,18 @@ import type { AmazonReview, AmazonSearchItem, HomedepotDetailItem } from './page class BaseInjector { readonly _tab: Tabs.Tab; - constructor(tab: Tabs.Tab) { + readonly _timeout: number; + + constructor(tab: Tabs.Tab, timeout: number = 30000) { this._tab = tab; + this._timeout = timeout; } run>( func: (payload: P) => Promise, payload?: P, ): Promise { - return exec(this._tab.id!, func, payload as P); + return exec(this._tab.id!, func, payload as P, { timeout: this._timeout }); } } @@ -443,23 +446,34 @@ export class AmazonReviewPageInjector extends BaseInjector { } export class HomedepotDetailPageInjector extends BaseInjector { + constructor(tab: Tabs.Tab) { + super(tab, 60000); + } + public waitForPageLoad() { return this.run(async () => { + let timeout = false; + setTimeout(() => (timeout = true), 15000); + const isLoaded = () => { + const reviewPlaceholderEl = document.querySelector( + `#product-section-rr div[role='button'][aria-expanded='true']`, + ); + return reviewPlaceholderEl && (document.readyState == 'complete' || timeout); + }; while (true) { + await new Promise((resolve) => setTimeout(resolve, 500 + ~~(Math.random() * 500))); document .querySelector( - `#product-section-overview div[role='button'][aria-expanded='false']`, + `#product-section-rr div[role='button'][aria-expanded='false'], #product-section-overview div[role='button'][aria-expanded='false']`, ) ?.click(); - const reviewPlaceholderEl = document.querySelector( - `[data-component^="ratings-and-reviews"] [class^="placeholder"]`, - ); - reviewPlaceholderEl?.scrollIntoView({ behavior: 'smooth' }); - if (document.readyState === 'complete' && !reviewPlaceholderEl) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - document - .querySelector(`#product-section-rr`) - ?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + const { scrollHeight, scrollTop } = document.documentElement; + scrollHeight - scrollTop > 100 + ? window.scrollBy({ top: 100, behavior: 'smooth' }) + : document + .querySelector('[data-component^="product-details:ProductDetailsTitle"]') + ?.scrollIntoView({ behavior: 'smooth' }); + if (isLoaded()) { break; } } @@ -471,22 +485,26 @@ export class HomedepotDetailPageInjector extends BaseInjector { const link = document.location.toString(); const brandName = document.querySelector( `[data-component^="product-details:ProductDetailsBrandCollection"]`, - )!.innerText; + )?.innerText; const title = document.querySelector( `[data-component^="product-details:ProductDetailsTitle"]`, )!.innerText; - const price = document.querySelector(`#standard-price`)!.innerText; - const rate = /\d\.\d/.exec( - document.querySelector(`[data-component^="ratings-and-reviews"] .sui-mr-1`)! - .innerText, - )![0]; - const reviewCount = Number( - /[\d]+/.exec( - document.querySelector( - `[data-component^="ratings-and-reviews"] button > span:last-child`, - )!.innerText, - )![0], + const price = document + .querySelector(`#standard-price`)! + .innerText.replaceAll('\n', ''); + const rateEl = document.querySelector( + `[data-component^="ratings-and-reviews"] .sui-mr-1`, ); + const rate = rateEl ? /\d(\.\d)?/.exec(rateEl.innerText)![0] : undefined; + const reviewCount = rateEl + ? Number( + /\d+/.exec( + document.querySelector( + `[data-component^="ratings-and-reviews"] [name="simple-rating"] + span`, + )!.innerText, + )![0], + ) + : undefined; const mainImageUrl = document.querySelector( `.mediagallery__mainimage img`, )!.src; diff --git a/src/options/Options.vue b/src/options/Options.vue index 851e474..17182af 100644 --- a/src/options/Options.vue +++ b/src/options/Options.vue @@ -1,9 +1,12 @@ - + @@ -13,6 +16,10 @@ main { flex-direction: column; align-items: center; + .header-title { + cursor: default; + } + .result-table { height: 90vh; width: 95vw; diff --git a/src/components/ResultTable.vue b/src/options/views/AmazonResultTable.vue similarity index 92% rename from src/components/ResultTable.vue rename to src/options/views/AmazonResultTable.vue index d42cc4c..02015c4 100644 --- a/src/components/ResultTable.vue +++ b/src/options/views/AmazonResultTable.vue @@ -1,11 +1,11 @@