├── .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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 1. 粘贴商品ID
35 | 2. 生成二维码
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 清空商品
47 | 打乱商品
48 | 打开二维码保存文件夹
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
353 |
354 |
359 |
--------------------------------------------------------------------------------