├── .browserslistrc ├── src ├── style.less ├── assets │ └── logo.png ├── App.vue ├── store │ └── index.js ├── router │ └── index.js ├── main.js ├── electron │ ├── preload.js │ ├── config.js │ └── server.js ├── background.js └── views │ └── Qrcode.vue ├── public ├── favicon.ico └── index.html ├── babel.config.js ├── .gitignore ├── README.md ├── .eslintrc.js ├── vue.config.js └── package.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /src/style.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iHunterDev/one_cart_qrcode/main/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iHunterDev/one_cart_qrcode/main/src/assets/logo.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | export default createStore({ 4 | state: { 5 | }, 6 | mutations: { 7 | }, 8 | actions: { 9 | }, 10 | modules: { 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router' 2 | 3 | const routes = [ 4 | { 5 | path: '/', 6 | component: () => import('@/views/Qrcode.vue') 7 | } 8 | ] 9 | 10 | const router = createRouter({ 11 | history: createWebHashHistory(), 12 | routes 13 | }) 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.less' 3 | import App from './App.vue' 4 | import router from './router' 5 | import store from './store' 6 | import TDesign from 'tdesign-vue-next'; 7 | 8 | // 引入组件库全局样式资源 9 | import 'tdesign-vue-next/es/style/index.css'; 10 | 11 | createApp(App).use(store).use(router).use(TDesign).mount('#app') 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | #Electron-builder output 26 | /dist_electron -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # one_cart_qrcode_app 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-undef': 'off', 17 | 'no-unused-vars': 'off' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pluginOptions:{ 3 | electronBuilder:{ 4 | preload: 'src/electron/preload.js', 5 | builderOptions: { 6 | appId: "com.electron.one_cart_qrcode", 7 | productName: "淘宝一键加购二维码生成助手(单sku版本)", 8 | copyright: "Copyright © 2022-2023 ${author}", 9 | win: { 10 | target: [ 11 | { 12 | "target": "portable",//利用nsis制作安装程序 13 | "arch": [ 14 | "x64",//64位 15 | ] 16 | } 17 | ] 18 | } 19 | } 20 | } 21 | }, 22 | chainWebpack: config => { 23 | config 24 | .plugin('html') 25 | .tap(args => { 26 | args[0].title = "淘宝一键加购二维码生成助手(单sku版本)"; 27 | return args 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/electron/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron') 2 | const os = require('os') 3 | contextBridge.exposeInMainWorld('serverApi', { 4 | // node: () => process.versions.node, 5 | // chrome: () => process.versions.chrome, 6 | // electron: () => process.versions.electron, 7 | // 能暴露的不仅仅是函数,我们还可以暴露变量 8 | 9 | // 服务端操作 10 | generateQrcode: (index, data, fileprefix='qrcode') => ipcRenderer.invoke('generateQrcode', index, data, fileprefix), 11 | batchGenerateQrcode: (data, goods_num, piece, round, vip_id) => ipcRenderer.invoke('batchGenerateQrcode', data, goods_num, piece, round, vip_id), 12 | 13 | // 剪切板读取 14 | clipboard: { 15 | readText: () => ipcRenderer.invoke('clipboard_readText') 16 | }, 17 | 18 | // 打开二维码文件夹 19 | openQrcodeFolder: () => ipcRenderer.invoke('openQrcodeFolder') 20 | }) -------------------------------------------------------------------------------- /src/electron/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const os = require('os') 3 | 4 | 5 | // 二维码保存位置 6 | let qrcodeSavePath = path.resolve(os.homedir(), './Documents/淘宝一键加购二维码') 7 | 8 | // 菜单配置 9 | const menu = [ 10 | { 11 | label: '淘宝一键加购二维码生成助手(单sku版本)', 12 | submenu: [ 13 | { 14 | label: '退出', 15 | accelerator: 'CmdOrCtrl+Q', 16 | role: 'quit' 17 | } 18 | ] 19 | }, 20 | { 21 | label: '文件', 22 | submenu: [ 23 | { 24 | label: '关闭窗口', 25 | accelerator: 'CmdOrCtrl+W', 26 | role: 'close' 27 | } 28 | ] 29 | }, 30 | { 31 | label: '编辑', 32 | submenu: [ 33 | { 34 | label: '撤销', 35 | accelerator: 'CmdOrCtrl+Z', 36 | role: 'undo' 37 | }, 38 | { 39 | label: '重做', 40 | accelerator: 'Shift+CmdOrCtrl+Z', 41 | role: 'redo' 42 | }, 43 | { 44 | label: '剪切', 45 | accelerator: 'CmdOrCtrl+X', 46 | role: 'cut' 47 | }, 48 | { 49 | label: '复制', 50 | accelerator: 'CmdOrCtrl+C', 51 | role: 'copy' 52 | }, 53 | { 54 | label: '粘贴', 55 | accelerator: 'CmdOrCtrl+V', 56 | role: 'paste' 57 | }, 58 | { 59 | label: '全选', 60 | accelerator: 'CmdOrCtrl+A', 61 | role: 'selectAll' 62 | }, 63 | ] 64 | } 65 | ] 66 | 67 | module.exports = { 68 | qrcodeSavePath, 69 | menu 70 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "one_cart_qrcode", 3 | "version": "0.2.0", 4 | "author": { 5 | "name": "wzblog", 6 | "url": "https://github.com/wzblog", 7 | "email": "wcgcodes@gmail.com" 8 | }, 9 | "private": true, 10 | "scripts": { 11 | "serve": "vue-cli-service serve", 12 | "build": "vue-cli-service build", 13 | "lint": "vue-cli-service lint", 14 | "electron:build": "vue-cli-service electron:build --win --mac", 15 | "electron:serve": "vue-cli-service electron:serve", 16 | "postinstall": "electron-builder install-app-deps", 17 | "postuninstall": "electron-builder install-app-deps" 18 | }, 19 | "main": "background.js", 20 | "dependencies": { 21 | "core-js": "^3.6.5", 22 | "qrcode": "^1.5.1", 23 | "tdesign-vue-next": "^1.0.2", 24 | "vue": "^3.0.0", 25 | "vue-router": "^4.0.0-0", 26 | "vuex": "^4.0.0-0" 27 | }, 28 | "devDependencies": { 29 | "@vue/cli-plugin-babel": "~4.5.15", 30 | "@vue/cli-plugin-eslint": "~4.5.15", 31 | "@vue/cli-plugin-router": "~4.5.15", 32 | "@vue/cli-plugin-vuex": "~4.5.15", 33 | "@vue/cli-service": "~4.5.15", 34 | "@vue/compiler-sfc": "^3.0.0", 35 | "babel-eslint": "^10.1.0", 36 | "electron": "^13.0.0", 37 | "electron-devtools-installer": "^3.1.0", 38 | "eslint": "^6.7.2", 39 | "eslint-plugin-vue": "^7.0.0", 40 | "less": "^3.0.4", 41 | "less-loader": "^5.0.0", 42 | "vue-cli-plugin-electron-builder": "~2.1.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { app, protocol, BrowserWindow, ipcMain, Menu } from 'electron' 4 | import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' 5 | import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer' 6 | const isDevelopment = process.env.NODE_ENV !== 'production' 7 | const path = require('path') 8 | const config = require('./electron/config') 9 | 10 | const server = require('./electron/server') 11 | // 加载服务端处理函数(不直接开放权限给渲染进程) 12 | for (let funcName in server) { 13 | ipcMain.handle(funcName, server[funcName]) 14 | } 15 | 16 | // Scheme must be registered before the app is ready 17 | protocol.registerSchemesAsPrivileged([ 18 | { scheme: 'app', privileges: { secure: true, standard: true } } 19 | ]) 20 | 21 | async function createWindow() { 22 | // Create the browser window. 23 | const win = new BrowserWindow({ 24 | width: 800, 25 | height: 600, 26 | webPreferences: { 27 | 28 | // Use pluginOptions.nodeIntegration, leave this alone 29 | // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info 30 | // nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, 31 | // contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION 32 | 33 | preload: path.join(__dirname, '/preload.js') 34 | } 35 | }) 36 | 37 | if (process.env.WEBPACK_DEV_SERVER_URL) { 38 | // Load the url of the dev server if in development mode 39 | await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) 40 | if (!process.env.IS_TEST) win.webContents.openDevTools() 41 | } else { 42 | // Create Menu 43 | Menu.setApplicationMenu(Menu.buildFromTemplate(config.menu)) 44 | 45 | createProtocol('app') 46 | // Load the index.html when not in development 47 | win.loadURL('app://./index.html') 48 | } 49 | } 50 | 51 | // Quit when all windows are closed. 52 | app.on('window-all-closed', () => { 53 | // On macOS it is common for applications and their menu bar 54 | // to stay active until the user quits explicitly with Cmd + Q 55 | if (process.platform !== 'darwin') { 56 | app.quit() 57 | } 58 | }) 59 | 60 | app.on('activate', () => { 61 | // On macOS it's common to re-create a window in the app when the 62 | // dock icon is clicked and there are no other windows open. 63 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 64 | }) 65 | 66 | // This method will be called when Electron has finished 67 | // initialization and is ready to create browser windows. 68 | // Some APIs can only be used after this event occurs. 69 | app.on('ready', async () => { 70 | if (isDevelopment && !process.env.IS_TEST) { 71 | // Install Vue Devtools 72 | try { 73 | await installExtension(VUEJS3_DEVTOOLS) 74 | } catch (e) { 75 | console.error('Vue Devtools failed to install:', e.toString()) 76 | } 77 | } 78 | createWindow() 79 | }) 80 | 81 | // Exit cleanly on request from parent process in development mode. 82 | if (isDevelopment) { 83 | if (process.platform === 'win32') { 84 | process.on('message', (data) => { 85 | if (data === 'graceful-exit') { 86 | app.quit() 87 | } 88 | }) 89 | } else { 90 | process.on('SIGTERM', () => { 91 | app.quit() 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/electron/server.js: -------------------------------------------------------------------------------- 1 | const { clipboard, shell } = require('electron') 2 | const fs = require('fs') 3 | const QRCode = require('qrcode') 4 | const uniqueFilename = require('unique-filename') 5 | const { qrcodeSavePath } = require('./config') 6 | 7 | /** 8 | * 获取剪切板内容为文字 9 | * @returns 剪切板文字 10 | */ 11 | async function clipboard_readText() { 12 | return clipboard.readText() 13 | } 14 | 15 | 16 | async function generateQrcode(event, index, data, fileprefix='qrcode') { 17 | // 创建二维码存放目录 18 | if (!fs.existsSync(qrcodeSavePath)) { 19 | fs.mkdirSync(qrcodeSavePath) 20 | } 21 | 22 | // 生成二维码 23 | let qrcodeImgBase64 = await QRCode.toDataURL(data) 24 | let qrcodeImgBinary = Buffer.from(qrcodeImgBase64.split(',')[1], 'base64') 25 | 26 | // 保存二维码 27 | const qrcodeFilename = uniqueFilename(qrcodeSavePath, fileprefix + index) + '.png' 28 | fs.writeFileSync(qrcodeFilename, qrcodeImgBinary) 29 | 30 | // 返回成功 31 | return true 32 | } 33 | 34 | 35 | async function batchGenerateQrcode (event, data, goods_num, piece, round, vip_id) { 36 | for (let r = 1; r <= round; r++) { 37 | if (r > 1) data = shuffleArray(data) 38 | console.log(data) 39 | let groupData = arrayGroup(data, goods_num) 40 | 41 | for (let i = 0; i < groupData.length; i++) { 42 | let link = generateBuyLink(groupData[i], [vip_id]) 43 | 44 | // index+1 是为了给二维码加一个数字编号 45 | await generateQrcode(null, i+1, link, `qrcode-round${r}-`) 46 | } 47 | } 48 | 49 | return true 50 | } 51 | 52 | async function openQrcodeFolder() { 53 | shell.showItemInFolder(qrcodeSavePath) 54 | return true 55 | } 56 | 57 | //+----------------------------- 58 | //| 辅助函数 59 | //+----------------------------- 60 | 61 | /** 62 | * 对数组进行分组 63 | * @param {array} array 原数组 64 | * @param {number} subGroupLength 分组大小 65 | * @param {callback} callback 数据回调处理 66 | */ 67 | function arrayGroup(array, subGroupLength, callback) { 68 | let index = 0; 69 | let newArray = []; 70 | 71 | while (index < array.length) { 72 | if (callback) { 73 | let newData = [] 74 | let tmpArray = array.slice(index, index += subGroupLength) 75 | tmpArray.forEach(item => { 76 | newData.push(callback(item)) 77 | }) 78 | newArray.push(newData) 79 | } else { 80 | newArray.push(array.slice(index, index += subGroupLength)) 81 | } 82 | } 83 | 84 | return newArray; 85 | } 86 | 87 | 88 | /** 89 | * 生成加购链接 90 | * @param {array} idList 商品ID列表 91 | * @param {array} withIdList 附加上的商品ID列表 92 | */ 93 | function generateBuyLink(idList, withIdList=[]) { 94 | let paramArray = [] 95 | 96 | // 附加的商品列表 97 | if (withIdList.length && withIdList[0]) { 98 | withIdList.forEach(item => { 99 | if (isNaN(parseInt(item))) { 100 | paramArray.push(`${item.id}_${item.piece}_0`) 101 | } else { 102 | paramArray.push(`${item}_1_0`) 103 | } 104 | }) 105 | } 106 | 107 | 108 | // 商品ID列表 109 | idList.forEach(item => { 110 | paramArray.push(`${item.id}_${item.piece}_0`) 111 | }) 112 | 113 | let baseUrl = 'https://main.m.taobao.com/order/index.html?buyNow=false&buyParam=' 114 | return baseUrl + paramArray.join(',') 115 | } 116 | 117 | 118 | /** 119 | * 打乱商品 120 | * @param {array} data 数组 121 | * @returns 122 | */ 123 | function shuffleArray(data) { 124 | data.sort(() => { 125 | return .5 - Math.random() 126 | }) 127 | 128 | return data 129 | } 130 | 131 | module.exports = { 132 | clipboard_readText, 133 | generateQrcode, 134 | batchGenerateQrcode, 135 | openQrcodeFolder, 136 | } -------------------------------------------------------------------------------- /src/views/Qrcode.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 353 | 354 | 359 | --------------------------------------------------------------------------------