├── .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 | 
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 | 
87 |
88 | 
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/src/components/common/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 山楂小说
6 |
7 |
8 |
9 | {{ navBar.leftTitle }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 | 首页
36 |
37 |
38 |
39 |
40 |
41 | 分类
42 |
43 |
44 |
45 |
46 |
47 | 排行榜
48 |
49 |
50 |
56 |
62 |
63 |
64 |
65 | 账号
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
94 |
95 |
250 |
251 |
259 |
--------------------------------------------------------------------------------
/src/components/common/NavHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{title}}
6 |
7 |
8 | {{desc}}
9 |
10 |
11 |
12 | 更多
13 |
14 |
15 |
16 |
17 |
28 |
29 |
64 |
72 |
--------------------------------------------------------------------------------
/src/components/common/PullUp.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
150 |
151 |
161 |
--------------------------------------------------------------------------------
/src/components/common/backTop.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
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 |
2 |
3 |
4 |
5 |
15 |
16 |
18 |
--------------------------------------------------------------------------------
/src/views/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
30 |
--------------------------------------------------------------------------------
/src/views/book/article/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ bookInfo.title }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{ dataList.title }}
15 |
16 | {{ item.field }}
17 |
18 |
19 | -- 到底啦 --
20 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
35 |
36 |
105 |
106 |
176 |
--------------------------------------------------------------------------------
/src/views/booklist/component/List.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
17 | {{ item.bName }}
18 |
19 |
20 | {{ item.desc }}
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 | {{ item.bAuth }}
32 |
33 |
34 | {{ item.cat }}
41 | {{
42 | item.state
43 | }}
44 | {{ item.cnt }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
93 |
94 |
181 |
--------------------------------------------------------------------------------
/src/views/booklist/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
25 |
26 |
32 |
--------------------------------------------------------------------------------
/src/views/category/detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
17 | {{ item.bName }}
18 |
19 |
20 | {{ item.desc }}
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 | {{ item.bAuth }}
32 |
33 |
34 | {{ item.cat }}
41 | {{
42 | item.state
43 | }}
44 | {{ item.cnt }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
106 |
107 |
184 |
--------------------------------------------------------------------------------
/src/views/category/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 | {{ item.cName }}
13 | 共{{ item.total }}本作品
14 |
15 |
16 |
17 | {{ sub.cName }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
77 |
78 |
128 |
--------------------------------------------------------------------------------
/src/views/home/component/Fun.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
20 |
21 |
79 |
--------------------------------------------------------------------------------
/src/views/home/component/List.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{item.bName}}
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 | {{item.bName}}
23 |
24 |
25 | {{item.desc}}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {{item.bAuth}}
34 |
35 |
36 | {{item.cat}}
37 | {{item.state}}
38 | {{item.cnt}}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
71 |
72 |
73 |
74 |
93 |
94 |
253 |
--------------------------------------------------------------------------------
/src/views/home/component/LunBo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 | {{ days }}
16 | {{ hours }}
17 | {{ minutes }}
18 | {{ seconds }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
33 |
34 |
42 |
43 |
47 |
48 |
52 |
53 |
54 |
55 |
153 |
154 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
山楂登录
6 |
Welcome to Shanzha
7 |
8 |
9 |
10 |
11 |
12 |
19 |
27 |
28 | 登录
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
66 |
67 |
127 |
--------------------------------------------------------------------------------
/src/views/person/component/User.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
{{ userInfo.nickName }}
16 |
立即登录
17 |
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 | 0
26 | 月票
27 |
28 |
29 | -
30 |
31 | 0
32 | 推荐票
33 |
34 |
35 | -
36 |
37 | {{ collectList.length }}
38 | 收藏
39 |
40 |
41 | -
42 |
43 | {{ likeList.length }}
44 | 点赞
45 |
46 |
47 |
48 |
49 |
50 | -
51 |
52 |
经验等级
53 |
54 |
55 |
56 | -
57 |
58 |
消息中心
59 |
60 |
61 |
62 | -
63 |
64 |
65 | 会员等级
66 |
67 |
68 |
69 |
70 | -
71 |
72 |
安全中心
73 |
74 |
75 |
76 | -
77 |
78 |
帮助中心
79 |
80 |
81 |
82 |
83 |
84 | 退出登录
85 |
86 |
87 |
88 |
89 |
90 |
139 |
140 |
286 |
--------------------------------------------------------------------------------
/src/views/person/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
28 |
29 |
35 |
--------------------------------------------------------------------------------
/src/views/rank/component/List.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
17 | {{ item.bName }}
18 |
19 |
20 | {{ item.desc }}
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 | 作者:{{ item.bAuth }}
32 |
33 |
34 | {{ item.cat }}
41 | {{
42 | item.state
43 | }}
44 | {{ item.cnt }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
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 |
2 |
3 |
10 |
11 |
12 | 全部
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 玄幻
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 武侠
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 都市
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 现实
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 军事
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | 历史
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 悬疑
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 轻小说
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
105 |
106 |
119 |
--------------------------------------------------------------------------------
/src/views/rank/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ item.title.substr(0, 2) }}
9 |
10 | {{ item.title.substr(2, item.title.length) }}
11 |
12 | {{ item.title }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | -
21 |
22 | {{ sub.cName }}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
62 |
63 |
148 |
--------------------------------------------------------------------------------
/src/views/search/component/searchList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
17 |
18 |
19 | {{ item.bName }}
20 |
21 |
22 | {{ item.desc }}
23 |
24 |
25 |
29 |
30 |
31 |
32 |
33 | 作者:{{ item.bAuth }}
34 |
35 |
36 | {{ item.cat }}
43 | {{
44 | item.state
45 | }}
46 | {{ item.cnt }}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
103 |
104 |
184 |
--------------------------------------------------------------------------------
/src/views/search/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
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 | }
--------------------------------------------------------------------------------