├── .env.development ├── .env.production ├── .gitignore ├── README.md ├── a.json ├── babel.config.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── server ├── app.js ├── cheerio │ ├── article.js │ ├── book.js │ ├── catalog.js │ ├── category.js │ ├── home.js │ ├── index.js │ └── rank.js ├── config.json ├── controler │ ├── bookControler.js │ ├── commControler.js │ ├── rankControler.js │ └── userControler.js ├── graphql │ └── schema.js ├── model │ ├── book.js │ ├── bookdetail.js │ ├── category.js │ ├── collect.js │ ├── freebook.js │ ├── hotsalebook.js │ ├── like.js │ ├── newbook.js │ ├── rank.js │ ├── rhotsalebook.js │ ├── rnewauthorbook.js │ ├── rnewbook.js │ ├── rnewfansbook.js │ ├── rrecommend.js │ ├── rsignbook.js │ ├── user.js │ ├── userinfo.js │ ├── yuepiao copy.js │ └── yuepiao.js ├── module │ ├── article.js │ ├── book.js │ ├── category.js │ ├── category_detail.js │ ├── collect_add.js │ ├── collect_del.js │ ├── collect_list.js │ ├── getCollectList.js │ ├── getLikeList.js │ ├── getUserInfo.js │ ├── getbooklist.js │ ├── like_add.js │ ├── like_del.js │ ├── like_list.js │ ├── login.js │ ├── rank.js │ ├── rank_detail.js │ └── search.js ├── package.json ├── request │ ├── api.js │ ├── getArticle.js │ └── http.js ├── router │ └── index.js ├── static │ └── js │ │ └── home_index.js ├── utils │ ├── connect.js │ ├── dataio.js │ ├── err.js │ ├── filterResCode.js │ ├── httpwithjson.js │ ├── tool.js │ ├── userToken.js │ ├── uuid.js │ ├── whiteList.js │ └── writeData.js └── yarn.lock ├── src ├── App.vue ├── assets │ ├── css │ │ └── reset.scss │ └── logo.png ├── components │ └── common │ │ ├── LoadHeader.vue │ │ ├── NavBar.vue │ │ ├── NavHeader.vue │ │ ├── PullUp.vue │ │ └── backTop.vue ├── interface │ ├── base.ts │ └── user.ts ├── main.ts ├── request │ ├── api.ts │ └── http.ts ├── router │ └── index.ts ├── shims-tsx.d.ts ├── shims-vue-page-stack.d.ts ├── shims-vue-proptype.d.ts ├── shims-vue.d.ts ├── store │ ├── getter.ts │ ├── index.ts │ └── module │ │ ├── book │ │ ├── index.ts │ │ ├── interface.ts │ │ └── type.ts │ │ ├── category │ │ ├── index.ts │ │ ├── interface.ts │ │ └── type.ts │ │ ├── common │ │ ├── index.ts │ │ ├── interface.ts │ │ └── type.ts │ │ ├── rank │ │ ├── index.ts │ │ ├── interface.ts │ │ └── type.ts │ │ └── user │ │ ├── index.ts │ │ ├── interface.ts │ │ └── type.ts ├── utils │ ├── aes.ts │ ├── auth.ts │ ├── loadjs.js │ ├── tool.ts │ └── vw.css └── views │ ├── BookInfo.vue │ ├── Layout.vue │ ├── book │ ├── article │ │ └── index.vue │ └── index.vue │ ├── booklist │ ├── component │ │ └── List.vue │ └── index.vue │ ├── category │ ├── detail.vue │ └── index.vue │ ├── home │ ├── component │ │ ├── Fun.vue │ │ ├── List.vue │ │ ├── LunBo.vue │ │ └── data.ts │ └── index.vue │ ├── login │ └── index.vue │ ├── person │ ├── component │ │ └── User.vue │ └── index.vue │ ├── rank │ ├── component │ │ ├── List.vue │ │ └── rtype.ts │ ├── detail.vue │ └── index.vue │ └── search │ ├── component │ └── searchList.vue │ └── index.vue ├── static ├── 1.png ├── 2.png └── 3.png ├── tsconfig.json ├── vue.config.js └── yarn.lock /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_BASEURL="http://192.168.1.101:3000" -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_BASEURL="https://fiction.tmfree.xyz:3001" -------------------------------------------------------------------------------- /.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 | # server 24 | /server/router/craw/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-fiction 2 | 3 | ## 在线预览 4 | **由于目前只支持指定账号登录(没有使用数据库),所以没有更新到服务器,需自己clone下来运行看效果。(本人对后端业务了解比较少,可能存在问题。后期会努力提升)** 5 | > 项目演示地址:[仿小说网WebApp](http://1.116.232.86/)。(纯属个人练手,巩固知识,无其他用途) 6 | 7 | > 项目源码地址:[vue-fiction](https://github.com/Tmfree/vue-fiction)。觉得还可以的话给个star 在这先谢谢了~ 8 | 9 | ## Build Setup 10 | 11 | ### 客户端 12 | ``` bash 13 | # 下载或克隆下来,然后安装依赖 14 | yarn or npm install 15 | 16 | # 开发预览 17 | yarn or npm serve 18 | 19 | # 打包发布,生成的文件在dist文件夹中 20 | yarn or npm build 21 | ``` 22 | 23 | ### 服务端 24 | ``` bash 25 | # 提取server文件夹,安装依赖 26 | yarn or npm install 27 | 28 | # 运行 29 | node app.js 30 | ``` 31 | ## 接口请求的变量修改 32 | 33 | ### 开发环境 34 | ```bash 35 | # .env.development文件 36 | VUE_APP_BASEURL="url" 37 | ``` 38 | ### 生产环境 39 | ```bash 40 | # .env.production文件 41 | VUE_APP_BASEURL="url" 42 | ``` 43 | ## 更新 44 | 添加登录模块、点赞和收藏,目前只支持现有账号的登录,后期添加注册模块 45 | ``` 46 | 账号:123456 47 | 密码:123456 48 | ``` 49 | ![效果图](static/3.png) 50 | 51 | ## 项目部分结构 52 | ``` 53 | |-- fiction 54 | |-- .env.development 开发环境变量 55 | |-- .env.production 生成环境变量 56 | |-- .gitignore 57 | |-- babel.config.js 58 | |-- tsconfig.json 59 | |-- package.json 60 | |-- postcss.config.js 61 | |-- README.md 62 | |-- vue.config.js 63 | |-- yarn.lock 64 | |-- src 65 | |-- interface //接口存放 66 | |-- base.ts 67 | |-- user.ts 68 | |-- App.vue 69 | |-- main.ts 70 | |-- assets 71 | |-- components 72 | |-- request 请求设置 73 | |-- router 路由 74 | |-- store 75 | | |-- getter.ts 76 | | |-- index.ts 77 | | |-- module 78 | | |-- common 79 | | |-- rank 80 | |-- utils 81 | | |-- navBar.ts 82 | | |-- vw.css 83 | ``` 84 | ## 预览 85 | 86 | ![效果图](static/1.png) 87 | 88 | ![效果图](static/2.png) 89 | 90 | ## 最后 91 | 要是感兴趣的话,可以自行看代码,大部分内容还是比较简单的,有问题的话欢迎提出了一起讨论 92 | 93 | [1]: https://github.com/Tmfree/vue-fiction 94 | [2]: https://tmfree.dowy.cn/ 95 | 96 | ## Build Setup 97 | 98 | ### 客户端 99 | ``` bash 100 | # 下载或克隆下来,然后安装依赖 101 | yarn or npm install 102 | 103 | # 开发预览 104 | yarn or npm serve 105 | 106 | # 打包发布,生成的文件在dist文件夹中 107 | yarn or npm build 108 | ``` 109 | 110 | ### 服务端 111 | ``` bash 112 | # 提取server文件夹,安装依赖 113 | yarn or npm install 114 | 115 | # 运行 116 | node app.js 117 | ``` 118 | -------------------------------------------------------------------------------- /a.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "catId": "21", 4 | "cName": "玄幻", 5 | "pCatId": "0" 6 | }, 7 | { 8 | "cName": "东方玄幻", 9 | "catId": "8", 10 | "pCatId": "21" 11 | }, 12 | { 13 | "cName": "异世大陆", 14 | "catId": "73", 15 | "pCatId": "21" 16 | }, 17 | { 18 | "cName": "王朝争霸", 19 | "catId": "58", 20 | "pCatId": "21" 21 | }, 22 | { 23 | "cName": "高武世界", 24 | "catId": "78", 25 | "pCatId": "21" 26 | }, 27 | { 28 | "catId": "1", 29 | "cName": "奇幻", 30 | "pCatId": "0" 31 | }, 32 | { 33 | "cName": "现代魔法", 34 | "catId": "38", 35 | "pCatId": "1" 36 | }, 37 | { 38 | "cName": "剑与魔法", 39 | "catId": "62", 40 | "pCatId": "1" 41 | }, 42 | { 43 | "cName": "史诗奇幻", 44 | "catId": "201", 45 | "pCatId": "1" 46 | }, 47 | { 48 | "cName": "黑暗幻想", 49 | "catId": "202", 50 | "pCatId": "1" 51 | }, 52 | { 53 | "cName": "历史神话", 54 | "catId": "20092", 55 | "pCatId": "1" 56 | }, 57 | { 58 | "cName": "另类幻想", 59 | "catId": "20093", 60 | "pCatId": "1" 61 | }, 62 | { 63 | "catId": "2", 64 | "cName": "武侠", 65 | "pCatId": "0" 66 | }, 67 | { 68 | "cName": "传统武侠", 69 | "catId": "5", 70 | "pCatId": "2" 71 | }, 72 | { 73 | "cName": "武侠幻想", 74 | "catId": "30", 75 | "pCatId": "2" 76 | }, 77 | { 78 | "cName": "国术无双", 79 | "catId": "206", 80 | "pCatId": "2" 81 | }, 82 | { 83 | "cName": "古武未来", 84 | "catId": "20099", 85 | "pCatId": "2" 86 | }, 87 | { 88 | "cName": "武侠同人", 89 | "catId": "20100", 90 | "pCatId": "2" 91 | }, 92 | { 93 | "catId": "22", 94 | "cName": "仙侠", 95 | "pCatId": "0" 96 | }, 97 | { 98 | "cName": "修真文明", 99 | "catId": "18", 100 | "pCatId": "22" 101 | }, 102 | { 103 | "cName": "幻想修仙", 104 | "catId": "44", 105 | "pCatId": "22" 106 | }, 107 | { 108 | "cName": "现代修真", 109 | "catId": "64", 110 | "pCatId": "22" 111 | }, 112 | { 113 | "cName": "神话修真", 114 | "catId": "207", 115 | "pCatId": "22" 116 | }, 117 | { 118 | "cName": "古典仙侠", 119 | "catId": "20101", 120 | "pCatId": "22" 121 | }, 122 | { 123 | "catId": "4", 124 | "cName": "都市", 125 | "pCatId": "0" 126 | }, 127 | { 128 | "cName": "爱情婚姻", 129 | "catId": "6", 130 | "pCatId": "4" 131 | }, 132 | { 133 | "cName": "都市生活", 134 | "catId": "12", 135 | "pCatId": "4" 136 | }, 137 | { 138 | "cName": "都市异能", 139 | "catId": "16", 140 | "pCatId": "4" 141 | }, 142 | { 143 | "cName": "异术超能", 144 | "catId": "74", 145 | "pCatId": "4" 146 | }, 147 | { 148 | "cName": "青春校园", 149 | "catId": "130", 150 | "pCatId": "4" 151 | }, 152 | { 153 | "cName": "娱乐明星", 154 | "catId": "151", 155 | "pCatId": "4" 156 | }, 157 | { 158 | "cName": "商战职场", 159 | "catId": "153", 160 | "pCatId": "4" 161 | }, 162 | { 163 | "catId": "15", 164 | "cName": "现实", 165 | "pCatId": "0" 166 | }, 167 | { 168 | "cName": "社会乡土", 169 | "catId": "153", 170 | "pCatId": "15" 171 | }, 172 | { 173 | "cName": "生活时尚", 174 | "catId": "153", 175 | "pCatId": "15" 176 | }, 177 | { 178 | "cName": "文学艺术", 179 | "catId": "153", 180 | "pCatId": "15" 181 | }, 182 | { 183 | "cName": "成功励志", 184 | "catId": "153", 185 | "pCatId": "15" 186 | }, 187 | { 188 | "cName": "青春文学", 189 | "catId": "153", 190 | "pCatId": "15" 191 | }, 192 | { 193 | "cName": "爱情婚姻", 194 | "catId": "153", 195 | "pCatId": "15" 196 | }, 197 | { 198 | "cName": "现实百态", 199 | "catId": "153", 200 | "pCatId": "15" 201 | }, 202 | { 203 | "catId": "6", 204 | "cName": "军事", 205 | "pCatId": "0" 206 | }, 207 | { 208 | "cName": "军旅生涯", 209 | "catId": "54", 210 | "pCatId": "6" 211 | }, 212 | { 213 | "cName": "军事战争", 214 | "catId": "65", 215 | "pCatId": "6" 216 | }, 217 | { 218 | "cName": "战争幻想", 219 | "catId": "80", 220 | "pCatId": "6" 221 | }, 222 | { 223 | "cName": "抗战烽火", 224 | "catId": "230", 225 | "pCatId": "6" 226 | }, 227 | { 228 | "cName": "谍战特工", 229 | "catId": "231", 230 | "pCatId": "6" 231 | }, 232 | { 233 | "catId": "5", 234 | "cName": "历史", 235 | "pCatId": "0" 236 | }, 237 | { 238 | "cName": "架空历史", 239 | "catId": "22", 240 | "pCatId": "5" 241 | }, 242 | { 243 | "cName": "秦汉三国", 244 | "catId": "48", 245 | "pCatId": "5" 246 | }, 247 | { 248 | "cName": "上古先秦", 249 | "catId": "220", 250 | "pCatId": "5" 251 | }, 252 | { 253 | "cName": "历史传记", 254 | "catId": "32", 255 | "pCatId": "5" 256 | }, 257 | { 258 | "cName": "两晋隋唐", 259 | "catId": "222", 260 | "pCatId": "5" 261 | }, 262 | { 263 | "cName": "五代十国", 264 | "catId": "223", 265 | "pCatId": "5" 266 | }, 267 | { 268 | "cName": "两宋元明", 269 | "catId": "224", 270 | "pCatId": "5" 271 | }, 272 | { 273 | "cName": "清史民国", 274 | "catId": "225", 275 | "pCatId": "5" 276 | }, 277 | { 278 | "cName": "外国历史", 279 | "catId": "226", 280 | "pCatId": "5" 281 | }, 282 | { 283 | "cName": "民间传说", 284 | "catId": "20094", 285 | "pCatId": "5" 286 | }, 287 | { 288 | "catId": "7", 289 | "cName": "游戏", 290 | "pCatId": "0" 291 | }, 292 | { 293 | "cName": "电子竞技", 294 | "catId": "7", 295 | "pCatId": "7" 296 | }, 297 | { 298 | "cName": "虚拟网游", 299 | "catId": "70", 300 | "pCatId": "7" 301 | }, 302 | { 303 | "cName": "游戏异界", 304 | "catId": "240", 305 | "pCatId": "7" 306 | }, 307 | { 308 | "cName": "游戏系统", 309 | "catId": "20102", 310 | "pCatId": "7" 311 | }, 312 | { 313 | "cName": "游戏主播", 314 | "catId": "20103", 315 | "pCatId": "7" 316 | }, 317 | { 318 | "catId": "8", 319 | "cName": "体育", 320 | "pCatId": "0" 321 | }, 322 | { 323 | "cName": "篮球运动", 324 | "catId": "28", 325 | "pCatId": "8" 326 | }, 327 | { 328 | "cName": "体育赛事", 329 | "catId": "55", 330 | "pCatId": "8" 331 | }, 332 | { 333 | "cName": "足球运动", 334 | "catId": "82", 335 | "pCatId": "8" 336 | }, 337 | { 338 | "catId": "9", 339 | "cName": "科幻", 340 | "pCatId": "0" 341 | }, 342 | { 343 | "cName": "古武机甲", 344 | "catId": "21", 345 | "pCatId": "9" 346 | }, 347 | { 348 | "cName": "未来世界", 349 | "catId": "25", 350 | "pCatId": "9" 351 | }, 352 | { 353 | "cName": "星际文明", 354 | "catId": "68", 355 | "pCatId": "9" 356 | }, 357 | { 358 | "cName": "超级科技", 359 | "catId": "250", 360 | "pCatId": "9" 361 | }, 362 | { 363 | "cName": "时空穿梭", 364 | "catId": "251", 365 | "pCatId": "9" 366 | }, 367 | { 368 | "cName": "进化变异", 369 | "catId": "252", 370 | "pCatId": "9" 371 | }, 372 | { 373 | "cName": "末世危机", 374 | "catId": "253", 375 | "pCatId": "9" 376 | }, 377 | { 378 | "catId": "10", 379 | "cName": "悬疑", 380 | "pCatId": "0" 381 | }, 382 | { 383 | "cName": "诡秘悬疑", 384 | "catId": "26", 385 | "pCatId": "10" 386 | }, 387 | { 388 | "cName": "奇妙世界", 389 | "catId": "35", 390 | "pCatId": "10" 391 | }, 392 | { 393 | "cName": "侦探推理", 394 | "catId": "57", 395 | "pCatId": "10" 396 | }, 397 | { 398 | "cName": "探险生存", 399 | "catId": "260", 400 | "pCatId": "10" 401 | }, 402 | { 403 | "cName": "古今传奇", 404 | "catId": "20095", 405 | "pCatId": "10" 406 | }, 407 | { 408 | "catId": "12", 409 | "cName": "轻小说", 410 | "pCatId": "0" 411 | }, 412 | { 413 | "cName": "原生幻想", 414 | "catId": "60", 415 | "pCatId": "12" 416 | }, 417 | { 418 | "cName": "现代幻想", 419 | "catId": "10", 420 | "pCatId": "12" 421 | }, 422 | { 423 | "cName": "衍生同人", 424 | "catId": "281", 425 | "pCatId": "12" 426 | }, 427 | { 428 | "cName": "搞笑吐槽", 429 | "catId": "282", 430 | "pCatId": "12" 431 | }, 432 | { 433 | "cName": "青春日常", 434 | "catId": "66", 435 | "pCatId": "12" 436 | }, 437 | { 438 | "catId": "20076", 439 | "cName": "短篇", 440 | "pCatId": "0" 441 | }, 442 | { 443 | "cName": "诗歌散文", 444 | "catId": "20097", 445 | "pCatId": "20076" 446 | }, 447 | { 448 | "cName": "人物传记", 449 | "catId": "20098", 450 | "pCatId": "20076" 451 | }, 452 | { 453 | "cName": "影视剧本", 454 | "catId": "20075", 455 | "pCatId": "20076" 456 | }, 457 | { 458 | "cName": "评论文集", 459 | "catId": "20077", 460 | "pCatId": "20076" 461 | }, 462 | { 463 | "cName": "生活随笔", 464 | "catId": "20078", 465 | "pCatId": "20076" 466 | }, 467 | { 468 | "cName": "美文游记", 469 | "catId": "20079", 470 | "pCatId": "20076" 471 | }, 472 | { 473 | "cName": "短篇小说", 474 | "catId": "20096", 475 | "pCatId": "20076" 476 | } 477 | ] -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 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 | "@types/crypto-js": "^3.1.46", 12 | "@types/js-cookie": "^2.2.6", 13 | "axios": "^0.19.2", 14 | "core-js": "^3.6.4", 15 | "crypto-js": "^4.0.0", 16 | "js-cookie": "^2.2.1", 17 | "normalize.css": "^8.0.1", 18 | "vant": "^2.6.3", 19 | "vue": "^2.6.11", 20 | "vue-class-component": "^7.2.3", 21 | "vue-lazyload": "^1.3.3", 22 | "vue-page-stack": "^1.4.1", 23 | "vue-property-decorator": "^8.4.1", 24 | "vue-router": "^3.1.6", 25 | "vuex": "^3.1.3" 26 | }, 27 | "devDependencies": { 28 | "@typescript-eslint/eslint-plugin": "^2.26.0", 29 | "@typescript-eslint/parser": "^2.26.0", 30 | "@vue/cli-plugin-babel": "~4.3.0", 31 | "@vue/cli-plugin-eslint": "~4.3.0", 32 | "@vue/cli-plugin-router": "~4.3.0", 33 | "@vue/cli-plugin-typescript": "~4.3.0", 34 | "@vue/cli-plugin-vuex": "~4.3.0", 35 | "@vue/cli-service": "~4.3.0", 36 | "@vue/eslint-config-prettier": "^6.0.0", 37 | "@vue/eslint-config-typescript": "^5.0.2", 38 | "babel-eslint": "^10.1.0", 39 | "compression-webpack-plugin": "^4.0.0", 40 | "eslint": "^6.7.2", 41 | "eslint-plugin-prettier": "^3.1.1", 42 | "eslint-plugin-vue": "^6.2.2", 43 | "postcss-pxtorem": "^5.1.1", 44 | "prettier": "^1.19.1", 45 | "sass": "^1.26.3", 46 | "sass-loader": "^8.0.2", 47 | "typescript": "~3.8.3", 48 | "vue-template-compiler": "^2.6.11" 49 | }, 50 | "eslintConfig": { 51 | "root": true, 52 | "env": { 53 | "node": true 54 | }, 55 | "extends": [ 56 | "@vue/typescript" 57 | ], 58 | "parserOptions": { 59 | "parser": "@typescript-eslint/parser" 60 | }, 61 | "rules": { 62 | "generator-star-spacing": "off", 63 | "no-tabs": "off", 64 | "no-unused-vars": "off", 65 | "no-console": "off", 66 | "no-irregular-whitespace": "off", 67 | "no-debugger": "off" 68 | } 69 | }, 70 | "browserslist": [ 71 | "> 1%", 72 | "last 2 versions", 73 | "not dead" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { 3 | plugins: { 4 | "postcss-pxtorem": { 5 | rootValue: 100, //结果为:设计稿元素尺寸/75,设计稿宽 750,最终页面会换算成 7.5rem 6 | // unitPrecision: 5, // 允许REM单位增长的十进制数 7 | propList: ["*"], 8 | selectorBlackList: ["html"], //(数组)要忽略的选择器并保留为px。 9 | minPixelValue: 2 // (数字)设置要替换的最小像素值 解决 1px 问题 10 | } 11 | } 12 | }; -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tmfree/vue-fiction/fc92e026d5017b02ce9bc559d299950c48f4dfc0/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const bodyParser = require('body-parser') 4 | const app = express(); 5 | const router = require('./router/index') 6 | const userToken = require('./utils/userToken') 7 | //const cache = require('apicache').middleware 8 | //const crawRouter = require('./router/craw') 9 | const { connect } = require('./utils/connect') 10 | const { graphqlHTTP } = require('express-graphql') 11 | const graphschema = require('./graphql/schema') 12 | //链接数据库 13 | connect() 14 | //跨域 15 | app.use((req, res, next) => { 16 | res.set({ 17 | 'Access-Control-Allow-Credentials': true, 18 | 'Access-Control-Allow-Origin': req.headers.origin || '*', 19 | 'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type,Authorization', 20 | 'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS' 21 | }) 22 | req.method === 'OPTIONS' ? res.status(204).end() : next() 23 | }) 24 | //静态资源 25 | app.use('/static', express.static(path.join(__dirname, 'static'))) 26 | 27 | // body parser 28 | app.use(bodyParser.json()) 29 | app.use(bodyParser.urlencoded({ extended: false })) 30 | 31 | //graphql 32 | app.use('/graphql', graphqlHTTP({ 33 | schema: graphschema, 34 | graphiql: true 35 | })) 36 | // cache 37 | //app.use(cache('3 minutes', ((req, res) => res.statusCode === 200))) 38 | 39 | //拦截需要token验证的路由 40 | app.use(async (req, res, next) => { 41 | let token = req.headers.authorization 42 | let code = await userToken.verifyToken(token) 43 | req.query.tokenCode = code 44 | req.query.token = token 45 | next() 46 | }) 47 | //app.use('/craw', crawRouter) 48 | app.use('/api', router) 49 | 50 | const port = process.env.PORT || 3000 51 | const host = process.env.HOST || '' 52 | 53 | app.server = app.listen(port, host, () => { 54 | console.log(`server running @ http://${host ? host : 'localhost'}:${port}`) 55 | }) 56 | -------------------------------------------------------------------------------- /server/cheerio/article.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | function resolveArticle(str) { 3 | let data = {}; 4 | let $ = cheerio.load(str, { decodeEntities: false }); 5 | data.title = $('article#chapterContent h3').text(); 6 | data.content = []; 7 | $('article#chapterContent p').each(function (i, el) { 8 | let temp = { 9 | field: $(this).text().trim(), 10 | serialNum: i 11 | } 12 | data.content.push(temp) 13 | }) 14 | return data; 15 | } 16 | module.exports = resolveArticle; -------------------------------------------------------------------------------- /server/cheerio/book.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | function resolveBook(str) { 3 | let data = {}; 4 | let $ = cheerio.load(str, { decodeEntities: false }); 5 | data ={ 6 | title: $('h2.book-title').text(), 7 | tag: $('p.book-meta').eq(0).text(), 8 | wordCount: parseFloat($('p.book-meta').eq(1).text()), 9 | summary: $('#bookSummary').find('content').text(), 10 | ticket:$('span.recomm-ticket-cnt').text(), 11 | rewardCount: $('span.reward-week-cnt').text(), 12 | starScore: $('span.star-score').attr('data-score') ? $('span.star-score').attr('data-score') : 0 13 | } 14 | return data; 15 | } 16 | module.exports = resolveBook; -------------------------------------------------------------------------------- /server/cheerio/catalog.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | function resolveCatalog(str) { 3 | let data = {}; 4 | data.sub=[]; 5 | let $ = cheerio.load(str, { decodeEntities: false }); 6 | let total = $('h4.chapter-sub-title').find('output').text(); 7 | let strData = $('script')[10].children[0].data.split('g_data.volumes = ')[1].trim(); 8 | data.catalog = JSON.parse(strData.substr(0, strData.length - 1)); 9 | data.total = total; 10 | return data; 11 | } 12 | module.exports = resolveCatalog; -------------------------------------------------------------------------------- /server/cheerio/category.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | function resolveCategory(str) { 3 | let data = []; 4 | let $ = cheerio.load(str, { decodeEntities: false }); 5 | $('ul.sort-ul').find('li').map(function (i, el) { 6 | let catList = []; 7 | $(this).find('div.sort-li-detail a').each(function (i, el) { 8 | let sub = { 9 | text: $(this).text(), 10 | subCatId: $(this).attr("href").split('&')[1].split('=')[1] 11 | } 12 | catList.push(sub); 13 | }) 14 | let temp = { 15 | catId: $(this).find('a.sort-li-header').attr('href').split('/')[2], 16 | title: $(this).find('h3.module-title').text(), 17 | total: $(this).find("output").text(), 18 | catList 19 | } 20 | data.push(temp); 21 | }); 22 | return data; 23 | } 24 | module.exports = resolveCategory; -------------------------------------------------------------------------------- /server/cheerio/home.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | 3 | function resolveLunBo(str) { 4 | let data = []; 5 | let $ = cheerio.load(str, { decodeEntities: false }); 6 | let len = $('ul#slideUl').find('a').map(function (i, el) { 7 | let sub = $(this).attr('href').split('/'); 8 | let temp = { 9 | bid: sub[sub.length-1], 10 | imgUrl: i == 0 ? $(this).find('img').attr('src') : $(this).find('img').attr('data-src') 11 | } 12 | data.push(temp) 13 | }); 14 | 15 | return data; 16 | } 17 | //热门小说 18 | function resolveHotFiction(str) { 19 | let data = []; 20 | let $ = cheerio.load(str, { decodeEntities: false }); 21 | let list = $('div.module').eq(0).find("div.module-slide a"); 22 | list.map(function (i, el) { 23 | let temp = { 24 | bid: $(this).attr('data-bid'), 25 | uid: $(this).attr('data-auid'), 26 | bName: $(this).find('.module-slide-caption').text() 27 | } 28 | data.push(temp); 29 | }); 30 | return data; 31 | } 32 | //首页免费小说 33 | function resolveFreeFiction(str) { 34 | let data = []; 35 | let $ = cheerio.load(str, { decodeEntities: false }); 36 | let list = $('div.module').eq(1).find("div.module-slide a"); 37 | list.map(function (i, el) { 38 | let temp = { 39 | bid: $(this).attr('data-bid'), 40 | uid: $(this).attr('data-auid'), 41 | bName: $(this).find('.module-slide-caption').text() 42 | } 43 | data.push(temp); 44 | }); 45 | return data; 46 | } 47 | 48 | //首页新书抢鲜 49 | function resolveNewFiction(str) { 50 | let data = []; 51 | let $ = cheerio.load(str, { decodeEntities: false }); 52 | let list = $('div.module').eq(3).find("ol.book-ol a"); 53 | list.map(function (i, el) { 54 | let tags = []; 55 | $(this).find(".tag-small").each(function (i, el) { 56 | tags.push($(this).text()) 57 | }) 58 | let temp = { 59 | bid: $(this).attr('data-bid'), 60 | uid: $(this).attr('data-auid'), 61 | bName: $(this).find('.book-title').text(), 62 | desc: $(this).find('.book-desc').text(), 63 | author: $(this).find('.book-author').text(), 64 | tags 65 | } 66 | data.push(temp); 67 | }); 68 | return data; 69 | } 70 | //轻小说 71 | function resolveQingFiction(str) { 72 | let data = []; 73 | let $ = cheerio.load(str, { decodeEntities: false }); 74 | let list = $('div.module').eq(6).find("div.module-slide a"); 75 | list.map(function (i, el) { 76 | let temp = { 77 | bid: $(this).attr('data-bid'), 78 | uid: $(this).attr('data-auid'), 79 | title: $(this).find('.module-slide-caption').text() 80 | } 81 | data.push(temp); 82 | }); 83 | return data; 84 | } 85 | module.exports = { 86 | resolveLunBo, 87 | resolveHotFiction, 88 | resolveFreeFiction, 89 | resolveNewFiction, 90 | resolveQingFiction 91 | }; -------------------------------------------------------------------------------- /server/cheerio/index.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio') 2 | class CheerioData { 3 | resolveCategory(str) { 4 | try { 5 | let data = [] 6 | let $ = cheerio.load(str, { decodeEntities: false }) 7 | $('ul.sort-ul').find('li').map(function (i, el) { 8 | let tags = [] 9 | $(this).find('div.sort-li-detail a').each(function (i, el) { 10 | let sub = { 11 | cName: $(this).text(), 12 | tagId: $(this).attr("href").split('&')[1].split('=')[1], 13 | } 14 | tags.push(sub) 15 | }) 16 | let temp = { 17 | catId: $(this).find('a.sort-li-header').attr('href').split('/')[2], 18 | cName: $(this).find('h3.module-title').text(), 19 | total:$(this).find('output').text(), 20 | tags 21 | } 22 | data.push(temp) 23 | }); 24 | return data; 25 | } catch (error) { 26 | console.log(error) 27 | } 28 | } 29 | 30 | resolveBook(str) { 31 | let data = {}; 32 | let $ = cheerio.load(str, { decodeEntities: false }); 33 | data = { 34 | title: $('h2.book-title').text(), 35 | tag: $('p.book-meta').eq(0).text(), 36 | wordCount: $('p.book-meta').eq(1).text().split('字')[0], 37 | summary: $('#bookSummary').find('content').text(), 38 | ticket: $('span.recomm-ticket-cnt').text(), 39 | fansNum: $('span#ariaFansNum').text().split(':')[1], 40 | rewardCount: $('span.reward-week-cnt').text(), 41 | starScore: $('span.star-score').attr('data-score') ? $('span.star-score').attr('data-score') : 0 42 | } 43 | return data; 44 | } 45 | 46 | resolveCatalog(str) { 47 | 48 | let data = {}; 49 | data.sub=[]; 50 | let $ = cheerio.load(str, { decodeEntities: false }); 51 | let total = $('h4.chapter-sub-title').find('output').text(); 52 | let strData = $('script')[11].children[0].data.split('g_data.volumes = ')[1].trim(); 53 | data.catalog = JSON.parse(strData.substr(0, strData.length - 1)); 54 | data.cTotal = total; 55 | 56 | return data; 57 | } 58 | } 59 | module.exports = new CheerioData() -------------------------------------------------------------------------------- /server/cheerio/rank.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | function resolveRank(str) { 3 | let data = []; 4 | let $ = cheerio.load(str, { decodeEntities: false }); 5 | $("div.module-toplist").each(function (i, el) { 6 | let temp = { 7 | type: $(this).find('a.book-toplist').attr('href').split('/')[2], 8 | title:$(this).find('h2.book-toplist-title').text(), 9 | coverImg: $(this).find('img.book-toplist-cover').attr('src') 10 | } 11 | temp.topList = []; 12 | $(this).find('a.book-layout').each(function () { 13 | let sub = { 14 | bid: $(this).attr('data-bid'), 15 | cName: $(this).find('h3').text() 16 | }; 17 | temp.topList.push(sub); 18 | }) 19 | data.push(temp); 20 | }) 21 | return data; 22 | } 23 | module.exports = resolveRank; -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl":"https://m.qidian.com", 3 | "categoryApi":"/category/male", 4 | "bookApi":"/book", 5 | "catalogApi":"/catalog", 6 | "secretOrKey":"tmfree" 7 | } -------------------------------------------------------------------------------- /server/controler/bookControler.js: -------------------------------------------------------------------------------- 1 | const ajax = require('../request/api') 2 | const BookModel = require('../model/book') 3 | const NewBookModel = require('../model/newbook') 4 | const FreeBookModel = require('../model/freebook') 5 | const NewAuthorModel = require('../model/rnewauthorbook') 6 | const HotSaleBookModel = require('../model/hotsalebook') 7 | const YuePiaoModel = require('../model/yuepiao') 8 | const BookDetailModel = require('../model/bookdetail') 9 | const CatModel = require('../model/category') 10 | const cheerioData = require('../cheerio') 11 | const { param } = require('../router') 12 | class BookControler { 13 | async getBook(params) { 14 | let { catId, page } = params 15 | try { 16 | let tags = (await CatModel.find({ catId }))[0].tags 17 | for (let item of tags) { 18 | params.tagId = item.tagId 19 | let result = await ajax.getBook(params) 20 | await BookModel.insertMany(result, { ordered: false }) 21 | console.log(`${catId} - ${item.tagId}导入数据成功`) 22 | } 23 | console.log(`${catId} - ${page}页成功`) 24 | return `${catId} - ${page}页成功` 25 | } catch (error) { 26 | console.log(error) 27 | } 28 | } 29 | //小说列表 30 | async getBookList(params) { 31 | let { type, page } = params 32 | let ps = 20 33 | let sk = (page - 1) * ps 34 | let result = [] 35 | switch (type) { 36 | case '1': 37 | result = await FreeBookModel.find({}).skip(sk).limit(ps) 38 | return result 39 | case '2': 40 | result = await NewBookModel.find({}).skip(sk).limit(ps) 41 | return result 42 | case '3': 43 | result = await HotSaleBookModel.find({}).skip(sk).limit(ps) 44 | return result 45 | case '4': 46 | result = await BookModel.find({ 'catId': '12' }).skip(sk).limit(ps) 47 | return result 48 | } 49 | } 50 | async getCategoryList(params) { 51 | try { 52 | let { catId, tagId, page } = params 53 | let ps = 20 54 | let sk = (page - 1) * ps 55 | let result = await BookModel.find({ catId, tagId }).skip(sk).limit(ps) 56 | return result 57 | } catch (error) { 58 | console.log(error) 59 | } 60 | } 61 | async getNewBook(params) { 62 | let { page } = params 63 | try { 64 | let result = await ajax.getNewBook(params) 65 | await NewBookModel.insertMany(result, { ordered: false }) 66 | console.log(`${page}导入数据成功`) 67 | return `${page}页成功` 68 | } catch (error) { 69 | console.log(error) 70 | } 71 | } 72 | async getFreeBook(params) { 73 | let { page } = params 74 | try { 75 | let result = await ajax.getFreeBook(params) 76 | await FreeBookModel.insertMany(result, { ordered: false }) 77 | console.log(`${page}导入数据成功`) 78 | return `${page}页成功` 79 | } catch (error) { 80 | console.log(error) 81 | } 82 | } 83 | async getHotSaleBook(params) { 84 | let { page } = params 85 | try { 86 | let result = await ajax.getHotSaleBook(params) 87 | await HotSaleBookModel.insertMany(result, { ordered: false }) 88 | console.log(`${page}导入数据成功`) 89 | return `${page}页成功` 90 | } catch (error) { 91 | console.log(error) 92 | } 93 | } 94 | async getNewAuthorBook(params) { 95 | let { page } = params 96 | try { 97 | for (let i = 3; i <= 30; i++) { 98 | params.page = i 99 | let result = await ajax.getNewAuthorBook(params) 100 | await NewAuthorModel.insertMany(result, { ordered: false }) 101 | console.log(`${i}导入数据成功`) 102 | } 103 | return `${i}页成功` 104 | } catch (error) { 105 | console.log(error) 106 | } 107 | } 108 | //详情 109 | async getBookDetail(params) { 110 | try { 111 | let { bid } = params 112 | let result = (await BookDetailModel.find({ bid }))[0] 113 | return result 114 | } catch (error) { 115 | console.log(error) 116 | } 117 | } 118 | // async getBookDetail(params) { 119 | // try { 120 | // let sk = (params.page - 1) * params.ps 121 | // //let bookList = await BookModel.find({}).skip(sk).limit(params.ps) 122 | // let bookList = await NewAuthorModel.find({}).skip(0) 123 | // let bookInfo = {} 124 | // for (let item of bookList) { 125 | // let r = await BookDetailModel.find({ 'bid': item.bid }) 126 | // console.log(item.bid) 127 | // if (r.length >= 1) { 128 | // continue; 129 | // } 130 | // params.bid = item.bid 131 | // let detailHtml = await ajax.getBookDetail(params) 132 | // let catalogHtml = await ajax.getCatalog(params) 133 | // let bookData = cheerioData.resolveBook(detailHtml) 134 | // let catalogData = cheerioData.resolveCatalog(catalogHtml) 135 | // bookInfo = { bid: item.bid, ...bookData, ...catalogData } 136 | // await BookDetailModel.insertMany(bookInfo, { ordered: false }) 137 | // console.log(`${item.bName} - 数据导入成功`) 138 | // } 139 | // console.log(`${params.page} 数据导入成功`) 140 | // return bookList 141 | // } catch (error) { 142 | // console.log(error) 143 | 144 | // } 145 | // } 146 | //搜索 147 | async searchBook(params = {}) { 148 | try { 149 | let { kw,page } = params 150 | let regexp = new RegExp(kw) 151 | let ps = 20 152 | let sk = (page - 1) * ps 153 | let result = await BookModel.find({ bName: { $regex: regexp } }).skip(sk).limit(ps) 154 | return result 155 | } catch (error) { 156 | console.log(error) 157 | } 158 | } 159 | } 160 | module.exports = new BookControler() -------------------------------------------------------------------------------- /server/controler/commControler.js: -------------------------------------------------------------------------------- 1 | const CateModel = require('../model/category') 2 | class CommControler { 3 | async getCategory(params = {}) { 4 | try { 5 | let result = await CateModel.find({}) 6 | return result 7 | } catch (error) { 8 | console.log(error) 9 | } 10 | } 11 | } 12 | module.exports = new CommControler() -------------------------------------------------------------------------------- /server/controler/rankControler.js: -------------------------------------------------------------------------------- 1 | const RankModel = require('../model/rank') 2 | const YuePiaoModel = require('../model/yuepiao') 3 | const HotSaleModel = require('../model/rhotsalebook') 4 | const NewBookModel = require('../model/rnewbook') 5 | const NewFansModel = require('../model/rnewfansbook') 6 | const RrcommendModel = require('../model/rrecommend') 7 | const SignBookModel = require('../model/rsignbook') 8 | const NewAuthorModel = require('../model/rnewauthorbook') 9 | class RankControler { 10 | async getRank(params = {}) { 11 | try { 12 | let result = await RankModel.find({}) 13 | return result 14 | } catch (error) { 15 | console.log(error) 16 | } 17 | } 18 | async getRankList(params = {}) { 19 | try { 20 | let { type, catId, page } = params 21 | let ps = 20 22 | let sk = (page - 1) * ps 23 | let result = [] 24 | let q = {} 25 | if (catId != '-1') { 26 | q = { catId } 27 | } 28 | switch (type) { 29 | case 'yuepiao': 30 | result = await YuePiaoModel.find(q).skip(sk).limit(ps) 31 | return result 32 | case 'hotsales': 33 | result = await HotSaleModel.find(q).skip(sk).limit(ps) 34 | return result 35 | case 'readIndex': 36 | result = await NewBookModel.find(q).skip(sk).limit(ps) 37 | return result 38 | case 'newfans': 39 | result = await NewFansModel.find(q).skip(sk).limit(ps) 40 | return result 41 | case 'rec': 42 | result = await RrcommendModel.find(q).skip(sk).limit(ps) 43 | return result 44 | case 'reward': 45 | result = await YuePiaoModel.find(q).skip(sk).limit(ps) 46 | return result 47 | case 'update': 48 | result = await NewBookModel.find(q).skip(sk).limit(ps) 49 | return result 50 | case 'sign': 51 | result = await SignBookModel.find(q).skip(sk).limit(ps) 52 | return result 53 | case 'newbook': 54 | result = await NewBookModel.find(q).skip(sk).limit(ps) 55 | return result 56 | case 'newauthor': 57 | result = await NewAuthorModel.find(q).skip(sk).limit(ps) 58 | return result 59 | } 60 | } catch (error) { 61 | 62 | } 63 | } 64 | } 65 | module.exports = new RankControler() -------------------------------------------------------------------------------- /server/controler/userControler.js: -------------------------------------------------------------------------------- 1 | const UserModel = require('../model/user') 2 | const UserInfoModel = require('../model/userinfo') 3 | const CollcetModel = require('../model/collect') 4 | const LikeModel = require('../model/like') 5 | class UserControler { 6 | async getUser(params) { 7 | try { 8 | let { account } = params 9 | let result = (await UserModel.find({ 'uAccount': account }))[0] 10 | return result ? result : {} 11 | } catch (error) { 12 | console.log(error) 13 | } 14 | } 15 | async getUserInfo(params) { 16 | try { 17 | let { uId } = params 18 | let result = (await UserInfoModel.find({ uId }))[0] 19 | return result 20 | } catch (error) { 21 | console.log(error) 22 | } 23 | } 24 | async getCollectList(params) { 25 | try { 26 | let { uId, bid } = params 27 | let q = { uId } 28 | bid && (q.bid = bid) 29 | let result = await CollcetModel.find(q) 30 | return result 31 | } catch (error) { 32 | console.log(error) 33 | } 34 | } 35 | async addCollect(params) { 36 | try { 37 | let { uId, bid, bName } = params 38 | await CollcetModel.insertMany({ uId, bid, bName }) 39 | } catch (error) { 40 | console.log(error) 41 | } 42 | } 43 | async delCollect(params) { 44 | try { 45 | let { uId, bid } = params 46 | await CollcetModel.remove({ uId, bid }) 47 | } catch (error) { 48 | console.log(error) 49 | } 50 | } 51 | async getLikeList(params) { 52 | try { 53 | let { uId, bid } = params 54 | let q = { uId } 55 | bid && (q.bid = bid) 56 | let result = await LikeModel.find(q) 57 | return result 58 | } catch (error) { 59 | console.log(error) 60 | } 61 | } 62 | async addLike(params) { 63 | try { 64 | let { uId, bid, bName } = params 65 | await LikeModel.insertMany({ uId, bid, bName }) 66 | } catch (error) { 67 | console.log(error) 68 | } 69 | } 70 | async delLike(params) { 71 | try { 72 | let { uId, bid } = params 73 | await LikeModel.remove({ uId, bid }) 74 | } catch (error) { 75 | console.log(error) 76 | } 77 | } 78 | } 79 | module.exports = new UserControler() -------------------------------------------------------------------------------- /server/graphql/schema.js: -------------------------------------------------------------------------------- 1 | const { 2 | GraphQLObjectType, 3 | GraphQLInt, 4 | GraphQLString, 5 | GraphQLBoolean, 6 | GraphQLList, 7 | GraphQLSchema } = require('graphql') 8 | const BookModel = require('../model/book') 9 | const NewBookModel = require('../model/newbook') 10 | const HotSaleModel = require('../model/hotsalebook') 11 | const FreeBookModel = require('../model/freebook') 12 | const HHotType = new GraphQLObjectType({ 13 | name: "HHotFiction", 14 | fields: () => ({ 15 | bid: { type: GraphQLString }, 16 | bName: { type: GraphQLString }, 17 | bAuth: { type: GraphQLString }, 18 | desc: { type: GraphQLString }, 19 | cat: { type: GraphQLString }, 20 | catId: { type: GraphQLString }, 21 | cnt: { type: GraphQLString }, 22 | rankCnt: { type: GraphQLString }, 23 | rankNum:{ type: GraphQLString }, 24 | state:{type: GraphQLString} 25 | }) 26 | }); 27 | 28 | const RootQuery = new GraphQLObjectType({ 29 | name: "RootQueryType", 30 | fields: { 31 | hotfiction: { 32 | type: new GraphQLList(HHotType), 33 | resolve(parent, args) { 34 | return YPModel.find({}).skip(0).limit(7) 35 | } 36 | }, 37 | bestsellfiction: { 38 | type: new GraphQLList(HHotType), 39 | resolve(parent, args) { 40 | return HotSaleModel.find({}).skip(0).limit(3) 41 | } 42 | }, 43 | freefiction:{ 44 | type: new GraphQLList(HHotType), 45 | resolve(parent, args) { 46 | return FreeBookModel.find({}).skip(0).limit(7) 47 | } 48 | }, 49 | newfiction:{ 50 | type: new GraphQLList(HHotType), 51 | resolve(parent, args) { 52 | return NewBookModel.find({}).skip(0).limit(3) 53 | } 54 | }, 55 | qingfiction:{ 56 | type: new GraphQLList(HHotType), 57 | resolve(parent, args) { 58 | return BookModel.find({'catId':'12'}).skip(0).limit(7) 59 | } 60 | } 61 | } 62 | }) 63 | 64 | module.exports = new GraphQLSchema({ 65 | query: RootQuery 66 | }); -------------------------------------------------------------------------------- /server/model/book.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | tagId: String, 7 | bAuth: String, 8 | bName: String, 9 | cat: String, 10 | cid: String, 11 | cnt: String, 12 | desc: String, 13 | state: String 14 | }) 15 | module.exports = Mongoose.model('book', bookSchema) 16 | -------------------------------------------------------------------------------- /server/model/bookdetail.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | title: String, 6 | tag: String, 7 | wordCount: String, 8 | summary: String, 9 | ticket: String, 10 | rewardCount: String, 11 | starScore: String, 12 | fansNum: String, 13 | cTotal: String, 14 | catalog: Array 15 | }) 16 | module.exports = Mongoose.model('bookdetail', bookSchema) 17 | -------------------------------------------------------------------------------- /server/model/category.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let tagSchema = new Schema({ 4 | tagId: String, 5 | cName: String 6 | }) 7 | let categorySchema = new Schema({ 8 | catId: { type: String, unique: true }, 9 | cName: String, 10 | total: String, 11 | tags: [tagSchema] 12 | }) 13 | 14 | module.exports = Mongoose.model('Category', categorySchema) -------------------------------------------------------------------------------- /server/model/collect.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let collectSchema = new Schema({ 4 | bid: String, 5 | uId:String, 6 | bName: String 7 | }) 8 | module.exports = Mongoose.model('collect', collectSchema) 9 | -------------------------------------------------------------------------------- /server/model/freebook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | tagId: String, 7 | bAuth: String, 8 | bName: String, 9 | cat: String, 10 | cid: String, 11 | cnt: String, 12 | desc: String, 13 | state: String 14 | }) 15 | module.exports = Mongoose.model('freebook', bookSchema) 16 | -------------------------------------------------------------------------------- /server/model/hotsalebook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | tagId: String, 7 | bAuth: String, 8 | bName: String, 9 | cat: String, 10 | cid: String, 11 | cnt: String, 12 | desc: String, 13 | state: String 14 | }) 15 | module.exports = Mongoose.model('hotsalebook', bookSchema) 16 | -------------------------------------------------------------------------------- /server/model/like.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let likeSchema = new Schema({ 4 | bid: String, 5 | uId:String, 6 | bName: String 7 | }) 8 | module.exports = Mongoose.model('like', likeSchema) 9 | -------------------------------------------------------------------------------- /server/model/newbook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | tagId: String, 7 | bAuth: String, 8 | bName: String, 9 | cat: String, 10 | cid: String, 11 | cnt: String, 12 | desc: String, 13 | state: String 14 | }) 15 | module.exports = Mongoose.model('newbook', bookSchema) 16 | -------------------------------------------------------------------------------- /server/model/rank.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let rankSchema = new Schema({ 4 | coverImg:String, 5 | title:String, 6 | type:String, 7 | topList:Array 8 | }) 9 | module.exports = Mongoose.model('rank', rankSchema) 10 | -------------------------------------------------------------------------------- /server/model/rhotsalebook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('rhotsalebook', bookSchema) 15 | -------------------------------------------------------------------------------- /server/model/rnewauthorbook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('rnewauthorbook', bookSchema) 15 | -------------------------------------------------------------------------------- /server/model/rnewbook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('rnewbook', bookSchema) 15 | -------------------------------------------------------------------------------- /server/model/rnewfansbook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('rnewfansbook', bookSchema) 15 | -------------------------------------------------------------------------------- /server/model/rrecommend.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('rrecommend', bookSchema) 15 | -------------------------------------------------------------------------------- /server/model/rsignbook.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('rsignbook', bookSchema) 15 | -------------------------------------------------------------------------------- /server/model/user.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let userSchema = new Schema({ 4 | uId: { type: String, unique: true }, 5 | uAccount: { type: String, unique: true }, 6 | uPassword: String 7 | }) 8 | module.exports = Mongoose.model('user', userSchema) 9 | -------------------------------------------------------------------------------- /server/model/userinfo.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let userInfoSchema = new Schema({ 4 | uId: { type: String, unique: true }, 5 | nickName: String, 6 | gender: String, 7 | avatarUrl: String 8 | }, { timestamps: true }) 9 | module.exports = Mongoose.model('userinfo', userInfoSchema) 10 | -------------------------------------------------------------------------------- /server/model/yuepiao copy.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('ryuepiao', bookSchema) 15 | -------------------------------------------------------------------------------- /server/model/yuepiao.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose') 2 | let Schema = Mongoose.Schema 3 | let bookSchema = new Schema({ 4 | bid: { type: String, unique: true }, 5 | catId: String, 6 | bAuth: String, 7 | bName: String, 8 | cat: String, 9 | cnt: String, 10 | desc: String, 11 | rankCnt: String, 12 | rankNum: String 13 | }) 14 | module.exports = Mongoose.model('ryuepiao', bookSchema) 15 | -------------------------------------------------------------------------------- /server/module/article.js: -------------------------------------------------------------------------------- 1 | let getArticle = require("../request/getArticle"); 2 | function getArticleModule(data = {}) { 3 | return new Promise((resolve, reject) => { 4 | getArticle(data).then(res => { 5 | resolve(res) 6 | }) 7 | }) 8 | } 9 | 10 | module.exports = getArticleModule; -------------------------------------------------------------------------------- /server/module/book.js: -------------------------------------------------------------------------------- 1 | const BookCntl = require('../controler/bookControler') 2 | async function getBookModule(params = {}) { 3 | let { tokenCode } = params 4 | //已登录 5 | if (tokenCode == 0) { 6 | } 7 | let result = await BookCntl.getBookDetail(params) 8 | //未登录 9 | return result 10 | } 11 | 12 | module.exports = getBookModule -------------------------------------------------------------------------------- /server/module/category.js: -------------------------------------------------------------------------------- 1 | let CommCtrl= require('../controler/commControler') 2 | async function getCategoryModule(params = {}) { 3 | let result = await CommCtrl.getCategory(params); 4 | return result; 5 | } 6 | 7 | module.exports = getCategoryModule; -------------------------------------------------------------------------------- /server/module/category_detail.js: -------------------------------------------------------------------------------- 1 | let Bookctrl = require('../controler/bookControler') 2 | async function getCategoryDetailModule(params = {}) { 3 | let result = await Bookctrl.getCategoryList(params) 4 | return result 5 | } 6 | 7 | module.exports = getCategoryDetailModule -------------------------------------------------------------------------------- /server/module/collect_add.js: -------------------------------------------------------------------------------- 1 | //收藏 2 | const UserCtrl = require('../controler/userControler') 3 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 4 | async function getCollectList(params = {}) { 5 | try { 6 | let { tokenCode } = params 7 | if (tokenCode == 4001) { 8 | return new HttpError(tokenCode, '请登录') 9 | } else { 10 | await UserCtrl.addCollect(params) 11 | return new HttpSuccess('关注成功') 12 | } 13 | } catch (error) { 14 | console.log(error) 15 | } 16 | 17 | } 18 | module.exports = getCollectList -------------------------------------------------------------------------------- /server/module/collect_del.js: -------------------------------------------------------------------------------- 1 | //收藏 2 | const UserCtrl = require('../controler/userControler') 3 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 4 | async function getCollectList(params = {}) { 5 | try { 6 | let { tokenCode } = params 7 | if (tokenCode == 4001) { 8 | return new HttpError(tokenCode, '请登录') 9 | } else { 10 | await UserCtrl.delCollect(params) 11 | return new HttpSuccess('取消关注') 12 | } 13 | } catch (error) { 14 | console.log(error) 15 | } 16 | 17 | } 18 | module.exports = getCollectList -------------------------------------------------------------------------------- /server/module/collect_list.js: -------------------------------------------------------------------------------- 1 | //收藏 2 | const UserCtrl = require('../controler/userControler') 3 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 4 | async function getCollectList(params = {}) { 5 | try { 6 | let { tokenCode } = params 7 | if (tokenCode == 4001) { 8 | return new HttpSuccess('操作成功',[]) 9 | } else { 10 | let result = await UserCtrl.getCollectList(params) 11 | return new HttpSuccess('操作成功', result) 12 | } 13 | } catch (error) { 14 | console.log(error) 15 | } 16 | 17 | } 18 | module.exports = getCollectList -------------------------------------------------------------------------------- /server/module/getCollectList.js: -------------------------------------------------------------------------------- 1 | //获取收藏的文章 2 | const path = require('path') 3 | const dataIO = require('../utils/dataio') 4 | const filterResCode = require('../utils/filterResCode') 5 | async function getCollectBook(data = {}) { 6 | let { tokenCode } = data 7 | if (tokenCode !== 0) { 8 | return filterResCode(-1, '未登录', []) 9 | } 10 | //数据路径 11 | let collectFilePath = path.resolve(__dirname, '../mock/collect.json') 12 | let collectList = await dataIO.find(collectFilePath) 13 | return filterResCode(0, '成功', collectList) 14 | 15 | } 16 | module.exports = getCollectBook -------------------------------------------------------------------------------- /server/module/getLikeList.js: -------------------------------------------------------------------------------- 1 | //获取收藏的文章 2 | const path = require('path') 3 | const dataIO = require('../utils/dataio') 4 | const filterResCode = require('../utils/filterResCode') 5 | async function getLikeBook(data = {}) { 6 | let { tokenCode } = data 7 | if (tokenCode !== 0) { 8 | return filterResCode(-1, '未登录', []) 9 | } 10 | //数据路径 11 | let likeFilePath = path.resolve(__dirname, '../mock/like.json') 12 | let likeList = await dataIO.find(likeFilePath) 13 | return filterResCode(0, '成功', likeList) 14 | } 15 | module.exports = getLikeBook -------------------------------------------------------------------------------- /server/module/getUserInfo.js: -------------------------------------------------------------------------------- 1 | const UserCtrl = require('../controler/userControler') 2 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 3 | async function userInfo(params = {}) { 4 | try { 5 | let { tokenCode } = params 6 | if (tokenCode == 2000) { 7 | let result = await UserCtrl.getUserInfo(params) 8 | return new HttpSuccess('获取成功', result) 9 | } else { 10 | return new HttpSuccess('请登录') 11 | } 12 | } catch (error) { 13 | console.log(error) 14 | } 15 | } 16 | module.exports = userInfo -------------------------------------------------------------------------------- /server/module/getbooklist.js: -------------------------------------------------------------------------------- 1 | const BookCntl = require('../controler/bookControler') 2 | async function getbooklist(params){ 3 | return await BookCntl.getBookList(params) 4 | } 5 | module.exports = getbooklist -------------------------------------------------------------------------------- /server/module/like_add.js: -------------------------------------------------------------------------------- 1 | //收藏 2 | const UserCtrl = require('../controler/userControler') 3 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 4 | async function addList(params = {}) { 5 | try { 6 | let { tokenCode } = params 7 | if (tokenCode == 4001) { 8 | return new HttpError(tokenCode, '请登录') 9 | } else { 10 | await UserCtrl.addLike(params) 11 | return new HttpSuccess('关注成功') 12 | } 13 | } catch (error) { 14 | console.log(error) 15 | } 16 | 17 | } 18 | module.exports = addList -------------------------------------------------------------------------------- /server/module/like_del.js: -------------------------------------------------------------------------------- 1 | //收藏 2 | const UserCtrl = require('../controler/userControler') 3 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 4 | async function delLike(params = {}) { 5 | try { 6 | let { tokenCode } = params 7 | if (tokenCode == 4001) { 8 | return new HttpError(tokenCode, '请登录') 9 | } else { 10 | await UserCtrl.delLike(params) 11 | return new HttpSuccess('取消关注') 12 | } 13 | } catch (error) { 14 | console.log(error) 15 | } 16 | 17 | } 18 | module.exports = delLike -------------------------------------------------------------------------------- /server/module/like_list.js: -------------------------------------------------------------------------------- 1 | //喜欢 2 | const UserCtrl = require('../controler/userControler') 3 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 4 | async function getLikeList(params = {}) { 5 | try { 6 | let { tokenCode } = params 7 | if (tokenCode == 4001) { 8 | return new HttpSuccess('操作成功',[]) 9 | } else { 10 | let result = await UserCtrl.getLikeList(params) 11 | return new HttpSuccess('操作成功', result) 12 | } 13 | } catch (error) { 14 | console.log(error) 15 | } 16 | 17 | } 18 | module.exports = getLikeList -------------------------------------------------------------------------------- /server/module/login.js: -------------------------------------------------------------------------------- 1 | //登录 2 | const UserCtrl = require('../controler/userControler') 3 | const { HttpSuccess, HttpError } = require('../utils/httpwithjson') 4 | const userToken = require('../utils/userToken') 5 | async function login(params = {}) { 6 | try { 7 | let { account, password } = params 8 | let { uId, uAccount, uPassword } = await UserCtrl.getUser(params) 9 | // 判断用户名密码是否匹配 10 | let checkUser = account == uAccount && password == uPassword 11 | if (checkUser) { 12 | let payload = { uId, uAccount, uPassword } 13 | let token = userToken.addToken(payload, '1h') 14 | return new HttpSuccess('登录成功', { token, uId }) 15 | } else { 16 | return new HttpError(4000, '账号或密码错误') 17 | } 18 | } catch (error) { 19 | console.log(error) 20 | } 21 | } 22 | 23 | module.exports = login -------------------------------------------------------------------------------- /server/module/rank.js: -------------------------------------------------------------------------------- 1 | let RankCtrl = require('../controler/rankControler') 2 | async function getRankModule(params = {}) { 3 | let result = await RankCtrl.getRank(params); 4 | return result; 5 | } 6 | 7 | module.exports = getRankModule; -------------------------------------------------------------------------------- /server/module/rank_detail.js: -------------------------------------------------------------------------------- 1 | let RankCtrl = require('../controler/rankControler') 2 | async function getRankDetailModule(params = {}) { 3 | let result = await RankCtrl.getRankList(params) 4 | return result; 5 | } 6 | 7 | module.exports = getRankDetailModule; -------------------------------------------------------------------------------- /server/module/search.js: -------------------------------------------------------------------------------- 1 | let BCtrl = require('../controler/bookControler') 2 | const { param } = require('../router') 3 | async function getSearchModule(params = {}) { 4 | try { 5 | let result = await BCtrl.searchBook(params) 6 | return result 7 | } catch (error) { 8 | console.log(error) 9 | } 10 | 11 | } 12 | 13 | module.exports = getSearchModule -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "nodemon app.js" 8 | }, 9 | "dependencies": { 10 | "apicache": "^1.5.3", 11 | "axios": "^0.19.2", 12 | "body-parse": "^0.1.0", 13 | "cheerio": "^1.0.0-rc.3", 14 | "express": "^4.17.1", 15 | "express-graphql": "^0.12.0", 16 | "graphql": "^15.4.0", 17 | "jsonwebtoken": "^8.5.1", 18 | "mongoose": "^5.11.4", 19 | "nodemon": "^2.0.6", 20 | "uuid": "^8.3.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/request/api.js: -------------------------------------------------------------------------------- 1 | let http = require('./http') 2 | class Ajax { 3 | async getCategory(params) { 4 | try { 5 | let url = 'https://m.qidian.com/category/male' 6 | let result = await http({ url, params }) 7 | return result.data 8 | } catch (error) { 9 | console.log(error) 10 | } 11 | } 12 | async getBook(params) { 13 | try { 14 | let url = `https://m.qidian.com/majax/category/list?_csrfToken=Yr2GT7a1rNukMCmZDhclBvJ9lJxsI4EUrKqTyTqx&gender=male&pageNum=${params.page}&pageSize=${params.pageSize}&catId=${params.catId}&subCatId=${params.tagId}` 15 | let result = await http({ url }) 16 | return result.data.data.records.map((item) => { 17 | item.tagId = params.tagId 18 | return item 19 | }) 20 | } catch (error) { 21 | 22 | } 23 | } 24 | async getNewBook(params) { 25 | try { 26 | let url = `https://m.qidian.com/majax/recommend/newBooklist?_csrfToken=Yr2GT7a1rNukMCmZDhclBvJ9lJxsI4EUrKqTyTqx&gender=male&pageNum=${params.page}` 27 | let result = await http({ url }) 28 | return result.data.data.records 29 | } catch (error) { 30 | 31 | } 32 | } 33 | async getFreeBook(params) { 34 | try { 35 | let url = `https://m.qidian.com/majax/recommend/hotFreelist?_csrfToken=Yr2GT7a1rNukMCmZDhclBvJ9lJxsI4EUrKqTyTqx&gender=male&pageNum=${params.page}` 36 | let result = await http({ url }) 37 | return result.data.data.records 38 | } catch (error) { 39 | 40 | } 41 | } 42 | async getHotSaleBook(params) { 43 | try { 44 | let url = `https://m.qidian.com/majax/recommend/bestSelllist?_csrfToken=Yr2GT7a1rNukMCmZDhclBvJ9lJxsI4EUrKqTyTqx&gender=male&pageNum=${params.page}` 45 | let result = await http({ url }) 46 | return result.data.data.records 47 | } catch (error) { 48 | 49 | } 50 | } 51 | async getNewAuthorBook(params) { 52 | try { 53 | let url = `https://m.qidian.com/majax/rank/newauthorlist?_csrfToken=Yr2GT7a1rNukMCmZDhclBvJ9lJxsI4EUrKqTyTqx&gender=male&pageNum=${params.page}&catId=-1` 54 | let result = await http({ url }) 55 | return result.data.data.records 56 | } catch (error) { 57 | 58 | } 59 | } 60 | async getBookDetail(params) { 61 | try { 62 | let url = `https://m.qidian.com/book/${params.bid}` 63 | let result = await http({ url }) 64 | return result.data 65 | } catch (error) { 66 | 67 | } 68 | } 69 | async getCatalog(params) { 70 | try { 71 | let url = `https://m.qidian.com/book/${params.bid}/catalog` 72 | let result = await http({ url }) 73 | return result.data 74 | } catch (error) { 75 | 76 | } 77 | } 78 | async getRank(params) { 79 | let url = '/rank/male'; 80 | let result = await http({ 81 | method: 'get', 82 | url 83 | }) 84 | return result.data 85 | } 86 | } 87 | module.exports = new Ajax() -------------------------------------------------------------------------------- /server/request/getArticle.js: -------------------------------------------------------------------------------- 1 | let http = require('./http'); 2 | let resolveArticle = require('../cheerio/article') 3 | let config = require('../config.json') 4 | async function getArticle(data) { 5 | let url = `${config.bookApi}/${data.bid}/${data.aid}` 6 | let result = await http({ method: 'get', url }) 7 | result = resolveArticle(result.data) 8 | return result 9 | } 10 | module.exports = getArticle -------------------------------------------------------------------------------- /server/request/http.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const config = require('../config.json'); 3 | const server = axios.create({ 4 | baseURL: config.baseUrl, 5 | method: 'GET', 6 | timeout: 6000 7 | }); 8 | // Add a request interceptor 9 | server.interceptors.request.use(function (config) { 10 | // Do something before request is sent 11 | return config; 12 | }, function (error) { 13 | // Do something with request error 14 | return Promise.reject(error); 15 | }); 16 | 17 | // Add a response interceptor 18 | server.interceptors.response.use(function (response) { 19 | // Do something with response data 20 | return response; 21 | }, function (error) { 22 | // Do something with response error 23 | return Promise.reject(error); 24 | }); 25 | module.exports = server; 26 | -------------------------------------------------------------------------------- /server/router/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const router = require('express').Router(); 4 | fs.readdirSync(path.resolve(__dirname, '../module')).reverse().forEach(file => { 5 | if (!file.endsWith('.js')) return 6 | let route = '/' + file.replace(/\.js$/i, '').replace(/_/g, '/') 7 | let question = require(path.resolve(__dirname, '../module', file)) 8 | router.use(route, async (req, res) => { 9 | let query = Object.assign({}, req.query, req.body) 10 | let result = await question(query) 11 | res.send(result) 12 | }) 13 | }) 14 | 15 | 16 | module.exports = router -------------------------------------------------------------------------------- /server/static/js/home_index.js: -------------------------------------------------------------------------------- 1 | var home_index = { 2 | "banner": [ 3 | { 4 | "bid": "1022899262", 5 | "imgUrl": "//bossaudioandcomic-1252317822.image.myqcloud.com/activity/document/dbc03945a118a49409a71aa7ba21dad5.jpg" 6 | }, 7 | { 8 | "bid": "1016519510", 9 | "imgUrl": "//bossaudioandcomic-1252317822.image.myqcloud.com/activity/document/abcea2f8b6e3f8a19bc027dfbf922fe3.jpg" 10 | }, 11 | { 12 | "bid": "1015567442", 13 | "imgUrl": "//bossaudioandcomic-1252317822.image.myqcloud.com/activity/document/8a55de10b591a1c3e43512094eedb93b.jpg" 14 | }, 15 | { 16 | "bid": "1016735308", 17 | "imgUrl": "//bossaudioandcomic-1252317822.image.myqcloud.com/activity/document/0f304220ccc56f038d40db413db0c533.jpg" 18 | }, 19 | { 20 | "bid":"1019605599", 21 | "imgUrl":"//bossaudioandcomic-1252317822.image.myqcloud.com/activity/document/023c1f6a07bf18202370253d2c24ffaa.jpg" 22 | } 23 | ], 24 | "hotfiction": [ 25 | { 26 | "bid": "1018027842", 27 | "bName": "万族之劫" 28 | }, 29 | { 30 | "bid": "1019664125", 31 | "bName": "大奉打更人" 32 | }, 33 | { 34 | "bid": "1009480992", 35 | "bName": "超神机械师" 36 | }, 37 | { 38 | "bid": "1010868264", 39 | "bName": "诡秘之主" 40 | }, 41 | { 42 | "bid": "1016572786", 43 | "bName": "我师兄实在太稳健了" 44 | }, 45 | { 46 | "bid": "1009817672", 47 | "bName": "轮回乐园" 48 | }, 49 | { 50 | "bid": "1022282526", 51 | "bName": "全职艺术家" 52 | } 53 | ], 54 | "freefiction": [ 55 | { 56 | "bid": "1023422452", 57 | "bName": "长夜余火" 58 | }, 59 | { 60 | "bid": "1024222253", 61 | "bName": "不死的我只好假扮血族" 62 | }, 63 | { 64 | "bid": "1024417666", 65 | "bName": "洪荒关系户" 66 | }, 67 | { 68 | "bid": "1024544333", 69 | "bName": "全球神祇之东方神话MOD" 70 | }, 71 | { 72 | "bid": "1024525575", 73 | "bName": "全球神祇时代" 74 | }, 75 | { 76 | "bid": "1024722352", 77 | "bName": "听说你很拽啊" 78 | }, 79 | { 80 | "bid": "1023999278", 81 | "bName": "十步神仙" 82 | } 83 | ], 84 | "newfiction": [ 85 | { 86 | "bid": "1023439204", 87 | "bName": "我在玄幻世界冒充天机神算", 88 | "bAuth": "残剑", 89 | "desc": "【26章起飞】这是个弱者会沦为魂奴、药奴、鼎炉,囚笼的黑暗修行界。你以为穿越有系统就能作威作福?就能三妻四妾?殊不知从你能力展现的那一刻起,便已身在局中。这是一个小萌新在玄幻世界慢慢成长为超级老阴货、无限套娃老祖宗的高智商烧脑故事。(PS1:前26章主角是萌新还有系统因而比较飘,所以文风偏小白,可先跳过从26章开始阅读。)(PS2:本人总共创作4000余万字小说,老书《剑道邪尊》500万字,高订12万,曾创下Q平台最高订阅记录,实力质量有保证,所以请放心收藏观看本书,本书乃呕心沥血之作,绝不让您失望。)(PS3:新建VIP粉丝群:1037654520,喜欢可加群探讨~)", 90 | "cat": "玄幻", 91 | "catId": "21", 92 | "cnt": "168.58万字", 93 | "state": "连载" 94 | }, 95 | { 96 | "bid": "1016477783", 97 | "bName": "我只会拍烂片啊", 98 | "bAuth": "巫马行", 99 | "desc": "“你们信吗!我说,未来我们将创造一个时代,一个让好莱坞都颤抖的时代,那个时候……”那一年,燕影大学混吃等死,被辅导员称为老鼠屎的大四学渣喝醉酒对着兄弟们吹了这么一个牛逼,他本来以为也就吹牛逼,酒醒后大家该干嘛干嘛……但!一个星期后,当兄弟们砸锅卖铁建好剧组,并眼巴巴等待他拍摄以后……他慌了………………………………后来,在那一年之后……世界电影圈所有人心态炸裂了……", 100 | "cat": "都市", 101 | "catId": "4", 102 | "cnt": "114.37万字", 103 | "state": "连载" 104 | }, 105 | { 106 | "bid": "1022763490", 107 | "bName": "从网络神豪开始", 108 | "bAuth": "琉璃湾", 109 | "desc": "身为神豪,您的时间就是金钱!【当前系统等级:1级】【每一秒钟,获得0.01元的现金】【叮!恭喜您获得“网络神豪体验卡”一张】神豪之路,就此开启……", 110 | "cat": "都市", 111 | "catId": "4", 112 | "cnt": "97.1万字", 113 | "state": "连载" 114 | } 115 | ], 116 | "bestsellfiction": [ 117 | { 118 | "bid": "1115277", 119 | "bName": "斗罗大陆", 120 | "bAuth": "唐家三少", 121 | "desc": "唐门外门弟子唐三,因偷学内门绝学为唐门所不容,跳崖明志时却发现没有死,反而以另外一个身份来到了另一个世界,一个属于武魂的世界,名叫斗罗大陆。这里没有魔法,没有斗气,没有武术,却有神", 122 | "cat": "玄幻", 123 | "catId": "21", 124 | "cnt": "298.02万字", 125 | "state": "连载" 126 | }, 127 | { 128 | "bid": "2248950", 129 | "bName": "最强弃少", 130 | "bAuth": "鹅是老五", 131 | "desc": "叶默蓦然清醒过来的时候,才发现周围的一切似乎都变了,美女师父也不见了。他也发现了自己成了被世家抛弃的弃子,被别人退婚的苦逼,还是被女人站在讲台上拿着他情书羞辱的对象......但是", 132 | "cat": "仙侠", 133 | "catId": "22", 134 | "cnt": "730.83万字", 135 | "state": "连载" 136 | }, 137 | { 138 | "bid": "3439785", 139 | "bName": "修真四万年", 140 | "bAuth": "卧牛真人", 141 | "desc": "“倘若这宇宙,真是一片残酷血腥的黑暗森林,我们修真者,也会燃烧自己的生命,绽放出微弱的火花!”“哪怕这火花再微弱,再短暂,再渺小,可是只要我们源源不断,前赴后继,终有一日,火花会点", 142 | "cat": "科幻", 143 | "catId": "9", 144 | "cnt": "1065.37万字", 145 | "state": "连载" 146 | } 147 | ], 148 | "qingfiction": [ 149 | { 150 | "bid": "1023233212", 151 | "bName": "姑娘你不对劲啊" 152 | }, 153 | { 154 | "bid": "1021580268", 155 | "bName": "我真不是魔神" 156 | }, 157 | { 158 | "bid": "1012891245", 159 | "bName": "璀璨城13科的吉恩" 160 | }, 161 | { 162 | "bid": "1022899262", 163 | "bName": "不会真有人觉得修仙难吧" 164 | }, 165 | { 166 | "bid": "1024008030", 167 | "bName": "我在古代日本当剑豪" 168 | }, 169 | { 170 | "bid": "1019593678", 171 | "bName": "我真的不是气运之子" 172 | }, 173 | { 174 | "bid": "1017757758", 175 | "bName": "无敌师叔祖" 176 | } 177 | ] 178 | } -------------------------------------------------------------------------------- /server/utils/connect.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose") 2 | //数据库位置 3 | const dbLocal = "mongodb://127.0.0.1/fiction_20201206" 4 | 5 | exports.connect = () => { 6 | Mongoose.connect(dbLocal) 7 | let db = Mongoose.connection 8 | db.once('open', () => { 9 | console.log("数据库链接成功") 10 | }) 11 | db.on("disconnected", () => { 12 | console.log("数据库断开") 13 | }) 14 | db.on('error', (err) => { 15 | console.log("链接失败") 16 | }) 17 | 18 | } -------------------------------------------------------------------------------- /server/utils/dataio.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const tool = require('./tool') 3 | //判断文件是否存在 4 | function isExitFile(filePath) { 5 | if (fs.existsSync(filePath)) { 6 | return true 7 | } 8 | return false 9 | } 10 | //读取文件 11 | function readFileData(filePath) { 12 | let data = [] 13 | try { 14 | let result = fs.readFileSync(filePath, 'utf8') 15 | data = result ? tool.formatData(result) : [] 16 | } catch (e) { 17 | //读取不到文件直接新建data 18 | return [e, data] 19 | } 20 | return [null, data] 21 | } 22 | //写入文件 23 | function writeFileData(filePath, data) { 24 | try { 25 | fs.writeFileSync(filePath, tool.formatData(data), 'utf8'); 26 | } 27 | catch (e) { 28 | return false; 29 | } 30 | return true; 31 | } 32 | /** 33 | * 遍历目标和原数据是否匹配 34 | * @param {Array} origin 目标查询的文件总数据 35 | * @param {Object} target 目标查询字段 36 | * @return {Boolean} 返回是否匹配上 37 | */ 38 | function isMatch(origin, target) { 39 | let keys = Object.keys(target); 40 | //every方法,全部都匹配上了才返回true 41 | return keys.every(item => target[item] == origin[item]) 42 | } 43 | /** 44 | * 文件里匹配的记录 45 | * @param {Array} origin 目标查询的文件总数据 46 | * @param {Object} target 目标查询字段 47 | * @return {Array} 返回匹配的数据条 48 | */ 49 | function isRecord(origin, target) { 50 | let result = origin.filter(item => isMatch(item, target)) 51 | return result 52 | } 53 | //删除指定数据和目录,返回数据集 54 | function remove(filePath, qField) { 55 | let [err, result] = readFileData(filePath) 56 | result.map((item, index) => { 57 | if (isMatch(item, qField)) { 58 | result.splice(index, 1); 59 | } 60 | }) 61 | fs.unlinkSync(filePath); 62 | return result; 63 | } 64 | //删除数据 65 | function del(filePath, qField){ 66 | let result = remove(filePath, qField); 67 | return writeFileData(filePath, result) 68 | } 69 | //查询数据 70 | function find(filePath, obj = {}) { 71 | let result = [] 72 | return new Promise((resolve, reject) => { 73 | let [err, data] = readFileData(filePath) 74 | if (err) { 75 | return resolve(result) 76 | } 77 | //查询全部 78 | if (!tool.isEmptyObj(obj)) { 79 | return resolve(data) 80 | } 81 | //过滤匹配的数据 82 | result = isRecord(data, obj) 83 | resolve(result) 84 | }) 85 | } 86 | //添加数据 87 | function insert(filePath, data) { 88 | let [err, result] = readFileData(filePath) 89 | result.push(data) 90 | return writeFileData(filePath, result) 91 | } 92 | //更新数据 93 | async function update(filePath, qField, uField) { 94 | let udata = await find(filePath, qField); 95 | udata.map(item => { 96 | Object.keys(uField).map(sub => { 97 | item[sub] = uField[sub] 98 | }) 99 | }) 100 | //删除数据 101 | let result = remove(filePath, qField).concat(udata) 102 | return writeFileData(filePath, result) 103 | } 104 | module.exports = { 105 | find, 106 | insert, 107 | update, 108 | del 109 | } -------------------------------------------------------------------------------- /server/utils/err.js: -------------------------------------------------------------------------------- 1 | function go(promise, errorProps = {}, errorFirst = true) { 2 | return promise.then((data) => { 3 | return errorFirst ? [null, data] : [data, null] 4 | }).catch(err => { 5 | console.log('err', err) 6 | if (errorProps) Object.assign(err, errorProps) 7 | return errorFirst ? [err, null] : [null, err] 8 | }) 9 | } 10 | 11 | module.exports = go; -------------------------------------------------------------------------------- /server/utils/filterResCode.js: -------------------------------------------------------------------------------- 1 | function formatBackCode(code, msg, data = null) { 2 | return { 3 | code, 4 | msg, 5 | data 6 | } 7 | } 8 | module.exports = formatBackCode; -------------------------------------------------------------------------------- /server/utils/httpwithjson.js: -------------------------------------------------------------------------------- 1 | class HttpJson { 2 | constructor(code, msg, status, data) { 3 | this.code = code 4 | this.msg = msg 5 | this.status = status 6 | this.data = data || null 7 | } 8 | } 9 | class HttpSuccess extends HttpJson { 10 | constructor(msg = '操作成功', data) { 11 | super(2000, msg, 'success', data) 12 | } 13 | } 14 | class HttpError extends HttpJson { 15 | constructor(code, msg = '操作失败') { 16 | super(code, msg, 'error') 17 | } 18 | } 19 | module.exports = { 20 | HttpSuccess, 21 | HttpError 22 | } -------------------------------------------------------------------------------- /server/utils/tool.js: -------------------------------------------------------------------------------- 1 | //判断对象是否为空 2 | function isEmptyObj(obj) { 3 | return Object.keys(obj).length > 0 4 | } 5 | //json数据解析 6 | function formatData(data) { 7 | if (Object.prototype.toString.call(data) == "[object String]") { 8 | return JSON.parse(data) 9 | } 10 | return JSON.stringify(data) 11 | } 12 | module.exports = { 13 | isEmptyObj, 14 | formatData 15 | } -------------------------------------------------------------------------------- /server/utils/userToken.js: -------------------------------------------------------------------------------- 1 | const config = require('../config.json'); 2 | const jwt = require('jsonwebtoken'); 3 | /** 4 | * //添加token验证 5 | * @param {Object} payload //载体信息,额外加密的信息 6 | * @param {Date} expiresIn //过期时间 7 | * @return {String} //返回一个sign的token 8 | */ 9 | function addToken(payload, expiresIn) { 10 | return jwt.sign(payload, config.secretOrKey, { expiresIn }); 11 | } 12 | /** 13 | * //验证token 14 | * @param {String} token //前台带过来的token 15 | * @return {Promise} //返回一个自定义的状态码 50001:token无效,50002:过期 16 | */ 17 | async function verifyToken(token) { 18 | try { 19 | jwt.verify(token, config.secretOrKey) 20 | return 2000 21 | } catch (error) { 22 | return 4001 23 | } 24 | } 25 | module.exports = { 26 | addToken, 27 | verifyToken 28 | } -------------------------------------------------------------------------------- /server/utils/uuid.js: -------------------------------------------------------------------------------- 1 | const { v4: uuidv4 } = require('uuid'); 2 | 3 | function generateUId() { 4 | let strUUID = uuidv4() 5 | let strUUID2 = strUUID.replace(/-/g, '') 6 | return strUUID2 7 | } 8 | module.exports = generateUId -------------------------------------------------------------------------------- /server/utils/whiteList.js: -------------------------------------------------------------------------------- 1 | const whiteList = ['/api/collect', '/api/like']; 2 | module.exports = whiteList; -------------------------------------------------------------------------------- /server/utils/writeData.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const dataIO = require('./dataio') 4 | 5 | /** 6 | * @param {Function} requestA 请求方法 7 | * @param {Object} data 请求参数 8 | * @param {String} fileName 写入数据的文件名 9 | * @param {Boolean} isCache 是否从本地拿数据 10 | */ 11 | const preAjax = (requestA, data = {}, fileName, isCache = true) => { 12 | let mockFilePath = path.resolve(__dirname, `../mock/${fileName}.json`) 13 | return new Promise(async (resolve, reject) => { 14 | if (isCache) { 15 | let result = await dataIO.find(mockFilePath) 16 | if (result.length > 0) { 17 | return resolve(result) 18 | } 19 | result = await requestA(data) 20 | resolve(result) 21 | fs.writeFileSync(mockFilePath, JSON.stringify(result), 'utf-8') 22 | } else { 23 | let result = await requestA(data) 24 | resolve(result) 25 | } 26 | }) 27 | } 28 | module.exports = preAjax; -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 31 | -------------------------------------------------------------------------------- /src/assets/css/reset.scss: -------------------------------------------------------------------------------- 1 | blockquote, body, dd, dl, figure, form, h1, h2, h3, h4, h5, h6, ol, p, ul{ 2 | margin:0; 3 | box-sizing: border-box; 4 | } 5 | @font-face { 6 | font-family: KUHEI; 7 | font-style: normal; 8 | font-weight: 400; 9 | src: url(https://qidian.gtimg.com/qdm/font/KUHEI/KUHEI2.c389c.woff2) format('woff2') 10 | } 11 | .van-swipe__indicator{ 12 | width: 10px; 13 | height: 10px; 14 | background-color:#ffffff; 15 | opacity: 1; 16 | } 17 | .van-pull-refresh__head{ 18 | font-size: 28px; 19 | } 20 | .category{ 21 | .van-cell{ 22 | padding: 32px; 23 | } 24 | .van-cell__right-icon{ 25 | font-size: 32px; 26 | } 27 | } 28 | .van-tabs--line .van-tabs__wrap { 29 | height: 80px; 30 | } 31 | .van-tab{ 32 | line-height: 80px; 33 | } 34 | .article-container .van-pagination__item{ 35 | height: 88px; 36 | color: #33373d; 37 | } 38 | .article-container .van-pagination__page-desc{ 39 | height: 88px; 40 | background-color: #f6f7f9; 41 | } 42 | .article-container .van-pagination__item:active{ 43 | background-color: #ffffff; 44 | color: #33373d; 45 | } 46 | .search-container{ 47 | .van-search { 48 | padding: 14px 32px; 49 | } 50 | .van-search .van-cell{ 51 | align-items: center; 52 | padding:0; 53 | } 54 | .van-search__content { 55 | border-radius: 4px; 56 | } 57 | .van-field__control{ 58 | height: 60px; 59 | line-height: 60px; 60 | font-size: 28px; 61 | } 62 | .van-search__action{ 63 | font-size: 28px; 64 | } 65 | .van-field__left-icon .van-icon{ 66 | font-size: 34px; 67 | color: #969ba3; 68 | } 69 | .van-icon-clear{ 70 | font-size: 30px; 71 | color: #969ba3; 72 | } 73 | } 74 | .pull-container{ 75 | .van-list__finished-text{ 76 | height: 80px; 77 | line-height: 88px; 78 | font-size: 28px; 79 | } 80 | .van-loading__text{ 81 | font-size: 28px; 82 | } 83 | } 84 | .van-skeleton{ 85 | padding: 32px; 86 | background-color: #ffffff; 87 | } 88 | .van-skeleton__row, .van-skeleton__title { 89 | height: 32px; 90 | background-color: #ffffff; 91 | animation: changebg .5s ease-in infinite; 92 | } 93 | @keyframes changebg{ 94 | 0%{background-image: linear-gradient(to right, rgba(242, 243, 245,1),rgba(242, 243, 245,0) 20%, rgba(242, 243, 245,1));} 95 | 20%{background-image: linear-gradient(to right, rgba(242, 243, 245,1),rgba(242, 243, 245,0) 40%, rgba(242, 243, 245,1));} 96 | 40%{background-image: linear-gradient(to right, rgba(242, 243, 245,1),rgba(242, 243, 245,0) 60%, rgba(242, 243, 245,1));} 97 | 60%{background-image: linear-gradient(to right, rgba(242, 243, 245,1),rgba(242, 243, 245,0) 80%, rgba(242, 243, 245,1));} 98 | 80%{background-image: linear-gradient(to right, rgba(242, 243, 245,1),rgba(242, 243, 245,0) 90%, rgba(242, 243, 245,1));} 99 | 100%{background-image: linear-gradient(to right, rgba(242, 243, 245,1),rgba(242, 243, 245,0) 100%, rgba(242, 243, 245,1));} 100 | } 101 | .book-container{ 102 | .van-sticky--fixed{ 103 | top:88px; 104 | background-color: #ffffff; 105 | border-bottom: 1px solid #ed424b; 106 | } 107 | } 108 | .van-field__error-message{ 109 | font-size: 28px; 110 | } 111 | a { 112 | text-decoration: none; 113 | color: inherit; 114 | outline: 0; 115 | } 116 | .person-container{ 117 | .van-nav-bar{ 118 | background-color: transparent !important; 119 | border-bottom: none !important; 120 | } 121 | .van-hairline--bottom::after { 122 | border-bottom-width: 0px !important; 123 | } 124 | .sub-title{ 125 | color: #ffffff; 126 | } 127 | .van-icon-arrow-left{ 128 | color: #ffffff !important; 129 | } 130 | .van-icon-search{ 131 | color: #ffffff !important; 132 | } 133 | span.more{ 134 | &:empty::before, &:empty::after{ 135 | border-color:#ffffff !important; 136 | } 137 | } 138 | } 139 | .custom-toast{ 140 | border-radius: 6px; 141 | min-width: auto; 142 | top: 40%; 143 | font-size: 28px; 144 | padding: 20px 24px; 145 | } 146 | .van-dialog{ 147 | .van-dialog__message{ 148 | font-size: 30px; 149 | padding: 30px 0; 150 | } 151 | .van-button--large{ 152 | height: 70px; 153 | line-height: 70px; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tmfree/vue-fiction/fc92e026d5017b02ce9bc559d299950c48f4dfc0/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/common/LoadHeader.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/components/common/NavBar.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 94 | 95 | 250 | 251 | 259 | -------------------------------------------------------------------------------- /src/components/common/NavHeader.vue: -------------------------------------------------------------------------------- 1 | 17 | 28 | 29 | 64 | 72 | -------------------------------------------------------------------------------- /src/components/common/PullUp.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 150 | 151 | 161 | -------------------------------------------------------------------------------- /src/components/common/backTop.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 42 | -------------------------------------------------------------------------------- /src/interface/base.ts: -------------------------------------------------------------------------------- 1 | export interface ResInfo { 2 | code: number 3 | msg: string 4 | data: T 5 | } 6 | 7 | export interface LunBoInfo { 8 | bid: string 9 | imgUrl: string 10 | } -------------------------------------------------------------------------------- /src/interface/user.ts: -------------------------------------------------------------------------------- 1 | export interface UserInfo { 2 | uid: string 3 | nickName: string 4 | avatar: string 5 | isLogin: boolean 6 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | import Vant from 'vant'; 6 | import VueLazyload from 'vue-lazyload'; 7 | import VuePageStack from 'vue-page-stack'; 8 | import Component from "vue-class-component"; 9 | import "normalize.css/normalize.css"; 10 | import './utils/vw.css'; 11 | import 'vant/lib/index.css'; 12 | import './assets/css/reset.scss' 13 | import Ajax from './request/api' 14 | 15 | Vue.prototype.$Ajax = Ajax 16 | Vue.use(Vant); 17 | Vue.use(VuePageStack, { router }) 18 | Vue.config.productionTip = false; 19 | Vue.use(VueLazyload, { 20 | preLoad: 1.3, 21 | loading: '//qidian.gtimg.com/qdm/img/book-cover.c977e.svg', 22 | attempt: 1 23 | }) 24 | Component.registerHooks([ 25 | "beforeRouteEnter", 26 | "beforeRouteLeave", 27 | "beforeRouteUpdate" 28 | ]) 29 | new Vue({ 30 | router, 31 | store, 32 | render: h => h(App) 33 | }).$mount("#app"); 34 | -------------------------------------------------------------------------------- /src/request/api.ts: -------------------------------------------------------------------------------- 1 | import http from "./http" 2 | import { AxiosResponse } from 'axios' 3 | import { ResInfo } from "@/interface/base" 4 | import { setToken, getToken } from '@/utils/auth' 5 | 6 | //小说介绍 7 | export const getBookInfo = async (data = {}) => { 8 | let url = `/api/book`; 9 | let result: AxiosResponse = await http({ 10 | method: 'get', 11 | url, 12 | params: data 13 | }) 14 | return result.data; 15 | } 16 | 17 | //小说章节 18 | export const getBookCatalog = async (data = {}) => { 19 | let url = `/api/catalog`; 20 | let result: AxiosResponse = await http({ 21 | method: 'get', 22 | url, 23 | params: data 24 | }) 25 | return result.data; 26 | } 27 | 28 | //小说章节内容 29 | export const getArticle = async (data = {}) => { 30 | let url = `/api/article`; 31 | let result: AxiosResponse = await http({ 32 | method: 'get', 33 | url, 34 | params: data 35 | }) 36 | return result.data; 37 | } 38 | 39 | //分类 40 | export const getCategory = async (data = {}) => { 41 | let url = `/api/category`; 42 | let result: AxiosResponse = await http({ 43 | method: 'get', 44 | url, 45 | params: data 46 | }) 47 | return result.data; 48 | } 49 | 50 | class Ajax { 51 | async getRank(params = {}) { 52 | let url = `/api/rank`; 53 | let result: AxiosResponse = await http({ 54 | method: 'get', 55 | url, 56 | params 57 | }) 58 | return result.data; 59 | } 60 | async getRankDetail(params = {}) { 61 | let url = `/api/rank/detail`; 62 | let result: AxiosResponse = await http({ 63 | method: 'get', 64 | url, 65 | params 66 | }) 67 | return result.data; 68 | } 69 | async getCategory(params = {}) { 70 | let url = `/api/category`; 71 | let result: AxiosResponse = await http({ 72 | method: 'get', 73 | url, 74 | params 75 | }) 76 | return result.data; 77 | } 78 | async getCategoryList(params = {}) { 79 | let url = `/api/category/detail`; 80 | let result: AxiosResponse = await http({ 81 | method: 'get', 82 | url, 83 | params 84 | }) 85 | return result.data; 86 | } 87 | async getBookList(params = {}) { 88 | let url = `/api/getbooklist`; 89 | let result: AxiosResponse = await http({ 90 | method: 'get', 91 | url, 92 | params 93 | }) 94 | return result.data; 95 | } 96 | async getBookInfo(params = {}) { 97 | let url = `/api/book`; 98 | let result: AxiosResponse = await http({ 99 | method: 'get', 100 | url, 101 | params 102 | }) 103 | return result.data; 104 | } 105 | async getSearch(params = {}) { 106 | let url = `/api/search`; 107 | let result: AxiosResponse = await http({ 108 | method: 'get', 109 | url, 110 | params 111 | }) 112 | return result.data; 113 | } 114 | 115 | async login(params = {}) { 116 | let url = '/api/login'; 117 | let result: AxiosResponse = await http({ 118 | method: 'post', 119 | url, 120 | params 121 | }) 122 | result.data.data.token && setToken('User-Token', result.data.data.token) 123 | result.data.data.uId && setToken('User-Id', result.data.data.uId) 124 | return result.data; 125 | } 126 | 127 | async getUserInfo(params: any = {}) { 128 | let url = '/api/getUserInfo'; 129 | params.uId = getToken("User-Id"); 130 | let result: AxiosResponse = await http({ 131 | method: 'post', 132 | url, 133 | params 134 | }) 135 | return result.data.data; 136 | } 137 | 138 | async getCollectList(params: any) { 139 | let url = `/api/collect/list`; 140 | params.uId = getToken('User-Id'); 141 | let result: AxiosResponse = await http({ 142 | method: 'get', 143 | url, 144 | params 145 | }) 146 | return result.data; 147 | } 148 | async addCollect(params: any) { 149 | let url = `/api/collect/add`; 150 | params.uId = getToken('User-Id'); 151 | let result: AxiosResponse = await http({ 152 | method: 'get', 153 | url, 154 | params 155 | }) 156 | return result.data; 157 | } 158 | async delCollect(params: any) { 159 | let url = `/api/collect/del`; 160 | params.uId = getToken('User-Id'); 161 | let result: AxiosResponse = await http({ 162 | method: 'get', 163 | url, 164 | params 165 | }) 166 | return result.data; 167 | } 168 | 169 | async getLikeList(params: any) { 170 | let url = `/api/like/list`; 171 | params.uId = getToken('User-Id'); 172 | let result: AxiosResponse = await http({ 173 | method: 'get', 174 | url, 175 | params 176 | }) 177 | return result.data; 178 | } 179 | async addLike(params: any) { 180 | let url = `/api/like/add`; 181 | params.uId = getToken('User-Id'); 182 | let result: AxiosResponse = await http({ 183 | method: 'get', 184 | url, 185 | params 186 | }) 187 | return result.data; 188 | } 189 | async delLike(params: any) { 190 | let url = `/api/like/del`; 191 | params.uId = getToken('User-Id'); 192 | let result: AxiosResponse = await http({ 193 | method: 'get', 194 | url, 195 | params 196 | }) 197 | return result.data; 198 | } 199 | } 200 | 201 | export default new Ajax() -------------------------------------------------------------------------------- /src/request/http.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { Dialog } from 'vant' 3 | import router from "../router" 4 | import { getToken } from '@/utils/auth' 5 | const server = axios.create({ 6 | baseURL: process.env.VUE_APP_BASEURL 7 | }); 8 | // Add a request interceptor 9 | server.interceptors.request.use(function (config) { 10 | if (config.method.toLocaleUpperCase() == 'POST') { 11 | config.data = config.params; 12 | } 13 | // Do something before request is sent 14 | if (getToken('User-Token')) { 15 | config.headers['Authorization'] = getToken('User-Token') 16 | } 17 | return config 18 | }, function (error) { 19 | // Do something with request error 20 | return Promise.reject(error); 21 | }); 22 | 23 | // Add a response interceptor 24 | server.interceptors.response.use(function (response) { 25 | // Do something with response data 26 | if (response.data.code == 4001) { 27 | Dialog.confirm({ 28 | message: response.data.msg 29 | }).then(() => { 30 | // on confirm 31 | router.replace(`/login?redirect=${router.currentRoute.path}`) 32 | }).catch(() => { 33 | // on cancel 34 | }); 35 | Dialog.setDefaultOptions({ 36 | closeOnClickOverlay: true, 37 | confirmButtonColor: '' 38 | }) 39 | } 40 | return response; 41 | }, function (error) { 42 | // Do something with response error 43 | return Promise.reject(error); 44 | }); 45 | 46 | export default server; -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter, { RouteConfig } from "vue-router"; 3 | import { getToken } from '@/utils/auth' 4 | Vue.use(VueRouter); 5 | const routes: Array = [ 6 | { 7 | path: "/", 8 | component: () => import("../views/Layout.vue"), 9 | children: [ 10 | { 11 | path: '', 12 | component: () => import("../views/home/index.vue"), 13 | meta: { 14 | isShowNav: true, 15 | isCloseRefresh: true, 16 | isStartLoadMore: false, 17 | isLogin: false 18 | } 19 | }, 20 | { 21 | path: '/book/list/:id', 22 | component: () => import("../views/booklist/index.vue"), 23 | meta: { 24 | isShowNav: true, 25 | isCloseRefresh: true, 26 | isStartLoadMore: true, 27 | isLogin: false 28 | } 29 | }, 30 | { 31 | path: 'category', 32 | component: () => import("../views/category/index.vue"), 33 | meta: { 34 | isShowNav: true, 35 | isCloseRefresh: true, 36 | isStartLoadMore: false, 37 | isLogin: false 38 | } 39 | }, 40 | { 41 | path: 'category/:bname', 42 | name: "catDetail", 43 | component: () => import("../views/category/detail.vue"), 44 | meta: { 45 | isShowNav: true, 46 | isCloseRefresh: false, 47 | isStartLoadMore: true, 48 | isLogin: false 49 | } 50 | }, 51 | { 52 | path: "rank", 53 | component: () => import("../views/rank/index.vue"), 54 | meta: { 55 | isShowNav: true, 56 | isCloseRefresh: true, 57 | isStartLoadMore: false, 58 | isLogin: false 59 | } 60 | }, 61 | { 62 | path: "rank/:bname", 63 | component: () => import("../views/rank/detail.vue"), 64 | meta: { 65 | isShowNav: true, 66 | isCloseRefresh: false, 67 | isStartLoadMore: true, 68 | isLogin: false 69 | } 70 | }, 71 | { 72 | path: "book/:id", 73 | name: "Book", 74 | component: () => import("../views/book/index.vue"), 75 | meta: { 76 | isShowNav: true, 77 | isCloseRefresh: true, 78 | isStartLoadMore: false, 79 | isLogin: false 80 | } 81 | }, 82 | { 83 | path: "search", 84 | component: () => import("../views/search/index.vue"), 85 | meta: { 86 | isShowNav: false, 87 | isCloseRefresh: true, 88 | isStartLoadMore: true, 89 | isLogin: false 90 | } 91 | } 92 | ] 93 | }, 94 | { 95 | path: '/login', 96 | name: 'login', 97 | component: () => import("../views/login/index.vue"), 98 | meta: { 99 | isLogin: false 100 | } 101 | }, 102 | { 103 | path: '/user', 104 | component: () => import("../views/person/index.vue"), 105 | meta: { 106 | isLogin: false 107 | } 108 | } 109 | ]; 110 | 111 | const router = new VueRouter({ 112 | routes 113 | }); 114 | const whiteList = ['/login'];//不需要重定向 115 | router.beforeEach((to, from, next) => { 116 | // ... 117 | let { isLogin } = to.meta; 118 | if (isLogin) { 119 | const hasToken = getToken('User-Token'); 120 | if (hasToken) { 121 | next(); 122 | } else { 123 | //不需要token 124 | if (whiteList.indexOf(to.path) !== -1) { 125 | next() 126 | } else { 127 | // 页面跳转 128 | next(`/login?redirect=${to.path}`) 129 | } 130 | } 131 | } else { 132 | next(); 133 | } 134 | }) 135 | 136 | export default router; 137 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-vue-page-stack.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-page-stack' { 2 | const VuePageStackPlugin: any 3 | export default VuePageStackPlugin 4 | } -------------------------------------------------------------------------------- /src/shims-vue-proptype.d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | declare module 'vue/types/vue' { 3 | interface Vue { 4 | $Ajax: any; 5 | } 6 | } -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/store/getter.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from "vuex" 2 | let getters: GetterTree = { 3 | navBar: state => state.generalModule.navBar,//导航头信息 4 | rankDataList: state => state.rankModule.dataList, 5 | bookInfo: state => state.bookModule.bookInfo,//小说介绍 6 | articleContent: state => state.bookModule.articleContent,//小说章节内容 7 | categoryList: state => state.categoryModule.categoryList,//小说分类 8 | categoryDetailList: state => state.categoryModule.categoryDetail,//小说分类详情 9 | searchList: state => state.generalModule.searchList,//搜索结果 10 | searchKW: state => state.generalModule.searchKW,//搜索关键字 11 | rank: state => state.rankModule.rank,//排行榜 12 | rankDetail: state => state.rankModule.rankDetail,//排行榜详情 13 | isLogin: state => state.userModule.isLogin,//是否登录 14 | loginInfo: state => state.userModule.loginInfo,//登录信息 15 | isCollect: state => state.userModule.isCollect,//收藏 16 | userInfo: state => state.userModule.userInfo,//用户信息 17 | collectList: state => state.userModule.collectList,//收藏列表 18 | likeList: state => state.userModule.likeList,//点赞列表 19 | } 20 | 21 | export default getters -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import getters from './getter' 4 | import rankModule from './module/rank'; 5 | import generalModule from './module/common'; 6 | import bookModule from './module/book'; 7 | import categoryModule from './module/category'; 8 | import userModule from './module/user'; 9 | Vue.use(Vuex); 10 | export default new Vuex.Store({ 11 | state: {}, 12 | mutations: {}, 13 | actions: {}, 14 | modules: { 15 | bookModule, 16 | rankModule, 17 | generalModule, 18 | categoryModule, 19 | userModule 20 | }, 21 | getters 22 | }); 23 | -------------------------------------------------------------------------------- /src/store/module/book/index.ts: -------------------------------------------------------------------------------- 1 | import { book } from './interface' 2 | import * as Type from './type' 3 | import { ActionTree, MutationTree } from 'vuex' 4 | import { getBookInfo, getArticle, getBookCatalog } from "@/request/api" 5 | let state: book = { 6 | bookInfo: {}, 7 | bookCatalog: {}, 8 | articleContent: {} 9 | } 10 | let mutations: MutationTree = { 11 | [Type.SET_BOOK_INFO](state, data) { 12 | state.bookInfo = data; 13 | }, 14 | [Type.SET_BOOK_CATALOG](state, data) { 15 | state.bookCatalog = data; 16 | }, 17 | [Type.SET_ARTICLE](state, data) { 18 | state.articleContent = data; 19 | }, 20 | } 21 | let actions: ActionTree = { 22 | async getBookInfo({ commit }, data = {}) { 23 | let result = await getBookInfo(data); 24 | commit(Type.SET_BOOK_INFO, result); 25 | return result; 26 | }, 27 | async getBookCatalog({ commit }, data = {}) { 28 | let result = await getBookCatalog(data); 29 | commit(Type.SET_BOOK_CATALOG, result); 30 | return result; 31 | }, 32 | async getArticle({ commit }, data = {}) { 33 | let result = await getArticle(data); 34 | commit(Type.SET_ARTICLE, result); 35 | return result; 36 | } 37 | } 38 | export default { 39 | state, 40 | mutations, 41 | actions 42 | } -------------------------------------------------------------------------------- /src/store/module/book/interface.ts: -------------------------------------------------------------------------------- 1 | export interface book { 2 | bookInfo?: any 3 | bookCatalog?: any 4 | articleContent?: any 5 | } -------------------------------------------------------------------------------- /src/store/module/book/type.ts: -------------------------------------------------------------------------------- 1 | export const SET_BOOK_INFO="SET_BOOK_INFO"; 2 | export const SET_BOOK_CATALOG ="SET_BOOK_CATALOG"; 3 | export const SET_ARTICLE ="SET_ARTICLE"; -------------------------------------------------------------------------------- /src/store/module/category/index.ts: -------------------------------------------------------------------------------- 1 | import { Icategory } from './interface' 2 | import * as Type from './type' 3 | import { ActionTree, MutationTree } from "vuex"; 4 | import Ajax from "@/request/api" 5 | let state: Icategory = { 6 | categoryList: [], 7 | categoryDetail: [] 8 | } 9 | let mutations: MutationTree = { 10 | [Type.SET_CATEGORY](state, data) { 11 | state.categoryList = data; 12 | }, 13 | [Type.STE_CATEGORY_DETAIL](state, data) { 14 | state.categoryDetail = data; 15 | } 16 | } 17 | let actions: ActionTree = { 18 | async getCategory({ commit }, data = {}) { 19 | let result = await Ajax.getCategory(data); 20 | commit(Type.SET_CATEGORY, result); 21 | return result; 22 | }, 23 | async getCategoryDetail({ commit }, data = {}) { 24 | let result = await Ajax.getCategoryList(data); 25 | commit(Type.STE_CATEGORY_DETAIL, result.data); 26 | return result.data; 27 | } 28 | } 29 | export default { 30 | state, 31 | mutations, 32 | actions 33 | } -------------------------------------------------------------------------------- /src/store/module/category/interface.ts: -------------------------------------------------------------------------------- 1 | export interface Icategory { 2 | categoryList?: Array 3 | categoryDetail?: Array 4 | } -------------------------------------------------------------------------------- /src/store/module/category/type.ts: -------------------------------------------------------------------------------- 1 | export const SET_CATEGORY = "SET_CATEGORY"; 2 | export const STE_CATEGORY_DETAIL = "STE_CATEGORY_DETAIL"; -------------------------------------------------------------------------------- /src/store/module/common/index.ts: -------------------------------------------------------------------------------- 1 | import { base } from "./interface"; 2 | import * as Type from './type'; 3 | import { ActionTree, MutationTree } from "vuex"; 4 | 5 | let state: base = { 6 | lunboList: [], 7 | hotFictionList: [], 8 | freeFictionList: [], 9 | newFictionList: [], 10 | qingFictionList: [], 11 | searchList: [], 12 | navBar: { 13 | showTitle: true, 14 | showLeftTitle: true, 15 | leftTitle: "" 16 | }, 17 | searchKW: "" 18 | }; 19 | let mutations: MutationTree = { 20 | [Type.SET_NAVBAR_TITLE](state, data) { 21 | state.navBar = data 22 | }, 23 | [Type.SET_SEARCHKW](state, data) { 24 | state.searchKW = data 25 | } 26 | } 27 | let actions: ActionTree = { 28 | } 29 | export default { state, mutations, actions } -------------------------------------------------------------------------------- /src/store/module/common/interface.ts: -------------------------------------------------------------------------------- 1 | export interface base { 2 | lunboList?: Array 3 | hotFictionList?: Array 4 | freeFictionList?: Array 5 | newFictionList?: Array 6 | qingFictionList?: Array 7 | searchList?: Array 8 | navBar?: navBar 9 | searchKW?: string 10 | } 11 | 12 | export interface navBar { 13 | showTitle: boolean 14 | showLeftTitle: boolean 15 | leftTitle: string 16 | } -------------------------------------------------------------------------------- /src/store/module/common/type.ts: -------------------------------------------------------------------------------- 1 | export const SET_LUNBO_LIST = 'SET_LUNBO_LIST'; 2 | export const SET_HOTFICTION_LIST = 'SET_HOTFICTION_LIST'; 3 | export const SET_FREEFICTION_LIST = 'SET_FREEFICTION_LIST'; 4 | export const SET_NEWFICTION_LIST = 'SET_NEWFICTION_LIST'; 5 | export const SET_QINGFICTION_LIST = 'SET_QINGFICTION_LIST'; 6 | export const SET_NAVBAR_TITLE = 'SET_NAVBAR_TITLE'; 7 | export const SET_SEARCH = 'SET_SEARCH'; 8 | export const SET_SEARCHKW = 'SET_SEARCHKW'; -------------------------------------------------------------------------------- /src/store/module/rank/index.ts: -------------------------------------------------------------------------------- 1 | import { rank } from './interface' 2 | import * as Type from './type' 3 | import { ActionTree, MutationTree } from 'vuex' 4 | import Ajax from "@/request/api" 5 | let state: rank = { 6 | rank: [], 7 | rankDetail: [] 8 | } 9 | let mutations: MutationTree = { 10 | [Type.SET_RANK](state, data) { 11 | state.rank = data; 12 | }, 13 | [Type.SET_RANK_DETAIL](state, data) { 14 | state.rankDetail = data; 15 | } 16 | } 17 | let actions: ActionTree = { 18 | async getRank({ commit }, data = {}) { 19 | let result = await Ajax.getRank(data); 20 | commit(Type.SET_RANK, result); 21 | return result; 22 | }, 23 | async getRankDetail({ commit }, data = {}) { 24 | let result = await Ajax.getRankDetail(data); 25 | commit(Type.SET_RANK_DETAIL, result); 26 | return result; 27 | } 28 | } 29 | export default { 30 | state, 31 | mutations, 32 | actions 33 | } -------------------------------------------------------------------------------- /src/store/module/rank/interface.ts: -------------------------------------------------------------------------------- 1 | export interface rank { 2 | rank?: Array 3 | rankDetail?: Array 4 | } -------------------------------------------------------------------------------- /src/store/module/rank/type.ts: -------------------------------------------------------------------------------- 1 | export const SET_RANK = 'SET_RANK'; 2 | export const SET_RANK_DETAIL = 'SET_RANK_DETAIL'; -------------------------------------------------------------------------------- /src/store/module/user/index.ts: -------------------------------------------------------------------------------- 1 | import { Iuser } from './interface' 2 | import * as Type from "./type"; 3 | import { ActionTree, MutationTree } from 'vuex' 4 | import Ajax from "@/request/api"; 5 | import Aes from "@/utils/aes"; 6 | import { setToken } from '@/utils/auth' 7 | let state: Iuser = { 8 | isLogin: false, 9 | loginInfo: {}, 10 | isCollect: false, 11 | userInfo: { 12 | uid: "", 13 | nickName: "", 14 | avatar: "", 15 | isLogin: false 16 | }, 17 | collectList: [], 18 | likeList: [] 19 | } 20 | let mutations: MutationTree = { 21 | [Type.SET_LOGIN_STATUS](state, data) { 22 | state.isLogin = data; 23 | }, 24 | [Type.SET_LOGIN_INFO](state, data) { 25 | state.loginInfo = data; 26 | }, 27 | [Type.SET_USER_INFO](state, data) { 28 | state.userInfo = data 29 | }, 30 | [Type.SET_COLLECT_LIST](state, data) { 31 | state.collectList = data 32 | }, 33 | [Type.SET_LIKE_LIST](state, data) { 34 | state.likeList = data 35 | } 36 | } 37 | let actions: ActionTree = { 38 | async login({ commit }, data = {}) { 39 | //md5+aes加密 40 | data.password = Aes.encrypt(data.password); 41 | let result = await Ajax.login(data); 42 | if (result.code == 0) { 43 | setToken('User-Token', result.data.token); 44 | commit(Type.SET_LOGIN_STATUS, true); 45 | commit(Type.SET_LOGIN_INFO, result); 46 | } 47 | return result; 48 | }, 49 | async getUserInfo({ commit }, data = {}) { 50 | let result = await Ajax.getUserInfo(data); 51 | commit(Type.SET_USER_INFO, result); 52 | return result; 53 | }, 54 | async getCollectList({ commit }, data = {}) { 55 | let result = await Ajax.getCollectList(data); 56 | commit(Type.SET_COLLECT_LIST, result.data); 57 | return result; 58 | }, 59 | async getLikeList({ commit }, data = {}) { 60 | let result = await Ajax.getLikeList(data); 61 | commit(Type.SET_LIKE_LIST, result.data); 62 | return result; 63 | } 64 | } 65 | export default { 66 | state, 67 | mutations, 68 | actions 69 | } -------------------------------------------------------------------------------- /src/store/module/user/interface.ts: -------------------------------------------------------------------------------- 1 | import { UserInfo } from "@/interface/user" 2 | export interface Iuser { 3 | isLogin?: boolean, 4 | loginInfo?: any, 5 | isCollect?: boolean, 6 | userInfo?: UserInfo, 7 | collectList?: Array, 8 | likeList?: Array 9 | } 10 | -------------------------------------------------------------------------------- /src/store/module/user/type.ts: -------------------------------------------------------------------------------- 1 | export const SET_LOGIN_STATUS = 'SET_LOGIN_STATUS'; 2 | export const SET_LOGIN_INFO = 'SET_LOGIN_INFO'; 3 | export const SET_COLLECT = 'SET_COLLECT'; 4 | export const SET_USER_INFO = 'SET_USER_INFO'; 5 | export const SET_COLLECT_LIST = 'SET_COLLECT_LIST'; 6 | export const SET_LIKE_LIST = 'SET_LIKE_LIST'; -------------------------------------------------------------------------------- /src/utils/aes.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js'; 2 | // AES-128-CBC偏移量 3 | const CBCIV = "abcdefgabcdefg12"; 4 | export default { 5 | //加密 6 | encrypt(data: string) { 7 | data = CryptoJS.MD5("copyright." + data + "pms@2016").toString(); 8 | let key = CryptoJS.enc.Utf8.parse(CBCIV); 9 | let secretData = CryptoJS.enc.Utf8.parse(data); 10 | let encrypted = CryptoJS.AES.encrypt( 11 | secretData, 12 | key, 13 | { 14 | iv: CryptoJS.enc.Utf8.parse(CBCIV), 15 | mode: CryptoJS.mode.CBC, 16 | padding: CryptoJS.pad.Pkcs7 17 | } 18 | ); 19 | return encrypted.toString(); 20 | }, 21 | 22 | //解密 23 | decrypt(data:string) { 24 | let key = CryptoJS.enc.Utf8.parse(CBCIV); 25 | let decrypt = CryptoJS.AES.decrypt( 26 | data, 27 | key, 28 | { 29 | iv: CryptoJS.enc.Utf8.parse(CBCIV), 30 | mode: CryptoJS.mode.CBC, 31 | padding: CryptoJS.pad.Pkcs7 32 | }); 33 | return CryptoJS.enc.Utf8.stringify(decrypt).toString(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | export function getToken(key: string) { 4 | return Cookies.get(key) 5 | } 6 | 7 | export function setToken(key: string, val: string) { 8 | return Cookies.set(key, val) 9 | } 10 | 11 | export function removeToken(key:string) { 12 | return Cookies.remove(key) 13 | } -------------------------------------------------------------------------------- /src/utils/loadjs.js: -------------------------------------------------------------------------------- 1 | function loadJs(src) { 2 | return new Promise((resolve, reject) => { 3 | let script = document.createElement('script') 4 | script.type = "text/javascript" 5 | script.onload = () => { 6 | resolve() 7 | } 8 | script.onerror = () => { 9 | reject() 10 | } 11 | script.src = src 12 | document.getElementsByTagName('body')[0].appendChild(script) 13 | }) 14 | } 15 | 16 | export default loadJs -------------------------------------------------------------------------------- /src/utils/tool.ts: -------------------------------------------------------------------------------- 1 | /** 设置导航的标题 2 | * @param{vm} context 当前上下文this 3 | * @param{Boolean} showTitle 是否显示title 4 | * @param{Boolean} showLeftTitle 是否显示左边title 5 | * @param{String} leftTitle 左边title内容 6 | * @param{String} back 返回路由路径 7 | * @return{void} 空 8 | */ 9 | export function siteNavTitle(vm: any, showTitle: boolean, showLeftTitle: boolean, leftTitle: string | string[], back: string = ''): void { 10 | let type = { 11 | back, 12 | showTitle, 13 | showLeftTitle, 14 | leftTitle 15 | } 16 | vm.$store.commit('SET_NAVBAR_TITLE', type); 17 | } 18 | //防抖 19 | export class Debounce { 20 | private wait: number 21 | constructor(wait: number) { 22 | this.wait = wait 23 | } 24 | use(fn: Function) { 25 | let timeout: any = null; 26 | return (...args: any[]): void => { 27 | clearTimeout(timeout) 28 | timeout = setTimeout(() => { 29 | fn.call(this, ...args) 30 | }, this.wait) 31 | } 32 | } 33 | } 34 | //节流 35 | export class Throttle { 36 | private delay: number 37 | constructor(delay: number) { 38 | this.delay = delay 39 | } 40 | use(fn: Function) { 41 | var timer: any = null; 42 | return (...args: any[]) => { 43 | if (!timer) { 44 | timer = setTimeout(() => { 45 | fn.call(this, ...args); 46 | timer = null; 47 | }, this.delay); 48 | } 49 | } 50 | } 51 | } 52 | // //防抖 53 | // export function debounce(fn: Function, wait: number) { 54 | // let timeout: any = null; 55 | // return function (this: any, ...args: any[]) { 56 | // clearTimeout(timeout) 57 | // timeout = setTimeout(() => { 58 | // fn.call(this, ...args) 59 | // }, wait) 60 | // } 61 | // } 62 | // //节流 63 | // export function throttle(fn: Function, delay: number) { 64 | // var timer: any = null; 65 | // return function (this: any, ...args: any[]) { 66 | // if (!timer) { 67 | // timer = setTimeout(() => { 68 | // fn.apply(this, args); 69 | // timer = null; 70 | // }, delay); 71 | // } 72 | // } 73 | // } -------------------------------------------------------------------------------- /src/utils/vw.css: -------------------------------------------------------------------------------- 1 | /** 2 | * view-port list: 3 | 320x480 4 | 320x568 5 | 320x570 6 | 360x592 7 | 360x598 8 | 360x604 9 | 360x640 10 | 360x720 11 | 375x667 12 | 375x812 13 | 393x699 14 | 412x732 15 | 414x736 16 | 480x854 17 | 540x960 18 | 640x360 19 | 720x1184 20 | 720x1280 21 | 800x600 22 | 1024x768 23 | 1080x1812 24 | 1080x1920 25 | */ 26 | html { 27 | font-size: -webkit-calc(13.33333333vw); 28 | font-size: calc(13.33333333vw); 29 | } 30 | @media screen and (max-width: 320px) { 31 | html { 32 | font-size: 42.667px; 33 | font-size: -webkit-calc(13.33333333vw); 34 | font-size: calc(13.33333333vw); 35 | } 36 | } 37 | @media screen and (min-width: 321px) and (max-width: 360px) { 38 | html { 39 | font-size: 48px; 40 | font-size: -webkit-calc(13.33333333vw); 41 | font-size: calc(13.33333333vw); 42 | } 43 | } 44 | @media screen and (min-width: 361px) and (max-width: 375px) { 45 | html { 46 | font-size: 50px; 47 | font-size: -webkit-calc(13.33333333vw); 48 | font-size: calc(13.33333333vw); 49 | } 50 | } 51 | @media screen and (min-width: 376px) and (max-width: 393px) { 52 | html { 53 | font-size: 52.4px; 54 | font-size: -webkit-calc(13.33333333vw); 55 | font-size: calc(13.33333333vw); 56 | } 57 | } 58 | @media screen and (min-width: 394px) and (max-width: 412px) { 59 | html { 60 | font-size: 54.93px; 61 | font-size: -webkit-calc(13.33333333vw); 62 | font-size: calc(13.33333333vw); 63 | } 64 | } 65 | @media screen and (min-width: 413px) and (max-width: 414px) { 66 | html { 67 | font-size: 55.2px; 68 | font-size: -webkit-calc(13.33333333vw); 69 | font-size: calc(13.33333333vw); 70 | } 71 | } 72 | @media screen and (min-width: 415px) and (max-width: 480px) { 73 | html { 74 | font-size: 64px; 75 | font-size: -webkit-calc(13.33333333vw); 76 | font-size: calc(13.33333333vw); 77 | } 78 | } 79 | @media screen and (min-width: 481px) and (max-width: 540px) { 80 | html { 81 | font-size: 72px; 82 | font-size: -webkit-calc(13.33333333vw); 83 | font-size: calc(13.33333333vw); 84 | } 85 | } 86 | @media screen and (min-width: 541px) and (max-width: 640px) { 87 | html { 88 | font-size: 85.33px; 89 | font-size: -webkit-calc(13.33333333vw); 90 | font-size: calc(13.33333333vw); 91 | } 92 | } 93 | @media screen and (min-width: 641px) and (max-width: 720px) { 94 | html { 95 | font-size: 96px; 96 | font-size: -webkit-calc(13.33333333vw); 97 | font-size: calc(13.33333333vw); 98 | } 99 | } 100 | @media screen and (min-width: 721px) and (max-width: 768px) { 101 | html { 102 | font-size: 102.4px; 103 | font-size: -webkit-calc(13.33333333vw); 104 | font-size: calc(13.33333333vw); 105 | } 106 | } 107 | @media screen and (min-width: 769px) { 108 | html { 109 | font-size: 102.4px; 110 | font-size: -webkit-calc(13.33333333vw); 111 | font-size: calc(13.33333333vw); 112 | } 113 | } -------------------------------------------------------------------------------- /src/views/BookInfo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /src/views/Layout.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 30 | -------------------------------------------------------------------------------- /src/views/book/article/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 105 | 106 | 176 | -------------------------------------------------------------------------------- /src/views/booklist/component/List.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 93 | 94 | 181 | -------------------------------------------------------------------------------- /src/views/booklist/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 32 | -------------------------------------------------------------------------------- /src/views/category/detail.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 106 | 107 | 184 | -------------------------------------------------------------------------------- /src/views/category/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 77 | 78 | 128 | -------------------------------------------------------------------------------- /src/views/home/component/Fun.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 79 | -------------------------------------------------------------------------------- /src/views/home/component/List.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 93 | 94 | 253 | -------------------------------------------------------------------------------- /src/views/home/component/LunBo.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 48 | -------------------------------------------------------------------------------- /src/views/home/component/data.ts: -------------------------------------------------------------------------------- 1 | export const funList = [ 2 | { 3 | id: 1, 4 | text: "分类", 5 | className: "icon-sort", 6 | href: "/category" 7 | }, 8 | { 9 | id: 2, 10 | text: "排行榜", 11 | className: "icon-rank", 12 | href: "/rank" 13 | }, 14 | { 15 | id: 3, 16 | text: "免费", 17 | className: "icon-free", 18 | href: "/book/list/免费小说" 19 | }, 20 | { 21 | id: 4, 22 | text: "完本", 23 | className: "icon-end", 24 | href: "/category" 25 | }, 26 | { 27 | id: 5, 28 | text: "大神", 29 | className: "icon-god", 30 | href: "/category" 31 | } 32 | ] 33 | 34 | export const dataList = [ 35 | { 36 | fictionId: "1243", 37 | imgSrc: "https://bookcover.yuewen.com/qdbimg/349573/1013451836/150", 38 | title: "哈哈", 39 | author: "五重楼", 40 | tags: ['奇幻','连载'], 41 | desc: "我是这诸天万族的劫!已有完本作品《全球高武》《重生之财源滚滚》,没看过的书友可以去看看,新书收藏一下慢慢养。" 42 | } 43 | ] -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 153 | 154 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 66 | 67 | 127 | -------------------------------------------------------------------------------- /src/views/person/component/User.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 139 | 140 | 286 | -------------------------------------------------------------------------------- /src/views/person/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 28 | 29 | 35 | -------------------------------------------------------------------------------- /src/views/rank/component/List.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 103 | 104 | 190 | -------------------------------------------------------------------------------- /src/views/rank/component/rtype.ts: -------------------------------------------------------------------------------- 1 | let rtype: Array = [ 2 | ['原创风云榜', 'yuepiao'], 3 | ['畅销榜', 'hotsales'], 4 | ['阅读榜', 'readIndex'], 5 | ['新增粉丝榜', 'newfans'], 6 | ['推荐榜', 'rec'], 7 | ['打赏榜', 'reward'], 8 | ['更新榜', 'update'], 9 | ['签约榜', 'sign'], 10 | ['新书榜', 'newdbook'], 11 | ['新人榜', 'newauthor'] 12 | ] 13 | 14 | export default rtype -------------------------------------------------------------------------------- /src/views/rank/detail.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 105 | 106 | 119 | -------------------------------------------------------------------------------- /src/views/rank/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 62 | 63 | 148 | -------------------------------------------------------------------------------- /src/views/search/component/searchList.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 103 | 104 | 184 | -------------------------------------------------------------------------------- /src/views/search/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 40 | 41 | 52 | -------------------------------------------------------------------------------- /static/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tmfree/vue-fiction/fc92e026d5017b02ce9bc559d299950c48f4dfc0/static/1.png -------------------------------------------------------------------------------- /static/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tmfree/vue-fiction/fc92e026d5017b02ce9bc559d299950c48f4dfc0/static/2.png -------------------------------------------------------------------------------- /static/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tmfree/vue-fiction/fc92e026d5017b02ce9bc559d299950c48f4dfc0/static/3.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": false, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "allowJs": true, 14 | "baseUrl": ".", 15 | "typeRoots": ["types"], 16 | "types": [ 17 | "webpack-env" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const CompressionWebpackPlugin = require('compression-webpack-plugin'); 2 | const isProduction = process.env.NODE_ENV === 'production'; 3 | module.exports = { 4 | lintOnSave: false, 5 | configureWebpack: config => { 6 | if (isProduction) { 7 | // 开启gzip压缩 8 | config.plugins.push(new CompressionWebpackPlugin({ 9 | algorithm: 'gzip', 10 | test: /\.js$|\.html$|\.json$|\.css/, 11 | threshold: 10240, 12 | minRatio: 0.8 13 | })) 14 | //分离js 15 | config.optimization = { 16 | runtimeChunk: 'single', 17 | splitChunks: { 18 | chunks: 'all', 19 | maxInitialRequests: Infinity, 20 | minSize: 20000, 21 | cacheGroups: { 22 | vendor: { 23 | test: /[\\/]node_modules[\\/]/, 24 | name(module) { 25 | // get the name. E.g. node_modules/packageName/not/this/part.js 26 | // or node_modules/packageName 27 | const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1] 28 | // npm package names are URL-safe, but some servers don't like @ symbols 29 | return `npm.${packageName.replace('@', '')}` 30 | } 31 | } 32 | } 33 | } 34 | } 35 | // 取消webpack警告的性能提示 36 | config.performance = { 37 | hints: 'warning', 38 | //入口起点的最大体积 39 | maxEntrypointSize: 50000000, 40 | //生成文件的最大体积 41 | maxAssetSize: 30000000, 42 | //只给出 js 文件的性能提示 43 | assetFilter: function (assetFilename) { 44 | return assetFilename.endsWith('.js'); 45 | } 46 | } 47 | } else { 48 | // 为开发环境修改配置... 49 | } 50 | }, 51 | // 如果你不需要使用eslint,把lintOnSave设为false即可 52 | lintOnSave: true, 53 | // 打包时不生成.map文件 54 | productionSourceMap: false 55 | } --------------------------------------------------------------------------------