├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── images ├── article.gif ├── book.gif ├── home.gif ├── me.gif ├── pin-detail.gif ├── pin.gif ├── search.gif ├── test.jpg ├── topics.gif ├── user.gif ├── 微信图片_20200306204705.jpg ├── 微信图片_20200309110857.jpg ├── 微信图片_20200327193455.jpg ├── 微信图片_20200327230420.jpg ├── 微信图片_20200328120726.jpg ├── 微信图片_20200328120821.jpg ├── 微信图片_20200328152341.jpg ├── 微信图片_20200328211854.jpg ├── 微信图片_20200329194915.jpg ├── 微信图片_20200330234326.jpg ├── 微信图片_20200331130952.jpg ├── 微信图片_20200331174417.jpg ├── 微信图片_20200331190849.jpg ├── 微信图片_20200331222139.jpg ├── 微信图片_20200401163936.jpg ├── 微信图片_20200401221547.jpg ├── 微信图片_20200402114039.jpg └── 微信图片_20200408215158.jpg ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── auth.js │ ├── book.js │ ├── collection.js │ ├── common.js │ ├── entry.js │ ├── home.js │ ├── pins.js │ ├── post.js │ ├── search.js │ ├── topic.js │ └── user.js ├── assets │ └── css │ │ ├── article.styl │ │ ├── common.styl │ │ ├── normalize.styl │ │ └── variable.styl ├── common │ ├── cache.js │ ├── config.js │ ├── const.js │ └── store.js ├── components │ ├── book-entry │ │ └── index.vue │ ├── book-section-entry │ │ └── index.vue │ ├── collection-item │ │ └── index.vue │ ├── comment │ │ └── index.vue │ ├── fold-text │ │ └── index.vue │ ├── l-article-entry │ │ └── index.vue │ ├── l-pin-entry │ │ └── index.vue │ ├── link-view │ │ └── index.vue │ ├── loading │ │ ├── image │ │ │ └── loading.gif │ │ └── index.vue │ ├── m-article-entry │ │ └── index.vue │ ├── m-author │ │ └── index.vue │ ├── m-header │ │ └── index.vue │ ├── message │ │ ├── index.js │ │ └── src │ │ │ └── index.vue │ ├── native-scroll │ │ └── index.vue │ ├── nav-tab │ │ └── index.vue │ ├── need-login │ │ └── index.vue │ ├── pin-info │ │ └── index.vue │ ├── refresh │ │ ├── image │ │ │ └── refresh.gif │ │ └── index.vue │ ├── router-transition │ │ └── index.vue │ ├── s-article-entry │ │ └── index.vue │ ├── s-author │ │ └── index.vue │ ├── s-header │ │ └── index.vue │ ├── s-pin-entry │ │ └── index.vue │ ├── scroll │ │ └── index.vue │ ├── search-box │ │ └── index.vue │ ├── switch │ │ └── index.vue │ ├── tab │ │ └── index.vue │ ├── tag-item │ │ └── index.vue │ ├── tag │ │ └── index.vue │ ├── three-op │ │ └── index.vue │ └── topic-item │ │ └── index.vue ├── fliter │ └── index.js ├── layout │ ├── footer │ │ └── index.vue │ └── index.vue ├── main.js ├── mixins │ └── index.js ├── plugin │ └── router-transition │ │ ├── constant.js │ │ ├── index.js │ │ ├── index.vue │ │ └── util.js ├── router │ ├── home.js │ ├── index.js │ ├── pins.js │ ├── topic.js │ └── user.js ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── mutation-types.js │ ├── mutations.js │ └── state.js ├── util │ ├── index.js │ └── request.js └── views │ ├── about │ └── index.vue │ ├── book │ └── index.vue │ ├── books │ ├── index.vue │ └── pages │ │ └── book-entry-list │ │ └── index.vue │ ├── collection-set │ └── index.vue │ ├── collection │ └── index.vue │ ├── feedback │ └── index.vue │ ├── home │ ├── index.vue │ └── pages │ │ ├── home-article-list │ │ └── index.vue │ │ └── home-following │ │ └── index.vue │ ├── login │ └── index.vue │ ├── me │ └── index.vue │ ├── my-like │ └── index.vue │ ├── my-purchased-book │ └── index.vue │ ├── pin │ └── index.vue │ ├── pins │ ├── index.vue │ └── pages │ │ ├── pin-following │ │ └── index.vue │ │ └── pins-entry-list │ │ └── index.vue │ ├── post │ └── index.vue │ ├── recommendation-author │ ├── index.vue │ └── pages │ │ └── recommend-author-list │ │ └── index.vue │ ├── search │ ├── index.vue │ └── pages │ │ └── search-result │ │ └── index.vue │ ├── section-detail │ ├── components │ │ └── section-content │ │ │ └── index.vue │ └── index.vue │ ├── set-psd │ └── index.vue │ ├── setting │ └── index.vue │ ├── sousou │ └── index.vue │ ├── special-show-edit │ └── index.vue │ ├── tag-manage │ └── index.vue │ ├── topic-attender │ └── index.vue │ ├── topic │ ├── index.vue │ └── pages │ │ └── topic-pin-list │ │ └── index.vue │ ├── topics │ └── index.vue │ ├── user-like │ └── index.vue │ ├── user-read-history │ └── index.vue │ ├── user-tag │ └── index.vue │ └── user │ ├── index.vue │ └── pages │ ├── user-activity │ └── index.vue │ ├── user-more │ └── index.vue │ ├── user-pin │ └── index.vue │ ├── user-post │ └── index.vue │ └── user-share │ └── index.vue ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"], 7 | parserOptions: { 8 | parser: "babel-eslint" 9 | }, 10 | rules: { 11 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 12 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue仿照掘金客户端App开发Web版掘金App 2 | 3 | ## 运行 4 | 5 | ``` 6 | git clone https://github.com/sanfengliao/vue-juejin.git 7 | 8 | cd vue-juejin 9 | 10 | npm install 11 | 12 | # serve with hot reload at localhost:8080 13 | npm run serve 14 | 15 | # build juejin application for production 16 | npm run build 17 | ``` 18 | ## 正文 19 | ### 介绍 20 | 该项目是~~抄袭~~仿照掘金客户端使用Vue开发的WebApp。里面所有的API均来自官方Android。页面基本上和掘金App差不多,不过由于里面的一些图片本菜使用的是阿里的iconfont,因此会和掘金App上面的图片有点不一样,但是整体的功能和掘金App还是差不多的。 21 | 22 | 该项目源码已经开源在~~gayhub~~github, [点击可查看源码](https://github.com/sanfengliao/vue-juejin),希望各位掘友大大给个star 23 | 24 | ### 效果图 25 | (前面一大波长gif来袭) 26 | 27 | (如果图片显示不出,请访问[原文链接](https://juejin.im/post/5e98441df265da47ef2f3720)) 28 | 29 | * 首页 30 | 31 | ![首页效果](https://user-gold-cdn.xitu.io/2020/4/16/171838443f17671b?w=360&h=780&f=gif&s=4888738) 32 | 33 | * 沸点 34 | 35 | ![沸点列表](https://user-gold-cdn.xitu.io/2020/4/16/17183850b393a4d3?w=360&h=780&f=gif&s=1248458) 36 | 37 | * 搜索 38 | 39 | ![](https://user-gold-cdn.xitu.io/2020/4/16/171838680ca1aff4?w=360&h=780&f=gif&s=950128) 40 | 41 | * 小册 42 | 43 | ![小册页面](https://user-gold-cdn.xitu.io/2020/4/16/1718385ef5f13720?w=360&h=780&f=gif&s=3202154) 44 | 45 | * 我的 46 | 47 | ![我的](https://user-gold-cdn.xitu.io/2020/4/16/1718386fa6af9727?w=360&h=780&f=gif&s=1404012) 48 | 49 | * 用户主页 50 | 51 | ![用户主页](https://user-gold-cdn.xitu.io/2020/4/16/1718387844061e8c?w=360&h=780&f=gif&s=1946699) 52 | 53 | * 话题主页 54 | 55 | ![话题主页](https://user-gold-cdn.xitu.io/2020/4/16/17183890948738fb?w=360&h=780&f=gif&s=381336) 56 | 57 | * 文章详情 58 | 59 | ![文章详情](https://user-gold-cdn.xitu.io/2020/4/16/1718389b09f8d27b?w=360&h=780&f=gif&s=1319213) 60 | 61 | * 沸点详情 62 | 63 | ![沸点详情](https://user-gold-cdn.xitu.io/2020/4/16/171838a50e7fe8a5?w=360&h=780&f=gif&s=1237673) 64 | ### 完成度 65 | 本来以为仿照App实现的话应该很快就可以全部昨晚,可做起来才发现APP 里面的东西实在是不少(有一些是本菜实在做不了,比如支付),包括页面和交互,要完全照抄实现确实需要一些时间和精力,UI 之类的都是简单测量+肉眼调试实现的,下面列出页面和交互的完成度,这里应该只是列出了绝大部分。未列出、未实现的后续会根据时间、精力来实现。 66 | 实际完成度请以代码为主 67 | - [x] 启动页 不做 68 | - [x] 登录、未登录跳转和页面数据刷新(逻辑还不够眼睛) 69 | - [x] 上拉加载、下拉锁芯 70 | - [x] HOME 完成 71 | - [x] TAB切换 72 | - [x] TAB编辑 73 | - [x] 关注的作者发布的文章 74 | - [x] 点赞 75 | - [x] 沸点 76 | - [x] TAB切换 77 | - [x] TAB编辑 78 | - [x] 关注的作者发布的动态 79 | - [x] 点赞 80 | - [x] 沸点详情 81 | - [x] 搜索 完成 82 | - [x] 按照综合、文章、用户、标签搜索 83 | - [ ] 小册 84 | - [x] 小册列表 85 | - [x] 小册详情 86 | - [x] 小册章节详情 87 | - [ ] 购买小册 (臣妾做不到啊) 88 | - [ ] 我的 89 | - [x] 个人主页 90 | - [ ] 编辑 91 | - [x] 活动 92 | - [x] 原创文章 93 | - [x] 沸点 94 | - [x] 收藏集 95 | - [x] 收藏集详情页 96 | - [x] 喜欢的文章 97 | - [x] 关注的标签 98 | - [ ] 标签详情页 99 | - [ ] 消息中心 100 | - [x] 赞过的文章和沸点 101 | - [x] 收藏集 102 | - [x] 创建的 103 | - [x] 已关注的 104 | - [x] 已购小册 (不知道有没有问题) 105 | - [x] 阅读过的文章 106 | - [x] 标签管理 107 | - [x] 已关注标签 108 | - [x] 所有标签 109 | - [x] 推荐标签 110 | - [x] 所有标签 111 | - [x] 关注标签 112 | - [ ] 夜间模式 113 | - [ ] 设置 完成一些 114 | - [x] 修改密码 115 | - [ ] 登录页 116 | - [x] 作者榜 117 | - [x] 用户主页 118 | - [x] 关注 119 | - [x] 活动 120 | - [x] 原创文章 121 | - [x] 沸点 122 | - [x] 收藏集 123 | - [x] 收藏集详情页 124 | - [x] 关注收藏集 125 | - [x] 收藏集文章列表 126 | - [x] 喜欢的文章 127 | - [x] 关注的标签 128 | - [ ] 标签详情页 129 | - [ ] 话题榜 130 | - [x] 更多话题 131 | - [ ] 已关注话题 132 | - [x] 话题详情 133 | - [x] 关注话 134 | - [ ] 文章详情页 135 | - [x] 文章内容 136 | - [x] 显示评论 137 | - [x] 文章作者其他系列文章 138 | - [x] 关注作者 139 | - [ ] 收藏文章、分享文章 140 | - [ ] 评论 141 | - [ ] 其他 142 | - [ ] 沸点详情页 143 | - [x] 文章内容 144 | - [x] 显示评论 145 | - [x] 推荐沸点 146 | - [x] 关注作者 147 | - [ ] 分享沸点 148 | - [ ] 评论 149 | - [ ] 其他 150 | - [ ] ... 151 | 应该还有一些完成的没有列举出来,大家clone下来跑一遍试试吧 [点击此下载源码](https://github.com/sanfengliao/vue-juejin.git) 152 | #### 交互完成度 153 | 评论、留言、关注、添加到收藏集、发表沸点等暂时均没有实现,因为 APP 里面的东西实在是不少...... 154 | 155 | - [ ] 评论 156 | - [ ] 留言 157 | - [ ] 未完待续部分... 158 | ### 后续 159 | 1. 话说掘金的 API 域名(二级)真是多啊,使用webpack-dev-server的proxy配置代理都让node报`possible EventEmiter memory leak deteceted`的警告了。 160 | 2. 富文本部分是直接拷贝掘金web官网的富文本样式 161 | 3. 有些页面的显示还不够丝滑,后续需要改进 162 | 4. 认真的看效果图的话,可以看出某些页面还是有一些bug的, 163 | 5. 关于页面切换动画似乎还不够连贯,不知道是代码的问题(绝对是代码的问题),还是浏览器的问题 164 | 6. 登录的token好像有一些问题,在两个app登录同一个账号不会报token异常,但是在该项目中在两个浏览器中登录会出问题。 165 | 7. html节点的font-size设置太小了,因此有些忘记设计font-size的元素可能会显示不出文字。 166 | 8. 后续会不停的完善该项目,把一些能够开发的功能都开发出来,希望各位朋友们多多支持。 -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [["@vue/cli-plugin-babel/preset", {useBuiltIns: "entry"}]] 3 | }; 4 | -------------------------------------------------------------------------------- /images/article.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/article.gif -------------------------------------------------------------------------------- /images/book.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/book.gif -------------------------------------------------------------------------------- /images/home.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/home.gif -------------------------------------------------------------------------------- /images/me.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/me.gif -------------------------------------------------------------------------------- /images/pin-detail.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/pin-detail.gif -------------------------------------------------------------------------------- /images/pin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/pin.gif -------------------------------------------------------------------------------- /images/search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/search.gif -------------------------------------------------------------------------------- /images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/test.jpg -------------------------------------------------------------------------------- /images/topics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/topics.gif -------------------------------------------------------------------------------- /images/user.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/user.gif -------------------------------------------------------------------------------- /images/微信图片_20200306204705.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200306204705.jpg -------------------------------------------------------------------------------- /images/微信图片_20200309110857.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200309110857.jpg -------------------------------------------------------------------------------- /images/微信图片_20200327193455.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200327193455.jpg -------------------------------------------------------------------------------- /images/微信图片_20200327230420.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200327230420.jpg -------------------------------------------------------------------------------- /images/微信图片_20200328120726.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200328120726.jpg -------------------------------------------------------------------------------- /images/微信图片_20200328120821.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200328120821.jpg -------------------------------------------------------------------------------- /images/微信图片_20200328152341.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200328152341.jpg -------------------------------------------------------------------------------- /images/微信图片_20200328211854.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200328211854.jpg -------------------------------------------------------------------------------- /images/微信图片_20200329194915.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200329194915.jpg -------------------------------------------------------------------------------- /images/微信图片_20200330234326.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200330234326.jpg -------------------------------------------------------------------------------- /images/微信图片_20200331130952.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200331130952.jpg -------------------------------------------------------------------------------- /images/微信图片_20200331174417.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200331174417.jpg -------------------------------------------------------------------------------- /images/微信图片_20200331190849.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200331190849.jpg -------------------------------------------------------------------------------- /images/微信图片_20200331222139.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200331222139.jpg -------------------------------------------------------------------------------- /images/微信图片_20200401163936.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200401163936.jpg -------------------------------------------------------------------------------- /images/微信图片_20200401221547.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200401221547.jpg -------------------------------------------------------------------------------- /images/微信图片_20200402114039.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200402114039.jpg -------------------------------------------------------------------------------- /images/微信图片_20200408215158.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/images/微信图片_20200408215158.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-juejin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.19.2", 12 | "better-scroll": "^1.15.2", 13 | "core-js": "^3.6.4", 14 | "qs": "^6.9.3", 15 | "swiper": "^5.3.6", 16 | "vue": "^2.6.11", 17 | "vue-awesome-swiper": "^4.1.0", 18 | "vue-router": "^3.1.5", 19 | "vuex": "^3.1.2" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "~4.2.0", 23 | "@vue/cli-plugin-eslint": "~4.2.0", 24 | "@vue/cli-plugin-router": "~4.2.0", 25 | "@vue/cli-plugin-vuex": "~4.2.0", 26 | "@vue/cli-service": "~4.2.0", 27 | "@vue/eslint-config-prettier": "^6.0.0", 28 | "babel-eslint": "^10.0.3", 29 | "eslint": "^6.7.2", 30 | "eslint-plugin-prettier": "^3.1.1", 31 | "eslint-plugin-vue": "^6.1.2", 32 | "prettier": "^1.19.1", 33 | "stylus": "^0.54.7", 34 | "stylus-loader": "^3.0.2", 35 | "vue-template-compiler": "^2.6.11" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= htmlWebpackPlugin.options.title %> 11 | 12 | 13 | 14 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | 44 | -------------------------------------------------------------------------------- /src/api/auth.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import store from '../store' 3 | import { client_id, device_id } from '../common/config' 4 | 5 | /** 6 | * 登录功能 7 | * @param {string} username 8 | * @param {string} password 9 | * @param {string} loginType 10 | */ 11 | export const login = (username, password, loginType) => { 12 | console.log('login') 13 | let data = `user=${username}&psd=${password}&src=android&login_type=${loginType}&client_id=${client_id}&device_id=${device_id}&state=state&token=${store.state.token}` 14 | return request.post('/auth-center/v1/login',data, { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded' 17 | } 18 | }).then(res => res.data) 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/api/book.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import { commonParams } from './common' 3 | import store from '../store' 4 | const state = store.state 5 | /** 6 | * 获取小册 7 | * @param {number} pageNum 8 | */ 9 | export const getBooks = (pageNum=1) => { 10 | // TODO 添加token 11 | return request({ 12 | method: 'GET', 13 | url: '/xiaoce-timeline/v1/getListByLastTime', 14 | params: { 15 | pageNum, 16 | token: state.token, 17 | uid: state.uid, 18 | ...commonParams, 19 | } 20 | }).then(res => res.data) 21 | } 22 | /** 23 | * 24 | * @param {number} pageNum 25 | */ 26 | export const getMyBooks = (pageNum=1) => { 27 | // TODO 添加token 28 | return request({ 29 | method: 'GET', 30 | url: '/xiaoce-cache/v1/userBuyList', 31 | params: { 32 | pageNum, 33 | token: state.token, 34 | uid: state.uid, 35 | ...commonParams 36 | } 37 | }).then(res => res.data) 38 | } 39 | 40 | 41 | export const getBookDetail = (id) => { 42 | return request({ 43 | method: 'GET', 44 | url: '/xiaoce-cache/v1/get', 45 | params: { 46 | id, 47 | token: state.token, 48 | uid: state.uid, 49 | ...commonParams 50 | } 51 | }).then(res => res.data) 52 | } 53 | 54 | /** 55 | * 56 | * @param {string} id 57 | */ 58 | export const getBookSectionDetail = (sectionId) => { 59 | // TODO 添加token 60 | return request({ 61 | method: 'GET', 62 | url: '/xiaoce-cache/v1/getSection', 63 | params: { 64 | sectionId, 65 | token: state.token, 66 | uid: state.uid, 67 | ...commonParams 68 | } 69 | }).then(res => res.data) 70 | } 71 | 72 | /** 73 | * 74 | * @param {string} id 75 | */ 76 | export const getBookSections = (id) => { 77 | // TODO 添加token 78 | return request({ 79 | method: 'GET', 80 | url: '/xiaoce-cache/v1/getListSection', 81 | params: { 82 | id, 83 | token: state.token, 84 | uid: state.uid, 85 | ...commonParams 86 | } 87 | }).then(res => res.data) 88 | } 89 | /** 90 | * 91 | * @param {string} id 92 | * @param {number} pageNum 93 | * @param {number} pageSize 94 | */ 95 | export const getListBuy = (id, pageNum=1, pageSize=20) => { 96 | // TODO 添加token 97 | return request({ 98 | method: 'GET', 99 | url: '/xiaoce-cache/v1/getListBuy', 100 | params: { 101 | id, 102 | pageNum, 103 | pageSize, 104 | token: state.token, 105 | uid: state.uid, 106 | ...commonParams 107 | } 108 | }).then(res => res.data) 109 | } -------------------------------------------------------------------------------- /src/api/collection.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import { commonParams } from './common' 3 | import store from '../store' 4 | const state = store.state 5 | 6 | /** 7 | * 8 | * @param {string} targetUserId 9 | * @param {number} page 10 | */ 11 | export const getUserCollectionSet = (targetUserId, page=0) => { 12 | return request({ 13 | method: 'GET', 14 | url: '/collection-set/v1/getUserCollectionSet', 15 | params: { 16 | src: commonParams.src, 17 | clientId: commonParams.client_id, 18 | token: state.token, 19 | userId: state.uid, 20 | targetUserId, 21 | page 22 | } 23 | }).then(res => res.data) 24 | } 25 | 26 | export const getFollowedCollectionSet = (targetUserId, page=0, pageSize=30) => { 27 | return request({ 28 | method: 'GET', 29 | url: '/collection-set/v1/getFollowedCollectionSet', 30 | params: { 31 | src: commonParams.src, 32 | clientId: commonParams.client_id, 33 | token: state.token, 34 | userId: state.uid, 35 | targetUserId, 36 | page, 37 | pageSize 38 | } 39 | }).then(res => res.data) 40 | } 41 | 42 | export const getCollectionSetInfo = (csId) => { 43 | return request({ 44 | method: 'GET', 45 | url: '/collection-set/v1/getCollectionSetInfo', 46 | params: { 47 | csId, 48 | src: commonParams.src, 49 | clientId: commonParams.client_id, 50 | userId: state.uid, 51 | token: state.token 52 | } 53 | }).then(res => res.data) 54 | } 55 | 56 | export const getCollectionSetEntries = (csId, page, rankType='new') => { 57 | return request({ 58 | method:'GET', 59 | url: '/collection-set/v1/getCollectionSetEntries', 60 | params: { 61 | page, 62 | csId, 63 | rankType, 64 | src: commonParams.src, 65 | clientId: commonParams.client_id, 66 | userId: state.uid, 67 | token: state.token 68 | } 69 | }).then(res => res.data) 70 | } 71 | 72 | export const followCollection = (csId) => { 73 | return request({ 74 | method: 'PUT', 75 | url: '/collection-set/v1/follow', 76 | params: { 77 | csId, 78 | userId: state.uid, 79 | token: state.token, 80 | clientId: commonParams.client_id, 81 | src: commonParams.src 82 | } 83 | }).then(res => res.data) 84 | } 85 | 86 | export const unFollowCollection = (csId) => { 87 | return request({ 88 | method: 'PUT', 89 | url: '/collection-set/v1/unfollow', 90 | params: { 91 | csId, 92 | userId: state.uid, 93 | token: state.token, 94 | clientId: commonParams.client_id, 95 | src: commonParams.src 96 | } 97 | }).then(res => res.data) 98 | } -------------------------------------------------------------------------------- /src/api/common.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | import { device_id, client_id } from '../common/config' 3 | export const commonParams = { 4 | device_id, 5 | client_id, 6 | src: 'android' 7 | } -------------------------------------------------------------------------------- /src/api/entry.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import { commonParams } from './common' 3 | import store from '../store' 4 | import qs from 'qs' 5 | const state = store.state 6 | 7 | export const getEntryByIds = (entryIds) => { 8 | return request({ 9 | method: 'GET', 10 | url: '/timeline-merger/v1/get_entry_by_ids', 11 | params: { 12 | entryIds, 13 | ...commonParams, 14 | token: state.token 15 | } 16 | }).then(res => res.data) 17 | } 18 | 19 | export const likeEntry = (entryId) => { 20 | return request.put(`/user-like/v1/user/like/entry/${entryId}`, qs.stringify({ 21 | uid: store.state.uid, 22 | token: store.state.token, 23 | device_id: commonParams.device_id, 24 | src: commonParams.src 25 | }),{ 26 | headers: { 27 | 'X-Juejin-Client': commonParams.client_id, 28 | 'X-Juejin-Src': 'web', 29 | 'X-Juejin-Token': store.state.token, 30 | 'X-Juejin-Uid': store.state.uid, 31 | 'Content-Type': 'application/x-www-form-urlencoded' 32 | } 33 | }).then(res => res.data) 34 | // return request({ 35 | // method: 'PUT', 36 | // url: `/user-like/v1/user/like/entry/${entryId}`, 37 | // headers: { 38 | // 'X-Juejin-Client': commonParams.client_id, 39 | // 'X-Juejin-Src': 'web', 40 | // 'X-Juejin-Token': store.state.token, 41 | // 'X-Juejin-Uid': store.state.uid 42 | // } 43 | // }).then(res => res.data) 44 | } -------------------------------------------------------------------------------- /src/api/home.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import { apiUrl, } from '../common/config' 3 | 4 | import { commonParams } from './common' 5 | import store from '../store' 6 | const state = store.state 7 | 8 | /** 9 | * 获取文章列表 10 | * @param {object} data 11 | */ 12 | export const query = (data) => { 13 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, data).then(res => { 14 | return res.data 15 | }) 16 | } 17 | 18 | /** 19 | * 获取标签 20 | * @param {string} category 21 | */ 22 | export const queryTag = (category) => { 23 | return request.post(apiUrl.WEB_HOME_REQUEST_URL, { 24 | operationName: "", 25 | query: "", 26 | variables: {category, limit: 15}, 27 | extensions: {query: {id: "801e22bdc908798e1c828ba6b71a9fd9"}} 28 | }).then(res => { 29 | return res.data 30 | }) 31 | } 32 | 33 | /** 34 | * 获取首页文章列表 35 | * @param {string} after 36 | */ 37 | export const getRecommendedArticle = (after='') => { 38 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 39 | variables: { 40 | after, 41 | platformCode: '2', 42 | positionCodes: [2, 3], 43 | tags: [] 44 | }, 45 | extensions: { 46 | query: { 47 | id: 'f948b4528c56f0d2ceaff0be67b0809d' 48 | } 49 | } 50 | }).then(res => { 51 | return res.data 52 | }) 53 | } 54 | 55 | /** 56 | * 获取热门文章列表 57 | * @param {string} after 58 | * @param {string} order 59 | */ 60 | export const getHotArticle = (after='', order='THREE_DAYS_HOTTEST') => { 61 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 62 | variables: { 63 | after, 64 | order, 65 | "first": 30 66 | }, 67 | "extensions": { 68 | "query": { 69 | "id": "f30157ca93f83fc7ff860ed0fb45599e" 70 | } 71 | } 72 | }).then(res => { 73 | return res.data 74 | }) 75 | } 76 | /** 77 | * 获取文章列表 78 | * @param {string} category 79 | * @param {string[]} tags 80 | * @param {string} before 81 | * @param {number} limit 82 | */ 83 | export const getEntryByTimeline = (category, tags=[], after='', limit=20) => { 84 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 85 | variables: { 86 | after, 87 | category, 88 | tags, 89 | }, 90 | extensions: { 91 | query: { 92 | id: '242a289e6ba916aa4f3ceae00a4225d3' 93 | } 94 | } 95 | }).then(res => { 96 | return res.data 97 | }) 98 | } 99 | 100 | /** 101 | * 102 | * @param {string} category 103 | * @param {number} limit 104 | * @param {string} period 105 | */ 106 | export const getEntryByPeriod = (category, before='', limit=20, period='3day') => { 107 | return request({ 108 | method: 'get', 109 | url: '/timeline-merger/v1/get_entry_by_period', 110 | params: { 111 | category, 112 | limit, 113 | period, 114 | before, 115 | token: state.token, 116 | uid: state.uid, 117 | ...commonParams 118 | } 119 | }).then(res => { 120 | return res.data 121 | }) 122 | } 123 | 124 | 125 | export const getFollowingUserArticle = (after) => { 126 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 127 | variables: { 128 | after, 129 | feedType: "ARTICLE", 130 | afterPosition: "5e8fba2cef23ab3a04e4ee2b" 131 | }, 132 | extensions: { 133 | query: { 134 | id: "05d2b2b04536aceb593531ec832d6b22" 135 | } 136 | }, 137 | 138 | }).then(res => res.data) 139 | } 140 | 141 | /** 142 | * 143 | * @param {string[]} exclude 144 | */ 145 | export const getRecommendedUser = (exclude=[]) => { 146 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 147 | variables: { 148 | excluded: [] 149 | }, 150 | extensions: { 151 | query: { 152 | id: "dc4406d13ccf186ca585b47f14e902b4" 153 | } 154 | } 155 | }).then(res => res.data) 156 | } 157 | -------------------------------------------------------------------------------- /src/api/pins.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import { apiUrl } from '../common/config' 3 | import store from '../store' 4 | import { commonParams } from './common' 5 | 6 | const state = store.state 7 | 8 | /** 9 | * 获取沸点推荐 10 | */ 11 | export const getHotRecommendList = () => { 12 | return request({ 13 | method: 'GET', 14 | url: '/short-msg/v1/getHotRecommendList', 15 | params: { 16 | token: state.token, 17 | uid: state.uid, 18 | ...commonParams 19 | } 20 | }).then(res => { 21 | return res.data 22 | }) 23 | } 24 | 25 | /** 26 | * 获取主题 27 | * @param {string} topicId 28 | * @param {number} page 29 | * @param {number} pageSize 30 | * @param {string} sortType 31 | */ 32 | export const getTopicList = (topicId, page=0, pageSize=20, sortType='rank') => { 33 | return request({ 34 | method: 'GET', 35 | url: '/short-msg/v1/pinList/topic', 36 | params: { 37 | topicId, 38 | page, 39 | pageSize, 40 | sortType, 41 | token: state.token, 42 | uid: state.uid, 43 | ...commonParams 44 | } 45 | }).then(res => { 46 | return res.data 47 | }) 48 | } 49 | 50 | /** 51 | * 获取沸点推荐列表 52 | * @param {string} after 53 | */ 54 | export const getRecommendedFeed = (after='') => { 55 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 56 | "variables": { 57 | after, 58 | "afterPosition": "" 59 | }, 60 | "extensions": { 61 | "query": { 62 | "id": "e3e03965a15cf246cf7cc3944086843b" 63 | } 64 | } 65 | }).then(res => res.data) 66 | } 67 | 68 | /** 69 | * 获取热门列表 70 | * @param {string} after 71 | */ 72 | export const getPopularPinList = (after='') => { 73 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 74 | "variables": { 75 | after, 76 | "first": 20 77 | }, 78 | "extensions": { 79 | "query": { 80 | "id": "1b6b07b2ffcdc06c43114d79ca131afa" 81 | } 82 | } 83 | }).then(res => res.data) 84 | } 85 | 86 | /** 87 | * 获取沸点评论 88 | * @param {string} id 89 | * @param {number} pageNum 90 | * @param {number} pageSize 91 | * @param {string} rankType 92 | */ 93 | export const getPinComments = (id, pageNum=0, pageSize=20, rankType='new') => { 94 | return request({ 95 | method: 'GET', 96 | url: `/hot-topic-comment-wrapper/v1/comments/${id}`, 97 | params: { 98 | pageNum, 99 | pageSize, 100 | rankType, 101 | token: state.token, 102 | uid: state.uid, 103 | ...commonParams 104 | 105 | } 106 | }).then(res => res.data) 107 | } 108 | 109 | // TODO 请求参数加上token 110 | /** 111 | * 112 | * @param {string} id 113 | */ 114 | export const getPinById = (id) => { 115 | return request({ 116 | method: 'GET', 117 | url: '/short-msg/v1/getByID', 118 | params: { 119 | msgId: id, 120 | token: state.token, 121 | uid: state.uid, 122 | ...commonParams 123 | } 124 | }).then(res => res.data) 125 | } 126 | 127 | export const getFollowingUserActivities = (after) => { 128 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 129 | variables: { 130 | after, 131 | feedType: "MAIN" 132 | }, 133 | extensions: { 134 | query: { 135 | id: "05d2b2b04536aceb593531ec832d6b22" 136 | } 137 | } 138 | }).then(res => res.data) 139 | } 140 | 141 | /** 142 | * 143 | * @param {string[]} exclude 144 | */ 145 | export const getRecommendedUser = (exclude=[], limit=8) => { 146 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 147 | variables: { 148 | excluded: JSON.stringify(exclude), 149 | limit, 150 | }, 151 | extensions: { 152 | query: { 153 | id: "038909ed3577b0925031b01b445819a0" 154 | } 155 | } 156 | }).then(res => res.data) 157 | } 158 | 159 | export const likePin = (msgId) => { 160 | return request({ 161 | method: 'GET', 162 | url: '/short-msg/v1/like', 163 | params: { 164 | msgId, 165 | uid: store.state.uid, 166 | token: store.state.token, 167 | ...commonParams 168 | } 169 | }).then(res => res.data) 170 | } 171 | 172 | export const unlikePin = (msgId) => { 173 | return request({ 174 | method: 'GET', 175 | url: '/short-msg/v1/unlike', 176 | params: { 177 | msgId, 178 | uid: store.state.uid, 179 | token: store.state.token, 180 | ...commonParams 181 | } 182 | }).then(res => res.data) 183 | } -------------------------------------------------------------------------------- /src/api/post.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import store from '../store' 3 | 4 | import { commonParams } from './common' 5 | const state = store.state 6 | 7 | export const getEntryView = (entryId) => { 8 | return request({ 9 | method: 'GET', 10 | url: '/entry-view/v1/getEntryView', 11 | params: { 12 | entryId, 13 | token: state.token, 14 | uid: state.uid, 15 | ...commonParams, 16 | // src: commonParams.src 17 | } 18 | }).then(res => { 19 | return res.data 20 | }) 21 | } 22 | 23 | export const getEntryByEntryIds = (entryIds) => { 24 | return request({ 25 | method: 'GET', 26 | url: '/timeline-merger/v1/get_entry_by_ids', 27 | params: { 28 | entryIds, 29 | token: state.token, 30 | uid: state.uid, 31 | ...commonParams 32 | // src: commonParams.src 33 | } 34 | }).then(res => res.data) 35 | } 36 | 37 | export const getRelatedEntry = (entryId, limit=4) => { 38 | return request({ 39 | method: 'GET', 40 | url: '/timeline-merger/v1/get_related_entry', 41 | params: { 42 | entryId, 43 | limit, 44 | token: state.token, 45 | uid: state.uid, 46 | ...commonParams 47 | // src: commonParams.src 48 | } 49 | }).then(res => res.data) 50 | } 51 | 52 | export const getComments = (id, createdAt='', rankType='new') => { 53 | return request({ 54 | method: 'GET', 55 | url: `/comment-wrapper-ms/v2/comments/entry/${id}`, 56 | params: { 57 | createdAt, 58 | rankType, 59 | // token: state.token, 60 | // uid: state.uid, 61 | // ...commonParams 62 | src: commonParams.src 63 | } 64 | }).then(res => res.data) 65 | } -------------------------------------------------------------------------------- /src/api/search.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import { apiUrl } from '../common/config' 3 | import { commonParams } from './common' 4 | import store from '../store' 5 | const state = store.state 6 | 7 | 8 | // TODO 添加token 9 | /** 10 | * 获取banner 11 | */ 12 | export const getEventBanner = () => { 13 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 14 | variables: { 15 | platformCode: 2, 16 | positionCodes: [4] 17 | }, 18 | extensions: { 19 | query: { 20 | id: "1623ab25980fadf6bae12e10fc0af1e8" 21 | } 22 | } 23 | }).then(res => res.data) 24 | } 25 | 26 | // TODO 添加uid和token 27 | /** 28 | * 29 | * @param {string} before 30 | */ 31 | export const getEntryByRank = (before) => { 32 | return request({ 33 | method: 'GET', 34 | url: '/timeline-merger/v1/get_entry_by_rank', 35 | params: { 36 | before, 37 | limit: 30, 38 | src: commonParams.src, 39 | token: state.token, 40 | uid: state.uid, 41 | ...commonParams 42 | } 43 | }).then(res => res.data) 44 | } 45 | 46 | /** 47 | * 48 | * @param {string} query 49 | * @param {string} type 50 | * @param {string} after 51 | */ 52 | export const search = (query, type, after='') => { 53 | return request.post(apiUrl.ANDROID_HOME_REQUEST_URL, { 54 | variables: { 55 | after, 56 | type: type.toUpperCase(), 57 | query 58 | }, 59 | extensions: { 60 | query: { 61 | id: "caee4ea8b64f5860b8867564230e905f" 62 | } 63 | } 64 | }).then(res => res.data) 65 | } -------------------------------------------------------------------------------- /src/api/topic.js: -------------------------------------------------------------------------------- 1 | import request from '../util/request' 2 | import { apiUrl } from '../common/config' 3 | import { commonParams } from './common' 4 | import store from '../store' 5 | import qs from 'qs' 6 | 7 | export const getTopicInfo = (topicId) => { 8 | return request({ 9 | method: 'GET', 10 | url: `/short-msg/v1/topic/${topicId}`, 11 | params: { 12 | uid: store.state.uid, 13 | token: store.state.token, 14 | src: commonParams.src, 15 | device_id: commonParams.device_id 16 | } 17 | }).then(res => res.data) 18 | } 19 | 20 | export const getTopicPinList = (topicId, sortType='rank', page=0, pageSize=20) => { 21 | return request({ 22 | method: 'GET', 23 | url: `/short-msg/v1/pinList/topic`, 24 | params: { 25 | topicId, 26 | sortType, 27 | page, 28 | pageSize, 29 | uid: store.state.uid, 30 | token: store.state.token, 31 | ...commonParams 32 | } 33 | }).then(res => res.data) 34 | } 35 | 36 | export const getTopicAttender = (topicId, page=0, pageSize=20) => { 37 | return request({ 38 | method: 'GET', 39 | url: '/short-msg/v1/topic/attenders', 40 | params: { 41 | topicId, 42 | page, 43 | pageSize, 44 | uid: store.state.uid, 45 | token: store.state.token, 46 | ...commonParams 47 | } 48 | }).then(res => res.data) 49 | } 50 | 51 | /** 52 | * 53 | * @param {string} sortType 54 | * @param {number} page 55 | * @param {number} pageSize 56 | */ 57 | export const getTopicList = (sortType='hot', page=0, pageSize=20) => { 58 | return request({ 59 | method: 'GET', 60 | url: '/short-msg/v1/topicList', 61 | params: { 62 | ...commonParams, 63 | uid: store.state.uid, 64 | token: store.state.token, 65 | sortType, 66 | page, 67 | pageSize 68 | } 69 | }).then( res => res.data) 70 | } 71 | 72 | /** 73 | * 74 | * @param {string} topicIds 75 | */ 76 | export const followTopic = (topicIds) => { 77 | return request.post('/short-msg/v1/topic/follow',qs.stringify({ 78 | topicIds, 79 | ...commonParams, 80 | uid: store.state.uid, 81 | token: store.state.token 82 | }), { 83 | headers: { 84 | 'Content-Type':' application/x-www-form-urlencoded' 85 | } 86 | }).then(res => res.data) 87 | } -------------------------------------------------------------------------------- /src/assets/css/common.styl: -------------------------------------------------------------------------------- 1 | 2 | .con { 3 | padding: 0 20*$unit; 4 | background: #fff; 5 | } 6 | 7 | .scroll-with-no-bar { 8 | overflow: scroll 9 | } 10 | 11 | .scroll-with-no-bar::-webkit-scrollbar { 12 | display: none 13 | } 14 | 15 | .page-content { 16 | display: flex; 17 | flex-direction: column; 18 | } 19 | 20 | .page-section { 21 | flex: 1; 22 | height: 0; 23 | } 24 | 25 | .slide-enter, .slide-leave-to{ 26 | 27 | transform: translateY(100%); 28 | z-index: 1; 29 | } 30 | 31 | .border-top-1px { 32 | position: relative; 33 | } 34 | 35 | .touch-active:active { 36 | background: #dcdbdb !important; 37 | } 38 | 39 | .follow { 40 | padding: 14*$unit 0; 41 | width: 150*$unit; 42 | border: 1px #6cbd45 solid; 43 | text-align: center; 44 | color: #6cbd45; 45 | font-size: 26*$unit; 46 | border-radius: 5*$unit; 47 | } 48 | .edit-btn { 49 | padding: 14*$unit 35*$unit; 50 | /* width: 140*$unit; */ 51 | border: 1px #007fff solid; 52 | color: #007fff; 53 | font-size: 26*$unit; 54 | border-radius: 5*$unit; 55 | } 56 | .follow.active { 57 | background: #6cbd45; 58 | border: none; 59 | color: #fff; 60 | } 61 | .follow .iconfont{ 62 | padding: 0; 63 | margin-right: 10*$unit; 64 | font-size: 26*$unit; 65 | } 66 | 67 | .border-top-1px::after { 68 | content: ' '; 69 | width: 100%; 70 | height: 1px; 71 | background: #edeeef; 72 | position: absolute; 73 | left: 0; 74 | top: 0; 75 | overflow: hidden; 76 | /* border-bottom: 1px solid #71777c; */ 77 | transform-origin: left bottom; 78 | } 79 | 80 | .border-bottom-1px { 81 | position: relative; 82 | } 83 | 84 | .border-bottom-1px::after { 85 | content: ' '; 86 | width: 100%; 87 | height: 1px; 88 | background: #edeeef; 89 | position: absolute; 90 | left: 0; 91 | bottom: 0; 92 | overflow: hidden; 93 | /* border-bottom: 1px solid #71777c; */ 94 | transform-origin: left bottom; 95 | } 96 | 97 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) { 98 | .border-bottom-1px::after { 99 | -webkit-transform: scaleY(.7); 100 | transform: scaleY(.7); 101 | } 102 | .border-top-1px::after { 103 | -webkit-transform: scaleY(.7); 104 | transform: scaleY(.7); 105 | } 106 | } 107 | 108 | @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) { 109 | .border-bottom-1px::after { 110 | -webkit-transform: scaleY(.5); 111 | transform: scaleY(.5); 112 | } 113 | .border-top-1px::after { 114 | -webkit-transform: scaleY(.7); 115 | transform: scaleY(.7); 116 | } 117 | } 118 | 119 | @media only screen and (-webkit-min-device-pixel-ratio: 3), only screen and (min-device-pixel-ratio: 3) { 120 | .border-bottom-1px ::after { 121 | -webkit-transform: scaleY(.33); 122 | transform: scaleY(.33); 123 | } 124 | .border-top-1px::after { 125 | -webkit-transform: scaleY(.7); 126 | transform: scaleY(.7); 127 | } 128 | } -------------------------------------------------------------------------------- /src/assets/css/variable.styl: -------------------------------------------------------------------------------- 1 | $gray-color=#f4f5f5 2 | $primary-color=#0180ff 3 | $border-color=#edeeef 4 | $text-color=#666 5 | $title-color=#2e3135 6 | $gray-text-color=#909090 7 | $primary-text-color=#007fff 8 | $active-color=#dcdbdb 9 | $primary-green-color=#6cbd45 10 | $article-content-color=#5c6066 11 | $unit=1/32rem 12 | -------------------------------------------------------------------------------- /src/common/cache.js: -------------------------------------------------------------------------------- 1 | const cache = { 2 | /** 3 | * 4 | * @param {string} key 5 | * @param {any} value 6 | */ 7 | set(key, value) { 8 | cache[key] = value 9 | }, 10 | /** 11 | * 12 | * @param {string} key 13 | * @param {any} value 14 | */ 15 | get(key) { 16 | return cache[key] 17 | } 18 | } 19 | 20 | export default cache -------------------------------------------------------------------------------- /src/common/config.js: -------------------------------------------------------------------------------- 1 | import store from './store' 2 | import { HOME_ROUTE_KEY, PIN_ROUTE_KEY, HOME_ROUTE_INDEX_KEY, PIN_ROUTE_INDEX_KEY } from './const' 3 | import { generateDeviceId } from '../util' 4 | 5 | export let defaultHomeRoutes = [{ 6 | path: '/home/following', 7 | title: '关注' 8 | },{ 9 | path: '/home/recommended', 10 | title: '推荐', 11 | },{ 12 | path: '/home/hot', 13 | title: '热榜' 14 | }, 15 | ] 16 | 17 | export let homeRoutes = store.get(HOME_ROUTE_KEY, [{ 18 | path: '/home/backend', 19 | title: '后端', 20 | show: false 21 | }, { 22 | path: '/home/frontend', 23 | title: '前端', 24 | show: false 25 | }, { 26 | path: '/home/android', 27 | title: 'Android', 28 | show: false 29 | }, { 30 | path: '/home/ios', 31 | title: 'IOS', 32 | show: false 33 | }, { 34 | path: '/home/ai', 35 | title: '人工智能', 36 | show: false 37 | }, { 38 | path: '/home/freebie', 39 | title: '开发工具', 40 | show: false 41 | }, { 42 | path: '/home/career', 43 | title: '代码人生', 44 | show: false 45 | }, { 46 | path: '/home/article', 47 | title: '阅读', 48 | show: false 49 | } 50 | ]) 51 | 52 | export const homeRouteIndex = store.get(HOME_ROUTE_INDEX_KEY, { 53 | '/home/following': -2, 54 | '/home/recommended': -1, 55 | '/home/hot': 0, 56 | '/home/backend': 1, 57 | '/home/frontend': 2, 58 | '/home/android': 3, 59 | '/home/ios': 4, 60 | '/home/ai': 5, 61 | '/home/freebie': 6, 62 | '/home/career': 7, 63 | '/home/article': 8, 64 | }) 65 | 66 | 67 | export const pinRouteIndex = store.get(PIN_ROUTE_INDEX_KEY, { 68 | '/pins/following': -2, 69 | '/pins/recommended': -1, 70 | '/pins/hot': -0, 71 | '/pins/topic/5c09ea2b092dcb42c740fe73': 1, 72 | '/pins/topic/5abb61e1092dcb4620ca3322': 2, 73 | '/pins/topic/5c106be9092dcb2cc5de7257': 3, 74 | '/pins/topic/5b514af1092dcb61bd72800d': 4, 75 | '/pins/topic/5abb67d2092dcb4620ca3324': 5, 76 | '/pins/topic/5c46a17f092dcb4737217152': 6, 77 | }) 78 | 79 | export let defaultPinsRoutes = [ 80 | { 81 | path: '/pins/following', 82 | title: '关注' 83 | }, 84 | { 85 | path: '/pins/recommended', 86 | title: '推荐' 87 | },{ 88 | path: '/pins/hot', 89 | title: '热门' 90 | } 91 | ] 92 | 93 | export let pinsRoutes = store.get(PIN_ROUTE_KEY, [{ 94 | path: '/pins/topic/5c09ea2b092dcb42c740fe73', 95 | title: '开源推荐', 96 | show: true 97 | },{ 98 | path: '/pins/topic/5abb61e1092dcb4620ca3322', 99 | title: '内推招聘', 100 | show: true, 101 | },{ 102 | path: '/pins/topic/5abcaa67092dcb4620ca335c', 103 | title: '掘金相亲', 104 | show: true, 105 | },{ 106 | path: '/pins/topic/5c106be9092dcb2cc5de7257', 107 | title: '上班摸鱼', 108 | show: true, 109 | },{ 110 | path: '/pins/topic/5b514af1092dcb61bd72800d', 111 | title: '应用安利', 112 | show: true, 113 | }, { 114 | path: '/pins/topic/5abb67d2092dcb4620ca3324', 115 | title: '开发工具', 116 | show: true 117 | }, { 118 | path: '/pins/topic/5c46a17f092dcb4737217152', 119 | title: 'New资讯', 120 | show: true 121 | }] 122 | ) 123 | 124 | export const apiUrl = { 125 | ANDROID_HOME_REQUEST_URL: 'https://android-api.juejin.im/graphql', 126 | WEB_HOME_REQUEST_URL: 'https://web-api.juejin.im/query' 127 | } 128 | 129 | export const pinsRouteType = { 130 | RECOMMENDED: 'RECOMMENDED', 131 | HOT: 'HOT', 132 | TOPIC: 'TOPIC' 133 | } 134 | 135 | export const routeTypes = { 136 | HOME_CATEGORY: 'home_category', 137 | HOME_RECOMMEND: 'home_recommend', 138 | HOME_HOT: 'home_hot', 139 | } 140 | 141 | 142 | export const device_id = generateDeviceId() 143 | export const client_id = device_id -------------------------------------------------------------------------------- /src/common/const.js: -------------------------------------------------------------------------------- 1 | export const HOME_ROUTE_KEY = '__HOME_ROUTES__' 2 | export const PIN_ROUTE_KEY = '__PIN_ROUTE_KEY__' 3 | export const TOKEN_KEY = '__TOKEN__' 4 | export const UID_KEY = '__UID_KEY__' 5 | export const IS_LOGIN_KEY = '__IS_LOGIN__' 6 | export const HOME_ROUTE_INDEX_KEY = '_HOME_ROUTE_INDEX_KEY_' 7 | export const PIN_ROUTE_INDEX_KEY = '_PIN_ROUTE_INDEX_KEY_' -------------------------------------------------------------------------------- /src/common/store.js: -------------------------------------------------------------------------------- 1 | const store = { 2 | get(key, defaultValue) { 3 | return JSON.parse(localStorage.getItem(key)) || defaultValue 4 | }, 5 | set(key, value) { 6 | localStorage.setItem(key, JSON.stringify(value)) 7 | } 8 | } 9 | 10 | export default store -------------------------------------------------------------------------------- /src/components/book-entry/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/book-section-entry/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/collection-item/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/comment/index.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 76 | 77 | -------------------------------------------------------------------------------- /src/components/fold-text/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 64 | 65 | -------------------------------------------------------------------------------- /src/components/l-article-entry/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 59 | 60 | -------------------------------------------------------------------------------- /src/components/l-pin-entry/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/link-view/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/loading/image/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/src/components/loading/image/loading.gif -------------------------------------------------------------------------------- /src/components/loading/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/m-article-entry/index.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 67 | 68 | -------------------------------------------------------------------------------- /src/components/m-author/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 72 | 73 | -------------------------------------------------------------------------------- /src/components/m-header/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/message/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' // 引入 Vue 2 | import MessageMain from './src' // 引入上边定义好的 message 模板 3 | const MessageBox = Vue.extend(MessageMain) // 使用 Vue.extend 来创建一个构造器 4 | let instance // instance 变量用来保存实例 5 | let timer = null // timer 变量用来保存定时器 6 | // 定义一个 function, 参数为 options, 默认为一个对象 7 | const Message = function(options = {}) { 8 | // 如果当前处在服务器端, 则直接返回 9 | if(Vue.prototype.$isServer) return 10 | // 如果当前定时器已开启, 说明页面上已经有一个 message-box 了, 则不能再继续创建新的 message-box 11 | if(timer) return 12 | // 对 options 做处理, 如果直接传入 string, 则使其保存在 options 的 message 属性上 13 | if(typeof options === 'string') { 14 | options = { 15 | message: options 16 | } 17 | } 18 | // 初始化实例, 并将 options 作为新的 data 传入, Vue 会将 options 合并到原有的 data 上, 覆盖原有的默认值, 但是, 在 options 中没有设置的是不会被改变的 19 | instance = new MessageBox({ 20 | data: options 21 | }) 22 | // 调用 $mount 方法, 将当前实例渲染为真实 DOM, 生成 $el,, 如果不执行这一步, 将拿不到 $el 的值, 但是不指定 DOM 节点接管当前实例 23 | instance.vm = instance.$mount() 24 | // 使用原生 JS 的 API 将当前实例的真实 DOM 追加到 body 中 25 | document.body.appendChild(instance.vm.$el) 26 | // 实例上的 vm 就是我们的 Vue 组件, 所以我们可以通过 vm 访问到当前实例中的所有属性 27 | // 将 visible 设置为 true, 即显示当前 message-box 28 | instance.vm.visible = true 29 | // 开启定时器 30 | timer = setTimeout(() => { 31 | // 在时间结束后将当前实例手动卸载 32 | instance.vm.visible = false 33 | // 使用原生 API 将当前实例生成的 DOM 节点在真实的 DOM 树中删除 34 | setTimeout(() => { 35 | instance.vm.$destroy() 36 | instance.vm.$el.parentNode.removeChild(instance.vm.$el) 37 | timer = null 38 | }, 500) 39 | 40 | // 清除定时器 41 | }, instance.vm.duration) 42 | // 定时器的时间使用 vm 中定义的时间 43 | return instance.vm 44 | } 45 | // 最终抛出一个对象, 对象上我们可以使用 install 来扩展 Vue 的插件 46 | // 当我们的对象上有 install 方法的时候, 它接收第一个参数为 Vue, 47 | // 我这里为了方便使用, 还在当前抛出的对象上定义了一个 message 方法, 为了方便在 axios 的拦截器中使用 48 | export default { 49 | message: Message, 50 | install(Vue) { 51 | Vue.prototype.$message = Message 52 | Vue.message = Message 53 | } 54 | } -------------------------------------------------------------------------------- /src/components/message/src/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/native-scroll/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 72 | 73 | -------------------------------------------------------------------------------- /src/components/nav-tab/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 82 | 83 | -------------------------------------------------------------------------------- /src/components/need-login/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/pin-info/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 53 | 54 | -------------------------------------------------------------------------------- /src/components/refresh/image/refresh.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengliao/vue-juejin/c9c3c1cd310e2c83a1ffc7103cf74d5906890f08/src/components/refresh/image/refresh.gif -------------------------------------------------------------------------------- /src/components/refresh/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/router-transition/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/s-article-entry/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/s-author/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 42 | 43 | -------------------------------------------------------------------------------- /src/components/s-header/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/s-pin-entry/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/scroll/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 138 | 139 | -------------------------------------------------------------------------------- /src/components/search-box/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/switch/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/tab/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/tag-item/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/tag/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/three-op/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/topic-item/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 41 | 42 | -------------------------------------------------------------------------------- /src/fliter/index.js: -------------------------------------------------------------------------------- 1 | export const dateDis = date => { 2 | let stime = new Date(date).getTime() 3 | let etime = Date.now() 4 | let dtime = etime - stime 5 | let year = Math.floor(dtime / (24 * 3600 * 1000 * 365)) 6 | if (year > 0) { 7 | return year + '年前' 8 | } 9 | let days = Math.floor(dtime / (24 * 3600 * 1000)) 10 | if (days > 0) { 11 | return days + '天前' 12 | } 13 | let hours = Math.floor(dtime / (3600 * 1000)) 14 | if (hours > 0) { 15 | return hours + '小时前' 16 | } 17 | let minutes = Math.floor(dtime / (60 * 1000)) 18 | if (minutes > 5) { 19 | return minutes + '分钟前' 20 | } 21 | return '刚刚' 22 | } 23 | 24 | const backgroundImages = [ 25 | 'https://b-gold-cdn.xitu.io/v3/static/img/lv-1.636691c.svg', 26 | 'https://b-gold-cdn.xitu.io/v3/static/img/lv-2.f597b88.svg', 27 | 'https://b-gold-cdn.xitu.io/v3/static/img/lv-3.e108c68.svg', 28 | 'https://b-gold-cdn.xitu.io/v3/static/img/lv-4.2c3fafd.svg', 29 | 'https://b-gold-cdn.xitu.io/v3/static/img/lv-5.f8d5198.svg', 30 | 'https://b-gold-cdn.xitu.io/v3/static/img/lv-6.74bd93a.svg' 31 | ] 32 | 33 | export const levelImage = index => { 34 | return backgroundImages[index - 1] 35 | } -------------------------------------------------------------------------------- /src/layout/footer/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 45 | 46 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | 24 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import App from "./App.vue" 3 | import router from "./router" 4 | import store from "./store" 5 | import routerTransition from './plugin/router-transition' 6 | // import RouterTransition from './plugin/router-transition/index.vue' 7 | // import RouterTransition from './components/router-transition' 8 | import Message from './components/message' 9 | 10 | import './assets/css/normalize.styl' 11 | import './assets/css/common.styl' 12 | import { dateDis, levelImage } from './fliter' 13 | import VueAwesomeSwiper from 'vue-awesome-swiper' 14 | 15 | // import style 16 | import 'swiper/css/swiper.css' 17 | import './assets/css/article.styl' 18 | 19 | // function resize() { 20 | // let clientWidth = document.documentElement.clientWidth 21 | // let fontSize = 16 * clientWidth / 375 22 | // document.documentElement.style.fontSize = fontSize + 'px' 23 | // } 24 | 25 | console.log(navigator.userAgent) 26 | 27 | 28 | Vue.use(VueAwesomeSwiper) 29 | Vue.use(Message) 30 | Vue.config.productionTip = false 31 | 32 | Vue.use(routerTransition, { 33 | router 34 | }) 35 | 36 | Vue.filter('dateDis', dateDis) 37 | Vue.filter('levelImage', levelImage) 38 | 39 | new Vue({ 40 | router, 41 | store, 42 | render: h => h(App) 43 | }).$mount("#app") 44 | 45 | -------------------------------------------------------------------------------- /src/mixins/index.js: -------------------------------------------------------------------------------- 1 | export const keepAliveMixin = { 2 | created() { 3 | this.$store.dispatch('addKeepAlive', this.$options.name) 4 | }, 5 | methods: { 6 | goBackAndRemoveKeepAlive() { 7 | this.$store.dispatch('removeKeepAlive', this.$options.name) 8 | this.$router.goBack() 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/plugin/router-transition/constant.js: -------------------------------------------------------------------------------- 1 | export const transitionType = { 2 | PUSH: 'push', 3 | COVER: 'cover' 4 | } 5 | 6 | export const directionType = { 7 | TOP_TO_BOTTOM: 'top-to-bottom', 8 | BOTTOM_TO_TOP: 'bottom-to-top', 9 | LEFT_TO_RIGHT: 'left-to-right', 10 | RIGHT_TO_LEFT: 'right-to-left' 11 | } -------------------------------------------------------------------------------- /src/plugin/router-transition/index.js: -------------------------------------------------------------------------------- 1 | import RouterTransition from './index.vue' 2 | export default { 3 | /** 4 | * 5 | * @param {Vue} Vue 6 | * @param {} options 7 | */ 8 | install(Vue, options) { 9 | let { routeIndex } = options 10 | Vue.prototype.$routeIndex = routeIndex 11 | Vue.component('router-transition', RouterTransition) 12 | 13 | }, 14 | } -------------------------------------------------------------------------------- /src/plugin/router-transition/util.js: -------------------------------------------------------------------------------- 1 | export const isDef = (o) => { 2 | return o!== undefined && o !== null 3 | } -------------------------------------------------------------------------------- /src/router/home.js: -------------------------------------------------------------------------------- 1 | import HomeArticleList from '../views/home/pages/home-article-list' 2 | import HomeFollowing from '../views/home/pages/home-following' 3 | import { homeRoutes as _homeRoutes, routeTypes} from '../common/config' 4 | 5 | const routeMap = { 6 | '/home/backend': { 7 | props: { 8 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 9 | categoryId: '5562b419e4b00c57d9b94ae2', 10 | hasTags: true 11 | }, 12 | path: '/home/backend', 13 | name: '后端', 14 | component: HomeArticleList, 15 | meta: { 16 | routeType: routeTypes.HOME_CATEGORY 17 | } 18 | }, 19 | '/home/frontend': { 20 | props: { 21 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 22 | categoryId: '5562b415e4b00c57d9b94ac8', 23 | hasTags: true 24 | }, 25 | path: '/home/frontend', 26 | name: '前端', 27 | component: HomeArticleList, 28 | meta: { 29 | routeType: routeTypes.HOME_CATEGORY 30 | } 31 | }, 32 | '/home/android': { 33 | props: { 34 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 35 | categoryId: '5562b410e4b00c57d9b94a92', 36 | hasTags: true 37 | }, 38 | path: '/home/android', 39 | name: 'Android', 40 | component: HomeArticleList, 41 | meta: { 42 | routeType: routeTypes.HOME_CATEGORY 43 | } 44 | }, 45 | '/home/ios': { 46 | props: { 47 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 48 | categoryId: '5562b405e4b00c57d9b94a41', 49 | hasTags: true 50 | }, 51 | path: '/home/ios', 52 | name: 'IOS', 53 | component: HomeArticleList, 54 | meta: { 55 | routeType: routeTypes.HOME_CATEGORY 56 | } 57 | }, 58 | '/home/ai': { 59 | props: { 60 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 61 | categoryId: '57be7c18128fe1005fa902de', 62 | hasTags: true 63 | }, 64 | path: '/home/ai', 65 | name: '人工智能', 66 | component: HomeArticleList, 67 | meta: { 68 | routeType: routeTypes.HOME_CATEGORY 69 | } 70 | }, 71 | '/home/freebie': { 72 | props: { 73 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 74 | categoryId: '5562b422e4b00c57d9b94b53', 75 | hasTags: true 76 | }, 77 | path: '/home/freebie', 78 | name: '开发工具', 79 | component: HomeArticleList, 80 | meta: { 81 | routeType: routeTypes.HOME_CATEGORY 82 | } 83 | }, 84 | '/home/career': { 85 | props: { 86 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 87 | categoryId: '5c9c7cca1b117f3c60fee548', 88 | hasTags: true 89 | }, 90 | path: '/home/career', 91 | name: '代码人生', 92 | component: HomeArticleList, 93 | meta: { 94 | routeType: routeTypes.HOME_CATEGORY 95 | } 96 | }, 97 | '/home/article': { 98 | props: { 99 | queryId: '242a289e6ba916aa4f3ceae00a4225d3', 100 | categoryId: '5562b428e4b00c57d9b94b9d', 101 | hasTags: true 102 | }, 103 | path: '/home/article', 104 | name: '阅读', 105 | component: HomeArticleList, 106 | meta: { 107 | routeType: routeTypes.HOME_CATEGORY 108 | } 109 | } 110 | } 111 | 112 | export const homeRoutes = [ 113 | { 114 | name: '关注', 115 | path: '/home/following', 116 | component: HomeFollowing 117 | }, 118 | { 119 | props: { 120 | queryId: 'f948b4528c56f0d2ceaff0be67b0809d' 121 | }, 122 | name: '推荐', 123 | path: '/home/recommended', 124 | component: HomeArticleList, 125 | meta: { 126 | routeType: routeTypes.HOME_RECOMMEND 127 | } 128 | },{ 129 | props: { 130 | queryId: 'f30157ca93f83fc7ff860ed0fb45599e', 131 | sort: 'THREE_DAYS_HOTTEST', 132 | sortTags:[{ 133 | tagId: 'THREE_DAYS_HOTTEST', 134 | title: '三天内' 135 | }, { 136 | tagId: 'WEEKLY_HOTTEST', 137 | title: '七天内' 138 | }, { 139 | tagId: 'MONTHLY_HOTTEST', 140 | title: '30天内' 141 | }, { 142 | tagId: 'HOTTEST', 143 | title: '全部' 144 | }] 145 | }, 146 | name: '热榜', 147 | path: '/home/hot', 148 | component: HomeArticleList, 149 | meta: { 150 | routeType: routeTypes.HOME_HOT 151 | } 152 | } 153 | ] 154 | 155 | for (let item of _homeRoutes) { 156 | homeRoutes.push(routeMap[item.path]) 157 | } 158 | -------------------------------------------------------------------------------- /src/router/pins.js: -------------------------------------------------------------------------------- 1 | import PinsEntryList from '../views/pins/pages/pins-entry-list' 2 | import PinFollowing from '../views/pins/pages/pin-following' 3 | import { pinsRouteType } from '../common/config' 4 | 5 | export const pinsRoutes = [{ 6 | name: 'pins-following', 7 | path: '/pins/following', 8 | component: PinFollowing 9 | },{ 10 | name: 'pins-recommended', 11 | path: '/pins/recommended', 12 | component: PinsEntryList, 13 | meta: { 14 | type: pinsRouteType.RECOMMENDED 15 | } 16 | }, { 17 | name: 'pins-hot', 18 | path: '/pins/hot', 19 | component: PinsEntryList, 20 | meta: { 21 | type: pinsRouteType.HOT, 22 | } 23 | },{ 24 | path: '/pins/topic/:id', 25 | component: PinsEntryList, 26 | meta: { 27 | type: pinsRouteType.TOPIC 28 | } 29 | }] -------------------------------------------------------------------------------- /src/router/topic.js: -------------------------------------------------------------------------------- 1 | import TopicPinList from '../views/topic/pages/topic-pin-list' 2 | 3 | export const topicRoutes = [{ 4 | name: 'topic-pin-list', 5 | path: ':type', 6 | component: TopicPinList 7 | }] -------------------------------------------------------------------------------- /src/router/user.js: -------------------------------------------------------------------------------- 1 | import UserActivity from '../views/user/pages/user-activity' 2 | import UserPost from '../views/user/pages/user-post' 3 | import UserPin from '../views/user/pages/user-pin' 4 | import UserShare from '../views/user/pages/user-share' 5 | import UserMore from '../views/user/pages/user-more' 6 | import { ROUTE_INDEX } from '../common/const' 7 | export const userRoutes = [ 8 | { 9 | name: 'user-activity', 10 | path: 'activies', 11 | component: UserActivity 12 | }, 13 | { 14 | name: 'user-post', 15 | path: 'posts', 16 | component: UserPost 17 | }, 18 | { 19 | name: 'user-pin', 20 | path: 'pins', 21 | component: UserPin 22 | }, 23 | { 24 | name: 'user-share', 25 | path: 'shares', 26 | component: UserShare, 27 | }, 28 | { 29 | name: 'user-more', 30 | path: 'more', 31 | component: UserMore, 32 | }, 33 | ] -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | 3 | import { HOME_ROUTE_KEY, PIN_ROUTE_KEY, TOKEN_KEY, UID_KEY, IS_LOGIN_KEY, HOME_ROUTE_INDEX_KEY, PIN_ROUTE_INDEX_KEY } from '../common/const' 4 | import { login } from '../api/auth' 5 | 6 | import Message from '../components/message' 7 | import store from '../common/store' 8 | 9 | export const setRouteTransition = ({commit}, value) => { 10 | commit(types.SET_ROUTE_TRANSITION, value) 11 | } 12 | 13 | export const userLogin = async ({ commit },{username, password, loginType}) => { 14 | let data = await login(username, password, loginType) 15 | 16 | if (data.s !== 1) { 17 | Message.message(data.m) 18 | return false 19 | } 20 | let { token , user_id } = data.d 21 | commit(types.SET_TOKEN, token) 22 | commit(types.SET_UID, user_id) 23 | commit(types.SET_IS_LOGIN, true) 24 | localStorage.setItem(TOKEN_KEY, token) 25 | localStorage.setItem(UID_KEY, user_id) 26 | localStorage.setItem(IS_LOGIN_KEY, true) 27 | return true 28 | } 29 | 30 | export const userLogout = ({ commit }) => { 31 | commit(types.SET_IS_LOGIN, false) 32 | localStorage.setItem(IS_LOGIN_KEY, false) 33 | } 34 | 35 | export const addKeepAlive = ({commit,state}, componentName) => { 36 | let { keepAliveArr } = state 37 | if (keepAliveArr.indexOf(componentName) === -1) { 38 | keepAliveArr.push(componentName) 39 | commit(types.SET_KEEP_ALIVE_ARR, keepAliveArr) 40 | } 41 | } 42 | 43 | export const removeKeepAlive = ({commit, state}, componentName) => { 44 | let { keepAliveArr } = state 45 | let index = keepAliveArr.indexOf(componentName) 46 | if (index !== -1) { 47 | keepAliveArr.splice(index, 1) 48 | commit(types.SET_KEEP_ALIVE_ARR, keepAliveArr) 49 | } 50 | } 51 | 52 | 53 | export const setHomeRoutes = ({commit}, homeRoutes) => { 54 | // store.set(HOME_ROUTE_KEY, homeRoutes) 55 | commit(types.SET_HOME_ROUTES, homeRoutes) 56 | store.set(HOME_ROUTE_KEY, homeRoutes) 57 | } 58 | 59 | 60 | export const setPinRoutes = ({commit}, pinRoutes) => { 61 | // store.set(PIN_ROUTE_KEY, pinRoutes) 62 | commit(types.SET_PINS_ROUTES, pinRoutes) 63 | store.set(PIN_ROUTE_KEY, pinRoutes) 64 | } 65 | 66 | export const setQuery = ({ commit }, query) => { 67 | commit(types.SET_QUERY, query) 68 | } 69 | 70 | 71 | export const setHomeRouteIndex = ({commit, state}, routeIndex) => { 72 | let { homeRouteIndex } = state 73 | homeRouteIndex = Object.assign(homeRouteIndex, routeIndex) 74 | commit(types.SET_HOME_ROUTE_INDEX, homeRouteIndex) 75 | store.set(HOME_ROUTE_INDEX_KEY, homeRouteIndex) 76 | } 77 | 78 | export const setPinRouteIndex = ({commit, state}, routeIndex) => { 79 | let { pinRouteIndex } = state 80 | pinRouteIndex = Object.assign(pinRouteIndex, routeIndex) 81 | commit(types.SET_PIN_ROUTE_INDEX, pinRouteIndex) 82 | store.set(PIN_ROUTE_INDEX_KEY, pinRouteIndex) 83 | } 84 | 85 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | export const routeTransition = state => state.routeTransition -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import Vuex from "vuex" 3 | 4 | Vue.use(Vuex) 5 | import * as actions from './actions' 6 | import * as getters from './getters' 7 | import mutations from './mutations' 8 | import state from './state' 9 | export default new Vuex.Store({ 10 | actions, 11 | getters, 12 | mutations, 13 | state 14 | }) 15 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const SET_ROUTE_TRANSITION = 'set_route_transition' 2 | export const SET_HOME_ROUTES = 'set_home_routes' 3 | export const SET_PINS_ROUTES = 'set_pins_routes' 4 | export const SET_TOKEN = 'set_token' 5 | export const SET_UID = 'set_uid' 6 | export const SET_IS_LOGIN = 'set_is_login' 7 | export const SET_KEEP_ALIVE_ARR = 'set_keep_alive_arr' 8 | export const SET_QUERY = 'set_query' 9 | export const SET_HOME_ROUTE_INDEX = 'set_home_route_index' 10 | export const SET_PIN_ROUTE_INDEX = 'set_pin_route_index' -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | const mutations = { 3 | [types.SET_ROUTE_TRANSITION](state, routeTransition) { 4 | state.routeTransition = routeTransition 5 | }, 6 | [types.SET_HOME_ROUTES](state, homeRoutes) { 7 | state.homeRoutes = homeRoutes 8 | }, 9 | [types.SET_PINS_ROUTES](state, pinsRoutes) { 10 | state.pinsRoutes = pinsRoutes 11 | }, 12 | [types.SET_TOKEN](state, token) { 13 | state.token = token 14 | }, 15 | [types.SET_UID](state, uid) { 16 | state.uid = uid 17 | }, 18 | [types.SET_IS_LOGIN](state, isLogin) { 19 | state.isLogin = isLogin 20 | }, 21 | [types.SET_KEEP_ALIVE_ARR](state, arr) { 22 | state.keepAliveArr = arr 23 | }, 24 | [types.SET_QUERY](state, query) { 25 | state.query = query 26 | }, 27 | [types.SET_HOME_ROUTE_INDEX](state, homeRouteIndex) { 28 | state.homeRouteIndex = homeRouteIndex 29 | }, 30 | [types.SET_PIN_ROUTE_INDEX](state, pinRouteIndex) { 31 | state.pinRouteIndex = pinRouteIndex 32 | } 33 | } 34 | 35 | export default mutations -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | import { homeRoutes,pinsRoutes, homeRouteIndex, pinRouteIndex } from '../common/config' 2 | import { TOKEN_KEY, UID_KEY, IS_LOGIN_KEY } from '../common/const' 3 | 4 | const state = { 5 | routeTransition: true, 6 | defaultAvatar: 'https://b-gold-cdn.xitu.io/v3/static/img/default-avatar.e30559a.svg', 7 | homeRoutes, 8 | pinsRoutes, 9 | homeRouteIndex, 10 | pinRouteIndex, 11 | token: localStorage.getItem(TOKEN_KEY) || '', 12 | uid: localStorage.getItem(UID_KEY) || '', 13 | isLogin: JSON.parse(localStorage.getItem(IS_LOGIN_KEY)) || false, 14 | keepAliveArr: ['layout'], 15 | query: '' 16 | } 17 | 18 | export default state -------------------------------------------------------------------------------- /src/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 从数组中随机抽取n个 3 | * @param {Array} arr 4 | * @param {number} n 5 | * @returns 6 | */ 7 | export const randomSelect = (arr, n) => { 8 | let _arr = arr.slice() 9 | let result = [] 10 | if (n >= _arr.length) { 11 | return _arr 12 | } 13 | let len = _arr.length 14 | for (let i = 0; i < n; ++i) { 15 | let index = Math.floor(Math.random() * (len - i)) 16 | result.push(_arr[index]) 17 | let temp = _arr[index] 18 | _arr[index] = _arr[len - 1 - i] 19 | _arr[len - 1 - i] = temp 20 | } 21 | return result 22 | } 23 | 24 | /** 25 | * 26 | * @param {string} url 27 | */ 28 | export const getUrlParams = (url) => { 29 | let queryString = url.split('?')[1] 30 | if (queryString) { 31 | let kvs = queryString.split('&') 32 | if (kvs.length > 0) { 33 | let params = {} 34 | for (let item of kvs) { 35 | let [k, v] = item.split('=') 36 | params[k] = v 37 | } 38 | return params 39 | } 40 | } 41 | 42 | } 43 | 44 | /** 45 | * 46 | * @param {Function} fn 47 | * @param {number} delay 48 | */ 49 | export const throttle = function(fn, delay=500) { 50 | let last = 0 51 | return function(...args) { 52 | if (Date.now() - last >= delay) { 53 | fn.apply(this, args) 54 | last = Date.now() 55 | } 56 | } 57 | } 58 | 59 | export function generateDeviceId() { 60 | let deviceId = localStorage.getItem('_device_id_') 61 | if (!deviceId) { 62 | deviceId = Math.random().toString().slice(2, 6) 63 | let deviceId = localStorage.setItem('_device_id_', deviceId) 64 | } 65 | return deviceId 66 | } -------------------------------------------------------------------------------- /src/util/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '../store' 3 | import { device_id, client_id } from '../common/config' 4 | 5 | 6 | const service = axios.create({ 7 | timeout: 5000 8 | }) 9 | 10 | service.interceptors.request.use( 11 | config => { 12 | config.headers['X-Agent'] = 'Juejin/xiaomi/Redmi Note 7 Pro Android/9 Juejin/Android/5.9.3', 13 | config.headers['X-Juejin-Src'] = 'android' 14 | // let { token, uid } = store.state 15 | // config.headers['X-Juejin-Uid'] = state.uid 16 | // config.headers['X-Juejin-Token'] = state.token 17 | // config.headers['X-Token'] = state.token 18 | const state = store.state 19 | config.headers['X-Legacy-Token'] = state.token 20 | config.headers['X-Legacy-Uid'] = state.uid 21 | config.headers['X-Juejin-Client'] = client_id 22 | config.headers['X-Legacy-Device-Id'] = device_id 23 | return config 24 | } 25 | ) 26 | service.interceptors.response.use(res=>{ 27 | return res 28 | }, err=> { 29 | return err.response 30 | }) 31 | 32 | export default service -------------------------------------------------------------------------------- /src/views/about/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 75 | 76 | -------------------------------------------------------------------------------- /src/views/books/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 56 | 57 | -------------------------------------------------------------------------------- /src/views/books/pages/book-entry-list/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 79 | 80 | -------------------------------------------------------------------------------- /src/views/collection-set/index.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 116 | 117 | -------------------------------------------------------------------------------- /src/views/feedback/index.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 78 | 79 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 96 | 97 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 81 | 82 | -------------------------------------------------------------------------------- /src/views/my-like/index.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 111 | 112 | -------------------------------------------------------------------------------- /src/views/my-purchased-book/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 58 | 59 | -------------------------------------------------------------------------------- /src/views/pins/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/recommendation-author/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 77 | 78 | -------------------------------------------------------------------------------- /src/views/recommendation-author/pages/recommend-author-list/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/search/index.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 128 | 129 | -------------------------------------------------------------------------------- /src/views/search/pages/search-result/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 74 | 75 | -------------------------------------------------------------------------------- /src/views/section-detail/components/section-content/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 48 | 49 | -------------------------------------------------------------------------------- /src/views/set-psd/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 59 | 60 | -------------------------------------------------------------------------------- /src/views/setting/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 63 | 64 | -------------------------------------------------------------------------------- /src/views/tag-manage/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 138 | 139 | -------------------------------------------------------------------------------- /src/views/topic-attender/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 80 | 81 | -------------------------------------------------------------------------------- /src/views/topic/pages/topic-pin-list/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 49 | 50 | -------------------------------------------------------------------------------- /src/views/topics/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 86 | 87 | -------------------------------------------------------------------------------- /src/views/user-like/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 59 | 60 | -------------------------------------------------------------------------------- /src/views/user-read-history/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 79 | 80 | -------------------------------------------------------------------------------- /src/views/user-tag/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 58 | 59 | -------------------------------------------------------------------------------- /src/views/user/pages/user-more/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 50 | 51 | -------------------------------------------------------------------------------- /src/views/user/pages/user-pin/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 43 | 44 | -------------------------------------------------------------------------------- /src/views/user/pages/user-post/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | 44 | -------------------------------------------------------------------------------- /src/views/user/pages/user-share/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 42 | 43 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const events = require('events') 2 | events.EventEmitter.defaultMaxListeners = 50 3 | module.exports = { 4 | lintOnSave: false, 5 | devServer: { 6 | proxy: { 7 | '/timeline-merger': { 8 | target: 'http://timeline-merger-ms.juejin.im', 9 | changeOrigin: true, 10 | pathRewrite: { 11 | '/timeline-merger': '/' 12 | } 13 | }, 14 | '/entry-view': { 15 | target: 'https://entry-view-storage-api-ms.juejin.im', 16 | changeOrigin: true, 17 | pathRewrite: { 18 | '/entry-view': '/' 19 | } 20 | }, 21 | '/hot-topic-comment-wrapper': { 22 | target: 'https://hot-topic-comment-wrapper-ms.juejin.im', 23 | changeOrigin: true, 24 | pathRewrite: { 25 | '/hot-topic-comment-wrapper': '/' 26 | } 27 | }, 28 | '/short-msg': { 29 | target: 'https://short-msg-ms.juejin.im', 30 | changeOrigin: true, 31 | pathRewrite: { 32 | '/short-msg': '/' 33 | } 34 | }, 35 | '/xiaoce-cache': { 36 | target: 'https://xiaoce-cache-api-ms.juejin.im', 37 | changeOrigin: true, 38 | pathRewrite: { 39 | '/xiaoce-cache': '/' 40 | } 41 | }, 42 | '/xiaoce-timeline': { 43 | target: 'https://xiaoce-timeline-api-ms.juejin.im', 44 | changeOrigin: true, 45 | pathRewrite: { 46 | '/xiaoce-timeline': '/' 47 | } 48 | }, 49 | 50 | '/comment-wrapper-ms': { 51 | target: 'https://comment-wrapper-ms.juejin.im', 52 | changeOrigin: true, 53 | pathRewrite: { 54 | '/comment-wrapper-ms': '/' 55 | } 56 | }, 57 | '/event-storage': { 58 | target: 'https://event-storage-api-ms.juejin.im', 59 | changeOrigin: true, 60 | pathRewrite: { 61 | '/event-storage': '/' 62 | } 63 | }, 64 | '/auth-center': { 65 | target: 'https://auth-center-ms.juejin.im', 66 | changeOrigin: true, 67 | pathRewrite: { 68 | '/auth-center': '/' 69 | } 70 | }, 71 | '/user-storage': { 72 | target: 'https://user-storage-api-ms.juejin.im', 73 | changeOrigin: true, 74 | pathRewrite: { 75 | '/user-storage': '/' 76 | } 77 | }, 78 | '/gold-tag': { 79 | target: 'https://gold-tag-ms.juejin.im', 80 | changeOrigin: true, 81 | pathRewrite: { 82 | '/gold-tag': '/' 83 | } 84 | }, 85 | '/user-like': { 86 | target: 'https://user-like-wrapper-ms.juejin.im', 87 | changeOrigin: true, 88 | pathRewrite: { 89 | '/user-like': '/' 90 | } 91 | }, 92 | '/collection-set': { 93 | target: 'https://collection-set-ms.juejin.im', 94 | changeOrigin: true, 95 | pathRewrite: { 96 | '/collection-set': '/' 97 | } 98 | }, 99 | '/lccro': { 100 | target: 'https://lccro-api-ms.juejin.im', 101 | changeOrigin: true, 102 | pathRewrite: { 103 | '/lccro': '/' 104 | } 105 | }, 106 | '/follow-api': { 107 | target: 'https://follow-api-ms.juejin.im', 108 | changeOrigin: true, 109 | pathRewrite: { 110 | '/follow-api': '/' 111 | } 112 | }, 113 | }, 114 | }, 115 | css: { 116 | loaderOptions: { 117 | stylus: { 118 | import: '~@/assets/css/variable.styl' 119 | } 120 | } 121 | } 122 | } --------------------------------------------------------------------------------