├── .nvmrc ├── .gitignore ├── static ├── image │ ├── m.png │ ├── bean.png │ ├── coin.png │ ├── coupon.png │ ├── github.png │ ├── money.png │ ├── notice.png │ ├── rebate.png │ ├── samedi.png │ ├── update.png │ ├── zaoshu.png │ ├── icon │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 48.png │ │ ├── jjb.png │ │ ├── jjb19x.png │ │ ├── jjb38x.png │ │ ├── offline19x.png │ │ ├── offline38x.png │ │ ├── partial-offline19x.png │ │ └── partial-offline38x.png │ ├── alipay_pay.png │ ├── weixin_pay.png │ ├── notice.svg │ ├── stop.svg │ ├── play.svg │ ├── show.svg │ ├── checkin.svg │ ├── m.svg │ ├── pc.svg │ ├── hide.svg │ ├── setting.svg │ ├── coupon.svg │ ├── reload.svg │ ├── empty.svg │ ├── avatar.svg │ ├── online.svg │ └── offline.svg ├── style │ ├── start.css │ ├── content.css │ └── popup.css └── audio │ ├── beans.ogg │ ├── rebate.ogg │ ├── coin_drop.ogg │ └── price_protection.ogg ├── .github ├── ISSUE_TEMPLATE │ ├── ----.md │ ├── feature_request.md │ └── bug_report.md └── FUNDING.yml ├── public ├── background.html ├── start.html ├── manifest.json └── popup.html ├── LICENSE ├── src ├── start.js ├── page_script.js ├── account.js ├── components │ ├── messageBox │ │ ├── messageBox.js │ │ └── messageBox.vue │ ├── we-dialog.vue │ ├── events.vue │ ├── toast │ │ ├── toast.js │ │ └── toast.vue │ ├── links.vue │ ├── popup.vue │ ├── loading.vue │ ├── guide.vue │ ├── report.vue │ ├── login-notice.vue │ ├── support.vue │ ├── task-setting.vue │ ├── discounts.vue │ └── App.vue ├── variables.js ├── utils.js ├── mobile_script.js ├── popup.js ├── db.js ├── priceChart.js └── tasks.js ├── package.json ├── webpack.config.js └── readme.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | .vscode 4 | build 5 | -------------------------------------------------------------------------------- /static/image/m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/m.png -------------------------------------------------------------------------------- /static/style/start.css: -------------------------------------------------------------------------------- 1 | .start{ 2 | width: 640px; 3 | margin: 0 auto; 4 | } -------------------------------------------------------------------------------- /static/image/bean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/bean.png -------------------------------------------------------------------------------- /static/image/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/coin.png -------------------------------------------------------------------------------- /static/audio/beans.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/audio/beans.ogg -------------------------------------------------------------------------------- /static/audio/rebate.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/audio/rebate.ogg -------------------------------------------------------------------------------- /static/image/coupon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/coupon.png -------------------------------------------------------------------------------- /static/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/github.png -------------------------------------------------------------------------------- /static/image/money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/money.png -------------------------------------------------------------------------------- /static/image/notice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/notice.png -------------------------------------------------------------------------------- /static/image/rebate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/rebate.png -------------------------------------------------------------------------------- /static/image/samedi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/samedi.png -------------------------------------------------------------------------------- /static/image/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/update.png -------------------------------------------------------------------------------- /static/image/zaoshu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/zaoshu.png -------------------------------------------------------------------------------- /static/audio/coin_drop.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/audio/coin_drop.ogg -------------------------------------------------------------------------------- /static/image/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/128.png -------------------------------------------------------------------------------- /static/image/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/16.png -------------------------------------------------------------------------------- /static/image/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/48.png -------------------------------------------------------------------------------- /static/image/icon/jjb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/jjb.png -------------------------------------------------------------------------------- /static/image/alipay_pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/alipay_pay.png -------------------------------------------------------------------------------- /static/image/icon/jjb19x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/jjb19x.png -------------------------------------------------------------------------------- /static/image/icon/jjb38x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/jjb38x.png -------------------------------------------------------------------------------- /static/image/weixin_pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/weixin_pay.png -------------------------------------------------------------------------------- /static/audio/price_protection.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/audio/price_protection.ogg -------------------------------------------------------------------------------- /static/image/icon/offline19x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/offline19x.png -------------------------------------------------------------------------------- /static/image/icon/offline38x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/offline38x.png -------------------------------------------------------------------------------- /static/image/icon/partial-offline19x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/partial-offline19x.png -------------------------------------------------------------------------------- /static/image/icon/partial-offline38x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icai/jjb/master/static/image/icon/partial-offline38x.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题咨询 3 | about: 使用问题/安全问题/其他问题 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 请发送邮件至:ming@tiny.group 11 | 或在应用内“高级设置” - “建议反馈” 填写表单 12 | 13 | 京价保不设客服人员,可能不会回复你的咨询。 14 | -------------------------------------------------------------------------------- /static/image/notice.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Ming Sun. All Rights Reserved. 2 | 3 | 京价保并非一个开源软件,作者保留全部的权利。 公开源代码的目的是为了让使用者能够审计代码,但是你仍然可以就以下方式合法的使用本项目的全部代码和资源: 4 | 5 | * 个人使用 6 | * 以学习目的使用全部或部分代码 7 | 8 | 但你不可以: 9 | 10 | * 将本项目的部分或全部代码和资源进行任何形式的再发行(尤其是上传到 Chrome 商店) 11 | * 利用本项目的部分或全部代码和资源进行任何商业行为 -------------------------------------------------------------------------------- /src/start.js: -------------------------------------------------------------------------------- 1 | import 'weui' 2 | import '../static/style/start.css' 3 | 4 | document.getElementById("login").onclick = function () { 5 | chrome.runtime.sendMessage({ 6 | text: "openLogin", 7 | }, function(response) { 8 | console.log("Response: ", response); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project / 功能请求 4 | title: '' 5 | labels: 功能请求 6 | assignees: '' 7 | 8 | --- 9 | 10 | 请详细填写以下四项关键元素 11 | 12 | ## 期待增加的功能描述: 13 | 14 | ## 实现此功能带来的增益: 15 | 16 | ## 缺少此功能的影响: 17 | 18 | ## 实现的思路: 19 | -------------------------------------------------------------------------------- /src/page_script.js: -------------------------------------------------------------------------------- 1 | window.addSelfOperated = function (a) { 2 | var e = document.getElementById(a); 3 | var f = e.value; 4 | if (f = f.replace(/^\s*(.*?)\s*$/, "$1"), f.length > 100 && (f = f.substring(0, 100)), "" == f) return void(window.location.href = window.location.href); 5 | $("#key").val(f + ' 自营'); 6 | document.getElementById(a).value = f + ' 自营'; 7 | } -------------------------------------------------------------------------------- /static/image/stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/image/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/image/show.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: tinyming 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://paypal.me/tinyming?locale.x=zh_XC"] 13 | -------------------------------------------------------------------------------- /static/image/checkin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/account.js: -------------------------------------------------------------------------------- 1 | import { getSetting } from './utils' 2 | 3 | export const getLoginState = function () { 4 | let loginState = { 5 | pc: getSetting('jjb_login-state_pc', { 6 | state: "unknown" 7 | }), 8 | m: getSetting('jjb_login-state_m', { 9 | state: "unknown" 10 | }), 11 | class: "unknown" 12 | } 13 | // 处理登录状态 14 | if (loginState.pc.state == 'alive' && loginState.m.state == 'alive') { 15 | loginState.class = "alive" 16 | } else if (loginState.pc.state == 'failed' && loginState.m.state == 'failed') { 17 | loginState.class = "failed" 18 | } else if (loginState.pc.state == 'failed' || loginState.m.state == 'failed' || loginState.m.state == 'alive' || loginState.pc.state == 'alive') { 19 | loginState.class = "warning" 20 | } 21 | return loginState 22 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report of bug / 如果你认为你发现了一项代码问题 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | 请详细的描述这个bug的细节 15 | 16 | **To Reproduce** 17 | 18 | Steps to reproduce the behavior 19 | 20 | 请详细描述重现这个bug的步骤 21 | 22 | 23 | **Expected behavior** 24 | 25 | A clear and concise description of what you expected to happen. 26 | 27 | 你认为这个功能本应如何工作 28 | 29 | **Screenshots** 30 | 31 | If applicable, add screenshots to help explain your problem. 32 | 33 | 如果有可能,请提供截图 34 | 35 | **Desktop (please complete the following information):** 36 | - OS: [e.g. iOS] 37 | - Browser [e.g. chrome, safari] 38 | - Version [e.g. 22] 39 | 40 | 你的操作系统、浏览器版本、京价保版本 41 | 42 | 43 | **Additional context** 44 | 45 | Add any other context about the problem here. 46 | 47 | 额外的描述 48 | -------------------------------------------------------------------------------- /static/image/m.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/image/pc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/image/hide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/messageBox/messageBox.js: -------------------------------------------------------------------------------- 1 | import msgboxVue from './messageBox.vue'; 2 | // 定义插件对象 3 | const MessageBox = {}; 4 | // vue的install方法,用于定义vue插件 5 | MessageBox.install = function (Vue, options) { 6 | const MessageBoxInstance = Vue.extend(msgboxVue); 7 | let currentMsg; 8 | const initInstance = () => { 9 | // 实例化vue实例 10 | currentMsg = new MessageBoxInstance(); 11 | let msgBoxEl = currentMsg.$mount().$el; 12 | document.body.appendChild(msgBoxEl); 13 | }; 14 | // 在Vue的原型上添加实例方法,以全局调用 15 | Vue.prototype.$msgBox = { 16 | showMsgBox (options) { 17 | if (!currentMsg) { 18 | initInstance(); 19 | } 20 | if (typeof options === 'string') { 21 | currentMsg.content = options; 22 | } else if (typeof options === 'object') { 23 | Object.assign(currentMsg, options); 24 | } 25 | // Object.assign方法只会拷贝源对象自身的并且可枚举的属性到目标对象 26 | return currentMsg.showMsgBox() 27 | .then(val => { 28 | currentMsg = null; 29 | return Promise.resolve(val); 30 | }) 31 | .catch(err => { 32 | currentMsg = null; 33 | return Promise.reject(err); 34 | }); 35 | } 36 | }; 37 | }; 38 | export default MessageBox; -------------------------------------------------------------------------------- /public/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 京价保安装成功! 6 | 7 | 8 |
9 |
10 |
11 |
12 |
13 |

京价保安装成功

14 |

我可以自动为你领取优惠券、自动签到领京豆、自动申请价格保护

15 |
16 |
17 |

18 | 立即登录京东 19 |

20 |

强烈建议登录时勾选“记住密码,并为我自动登录”选项,以便京价保自动完成工作

21 |
22 |
23 | 26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/we-dialog.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | -------------------------------------------------------------------------------- /src/components/events.vue: -------------------------------------------------------------------------------- 1 | 13 | 33 | 56 | -------------------------------------------------------------------------------- /src/components/toast/toast.js: -------------------------------------------------------------------------------- 1 | import Alert from './toast.vue'; 2 | var Toast = {} // 定义插件对象 3 | Toast.install = function (Vue, options) { // vue的install方法,用于定义vue插件 4 | // 如果toast还在,则不再执行 5 | if(document.getElementsByClassName('alertBox').length){ 6 | return 7 | } 8 | let toastTpl = Vue.extend(Alert) // 创建vue构造器 9 | // el:提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标。可以是css选择器,也可以是HTMLElement实例。 10 | // 在实例挂载之后,可以通过$vm.$el访问。 11 | // 如果这个选项在实例化时有用到,实例将立即进入编译过程。否则,需要显示调用vm.$mount()手动开启编译(如下) 12 | // 提供的元素只能作为挂载点。所有的挂载元素会被vue生成的dom替换。因此不能挂载在顶级元素(html, body)上 13 | // let $vm = new toastTpl({ 14 | // el: document.createElement('div') 15 | // }) 16 | let $vm = new toastTpl() // 实例化vue实例 17 | // 此处使用$mount来手动开启编译。用$el来访问元素,并插入到body中 18 | let tpl = $vm.$mount().$el 19 | document.body.appendChild(tpl) 20 | 21 | Vue.prototype.$toast = { // 在Vue的原型上添加实例方法,以全局调用 22 | show(options) { // 控制toast显示的方法 23 | if (typeof options === 'string') { // 对参数进行判断 24 | $vm.text = options // 传入props 25 | } 26 | else if (typeof options === 'object') { 27 | Object.assign($vm, options) // 合并参数与实例 28 | } 29 | $vm.show = true // 显示toast (防止每次弹窗创建一次新的定时器) 30 | setTimeout(()=>{ 31 | $vm.show = false 32 | },$vm.time) //消失时间 33 | }, 34 | hide() { // 控制toast隐藏的方法 35 | $vm.show = false 36 | } 37 | } 38 | } 39 | export default Toast;// 导出Toast(注意:此处不能用module exports导出,在一个文件中,不能同时使用require方式引入,而用module exports导出,两种方式不能混用) 40 | 41 | -------------------------------------------------------------------------------- /static/image/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/image/coupon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "京价保 - 京东价保助手", 4 | "short_name": "京价保", 5 | "description": "京价保(京价宝)是自动为你申请京东价格保护,自动签到领京豆、钢镚的多功能京东助手", 6 | "version": "0.1.1", 7 | "background": { 8 | "page": "background.html", 9 | "persistent": true 10 | }, 11 | "browser_action": { 12 | "default_icon": "static/image/jjb.png", 13 | "default_popup": "popup.html" 14 | }, 15 | "content_scripts": [ 16 | { 17 | "matches": ["*://*.jd.com/*", "*://*.jd.hk/*"], 18 | "js": [ 19 | "static/jquery.min.js", 20 | "static/content_script.js" 21 | ], 22 | "run_at": "document_end", 23 | "all_frames": true 24 | }, 25 | { 26 | "run_at": "document_start", 27 | "js": [ 28 | "static/mobile_script.js" 29 | ], 30 | "matches": [ 31 | "*://*.m.jd.com/*", 32 | "*://m.jr.jd.com/*", 33 | "*://wq.jd.com/*", 34 | "*://wqs.jd.com/*", 35 | "*://msitepp-fm.jd.com/*" 36 | ], 37 | "all_frames": true 38 | } 39 | ], 40 | "icons": { 41 | "16": "static/image/16.png", 42 | "48": "static/image/48.png", 43 | "128": "static/image/128.png" 44 | }, 45 | "web_accessible_resources": [ 46 | "static/priceChart.js", 47 | "static/touch-emulator.js" 48 | ], 49 | "permissions": [ 50 | "*://*.jd.com/*", 51 | "*://*.jd.hk/*", 52 | "webRequest", 53 | "webRequestBlocking", 54 | "alarms", 55 | "contextMenus", 56 | "notifications" 57 | ] 58 | } -------------------------------------------------------------------------------- /static/image/reload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/image/empty.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 京价保 6 | 7 | 8 | 9 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jjb", 3 | "version": "1.15.9", 4 | "author": "Ming", 5 | "private": true, 6 | "scripts": { 7 | "start": "npx webpack --config webpack.config.js", 8 | "build": "NODE_ENV=production yarn start", 9 | "dev": "webpack --watch" 10 | }, 11 | "dependencies": { 12 | "@antv/g2": "^3.5.8-beta.3", 13 | "@sunoj/touchemulator": "^0.0.2", 14 | "babel-plugin-transform-runtime": "^6.23.0", 15 | "dexie": "^2.0.4", 16 | "dialog-polyfill": "^0.5.1", 17 | "gulp": "^4.0.2", 18 | "gulp-clean-css": "^3.9.0", 19 | "gulp-concat": "^2.6.1", 20 | "gulp-preprocess": "^2.0.0", 21 | "gulp-replace": "^0.6.1", 22 | "gulp-watch": "^4.3.11", 23 | "hooper": "^0.1.5", 24 | "jobs": "^0.0.4", 25 | "jquery": "3.5", 26 | "lodash": "^4.17.10", 27 | "logline": "^1.1.2", 28 | "luxon": "^1.4.3", 29 | "tippy.js": "^4.2.0", 30 | "vue": "^2.6.11", 31 | "vue-infinite-loading": "^2.4.5", 32 | "vue-lazyload": "^1.3.3", 33 | "weui": "^1.1.3", 34 | "weui.js": "^1.1.4" 35 | }, 36 | "alias": { 37 | "vue": "./node_modules/vue/dist/vue.common.js" 38 | }, 39 | "staticFiles": { 40 | "staticPath": "static", 41 | "watcherGlob": "**" 42 | }, 43 | "devDependencies": { 44 | "babel-core": "^6.26.3", 45 | "babel-preset-env": "^1.7.0", 46 | "clean-webpack-plugin": "^3.0.0", 47 | "copy-webpack-plugin": "^5.1.1", 48 | "css-loader": "^3.5.3", 49 | "file-loader": "^6.0.0", 50 | "html-webpack-plugin": "^4.3.0", 51 | "less": "^3.11.1", 52 | "less-loader": "^6.0.0", 53 | "parcel-plugin-static-files-copy": "^2.3.1", 54 | "style-loader": "^1.2.1", 55 | "svg-inline-loader": "^0.8.2", 56 | "svg-url-loader": "^5.0.0", 57 | "url-loader": "^4.1.0", 58 | "vue-loader": "^15.9.1", 59 | "vue-template-compiler": "^2.6.11", 60 | "webpack": "^4.43.0", 61 | "webpack-cli": "^3.3.11" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /static/image/avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/variables.js: -------------------------------------------------------------------------------- 1 | let rewards = [ 2 | '给开发者加个鸡腿', 3 | '请开发者喝杯咖啡', 4 | '京价保就是好', 5 | '保价成功,感谢开发者', 6 | '返利到手,打赏开发者', 7 | '赞赏支持', 8 | '打赏声优' 9 | ] 10 | 11 | module.exports = { 12 | rewards: rewards, 13 | stateText: { 14 | "failed": "失败", 15 | "alive": "有效", 16 | "unknown": "未知" 17 | }, 18 | notices: [ 19 | { 20 | text: '京东页面经常修改,唯有你的支持才能让京价保保持更新持续运作。', 21 | button: rewards[5], 22 | type: 'reward', 23 | target: 'ming' 24 | }, 25 | { 26 | text: '京东的登录有效期很短,请在登录时勾选保存密码自动登录以便京价保自动完成工作。', 27 | button: rewards[0], 28 | type: 'reward', 29 | target: 'ming' 30 | }, 31 | { 32 | text: '软件开发需要开发者付出劳动和智慧,每一行代码都要付出相应的工作,并非唾手可得。', 33 | button: rewards[5], 34 | type: 'reward', 35 | target: 'ming' 36 | }, 37 | { 38 | text: '京价保并不强制付费,但如果它确实帮到你,希望你也能帮助它保持更新。', 39 | button: rewards[5], 40 | type: 'reward', 41 | target: 'ming' 42 | }, 43 | { 44 | text: '许多开源项目因为缺乏支持而停止更新,如果你希望京价保保持更新,请赞赏支持。', 45 | button: rewards[5], 46 | type: 'reward', 47 | target: 'ming' 48 | }, 49 | { 50 | text: '如果你的京东账号修改了密码,请在高级设置中选择清除密码重新登录来继续使用京价保', 51 | button: rewards[5], 52 | type: 'reward', 53 | target: 'ming' 54 | }, 55 | { 56 | text: '把京价保推荐给你的朋友同样能帮助京价保保持更新,如果缺乏使用者,开发者可能会放弃维护项目。', 57 | button: rewards[2], 58 | type: 'reward', 59 | target: 'ming' 60 | }, 61 | { 62 | text: '如果价保成功的话,打赏声优小姐姐几块钱她会很开心哦', 63 | button: rewards[6], 64 | type: 'reward', 65 | target: 'samedi' 66 | } 67 | ], 68 | recommendServices: [ 69 | { 70 | link: "https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=8c3eff7793dd70781315d9b5c9727c39&from=console", 71 | title: "腾讯云新客礼包", 72 | description: "新客户无门槛领取2775元代金券", 73 | class: "el-tag el-tag--success" 74 | }, 75 | { 76 | link: "https://www.boslife.me/aff.php?aff=435", 77 | title: "科学上网服务", 78 | description: "小明使用2年的科学上网服务", 79 | class: "el-tag el-tag--warning" 80 | }, 81 | { 82 | link: "https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=sqj7d3bm", 83 | title: "阿里云优惠券", 84 | description: "领取阿里云全品类优惠券", 85 | class: "el-tag" 86 | }, 87 | ] 88 | }; -------------------------------------------------------------------------------- /src/components/links.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 85 | 86 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon' 2 | 3 | export const rand = function (n) { 4 | return (Math.floor(Math.random() * n + 1)); 5 | } 6 | export const macId = function () { 7 | let macId = null; 8 | let storedMacId = localStorage.getItem('machine-id'); 9 | if (!storedMacId) { 10 | storedMacId = Math.random().toString(36).slice(2); 11 | localStorage.setItem('machine-id', storedMacId); 12 | } 13 | macId = storedMacId; 14 | return macId 15 | } 16 | export const price = function (price) { 17 | return Number(Number(price).toFixed(2)) 18 | } 19 | export const getSetting = function (settingKey, defaultValue) { 20 | let setting = localStorage.getItem(settingKey) 21 | if (setting) { 22 | try { 23 | setting = JSON.parse(setting) 24 | } catch (error) { } 25 | } 26 | return setting ? setting : defaultValue 27 | } 28 | export const saveSetting = function (settingKey, value) { 29 | return localStorage.setItem(settingKey, JSON.stringify(value)) 30 | } 31 | export const readableTime = function (dateTime, withSeconds = false) { 32 | const mode = withSeconds ? DateTime.TIME_24_WITH_SECONDS : DateTime.TIME_SIMPLE 33 | if (DateTime.local().hasSame(dateTime, 'day')) { 34 | return '今天 ' + dateTime.setLocale('zh-cn').toLocaleString(mode) 35 | } 36 | if (DateTime.local().hasSame(dateTime.plus({ days: 1 }), 'day')) { 37 | return '昨天 ' + dateTime.setLocale('zh-cn').toLocaleString(mode) 38 | } 39 | return dateTime.setLocale('zh-cn').toFormat('f') 40 | } 41 | export const versionCompare = function (v1, v2, options) { 42 | var lexicographical = options && options.lexicographical, 43 | zeroExtend = options && options.zeroExtend, 44 | v1parts = v1.split('.'), 45 | v2parts = v2.split('.'); 46 | function isValidPart(x) { 47 | return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); 48 | } 49 | if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { 50 | return NaN; 51 | } 52 | if (zeroExtend) { 53 | while (v1parts.length < v2parts.length) v1parts.push("0"); 54 | while (v2parts.length < v1parts.length) v2parts.push("0"); 55 | } 56 | if (!lexicographical) { 57 | v1parts = v1parts.map(Number); 58 | v2parts = v2parts.map(Number); 59 | } 60 | for (var i = 0; i < v1parts.length; ++i) { 61 | if (v2parts.length == i) { 62 | return 1; 63 | } 64 | if (v1parts[i] == v2parts[i]) { 65 | continue; 66 | } 67 | else if (v1parts[i] > v2parts[i]) { 68 | return 1; 69 | } 70 | else { 71 | return -1; 72 | } 73 | } 74 | if (v1parts.length != v2parts.length) { 75 | return -1; 76 | } 77 | return 0; 78 | } -------------------------------------------------------------------------------- /static/image/online.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/mobile_script.js: -------------------------------------------------------------------------------- 1 | var actualCode = '(' + function() { 2 | 'use strict'; 3 | /** 4 | * Creates a read/writable property which returns a function set for write/set (assignment) 5 | * and read/get access on a variable 6 | * 7 | * @param {Any} value initial value of the property 8 | */ 9 | function createProperty(value) { 10 | var _value = value; 11 | 12 | /** 13 | * Overwrite getter. 14 | * 15 | * @returns {Any} The Value. 16 | * @private 17 | */ 18 | function _get() { 19 | return _value; 20 | } 21 | 22 | /** 23 | * Overwrite setter. 24 | * 25 | * @param {Any} v Sets the value. 26 | * @private 27 | */ 28 | function _set(v) { 29 | _value = v; 30 | } 31 | 32 | return { 33 | "get": _get, 34 | "set": _set 35 | }; 36 | }; 37 | 38 | /** 39 | * Creates or replaces a read-write-property in a given scope object, especially for non-writable properties. 40 | * This also works for built-in host objects (non-DOM objects), e.g. navigator. 41 | * Optional an initial value can be passed, otherwise the current value of the object-property will be set. 42 | * 43 | * @param {Object} objBase e.g. window 44 | * @param {String} objScopeName e.g. "navigator" 45 | * @param {String} propName e.g. "userAgent" 46 | * @param {Any} initValue (optional) e.g. window.navigator.userAgent 47 | */ 48 | function makePropertyWritable(objBase, objScopeName, propName, initValue) { 49 | var newProp, 50 | initObj; 51 | 52 | if (objBase && objScopeName in objBase && propName in objBase[objScopeName]) { 53 | if(typeof initValue === "undefined") { 54 | initValue = objBase[objScopeName][propName]; 55 | } 56 | 57 | newProp = createProperty(initValue); 58 | 59 | try { 60 | Object.defineProperty(objBase[objScopeName], propName, newProp); 61 | } catch (e) { 62 | initObj = {}; 63 | initObj[propName] = newProp; 64 | try { 65 | objBase[objScopeName] = Object.create(objBase[objScopeName], initObj); 66 | } catch(e) { 67 | // Workaround, but necessary to overwrite native host objects 68 | } 69 | } 70 | } 71 | }; 72 | 73 | makePropertyWritable(window, "navigator", "userAgent"); 74 | if (window.location.href.indexOf("/pc/") < 0) { 75 | window.navigator.userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/10.2 Mobile/15E148 Safari/604.1 jdjr-app ios'; 76 | } 77 | } + ')();'; 78 | 79 | var s = document.createElement('script'); 80 | s.textContent = actualCode; 81 | document.documentElement.appendChild(s); 82 | s.remove(); -------------------------------------------------------------------------------- /static/image/offline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 4 | const CopyPlugin = require('copy-webpack-plugin'); 5 | const { EnvironmentPlugin } = require('webpack') 6 | 7 | function modifyManifest(buffer) { 8 | let manifest = JSON.parse(buffer.toString()); 9 | 10 | // make any modifications you like, such as 11 | if (process.env.VERSION) { 12 | manifest.version = process.env.VERSION; 13 | } 14 | 15 | // pretty print to JSON with two spaces 16 | manifest_JSON = JSON.stringify(manifest, null, 2); 17 | return manifest_JSON; 18 | } 19 | 20 | module.exports = { 21 | mode: process.env.NODE_ENV || 'development', 22 | entry: { 23 | background: './src/background.js', 24 | content_script: './src/content_script.js', 25 | priceChart: './src/priceChart.js', 26 | start: './src/start.js', 27 | popup: './src/popup.js' 28 | }, 29 | output: { 30 | filename: 'static/[name].js', 31 | path: path.resolve(__dirname, 'dist') 32 | }, 33 | resolve: { 34 | alias: { 35 | 'vue$': 'vue/dist/vue.runtime.esm.js' 36 | }, 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.(png|jpg|gif)$/i, 42 | use: [ 43 | { 44 | loader: 'url-loader', 45 | loader: 'file-loader', 46 | options: { 47 | limit: 8192, 48 | outputPath: 'images', 49 | esModule: false 50 | }, 51 | }, 52 | ], 53 | }, 54 | { 55 | test: /\.svg$/, 56 | use: [ 57 | 'svg-url-loader', 58 | ] 59 | }, 60 | { 61 | test: /\.less$/, 62 | use: [ 63 | 'vue-style-loader', 64 | 'css-loader', 65 | 'less-loader' 66 | ] 67 | }, 68 | { 69 | test: /\.css$/i, 70 | use: ['vue-style-loader', 'style-loader', 'css-loader'], 71 | }, 72 | { 73 | test: /\.vue$/, 74 | loader: 'vue-loader' 75 | } 76 | ] 77 | }, 78 | plugins: [ 79 | new CopyPlugin([ 80 | { 81 | from: "public/manifest.json", 82 | to: "./manifest.json", 83 | transform(content, path) { 84 | return modifyManifest(content) 85 | } 86 | }, 87 | { from: 'public', to: '.' }, 88 | { 89 | from: 'static/audio', 90 | to: 'static/audio' 91 | }, 92 | { 93 | from: 'static/image/icon', 94 | to: 'static/image' 95 | }, 96 | { 97 | from: 'src/mobile_script.js', 98 | to: 'static' 99 | }, 100 | { 101 | from: 'node_modules/@sunoj/touchemulator/touch-emulator.js', 102 | to: 'static' 103 | }, 104 | { 105 | from: 'node_modules/jquery/dist/jquery.min.js', 106 | to: 'static' 107 | } 108 | ]), 109 | new CleanWebpackPlugin(), 110 | new VueLoaderPlugin(), 111 | new EnvironmentPlugin({ 112 | NODE_ENV: 'development', 113 | BROWSER: 'chrome', 114 | VERSION: '0.1.1', 115 | BUILDID: 0 116 | }) 117 | ] 118 | }; -------------------------------------------------------------------------------- /src/components/toast/toast.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 73 | 152 | -------------------------------------------------------------------------------- /src/components/popup.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 90 | -------------------------------------------------------------------------------- /src/components/loading.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 33 | 142 | -------------------------------------------------------------------------------- /src/popup.js: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash" 2 | $ = window.$ = window.jQuery = require('jquery') 3 | 4 | import tippy from 'tippy.js' 5 | import 'weui'; 6 | import Vue from 'vue' 7 | 8 | import { getLoginState } from './account' 9 | 10 | import App from './components/App.vue'; 11 | import VueLazyload from 'vue-lazyload' 12 | 13 | import MessageBox from './components/messageBox/messageBox'; 14 | Vue.use(MessageBox); 15 | 16 | import Toast from './components/toast/toast'; 17 | Vue.use(Toast); 18 | 19 | Vue.use(VueLazyload, { 20 | preLoad: 1.3, 21 | error: 'https://jjbcdn.zaoshu.so/web/img_error.png', 22 | attempt: 1 23 | }) 24 | new Vue({ 25 | el: '#app', 26 | render: h => h(App) 27 | }) 28 | 29 | 30 | 31 | $( document ).ready(function() { 32 | const paid = localStorage.getItem('jjb_paid'); 33 | const account = localStorage.getItem('jjb_account'); 34 | let loginState = getLoginState() 35 | 36 | // tippy 37 | tippy('.tippy') 38 | 39 | // 是否已存在弹窗 40 | function isNoDialog(){ 41 | return ($(".js_dialog:visible").length < 1) && ($(".weui-dialog:visible").length < 1) 42 | } 43 | 44 | // 常规弹窗延迟200ms 45 | setTimeout(() => { 46 | if (paid) { 47 | $("#dialogs").hide() 48 | } 49 | 50 | // 没有弹框 且 未登录账号 51 | if (isNoDialog() && (!account && loginState.class == "failed") || loginState.class == "unknown") { 52 | $("#loginNotice").show(); 53 | } 54 | 55 | if (!account) { 56 | $("#clearAccount").addClass('weui-btn_disabled') 57 | } 58 | }, 200); 59 | 60 | 61 | $(".weui-dialog__ft a").on("click", function () { 62 | $("#dialogs").hide() 63 | $("#listenAudio").hide() 64 | $("#loginNotice").hide() 65 | $("#changeLogs").hide() 66 | if ($(this).data('action') == 'paid') { 67 | chrome.runtime.sendMessage({ 68 | text: "paid" 69 | }, function(response) { 70 | console.log("Response: ", response); 71 | }); 72 | } else { 73 | if ($(this).data('action') == 'pay') { 74 | showReward() 75 | } 76 | } 77 | }) 78 | 79 | $("#listen").on("click", function () { 80 | $("#listenAudio").show() 81 | }) 82 | 83 | $(".showLoginState").on("click", function () { 84 | $("#loginNotice").show() 85 | }) 86 | 87 | $("#openFeedback").on("click", function () { 88 | // 加载反馈 89 | if ($("#feedbackIframe").attr('src') == '') { 90 | $("#feedbackIframe").attr('src', `https://i.duotai.net/forms/yovwz?version=${process.env.VERSION}`) 91 | setTimeout(function () { 92 | $('.iframe-loading').hide() 93 | }, 800) 94 | } 95 | $("#feedbackDialags").show() 96 | }) 97 | 98 | $("#openFaq").on("click", function () { 99 | // 加载反馈 100 | if ($("#faqIframe").attr('src') == '') { 101 | $("#faqIframe").attr('src', "https://i.duotai.net/forms/oqyvk/1lpb11l2") 102 | setTimeout(function () { 103 | $('.iframe-loading').hide() 104 | }, 800) 105 | } 106 | $("#faqDialags").show() 107 | }) 108 | 109 | $("#feedbackDialags .js-close").on("click", function () { 110 | $("#feedbackDialags").hide() 111 | }) 112 | 113 | $("#faqDialags .js-close").on("click", function () { 114 | $("#faqDialags").hide() 115 | }) 116 | 117 | $("#jEventDialags .js-close").on("click", function () { 118 | $("#jEventDialags").hide() 119 | }) 120 | 121 | $("#dialogs .js-close").on("click", function () { 122 | $("#dialogs").hide() 123 | }) 124 | 125 | $("#wechatDialags .js-close").on("click", function () { 126 | $("#wechatDialags").hide() 127 | }) 128 | 129 | 130 | $(".openLogin").on("click", function () { 131 | chrome.runtime.sendMessage({ 132 | text: "openLogin", 133 | }, function(response) { 134 | console.log("Response: ", response); 135 | }); 136 | }) 137 | 138 | $("#pricePro").on("click", function () { 139 | chrome.runtime.sendMessage({ 140 | text: "openPricePro" 141 | }, function (response) { 142 | console.log("Response: ", response); 143 | }) 144 | }) 145 | 146 | }) 147 | 148 | // 防止缩放 149 | chrome.tabs.getZoomSettings(function (zoomSettings) { 150 | if (zoomSettings.defaultZoomFactor > 1 && zoomSettings.scope == 'per-origin' && zoomSettings.mode == 'automatic') { 151 | let zoomPercent = (100 / (zoomSettings.defaultZoomFactor * 100)) * 100; 152 | document.body.style.zoom = zoomPercent + '%' 153 | } 154 | }) 155 | -------------------------------------------------------------------------------- /src/components/messageBox/messageBox.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 102 | 197 | 198 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 京价保 - 自动申请京东价格保护 2 | 3 | 京价保是一个帮你监控商品价格变化自动申请京东价格保护,顺便帮你签到领京豆、钢镚的 Chrome 拓展。 4 | 5 | **强烈推荐使用 Chrome 商店安装**(这样才能获得自动更新): 6 | 7 | 8 | Chrome
  9 |   web store 10 | 11 | 12 | 同时提供 13 | 14 | Firefox 版本: 15 | 16 | 17 | 安装 Firefox 版 18 | 19 | 20 | 21 | Microsoft Edge 版本: 22 | 23 | 24 | 安装 Edge 版 25 | 26 | 27 | 或者直接下载的 CRX文件手动安装(非常不建议) 28 | 29 | 30 | 下载 CRX 文件 31 | 32 | 33 | *此方法通常只适用于 Chromium 内核的国产浏览器,因为 Chrome 出于安全原因已禁止通过除 Chrome 官方商店以外的其他渠道安装拓展。* 34 | 35 | ## 主要功能 36 | 37 | * 自动监控最近订单商品的价格变化,在商品降价时自动申请价格保护 38 | * 自动签到领取京豆 39 | * 自动领白条券 40 | * 自动领Plus券 41 | * 自动领全品类 42 | * 商品价格走势图 43 | 44 | ## 界面截图 45 | 46 | ![主界面](https://jjbcdn.zaoshu.so/jjb-popup.2.5.8.1.png) 47 | 48 | 49 | ## 重要提示 50 | 51 | 1. 京价保并非开源软件,不许可您以任何形式进行再发行,请仔细阅读[#协议和授权](https://github.com/sunoj/jjb#%E5%8D%8F%E8%AE%AE%E5%92%8C%E6%8E%88%E6%9D%83)。 52 | 53 | 2. 当前仓库是插件源代码,无法直接安装,如需安装请自行参考 [#如何开发](https://github.com/sunoj/jjb#%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91) 编译。 54 | 55 | 3. 京价保绝对不会在任何情况下强行劫持任何网页的访问,如果发现类似问题请善用 Google 搜索并使用二分法停用插件排除,同时考虑运营商劫持的可能性。或者,为了防止京价保的影响亦可直接卸载京价保。故不再回复类似的 Issue。详情参考:[#安全提示](https://github.com/sunoj/jjb#%E5%AE%89%E5%85%A8%E6%8F%90%E7%A4%BA) 56 | 57 | 58 | ## 如何开发 59 | 60 | * 安装依赖 61 | > yarn 62 | 63 | * 开始开发 64 | > VERSION=2.1.1 BROWSER=chrome yarn build 65 | 66 | `主要作用就是合并压缩代码,质疑代码和市场版本不一致,请先自行打包一下再对比` 67 | 68 | ## 价格 69 | 70 | 京价保可以免费使用,如果京价保确实帮到你,你可以通过打赏作者来鼓励京价保继续更新。 71 | 72 | ![打赏作者](https://jjbcdn.zaoshu.so/weixin_pay.png?imageView2/0/h/300) 73 | 74 | 你的打赏能帮助京价保保持更新,适配京东的页面修改,添加更多自动功能。 75 | 76 | ## 历史价格走势图 77 | 78 | “[价格走势图](https://blog.jjb.im/price-chart.html)”是京价保 2.0 版本引入的新功能。 79 | 80 | 这是一个计划已久的功能,京价保的核心价保功能原本已经在监控已购商品的价格变化,本功能通过将这些价格信息共享,实现了一个互助的价格历史数据库。 81 | 82 | ![](https://i.v2ex.co/6G1a56k5.png) 83 | 84 | 目前京价保的价格历史数据库只覆盖了不到 2% 的 SKU,如果在商品页面看不到走势图请不要意外。 85 | 86 | ## 安全提示 87 | 88 | 京价保不会在任何情况下强行劫持访问、插入恶意代码、上传隐私信息或利用你的电脑挖矿。 89 | 90 | 若你发现任何类似问题,请首先确保你使用的是商店版本,不建议在任何情况下使用第三方提供的安装包。 91 | 92 | 京价保目前有约两万人正在使用(包括 Chrome 和 Firefox 以及其他浏览器非市场版本),其中至少有数千名开发者,京价保的代码有近千人关注,若怀疑京价保代码有问题,不妨直接审查代码。 93 | 94 | 如果你确实发现京东页面(或者还有其他电商和非电商网站)被劫持,请考虑运营商劫持的可能性,并筛查拓展列表。 95 | 96 | ### 隐私政策 97 | 98 | 京价保的部分功能链接带有京东联盟的返利链接,如果介意可以关闭这些功能。 99 | 100 | 了解详见:https://blog.jjb.im/policy.html 101 | 102 | ### 关于黑号 103 | 104 | 网络上有很多人表示多次领券和申请价格保护会导致账号“黑号”,不知道其对京东的风控政策是如何了解的,为何正常使用其官方提供的功能会导致账号被“黑”? 105 | 106 | 目前没有任何一例可靠的报告表明使用京价保会导致“黑号”,作者本人和同事家人使用京价保近一年,即使在开发阶段疯狂测试申请价格保护账号亦无任何异常。 107 | 108 | 但,若你担心此项风险,决定自我审查,关闭领券功能或直接卸载京价保即可。 109 | 110 | > Tips. 如果发现这部分情况下无法领券,有可能是因为你参加了一个特殊的活动:“爱奇艺会员+京东PLUS 89元”,了解详情可参考:https://github.com/sunoj/jjb/issues/96 111 | 112 | ## 系统支持 113 | 114 | 目前京价保对 Windows 和 Mac 平台的 Chrome 有较好的支持。 115 | 116 | 据用户反应,部分 Chromium 内核的国产浏览器可能有兼容问题(例如搜狗浏览器)。 117 | 118 | Ubuntu 有明确的兼容问题,由于作者不拥有任何 Ubuntu 设备,因此暂时无法解决。 119 | 120 | ## 获取信息 121 | 122 | 你可以 Telegram 上关注京价保: https://t.me/jingjiabao 123 | 124 | 您还可以关注京价保的公众号(发布更新通知): 125 | 126 | ![京价保公众号](https://jjbcdn.zaoshu.so/wechat/qrcode_for_gh_21550d50400c_430.jpg?imageView2/0/h/300) 127 | 128 | 京价保现在还有一个官方博客: https://blog.jjb.im 129 | 130 | 或者也可以关注京价保的知乎专栏:https://zhuanlan.zhihu.com/jjblog 131 | 132 | ## 协议和授权 133 | 134 | 京价保并非一个开源软件,作者保留全部的权利。 135 | 公开源代码的目的是为了让使用者能够审计代码,但是你仍然可以就以下方式合法的使用本项目的全部代码和资源: 136 | 137 | 1. 个人使用 138 | 2. 以学习目的使用全部或部分代码 139 | 140 | 但你不可以: 141 | 142 | 1. 将本项目的部分或全部代码和资源进行任何形式的再发行(尤其是上传到 Chrome 商店) 143 | 2. 利用本项目的部分或全部代码和资源进行任何商业行为 144 | 145 | ## 贡献代码 146 | 147 | 京价保并非一个开源项目,也不是社区共同创造,其全部功能由作者独立完成。 148 | 149 | 如果你愿意放弃所有权利,并将权利无条件转让给京价保作者,欢迎您贡献代码。 150 | 151 | ## 提交反馈 152 | 153 | 欢迎提交 issue,请写清楚遇到问题的原因,浏览器和操作系统环境,重现的流程。 154 | 155 | 任何反馈问题的 issue 均需按照模板格式填写,否则将被直接关闭。 156 | 157 | 如果有开发能力,建议在本地调试出出错的代码。 158 | 159 | 不接受功能请求的 issue,功能请求可能会被直接关闭,请谅解(正确的方式是打赏并附言)。 160 | 161 | 若有功能建议或其他非技术反馈,请使用应用内反馈。由于京价保不设客服人员,反馈将默认不回复。 162 | 163 | ## 联系作者 164 | 165 | 请发邮件至:`ming@tiny.group` 166 | 167 | 请勿发送功能咨询邮件,将不会收到回复。相关功能细节请自行了解。 168 | 169 | ## 衍生项目 170 | 171 |

172 | 茶友会 173 |

174 | -------------------------------------------------------------------------------- /src/components/guide.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 101 | -------------------------------------------------------------------------------- /src/components/report.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 117 | 188 | -------------------------------------------------------------------------------- /src/db.js: -------------------------------------------------------------------------------- 1 | import Dexie from 'dexie'; 2 | import { readableTime, getSetting, saveSetting } from './utils' 3 | import { DateTime } from 'luxon' 4 | 5 | // 6 | // Declare Database 7 | // 8 | const db = new Dexie("orders"); 9 | 10 | db.version(1).stores({ orders: "++id,timestamp" }); 11 | db.version(1).stores({ messages: "++id,type,timestamp" }); 12 | 13 | db.version(2).stores({ 14 | orders: "++id,timestamp", 15 | messages: "++id,type,timestamp", 16 | }); 17 | 18 | db.version(3).stores({ 19 | orders: "++id,timestamp", 20 | messages: "++id,type,timestamp", 21 | taskLogs: "++id,taskId,timestamp", 22 | }); 23 | 24 | async function findGood(orderId, good) { 25 | await db.orders.where('id').equals(orderId).modify(order => { 26 | order.goods.push(good); 27 | }); 28 | } 29 | 30 | async function findOrder(orderId, data) { 31 | let order = await db.orders.where('id').equals(orderId).toArray(); 32 | if (order && order.length > 0) return await db.orders.update(orderId, data) 33 | let orderInfo = Object.assign(data, { 34 | id: orderId, 35 | }) 36 | return await db.orders.add(orderInfo); 37 | } 38 | 39 | async function updateOrders() { 40 | let orders = await db.orders.where('timestamp').above(Date.now() - 60*60*1000*24*45).reverse().sortBy('timestamp') 41 | 42 | if (orders && orders.length > 0) { 43 | orders = orders.filter(order => order.goods && order.goods.length > 0); 44 | } 45 | saveSetting('jjb_orders', orders) 46 | chrome.runtime.sendMessage({ 47 | action: "orders_updated", 48 | orders: orders 49 | }); 50 | } 51 | 52 | async function newMessage(messageId, data) { 53 | let message = await db.messages.where('id').equals(messageId).toArray(); 54 | if (message && message.length > 0) return await db.messages.update(messageId, data) 55 | let messageInfo = Object.assign(data, { 56 | id: messageId, 57 | }) 58 | return await db.messages.add(messageInfo); 59 | } 60 | 61 | async function updateMessages() { 62 | // 最多只展示最近 30 天的消息 63 | let last30Day = Date.now() - 60*60*1000*24*30; 64 | let messages = await db.messages.where('timestamp').above(last30Day).reverse().sortBy('timestamp') 65 | saveSetting('jjb_messages', messages) 66 | chrome.runtime.sendMessage({ 67 | action: "messages_updated", 68 | messages: messages 69 | }); 70 | } 71 | 72 | async function getTodayMessagesByTaskId(taskId) { 73 | let now = new Date(); 74 | let startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate()) / 1 75 | let messages = await db.messages.where('timestamp').above(startOfDay).reverse().sortBy('timestamp') 76 | let todayMessages = messages.filter((message) => { 77 | return message.taskId == taskId 78 | }) 79 | saveSetting(`temporary:task-messages:${taskId}:${startOfDay}`, todayMessages) 80 | } 81 | 82 | async function addTaskLog(task) { 83 | const timestamp = Date.now() 84 | const log = await db.taskLogs.add({ 85 | id: timestamp, 86 | taskId: task.id, 87 | timestamp: timestamp, 88 | mode: task.mode, 89 | results: [] 90 | }); 91 | console.log('addTaskLog', log, timestamp) 92 | } 93 | 94 | async function findAndUpdateTaskResult(taskId, result) { 95 | let last5Minute = Date.now() - 60*5*1000; 96 | const lastRunLog = (await db.taskLogs.where('timestamp').above(last5Minute).reverse().sortBy('timestamp')).find((log) => { 97 | return log.taskId == taskId 98 | }) 99 | console.log('lastRunLog', taskId, lastRunLog, result) 100 | if (lastRunLog) { 101 | await db.taskLogs.where('id').equals(lastRunLog.id).modify(log => { 102 | log.results.push(result); 103 | }); 104 | } 105 | } 106 | 107 | async function getTaskLog(taskId, days = 7) { 108 | let lastWeek = Date.now() - 60*60*1000*24*days; 109 | const taskLogs = await db.taskLogs.where('timestamp').above(lastWeek).filter((log) => { 110 | return log.taskId == taskId 111 | }).reverse().sortBy('timestamp') 112 | return taskLogs.map((log) => { 113 | log.displayTime = readableTime(DateTime.fromMillis(log.timestamp)); 114 | return log 115 | }) 116 | } 117 | 118 | async function countTaskLog(taskId, hours = 1) { 119 | let lastHours = Date.now() - 60*60*1000*hours; 120 | return await db.taskLogs.where('timestamp').above(lastHours).filter((log) => { 121 | return log.taskId == taskId 122 | }).count() 123 | } 124 | 125 | async function getTaskUsageAndSave(taskId) { 126 | const actualUsage = { 127 | hour: await countTaskLog(taskId, 1) || 0, 128 | daily: await countTaskLog(taskId, 24) || 0, 129 | weekly: await countTaskLog(taskId, 24*7) || 0, 130 | } 131 | saveSetting(`task-usage:${taskId}`, actualUsage) 132 | } 133 | 134 | function getTaskUsageImmediately(taskId) { 135 | const usage = getSetting(`task-usage:${taskId}`, { 136 | hour: 0, 137 | daily: 0, 138 | weekly: 0 139 | }) 140 | getTaskUsageAndSave(taskId) 141 | return usage 142 | } 143 | 144 | function getTodayMessagesByTaskIdImmediately(taskId) { 145 | let now = new Date(); 146 | let startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate()) / 1 147 | const messages = getSetting(`temporary:task-messages:${taskId}:${startOfDay}`, []) 148 | getTodayMessagesByTaskId(taskId) 149 | return messages 150 | } 151 | 152 | export { 153 | findGood, 154 | findOrder, 155 | updateOrders, 156 | newMessage, 157 | updateMessages, 158 | addTaskLog, 159 | getTaskLog, 160 | findAndUpdateTaskResult, 161 | getTaskUsageImmediately, 162 | getTodayMessagesByTaskIdImmediately 163 | }; -------------------------------------------------------------------------------- /src/components/login-notice.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 137 | -------------------------------------------------------------------------------- /src/components/support.vue: -------------------------------------------------------------------------------- 1 | 103 | 104 | -------------------------------------------------------------------------------- /static/style/content.css: -------------------------------------------------------------------------------- 1 | .jjb iframe{ 2 | border: 0; 3 | text-align: center; 4 | width: 100%; 5 | height: 200px; 6 | } 7 | 8 | .jjb-login{ 9 | cursor: pointer; 10 | background: #4CAF50; 11 | border: 1px solid #4CAF50; 12 | display: block; 13 | width: 96%; 14 | height: 31px; 15 | line-height: 31px; 16 | color: #fff; 17 | padding: 4px 2%; 18 | font-size: 16px; 19 | text-align: center; 20 | } 21 | 22 | .auto_login{ 23 | margin-bottom: 5px; 24 | } 25 | 26 | 27 | dialog { 28 | position: fixed; 29 | top: 50%; 30 | transform: translate(0, -50%); 31 | width: 50%; 32 | max-width: 450px; 33 | min-width: 400px; 34 | min-height: 160px; 35 | left: 0; 36 | right: 0; 37 | width: fit-content; 38 | height: fit-content; 39 | margin: auto; 40 | border: solid; 41 | padding: 2em; 42 | background: white; 43 | color: black; 44 | display: block; 45 | } 46 | 47 | dialog:not([open]) { 48 | display: none; 49 | } 50 | 51 | dialog h3{ 52 | font-size: 16px; 53 | } 54 | 55 | dialog .consideration{ 56 | margin: 10px 0; 57 | font-size: 15px; 58 | } 59 | dialog .consideration p { 60 | margin-bottom: 5px; 61 | } 62 | 63 | dialog+.backdrop { 64 | position: fixed; 65 | top: 0; 66 | right: 0; 67 | bottom: 0; 68 | left: 0; 69 | background: rgba(0, 0, 0, 0.3); 70 | } 71 | 72 | ._dialog_overlay { 73 | position: fixed; 74 | top: 0; 75 | right: 0; 76 | bottom: 0; 77 | left: 0; 78 | } 79 | 80 | dialog.message{ 81 | max-width: none; 82 | min-width: 300px; 83 | min-height: auto; 84 | background: #84c460; 85 | border-color: rgba(78, 162, 32,0.5); 86 | text-align: center; 87 | border-radius: 6px; 88 | box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); 89 | } 90 | 91 | dialog .green-text{ 92 | background-color: #85c360; 93 | color: #fff; 94 | font-size: 1.6em; 95 | font-weight: 600; 96 | } 97 | 98 | dialog .tips { 99 | color: #999; 100 | font-size: 12px; 101 | color: #eee; 102 | margin-top: 10px; 103 | } 104 | 105 | dialog.fixed { 106 | position: fixed; 107 | top: 50%; 108 | transform: translate(0, -50%); 109 | } 110 | 111 | dialog .actions{ 112 | position: absolute; 113 | bottom: 15px; 114 | right: 15px; 115 | } 116 | 117 | dialog .actions .forcedbuy { 118 | margin-right: 15px; 119 | } 120 | 121 | dialog .admonish{ 122 | position: absolute; 123 | bottom: -20px; 124 | color: #333; 125 | font-size: 12px; 126 | left: 50%; 127 | margin-left: -60px; 128 | color: #999; 129 | font-style: italic; 130 | } 131 | 132 | dialog .close { 133 | position: absolute; 134 | right: 5px; 135 | top: 3px; 136 | padding: 5px; 137 | font-size: 18px; 138 | color: #555; 139 | font-weight: 500; 140 | } 141 | 142 | 143 | #username_login .code-box img{ 144 | width: 150px; 145 | height: 60px; 146 | margin-left: -60px; 147 | } 148 | #input-code .icon-clear { 149 | right: 140px; 150 | } 151 | 152 | #username_login .tips{ 153 | font-size: 12px; 154 | padding: 2px; 155 | display: block; 156 | width: 100%; 157 | text-align: center; 158 | margin: 0; 159 | color: #666; 160 | } 161 | 162 | .jjbPriceChart{ 163 | position: static; 164 | float: left; 165 | width: 100%; 166 | background-color: #fff; 167 | border: 1px solid #eee; 168 | } 169 | 170 | .jjbPriceChart .title { 171 | font-size: 14px; 172 | line-height: 24px; 173 | height: 28px; 174 | padding: 6px 10px; 175 | background-color: #f7f7f7; 176 | border-bottom: 1px solid #eee; 177 | } 178 | 179 | .g2-tooltip{ 180 | position: absolute; 181 | background: #ffffffe0; 182 | border: 1px solid #dededec2; 183 | padding: 1em 2em; 184 | min-width: 180px; 185 | } 186 | 187 | .g2-tooltip .promotions li{ 188 | font-size: 12px; 189 | margin-bottom: 0.5em; 190 | } 191 | 192 | .g2-tooltip .g2-tooltip-list li{ 193 | font-size: 14px; 194 | margin-bottom: 0.5em; 195 | } 196 | 197 | .g2-tooltip .tag{ 198 | color: #df3033; 199 | background: 0 0; 200 | border: 1px solid #df3033; 201 | padding: 2px 3px; 202 | margin-right: 5px; 203 | display: inline-block; 204 | line-height: 16px; 205 | } 206 | 207 | .jjbPriceChart .provider { 208 | padding: 3px 10px; 209 | position: absolute; 210 | right: 5px; 211 | bottom: 5px; 212 | } 213 | 214 | #jjbPriceChart{ 215 | width: 100%; 216 | background: #fff; 217 | } 218 | 219 | #disablePriceChart{ 220 | float: right; 221 | padding: 0px 4px; 222 | cursor: pointer; 223 | font-size: 16px; 224 | font-weight: 600; 225 | } 226 | 227 | #jjbPriceChart .no_data, 228 | #jjbPriceChart .loading { 229 | text-align: center; 230 | padding: 20px; 231 | background-position-y: top; 232 | padding-top: 30px; 233 | margin-top: 10px; 234 | } 235 | 236 | .first_area_md { 237 | height: auto !important; 238 | position: relative; 239 | } 240 | 241 | .first_area_md .jjbPriceChart{ 242 | margin-top: 20px; 243 | } 244 | 245 | .price_notice{ 246 | display: none; 247 | } 248 | 249 | .utm_source-notice{ 250 | text-align: center; 251 | background: #fdedd2; 252 | color: #947219; 253 | padding: 0.2em .6em; 254 | } 255 | 256 | .utm_source-notice .report{ 257 | float: right; 258 | cursor: pointer; 259 | color: #cc1f1f; 260 | background: #fff; 261 | padding: 0px 10px; 262 | border-radius: 2px; 263 | } 264 | 265 | .reportUtmSource .weui-dialog{ 266 | max-width: 500px; 267 | background: #f8f8f8; 268 | } 269 | 270 | #specialPromotion { 271 | width: 80%; 272 | display: inline-block; 273 | height: 22px; 274 | line-height: 24px; 275 | } 276 | 277 | .special-promotion-item{ 278 | padding: 3px 10px; 279 | vertical-align: middle; 280 | } 281 | 282 | .special-promotion-item a{ 283 | font-size: 14px; 284 | vertical-align: middle; 285 | } 286 | 287 | .special-promotion-item .icon{ 288 | padding-right: 10px; 289 | float: left; 290 | } 291 | 292 | 293 | #specialPromotion .promotions{ 294 | float: left; 295 | width: 80%; 296 | } 297 | 298 | #specialPromotion .controller{ 299 | width: 20%; 300 | float: right; 301 | display: flex; 302 | align-items: flex-end; 303 | justify-content: flex-end; 304 | padding: 8px 0px; 305 | } 306 | 307 | #specialPromotion .controller .item__child { 308 | cursor: pointer; 309 | min-width: 12px; 310 | min-width: 1.2rem; 311 | min-height: 4px; 312 | min-height: 0.4rem; 313 | display: block; 314 | margin: 0 3px; 315 | border: 1px solid #ddd; 316 | background-color: #dddddd; 317 | } 318 | 319 | #specialPromotion .controller .item__child.on { 320 | background-color: #7abd53; 321 | } 322 | 323 | #specialPromotion .controller .item__child:hover { 324 | background-color: #096 325 | } -------------------------------------------------------------------------------- /src/priceChart.js: -------------------------------------------------------------------------------- 1 | import 'weui'; 2 | import weui from 'weui.js'; 3 | import { Chart } from '@antv/g2'; 4 | 5 | $(document).ready(function () { 6 | let urlInfo = /(https|http):\/\/item.jd.com\/([0-9]*).html/g.exec(window.location.href); 7 | if (window.location.host == 're.jd.com') { 8 | urlInfo = /(https|http):\/\/re.jd.com\/cps\/item\/([0-9]*).html/g.exec(window.location.href); 9 | } 10 | let sku = urlInfo[2] 11 | let priceChartDOM = ` 12 |
13 |

14 | 价格走势 15 | 20 |
21 |
22 | × 23 |

24 |
25 |
加载中
26 |
27 | 由京价保提供 28 |
29 | `; 30 | if ($(".product-intro").length > 0) { 31 | $(".product-intro").append(priceChartDOM); 32 | } 33 | 34 | if ($(".first_area_md").length > 0) { 35 | $(".first_area_md").append(priceChartDOM); 36 | } 37 | 38 | function timestampToDateNumber(timestamp) { 39 | let currentDate = new Date(timestamp).toLocaleDateString(undefined, { 40 | day: '2-digit', 41 | month: '2-digit', 42 | year: 'numeric' 43 | }) 44 | return currentDate.replace(/\//g, ''); 45 | } 46 | 47 | var slideIndex = 1; 48 | function showPromotions(n) { 49 | var i; 50 | var x = document.getElementsByClassName("special-promotion-item"); 51 | slideIndex = n 52 | if (n > x.length) {slideIndex = 1} 53 | if (n < 1) {slideIndex = x.length} ; 54 | for (i = 0; i < x.length; i++) { 55 | x[i].style.display = "none"; 56 | } 57 | $(`#specialPromotion .controller .item__child`).removeClass('on') 58 | setTimeout(() => { 59 | $(`#specialPromotion .controller .item__child:eq(${slideIndex-1})`).addClass('on') 60 | }, 10); 61 | console.log('showPromotions', n, slideIndex, x[slideIndex-1]) 62 | if (x[slideIndex-1]) { 63 | x[slideIndex-1].style.display = "block"; 64 | } 65 | } 66 | 67 | function getPriceChart(sku, days) { 68 | $.ajax({ 69 | method: "GET", 70 | type: "GET", 71 | url: `https://api.zaoshu.so/price/${sku}/detail?days=${days}`, 72 | timeout: 5000, 73 | success: function (data) { 74 | if (data.chart.length > 2) { 75 | $("#jjbPriceChart").html('') 76 | let specialPromotion = data.specialPromotion 77 | let chart = new Chart({ 78 | container: 'jjbPriceChart', 79 | forceFit: true, 80 | padding: [50, '5%', 80, '6%'], 81 | height: 300 82 | }); 83 | chart.source(data.chart, { 84 | timestamp: { 85 | type: 'time', 86 | mask: 'MM-DD HH:mm', 87 | range: [0, 1], 88 | tickCount: 5 89 | } 90 | }); 91 | chart.line().position('timestamp*value').shape('hv').color('key'); 92 | chart.tooltip( 93 | { 94 | useHtml: true, 95 | htmlContent: function (title, items) { 96 | let itemDom = "" 97 | let promotionsDom = "" 98 | let promotions = [] 99 | items.forEach(item => { 100 | const itemTime = timestampToDateNumber(item.point._origin.timestamp) 101 | promotions = data.promotionLogs.find(function (promotion) { 102 | return promotion.date == itemTime; 103 | }); 104 | itemDom += `
  • ${item.name}: ${item.value} 元
  • ` 105 | }); 106 | promotions && promotions.detail && promotions.detail.forEach(item => { 107 | promotionsDom += `
  • ${item.typeName}${item.description}
  • ` 108 | }); 109 | return `
    110 |
    ${title}
    111 | 112 | 113 |
    ` 114 | } 115 | } 116 | ); 117 | 118 | let specialPromotionDom = `` 119 | specialPromotion && specialPromotion.forEach(item => { 120 | specialPromotionDom += `
    ${item.icon ? `` : ''}${item.title}
    ` 121 | }); 122 | let specialPromotionControllerDom = `` 123 | specialPromotion &&specialPromotion.forEach((item, index) => { 124 | specialPromotionControllerDom += `` 125 | }); 126 | $("#specialPromotion").html(` 127 |
    ${specialPromotionDom}
    128 |
    ${specialPromotionControllerDom}
    129 | `) 130 | chart.render(); 131 | setTimeout(() => { 132 | showPromotions(Math.floor(Math.random()*specialPromotion.length) + 1); 133 | $( "#specialPromotion .controller .item__child" ).live( "click", function() { 134 | let index = $(this).data('index'); 135 | console.log('index', index) 136 | showPromotions(index+1) 137 | }); 138 | }, 50); 139 | 140 | setInterval(() => { 141 | showPromotions(Math.floor(Math.random()*specialPromotion.length) + 1); 142 | }, 30000); 143 | } else { 144 | $("#jjbPriceChart").html(`
    暂无数据
    `) 145 | } 146 | }, 147 | error: function (xhr, type) { 148 | $("#jjbPriceChart").html(`
    查询失败,点击重试
    `) 149 | $('#retry').bind('click', () => { 150 | getPriceChart(sku) 151 | }) 152 | } 153 | }); 154 | } 155 | 156 | setTimeout(function () { 157 | $('#disablePriceChart').bind('click', () => { 158 | weui.confirm('停用此功能后京价保将不再在商品页展示价格走势图,同时也将停止上报获取到的商品价格', function () { 159 | var data = { 160 | type: "FROM_PAGE", 161 | text: "disablePriceChart" 162 | }; 163 | window.postMessage(data, "*"); 164 | $('.jjbPriceChart').hide() 165 | }, function () { 166 | console.log('no') 167 | }, { 168 | title: '停用价格走势图' 169 | }); 170 | }) 171 | getPriceChart(sku) 172 | $('.jjbPriceChart. select[name=days]').change(function () { 173 | getPriceChart(sku, $(this).val()); 174 | }); 175 | }, 1000) 176 | }); 177 | -------------------------------------------------------------------------------- /src/components/task-setting.vue: -------------------------------------------------------------------------------- 1 | 93 | 94 | 174 | -------------------------------------------------------------------------------- /src/tasks.js: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon' 2 | import { getLoginState } from './account' 3 | import { getSetting, readableTime } from './utils' 4 | import { getTaskUsageImmediately, getTodayMessagesByTaskIdImmediately } from './db' 5 | 6 | const priceProUrl = "https://msitepp-fm.jd.com/rest/priceprophone/priceProPhoneMenu" 7 | const frequencyOptionText = { 8 | '2h': "每2小时", 9 | '5h': "每5小时", 10 | 'daily': "每天", 11 | 'never': "从不" 12 | } 13 | const mapFrequency = { 14 | '2h': 2 * 60, 15 | '5h': 5 * 60, 16 | 'daily': 24 * 60, 17 | 'never': 99999 18 | } 19 | 20 | const mapReward = { 21 | 'goldCoin': "金币", 22 | 'bean': "京豆", 23 | 'coin': "钢镚" 24 | } 25 | 26 | 27 | const tasks = [ 28 | { 29 | id: '1', 30 | src: { 31 | m: "https://msitepp-fm.jd.com/rest/priceprophone/priceProPhoneMenu", 32 | pc: "https://pcsitepp-fm.jd.com/rest/pricepro/priceapply" 33 | }, 34 | title: '价格保护', 35 | description: "价格保护只显示京东系统尚在价保有效期内的商品", 36 | mode: 'iframe', 37 | type: ['pc', 'm'], 38 | frequencyOption: ['2h', '5h', 'daily', 'never'], 39 | frequency: '5h', 40 | location: { 41 | host: ['pcsitepp-fm.jd.com', 'msitepp-fm.jd.com'] 42 | }, 43 | rateLimit:{ 44 | weekly: 55, 45 | daily: 10, 46 | hour: 2 47 | } 48 | }, 49 | { 50 | id: '15', 51 | src: { 52 | pc: 'https://a.jd.com', 53 | }, 54 | url: 'https://a.jd.com', 55 | title: '全品类券', 56 | description: "每天尝试领取全品类券(29减2/105减5/500减20/1000减30)", 57 | schedule: [10, 12, 14, 16, 18, 20, 22], 58 | mode: 'iframe', 59 | location: { 60 | host: ['a.jd.com'], 61 | pathname: ['/'] 62 | }, 63 | type: ['pc'], 64 | frequencyOption: ['5h', 'daily', 'never'], 65 | frequency: '5h', 66 | rateLimit:{ 67 | weekly: 55, 68 | daily: 10, 69 | hour: 2 70 | } 71 | }, 72 | { 73 | id: '3', 74 | src: { 75 | m: 'https://plus.m.jd.com/index', 76 | }, 77 | title: 'PLUS专享券', 78 | description: "当然啦,你得先是PLUS会员才能领到(不是每月定额100的全品类券)", 79 | mode: 'iframe', 80 | frequencyOption: ['2h', '5h', 'daily', 'never'], 81 | type: ['m'], 82 | frequency: '5h', 83 | location: { 84 | host: ['plus.m.jd.com'], 85 | pathname: ['/index'] 86 | }, 87 | rateLimit:{ 88 | weekly: 32, 89 | daily: 4, 90 | hour: 2 91 | } 92 | }, 93 | { 94 | id: '4', 95 | src: { 96 | m: 'https://m.jr.jd.com/member/rightsCenter/#/coupon', 97 | }, 98 | title: '精选白条券', 99 | mode: 'iframe', 100 | frequencyOption: ['2h', '5h', 'daily', 'never'], 101 | type: ['m'], 102 | frequency: '5h', 103 | location: { 104 | host: ['m.jr.jd.com'], 105 | pathname: ['/member/rightsCenter/'], 106 | hash: ["#/coupon"] 107 | }, 108 | rateLimit:{ 109 | weekly: 32, 110 | daily: 4, 111 | hour: 2 112 | } 113 | }, 114 | { 115 | id: '21', 116 | src: { 117 | pc: 'https://jjb.zaoshu.so/event/coupon?type=phone', 118 | }, 119 | baseUrl: 'https://a.jd.com', 120 | title: '话费充值券', 121 | description: "领取京东最新的话费充值券", 122 | mode: 'iframe', 123 | type: ['pc'], 124 | schedule: [10, 12, 14, 18, 20], 125 | frequencyOption: ['daily', 'never'], 126 | frequency: 'daily', 127 | location: { 128 | host: ['a.jd.com'] 129 | }, 130 | selector: { 131 | target: ".coupon-item:last", 132 | result: ".mask .content", 133 | successKeyWord: "成功", 134 | }, 135 | rateLimit:{ 136 | weekly: 32, 137 | daily: 4, 138 | hour: 2 139 | } 140 | }, 141 | { 142 | id: '29', // 已失效 143 | src: { 144 | m: 'https://red-e.jd.com/resources/pineapple/index.html', 145 | }, 146 | title: '每日镚一镚', 147 | key: "pineapple", 148 | description: "京东每日镚一镚领取钢镚", 149 | mode: 'iframe', 150 | type: ['m'], 151 | checkin: true, 152 | frequencyOption: ['daily', 'never'], 153 | frequency: 'daily', 154 | location: { 155 | host: ['red-e.jd.com'], 156 | pathname: ['/resources/pineapple/index.html'] 157 | }, 158 | new: true, 159 | rateLimit:{ 160 | weekly: 14, 161 | daily: 3, 162 | hour: 2 163 | }, 164 | deprecated: true 165 | }, 166 | { 167 | id: '23', 168 | src: { 169 | m: 'https://m.jr.jd.com/vip/activity/newperback/index.html', 170 | }, 171 | title: '单单返京豆', 172 | description: "京东支付购物单单返京豆", 173 | mode: 'iframe', 174 | type: ['m'], 175 | frequencyOption: ['daily', 'never'], 176 | frequency: 'daily', 177 | location: { 178 | host: ['m.jr.jd.com'], 179 | pathname: ['/vip/activity/newperback/index.html'] 180 | }, 181 | new: true, 182 | rateLimit:{ 183 | weekly: 14, 184 | daily: 3, 185 | hour: 2 186 | } 187 | }, 188 | { 189 | id: '5', 190 | src: { 191 | m: 'https://vip.m.jd.com/page/signin', 192 | }, 193 | title: '京东会员签到', 194 | description: "京东会员移动页每日签到领京豆", 195 | mode: 'iframe', 196 | key: "vip", 197 | type: ['m'], 198 | checkin: true, 199 | frequencyOption: ['daily', 'never'], 200 | frequency: 'daily', 201 | rateLimit:{ 202 | weekly: 32, 203 | daily: 4, 204 | hour: 2 205 | } 206 | }, 207 | { 208 | id: '14', // 已失效 209 | src: { 210 | m: 'https://coin.jd.com/m/gb/index.html', 211 | }, 212 | title: '钢镚每日签到', 213 | key: "coin", 214 | checkin: true, 215 | mode: 'iframe', 216 | type: ['m'], 217 | frequencyOption: ['daily', 'never'], 218 | frequency: 'daily', 219 | location: { 220 | host: ['coin.jd.com'], 221 | pathname: ['/m/gb/index.html'] 222 | }, 223 | rateLimit:{ 224 | weekly: 32, 225 | daily: 4, 226 | hour: 2 227 | }, 228 | deprecated: true 229 | }, 230 | { 231 | id: '6', // 已失效 232 | src: { 233 | m: 'https://m.jr.jd.com/spe/qyy/main/index.html?userType=41', 234 | }, 235 | title: '金融钢镚签到', 236 | description: "京东金融惠赚钱每日签到有钢镚奖励", 237 | key: "jr-qyy", 238 | mode: 'iframe', 239 | type: ['m'], 240 | checkin: true, 241 | frequencyOption: ['daily', 'never'], 242 | frequency: 'daily', 243 | rateLimit:{ 244 | weekly: 32, 245 | daily: 4, 246 | hour: 2 247 | }, 248 | deprecated: true 249 | }, 250 | { 251 | id: '9', // 已经失效 252 | src: { 253 | m: 'https://uf.jr.jd.com/activities/sign/v5/index.html', 254 | }, 255 | title: '金融会员签到', 256 | description: "京东金融会员签到,需要实名认证", 257 | key: "jr-index", 258 | checkin: true, 259 | mode: 'iframe', 260 | type: ['m'], 261 | frequencyOption: ['daily', 'never'], 262 | frequency: 'daily', 263 | location: { 264 | host: ['uf.jr.jd.com'], 265 | pathname: ['/activities/sign/v5/index.html'] 266 | }, 267 | rateLimit:{ 268 | weekly: 32, 269 | daily: 4, 270 | hour: 2 271 | }, 272 | deprecated: true 273 | }, 274 | { 275 | id: '11', 276 | src: { 277 | m: 'https://bean.m.jd.com', 278 | }, 279 | title: '每日京豆签到', 280 | key: "bean", 281 | checkin: true, 282 | mode: 'iframe', 283 | type: ['m'], 284 | frequencyOption: ['daily', 'never'], 285 | frequency: 'daily', 286 | rateLimit:{ 287 | weekly: 32, 288 | daily: 4, 289 | hour: 2 290 | } 291 | }, 292 | { 293 | id: '12', // 已经失效 294 | src: { 295 | m: 'https://m.jr.jd.com/integrate/signin/index.html', 296 | }, 297 | title: '领取双签奖励', 298 | description: "完成京豆和京东金融签到有一个双签奖励", 299 | key: "double_check", 300 | mode: 'iframe', 301 | type: ['m'], 302 | checkin: true, 303 | location: { 304 | host: ['m.jr.jd.com'], 305 | pathname: ['/integrate/signin/index.html'] 306 | }, 307 | frequencyOption: ['daily', 'never'], 308 | frequency: 'daily', 309 | deprecated: true 310 | }, 311 | { 312 | id: '16', 313 | src: { 314 | m: 'https://m.jr.jd.com/btyingxiao/marketing/html/index.html', 315 | }, 316 | title: '白条免息红包', 317 | description: "大部分情况获得京豆,也有可能白条券", 318 | key: "baitiao", 319 | checkin: true, 320 | mode: 'iframe', 321 | type: ['m'], 322 | frequencyOption: ['daily', 'never'], 323 | frequency: 'daily', 324 | rateLimit:{ 325 | weekly: 32, 326 | daily: 4, 327 | hour: 2 328 | } 329 | }, 330 | { 331 | id: '30', 332 | src: { 333 | m: 'https://vip.jd.com/newPage/reward', 334 | }, 335 | key: "swing-reward", 336 | title: '摇一摇领京豆', 337 | description: "摇一摇领领京豆", 338 | mode: 'iframe', 339 | type: ['m'], 340 | checkin: true, 341 | frequencyOption: ['daily', 'never'], 342 | frequency: 'daily', 343 | location: { 344 | host: ['vip.jd.com'], 345 | pathname: ['/newPage/reward'] 346 | }, 347 | new: true, 348 | rateLimit:{ 349 | weekly: 32, 350 | daily: 4, 351 | hour: 2 352 | }, 353 | }, 354 | { 355 | id: '22', 356 | src: { 357 | m: 'https://member.jr.jd.com/gcmall/', 358 | }, 359 | key: "gcmall", 360 | checkin: true, 361 | title: '领取金融金币', 362 | description: "领取京东金融各种返金币", 363 | mode: 'iframe', 364 | type: ['m'], 365 | frequencyOption: ['daily', 'never'], 366 | frequency: 'daily', 367 | location: { 368 | host: ['member.jr.jd.com'], 369 | pathname: ['/gcmall/'] 370 | }, 371 | new: true, 372 | rateLimit:{ 373 | weekly: 14, 374 | daily: 3, 375 | hour: 2 376 | } 377 | }, 378 | { 379 | id: '31', 380 | src: { 381 | m: 'https://m.jr.jd.com/member/rightsCenter/#/white', 382 | }, 383 | key: "rights-center", 384 | title: '白条优惠券抽奖', 385 | description: "京东金融权益中心白条优惠券", 386 | mode: 'iframe', 387 | type: ['m'], 388 | checkin: true, 389 | frequencyOption: ['daily', 'never'], 390 | frequency: 'daily', 391 | location: { 392 | host: ['m.jr.jd.com'], 393 | pathname: ['/member/rightsCenter/'], 394 | hash: ["#/white"] 395 | }, 396 | new: true, 397 | rateLimit:{ 398 | weekly: 32, 399 | daily: 4, 400 | hour: 2 401 | }, 402 | } 403 | ] 404 | 405 | // 根据登录状态选择任务模式 406 | let findTaskPlatform = function (task) { 407 | let loginState = getLoginState() 408 | 409 | return task.type.find((platform) => loginState[platform].state == 'alive') 410 | } 411 | 412 | let getTask = function (taskId, currentPlatform) { 413 | let taskParameters = getSetting('task-parameters', []) 414 | let taskSettings = getSetting(`task-${taskId}:settings`, {}) 415 | let parameters = (Array.isArray(taskParameters) && taskParameters.length > 0) ? taskParameters.find(t => t.id == taskId.toString()) : {} 416 | let task = Object.assign({}, { 417 | rateLimit: { 418 | weekly: 21, 419 | daily: 5, 420 | hour: 2 421 | } 422 | }, tasks.find(t => t.id == taskId.toString()), parameters, taskSettings) 423 | let taskStatus = {} 424 | taskStatus.platform = findTaskPlatform(task); 425 | taskStatus.frequency = getSetting(`job${taskId}_frequency`, task.frequency) 426 | taskStatus.usage = getTaskUsageImmediately(taskId) 427 | taskStatus.last_run_at = localStorage.getItem(`job${task.id}_lasttime`) ? parseInt(localStorage.getItem(`job${task.id}_lasttime`)) : null 428 | taskStatus.last_run_description = taskStatus.last_run_at ? "上次运行: " + readableTime(DateTime.fromMillis(Number(taskStatus.last_run_at))) : "从未执行"; 429 | 430 | // 如果是签到任务,则读取签到状态 431 | if (task.checkin) { 432 | let checkinRecord = getSetting(`jjb_checkin_${task.key}`, null) 433 | if (checkinRecord && checkinRecord.date == DateTime.local().toFormat("o")) { 434 | taskStatus.checked = true 435 | taskStatus.checkin_description = "完成于:" + readableTime(DateTime.fromISO(checkinRecord.time)) + (checkinRecord.value ? ",领到:" + checkinRecord.value : ""); 436 | } 437 | } 438 | 439 | // 如果是单次任务,则读取完成状态 440 | if (task.onetimeKey) { 441 | let onetimeRecord = getSetting(`task_onetime_${task.onetimeKey}`, null) 442 | if (onetimeRecord) { 443 | taskStatus.checked = true 444 | taskStatus.checkin_description = `完成于:${readableTime(DateTime.fromISO(onetimeRecord.time))} ${onetimeRecord.message}` 445 | } 446 | } 447 | 448 | // 如果是每日任务,则读取当日运行结果 449 | if (!task.checkin && !task.onetimeKey && task.frequency == 'daily') { 450 | task.messages = getTodayMessagesByTaskIdImmediately(task.id) 451 | if (task.messages.length > 0) { 452 | let lastDone = task.messages[0] 453 | taskStatus.checked = true 454 | taskStatus.checkin_description = "最近一次完成于:" + readableTime(DateTime.fromMillis(lastDone.timestamp)) + (lastDone.value ? ",领到:" + lastDone.value : "") + (lastDone.reward ? mapReward[lastDone.reward] : "" ); 455 | } 456 | } 457 | 458 | // 如果限定平台 459 | if (currentPlatform) { 460 | if (task.type && task.type.indexOf(currentPlatform) < 0) { 461 | taskStatus.unavailable = true 462 | } 463 | } 464 | // 选择运行平台 465 | if (!task.url) { 466 | taskStatus.url = taskStatus.platform ? task.src[taskStatus.platform] : task.src[task.type[0]]; 467 | } 468 | // 如果任务无可运行平台 469 | if (!taskStatus.platform) { 470 | taskStatus.suspended = true; 471 | taskStatus.platform = task.type[0]; 472 | } 473 | // 如果超出限制 474 | if ((task.rateLimit.weekly && taskStatus.usage.weekly >= task.rateLimit.weekly) || taskStatus.usage.daily >= task.rateLimit.daily || taskStatus.usage.hour >= task.rateLimit.hour) { 475 | taskStatus.pause = true; 476 | taskStatus.pause_description = `超出频率限制` 477 | } 478 | // 如果是新任务 479 | if (task.new) { 480 | taskStatus.pause = true; 481 | taskStatus.pause_description = `新任务` 482 | } 483 | return Object.assign(task, taskStatus) 484 | } 485 | 486 | let getTasks = function (currentPlatform) { 487 | let taskList = tasks.map((task) => { 488 | return getTask(task.id, currentPlatform) 489 | }) 490 | return taskList.filter(task => !(task.unavailable || task.deprecated)); 491 | } 492 | 493 | export { 494 | priceProUrl, 495 | frequencyOptionText, 496 | mapFrequency, 497 | tasks, 498 | getTask, 499 | getTasks, 500 | findTaskPlatform 501 | }; -------------------------------------------------------------------------------- /src/components/discounts.vue: -------------------------------------------------------------------------------- 1 | 113 | 114 | 280 | 281 | -------------------------------------------------------------------------------- /static/style/popup.css: -------------------------------------------------------------------------------- 1 | select { 2 | text-indent: 0.01px; 3 | text-overflow: ''; 4 | -moz-appearance: none; 5 | } 6 | @-moz-document url-prefix() { 7 | html,body{overflow: hidden !important;} 8 | } 9 | a { 10 | color:#0b85c3; 11 | } 12 | 13 | a:hover { 14 | color: #006da5; 15 | } 16 | 17 | html, body { 18 | min-height: 580px; 19 | min-width: 780px; 20 | margin: 0; 21 | overflow-x: hidden; 22 | overflow-y: hidden; 23 | } 24 | 25 | .popup{ 26 | height: 600px; 27 | width: 800px; 28 | overflow: hidden; 29 | } 30 | 31 | .weui-cell_select{ 32 | height: 34px; 33 | font-size: 15px; 34 | padding: 2px 15px; 35 | } 36 | 37 | 38 | .frequency_settings .weui-cell_select{ 39 | height: 32px; 40 | font-size: 13px; 41 | padding: 1.5px 11px; 42 | } 43 | 44 | .frequency_settings .weui-cell__bd i.show{ 45 | font-size: 20px; 46 | margin-left: -5px; 47 | display: inline-block !important; 48 | height: 20px; 49 | margin-top: -2px; 50 | } 51 | 52 | .page__hd { 53 | padding: 10px; 54 | } 55 | 56 | .weui-dialog { 57 | max-width: 440px; 58 | } 59 | .page__desc{ 60 | padding: 5px 10px; 61 | } 62 | .settings{ 63 | width: 365px; 64 | height: 600px; 65 | float: left; 66 | background: #fbfbfb; 67 | border-right: 1px solid #eee; 68 | overflow: hidden; 69 | } 70 | 71 | .settings .weui-navbar__item.weui-bar__item_on { 72 | background-image: linear-gradient(180deg, #03A9F4, rgba(3, 169, 244, 0.52)); 73 | color: #fff; 74 | } 75 | 76 | .settings .weui-cell_switch { 77 | height: 32px; 78 | font-size: 15px; 79 | padding-top: 4px; 80 | padding-bottom: 4px; 81 | } 82 | 83 | .contents { 84 | width: 432px; 85 | height: 600px; 86 | float: right; 87 | overflow: hidden; 88 | background: #fafafa; 89 | } 90 | 91 | .contents .weui-navbar__item.weui-bar__item_on { 92 | background-image: linear-gradient(180deg,#e6ad00,#FF9800); 93 | color: #fff; 94 | } 95 | 96 | .weui-navbar__item.weui-bar__item_on.zaoshu-tab{ 97 | color: #921714; 98 | background: #eaeaea; 99 | border-bottom: 1px #ccc solid; 100 | } 101 | 102 | .contents .weui-tab { 103 | background: #f5f5f5; 104 | height: 550px; 105 | } 106 | 107 | .weui-cell_switch { 108 | height: 32px; 109 | font-size: 16px; 110 | } 111 | 112 | .weui-tabbar__item{ 113 | padding: 8px 1px 0px; 114 | } 115 | 116 | .contents .weui-tab .weui-badge{ 117 | margin-left: 5px; 118 | margin-top: -3px; 119 | background-color: #4CAF50; 120 | } 121 | 122 | .contents .weui-tab .weui-badge.new-discounts{ 123 | background-color: #b9201d; 124 | padding: 0.3em; 125 | position: absolute; 126 | } 127 | 128 | .other_actions{ 129 | padding: 10px; 130 | padding-bottom: 0; 131 | height: 280px; 132 | overflow: hidden; 133 | } 134 | 135 | .other_actions p{ 136 | padding: 5px 0; 137 | } 138 | 139 | .no_order, .no_message{ 140 | background: url(../image/empty.svg) no-repeat center 10px; 141 | padding: 5em 0em; 142 | text-align: center; 143 | color: #ccc; 144 | margin-top: 10em; 145 | } 146 | 147 | .bottom-tips { 148 | padding: 5px; 149 | } 150 | 151 | .other_actions h3{ 152 | font-size: 16px; 153 | } 154 | 155 | .recommendation{ 156 | height: 210px; 157 | font-size: 12px; 158 | } 159 | 160 | .tips .weui-btn_mini { 161 | padding: 0.1em .5em; 162 | line-height: 1.4; 163 | margin-bottom: -7px; 164 | font-size: 12px; 165 | } 166 | 167 | .reward_tips .newyear { 168 | color: #f15f5f; 169 | } 170 | 171 | #renderFrame{ 172 | height: 0px; 173 | } 174 | 175 | .reload-icon{ 176 | cursor: pointer; 177 | } 178 | 179 | .frequency_settings .weui-cell__bd{ 180 | line-height: 34px; 181 | } 182 | 183 | .frequency_settings .weui-icon-waiting-circle{ 184 | font-size: 19px; 185 | } 186 | 187 | .switch-paymethod{ 188 | cursor: pointer; 189 | } 190 | 191 | #notice{ 192 | color: #333; 193 | } 194 | .alipay_action{ 195 | line-height: 26px; 196 | height: 24px; 197 | width: 120px; 198 | margin: 0 auto; 199 | } 200 | 201 | .alipay_action svg{ 202 | float: left; 203 | } 204 | 205 | .reload-icon { 206 | background: url(../image/reload.svg) no-repeat 1px 1px; 207 | width: 20px; 208 | height: 21px; 209 | color: #dcdcdc; 210 | display: inline-block; 211 | vertical-align: middle; 212 | background-size: 17px; 213 | } 214 | 215 | .order-good{ 216 | overflow: hidden; 217 | } 218 | 219 | .orders li, .messages li{ 220 | display: block; 221 | overflow: hidden; 222 | } 223 | 224 | .orders .order_time{ 225 | position: relative; 226 | overflow: hidden; 227 | } 228 | 229 | .orders .show-order{ 230 | -webkit-mask: url(../image/show.svg) no-repeat center; 231 | mask: url(../image/show.svg) no-repeat center; 232 | -webkit-mask-size: 16px; 233 | mask-size: 16px; 234 | } 235 | 236 | .orders .show-order, .orders .hide-order{ 237 | width: 20px; 238 | height: 21px; 239 | color: #dcdcdc; 240 | display: inline-block; 241 | vertical-align: middle; 242 | background-size: 16px; 243 | cursor: pointer; 244 | background-color: #ccc; 245 | position: absolute; 246 | right: 5px; 247 | top: 1px; 248 | } 249 | 250 | .orders .hide-order{ 251 | -webkit-mask: url(../image/hide.svg) no-repeat center; 252 | mask: url(../image/hide.svg) no-repeat center; 253 | -webkit-mask-size: 16px; 254 | mask-size: 16px; 255 | } 256 | 257 | .monitoring .suspend, .monitoring .resume{ 258 | width: 55px; 259 | height: 55px; 260 | color: #dcdcdc; 261 | display: inline-block; 262 | vertical-align: middle; 263 | background-size: 16px; 264 | cursor: pointer; 265 | background-color: #8a8a8a; 266 | } 267 | 268 | .monitoring .suspend{ 269 | -webkit-mask: url(../image/stop.svg) no-repeat center; 270 | mask: url(../image/stop.svg) no-repeat center; 271 | -webkit-mask-size: 24px; 272 | mask-size: 24px; 273 | } 274 | 275 | .monitoring .resume{ 276 | -webkit-mask: url(../image/play.svg) no-repeat center; 277 | mask: url(../image/play.svg) no-repeat center; 278 | -webkit-mask-size: 24px; 279 | mask-size: 24px; 280 | } 281 | 282 | .good_img:hover img{ 283 | opacity: 0.3; 284 | } 285 | 286 | .good_img:hover .monitoring{ 287 | opacity: 1; 288 | } 289 | 290 | .monitoring{ 291 | width: 55px; 292 | height: 55px; 293 | position: absolute; 294 | opacity: 0.1; 295 | } 296 | 297 | .logo{ 298 | display: inline-block; 299 | } 300 | 301 | .order_time{ 302 | margin-top: .77em; 303 | margin-bottom: .3em; 304 | padding-left: 15px; 305 | padding-right: 15px; 306 | color: #999; 307 | font-size: 12px; 308 | padding-top: 5px; 309 | } 310 | 311 | #orders .weui-cell:before, .contents-box.weui-cells:before, .contents-box.weui-cells:after{ 312 | border-top: none; 313 | content: none; 314 | } 315 | .orders .good_title{ 316 | height: 55px; 317 | } 318 | 319 | .good_title{ 320 | font-size: 12px; 321 | height: 80px; 322 | display: block; 323 | clear: both; 324 | width: 98%; 325 | } 326 | 327 | .orders .good_title p { 328 | margin-left: 65px; 329 | } 330 | 331 | .self-recommendation p.tips { 332 | font-size: 12px; 333 | text-align: center; 334 | padding: 1em; 335 | color: #ccc; 336 | } 337 | 338 | .good_title p { 339 | overflow: hidden; 340 | text-overflow: ellipsis; 341 | display: -webkit-box; 342 | max-height: 78px; 343 | -webkit-line-clamp: 3; 344 | -moz-box-orient: vertical; 345 | -webkit-box-orient: vertical; 346 | padding-bottom: 5px; 347 | line-height: 18px; 348 | margin-left: 85px; 349 | } 350 | 351 | .orders .good_title img{ 352 | width: 55px; 353 | height: 55px; 354 | padding-right: 10px; 355 | overflow: hidden; 356 | float: left; 357 | } 358 | 359 | 360 | .good_title .description { 361 | font-size: 12px; 362 | color: #666; 363 | max-height: 35px; 364 | overflow: hidden; 365 | text-overflow: ellipsis; 366 | display: -webkit-box; 367 | -webkit-line-clamp: 2; 368 | -moz-box-orient: vertical; 369 | -webkit-box-orient: vertical; 370 | } 371 | .good_title span.count { 372 | color: #949494; 373 | } 374 | 375 | img.promotion_title{ 376 | display: inline-block; 377 | padding-right: 10px; 378 | width: 55px; 379 | height: 55px; 380 | overflow: hidden; 381 | position: unset; 382 | } 383 | 384 | .good_title a { 385 | font-size: 13px; 386 | } 387 | 388 | .promotion_price{ 389 | font-size: 14px; 390 | color: #1aad19; 391 | display: block; 392 | font-weight: 500; 393 | padding-bottom: 5px; 394 | } 395 | 396 | 397 | .changelog .time{ 398 | font-size: 12px; 399 | color: #666; 400 | } 401 | 402 | .changelog .blog{ 403 | float: right; 404 | color: #888; 405 | font-size: 12px; 406 | text-decoration: none; 407 | line-height: 24px; 408 | } 409 | 410 | .go-buy{ 411 | background: #1aad19; 412 | color: #fff; 413 | font-size: 14px; 414 | padding: .3em .5em; 415 | border-radius: 2px; 416 | } 417 | 418 | .orders .dismiss{ 419 | float: right; 420 | padding: 2px; 421 | position: absolute; 422 | top: -2px; 423 | right: 6px; 424 | font-size: 14px; 425 | cursor: pointer; 426 | } 427 | 428 | .weui-cell.promotion{ 429 | background: #feedba54; 430 | } 431 | 432 | .buy-btn{ 433 | padding: 2px 5px; 434 | line-height: 1.3; 435 | font-size: 12px; 436 | margin-top: 4px; 437 | } 438 | 439 | a.buy-btn:hover{ 440 | color: #efefef; 441 | } 442 | 443 | .good{ 444 | background: #fdfdfd; 445 | border-bottom: 1px solid #f3f3f3; 446 | border-top: 1px solid #f3f3f3; 447 | overflow: hidden; 448 | } 449 | 450 | .good + .good { 451 | border-top: none; 452 | } 453 | 454 | .order-good .log{ 455 | font-size: 12px; 456 | margin: 10px; 457 | } 458 | 459 | .log.success{ 460 | color: #690; 461 | } 462 | 463 | .log.failed{ 464 | color: #999; 465 | } 466 | 467 | .order_price{ 468 | font-size: 12px; 469 | color: #666; 470 | display: block; 471 | } 472 | 473 | #changeLogs { 474 | display: none; 475 | } 476 | 477 | .zaoshu-icon{ 478 | width: auto; 479 | height: 15px; 480 | margin-top: -4px; 481 | vertical-align:middle; 482 | } 483 | 484 | 485 | 486 | .testbox b{ 487 | color: #333; 488 | font-weight: 500; 489 | } 490 | 491 | .guide .weui-dialog__bd{ 492 | line-height: 1.6; 493 | max-height: 300px; 494 | overflow-x: hidden; 495 | } 496 | 497 | .guide .weui-dialog__bd p{ 498 | margin-bottom: 1em; 499 | } 500 | 501 | .testbox p { 502 | text-align: left; 503 | } 504 | 505 | .new_price{ 506 | font-size: 14px; 507 | color: #333; 508 | } 509 | 510 | .new_price.up{ 511 | color: #690; 512 | } 513 | 514 | .new_price.down{ 515 | color: #ea2222; 516 | } 517 | 518 | .time{ 519 | text-align: right; 520 | } 521 | 522 | .alipay_pay img{ 523 | padding: 24px; 524 | background: #fff; 525 | } 526 | 527 | .weui-dialog__ft a{ 528 | cursor: pointer; 529 | } 530 | 531 | .weui-dialog .segmented-control{ 532 | width: 80%; 533 | margin: 0 auto; 534 | border: 1px solid #eee; 535 | border-radius: 4px; 536 | } 537 | .segmented-control { 538 | display: table; 539 | width: 100%; 540 | margin: 2em 0; 541 | padding: 0; 542 | background: #fff; 543 | } 544 | .segmented-control__item:first-child { 545 | float: left; 546 | } 547 | .segmented-control__item:last-child { 548 | float: right; 549 | } 550 | 551 | .segmented-control__item:first-child .segmented-control__label{ 552 | border-radius: 4px 0 0 4px; 553 | } 554 | 555 | .segmented-control__item:last-child .segmented-control__label{ 556 | border-radius: 0 4px 4px 0; 557 | } 558 | 559 | .segmented-control__item { 560 | width: 49.5%; 561 | display: inline-block; 562 | margin: 0; 563 | padding: 0; 564 | list-style-type: none; 565 | } 566 | 567 | .segmented-control__input { 568 | position: absolute; 569 | visibility: hidden; 570 | } 571 | 572 | 573 | .segmented-control__label { 574 | display: block; 575 | margin: 0 -1px -1px 0; /* -1px margin removes double-thickness borders between items */ 576 | padding: .45em .25em; 577 | font: 14px/1.5 sans-serif; 578 | text-align: center; 579 | cursor: pointer; 580 | } 581 | 582 | .segmented-control__label:hover { 583 | background: #fafafa; 584 | } 585 | 586 | .checked .segmented-control__label{ 587 | background: #1aad19; 588 | color: #fff; 589 | } 590 | 591 | .auto_login{ 592 | font-size: 14px; 593 | margin: 10px 5px; 594 | } 595 | 596 | 597 | .el-radio-group { 598 | display: inline-block; 599 | line-height: 1; 600 | vertical-align: middle; 601 | font-size: 0; 602 | } 603 | .el-radio-button, .el-radio-button__inner { 604 | position: relative; 605 | display: inline-block; 606 | outline: none; 607 | } 608 | .el-radio-button__orig-radio { 609 | opacity: 0; 610 | outline: none; 611 | position: absolute; 612 | z-index: -1; 613 | } 614 | .el-radio-button__orig-radio:disabled+.el-radio-button__inner { 615 | color: #c0c4cc; 616 | cursor: not-allowed; 617 | background-image: none; 618 | background-color: #fff; 619 | border-color: #ebeef5; 620 | box-shadow: none; 621 | } 622 | .el-radio-button__orig-radio:checked+.el-radio-button__inner { 623 | color: #fff; 624 | background-color: #409eff; 625 | border-color: #409eff; 626 | box-shadow: -1px 0 0 0 #409eff; 627 | } 628 | .el-radio-button:first-child .el-radio-button__inner { 629 | border-left: 1px solid #dcdfe6; 630 | border-radius: 4px 0 0 4px; 631 | box-shadow: none!important; 632 | } 633 | .el-radio-button--mini .el-radio-button__inner { 634 | padding: 7px 15px; 635 | font-size: 12px; 636 | border-radius: 0; 637 | } 638 | .el-radio-button__inner { 639 | line-height: 1; 640 | white-space: nowrap; 641 | vertical-align: middle; 642 | background: #fff; 643 | border: 1px solid #dcdfe6; 644 | font-weight: 500; 645 | border-left: 0; 646 | color: #606266; 647 | -webkit-appearance: none; 648 | text-align: center; 649 | box-sizing: border-box; 650 | margin: 0; 651 | cursor: pointer; 652 | transition: all .3s cubic-bezier(.645,.045,.355,1); 653 | padding: 12px 20px; 654 | font-size: 14px; 655 | border-radius: 0; 656 | } 657 | .el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner { 658 | background-color: #f2f6fc; 659 | } 660 | .el-radio-button:last-child .el-radio-button__inner { 661 | border-radius: 0 4px 4px 0; 662 | } 663 | 664 | .popup .weui-navbar + .weui-tab__panel{ 665 | padding-top: 36px; 666 | } 667 | 668 | .popup .weui-cells.contents-box{ 669 | margin-top: 0; 670 | } 671 | 672 | .popup .weui-navbar__item{ 673 | padding: 7px 0; 674 | cursor: pointer; 675 | } 676 | 677 | .recommendedLink p{ 678 | text-align: center; 679 | margin-top: 5px; 680 | } 681 | 682 | .iframe-loading{ 683 | z-index: 0 !important; 684 | } 685 | 686 | .js-close{ 687 | position: absolute; 688 | right: 10px; 689 | top: 1px; 690 | padding: 4px; 691 | font-size: 18px; 692 | cursor: pointer; 693 | } 694 | .settings .weui-tab{ 695 | height: auto; 696 | } 697 | 698 | .tips .page__desc{ 699 | padding: 2px 5px; 700 | } 701 | 702 | .bottom{ 703 | height: 44px; 704 | font-size: 12px; 705 | position: relative; 706 | } 707 | 708 | img.weui-tabbar__icon { 709 | width: 20px; 710 | height: 20px; 711 | cursor: pointer; 712 | } 713 | 714 | .changelogs{ 715 | font-size: 14px; 716 | padding: 10px; 717 | text-align: left; 718 | line-height: 2.2; 719 | max-height: 300px; 720 | overflow-y: auto; 721 | } 722 | 723 | .loginNotice { 724 | font-size: 14px; 725 | padding: 10px 15px; 726 | text-align: left; 727 | line-height: 2.2; 728 | max-height: 300px; 729 | color: #5a5a5a; 730 | overflow-y: auto; 731 | } 732 | 733 | .loginNotice b{ 734 | color: #2b902f; 735 | } 736 | 737 | #loginNotice .title{ 738 | background: #fbf3a5; 739 | color: #ce7f66; 740 | padding-top: 0.8em; 741 | } 742 | 743 | #loginNotice a { 744 | color: #126700; 745 | } 746 | 747 | #loginNotice a:hover { 748 | color: #0c4600; 749 | } 750 | 751 | #loginNotice a.failed { 752 | color: #de4545; 753 | } 754 | 755 | #loginNotice.state-alive .title { 756 | background: #b9e684ad; 757 | color: #2b902f; 758 | padding-top: 0.8em; 759 | } 760 | 761 | #faqDialags .weui-dialog, 762 | #feedbackDialags .weui-dialog{ 763 | background-color: #f8f8f8; 764 | height: 460px; 765 | } 766 | 767 | #jEventDialags iframe, 768 | #faqDialags iframe, 769 | #feedbackDialags iframe { 770 | width: 90%; 771 | height: 450px; 772 | } 773 | 774 | 775 | #feedbackResult, 776 | #wechatDialags, 777 | #feedbackDialags, 778 | #faqDialags, 779 | #jEventDialags{ 780 | display: none; 781 | } 782 | 783 | .listenVoice{ 784 | text-align: left; 785 | } 786 | 787 | #order.weui-cells:after{ 788 | border-bottom: none; 789 | } 790 | 791 | .messages-tab { 792 | position: relative; 793 | flex: 1; 794 | height: 48px; 795 | cursor: pointer; 796 | } 797 | 798 | .messages-tab span{ 799 | width: 22px; 800 | height: 22px; 801 | background: #ccc; 802 | display: block; 803 | margin: 0 auto; 804 | } 805 | 806 | .messages-tab.selectedTab span{ 807 | background: #4bc2ff; 808 | } 809 | 810 | .messages-tab span.notice { 811 | -webkit-mask: url(../image/notice.svg) no-repeat center; 812 | mask: url(../image/notice.svg) no-repeat center; 813 | -webkit-mask-size: 20px; 814 | mask-size: 20px; 815 | } 816 | 817 | .messages-tab span.coupon { 818 | -webkit-mask: url(../image/coupon.svg) no-repeat center; 819 | mask: url(../image/coupon.svg) no-repeat center; 820 | -webkit-mask-size: 22px; 821 | mask-size: 22px; 822 | } 823 | 824 | .messages-tab span.checkin { 825 | -webkit-mask: url(../image/checkin.svg) no-repeat center; 826 | mask: url(../image/checkin.svg) no-repeat center; 827 | -webkit-mask-size: 20px; 828 | mask-size: 20px; 829 | } 830 | 831 | .message-items .weui-media-box { 832 | padding: 5px 15px; 833 | border-bottom: 1px solid #ebeef5; 834 | } 835 | 836 | .Button--link, 837 | .Button--plain { 838 | height: auto; 839 | padding: 0; 840 | line-height: inherit; 841 | border: none; 842 | border-radius: 0; 843 | } 844 | 845 | .messages-tabIcon { 846 | fill: #c2cfde; 847 | } 848 | 849 | .selectedTab .messages-tabIcon { 850 | fill: #0f88eb; 851 | } 852 | 853 | button.Button.messages-tab.Button--plain:focus { 854 | outline: none; 855 | } 856 | 857 | .selectedTab{ 858 | background: #f5f5f5; 859 | border: 1px solid #e6e6e6; 860 | border-bottom: none; 861 | border-top: none; 862 | background-image: linear-gradient(0deg, #ffffff, #e6e6e64a); 863 | } 864 | .message i{ 865 | padding-right: 5px; 866 | } 867 | 868 | .message .coupon{ 869 | background: url(../image/coupon.png) no-repeat; 870 | width: 20px; 871 | height: 20px; 872 | background-size: 20px; 873 | display: inline-block; 874 | margin-bottom: -3px; 875 | } 876 | 877 | .message .checkin_notice, .message .goldCoinReceived, .beanReceived { 878 | background: url(../image/bean.png) no-repeat; 879 | width: 20px; 880 | height: 20px; 881 | background-size: 20px; 882 | display: inline-block; 883 | margin-bottom: -3px; 884 | } 885 | .message .checkin_notice.coin, .message .goldCoinReceived.goldCoin { 886 | background-image: url(../image/coin.png); 887 | } 888 | 889 | .message .notice, .message .priceProtectionNotice { 890 | background: url(../image/money.png) no-repeat; 891 | width: 20px; 892 | height: 20px; 893 | background-size: 20px; 894 | display: inline-block; 895 | margin-bottom: -3px; 896 | } 897 | 898 | .coupon-box{ 899 | position: relative; 900 | height: 50px; 901 | border: 1px solid #f2f2f2; 902 | background: #fff; 903 | display: inline-block; 904 | display: block; 905 | padding: 10px; 906 | } 907 | 908 | .coupon-box .price { 909 | padding: 1px 5px; 910 | color: #f23030; 911 | font-size: 15px; 912 | background: #fff4ec; 913 | display: inline-block; 914 | } 915 | .coupon-box a { 916 | font-size: 14px; 917 | color: #555; 918 | padding: 4px; 919 | } 920 | 921 | .reward { 922 | cursor: pointer; 923 | display: block; 924 | color: #d29737; 925 | } 926 | 927 | .reward h4{ 928 | padding-top: 10px; 929 | font-size: 22px; 930 | color:#4e4c4c; 931 | } 932 | 933 | .reward .qrcode{ 934 | width: 210px; 935 | padding: 10px; 936 | } 937 | 938 | .reward .switch-tips{ 939 | font-size: 14px; 940 | color: #ccc; 941 | } 942 | 943 | .switch{ 944 | cursor: pointer; 945 | color: #f54e4d; 946 | } 947 | 948 | .switch p{ 949 | margin-top: -10px; 950 | } 951 | 952 | .switch .icon{ 953 | margin-bottom: -5px; 954 | } 955 | 956 | #unreadCount{ 957 | display: none; 958 | } 959 | 960 | .alipay_pay .redpack img{ 961 | padding: 10px; 962 | } 963 | 964 | .switch-paymethod i { 965 | font-size: 21px; 966 | margin-top: -4px; 967 | } 968 | 969 | .weui-cell__bd .weui-icon-info-circle{ 970 | margin-top: -4px; 971 | } 972 | 973 | #listenAudio .weui-cells{ 974 | margin-bottom: .8em; 975 | } 976 | 977 | #listenAudio .weui-cell_access{ 978 | cursor: pointer; 979 | } 980 | 981 | #changeLogs b { 982 | color: #4CAF50; 983 | } 984 | 985 | .text-tips { 986 | font-size: 12px; 987 | text-align: center; 988 | color: #666; 989 | padding-top: 5px; 990 | padding-bottom: 10px; 991 | } 992 | 993 | 994 | .recommendServices{ 995 | text-align: center; 996 | 997 | } 998 | .recommendServices .el-tag { 999 | margin-right: 2px; 1000 | } 1001 | 1002 | .el-tag { 1003 | background-color: rgba(64, 158, 255, .1); 1004 | display: inline-block; 1005 | padding: 0 10px; 1006 | height: 32px; 1007 | line-height: 30px; 1008 | font-size: 12px; 1009 | color: #409eff; 1010 | border-radius: 4px; 1011 | box-sizing: border-box; 1012 | border: 1px solid rgba(64, 158, 255, .2); 1013 | white-space: nowrap; 1014 | } 1015 | 1016 | .el-tag a{ 1017 | color: #2196F3; 1018 | } 1019 | 1020 | .el-tag--success { 1021 | background-color: rgba(103, 194, 58, .1); 1022 | border-color: rgba(103, 194, 58, .2); 1023 | color: #67c23a; 1024 | } 1025 | 1026 | .el-tag--success a{ 1027 | color: #67c23a; 1028 | } 1029 | 1030 | .el-tag--warning { 1031 | background-color: rgba(230, 162, 60, .1); 1032 | border-color: rgba(230, 162, 60, .2); 1033 | color: #e6a23c; 1034 | } 1035 | 1036 | .el-tag--warning a { 1037 | color: #e6a23c; 1038 | } 1039 | 1040 | .el-tag--danger { 1041 | background-color: hsla(0, 87%, 69%, .1); 1042 | border-color: hsla(0, 87%, 69%, .2); 1043 | color: #f56c6c; 1044 | } 1045 | .el-tag--danger a{ 1046 | color: #ff1d1d; 1047 | } 1048 | 1049 | .bottom-box{ 1050 | height: 55px; 1051 | overflow: hidden; 1052 | border-top: #d4d4d4; 1053 | background: #f7f7fa; 1054 | position: relative; 1055 | } 1056 | 1057 | 1058 | .bottom-box .avatar{ 1059 | width: 30px; 1060 | position: absolute; 1061 | left: 10px; 1062 | bottom: 11px; 1063 | height: 30px; 1064 | } 1065 | 1066 | .bottom-box .avatar .login-state { 1067 | -webkit-mask: url(../image/avatar.svg) no-repeat center; 1068 | -webkit-mask-size: 30px; 1069 | mask: url(../image/avatar.svg) no-repeat center; 1070 | mask-size: 30px; 1071 | width: 30px; 1072 | height: 30px; 1073 | color: #dcdcdc; 1074 | display: inline-block; 1075 | cursor: pointer; 1076 | background-color: #cecece; 1077 | } 1078 | 1079 | .bottom-box .avatar .login-state.alive{ 1080 | background-color: #41bd2a; 1081 | } 1082 | 1083 | .bottom-box .avatar .login-state.failed{ 1084 | background-color: #f56c6c; 1085 | } 1086 | 1087 | .bottom-box .avatar .login-state.warning { 1088 | background-color: #f7aa4d; 1089 | } 1090 | 1091 | 1092 | .bottom-box .action-list { 1093 | right: 10px; 1094 | position: absolute; 1095 | bottom: 10px; 1096 | } 1097 | 1098 | .action-list .text-tips{ 1099 | color: #bbb; 1100 | padding-bottom: 5px; 1101 | } 1102 | 1103 | .action-list .el-tag{ 1104 | padding: 0 8px; 1105 | } 1106 | 1107 | .tips a.weui-btn_primary.weui-btn:hover { 1108 | color: #ffffffd6; 1109 | } 1110 | 1111 | .showChangeLog{ 1112 | cursor: pointer; 1113 | } 1114 | 1115 | .offline-icon{ 1116 | -webkit-mask: url(../image/offline.svg) no-repeat center; 1117 | -webkit-mask-size: 22px; 1118 | mask: url(../image/offline.svg) no-repeat center; 1119 | mask-size: 22px; 1120 | width: 22px; 1121 | height: 22px; 1122 | display: inline-block; 1123 | background-color: #666; 1124 | padding-right: 5px; 1125 | margin-bottom: -4px; 1126 | } 1127 | 1128 | .online-icon{ 1129 | -webkit-mask: url(../image/online.svg) no-repeat center; 1130 | -webkit-mask-size: 22px; 1131 | mask: url(../image/online.svg) no-repeat center; 1132 | mask-size: 22px; 1133 | width: 22px; 1134 | height: 22px; 1135 | background-color: #2b902f; 1136 | padding-right: 5px; 1137 | margin-bottom: -4px; 1138 | display: none; 1139 | } 1140 | 1141 | .setting-icon{ 1142 | background: url(../image/setting.svg) no-repeat center center; 1143 | width: 17px; 1144 | height: 17px; 1145 | color: #dcdcdc; 1146 | display: inline-block; 1147 | vertical-align: middle; 1148 | margin-top: -3px; 1149 | margin-left: 1px; 1150 | background-size: 16px; 1151 | } 1152 | 1153 | .weui-navbar__item{ 1154 | line-height: 22px; 1155 | } 1156 | 1157 | .request-permissions-icon.weui-icon-warn { 1158 | color: #FFC107; 1159 | font-size: 22px; 1160 | cursor: pointer; 1161 | } 1162 | 1163 | .state-alive .online-icon { 1164 | display: inline-block; 1165 | } 1166 | 1167 | .state-alive .offline-icon { 1168 | display: none; 1169 | } 1170 | 1171 | .el-tag--plus { 1172 | border-color: #f7aa4d; 1173 | background-color: #f9d2a3; 1174 | } 1175 | 1176 | .el-tag--plus a{ 1177 | color: #da8d00; 1178 | } 1179 | 1180 | .help_btns .el-tag a{ 1181 | font-size: 14px; 1182 | padding: 12px; 1183 | } 1184 | 1185 | .text-tips.version{ 1186 | padding-bottom: 0; 1187 | cursor: pointer; 1188 | } 1189 | 1190 | 1191 | .loginNotice .detail { 1192 | padding-top: 20px; 1193 | } 1194 | 1195 | .loginNotice .detail h3 { 1196 | text-align: center; 1197 | } 1198 | 1199 | .loginNotice .status-icon{ 1200 | margin-left: 5px; 1201 | margin-right: 5px; 1202 | } 1203 | 1204 | .unknown .status-icon { 1205 | background-color: #ccc; 1206 | } 1207 | 1208 | .alive .status-icon { 1209 | background-color: #289e2d; 1210 | } 1211 | 1212 | .alive .status-text { 1213 | color: #289e2d; 1214 | } 1215 | 1216 | .alive .weui-cell { 1217 | background: #e6ffcad4; 1218 | } 1219 | .alive .weui-cell:hover { 1220 | background: #d8f9b4; 1221 | } 1222 | .failed .weui-cell { 1223 | background: #ffd3d3b5; 1224 | } 1225 | .failed .weui-cell:hover { 1226 | background: #ffd9d9; 1227 | } 1228 | .failed .status-text { 1229 | color: #de4545; 1230 | } 1231 | 1232 | #login i{ 1233 | margin-top: -3px; 1234 | } 1235 | 1236 | #know_more { 1237 | text-align: center; 1238 | padding-top: 10px; 1239 | color: #999; 1240 | cursor: pointer; 1241 | } 1242 | 1243 | .update .weui-dialog .weui-dialog__bd{ 1244 | white-space: pre-line; 1245 | text-align: left; 1246 | padding-top: 1em; 1247 | } 1248 | 1249 | .update .dismiss{ 1250 | cursor: pointer; 1251 | float: right; 1252 | margin-top: -13px; 1253 | padding: 5px 10px; 1254 | font-size: 22px; 1255 | margin-right: -7px; 1256 | } 1257 | 1258 | .showApplyAlipayCode{ 1259 | cursor: pointer; 1260 | margin-top: 10px; 1261 | color: #10aeff; 1262 | } 1263 | 1264 | .apply-alipay-code .weui-dialog{ 1265 | border: 5px solid #10aeff; 1266 | min-height: 400px; 1267 | } 1268 | 1269 | .apply-alipay-code .weui-dialog__title{ 1270 | color: #10aeff; 1271 | } 1272 | 1273 | .reward-tips{ 1274 | font-size: 12px; 1275 | } 1276 | 1277 | .new-version{ 1278 | margin-left: 5px; 1279 | margin-right: 5px; 1280 | margin-top: -2px; 1281 | padding: .2em .5em; 1282 | } -------------------------------------------------------------------------------- /src/components/App.vue: -------------------------------------------------------------------------------- 1 | 352 | 353 | 743 | 744 | --------------------------------------------------------------------------------