mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-01-19 13:13:22 +08:00
Update
This commit is contained in:
parent
a240242331
commit
0948413460
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
422
pnpm-lock.yaml
generated
422
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
4
shim.d.ts
vendored
4
shim.d.ts
vendored
@ -6,3 +6,7 @@ declare module 'webext-bridge' {
|
||||
// see https://github.com/antfu/webext-bridge#type-safe-protocols
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
type AppContext = 'options' | 'sidepanel';
|
||||
}
|
||||
|
||||
4
src/composables/useAppContext.ts
Normal file
4
src/composables/useAppContext.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export function useAppContext() {
|
||||
const appContext = document.location.pathname.split('/')[2] as AppContext;
|
||||
return { appContext };
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
export function useCurrentUrl() {
|
||||
const currentUrl = ref('');
|
||||
const currentUrl = ref<string | undefined>(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;
|
||||
}
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
export function usePageContext() {
|
||||
const pageContext = document.location.pathname.split('/')[2] as 'sidepanel' | 'options';
|
||||
return { pageContext };
|
||||
}
|
||||
@ -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<T> = UseStorageAsyncOptions<T>;
|
||||
|
||||
@ -121,7 +121,7 @@ export function useWebExtensionStorage<T>(
|
||||
return;
|
||||
}
|
||||
if (typeof listenToStorageChanges === 'string') {
|
||||
const { pageContext: context } = usePageContext();
|
||||
const { appContext: context } = useAppContext();
|
||||
if (listenToStorageChanges !== context) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<string, unknown>[], options: { headers?: Header[] } = {}) {
|
||||
async readJson(data: Record<string, unknown>[], 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<string, unknown> = {};
|
||||
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<string, unknown> = {};
|
||||
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<T>(options: { headers?: Header[] } = {}) {
|
||||
async toJson<T>(options: { headers?: Header[] } = {}) {
|
||||
const { headers } = options;
|
||||
|
||||
let jsonData = utils.sheet_to_json<Record<string, unknown>>(this._raw);
|
||||
if (headers) {
|
||||
jsonData = jsonData.map((item) => {
|
||||
const mappedItem: Record<string, unknown> = {};
|
||||
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<string, unknown>[] = [];
|
||||
this._ws.eachRow((row) => {
|
||||
const rowData: Record<string, unknown> = {};
|
||||
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<string, unknown> = {};
|
||||
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<string, unknown>, 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<string, unknown>[],
|
||||
options?: ExportBaseOptions & { asWorkSheet?: false },
|
||||
): void;
|
||||
export function exportToXLSX(
|
||||
data: Record<string, unknown>[],
|
||||
options: Omit<ExportBaseOptions, 'fileName'> & { asWorkSheet: true },
|
||||
): Worksheet;
|
||||
export function exportToXLSX(
|
||||
data: Record<string, unknown>[],
|
||||
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<T extends Record<string, unknown>>(
|
||||
|
||||
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<T>({ 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<T>({ headers }));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
@ -215,3 +258,10 @@ export async function importFromXLSX<T extends Record<string, unknown>>(
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Excel文件对象
|
||||
*/
|
||||
export function createWorkbook() {
|
||||
return Workbook.createWorkbook();
|
||||
}
|
||||
|
||||
@ -21,20 +21,29 @@
|
||||
* console.log(result); // Outputs: 42
|
||||
* ```
|
||||
*/
|
||||
export async function exec<T>(tabId: number, func: () => Promise<T>): Promise<T>;
|
||||
type ExecOptions = {
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export async function exec<T>(
|
||||
tabId: number,
|
||||
func: () => Promise<T>,
|
||||
payload?: undefined,
|
||||
options?: ExecOptions,
|
||||
): Promise<T>;
|
||||
export async function exec<T, P extends Record<string, unknown>>(
|
||||
tabId: number,
|
||||
func: (payload: P) => Promise<T>,
|
||||
payload: P,
|
||||
options?: ExecOptions,
|
||||
): Promise<T>;
|
||||
export async function exec<T, P extends Record<string, unknown>>(
|
||||
tabId: number,
|
||||
func: (payload?: P) => Promise<T>,
|
||||
payload?: P,
|
||||
options: ExecOptions = {},
|
||||
): Promise<T> {
|
||||
const { timeout } = {
|
||||
timeout: 30000,
|
||||
};
|
||||
const { timeout = 30000 } = options;
|
||||
return new Promise<T>(async (resolve, reject) => {
|
||||
setTimeout(() => reject('脚本运行超时'), timeout);
|
||||
try {
|
||||
|
||||
@ -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<Tabs.Tab> {
|
||||
const tab = await browser.tabs
|
||||
.query({ active: true, currentWindow: true })
|
||||
.then((tabs) => tabs[0]);
|
||||
return tab;
|
||||
}
|
||||
|
||||
private async createNewTab(url?: string): Promise<Tabs.Tab> {
|
||||
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(
|
||||
|
||||
6
src/logic/page-worker/types.d.ts
vendored
6
src/logic/page-worker/types.d.ts
vendored
@ -42,12 +42,12 @@ type AmazonItem = Pick<AmazonSearchItem, 'asin'> &
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@ -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<T, P extends Record<string, unknown>>(
|
||||
func: (payload: P) => Promise<T>,
|
||||
payload?: P,
|
||||
): Promise<T> {
|
||||
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<HTMLElement>(
|
||||
`#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<HTMLDivElement>(
|
||||
`[data-component^="product-details:ProductDetailsBrandCollection"]`,
|
||||
)!.innerText;
|
||||
)?.innerText;
|
||||
const title = document.querySelector<HTMLDivElement>(
|
||||
`[data-component^="product-details:ProductDetailsTitle"]`,
|
||||
)!.innerText;
|
||||
const price = document.querySelector<HTMLDivElement>(`#standard-price`)!.innerText;
|
||||
const rate = /\d\.\d/.exec(
|
||||
document.querySelector<HTMLDivElement>(`[data-component^="ratings-and-reviews"] .sui-mr-1`)!
|
||||
.innerText,
|
||||
)![0];
|
||||
const reviewCount = Number(
|
||||
/[\d]+/.exec(
|
||||
document.querySelector<HTMLDivElement>(
|
||||
`[data-component^="ratings-and-reviews"] button > span:last-child`,
|
||||
)!.innerText,
|
||||
)![0],
|
||||
const price = document
|
||||
.querySelector<HTMLDivElement>(`#standard-price`)!
|
||||
.innerText.replaceAll('\n', '');
|
||||
const rateEl = document.querySelector<HTMLDivElement>(
|
||||
`[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<HTMLDivElement>(
|
||||
`[data-component^="ratings-and-reviews"] [name="simple-rating"] + span`,
|
||||
)!.innerText,
|
||||
)![0],
|
||||
)
|
||||
: undefined;
|
||||
const mainImageUrl = document.querySelector<HTMLImageElement>(
|
||||
`.mediagallery__mainimage img`,
|
||||
)!.src;
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>采集结果</h1>
|
||||
<result-table class="result-table" />
|
||||
<h1 class="header-title">采集结果</h1>
|
||||
<!-- <result-table class="result-table" /> -->
|
||||
<router-view />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@ -13,6 +16,10 @@ main {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.header-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.result-table {
|
||||
height: 90vh;
|
||||
width: 95vw;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NSpace } from 'naive-ui';
|
||||
import type { TableColumn } from 'naive-ui/es/data-table/src/interface';
|
||||
import { exportToXLSX, Header, importFromXLSX } from '~/logic/data-io';
|
||||
import { createWorkbook, Header, importFromXLSX } from '~/logic/data-io';
|
||||
import type { AmazonDetailItem, AmazonItem, AmazonReview } from '~/logic/page-worker/types';
|
||||
import { allItems, reviewItems } from '~/logic/storage';
|
||||
import DetailDescription from './DetailDescription.vue';
|
||||
import ReviewPreview from './ReviewPreview.vue';
|
||||
import DetailDescription from '~/components/DetailDescription.vue';
|
||||
import ReviewPreview from '~/components/ReviewPreview.vue';
|
||||
|
||||
const message = useMessage();
|
||||
const modal = useModal();
|
||||
@ -243,11 +243,12 @@ const handleExport = async () => {
|
||||
return a;
|
||||
}, []);
|
||||
|
||||
const sheet1 = exportToXLSX(items, { headers: itemHeaders, asWorkSheet: true });
|
||||
const wb = sheet1.toWorkbook('items');
|
||||
const sheet2 = exportToXLSX(reviews, { headers: reviewHeaders, asWorkSheet: true });
|
||||
wb.addSheet('reviews', sheet2);
|
||||
wb.exportFile(`Items ${dayjs().format('YYYY-MM-DD')}.xlsx`);
|
||||
const wb = createWorkbook();
|
||||
const sheet1 = wb.addSheet('items');
|
||||
await sheet1.readJson(items, { headers: itemHeaders });
|
||||
const sheet2 = wb.addSheet('reviews');
|
||||
await sheet2.readJson(reviews, { headers: reviewHeaders });
|
||||
await wb.exportFile(`Items ${dayjs().format('YYYY-MM-DD')}.xlsx`);
|
||||
message.info('导出完成');
|
||||
};
|
||||
|
||||
@ -255,13 +256,15 @@ const handleImport = async (file: File) => {
|
||||
const itemHeaders = getItemHeaders();
|
||||
const wb = await importFromXLSX(file, { asWorkBook: true });
|
||||
|
||||
const sheet1 = wb.getSheet(0);
|
||||
const items = sheet1.toJson<AmazonItem>({ headers: itemHeaders });
|
||||
const sheet1 = wb.getSheet(0)!;
|
||||
const items = await sheet1.toJson<AmazonItem>({ headers: itemHeaders });
|
||||
allItems.value = items;
|
||||
|
||||
if (wb.sheetCount > 1) {
|
||||
const sheet2 = wb.getSheet(1);
|
||||
const reviews = sheet2.toJson<AmazonReview & { asin?: string }>({ headers: reviewHeaders });
|
||||
const sheet2 = wb.getSheet(1)!;
|
||||
const reviews = await sheet2.toJson<AmazonReview & { asin?: string }>({
|
||||
headers: reviewHeaders,
|
||||
});
|
||||
reviewItems.value = reviews.reduce((m, r) => {
|
||||
const asin = r.asin!;
|
||||
delete r.asin;
|
||||
27
src/router/index.ts
Normal file
27
src/router/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Plugin } from 'vue';
|
||||
import { createRouter, createMemoryHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { useAppContext } from '~/composables/useAppContext';
|
||||
|
||||
const routeObj: Record<AppContext, RouteRecordRaw[]> = {
|
||||
options: [
|
||||
{ path: '/', redirect: '/amazon' },
|
||||
{ path: '/amazon', component: () => import('~/options/views/AmazonResultTable.vue') },
|
||||
],
|
||||
sidepanel: [
|
||||
{ path: '/', redirect: '/amazon' },
|
||||
{ path: '/amazon', component: () => import('~/sidepanel/views/AmazonSidepanel.vue') },
|
||||
{ path: '/homedepot', component: () => import('~/sidepanel/views/HomedepotSidepanel.vue') },
|
||||
],
|
||||
};
|
||||
|
||||
export const router: Plugin = {
|
||||
install(app) {
|
||||
const { appContext: context } = useAppContext();
|
||||
const routes = routeObj[context];
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes,
|
||||
});
|
||||
app.use(router);
|
||||
},
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { GlobalThemeOverrides } from 'naive-ui';
|
||||
import SidePanel from './Sidepanel.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useCurrentUrl } from '~/composables/useCurrentUrl';
|
||||
|
||||
const theme: GlobalThemeOverrides = {
|
||||
common: {
|
||||
@ -10,6 +11,25 @@ const theme: GlobalThemeOverrides = {
|
||||
primaryColorSuppl: '#003366',
|
||||
},
|
||||
};
|
||||
|
||||
const currentUrl = useCurrentUrl();
|
||||
const router = useRouter();
|
||||
|
||||
watch(currentUrl, (newVal) => {
|
||||
if (newVal) {
|
||||
const url = new URL(newVal);
|
||||
switch (url.hostname) {
|
||||
case 'www.amazon.com':
|
||||
router.push('/amazon');
|
||||
break;
|
||||
case 'www.homedepot.com':
|
||||
router.push('/homedepot');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -18,7 +38,7 @@ const theme: GlobalThemeOverrides = {
|
||||
<n-message-provider>
|
||||
<n-dialog-provider>
|
||||
<n-modal-provider>
|
||||
<side-panel />
|
||||
<router-view />
|
||||
</n-modal-provider>
|
||||
</n-dialog-provider>
|
||||
</n-message-provider>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import DetailPageEntry from './DetailPageEntry.vue';
|
||||
import SearchPageEntry from './SearchPageEntry.vue';
|
||||
import ReviewPageEntry from './ReviewPageEntry.vue';
|
||||
import DetailPageEntry from './AmazonEntries/DetailPageEntry.vue';
|
||||
import SearchPageEntry from './AmazonEntries/SearchPageEntry.vue';
|
||||
import ReviewPageEntry from './AmazonEntries/ReviewPageEntry.vue';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
@ -17,6 +17,7 @@ const tabs = [
|
||||
component: ReviewPageEntry,
|
||||
},
|
||||
];
|
||||
|
||||
const selectedTab = ref(tabs[0].name);
|
||||
const currentComponent = computed(() => {
|
||||
const tab = tabs.find((tab) => tab.name === selectedTab.value);
|
||||
32
src/sidepanel/views/HomedepotSidepanel.vue
Normal file
32
src/sidepanel/views/HomedepotSidepanel.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { homedepot } from '~/logic/page-worker';
|
||||
|
||||
const inputText = ref('');
|
||||
const output = ref(undefined);
|
||||
|
||||
const worker = homedepot.useHomedepotWorker();
|
||||
worker.channel.on('detail-item-collected', ({ item }) => {
|
||||
output.value = item;
|
||||
});
|
||||
|
||||
const handleStart = () => {
|
||||
worker.runDetailPageTask(inputText.value.split('\n').filter((id) => /\d+/.exec(id)));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="homedepot-sidepanel">
|
||||
<h3>Hello World!</h3>
|
||||
<n-input type="textarea" v-model:value="inputText" />
|
||||
<n-button @click="handleStart">Test!</n-button>
|
||||
<n-code word-wrap :code="JSON.stringify(output)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.homedepot-sidepanel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user