├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── babel.config.js ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── background.js ├── main.js ├── main │ ├── api │ │ ├── index.js │ │ ├── url.js │ │ └── utils.js │ └── lifeCycle │ │ └── index.js ├── preload.js └── renderer │ ├── App.vue │ ├── assets │ └── icon.ico │ ├── layout │ └── MainLayout.vue │ ├── pages │ ├── Account.vue │ ├── Task.vue │ ├── Test.vue │ └── modal │ │ ├── AddGoods.vue │ │ └── AddTask.vue │ ├── router │ └── index.js │ ├── store │ ├── index.js │ └── modules │ │ ├── index.js │ │ ├── task.js │ │ └── user.js │ ├── styles │ └── index.less │ └── utils │ ├── index.js │ └── uuid.js ├── vue.config.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | dist/ 3 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | node: true, 7 | 'jest/globals': true 8 | }, 9 | extends: ['eslint:recommended', 'plugin:prettier/recommended', 'plugin:vue/essential'], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'object-curly-spacing': 'off', 14 | 'vue/no-parsing-error': [ 15 | 2, 16 | { 17 | 'x-invalid-end-tag': false 18 | } 19 | ], 20 | indent: [ 21 | 'error', 22 | 2, 23 | { 24 | ignoredNodes: ['TemplateLiteral'] 25 | } 26 | ], 27 | 'template-curly-spacing': ['off'] 28 | }, 29 | parserOptions: { 30 | sourceType: 'module', 31 | parser: 'babel-eslint' 32 | }, 33 | plugins: ['prettier', 'jest'] 34 | } 35 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "printWidth": 120, 4 | "semi": false, 5 | "singleQuote": true 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 京东抢购助手 2 | 3 | 京东的抢购有很多种不同的交互方式所以无法保证百分百成功。 4 | 5 | ### 使用方法 6 | 7 | ```yaml 8 | # install dependencies 安装依赖 9 | yarn install 10 | 11 | # start dev server 研发环境启动 12 | yarn run electron:serve 13 | 14 | # build pack 打包 15 | yarn run electron:build 16 | ``` 17 | 18 | ### 注意事项 19 | 20 | * 由于众所周知的网络问题,研发环境启动时可能因为下载`electron-devtools`失败而报`Failed to fetch extension, trying 4 more times`,但是不会影响使用 21 | * 同样的,第一次打包因为有依赖`winCodeSign-2.6.0.7z`这个包,下载失败也会导致打包失败,可以自己找一下资源下载完成后放到`dist_electron`这个目录下面 22 | * 目前还处于实验阶段,可能存在BUG及众多不稳定因素。由于还是个社畜,可能不能及时更新。 23 | 24 | electron打包配置还在学习中,所以目前**只支持window版本的安装** 25 | 26 | ### 声明 27 | 28 | * 项目主要是基于学习electron的目的创建的,禁止任何的商用 29 | * 由于贫穷没有mac,所以mac版本如果有问题也无法处理,见谅 30 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jd-zs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "jzc", 6 | "scripts": { 7 | "start": "yarn run electron:serve", 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint", 11 | "electron:build": "vue-cli-service electron:build", 12 | "electron:serve": "vue-cli-service electron:serve", 13 | "postinstall": "electron-builder install-app-deps", 14 | "postuninstall": "electron-builder install-app-deps" 15 | }, 16 | "main": "background.js", 17 | "dependencies": { 18 | "ant-design-vue": "^1.7.2", 19 | "core-js": "^3.6.5", 20 | "dayjs": "^1.9.7", 21 | "electron-log": "^4.3.0", 22 | "lodash": "^4.17.20", 23 | "request-promise": "^4.2.6", 24 | "vue": "^2.6.11", 25 | "vue-router": "^3.4.9", 26 | "vuex": "^3.6.0", 27 | "vuex-electron": "^1.0.3" 28 | }, 29 | "devDependencies": { 30 | "@vue/cli-plugin-babel": "~4.5.0", 31 | "@vue/cli-plugin-eslint": "~4.5.0", 32 | "@vue/cli-service": "~4.5.0", 33 | "babel-eslint": "^10.1.0", 34 | "electron": "^9.0.0", 35 | "electron-devtools-installer": "^3.1.0", 36 | "eslint": "^7.15.0", 37 | "eslint-config-prettier": "^7.0.0", 38 | "eslint-plugin-jest": "^24.1.3", 39 | "eslint-plugin-node": "^11.1.0", 40 | "eslint-plugin-prettier": "^3.2.0", 41 | "eslint-plugin-vue": "^6.2.2", 42 | "less": "^3.13.0", 43 | "less-loader": "^7.1.0", 44 | "vue-cli-plugin-electron-builder": "~2.0.0-rc.5", 45 | "vue-template-compiler": "^2.6.11" 46 | }, 47 | "eslintConfig": { 48 | "root": true, 49 | "env": { 50 | "node": true 51 | }, 52 | "extends": [ 53 | "plugin:vue/essential", 54 | "eslint:recommended" 55 | ], 56 | "parserOptions": { 57 | "parser": "babel-eslint" 58 | }, 59 | "rules": {} 60 | }, 61 | "browserslist": [ 62 | "> 1%", 63 | "last 2 versions", 64 | "not dead" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzclem/jdzs/ab2c84c60395b0cab89b2a52e9243831ef637ed9/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | import { bootstrap } from '~/main/lifeCycle' 2 | 3 | bootstrap.launchApp() 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import router from '@/router' 3 | import store from '@/store' 4 | import App from '@/App.vue' 5 | import Antd from 'ant-design-vue' 6 | import 'ant-design-vue/dist/antd.css' 7 | import '@/styles/index.less' 8 | 9 | Vue.config.productionTip = false 10 | 11 | Vue.use(Antd) 12 | 13 | /* eslint-disable no-new */ 14 | new Vue({ 15 | el: '#app', 16 | router, 17 | store, 18 | ...App 19 | }) 20 | -------------------------------------------------------------------------------- /src/main/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 京东相关接口 3 | */ 4 | import request from 'request-promise' 5 | import URLS from './url' 6 | import { handleResponse } from './utils' 7 | import log from 'electron-log' 8 | 9 | const UserAgent = 10 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36' 11 | const ContentType = 'application/x-www-form-urlencoded' 12 | 13 | /** 14 | * 查询登录状态及是否为京东plus会员 15 | * @param Cookie 16 | * @returns {Promise<{isLogin: boolean}|{isLogin: boolean, isPlusMember: boolean}>} 17 | */ 18 | function cookieCheck(Cookie) { 19 | return request({ 20 | uri: URLS.CHECK_ACCOUNT, 21 | headers: { 22 | Cookie, 23 | 'User-Agent': UserAgent 24 | }, 25 | json: true, 26 | resolveWithFullResponse: true 27 | }).then((resp) => { 28 | const body = resp.body 29 | log.info(`账号${body === true || body === false ? '有效' : '过期'}`) 30 | return { 31 | isLogin: !!(body === true || body === false), 32 | isPlusMember: body === true 33 | } 34 | }) 35 | } 36 | 37 | /** 38 | * 获取下单信息 39 | * @param Cookie 40 | * @returns {Promise} 41 | */ 42 | function getBuyInfo(Cookie) { 43 | return request({ 44 | uri: URLS.GET_ORDER, 45 | headers: { 46 | Cookie, 47 | 'User-Agent': UserAgent 48 | } 49 | }).then((resp) => { 50 | const parser = new DOMParser() 51 | const dom = parser.parseFromString(resp, 'text/html') 52 | const area = dom.querySelector('#hideAreaIds') 53 | if (!area) return false 54 | log.info('获取下单信息') 55 | const id = area.getAttribute('value') 56 | return id.replace(/-/g, '_') 57 | }) 58 | } 59 | 60 | /** 61 | * 获取库存信息 62 | * @param sku 63 | * @param area 64 | * @returns {Promise} 65 | */ 66 | function getStocks(sku, area) { 67 | return request(`${URLS.CHECK_STOCKS}?type=getstocks&skuIds=${sku}&area=${area}&_=${+new Date()}`).then((resp) => { 68 | let result = JSON.parse(resp) 69 | if (resp && result[sku]) { 70 | const skuState = result[sku].skuState // 商品是否上架 71 | const StockState = result[sku].StockState // 商品库存状态:33 -- 现货 0,34 -- 无货 36 -- 采购中 40 -- 可配货 72 | const status = skuState === 1 && [33, 36, 40].includes(StockState) 73 | log.info(`库存${status ? '有货' : '无货'}`) 74 | return status 75 | } 76 | return false 77 | }) 78 | } 79 | 80 | /** 81 | * 全选购物车中的商品 82 | * @param Cookie 83 | * @returns {Promise} 84 | */ 85 | function selectAllCart(Cookie) { 86 | return request({ 87 | uri: URLS.SELECT_ALL, 88 | headers: { 89 | Cookie, 90 | 'User-Agent': UserAgent 91 | }, 92 | resolveWithFullResponse: true 93 | }).then((resp) => { 94 | const result = handleResponse(resp) 95 | if (result && result.sortedWebCartResult) { 96 | log.info('全选购物车中的商品') 97 | return result.sortedWebCartResult.success 98 | } 99 | return false 100 | }) 101 | } 102 | 103 | /** 104 | * 清空购物车 105 | * @param Cookie 106 | * @returns {Promise} 107 | */ 108 | function clearCart(Cookie) { 109 | return selectAllCart(Cookie).then((res) => { 110 | if (res) { 111 | return request({ 112 | uri: URLS.CLEAR_ALL, 113 | headers: { 114 | Cookie, 115 | 'User-Agent': UserAgent 116 | }, 117 | resolveWithFullResponse: true 118 | }).then((resp) => { 119 | const result = handleResponse(resp) 120 | if (result && result.sortedWebCartResult) { 121 | log.info('清空购物车') 122 | return result.sortedWebCartResult.success 123 | } 124 | return false 125 | }) 126 | } 127 | return false 128 | }) 129 | } 130 | 131 | /** 132 | * 添加商品到购物车 133 | * @param Cookie 134 | * @param skuId 135 | * @param num 136 | * @returns {Promise} 137 | */ 138 | async function addGoodsToCart(Cookie, skuId, num) { 139 | return request({ 140 | uri: URLS.ADD_ITEM, 141 | qs: { 142 | pid: skuId, 143 | pcount: num, 144 | ptype: 1 145 | }, 146 | headers: { 147 | Cookie, 148 | 'User-Agent': UserAgent, 149 | 'Content-Type': ContentType 150 | }, 151 | json: true, 152 | resolveWithFullResponse: true 153 | }).then((resp) => { 154 | const html = handleResponse(resp) 155 | log.info('添加商品到购物车') 156 | return html.indexOf('成功') > -1 157 | }) 158 | } 159 | 160 | /** 161 | * 提交订单(当前购物车内所有商品) 162 | * @param Cookie 163 | * @param password 164 | * @param eid 165 | * @param fp 166 | * @returns {Promise} 167 | */ 168 | async function orderSubmit(Cookie, password, eid, fp) { 169 | const params = { 170 | overseaPurchaseCookies: '', 171 | vendorRemarks: '[]', 172 | presaleStockSign: 1, 173 | 'submitOrderParam.sopNotPutInvoice': 'false', 174 | 'submitOrderParam.trackID': 'TestTrackId', 175 | 'submitOrderParam.ignorePriceChange': '0', 176 | 'submitOrderParam.btSupport': '0', 177 | 'submitOrderParam.jxj': '1', 178 | 'submitOrderParam.payPassword': `u3${password}`, 179 | 'submitOrderParam.eid': eid, 180 | 'submitOrderParam.fp': fp, 181 | 'submitOrderParam.isBestCoupon': '1' 182 | } 183 | // 请求结算页面 184 | await request({ 185 | uri: URLS.GET_ORDER, 186 | headers: { 187 | Cookie, 188 | 'User-Agent': UserAgent, 189 | 'Content-Type': ContentType 190 | }, 191 | resolveWithFullResponse: true 192 | // eslint-disable-next-line no-unused-vars 193 | }).then((resp) => { 194 | log.info('请求结算页面') 195 | }) 196 | // 提交订单 197 | return request({ 198 | method: 'POST', 199 | uri: URLS.SUBMIT_ORDER, 200 | form: params, 201 | headers: { 202 | Cookie, 203 | 'User-Agent': UserAgent, 204 | Host: 'trade.jd.com', 205 | Referer: 'http://trade.jd.com/shopping/order/getOrderInfo.action' 206 | }, 207 | resolveWithFullResponse: true 208 | }).then((resp) => { 209 | log.info('提交订单') 210 | log.info(handleResponse(resp)) 211 | return handleResponse(resp) 212 | }) 213 | } 214 | 215 | /** 216 | * 请求商品详情页 217 | * @param skuId 218 | * @returns {Promise} 219 | */ 220 | function getItemInfo(skuId) { 221 | return request({ 222 | uri: `https://item.jd.com/${skuId}.html`, 223 | headers: { 224 | 'User-Agent': UserAgent 225 | }, 226 | resolveWithFullResponse: true 227 | }).then((resp) => { 228 | const parser = new DOMParser() 229 | const html = handleResponse(resp) 230 | if (!html) return false 231 | log.info('获取成功') 232 | // 解析返回的HTML代码 233 | const dom = parser.parseFromString(html, 'text/html') 234 | const pageConfig = dom.querySelectorAll('script')[0].innerText 235 | const imageSrc = dom.querySelector('#spec-img').dataset.origin 236 | const name = pageConfig.match(/name: '(.*)'/)[1] 237 | const easyBuyUrl = html.match(/easyBuyUrl:"(.*)"/)[1] 238 | const cat = pageConfig.match(/cat: \[(.*)\]/)[1] 239 | const venderId = pageConfig.match(/venderId:(\d*)/)[1] 240 | return { 241 | name, 242 | imageSrc, 243 | cat, 244 | venderId, 245 | easyBuyUrl 246 | } 247 | }) 248 | } 249 | 250 | /** 251 | * 查询京东服务器时间 252 | * @returns {Promise} 253 | */ 254 | function getServerTime() { 255 | return request({ 256 | uri: URLS.GET_SERVER_TIME, 257 | resolveWithFullResponse: true 258 | }).then((resp) => { 259 | return handleResponse(resp) 260 | }) 261 | } 262 | 263 | /** 264 | * 查询秒杀列表 265 | * @returns {Promise} 266 | */ 267 | function pcMiaoShaAreaList(Cookie, gid) { 268 | return request({ 269 | uri: URLS.GET_MIAOSHA_LIST, 270 | qs: { 271 | functionId: 'pcMiaoShaAreaList', 272 | client: 'pc', 273 | appid: 'o2_channels', 274 | clientVersion: '1.0.0', 275 | callback: 'pcMiaoShaAreaList', 276 | jsonp: 'pcMiaoShaAreaList', 277 | body: gid || {}, 278 | _: new Date().getTime() 279 | }, 280 | headers: { 281 | Cookie, 282 | 'User-Agent': UserAgent, 283 | Referer: 'https://miaosha.jd.com/' 284 | }, 285 | resolveWithFullResponse: true 286 | }).then((resp) => { 287 | return handleResponse(resp) 288 | }) 289 | } 290 | 291 | export default { 292 | cookieCheck, 293 | getBuyInfo, 294 | selectAllCart, 295 | clearCart, 296 | addGoodsToCart, 297 | orderSubmit, 298 | getItemInfo, 299 | getStocks, 300 | pcMiaoShaAreaList, 301 | getServerTime 302 | } 303 | -------------------------------------------------------------------------------- /src/main/api/url.js: -------------------------------------------------------------------------------- 1 | export default { 2 | CHECK_ACCOUNT: 'https://order.jd.com/lazy/isPlusMember.action', 3 | CHECK_STOCKS: 'https://cd.jd.com/stocks', 4 | GET_BUY_INFO: 'https://marathon.jd.com/seckillnew/orderService/pc/init.action', 5 | KILL_ORDER_SUBMIT: 'https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action', 6 | SELECT_ALL: 'https://cart.jd.com/selectAllItem.action', 7 | CLEAR_ALL: 'https://cart.jd.com/batchRemoveSkusFromCart.action', 8 | ADD_ITEM: 'https://cart.jd.com/gate.action', 9 | GET_ORDER: 'https://trade.jd.com/shopping/order/getOrderInfo.action', 10 | SUBMIT_ORDER: 'https://trade.jd.com/shopping/order/submitOrder.action', 11 | GET_ITEM_STOCK: 'https://c0.3.cn/stock', 12 | GET_MIAOSHA_LIST: 'https://api.m.jd.com/api', 13 | GET_SERVER_TIME: 'https://api.m.jd.com/client.action?functionId=queryMaterialProducts&client=wh5' 14 | } 15 | -------------------------------------------------------------------------------- /src/main/api/utils.js: -------------------------------------------------------------------------------- 1 | import log from 'electron-log' 2 | 3 | export const getRandomArbitrary = (min, max) => { 4 | return Math.floor(Math.random() * (max - min) + min) 5 | } 6 | 7 | export const handleResponse = (resp) => { 8 | // eslint-disable-next-line no-unused-vars 9 | const { body, statusCode, request } = resp 10 | // log.info(`接口 ${request.href} 请求结果:${statusCode}`) 11 | let result = parseJson(body) 12 | try { 13 | result = JSON.parse(result) 14 | } catch (error) { 15 | // log.info('response body is not JSON.') 16 | } 17 | return result 18 | } 19 | 20 | function parseJson(body) { 21 | if (typeof body === 'string') { 22 | const token = body.indexOf(" 10 | 11 | 30 | -------------------------------------------------------------------------------- /src/renderer/assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzclem/jdzs/ab2c84c60395b0cab89b2a52e9243831ef637ed9/src/renderer/assets/icon.ico -------------------------------------------------------------------------------- /src/renderer/layout/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 50 | 63 | -------------------------------------------------------------------------------- /src/renderer/pages/Account.vue: -------------------------------------------------------------------------------- 1 | 29 | 115 | -------------------------------------------------------------------------------- /src/renderer/pages/Task.vue: -------------------------------------------------------------------------------- 1 | 71 | 243 | -------------------------------------------------------------------------------- /src/renderer/pages/Test.vue: -------------------------------------------------------------------------------- 1 | 6 | 32 | -------------------------------------------------------------------------------- /src/renderer/pages/modal/AddGoods.vue: -------------------------------------------------------------------------------- 1 | 22 | 66 | -------------------------------------------------------------------------------- /src/renderer/pages/modal/AddTask.vue: -------------------------------------------------------------------------------- 1 | 49 | 135 | -------------------------------------------------------------------------------- /src/renderer/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | const MainLayout = () => import('@/layout/MainLayout') 5 | const Account = () => import('@/pages/Account') 6 | const Task = () => import('@/pages/Task') 7 | const Test = () => import('@/pages/Test') 8 | 9 | Vue.use(Router) 10 | 11 | export default new Router({ 12 | routes: [ 13 | { 14 | path: '/', 15 | component: MainLayout, 16 | redirect: { 17 | name: 'account' 18 | }, 19 | children: [ 20 | { 21 | path: 'account', 22 | name: 'account', 23 | component: Account 24 | }, 25 | { 26 | path: 'task', 27 | name: 'task', 28 | component: Task 29 | }, 30 | { 31 | path: 'test', 32 | name: 'test', 33 | component: Test 34 | } 35 | ] 36 | }, 37 | { 38 | path: '*', 39 | redirect: '/' 40 | } 41 | ] 42 | }) 43 | -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | // import log from 'electron-log' 4 | import { createPersistedState } from 'vuex-electron' 5 | 6 | import modules from './modules' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | namespaced: true, 12 | modules, 13 | plugins: [ 14 | createPersistedState() 15 | // createSharedMutations() 16 | ], 17 | strict: process.env.NODE_ENV !== 'production' 18 | }) 19 | -------------------------------------------------------------------------------- /src/renderer/store/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The file enables `@/store/index.js` to import all vuex modules 3 | * in a one-shot manner. There should not be any reason to edit this file. 4 | */ 5 | 6 | const files = require.context('.', false, /\.js$/) 7 | const modules = {} 8 | 9 | files.keys().forEach((key) => { 10 | if (key === './index.js') return 11 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 12 | }) 13 | 14 | export default modules 15 | -------------------------------------------------------------------------------- /src/renderer/store/modules/task.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const jd = window.preload.jd 4 | import message from 'ant-design-vue/es/message' 5 | 6 | const state = { 7 | /** 8 | * 任务列表 9 | * @property account 10 | * @property skuId 11 | * @property taskType 12 | * @property isSetTime 13 | * @property startTime 14 | * @property buyNum 15 | * @property advanceTime 16 | * @property buyInfo 17 | */ 18 | task: {} 19 | } 20 | const getters = { 21 | taskList: (state) => { 22 | let result = [] 23 | for (const key in state.task) { 24 | // eslint-disable-next-line no-prototype-builtins 25 | if (state.task.hasOwnProperty(key)) { 26 | result.push(state.task[key]) 27 | } 28 | } 29 | return result 30 | } 31 | } 32 | const mutations = { 33 | SAVE_OR_UPDATE(state, { skuId, taskType, isSetTime, startTime, buyNum, detail, advanceTime, account }) { 34 | const origin = state.task[skuId] 35 | let params = {} 36 | params.skuId = skuId || origin.skuId 37 | params.taskType = taskType || origin.taskType 38 | params.buyNum = buyNum || origin.buyNum 39 | params.detail = detail || origin.detail 40 | params.advanceTime = advanceTime || origin.advanceTime 41 | params.account = account || origin.account 42 | params.isSetTime = isSetTime || origin.isSetTime 43 | if (params.isSetTime) { 44 | params.startTime = startTime || origin.startTime 45 | } 46 | Vue.set(state.task, skuId, params) 47 | }, 48 | REMOVE(state, skuId) { 49 | Vue.delete(state.task, skuId) 50 | }, 51 | CLEAR_ALL(state) { 52 | state.task = {} 53 | } 54 | } 55 | 56 | const actions = { 57 | /** 58 | * 添加任务 59 | * @param commit 60 | * @param skuId 61 | * @param taskType 62 | * @param isSetTime 63 | * @param startTime 64 | * @param buyNum 65 | * @param advanceTime 66 | * @param account 67 | * @param form 68 | * @returns {Promise} 69 | */ 70 | async addTask({ commit }, { skuId, taskType, isSetTime, startTime, buyNum, advanceTime, account }) { 71 | let detail = false 72 | while (!detail) { 73 | message.warning('正在请求接口...') 74 | detail = await jd.getItemInfo(skuId) 75 | } 76 | message.success('请求接口成功!') 77 | commit('SAVE_OR_UPDATE', { 78 | skuId, 79 | taskType, 80 | isSetTime, 81 | startTime, 82 | buyNum, 83 | advanceTime, 84 | account, 85 | detail 86 | }) 87 | }, 88 | /** 89 | * 更新商品信息 90 | * @param state 91 | * @param commit 92 | * @returns {Promise} 93 | */ 94 | async checkTaskList({ state, commit }) { 95 | for (const key in state.task) { 96 | // eslint-disable-next-line no-prototype-builtins 97 | if (state.task.hasOwnProperty(key)) { 98 | let detail = false 99 | while (!detail) { 100 | message.warning('正在请求接口...') 101 | detail = await jd.getItemInfo(key) 102 | } 103 | message.success('请求接口成功!') 104 | commit('SAVE_OR_UPDATE', { 105 | skuId: key, 106 | detail 107 | }) 108 | } 109 | } 110 | } 111 | } 112 | 113 | export default { 114 | namespaced: true, 115 | state, 116 | getters, 117 | mutations, 118 | actions 119 | } 120 | -------------------------------------------------------------------------------- /src/renderer/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const jd = window.preload.jd 4 | 5 | const state = { 6 | /** 7 | * 账号列表 8 | * @property pinId 9 | * @property name 10 | * @property cookie 11 | * @property isLogin 12 | * @property isPlusMember 13 | */ 14 | account: {}, 15 | address: '', 16 | password: '', 17 | eid: '', 18 | fp: '' 19 | } 20 | const getters = { 21 | accountList: (state) => { 22 | let result = [] 23 | for (const key in state.account) { 24 | // eslint-disable-next-line no-prototype-builtins 25 | if (state.account.hasOwnProperty(key)) { 26 | result.push(state.account[key]) 27 | } 28 | } 29 | return result 30 | } 31 | } 32 | const mutations = { 33 | SAVE_OR_UPDATE(state, { pinId, name, cookie, isLogin, isPlusMember }) { 34 | const origin = state.account[pinId] 35 | let params = { pinId, name, cookie, isLogin, isPlusMember } 36 | params.name = name || origin.name 37 | params.cookie = cookie || origin.cookie 38 | if (isLogin === undefined) { 39 | params.isLogin = origin.isLogin 40 | } 41 | if (isPlusMember === undefined) { 42 | params.isPlusMember = origin.isPlusMember 43 | } 44 | Vue.set(state.account, pinId, params) 45 | }, 46 | REMOVE(state, pinId) { 47 | Vue.delete(state.account, pinId) 48 | }, 49 | CLEAR_ALL(state) { 50 | state.account = {} 51 | }, 52 | SAVE_ADDRESS_ID(state, address) { 53 | state.address = address 54 | }, 55 | SAVE_REMEMBER(state, password) { 56 | state.password = password 57 | }, 58 | SAVE_EID_FP(state, { eid, fp }) { 59 | state.eid = eid 60 | state.fp = fp 61 | } 62 | } 63 | 64 | const actions = { 65 | /** 66 | * 保存账号 67 | * @param commit 68 | * @param cookie 69 | * @param accountType 70 | * @returns {Promise} 71 | */ 72 | async saveAccount({ commit }, { cookie, accountType }) { 73 | const pinId = accountType ? cookie.match(/authId=(.*?);/)[1] : cookie.match(/pinId=(.*?);/)[1] 74 | const name = accountType ? cookie.match(/nick=(.*?);/)[1] : cookie.match(/unick=(.*?);/)[1] 75 | let res = { isLogin: false, isPlusMember: false } 76 | try { 77 | res = await jd.cookieCheck(cookie) 78 | } finally { 79 | commit('SAVE_OR_UPDATE', { 80 | pinId, 81 | cookie, 82 | name: window.decodeURIComponent(name), 83 | isLogin: res.isLogin, 84 | isPlusMember: res.isPlusMember 85 | }) 86 | } 87 | }, 88 | /** 89 | * 检查state里边所有账号有效性 90 | * @param state 91 | * @param commit 92 | * @returns {Promise} 93 | */ 94 | async checkAccountList({ state, commit }) { 95 | for (const key in state.account) { 96 | // eslint-disable-next-line no-prototype-builtins 97 | if (state.account.hasOwnProperty(key)) { 98 | const account = state.account[key] 99 | let res = { isLogin: false, isPlusMember: false } 100 | try { 101 | res = await jd.cookieCheck(account.cookie) 102 | } finally { 103 | commit('SAVE_OR_UPDATE', { 104 | pinId: key, 105 | isLogin: res.isLogin, 106 | isPlusMember: res.isPlusMember 107 | }) 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | export default { 115 | namespaced: true, 116 | state, 117 | getters, 118 | mutations, 119 | actions 120 | } 121 | -------------------------------------------------------------------------------- /src/renderer/styles/index.less: -------------------------------------------------------------------------------- 1 | .pd{ 2 | &-0{padding: 0;} 3 | &-5{padding: 5px;} 4 | &-10{padding: 10px;} 5 | &-15{padding: 15px;} 6 | &-20{padding: 20px;} 7 | &-25{padding: 25px;} 8 | &-30{padding: 30px;} 9 | &-t0{padding-top: 0;} 10 | &-t5{padding-top: 5px;} 11 | &-t10{padding-top: 10px;} 12 | &-t15{padding-top: 15px;} 13 | &-t20{padding-top: 20px;} 14 | &-t25{padding-top: 25px;} 15 | &-t30{padding-top: 30px;} 16 | &-b0{padding-bottom: 0;} 17 | &-b5{padding-bottom: 5px;} 18 | &-b10{padding-bottom: 10px;} 19 | &-b15{padding-bottom: 15px;} 20 | &-b20{padding-bottom: 20px;} 21 | &-b25{padding-bottom: 25px;} 22 | &-b30{padding-bottom: 30px;} 23 | &-l0{padding-left: 0;} 24 | &-l5{padding-left: 5px;} 25 | &-l10{padding-left: 10px;} 26 | &-l15{padding-left: 15px;} 27 | &-l20{padding-left: 20px;} 28 | &-l25{padding-left: 25px;} 29 | &-l30{padding-left: 30px;} 30 | &-r0{padding-right: 0;} 31 | &-r5{padding-right: 5px;} 32 | &-r10{padding-right: 10px;} 33 | &-r15{padding-right: 15px;} 34 | &-r20{padding-right: 20px;} 35 | &-r25{padding-right: 25px;} 36 | &-r30{padding-right: 30px;} 37 | } 38 | .mg{ 39 | &-0{margin: 0;} 40 | &-5{margin: 5px;} 41 | &-10{margin: 10px;} 42 | &-15{margin: 15px;} 43 | &-20{margin: 20px;} 44 | &-25{margin: 25px;} 45 | &-30{margin: 30px;} 46 | &-t0{margin-top: 0;} 47 | &-t5{margin-top: 5px;} 48 | &-t10{margin-top: 10px;} 49 | &-t15{margin-top: 15px;} 50 | &-t20{margin-top: 20px;} 51 | &-t25{margin-top: 25px;} 52 | &-t30{margin-top: 30px;} 53 | &-b0{margin-bottom: 0;} 54 | &-b5{margin-bottom: 5px;} 55 | &-b10{margin-bottom: 10px;} 56 | &-b15{margin-bottom: 15px;} 57 | &-b20{margin-bottom: 20px;} 58 | &-b25{margin-bottom: 25px;} 59 | &-b30{margin-bottom: 30px;} 60 | &-l0{margin-left: 0;} 61 | &-l5{margin-left: 5px;} 62 | &-l10{margin-left: 10px;} 63 | &-l15{margin-left: 15px;} 64 | &-l20{margin-left: 20px;} 65 | &-l25{margin-left: 25px;} 66 | &-l30{margin-left: 30px;} 67 | &-r0{margin-right: 0;} 68 | &-r5{margin-right: 5px;} 69 | &-r10{margin-right: 10px;} 70 | &-r15{margin-right: 15px;} 71 | &-r20{margin-right: 20px;} 72 | &-r25{margin-right: 25px;} 73 | &-r30{margin-right: 30px;} 74 | } -------------------------------------------------------------------------------- /src/renderer/utils/index.js: -------------------------------------------------------------------------------- 1 | const files = require.context('.', false, /\.js$/) 2 | const modules = {} 3 | 4 | files.keys().forEach((key) => { 5 | if (key === './index.js') return 6 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 7 | }) 8 | 9 | export default modules 10 | -------------------------------------------------------------------------------- /src/renderer/utils/uuid.js: -------------------------------------------------------------------------------- 1 | import { random } from 'lodash' 2 | 3 | const RFC4122_TEMPLATE = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' 4 | 5 | function replacePlaceholders(placeholder) { 6 | let value = random(15) 7 | value = placeholder === 'x' ? value : (value & 0x3 | 0x8) 8 | return value.toString(16) 9 | } 10 | 11 | export default () => { 12 | return RFC4122_TEMPLATE.replace(/[xy]/g, replacePlaceholders) 13 | } 14 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | function resolve(dir) { 3 | return path.join(__dirname, dir) 4 | } 5 | 6 | module.exports = { 7 | chainWebpack: (config) => { 8 | config.resolve.alias 9 | .set('@', resolve('src/renderer')) 10 | .set('~', resolve('src')) 11 | .set('root', resolve('./')) 12 | // 将 icons 目录排除在 svg 默认规则之外 13 | config.module 14 | .rule('svg') 15 | .exclude.add(resolve('src/renderer/icons')) 16 | .end() 17 | // 用 svg-sprite-loader 处理 icons 下的 svg 18 | config.module 19 | .rule('icons') 20 | .test(/\.svg$/) 21 | .include.add(resolve('src/renderer/icons')) 22 | .end() 23 | .use('svg-sprite-loader') 24 | .loader('svg-sprite-loader') 25 | .options({ 26 | symbolId: 'icon-[name]' 27 | }) 28 | .end() 29 | }, 30 | pluginOptions: { 31 | electronBuilder: { 32 | nodeIntegration: true, 33 | preload: 'src/preload.js', 34 | chainWebpackMainProcess: (config) => { 35 | config.resolve.alias 36 | .set('@', resolve('src/renderer')) 37 | .set('~', resolve('src')) 38 | .set('root', resolve('./')) 39 | }, 40 | mainProcessWatch: ['src/main/api/index.js'], 41 | builderOptions: { 42 | productName: '京东抢购助手', 43 | dmg: {}, 44 | mac: {}, 45 | win: { 46 | icon: 'src/renderer/assets/icon.ico', 47 | target: ['nsis', 'zip'] 48 | }, 49 | nsis: { 50 | oneClick: false, //是否一键安装 51 | allowToChangeInstallationDirectory: true, //允许修改安装目录 52 | installerIcon: 'src/renderer/assets/icon.ico', //安装图标 53 | uninstallerIcon: 'src/renderer/assets/icon.ico', //卸载图标 54 | installerHeaderIcon: 'src/renderer/assets/icon.ico', //安装时头部图标 55 | createDesktopShortcut: true, //创建桌面图标 56 | createStartMenuShortcut: false //创建开始菜单图标 57 | } 58 | } 59 | } 60 | } 61 | } 62 | --------------------------------------------------------------------------------