├── .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 |
2 |
3 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
50 |
63 |
--------------------------------------------------------------------------------
/src/renderer/pages/Account.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 添加账号
5 |
6 |
7 | 清空账号
8 |
9 |
10 | 京东
11 |
12 |
13 |
14 |
15 | 已登录
16 | 未登录
17 |
18 |
19 | {{ record.isPlusMember ? '是' : '否' }}
20 |
21 |
22 | 删除
23 |
24 | 清空购物车
25 |
26 |
27 |
28 |
29 |
115 |
--------------------------------------------------------------------------------
/src/renderer/pages/Task.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 添加任务
6 |
7 |
8 | 停止所有任务
9 |
10 |
11 | 更新商品信息
12 |
13 |
14 | 获取区域ID
15 |
16 |
17 | 获取eid与fp
18 |
19 |
20 |
21 | {{ item }}ms
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 记住密码
38 |
39 |
40 |
41 |
42 |
50 | {{ item.detail.name }}
51 |
52 |
53 |
60 | 停止
61 |
62 |
63 | 开抢
64 |
65 | 删除
66 |
67 |
68 |
69 |
70 |
71 |
243 |
--------------------------------------------------------------------------------
/src/renderer/pages/Test.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
32 |
--------------------------------------------------------------------------------
/src/renderer/pages/modal/AddGoods.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 | {{ item.shortWname }}
13 |
14 |
15 |
16 | 选择
17 |
18 |
19 |
20 |
21 |
22 |
66 |
--------------------------------------------------------------------------------
/src/renderer/pages/modal/AddTask.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 | 所有账号
14 |
15 | {{ item.name }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
30 |
31 |
32 |
33 | {{ item }}ms
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
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 |
--------------------------------------------------------------------------------