Browse Source

青涩知夏-创建了项目

huanghuijie 4 years ago
commit
f2b4051a89
98 changed files with 40180 additions and 0 deletions
  1. 1 0
      .env.production
  2. 3 0
      .gitattributes
  3. 25 0
      .gitignore
  4. 88 0
      README.md
  5. 15917 0
      package-lock.json
  6. 50 0
      package.json
  7. 37 0
      public/assets/autoload.js
  8. BIN
      public/assets/flat-ui-icons-regular.eot
  9. 126 0
      public/assets/flat-ui-icons-regular.svg
  10. BIN
      public/assets/flat-ui-icons-regular.ttf
  11. BIN
      public/assets/flat-ui-icons-regular.woff
  12. 13 0
      public/assets/jquery-ui.min.js
  13. 2 0
      public/assets/jquery.min.js
  14. 4231 0
      public/assets/live2d.js
  15. 7 0
      public/assets/live2d.min.js
  16. 405 0
      public/assets/waifu-tips.js
  17. 101 0
      public/assets/waifu-tips.json
  18. 2 0
      public/assets/waifu-tips.min.js
  19. 290 0
      public/assets/waifu.css
  20. 1 0
      public/assets/waifu.min.css
  21. BIN
      public/favicon.ico
  22. 21 0
      public/index.html
  23. 33 0
      public/js/anime.min.js
  24. 146 0
      public/js/fireworks.js
  25. 59 0
      src/App.js
  26. 44 0
      src/components/BackTop/index.js
  27. 82 0
      src/components/BackTop/style.js
  28. 34 0
      src/components/Footer/index.js
  29. 68 0
      src/components/Footer/style.js
  30. 273 0
      src/components/Header/index.js
  31. 78 0
      src/components/Header/store/actionCreators.js
  32. 3 0
      src/components/Header/store/constants.js
  33. 5 0
      src/components/Header/store/index.js
  34. 28 0
      src/components/Header/store/reducer.js
  35. 389 0
      src/components/Header/style.js
  36. 53 0
      src/components/List/index.js
  37. 162 0
      src/components/List/style.js
  38. 32 0
      src/components/PagInation/index.js
  39. 39 0
      src/components/PagInation/style.js
  40. 18 0
      src/components/ScrollToTop/index.js
  41. 6 0
      src/index.js
  42. 67 0
      src/lib/auth.js
  43. 19 0
      src/lib/axios.js
  44. 42 0
      src/lib/openWindow.js
  45. 57 0
      src/lib/public.js
  46. 134 0
      src/pages/archives/index.js
  47. 366 0
      src/pages/archives/style.js
  48. 182 0
      src/pages/article/components/Comments.js
  49. 159 0
      src/pages/article/index.js
  50. 653 0
      src/pages/article/style.js
  51. 79 0
      src/pages/article/tocify.js
  52. 129 0
      src/pages/category/index.js
  53. 131 0
      src/pages/category/style.js
  54. 20 0
      src/pages/error/index.js
  55. 51 0
      src/pages/error/style.js
  56. 120 0
      src/pages/home/components/Banner.js
  57. 48 0
      src/pages/home/components/Feature.js
  58. 102 0
      src/pages/home/components/List.js
  59. 99 0
      src/pages/home/index.js
  60. 67 0
      src/pages/home/store/actionCreators.js
  61. 5 0
      src/pages/home/store/constants.js
  62. 5 0
      src/pages/home/store/index.js
  63. 90 0
      src/pages/home/store/reducer.js
  64. 780 0
      src/pages/home/style.js
  65. 133 0
      src/pages/links/index.js
  66. 288 0
      src/pages/links/style.js
  67. 98 0
      src/pages/search/index.js
  68. 70 0
      src/pages/search/style.js
  69. 96 0
      src/pages/tags/index.js
  70. 112 0
      src/pages/tags/list.js
  71. 145 0
      src/pages/tags/style.js
  72. 32 0
      src/router.js
  73. BIN
      src/statics/iconfont/iconfont.eot
  74. 23 0
      src/statics/iconfont/iconfont.js
  75. 38 0
      src/statics/iconfont/iconfont.svg
  76. BIN
      src/statics/iconfont/iconfont.ttf
  77. BIN
      src/statics/iconfont/iconfont.woff
  78. BIN
      src/statics/iconfont/iconfont.woff2
  79. BIN
      src/statics/images/404.png
  80. BIN
      src/statics/images/ayuda.cur
  81. 1 0
      src/statics/images/email.svg
  82. BIN
      src/statics/images/grid.png
  83. BIN
      src/statics/images/hr.gif
  84. 1 0
      src/statics/images/next-b.svg
  85. BIN
      src/statics/images/normal.cur
  86. 193 0
      src/statics/images/nosum.svg
  87. BIN
      src/statics/images/qq.png
  88. BIN
      src/statics/images/scroll.png
  89. BIN
      src/statics/images/sina.png
  90. BIN
      src/statics/images/texto.cur
  91. BIN
      src/statics/images/twitter.png
  92. BIN
      src/statics/images/wave-bot.png
  93. BIN
      src/statics/images/wave-mid.png
  94. BIN
      src/statics/images/wave-top.png
  95. 7 0
      src/store/index.js
  96. 49 0
      src/store/reducer.js
  97. 141 0
      src/style.js
  98. 12476 0
      yarn.lock

+ 1 - 0
.env.production

@@ -0,0 +1 @@
+GENERATE_SOURCEMAP=false

+ 3 - 0
.gitattributes

@@ -0,0 +1,3 @@
+*.js linguist-language=React.js
+*.css linguist-language=React.js
+*.html linguist-language=React.js

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+/.idea
+# testing
+/coverage
+
+# production
+/build
+
+# by
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*

+ 88 - 0
README.md

@@ -0,0 +1,88 @@
+<h1 align="center"><a href="https://github.com/byteblogs168/theme-default3/" target="_blank">helloblog-sakura</a></h1>
+
+> [Hello Blog](http://helloblog.byteblogs.com/) 是一个前后端分离的博客系统,为了解除开发人员对后端的束缚,真正做到的一个面向接口开发的博客系统。
+------------------------------
+
+## 简介
+> sakura 风格,作者--白小纯(此 README 等待原作者修改中)
+>> API使用 helloblog,后台管理使用 helloblog-admin
+
+## helloblog 地址
+> [官网](http://helloblog.byteblogs.com/) | [社区](https://byteblogs.com) | [QQ 交流群](https://shang.qq.com/wpa/qunwpa?idkey=4f8653da80e632ef86ca1d57ccf8751602940d1036c79b04a3a5bc668adf8864) | 
+
+## 声明
+
+> 本项目,主要宗旨在于,简单、易用、不烧脑的博客,并且基于helloblog,搭建一个属于博主们的博客圈
+>> 后端API <https://github.com/byteblogs168/hello-blog><br>
+>> 后端管理系统 <https://github.com/byteblogs168/hello-blog-admin><br>
+>> 前端主题地址:<https://github.com/byteblogs168/theme-react-sakura/><br>
+## 快速开始
+
+### 克隆项目到本地
+```bash
+clone https://github.com/byteblogs168/theme-default3.git
+```
+
+### 打开 helloblog-admin 代码后进行构建
+```bash
+npm install --registry=https://registry.npm.taobao.org
+```
+### 启动后端管理系统
+```bash
+vue-cli-service serve
+```
+
+详细文档请移步:<http://helloblog.byteblogs.com/docs/>
+
+## 博客示例
+
+请移步: <https://preview.byteblogs.com/>
+
+## 生态圈
+
+- 后端代码(hello-blog):<https://github.com/byteblogs168/hello-blog>
+- 后端代码(hello-blog):<https://github.com/byteblogs168/hello-blog-admin>
+- 主题仓库:<https://github.com/byteblogs168/theme-react-sakura/><br>
+
+
+## 使用 helloblog 的优秀博主
+- 青涩知夏:<https://www.nosum.cn/>
+- 踏歌长行:<https://www.bygit.cn/>
+
+
+
+## 捐赠
+
+> 如果 helloblog 对你有帮助,可以请作者喝杯☕️。
+
+| 支付宝  | 微信 |
+| :------------: |  :------------: |
+| <img src="http://image.byteblogs.com/FvM2HsxIesxN41bPJCtRpUi8LQgA" width="200"/>  |  <img src="http://image.byteblogs.com/Fn33krSt1uIm4sgqBE67fkZL2-__" width="200"/>
+
+## 预览图
+
+<img src="https://cos.nosum.cn/preview/helloblog-theme/helloblog-theme3-perview%20%281%29.jpg" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-theme/helloblog-theme3-perview%20%282%29.jpg" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-theme/helloblog-theme3-perview%20%283%29.jpg" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-theme/helloblog-theme3-perview%20%284%29.jpg" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-theme/helloblog-theme3-perview%20%285%29.jpg" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-theme/helloblog-theme3-perview%20%286%29.jpg" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-theme/helloblog-theme3-perview%20%287%29.jpg" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-admin/helloblog-admin-preview%20%282%29.png" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-admin/helloblog-admin-preview%20%283%29.png" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-admin/helloblog-admin-preview%20%284%29.png" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-admin/helloblog-admin-preview%20%285%29.png" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-admin/helloblog-admin-preview%20%286%29.png" width="600"/>
+
+<img src="https://cos.nosum.cn/preview/helloblog-admin/helloblog-admin-preview%20%287%29.png" width="600"/>

File diff suppressed because it is too large
+ 15917 - 0
package-lock.json


+ 50 - 0
package.json

@@ -0,0 +1,50 @@
+{
+  "name": "blog",
+  "version": "0.1.0",
+  "private": true,
+  "proxy": "https://www.nosum.cn/",
+  "dependencies": {
+    "@testing-library/jest-dom": "^4.2.4",
+    "@testing-library/react": "^9.3.2",
+    "@testing-library/user-event": "^7.1.2",
+    "antd": "^3.26.2",
+    "aplayer": "^1.10.1",
+    "axios": "^0.19.0",
+    "highlight.js": "^9.17.1",
+    "immutable": "^4.0.0-rc.12",
+    "js-cookie": "^2.2.1",
+    "lodash": "^4.17.15",
+    "marked": "^0.8.0",
+    "react": "^16.12.0",
+    "react-dom": "^16.12.0",
+    "react-redux": "^7.1.3",
+    "react-router": "^5.1.2",
+    "react-router-dom": "^5.1.2",
+    "react-scripts": "3.4.0",
+    "redux": "^4.0.4",
+    "redux-immutable": "^4.0.0",
+    "redux-thunk": "^2.3.0",
+    "styled-components": "^4.4.1"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": "react-app"
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  }
+}

+ 37 - 0
public/assets/autoload.js

@@ -0,0 +1,37 @@
+try {
+    if (!isMobileBrowser()) {
+        $("<link>").attr({href: "/assets/waifu.min.css?v=1.4.2", rel: "stylesheet", type: "text/css"}).appendTo('head');
+        $('body').append('<div class="waifu"><div class="waifu-tips"></div><canvas id="live2d" class="live2d"/><div class="waifu-tool"><span class="fui-home"/> <span class="fui-chat"/> <span class="fui-eye"/> <span class="fui-user"/> <span class="fui-photo"/> <span class="fui-info-circle"/> <span class="fui-cross"/></div></div>');
+        $.ajax({url: '/assets/waifu-tips.min.js?v=1.4.2', dataType: "script", cache: true, async: false});
+        $.ajax({url: '/assets/live2d.min.js?v=1.0.5', dataType: "script", cache: true, async: false});
+        /* 可直接修改部分参数 */
+        live2d_settings['hitokotoAPI'] = 'hitokoto.cn';  // 一言 API
+        live2d_settings['modelId'] = 6;                  // 默认模型 ID
+        live2d_settings['modelTexturesId'] = 13;         // 默认材质 ID
+        live2d_settings['modelStorage'] = false;         // 不储存模型 ID
+        live2d_settings['waifuEdgeSide'] = 'right:30';
+        live2d_settings['homePageUrl'] = '/';
+        /* 在 initModel 前添加 */
+        initModel('/assets/waifu-tips.json');
+    } else {
+        console.log('手机端不显看板娘~!')
+    }
+} catch (err) {
+    console.log('[Error] JQuery is not defined.')
+}
+
+function isMobileBrowser() {
+    var sUserAgent = navigator.userAgent.toLowerCase();
+    var bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
+    var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
+    var bIsMidp = sUserAgent.match(/midp/i) == "midp";
+    var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
+    var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
+    var bIsAndroid = sUserAgent.match(/android/i) == "android";
+    var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
+    var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
+    if (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
+        return true;
+    } else
+        return false;
+}

BIN
public/assets/flat-ui-icons-regular.eot


File diff suppressed because it is too large
+ 126 - 0
public/assets/flat-ui-icons-regular.svg


BIN
public/assets/flat-ui-icons-regular.ttf


BIN
public/assets/flat-ui-icons-regular.woff


File diff suppressed because it is too large
+ 13 - 0
public/assets/jquery-ui.min.js


File diff suppressed because it is too large
+ 2 - 0
public/assets/jquery.min.js


File diff suppressed because it is too large
+ 4231 - 0
public/assets/live2d.js


File diff suppressed because it is too large
+ 7 - 0
public/assets/live2d.min.js


File diff suppressed because it is too large
+ 405 - 0
public/assets/waifu-tips.js


+ 101 - 0
public/assets/waifu-tips.json

@@ -0,0 +1,101 @@
+{
+    "waifu": {
+        "console_open_msg": ["哈哈,你打开了控制台,是想要看看我的秘密吗?"],
+        "copy_message": ["你都复制了些什么呀,转载要记得加上出处哦"],
+        "screenshot_message": ["照好了嘛,是不是很可爱呢?"],
+        "hidden_message": ["我们还能再见面的吧…"],
+        "load_rand_textures": ["我还没有其他衣服呢", "我的新衣服好看嘛"],
+        "hour_tips": {
+            "t5-7": ["早上好!一日之计在于晨,美好的一天就要开始了"],
+            "t7-11": ["上午好!工作顺利嘛,不要久坐,多起来走动走动哦!"],
+            "t11-14": ["中午了,工作了一个上午,现在是午餐时间!"],
+            "t14-17": ["午后很容易犯困呢,今天的运动目标完成了吗?"],
+            "t17-19": ["傍晚了!窗外夕阳的景色很美丽呢,最美不过夕阳红~"],
+            "t19-21": ["晚上好,今天过得怎么样?"],
+            "t21-23": ["已经这么晚了呀,早点休息吧,晚安~"],
+            "t23-5": ["你是夜猫子呀?这么晚还不睡觉,明天起的来嘛"],
+            "default": ["嗨~ 快来逗我玩吧!"]
+        },
+        "referrer_message": {
+            "localhost": ["欢迎阅读<span style=\"color:#0099cc;\">『", "』</span>", " - "],
+            "baidu": ["Hello! 来自 百度搜索 的朋友<br>你是搜索 <span style=\"color:#0099cc;\">", "</span> 找到的我吗?"],
+            "so": ["Hello! 来自 360搜索 的朋友<br>你是搜索 <span style=\"color:#0099cc;\">", "</span> 找到的我吗?"],
+            "google": ["Hello! 来自 谷歌搜索 的朋友<br>欢迎阅读<span style=\"color:#0099cc;\">『", "』</span>", " - "],
+            "default": ["Hello! 来自 <span style=\"color:#0099cc;\">", "</span> 的朋友"],
+            "none": ["欢迎阅读<span style=\"color:#0099cc;\">『", "』</span>", " - "]
+        },
+        "referrer_hostname": {
+            "example.com": ["示例网站"],
+            "www.fghrsh.net": ["FGHRSH 的博客"]
+        },
+        "model_message": {
+            "1": ["来自 Potion Maker 的 Pio 酱 ~"],
+            "2": ["来自 Potion Maker 的 Tia 酱 ~"]  
+        },
+        "hitokoto_api_message": {
+            "lwl12.com": ["这句一言来自 <span style=\"color:#0099cc;\">『{source}』</span>", ",是 <span style=\"color:#0099cc;\">{creator}</span> 投稿的", "。"],
+            "fghrsh.net": ["这句一言出处是 <span style=\"color:#0099cc;\">『{source}』</span>,是 <span style=\"color:#0099cc;\">FGHRSH</span> 在 {date} 收藏的!"],
+            "jinrishici.com": ["这句诗词出自 <span style=\"color:#0099cc;\">《{title}》</span>,是 {dynasty}诗人 {author} 创作的!"],
+            "hitokoto.cn": ["这句一言来自 <span style=\"color:#0099cc;\">『{source}』</span>,是 <span style=\"color:#0099cc;\">{creator}</span> 在 hitokoto.cn 投稿的。"]
+        }
+    },
+    "mouseover": [
+        { "selector": ".container a[href^='http']", "text": ["要看看 <span style=\"color:#0099cc;\">{text}</span> 么?"] },
+        { "selector": ".fui-home", "text": ["点击前往首页,想回到上一页可以使用浏览器的后退功能哦"] },
+        { "selector": ".fui-chat", "text": ["一言一语,一颦一笑。一字一句,一颗赛艇。"] },
+        { "selector": ".fui-eye", "text": ["嗯··· 要切换 看板娘 吗?"] },
+        { "selector": ".fui-user", "text": ["喜欢换装 Play 吗?"] },
+        { "selector": ".fui-photo", "text": ["要拍张纪念照片吗?"] },
+        { "selector": ".fui-info-circle", "text": ["这里有关于我的信息呢"] },
+        { "selector": ".fui-cross", "text": ["你不喜欢我了吗..."] },
+        { "selector": "#tor_show", "text": ["翻页比较麻烦吗,点击可以显示这篇文章的目录呢"] },
+        { "selector": "#comment_go", "text": ["想要去评论些什么吗?"] },
+        { "selector": "#night_mode", "text": ["深夜时要爱护眼睛呀"] },
+        { "selector": "#qrcode", "text": ["手机扫一下就能继续看,很方便呢"] },
+        { "selector": ".comment_reply", "text": ["要吐槽些什么呢"] },
+        { "selector": "#back-to-top", "text": ["回到开始的地方吧"] },
+        { "selector": "#author", "text": ["该怎么称呼你呢"] },
+        { "selector": "#mail", "text": ["留下你的邮箱,不然就是无头像人士了"] },
+        { "selector": "#url", "text": ["你的家在哪里呢,好让我去参观参观"] },
+        { "selector": "#textarea", "text": ["认真填写哦,垃圾评论是禁止事项"] },
+        { "selector": ".OwO-logo", "text": ["要插入一个表情吗"] },
+        { "selector": "#csubmit", "text": ["要[提交]^(Commit)了吗,首次评论需要审核,请耐心等待~"] },
+        { "selector": ".ImageBox", "text": ["点击图片可以放大呢"] },
+        { "selector": "input[name=s]", "text": ["找不到想看的内容?搜索看看吧"] },
+        { "selector": ".previous", "text": ["去上一页看看吧"] },
+        { "selector": ".next", "text": ["去下一页看看吧"] },
+        { "selector": ".dropdown-toggle", "text": ["这里是菜单"] },
+        { "selector": "c-player a.play-icon", "text": ["想要听点音乐吗"] },
+        { "selector": "c-player div.time", "text": ["在这里可以调整<span style=\"color:#0099cc;\">播放进度</span>呢"] },
+        { "selector": "c-player div.volume", "text": ["在这里可以调整<span style=\"color:#0099cc;\">音量</span>呢"] },
+        { "selector": "c-player div.list-button", "text": ["<span style=\"color:#0099cc;\">播放列表</span>里都有什么呢"] },
+        { "selector": "c-player div.lyric-button", "text": ["有<span style=\"color:#0099cc;\">歌词</span>的话就能跟着一起唱呢"] },
+        { "selector": ".waifu #live2d", "text": ["干嘛呢你,快把手拿开", "鼠…鼠标放错地方了!"] }
+    ],
+    "click": [
+        {
+            "selector": ".waifu #live2d",
+            "text": [
+                "是…是不小心碰到了吧",
+                "萝莉控是什么呀",
+                "你看到我的小熊了吗",
+                "再摸的话我可要报警了!⌇●﹏●⌇",
+                "110吗,这里有个变态一直在摸我(ó﹏ò。)"
+            ]
+        }
+    ],
+    "seasons": [
+        { "date": "01/01", "text": ["<span style=\"color:#0099cc;\">元旦</span>了呢,新的一年又开始了,今年是{year}年~"] },
+        { "date": "02/14", "text": ["又是一年<span style=\"color:#0099cc;\">情人节</span>,{year}年找到对象了嘛~"] },
+        { "date": "03/08", "text": ["今天是<span style=\"color:#0099cc;\">妇女节</span>!"] },
+        { "date": "03/12", "text": ["今天是<span style=\"color:#0099cc;\">植树节</span>,要保护环境呀"] },
+        { "date": "04/01", "text": ["悄悄告诉你一个秘密~<span style=\"background-color:#34495e;\">今天是愚人节,不要被骗了哦~</span>"] },
+        { "date": "05/01", "text": ["今天是<span style=\"color:#0099cc;\">五一劳动节</span>,计划好假期去哪里了吗~"] },
+        { "date": "06/01", "text": ["<span style=\"color:#0099cc;\">儿童节</span>了呢,快活的时光总是短暂,要是永远长不大该多好啊…"] },
+        { "date": "09/03", "text": ["<span style=\"color:#0099cc;\">中国人民抗日战争胜利纪念日</span>,铭记历史、缅怀先烈、珍爱和平、开创未来。"] },
+        { "date": "09/10", "text": ["<span style=\"color:#0099cc;\">教师节</span>,在学校要给老师问声好呀~"] },
+        { "date": "10/01", "text": ["<span style=\"color:#0099cc;\">国庆节</span>,新中国已经成立69年了呢"] },
+        { "date": "11/05-11/12", "text": ["今年的<span style=\"color:#0099cc;\">双十一</span>是和谁一起过的呢~"] },
+        { "date": "12/20-12/31", "text": ["这几天是<span style=\"color:#0099cc;\">圣诞节</span>,主人肯定又去剁手买买买了~"] }
+    ]
+}

File diff suppressed because it is too large
+ 2 - 0
public/assets/waifu-tips.min.js


+ 290 - 0
public/assets/waifu.css

@@ -0,0 +1,290 @@
+.waifu {
+    position: fixed;
+    bottom: 0;
+    z-index: 99;
+    font-size: 0;
+    -webkit-transform: translateY(3px);
+    transform: translateY(3px);
+}
+.waifu:hover {
+    -webkit-transform: translateY(0);
+    transform: translateY(0);
+}
+.waifu-tips {
+    opacity: 0;
+    margin: -20px 20px;
+    padding: 5px 10px;
+    border: 1px solid rgba(224, 186, 140, 0.62);
+    border-radius: 12px;
+    background-color: rgba(236, 217, 188, 0.5);
+    box-shadow: 0 3px 15px 2px rgba(191, 158, 118, 0.2);
+    text-overflow: ellipsis;
+    overflow: hidden;
+    position: absolute;
+    animation-delay: 5s;
+    animation-duration: 50s;
+    animation-iteration-count: infinite;
+    animation-name: shake;
+    animation-timing-function: ease-in-out;
+}
+.waifu-tool {
+    display: none;
+    color: #aaa;
+    top: 50px;
+    right: 10px;
+    position: absolute;
+}
+.waifu:hover .waifu-tool {
+    display: block;
+}
+.waifu-tool span {
+    display: block;
+    cursor: pointer;
+    color: #5b6c7d;
+    transition: 0.2s;
+}
+.waifu-tool span:hover {
+    color: #34495e;
+}
+.waifu #live2d{
+    position: relative;
+}
+
+@keyframes shake {
+    2% {
+        transform: translate(0.5px, -1.5px) rotate(-0.5deg);
+    }
+
+    4% {
+        transform: translate(0.5px, 1.5px) rotate(1.5deg);
+    }
+
+    6% {
+        transform: translate(1.5px, 1.5px) rotate(1.5deg);
+    }
+
+    8% {
+        transform: translate(2.5px, 1.5px) rotate(0.5deg);
+    }
+
+    10% {
+        transform: translate(0.5px, 2.5px) rotate(0.5deg);
+    }
+
+    12% {
+        transform: translate(1.5px, 1.5px) rotate(0.5deg);
+    }
+
+    14% {
+        transform: translate(0.5px, 0.5px) rotate(0.5deg);
+    }
+
+    16% {
+        transform: translate(-1.5px, -0.5px) rotate(1.5deg);
+    }
+
+    18% {
+        transform: translate(0.5px, 0.5px) rotate(1.5deg);
+    }
+
+    20% {
+        transform: translate(2.5px, 2.5px) rotate(1.5deg);
+    }
+
+    22% {
+        transform: translate(0.5px, -1.5px) rotate(1.5deg);
+    }
+
+    24% {
+        transform: translate(-1.5px, 1.5px) rotate(-0.5deg);
+    }
+
+    26% {
+        transform: translate(1.5px, 0.5px) rotate(1.5deg);
+    }
+
+    28% {
+        transform: translate(-0.5px, -0.5px) rotate(-0.5deg);
+    }
+
+    30% {
+        transform: translate(1.5px, -0.5px) rotate(-0.5deg);
+    }
+
+    32% {
+        transform: translate(2.5px, -1.5px) rotate(1.5deg);
+    }
+
+    34% {
+        transform: translate(2.5px, 2.5px) rotate(-0.5deg);
+    }
+
+    36% {
+        transform: translate(0.5px, -1.5px) rotate(0.5deg);
+    }
+
+    38% {
+        transform: translate(2.5px, -0.5px) rotate(-0.5deg);
+    }
+
+    40% {
+        transform: translate(-0.5px, 2.5px) rotate(0.5deg);
+    }
+
+    42% {
+        transform: translate(-1.5px, 2.5px) rotate(0.5deg);
+    }
+
+    44% {
+        transform: translate(-1.5px, 1.5px) rotate(0.5deg);
+    }
+
+    46% {
+        transform: translate(1.5px, -0.5px) rotate(-0.5deg);
+    }
+
+    48% {
+        transform: translate(2.5px, -0.5px) rotate(0.5deg);
+    }
+
+    50% {
+        transform: translate(-1.5px, 1.5px) rotate(0.5deg);
+    }
+
+    52% {
+        transform: translate(-0.5px, 1.5px) rotate(0.5deg);
+    }
+
+    54% {
+        transform: translate(-1.5px, 1.5px) rotate(0.5deg);
+    }
+
+    56% {
+        transform: translate(0.5px, 2.5px) rotate(1.5deg);
+    }
+
+    58% {
+        transform: translate(2.5px, 2.5px) rotate(0.5deg);
+    }
+
+    60% {
+        transform: translate(2.5px, -1.5px) rotate(1.5deg);
+    }
+
+    62% {
+        transform: translate(-1.5px, 0.5px) rotate(1.5deg);
+    }
+
+    64% {
+        transform: translate(-1.5px, 1.5px) rotate(1.5deg);
+    }
+
+    66% {
+        transform: translate(0.5px, 2.5px) rotate(1.5deg);
+    }
+
+    68% {
+        transform: translate(2.5px, -1.5px) rotate(1.5deg);
+    }
+
+    70% {
+        transform: translate(2.5px, 2.5px) rotate(0.5deg);
+    }
+
+    72% {
+        transform: translate(-0.5px, -1.5px) rotate(1.5deg);
+    }
+
+    74% {
+        transform: translate(-1.5px, 2.5px) rotate(1.5deg);
+    }
+
+    76% {
+        transform: translate(-1.5px, 2.5px) rotate(1.5deg);
+    }
+
+    78% {
+        transform: translate(-1.5px, 2.5px) rotate(0.5deg);
+    }
+
+    80% {
+        transform: translate(-1.5px, 0.5px) rotate(-0.5deg);
+    }
+
+    82% {
+        transform: translate(-1.5px, 0.5px) rotate(-0.5deg);
+    }
+
+    84% {
+        transform: translate(-0.5px, 0.5px) rotate(1.5deg);
+    }
+
+    86% {
+        transform: translate(2.5px, 1.5px) rotate(0.5deg);
+    }
+
+    88% {
+        transform: translate(-1.5px, 0.5px) rotate(1.5deg);
+    }
+
+    90% {
+        transform: translate(-1.5px, -0.5px) rotate(-0.5deg);
+    }
+
+    92% {
+        transform: translate(-1.5px, -1.5px) rotate(1.5deg);
+    }
+
+    94% {
+        transform: translate(0.5px, 0.5px) rotate(-0.5deg);
+    }
+
+    96% {
+        transform: translate(2.5px, -0.5px) rotate(-0.5deg);
+    }
+
+    98% {
+        transform: translate(-1.5px, -1.5px) rotate(-0.5deg);
+    }
+
+    0%, 100% {
+        transform: translate(0, 0) rotate(0);
+    }
+}
+@font-face {
+  font-family: 'Flat-UI-Icons';
+  src: url('flat-ui-icons-regular.eot');
+  src: url('flat-ui-icons-regular.eot?#iefix') format('embedded-opentype'), url('flat-ui-icons-regular.woff') format('woff'), url('flat-ui-icons-regular.ttf') format('truetype'), url('flat-ui-icons-regular.svg#flat-ui-icons-regular') format('svg');
+}
+[class^="fui-"],
+[class*="fui-"] {
+  font-family: 'Flat-UI-Icons';
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.fui-cross:before {
+  content: "\e609";
+}
+.fui-info-circle:before {
+  content: "\e60f";
+}
+.fui-photo:before {
+  content: "\e62a";
+}
+.fui-eye:before {
+  content: "\e62c";
+}
+.fui-chat:before {
+  content: "\e62d";
+}
+.fui-home:before {
+  content: "\e62e";
+}
+.fui-user:before {
+  content: "\e631";
+}

File diff suppressed because it is too large
+ 1 - 0
public/assets/waifu.min.css


BIN
public/favicon.ico


+ 21 - 0
public/index.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1"/>
+    <meta name="theme-color" content="#000000"/>
+    <meta name="description" content="Web site created using create-react-app"/>
+    <link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
+    <link rel="stylesheet" href="//at.alicdn.com/t/font_1569838_jvrdyorwtit.css">
+    <title></title>
+</head>
+<body>
+<noscript>You need to enable JavaScript to run this app.</noscript>
+<div id="root"></div>
+<canvas id="fireworks"></canvas>
+<script type="text/javascript" src="/js/anime.min.js"></script>
+<script type="text/javascript" src="/js/fireworks.js"></script>
+<script type="text/javascript" src="/assets/jquery.min.js"></script>
+<script type="text/javascript" src="/assets/autoload.js"></script>
+</body>
+</html>

File diff suppressed because it is too large
+ 33 - 0
public/js/anime.min.js


+ 146 - 0
public/js/fireworks.js

@@ -0,0 +1,146 @@
+function updateCoords(e) {
+    pointerX = (e.clientX || e.touches[0].clientX) - canvasEl.getBoundingClientRect().left,
+        pointerY = e.clientY || e.touches[0].clientY - canvasEl.getBoundingClientRect().top
+}
+
+function setParticuleDirection(e) {
+    var t = anime.random(0, 360) * Math.PI / 180
+        , a = anime.random(50, 180)
+        , n = [-1, 1][anime.random(0, 1)] * a;
+    return {
+        x: e.x + n * Math.cos(t),
+        y: e.y + n * Math.sin(t)
+    }
+}
+
+function createParticule(e, t) {
+    var a = {};
+    return a.x = e,
+        a.y = t,
+        a.color = colors[anime.random(0, colors.length - 1)],
+        a.radius = anime.random(16, 32),
+        a.endPos = setParticuleDirection(a),
+        a.draw = function () {
+            ctx.beginPath(),
+                ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0),
+                ctx.fillStyle = a.color,
+                ctx.fill()
+        }
+        ,
+        a
+}
+
+function createCircle(e, t) {
+    var a = {};
+    return a.x = e,
+        a.y = t,
+        a.color = "#F00",
+        a.radius = .1,
+        a.alpha = .5,
+        a.lineWidth = 6,
+        a.draw = function () {
+            ctx.globalAlpha = a.alpha,
+                ctx.beginPath(),
+                ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0),
+                ctx.lineWidth = a.lineWidth,
+                ctx.strokeStyle = a.color,
+                ctx.stroke(),
+                ctx.globalAlpha = 1
+        }
+        ,
+        a
+}
+
+function renderParticule(e) {
+    for (var t = 0; t < e.animatables.length; t++)
+        e.animatables[t].target.draw()
+}
+
+function animateParticules(e, t) {
+    for (var a = createCircle(e, t), n = [], i = 0; i < numberOfParticules; i++)
+        n.push(createParticule(e, t));
+    anime.timeline().add({
+        targets: n,
+        x: function (e) {
+            return e.endPos.x
+        },
+        y: function (e) {
+            return e.endPos.y
+        },
+        radius: .1,
+        duration: anime.random(1200, 1800),
+        easing: "easeOutExpo",
+        update: renderParticule
+    }).add({
+        targets: a,
+        radius: anime.random(80, 160),
+        lineWidth: 0,
+        alpha: {
+            value: 0,
+            easing: "linear",
+            duration: anime.random(600, 800)
+        },
+        duration: anime.random(1200, 1800),
+        easing: "easeOutExpo",
+        update: renderParticule,
+        offset: 0
+    })
+}
+
+function debounce(fn, delay) {
+    var timer
+    return function () {
+        var context = this
+        var args = arguments
+        clearTimeout(timer)
+        timer = setTimeout(function () {
+            fn.apply(context, args)
+        }, delay)
+    }
+}
+
+function isMobileBrowser() {
+    var sUserAgent = navigator.userAgent.toLowerCase();
+    var bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
+    var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
+    var bIsMidp = sUserAgent.match(/midp/i) == "midp";
+    var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
+    var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
+    var bIsAndroid = sUserAgent.match(/android/i) == "android";
+    var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
+    var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
+    if (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
+        return true;
+    } else
+        return false;
+}
+
+var canvasEl = document.querySelector("#fireworks");
+if (canvasEl && !isMobileBrowser()) {
+    var ctx = canvasEl.getContext("2d")
+        , numberOfParticules = 30
+        , pointerX = 0
+        , pointerY = 0
+        , tap = "mousedown"
+        , colors = ["#FF1461", "#18FF92", "#5A87FF", "#FBF38C"]
+        , setCanvasSize = debounce(function () {
+        canvasEl.width = 2 * window.innerWidth,
+            canvasEl.height = 2 * window.innerHeight,
+            canvasEl.style.width = window.innerWidth + "px",
+            canvasEl.style.height = window.innerHeight + "px",
+            canvasEl.getContext("2d").scale(2, 2)
+    }, 500)
+        , render = anime({
+        duration: 1 / 0,
+        update: function () {
+            ctx.clearRect(0, 0, canvasEl.width, canvasEl.height)
+        }
+    });
+    document.addEventListener(tap, function (e) {
+        "sidebar" !== e.target.id && "toggle-sidebar" !== e.target.id && "A" !== e.target.nodeName && "IMG" !== e.target.nodeName && (render.play(),
+            updateCoords(e),
+            animateParticules(pointerX, pointerY))
+    }, !1),
+        setCanvasSize(),
+        window.addEventListener("resize", setCanvasSize, !1)
+}

+ 59 - 0
src/App.js

@@ -0,0 +1,59 @@
+import React, {PureComponent} from 'react';
+import {BrowserRouter} from 'react-router-dom';
+import 'antd/dist/antd.css';
+import {GlobalStyle} from "./style";
+import store from "./store";
+import {Provider} from 'react-redux';
+import ScrollToTop from "./components/ScrollToTop";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import ToTop from './components/BackTop';
+import Router from './router';
+import APlayer from 'aplayer';
+import 'aplayer/dist/APlayer.min.css';
+import axios from "axios";
+
+class App extends PureComponent {
+    render() {
+        return (
+            <Provider store={store}>
+                <BrowserRouter>
+                    <ScrollToTop>
+                        <div id='player'/>
+                        <ToTop/>
+                        <GlobalStyle/>
+                        <Header/>
+                        <Router/>
+                        <Footer/>
+                    </ScrollToTop>
+                </BrowserRouter>
+            </Provider>
+        )
+    }
+
+    componentDidMount() {
+        this.getMuisic();
+    }
+
+    getMuisic(){
+        axios.get('/music/music/v1/list').then((res) => {
+            if(res.models.length){
+                const options = {
+                    container: document.getElementById('player'),
+                    fixed: true,
+                    theme: '#fe9600',
+                    listMaxHeight: '300px',
+                    listFolded: false,
+                    lrcType: 3,
+                    audio:res.models
+                };
+                const ap = new APlayer(options);
+                ap.on('ended', function () {
+                    console.log('player ended');
+                });
+            }
+        })
+    }
+}
+
+export default App;

+ 44 - 0
src/components/BackTop/index.js

@@ -0,0 +1,44 @@
+import React, {PureComponent} from "react";
+import {Top} from './style';
+import {BackTop} from 'antd';
+
+class ToTop extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            gotoTop: false
+        };
+        this.toTopfun = this.toTopfun.bind(this);
+    }
+
+    render() {
+        return (
+            <Top>
+                <div className={this.state.gotoTop ? 'toTop hidden' : 'toTop goTop hidden'}>
+                    <BackTop visibilityHeight={600} onClick={this.toTopfun}/>
+                    <img src={require('../../statics/images/scroll.png')} alt=""/>
+                </div>
+                <div className='phone-backtop'>
+                    <BackTop visibilityHeight={600}/>
+                </div>
+            </Top>
+        )
+    }
+
+    componentDidMount() {
+        window.onscroll = () => {
+            let t = document.documentElement.scrollTop || document.body.scrollTop;
+            this.setState({
+                gotoTop: t > 600
+            })
+        }
+    }
+
+    toTopfun() {
+        this.setState({
+            gotoTop: false,
+        });
+    }
+}
+
+export default ToTop

+ 82 - 0
src/components/BackTop/style.js

@@ -0,0 +1,82 @@
+import styled from "styled-components";
+
+export const Top = styled.div`
+    .toTop {
+        position: fixed;
+        right: 40px;
+        top: -150px;
+        z-index: 222;
+        width: 70px;
+        height: 900px;
+        transition: all .5s 0.3s ease-in-out;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+        -webkit-animation: float 2s linear infinite;
+        animation: float 2s linear infinite
+        @media(max-width:768px){
+            display:none;
+        }
+        .ant-back-top{
+            position: absolute;
+            bottom: 0;
+            width: 70px;
+            left: 0;
+            height: 110px;
+            opacity: 0;
+            .ant-back-top-content{
+                width: 70px;
+                height: 110px;
+                display: none;
+            }
+        }
+    }
+    @media(min-width:768px){
+        .phone-backtop{
+            display:none;
+        }
+    }
+    .goTop {
+        top: -950px;
+    }
+    
+    .toTop img, .toTophui img {
+        width: 100%;
+        height: auto;
+    }
+    
+    @-webkit-keyframes float {
+        0% {
+            -webkit-transform: translateY(0);
+            transform: translateY(0)
+        }
+    
+        50% {
+            -webkit-transform: translateY(-6px);
+            transform: translateY(-6px)
+        }
+    
+        100% {
+            -webkit-transform: translateY(0);
+            transform: translateY(0)
+        }
+    }
+    
+    @keyframes float {
+        0% {
+            -webkit-transform: translateY(0);
+            -ms-transform: translateY(0);
+            transform: translateY(0)
+        }
+    
+        50% {
+            -webkit-transform: translateY(-6px);
+            -ms-transform: translateY(-6px);
+            transform: translateY(-6px)
+        }
+    
+        100% {
+            -webkit-transform: translateY(0);
+            -ms-transform: translateY(0);
+            transform: translateY(0)
+        }
+    }
+`;

+ 34 - 0
src/components/Footer/index.js

@@ -0,0 +1,34 @@
+import React, {PureComponent} from 'react';
+import {Footers} from './style';
+import {connect} from "react-redux";
+import {withRouter} from "react-router-dom";
+
+class Footer extends PureComponent {
+    render() {
+        const {copyright, domain, icp, title} = this.props.confing.toJS();
+        return (
+            <Footers>
+                <div className='site-info'>
+                    <div className='footertext'>
+                        <p className='foo-logo'/>
+                        <p>项目托管于<a href={'https://www.aliyun.com/sale-season/2020/procurement-new-members?userCode=fzfxtn3t'}>阿里云</a></p>
+                        <p className='name'>
+                            <span>
+                                <a href={domain} rel="noopener noreferrer" target={'_blank'}>{copyright}</a>
+                            </span>
+                        </p>
+                        <p>© 2019 {title} {icp}</p>
+                    </div>
+                </div>
+            </Footers>
+        )
+    }
+}
+
+const mapStateToProps = (state) => {
+    return {
+        confing: state.getIn(['header', 'confing']),
+    }
+};
+
+export default connect(mapStateToProps)(withRouter(Footer));

+ 68 - 0
src/components/Footer/style.js

@@ -0,0 +1,68 @@
+import styled from "styled-components";
+import logo from '../../statics/images/nosum.svg';
+
+export const Footers = styled.footer`
+    padding: 2%;
+    background: rgba(255,255,255,.8);
+    max-width: 900px;
+    margin-left: auto;
+    margin-right: auto;
+    .site-info {
+        text-align: center;
+        font-size: 13px;
+        color: #b9b9b9;
+        .footertext .foo-logo {
+            background-image: url(${logo});
+            width: 30px;
+            height: 30px;
+            opacity: .3;
+            margin: 0 auto;
+            background-size: cover;
+            background-position: center center;
+            background-repeat: no-repeat;
+            animation: poi-deg 12s infinite linear;
+            -webkit-animation: poi-deg 12s infinite linear;
+        }
+        p{
+            margin:15px 0;
+        }
+        .name{
+            span{
+                color: #666666;
+            }
+            i{
+                color: #e74c3c;
+            }
+            a{
+                color: #000000;
+                text-decoration:none;
+            }
+        }
+    }
+    .footer-sponsor{
+        img{
+            width:52px;
+            margin-right:10px;
+        }
+    }
+    @keyframes poi-deg {
+        0% {
+            transform: rotate(0deg)
+        }
+    
+        100% {
+            transform: rotate(360deg)
+        }
+    }
+    
+    @-webkit-keyframes poi-deg {
+        0% {
+            transform: rotate(0deg)
+        }
+    
+        100% {
+            transform: rotate(360deg)
+        }
+    }
+`;
+

+ 273 - 0
src/components/Header/index.js

@@ -0,0 +1,273 @@
+import React, {PureComponent} from 'react';
+import {Link, withRouter} from 'react-router-dom';
+import {connect} from 'react-redux';
+import {Headers, NavWrapper, NavLeft, NavRight, Nav, NavItem, IconBox, MoNav, Mask} from './style';
+import {actionCreators} from './store';
+import {Icon, Menu, Dropdown, Affix, message} from 'antd';
+import {getAvatar, setAvatar, setToken} from '../../lib/auth';
+import axios from "axios";
+import openWindow from "../../lib/openWindow";
+
+class Header extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            isVisible: false,
+            value: '',
+            open: false,
+            isUser: false
+        };
+        this.handleClick = this.handleClick.bind(this);
+        this.keypress = this.keypress.bind(this);
+        this.setValue = this.setValue.bind(this);
+        this.openMonav = this.openMonav.bind(this);
+        this.login = this.login.bind(this);
+        this.loginGithubHandel = this.loginGithubHandel.bind(this);
+    }
+
+    render() {
+        const {isVisible, value, open, isUser} = this.state;
+        const {category} = this.props;
+        const {title, domain} = this.props.confing.toJS();
+        const {name, introduction, avatar} = this.props.userInfo.toJS();
+        if (title) document.title = title;
+        return (
+            <Headers>
+                <Affix>
+                    <NavWrapper className='nav-wrapper'>
+                        <NavLeft className='ellipsis'>
+                            <a href={domain}>{title}</a>
+                            <Icon type="menu" onClick={this.openMonav}/>
+                        </NavLeft>
+                        <NavRight>
+                            <div className='flex-items'>
+                                <Nav className='flex-items'>
+                                    <NavItem>
+                                        <Link to={'/'} className='nav-item'>
+                                            <i className='iconfont icon-fort-awesome'/>
+                                            <span>首页</span>
+                                        </Link>
+                                    </NavItem>
+                                    <NavItem id='area'>
+                                        <Dropdown
+                                            overlay={this.Category()}
+                                            placement="bottomCenter"
+                                            getPopupContainer={() => document.getElementById('area')}
+                                            overlayClassName='NavDropdown'
+                                        >
+                                            <span className='nav-item'>
+                                                <i className='iconfont icon-list-ul'/>
+                                                <span>分类</span>
+                                            </span>
+                                        </Dropdown>
+                                    </NavItem>
+                                    <NavItem>
+                                        <Link to={'/archives'} className='nav-item'>
+                                            <i className='iconfont icon-archive'/>
+                                            <span>归档</span>
+                                        </Link>
+                                    </NavItem>
+                                    <NavItem>
+                                        <Link to={'/links'} className='nav-item'>
+                                            <i className='iconfont icon-link'/>
+                                            <span>友人帐</span>
+                                        </Link>
+                                    </NavItem>
+                                    <NavItem>
+                                        <Link to={'/tags'} className='nav-item'>
+                                            <i className='iconfont icon-tag'/>
+                                            <span>标签墙</span>
+                                        </Link>
+                                    </NavItem>
+                                </Nav>
+                                <IconBox className='flex-items'>
+                                    <Icon type="search" onClick={this.handleClick}/>
+                                    {isUser || getAvatar()? <img src={getAvatar()} alt=""/> :
+                                        <Icon type="user" onClick={this.login}/>}
+                                </IconBox>
+                            </div>
+                        </NavRight>
+                    </NavWrapper>
+                </Affix>
+                <div
+                    className={isVisible ? 'search-form search-form--modal is-visible' : 'search-form search-form--modal'}>
+                    <div className='search-form__inner'>
+                        <div className='box'>
+                            <p className="micro mb-">想要找点什么呢?</p>
+                            <Icon type="search"/>
+                            <input
+                                type="search"
+                                name="s"
+                                placeholder="Search"
+                                onKeyPress={this.keypress}
+                                value={value}
+                                onChange={this.setValue}
+                            />
+                        </div>
+                    </div>
+                    <div className="search_close" onClick={this.handleClick}/>
+                </div>
+                <Mask className={open ? 'show' : 'hidden'} onClick={this.openMonav}/>
+                <MoNav className={open ? 'mo-nav open' : 'mo-nav'}>
+                    <div className='m-avatar'><img src={avatar} alt=""/></div>
+                    <p className='name ellipsis'>{name}</p>
+                    <p className='info ellipsis'>{introduction}</p>
+                    <ul className='menu'>
+                        <li>
+                            <Link to={'/'} className='item flex-items' onClick={this.openMonav}>
+                                <i className='iconfont icon-fort-awesome'/>
+                                <span>首页</span>
+                            </Link>
+                        </li>
+                        <li>
+                            <span className='item flex-items'>
+                                <i className='iconfont icon-list-ul'/>
+                                <span>分类</span>
+                            </span>
+                            <ul className='sub-menu'>
+                                {category.map((item, index) => {
+                                    return (
+                                        <li key={index}>
+                                            <Link to={'/category/' + item.get('id')} className='item flex-items'
+                                                  onClick={this.openMonav}>
+                                                <span>{item.get('name')}</span>
+                                            </Link>
+                                        </li>
+                                    )
+                                })}
+                            </ul>
+                        </li>
+                        <li>
+                            <Link to={'/archives'} className='item flex-items' onClick={this.openMonav}>
+                                <i className='iconfont icon-archive'/>
+                                <span>归档</span>
+                            </Link>
+                        </li>
+                        <li>
+                            <Link to={'/links'} className='item flex-items' onClick={this.openMonav}>
+                                <i className='iconfont icon-link'/>
+                                <span>友人帐</span>
+                            </Link>
+                        </li>
+                        <li>
+                            <Link to={'/tags'} className='item flex-items' onClick={this.openMonav}>
+                                <i className='iconfont icon-tag'/>
+                                <span>标签墙</span>
+                            </Link>
+                        </li>
+                    </ul>
+                </MoNav>
+            </Headers>
+        )
+    }
+
+    componentDidMount() {
+        this.props.getCategory();
+        this.props.getUser();
+        this.props.getConfing();
+    }
+
+    keypress(e) {
+        if (e.which === 13) {
+            const {value} = this.state;
+            if (value === '') {
+                message.warning('please type a comment');
+            } else {
+                this.props.history.push('/search/' + value);
+                this.handleClick();
+            }
+        }
+    }
+
+    login() {
+        axios.get('/auth/github/v1/get').then((res) => {
+            if (res.success === 1) {
+                openWindow(res.model, "绑定GitHub", 540, 540);
+                window.addEventListener("message", this.loginGithubHandel, false);
+            }
+        });
+    }
+
+    loginGithubHandel(e) {
+        const {socialId, avatar, name, htmlUrl} = e.data;
+        if (socialId) {
+            axios({
+                method: 'post',
+                url: '/auth/user/v1/login',
+                data: {
+                    socialId: socialId,
+                    avatar: avatar,
+                    name: name,
+                    htmlUrl: htmlUrl
+                }
+            }).then((res) => {
+                if (res.success === 1) {
+                    setToken(res.model.token);
+                    setAvatar(res.model.avatar);
+                    this.setState({isUser: true});
+                    message.success('登录成功');
+                }
+            });
+            window.removeEventListener("message", this.loginGithubHandel, false);
+        }
+    }
+
+    setValue(e) {
+        const value = e.target.value;
+        this.setState({value: value});
+    }
+
+    openMonav() {
+        this.setState((prevState) => ({
+            open: !prevState.open
+        }))
+    }
+
+    handleClick() {
+        this.setState((prevState) => ({
+            isVisible: !prevState.isVisible
+        }))
+    }
+
+    Category() {
+        const {category} = this.props;
+        const list = category.toJS();
+        return (
+            <Menu>
+                {
+                    list.map((item, index) => {
+                        return (
+                            <Menu.Item key={index}>
+                                <Link to={'/category/' + item.id}>{item.name}</Link>
+                            </Menu.Item>
+                        )
+                    })
+                }
+            </Menu>
+        )
+    }
+
+}
+
+const mapStateToProps = (state) => {
+    return {
+        category: state.getIn(['header', 'category']),
+        confing: state.getIn(['header', 'confing']),
+        userInfo: state.getIn(['header', 'userInfo']),
+    }
+};
+const mapDispatchToProps = (dispatch) => {
+    return {
+        getCategory() {
+            dispatch(actionCreators.getCategory());
+        },
+        getUser() {
+            dispatch(actionCreators.getUser());
+        },
+        getConfing() {
+            dispatch(actionCreators.getConfing());
+        }
+    }
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Header));

+ 78 - 0
src/components/Header/store/actionCreators.js

@@ -0,0 +1,78 @@
+import * as constants from './constants';
+import axios from 'axios';
+import {fromJS} from 'immutable';
+
+const changeCategory = (data) => ({
+    type: constants.GET_CATEGORY,
+    data: fromJS(data),
+});
+
+
+export const getCategory = () => {
+    return (dispatch) => {
+        axios.get('/category/category/v1/list').then((res) => {
+            if (res.success === 1) {
+                dispatch(changeCategory(res.models));
+            }
+        })
+    }
+};
+
+export const getUser = () => {
+    return (dispatch) => {
+        axios.get('/auth/master/v1/get').then((res) => {
+            if (res.success === 1) {
+                dispatch({
+                    type: constants.GET_USER,
+                    data: fromJS(res.model)
+                });
+            }
+        })
+    }
+};
+
+export const getConfing = () => {
+    return (dispatch) => {
+        axios.get('/config/config-base/v1/list').then((res) => {
+            if (res.success === 1) {
+                const {models} = res;
+                let data = {};
+                models.forEach(item => {
+                    if (item.configKey === "name") {
+                        data.title = item.configValue;
+                    }
+                    if (item.configKey === "keywords") {
+                        data.keywords = item.configValue;
+                        let oMeta = document.createElement('meta');
+                        oMeta.name = 'keywords';
+                        oMeta.content = item.configValue;
+                        document.getElementsByTagName('head')[0].appendChild(oMeta);
+                    }
+                    if (item.configKey === "description") {
+                        data.description = item.configValue;
+                        let Meta = document.createElement('meta');
+                        Meta.name = 'description';
+                        Meta.content = item.configValue;
+                        document.getElementsByTagName('head')[0].appendChild(Meta);
+                    }
+                    if (item.configKey === "domain") {
+                        data.domain = item.configValue;
+                    }
+                    if (item.configKey === "copyright") {
+                        data.copyright = item.configValue;
+                    }
+                    if (item.configKey === "metas") {
+                        data.metas = item.configValue;
+                    }
+                    if (item.configKey === "icp") {
+                        data.icp = item.configValue;
+                    }
+                });
+                dispatch({
+                    type: constants.GET_CONFING,
+                    data: fromJS(data)
+                });
+            }
+        })
+    }
+};

+ 3 - 0
src/components/Header/store/constants.js

@@ -0,0 +1,3 @@
+export const GET_CATEGORY = 'header/GET_CATEGORY';
+export const GET_USER = 'header/GET_USER';
+export const GET_CONFING = 'header/GET_CONFING';

+ 5 - 0
src/components/Header/store/index.js

@@ -0,0 +1,5 @@
+import reducer from "./reducer";
+import * as actionCreators from './actionCreators';
+import * as constants from './constants';
+
+export {reducer, actionCreators, constants}

+ 28 - 0
src/components/Header/store/reducer.js

@@ -0,0 +1,28 @@
+import * as constants from './constants';
+import {fromJS} from 'immutable';
+
+const defaultState = fromJS({
+    category: [],
+    userInfo: {},
+    confing: {}
+});
+
+//immutable对象的set方法,会结合之前的immutable对象的值和设置的值,返回一个全新的对象
+export default (state = defaultState, action) => {
+    switch (action.type) {
+        case constants.GET_CATEGORY:
+            return state.merge({
+                category: action.data
+            });
+        case constants.GET_USER:
+            return state.merge({
+                userInfo: action.data
+            });
+        case constants.GET_CONFING:
+            return state.merge({
+                confing: action.data
+            });
+        default:
+            return state;
+    }
+}

+ 389 - 0
src/components/Header/style.js

@@ -0,0 +1,389 @@
+import styled from "styled-components";
+
+export const Headers = styled.div`
+    .ant-affix{
+        z-index: 999;
+        .nav-wrapper{
+            background: rgba(255,255,255,.95);
+            box-shadow: 0 1px 40px -8px rgba(0,0,0,.5);
+        }
+    }
+    .search-form--modal {
+        -webkit-transition: visibility .25s ease,opacity .25s ease;
+        -moz-transition: visibility .25s ease,opacity .25s ease;
+        -ms-transition: visibility .25s ease,opacity .25s ease;
+        -o-transition: visibility .25s ease,opacity .25s ease;
+        transition: visibility .25s ease,opacity .25s ease;
+        overflow: hidden;
+        z-index: 999;
+        position: fixed;
+        top: 0;
+        right: 0;
+        left: 0;
+        bottom: 0;
+        background: #fff;
+        visibility: hidden;
+        opacity: 0;
+    }
+    .search-form--modal .search-form__inner {
+        max-width: 640px;
+        padding: 0 20px;
+        margin: auto;
+        text-align: left;
+        position: absolute;
+        width: 100%;
+        left: 0;
+        right: 0;
+        height: 285px;
+        top: 0;
+        bottom: 0;
+    }
+    .search-form .box {
+        position: relative;
+    }
+    .search-form--modal .search-form__inner p {
+        padding-left: 24px;
+        color: #404040;
+        font-size: 15px;
+        line-height: 1.5;
+        margin:15px 0 22px 0;
+    }
+    .search-form i {
+        font-size: 32px;
+        font-size: 2rem;
+        line-height: 1;
+        color: #ddd;
+        position: absolute;
+        bottom: 10px;
+        margin-top: -16px;
+        left: 16px;
+    }
+    .search-form input {
+        font-size: 24px;
+        font-size: 1.5rem;
+        background: #fff;
+        padding: 12px 24px 12px 64px;
+        width: 100%;
+        outline: none;
+        border-radius: 50px;
+        color: #666;
+        border: 1px solid #ccc;
+    }
+    .search-form .search_close {
+        position: absolute;
+        width: 35px;
+        height: 35px;
+        background: 0 0;
+        top: 20px;
+        right: 15px;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+    }
+    .search_close:before, .search_close:after {
+        background-color: #222;
+        position: absolute;
+        content: "";
+        width: 30px;
+        height: 2px;
+        top: 17px;
+        left: 2px;
+    }
+    .search_close:before {
+        transform: rotate(-45deg);
+        -webkit-transform: rotate(-45deg);
+    }
+    .search_close:after {
+        transform: rotate(45deg);
+        -webkit-transform: rotate(45deg);
+    }
+    .search-form.is-visible{
+        visibility: visible;
+        opacity: .99;
+        animation: elastic .5s;
+        background-image: url(https://cdn.jsdelivr.net/gh/moezx/cdn@3.2.1/img/other/iloli.gif);
+        background-repeat: no-repeat;
+        background-position: bottom right;
+    }
+    @keyframes elastic {
+        0% {
+            transform: scale(0)
+        }
+    
+        55% {
+            transform: scale(1)
+        }
+    
+        70% {
+            transform: scale(.98)
+        }
+    
+        100% {
+            transform: scale(1)
+        }
+    }
+    @media (min-width: 768px){
+        &:hover .nav-wrapper{
+            background: rgba(255,255,255,.95);
+            box-shadow: 0 1px 40px -8px rgba(0,0,0,.5);
+        }
+    }
+    @media(max-width:768px){
+        .search-form.is-visible{
+            background-size: 30%;
+        }
+        .search-form--modal .search-form__inner p{
+            padding-left: 10px;
+            font-size: 14px;
+        }
+        .search-form i{
+            font-size: 22px;
+            bottom: 11px;
+        }
+        .search-form input{
+            padding: 10px 10px 10px 45px;
+            font-size: 18px;
+        }
+    }
+`;
+
+export const NavWrapper = styled.div`
+    width: 100%;
+    height: 75px;
+    position:fixed;
+    top:0;
+    padding:0 30px
+    z-index:999;
+    transition: all .4s ease;
+    @media (max-width: 768px){
+        height: 50px;
+        padding:0 15px
+    }
+`;
+
+export const NavLeft = styled.div`
+    float:left;
+    height:75px;
+    line-height:75px;
+    max-width:120px;
+    a{
+        color: #464646;
+        font-size: 20px;
+        font-weight: 800;
+    }
+    i{
+        font-size:22px;
+        color:#666666;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+        display:none;
+    }
+    i:hover{
+        color:#fe9600;
+    }
+    a:hover{
+        color:#fe9600;
+    }
+    @media (max-width: 768px){
+        height:50px;
+        line-height:50px;
+        a{
+            display:none;
+        }
+        i{
+            display: inline-block;
+        }
+    }
+`;
+export const NavRight = styled.div`
+    float:right;
+    height:75px;
+    .flex-items{
+        height:75px;
+    }
+    @media (max-width: 768px){
+        height:50px;
+        .flex-items{
+            height:50px;
+        }
+    }
+`;
+
+export const Nav = styled.ul`
+    height:75px;
+    @media (max-width: 768px){
+        display:none;
+    }
+`;
+
+export const NavItem = styled.li`
+    padding:0 15px;
+    .nav-item{
+        display:block;
+        color: #666666;
+        font-size:16px;
+        height:40px;
+        line-height:40px;
+        position: relative;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+    }
+    .nav-item i{
+        margin-right:5px;
+    }
+    .nav-item:after{
+        content: "";
+        display: block;
+        position: absolute;
+        bottom: -17px;
+        height: 6px;
+        background-color: #fe9600;
+        width: 0px;
+        transition: width .25s ease-in-out;
+    }
+    &:hover .nav-item{
+        color:#fe9600;
+    }
+    &:hover .nav-item:after{
+        width:100%;
+    }
+    .NavDropdown ul{
+        padding:10px;
+        text-align: center;
+        box-shadow: 0 1px 40px -8px rgba(0,0,0,.5);
+    }
+    .NavDropdown ul li:hover{
+        background:none;
+    }
+    .NavDropdown ul li:hover a{
+        color:#fe9600;
+    }
+    .NavDropdown ul:before{
+        content: "";
+        position: absolute;
+        top: -20px;
+        left: 50%;
+        margin-left: -10px;
+        border-width: 10px;
+        border-style: solid;
+        border-color: transparent transparent #fff transparent;
+    }
+`;
+
+export const IconBox = styled.div`
+    i{
+        font-size:24px;
+        margin-left:20px;
+        color:#666666;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto !important;
+    }
+    i:hover{
+        color:#fe9600;
+    }
+    img{
+        width: 24px;
+        height: 24px;
+        border-radius: 50%;
+        margin-left:20px;
+    }
+`;
+
+export const Mask = styled.div`
+    position: fixed;
+    background-color: rgba(0, 0, 0, 0.5);
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    z-index: 998;
+    &.hidden{
+        display:none;
+    }
+    &.show{
+        display:block;
+    }
+`;
+
+export const MoNav = styled.div`
+    position: fixed;
+    left: 0;
+    bottom: 0;
+    top: 0;
+    background-color: #ffffff;
+    width: 55%;
+    z-index: 999;
+    transition-duration: .5s;
+    overflow-y: scroll;
+    -webkit-transform: translateX(-100%) translateY(0px);
+    transform: translateX(-100%) translateY(0px);
+    &.open{
+        transform: translateX(0px) translateY(0px);
+        -webkit-transform: translateX(0px) translateY(0px);
+    }
+    .m-avatar {
+        width:100%;
+        height: auto;
+        padding: 30px 0 20px;
+        text-align: center;
+    }
+    .m-avatar img {
+        width: 90px;
+        height: 90px;
+        max-width: 90px;
+        border-radius: 100%;
+    }
+    .name{
+        margin-bottom:10px;
+        padding:0 15px;
+        text-align: center;
+        font-size: 13px;
+        color: #333;
+    }
+    .info{
+        margin-bottom:10px;
+        padding:0 15px 15px 15px;
+        text-align: center;
+        font-size: 13px;
+        color: #333;
+        position: relative;
+        &:after{
+            position: absolute;
+            right:0px;
+            bottom: 0;
+            left:0px;
+            height: 1px;
+            content: '';
+            -webkit-transform: scaleY(.5);
+            transform: scaleY(.5);
+            background-color:#f1f1f1;   
+        }
+    }
+    .menu{
+        .item{
+            position: relative;
+            padding:10px 15px;
+            color: #333;
+            font-size: 14px;
+            span{
+                margin-left:5px;
+            }
+            i{
+                font-size: 12px;
+            }
+            &:after{
+                position: absolute;
+                right:0px;
+                bottom: 0;
+                left:0px;
+                height: 1px;
+                content: '';
+                -webkit-transform: scaleY(.5);
+                transform: scaleY(.5);
+                background-color:#f1f1f1;   
+            }
+        }
+    }
+    .sub-menu{
+        padding-left:15px;
+        .item{
+            padding:5px 10px;
+            font-size: 13px;
+        }
+    }
+`;

+ 53 - 0
src/components/List/index.js

@@ -0,0 +1,53 @@
+import React from "react";
+import {Link} from "react-router-dom";
+import {SiteMain} from "./style";
+import {getTime} from "../../lib/public";
+
+const CatList = (props) => {
+    let {list} = props;
+    return (
+        <SiteMain>
+            {list.map((item, index) => {
+                return (
+                    <article className='post post-list post-list-show' key={index}>
+                        <div className='post-entry'>
+                            <div className='feature'>
+                                <Link to={'/article/' + item.id}>
+                                    <img src={item.thumbnail} alt=""/>
+                                </Link>
+                            </div>
+                            <h1 className='entry-title'>
+                                <Link to={'/article/' + item.id}>{item.title}</Link>
+                            </h1>
+                            <div className='p-time'><i className='iconfont icon-time'/>发布于 {getTime(item.createTime)}
+                            </div>
+                            <p>{item.summary}</p>
+                            <footer className='entry-footer'>
+                                <div className='post-more'>
+                                    <Link to={'/article/' + item.id}>
+                                        <i className='iconfont icon-caidan'/>
+                                    </Link>
+                                </div>
+                                <div className='info-meta'>
+                                    <div className='comnum'>
+                                        <span>
+                                            <i className="iconfont icon-attention"/>{item.views} 热度
+                                        </span>
+                                    </div>
+                                    <div className='views'>
+                                        <span className="comments-number">
+                                            <i className="iconfont icon-mark"/>{item.comments} 评论
+                                        </span>
+                                    </div>
+                                </div>
+                            </footer>
+                        </div>
+                        <hr/>
+                    </article>
+                )
+            })}
+        </SiteMain>
+    )
+};
+
+export default CatList

+ 162 - 0
src/components/List/style.js

@@ -0,0 +1,162 @@
+import styled from "styled-components";
+
+export const SiteMain = styled.div`
+    .post-list {
+        margin: 0 0 8%;
+        position: relative;
+    }
+    .post-list-show {
+        animation: post-list-show .5s;
+        -webkit-animation: post-list-show .5s;
+        opacity: 1;
+    }
+    @keyframes post-list-show {
+        0% {
+            opacity: 0;
+            -webkit-transform: translateY(80px);
+            transform: translateY(80px)
+        }
+        100% {
+            opacity: 1;
+            -webkit-transform: translateY(0);
+            transform: translateY(0)
+        }
+    }
+    .feature {
+        position: absolute;
+        margin-top: 10px;
+        img {
+            width: 100px;
+            height: 100px;
+            border-radius: 50%;
+            padding: 2px;
+            border: 1px solid #dadada;
+            position: relative;
+        }
+    }
+    .entry-title {
+        font-size: 20px;
+        font-weight: 400;
+        line-height: 50px;
+        margin: 0 0 0 17%;
+        position: relative;
+        z-index: 1;
+        display: inline-block;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        width: 70%;
+        a {
+            display:block;
+            color: #504e4e;
+        }
+        &:hover a{
+            color: #FE9600;
+        }
+    }
+    .p-time {
+        position: absolute;
+        right: 0;
+        top: 16px;
+        font-size: 12px;
+        color: #989898;
+        letter-spacing: 0;
+        i{
+            font-size: 14px;
+            margin-right: 5px;
+        }
+    }
+    .post-list p {
+        min-height: 60px;
+        margin: 0 0 0 17%;
+        font-size: 15px;
+        color: rgba(0,0,0,.66);
+        letter-spacing: 0;
+        line-height: 30px;
+    }
+    .entry-footer {
+        margin: 0 0 0 17%;
+        list-style: none;
+    }
+    .post-more {
+        margin-top: 10px;
+        text-align: right;
+        i {
+            font-size: 25px;
+            color: #666;
+        }
+    }
+    .info-meta {
+        margin-top: 10px;
+        position: absolute;
+        top: 20px;
+        opacity: 0;
+        padding-top: 8px;
+        border-top: 1px solid #ddd;
+        -webkit-transform: translate3d(-150px,0,0);
+        transform: translate3d(-150px,0,0);
+        visibility: hidden;
+        transition: .7s all ease;
+        -webkit-transition: .6s all ease;
+        -moz-transition: .6s all linear;
+        -o-transition: .6s all ease;
+        -ms-transition: .6s all ease;
+    }
+    .comnum {
+        float: left;
+    }
+    .info-meta span {
+        color: #000;
+        font-size: 13px;
+        vertical-align: bottom;
+    }
+    .info-meta i {
+        margin-top: 3px;
+        margin-right: 10px;
+        float: left;
+    }
+    .info-meta a {
+        color: #000;
+    }
+    .post-list hr {
+        width: 30%;
+        height: 1px;
+        margin: 0 auto;
+        border: 0;
+        background: #efefef;
+        margin-top: 20px;
+    }
+    .post-list:hover .info-meta {
+        -webkit-transform: translate3d(-230px,0,0);
+        transform: translate3d(-230px,0,0);
+        opacity: 1;
+        visibility: visible;
+    }
+    @media(max-width:768px){
+        .feature{
+            img{
+                width: 52px;
+                height: 52px;
+            }
+        }
+        .entry-title{
+            margin: 0 0 0 65px;
+            font-size: 16px;
+            line-height: 30px;
+        }
+        .p-time {
+            position: relative;
+            margin: -15px 0 0 65px;
+        }        
+        .post-list p{
+            margin: 20px 0 0 65px;
+            font-size: 14px;
+            height: 30px;
+            overflow: hidden;
+            margin-bottom: 40px;
+        }
+        .entry-footer{
+            display:none;
+        }
+    }
+`;

+ 32 - 0
src/components/PagInation/index.js

@@ -0,0 +1,32 @@
+import React from "react";
+import {Spin} from "antd";
+import {PagWrapper} from './style';
+
+const PagInation = (props) => {
+    const {page, finished, loading, id} = props;
+    if (finished) {
+        return (
+            <PagWrapper>
+                <p>很高兴你翻到这里,但是真的没有了...</p>
+            </PagWrapper>
+        )
+    } else {
+        if (loading) {
+            return (
+                <PagWrapper>
+                    <div className="example">
+                        <Spin size="large"/>
+                    </div>
+                </PagWrapper>
+            )
+        } else {
+            return (
+                <PagWrapper>
+                    <div onClick={() => props.getList(page, id)} className='btn'>Previous</div>
+                </PagWrapper>
+            )
+        }
+    }
+};
+
+export default PagInation

+ 39 - 0
src/components/PagInation/style.js

@@ -0,0 +1,39 @@
+import styled from "styled-components";
+
+export const PagWrapper = styled.div`
+    width: 100%;
+    padding: 20px 0;
+    text-align: center;
+    margin: 40px 0 80px;
+    display: inline-block;
+    @media(max-width:768px){
+        margin: 0;
+    }
+    .btn{
+        display: inline-block;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+        padding: 13px 35px;
+        border: 1px solid #d6d6d6;
+        border-radius: 50px;
+        color: #adadad;
+    }
+    @media(min-width:768px){
+        .btn:hover{
+            border: 1px solid orange;
+            color: #FE9600;
+            border-color: #FE9600;
+            box-shadow: 0 0 4px rgba(255,165,0,.85);
+        }
+    }
+    p{
+        color: #989898;
+        font-size: 15px;
+    }
+    .example{
+        height:52px;
+        line-height:52px;
+        i{
+            background-color: #FE9600;
+        }
+    }
+`;

+ 18 - 0
src/components/ScrollToTop/index.js

@@ -0,0 +1,18 @@
+import {Component} from 'react';
+import {withRouter} from 'react-router-dom';
+
+class ScrollToTop extends Component {
+    componentDidUpdate(prevProps, prevState, snapshot) {
+        if (this.props.location.pathname!==prevProps.location.pathname){
+            window.scrollTo(0,0)
+        }
+    }
+
+    render() {
+        return (
+            this.props.children
+        );
+    }
+}
+
+export default withRouter(ScrollToTop);

+ 6 - 0
src/index.js

@@ -0,0 +1,6 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+import './lib/axios';
+
+ReactDOM.render(<App/>, document.getElementById('root'));

+ 67 - 0
src/lib/auth.js

@@ -0,0 +1,67 @@
+import Cookies from "js-cookie";
+import axios from "axios";
+import {message} from "antd";
+
+const TokenKey = "hello-blog-token";
+const Avatar = "Avatar";
+
+export function getAvatar() {
+    return Cookies.get(Avatar);
+}
+
+export function setAvatar(url) {
+    return Cookies.set(Avatar, url);
+}
+
+export function getToken() {
+    return Cookies.get(TokenKey);
+}
+
+export function setToken(token) {
+    return Cookies.set(TokenKey, token);
+}
+
+export function removeToken() {
+    return Cookies.remove(TokenKey);
+}
+
+export function loginGithubHandel(e) {
+    const {socialId, avatar, name, htmlUrl} = e.data;
+    if (socialId) {
+        axios({
+            method: 'post',
+            url: '/auth/user/v1/login',
+            data: {
+                socialId: socialId,
+                avatar: avatar,
+                name: name,
+                htmlUrl: htmlUrl
+            }
+        }).then((res) => {
+            if (res.success === 1) {
+                setToken(res.model.token);
+                setAvatar(res.model.avatar);
+                message.success('登录成功');
+            }
+        });
+        window.removeEventListener("message", loginGithubHandel, false);
+    }
+}
+
+export function scrollAnimation(currentY, targetY) {
+    // 计算需要移动的距离
+    let needScrollTop = targetY - currentY;
+    let _currentY = currentY;
+    setTimeout(() => {
+        // 一次调用滑动帧数,每次调用会不一样
+        const dist = Math.ceil(needScrollTop / 10);
+        _currentY += dist;
+        window.scrollTo(_currentY, currentY);
+        // 如果移动幅度小于十个像素,直接移动,否则递归调用,实现动画效果
+        if (needScrollTop > 10 || needScrollTop < -10) {
+            scrollAnimation(_currentY, targetY)
+        } else {
+            window.scrollTo(_currentY, targetY)
+        }
+    }, 1)
+}

+ 19 - 0
src/lib/axios.js

@@ -0,0 +1,19 @@
+import axios from 'axios';
+import {getToken} from './auth';
+
+axios.defaults.baseURL = '/api/blog';
+// 添加请求拦截器
+axios.interceptors.request.use(function (config) {
+    if (getToken()) {
+        config.headers["Authorization"] = getToken();
+    }
+    return config
+}, function (error) {
+    return Promise.reject(error)
+});
+// 添加响应拦截器
+axios.interceptors.response.use(function (response) {
+    return response.data;
+}, function (error) {
+    return Promise.reject(error)
+});

+ 42 - 0
src/lib/openWindow.js

@@ -0,0 +1,42 @@
+export default function openWindow(url, title, w, h) {
+    // Fixes dual-screen position                            Most browsers       Firefox
+    const dualScreenLeft =
+        // eslint-disable-next-line no-restricted-globals
+        window.screenLeft !== undefined ? window.screenLeft : screen.left;
+    const dualScreenTop =
+        // eslint-disable-next-line no-restricted-globals
+        window.screenTop !== undefined ? window.screenTop : screen.top;
+
+    const width = window.innerWidth
+        ? window.innerWidth
+        : document.documentElement.clientWidth
+            ? document.documentElement.clientWidth
+            // eslint-disable-next-line no-restricted-globals
+            : screen.width;
+    const height = window.innerHeight
+        ? window.innerHeight
+        : document.documentElement.clientHeight
+            ? document.documentElement.clientHeight
+            // eslint-disable-next-line no-restricted-globals
+            : screen.height;
+
+    const left = width / 2 - w / 2 + dualScreenLeft;
+    const top = height / 2 - h / 2 + dualScreenTop;
+    const newWindow = window.open(
+        url,
+        title,
+        "toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=" +
+        w +
+        ", height=" +
+        h +
+        ", top=" +
+        top +
+        ", left=" +
+        left
+    );
+
+    // Puts focus on the newWindow
+    if (window.focus) {
+        newWindow.focus();
+    }
+}

+ 57 - 0
src/lib/public.js

@@ -0,0 +1,57 @@
+export const getTime = (time) => {
+    const date = new Date(time);
+    let Y = date.getFullYear() + '-';
+    let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
+    let D = date.getDate() + ' ';
+    return (Y + M + D)
+};
+
+const getUnix = () => {
+    const date = new Date();
+    return date.getTime();
+};
+
+const getTodayUnix = () => {
+    const date = new Date();
+    date.setHours(0);
+    date.setMinutes(0);
+    date.setSeconds(0);
+    date.setMilliseconds(0);
+    return date.getTime();
+};
+
+const getLastDate = (time) => {
+    const date = new Date(time);
+    const month =
+        date.getMonth() + 1 < 10
+            ? "0" + (date.getMonth() + 1)
+            : date.getMonth() + 1;
+    const day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
+    return date.getFullYear() + "-" + month + "-" + day;
+};
+
+export const getFormatTime = (timestamp) => {
+    const now = getUnix(); // 当前时间戳
+    const today = getTodayUnix(); // 今天0点的时间戳
+    const timer = (now - timestamp) / 1000; // 转换为秒级时间戳
+    let tip = '';
+
+    if (timer <= 0) {
+        tip = "刚刚";
+    } else if (Math.floor(timer / 60) <= 0) {
+        tip = "刚刚";
+    } else if (timer < 3600) {
+        tip = Math.floor(timer / 60) + "分钟前";
+    } else if (timer >= 3600 && timestamp - today >= 0) {
+        tip = Math.floor(timer / 3600) + "小时前";
+    } else if (timer / 86400 <= 31) {
+        tip = Math.ceil(timer / 86400) + "天前";
+    } else {
+        tip = getLastDate(timestamp);
+    }
+    return tip;
+};
+
+export const getrand = (m, n) => {
+    return Math.floor(Math.random() * (n - m + 1)) + m;
+};

+ 134 - 0
src/pages/archives/index.js

@@ -0,0 +1,134 @@
+import React, {PureComponent} from "react";
+import {connect} from 'react-redux';
+import axios from "axios";
+import {ArchivesWrapper, ArticleTop, MainWrapper} from "./style";
+import {Link} from 'react-router-dom';
+import {Spin} from "antd";
+
+const setYears = (time) => {
+    const date = new Date(time);
+    let Y = date.getFullYear() + '年';
+    let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '月';
+    return (Y + M)
+};
+
+const setDay = (time) => {
+    const date = new Date(time);
+    return date.getDate() + '日'
+};
+
+const ArchivesList = (props) => {
+    const {list, loading} = props;
+    const Class = ['info', 'dark', 'success', 'black', 'warning', 'primary', 'danger'];
+    if (loading) {
+        return (
+            <div className="example">
+                <Spin size="large"/>
+            </div>
+        )
+    } else {
+        return (
+            <div className='wrapper-md'>
+                <ul className='timeline'>
+                    {list.map((item, index) => {
+                        return (
+                            <div className={Class[index % Class.length]} key={index}>
+                                <li className='tl-header'>
+                                    <h2 className='title'>{setYears(item.archiveDate)}</h2>
+                                </li>
+                                {item.archivePosts.map((item2, index2) => {
+                                    return (
+                                        <div className='tl-body' key={index2}>
+                                            <li className='tl-item'>
+                                                <div className='tl-wrap'>
+                                                    <span className='tl-date'>
+                                                        {setDay(item2.createTime)}
+                                                    </span>
+                                                    <h3 className="tl-content">
+                                                        <span className="arrow left"/>
+                                                        <Link to={'/article/' + item2.id} className="text-lt">
+                                                            {item2.title}
+                                                        </Link>
+                                                    </h3>
+                                                </div>
+                                            </li>
+                                        </div>
+                                    )
+                                })}
+                            </div>
+                        )
+                    })}
+                    <div className='tl-header'>
+                        <div className="start">开始</div>
+                    </div>
+                </ul>
+            </div>
+        )
+    }
+};
+
+class Archives extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            timg: '',
+            list: [],
+            loading: true
+        }
+    }
+
+    render() {
+        const {list, loading} = this.state;
+        return (
+            <ArchivesWrapper>
+                <div className='pattern-center-blank'/>
+                <ArticleTop>
+                    <div className='pattern-attachment-img'>
+                        <img className='lazyload' src={this.state.timg} alt=""/>
+                    </div>
+                    <div className='pattern-header '>
+                        <h1>文章归档</h1>
+                    </div>
+                </ArticleTop>
+                <MainWrapper>
+                    <ArchivesList list={list} loading={loading}/>
+                </MainWrapper>
+            </ArchivesWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.getArchives();
+        this.getTimg();
+    }
+
+    getArchives() {
+        this.setState({loading: true});
+        axios.get('/posts/archive/v1/list').then((res) => {
+            if (res.success === 1) {
+                this.setState({
+                    list: res.models,
+                    loading: false
+                })
+            }
+        });
+    }
+
+    getTimg() {
+        const list = this.props.topImg;
+        const num = this.getrand(0, list.length - 1);
+        this.setState({timg: list[num].img})
+    }
+
+    getrand(m, n) {
+        return Math.floor(Math.random() * (n - m + 1)) + m;
+    }
+}
+
+const mapState = (state) => {
+    return {
+        topImg: state.getIn(['image', 'topImg'])
+    }
+};
+
+export default connect(mapState)(Archives)

+ 366 - 0
src/pages/archives/style.js

@@ -0,0 +1,366 @@
+import styled from 'styled-components';
+
+export const ArchivesWrapper = styled.div`
+    .pattern-center-blank{
+        padding-top: 75px;
+        background-color: #fff;
+    }
+     @media(max-width:768px){
+        .pattern-center-blank{
+            padding-top: 50px;
+        }
+    }
+`;
+
+export const ArticleTop = styled.div`
+    position: relative;
+    top: 0;
+    left: 0;
+    width: 100%;
+    overflow: hidden;
+    &:before{
+        content: "";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: rgba(0,0,0,.3);
+    }
+    &:after{
+        content: '';
+        width: 150%;
+        height: 4.375rem;
+        background: #fff;
+        left: -25%;
+        bottom: -2.875rem;
+        border-radius: 100%;
+        position: absolute;
+    }
+    .pattern-attachment-img{
+        background-repeat: no-repeat;
+        background-size: cover;
+        background-position: center center;
+        background-origin: border-box;
+        width: 100%;
+        height: 400px;
+        img{
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            pointer-events: none;
+        }
+    }
+    .pattern-header {
+        position: absolute;
+        top: 45%;
+        left: 0;
+        right: 0;
+        text-align: center;
+        color: #fff;
+        z-index: 1;
+        h1{
+            color: #fff;
+            font-size: 40px;
+            font-weight: 500;
+            width: 80%;
+            margin: auto;
+            padding: 0;
+            border: 0;
+        }
+    }
+    @media(max-width:768px){
+        .pattern-attachment-img{
+            height:280px;
+        }
+        .pattern-header {
+            top:40%;
+            h1{
+                font-size:24px;
+            }
+        }
+    }
+`;
+
+export const MainWrapper = styled.div`
+    min-height:600px;
+    max-width: 900px;
+    padding: 0 10px;
+    margin-left: auto;
+    margin-right: auto;
+    padding-top:50px;
+    background-color: rgba(255,255,255,.8);
+    .example{
+        height:52px;
+        line-height:52px;
+        text-align: center;
+        i{
+            background-color: #FE9600;
+        }
+    }
+    .wrapper-md{
+        padding: 20px;
+        max-width: 600px;
+        margin: 0 auto;
+    }
+    .timeline {
+        padding: 0;
+        margin: 0;
+    }
+    .tl-header {
+        display: block;
+        width: 8em;
+        margin-right: 2px;
+        margin-left: 2px;
+        text-align: center;
+        h2{
+            color: #ffffff;
+            border-radius: 50px;
+            font-weight: 500;
+            display: inline-block;
+            padding: 5px 15px;
+            font-size: 12px;
+            line-height: 1.5;
+            border: 1px solid transparent;
+        }
+    }
+    .tl-item {
+        display: block;
+    }
+    .tl-item:after, .tl-item:before {
+        display: table;
+        content: " ";
+    }
+    .tl-item:after {
+        clear: both;
+    }
+    .tl-wrap {
+        display: block;
+        padding: 15px 0 15px 20px;
+        margin-left: 4em;
+        border-color: #dee5e7;
+        border-style: solid;
+        border-width: 0 0 0 4px;
+    }
+    .tl-wrap:after, .tl-wrap:before {
+        display: table;
+        content: " ";
+    }
+    .tl-wrap:after {
+        clear: both;
+    }
+    .tl-wrap:before {
+        position: relative;
+        top: 15px;
+        float: left;
+        width: 10px;
+        height: 10px;
+        margin-left: -27px;
+        background: #edf1f2;
+        border-color: inherit;
+        border-style: solid;
+        border-width: 3px;
+        border-radius: 50%;
+        content: "";
+        box-shadow: 0 0 0 4px #f0f3f4;
+    }
+    .tl-date {
+        position: relative;
+        top: 10px;
+        display: block;
+        float: left;
+        width: 4.5em;
+        margin-left: -7.5em;
+        text-align: right;
+        font-size: 14px;
+    }
+    .tl-content {
+        position: relative;
+        display: inline-block;
+        padding-top: 10px;
+        padding-bottom: 10px;
+        border: 1px solid transparent;
+        border-radius: 4px;
+        -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05);
+        box-shadow: 0 1px 1px rgba(0,0,0,.05);
+        padding-right: 15px;
+        padding-left: 15px;
+        a{
+            color: #ffffff;
+            font-size: 14px;
+        }
+    }
+    .arrow {
+        z-index: 10;
+        border-width: 9px;
+        left: -9px;
+        margin-top: -9px;
+        border-right-color: rgba(255,255,255,.1);
+        border-left-width: 0;
+        top: 19px;
+    }
+    .arrow:after {
+        border-width: 8px;
+        content: "";
+    }
+    .arrow, .arrow:after {
+        position: absolute;
+        display: block;
+        width: 0;
+        height: 0;
+        border-color: transparent;
+        border-style: solid;
+    }
+    .arrow.left:after {
+        bottom: -8px;
+        left: 1px;
+        border-right-color: #fff;
+        border-left-width: 0;
+    }
+    .start{
+        border-radius: 50px;
+        color: #58666e;
+        background-color: #fff;
+        border-color: #dee5e7;
+        box-shadow: 0 1px 1px rgba(90,90,90,.1);
+        font-weight: 500;
+        padding: 5px 15px;
+        font-size: 12px;
+        display: inline-block;
+        text-align: center;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+        border: 1px solid transparent;
+    }
+    .info .title {
+        background-color: #23b7e5;
+        border-color: #23b7e5;
+    }
+    
+    .info .tl-wrap{
+        border-color: #23b7e5;
+    }
+    
+    .info .tl-content{
+        background-color: #23b7e5;
+    }
+    
+    .info .arrow:after {
+        border-right-color: #23b7e5;
+    }
+    
+    .dark .title {
+        background-color: #3a3f51;
+        border-color: #3a3f51;
+    }
+    
+    .dark .tl-wrap{
+        border-color: #3a3f51;
+    }
+    
+    .dark .tl-content{
+        background-color: #3a3f51;
+    }
+    
+    .dark .arrow:after {
+        border-right-color: #3a3f51;
+    }    
+
+    .success .title {
+        background-color: #27c24c;
+        border-color: #27c24c;
+    }
+    
+    .success .tl-wrap{
+        border-color: #27c24c;
+    }
+    
+    .success .tl-content{
+        background-color: #27c24c;
+    }
+    
+    .success .arrow:after {
+        border-right-color: #27c24c;
+    } 
+    
+    .black .title {
+        background-color: #1c2b36;
+        border-color: #1c2b36;
+    }
+    
+    .black .tl-wrap{
+        border-color: #1c2b36;
+    }
+    
+    .black .tl-content{
+        background-color: #1c2b36;
+    }
+    
+    .black .arrow:after {
+        border-right-color: #1c2b36;
+    } 
+    
+    .warning .title {
+        background-color: #fad733;
+        border-color: #fad733;
+    }
+    
+    .warning .tl-wrap{
+        border-color: #fad733;
+    }
+    
+    .warning .tl-content{
+        background-color: #fad733;
+    }
+    
+    .warning .arrow:after {
+        border-right-color: #fad733;
+    } 
+    
+    .primary .title {
+        background-color: #7266ba;
+        border-color: #7266ba;
+    }
+    
+    .primary .tl-wrap{
+        border-color: #7266ba;
+    }
+    
+    .primary .tl-content{
+        background-color: #7266ba;
+    }
+    
+    .primary .arrow:after {
+        border-right-color: #7266ba;
+    } 
+    
+    .danger .title {
+        background-color: #f05050;
+        border-color: #f05050;
+    }
+    
+    .danger .tl-wrap{
+        border-color: #f05050;
+    }
+    
+    .danger .tl-content{
+        background-color: #f05050;
+    }
+    
+    .danger .arrow:after {
+        border-right-color: #f05050;
+    }
+    @media(max-width:768px){
+        padding-top:30px;
+        min-height:400px;
+        .page-header{
+            margin-bottom: 30px;
+                h1{
+                    font-size: 16px;
+                    font-weight: 400;
+                    border: 1px dashed #ddd;
+                    padding:10px;
+                    color: #828282;
+                }
+        }
+    }
+`;

+ 182 - 0
src/pages/article/components/Comments.js

@@ -0,0 +1,182 @@
+import React, {PureComponent} from "react";
+import {CommentsWrapper, CommentTextarea} from '../style';
+import {getFormatTime, getTime} from '../../../lib/public';
+import {Pagination, message} from 'antd';
+import axios from "axios";
+import {loginGithubHandel} from '../../../lib/auth';
+import openWindow from '../../../lib/openWindow';
+
+class Comments extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            id: props.id,
+            isComment: props.isComment,
+            commentsList: [],
+            pageInfo: {},
+            value: '',
+            parentId: '',
+            preContent: ''
+        };
+        this.setValue = this.setValue.bind(this);
+        this.addComments = this.addComments.bind(this);
+        this.reply = this.reply.bind(this);
+    }
+
+    render() {
+        const {pageInfo, id, commentsList, isComment} = this.state;
+        return (
+            <CommentsWrapper>
+                <h3 className='comments-list-title'>Comments | <span className="noticom">{pageInfo.total} 条评论 </span>
+                </h3>
+                <ul className='commentwrap'>
+                    {commentsList.map((item, index) => {
+                        return (
+                            <li className='comment' key={index}>
+                                <div className='commentinfo flex-items'>
+                                    <img src={item.authorAvatar} alt=""/>
+                                    <div className='commeta cell'>
+                                        <h2>{item.authorName}</h2>
+                                        <h3>{getTime(item.createTime)} | {getFormatTime(item.createTime)}</h3>
+                                    </div>
+                                    <span onClick={() => this.reply(item.id, item.authorName)}
+                                          className='comment-reply-link'>Reply</span>
+                                </div>
+                                <div className='body'>
+                                    <p>{item.parentUserName && <span>@{item.parentUserName}</span>}{item.content}</p>
+                                </div>
+                                <hr/>
+                            </li>
+                        )
+                    })}
+                </ul>
+                <Pagination
+                    className='pagination'
+                    hideOnSinglePage
+                    size="small"
+                    onChange={(page) => this.getComments(id, page)}
+                    itemRender={this.itemRender}
+                    current={pageInfo.page} pageSize={5} total={pageInfo.total}
+                />
+                {isComment === 1 ? <CommentTextarea>
+                    <textarea
+                        placeholder="你是我一生只会遇见一次的惊喜 ..."
+                        name="comment"
+                        className="commentbody"
+                        id="comment"
+                        rows="5" tabIndex="4"
+                        value={this.state.value}
+                        onChange={this.setValue}
+                    />
+                    <div className='form-submit'>
+                        <input
+                            onClick={this.addComments}
+                            name="submit"
+                            type="submit"
+                            id="submit"
+                            className="submit"
+                            value="BiuBiuBiu~"
+                        />
+                    </div>
+                </CommentTextarea> : <p className='text'>此处评论已关闭</p>}
+            </CommentsWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.getComments(this.props.id, 1);
+    }
+
+    reply(parentId, authorName) {
+        this.setState({
+            value: `@${authorName}:`,
+            preContent: `@${authorName}:`,
+            parentId: parentId
+        });
+    }
+
+    itemRender(current, type, originalElement) {
+        if (type === 'prev') {
+            return <span>« Older</span>;
+        }
+        if (type === 'next') {
+            return <span>Newer »</span>;
+        }
+        return originalElement;
+    }
+
+    setValue(e) {
+        const value = e.target.value;
+        this.setState((prevState) => {
+            return {
+                value: value,
+                parentId: value ? prevState.parentId : '',
+                preContent: value ? prevState.preContent : '',
+            }
+        });
+    }
+
+    addComments() {
+        const {value, id, parentId, preContent} = this.state;
+        const data = {content: value, postsId: id};
+        if (value === '') {
+            message.warning('please type a comment');
+            return false
+        }
+        if (parentId) {
+            let content = value.replace(preContent, "");
+            if (content === '') {
+                message.warning('please type a comment');
+                return false
+            }
+            if (value.indexOf(preContent, 0) !== -1) {
+                data["parentId"] = parentId;
+                data["content"] = content;
+            }
+        }
+        axios({
+            method: 'post',
+            url: '/comments/comments/v1/add',
+            data: data
+        }).then((res) => {
+            if (res.success === 1) {
+                message.success('评论成功');
+                this.setState({
+                    value: '',
+                    parentId: ''
+                });
+                this.getComments(id, 1);
+            } else {
+                this.login();
+            }
+        });
+    }
+
+    login() {
+        axios.get('/auth/github/v1/get').then((res) => {
+            if (res.success === 1) {
+                openWindow(res.model, "绑定GitHub", 540, 540);
+                window.addEventListener("message", loginGithubHandel, false);
+            }
+        });
+    }
+
+    getComments(id, page) {
+        axios.get('/comments/comments-posts/v1/list', {
+            params: {
+                page: page,
+                size: 5,
+                postsId: id
+            }
+        }).then((res) => {
+            if (res.success === 1) {
+                this.setState({
+                    commentsList: res.models,
+                    pageInfo: res.pageInfo
+                })
+            }
+        });
+    }
+}
+
+export default Comments;

+ 159 - 0
src/pages/article/index.js

@@ -0,0 +1,159 @@
+import React, {PureComponent} from 'react';
+import {connect} from 'react-redux';
+import marked from 'marked';
+import hljs from 'highlight.js';
+import {ArticleWrapper, ArticleTop, MainWrapper} from './style';
+import {getTime} from '../../lib/public';
+import 'highlight.js/styles/atom-one-dark.css'
+import {Spin} from 'antd';
+import Tocify from './tocify';
+import Comments from './components/Comments';
+import axios from "axios";
+
+class Article extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            content: '',
+            timg: '',
+            id: props.match.params.id,
+            socialsList: [],
+            tocify: new Tocify()
+        }
+    }
+
+    render() {
+        const {content, socialsList} = this.state;
+        const {name, avatar} = this.props.userInfo.toJS();
+        this.state.tocify && this.state.tocify.reset();
+        if (content.title) document.title = content.title;
+        return (
+            <ArticleWrapper>
+                <div className='pattern-center-blank'/>
+                <ArticleTop>
+                    <div className='pattern-attachment-img'>
+                        <img className='lazyload' src={content && (content.thumbnail || this.state.timg)} alt=""/>
+                    </div>
+                    <div className='single-header'>
+                        <h1 className='entry-title'>{content.title}</h1>
+                        {content && <p className='entry-census'>
+                            <span><img src={avatar} alt=""/></span>
+                            <span>{name}</span>
+                            <span className="bull">·</span>
+                            <span>{getTime(content.createTime)}</span>
+                            <span className="bull">·</span>
+                            <span>{content.views} 次阅读</span></p>
+                        }
+                    </div>
+                </ArticleTop>
+                <MainWrapper>
+                    {content ?
+                        <div className='flex-items'>
+                            <div className='cell'>
+                                <div className='entry-content'
+                                     dangerouslySetInnerHTML={{__html: marked(content.content)}}
+                                />
+                                {this.setSocials(socialsList)}
+                                <Comments id={this.state.id} isComment={content.isComment}/>
+                            </div>
+                            {this.state.tocify && this.state.tocify.render()}
+                        </div> : this.Spin()
+                    }
+                </MainWrapper>
+            </ArticleWrapper>
+        )
+    }
+
+
+    componentDidMount() {
+        const renderer = new marked.Renderer();
+        renderer.heading = (text, level) => {
+            const anchor = this.state.tocify.add(text, level);
+            return `<h${level} id="${anchor}">${text}</h${level}>`;
+        };
+        marked.setOptions({
+            renderer: renderer,
+            highlight: code => hljs.highlightAuto(code).value
+        });
+        this.getDetail(this.state.id);
+        this.getTimg();
+        this.getSocials();
+    }
+
+    getTimg() {
+        const list = this.props.topImg;
+        const num = this.getrand(0, list.length - 1);
+        this.setState({
+            timg: list[num].img
+        })
+    }
+
+    getDetail(id) {
+        axios.get('/posts/posts/v1/' + id).then((res) => {
+            if (res.success === 1) {
+                this.setState({
+                    content: res.model
+                })
+            }else {
+                this.props.history.push('/404');
+            }
+        });
+    }
+
+    getSocials(){
+        axios.get('/social/social/v1/socials?code=reward').then((res) => {
+            if (res.success === 1) {
+                this.setState({
+                    socialsList: res.models
+                })
+            }
+        });
+    }
+
+    setSocials(socialsList){
+        if(socialsList.length){
+            return(
+                <div className='single-reward'>
+                    <div className='reward-open'>
+                        <p>赏</p>
+                        <div className='reward-main'>
+                            <ul className='reward-row'>
+                                {socialsList.map((item,index)=>{
+                                    return(
+                                        <li key={index}>
+                                            <img src={item.content} alt=""/>
+                                            <p>{item.remark}</p>
+                                        </li>
+                                    )
+                                })}
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+            )
+        }else {
+            return null
+        }
+    }
+
+    getrand(m, n) {
+        return Math.floor(Math.random() * (n - m + 1)) + m;
+    }
+
+    Spin() {
+        return (
+            <div className="example">
+                <Spin size="large"/>
+            </div>
+        )
+    }
+}
+
+const mapState = (state) => {
+    return {
+        topImg: state.getIn(['image', 'topImg']),
+        userInfo: state.getIn(['header', 'userInfo']),
+    }
+};
+
+export default connect(mapState)(Article);

+ 653 - 0
src/pages/article/style.js

@@ -0,0 +1,653 @@
+import styled from 'styled-components';
+import hr from '../../statics/images/hr.gif';
+
+export const ArticleWrapper = styled.div`
+    .pattern-center-blank{
+        padding-top: 75px;
+        background-color: #fff;
+    }
+    @media(max-width:768px){
+        .pattern-center-blank{
+            padding-top: 50px;
+        }
+    }
+`;
+
+export const ArticleTop = styled.div`
+    position: relative;
+    top: 0;
+    left: 0;
+    width: 100%;
+    overflow: hidden;
+    &:before{
+        content: "";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: rgba(0,0,0,.3);
+    }
+    .pattern-attachment-img{
+        background-repeat: no-repeat;
+        background-size: cover;
+        background-position: center center;
+        background-origin: border-box;
+        width: 100%;
+        height: 400px;
+        img{
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            pointer-events: none;
+        }
+    }
+    .single-header{
+        max-width: 900px;
+        padding: 0 10px;
+        margin-left: auto;
+        margin-right: auto;
+        text-align: left;
+        top: auto;
+        bottom: 20px;
+        position: absolute;
+        left: 0;
+        right: 0;
+        color: #fff;
+        text-shadow: 2px 2px 10px #000;
+        z-index: 1;
+        .entry-title{
+            font-size: 32px;
+            width: 100%;
+            color: #fff;
+            font-weight: bold;
+        }
+        .entry-census{
+            color: #fff;
+            font-size: 14px;
+            padding: 18px 0 0;
+            line-height: 39px;
+            span{
+                color: #fff;
+                font-size: 14px;
+                img{
+                    width: 35px;
+                    height: 35px;
+                    border-radius: 100%;
+                    float: left;
+                    margin-right: 12px;
+                }
+            }
+            .bull {
+                margin: 0 5px;
+            }
+        }
+    }
+    @media(max-width:768px){
+        .pattern-attachment-img{
+            height:280px;
+        }
+        .single-header{
+            .entry-title{
+                font-size: 24px;
+            }
+            .entry-census{
+                padding:0;
+            }
+        }
+    }
+`;
+
+export const MainWrapper = styled.div`
+    min-height:600px;
+    max-width: 900px;
+    padding: 0 10px;
+    margin-left: auto;
+    margin-right: auto;
+    padding-top:50px;
+    background-color: rgba(255,255,255,.8);
+    @keyframes main {
+        0% {
+            opacity: 0;
+            transform: translateY(50px)
+        }
+        100% {
+            opacity: 1;
+            transform: translateY(0)
+        }
+    }
+    .cell{
+        margin-right:25px;
+    }
+    .entry-content {
+        position: relative;
+        animation: main 1s;
+    }
+    
+    .entry-content .begin,.single-begin {
+        float: left;
+        font-size: 3.6em;
+        line-height: 1em;
+        margin-right: 3px;
+        margin-top: 2px;
+        font-weight: 700
+    }
+    
+    @media screen and (max-width: 860px) {
+        .entry-content .begin,.single-begin {
+            margin-top:6px
+        }
+    }
+    
+    .entry-content ul {
+        list-style: disc;
+        border: 1px dashed #e4e4e4;
+        padding: 15px 10px 15px 50px;
+        color: #616161;
+        margin-left: 0;
+        border-radius: 10px;
+        margin:16px 0;
+    }
+    
+    .entry-content ol {
+        list-style: decimal;
+        border: 1px dashed #e4e4e4;
+        padding: 15px 10px 15px 50px;
+        color: #616161;
+        margin-left: 0;
+        border-radius: 10px;
+        margin:16px 0;
+    }
+    
+    .entry-content table {
+      display: block;
+      width: 100%;
+      overflow: auto;
+    }
+    
+    .entry-content table th {
+      font-weight: 600;
+    }
+    
+    .entry-content table th,.entry-content table td {
+      padding: 6px 13px;
+      border: 1px solid #dfe2e5;
+    }
+    
+    .entry-content table tr {
+      background-color: #fff;
+      border-top: 1px solid #c6cbd1;
+    }
+    
+    .entry-content table tr:nth-child(2n) {
+      background-color: #f6f8fa;
+    }
+    
+    .entry-content blockquote {
+        padding: 0 1em;
+        color: #6a737d;
+        border-left: .25em solid #dfe2e5;
+    }
+    
+    .entry-content blockquote>:first-child {
+      margin-top: 0;
+    }
+    
+    .entry-content blockquote>:last-child {
+      margin-bottom: 0;
+    }
+    
+    .entry-content ol li,.entry-content ul li {
+        padding: 8px 0
+    }
+    
+    .entry-content h3 {
+        padding-bottom: 8px;
+        border-bottom: 1px dashed #ddd;
+        color: #737373;
+        margim:17px 0;
+    }
+    
+    .entry-content h3,.entry-content h4,.entry-content h5 {
+        padding-left: 16px;
+    }
+    
+    .entry-content h1{
+        margin:16px 0;
+        clear:both;
+        font-size:24px;
+        color:rgb(64, 64, 64);
+    }
+    
+    .entry-content h2{
+        margin:18px 0;
+        clear:both;
+        font-size:22px;
+        color:rgb(64, 64, 64);
+    }
+    
+    .entry-content h3{
+        margin:17px 0;
+        clear:both;
+        font-size:20px;
+        color:rgb(64, 64, 64);
+    }
+    
+    .entry-content h4{
+        margin:16px 0;
+        clear:both;
+        font-size:18px;
+        color:rgb(64, 64, 64);
+    }
+    
+    .entry-content h5{
+        margin:15px 0;
+        clear:both;
+        font-size:16px;
+        color:rgb(64, 64, 64);
+    }
+    
+    .entry-content h6{
+        margin:14px 0;
+        clear:both;
+        font-size:14px;
+        color:rgb(64, 64, 64);
+    }
+    
+    .entry-content h2:after,.entry-content h1:after {
+        content: "\\00B6";
+        position: absolute;
+        color: #ff6d6d;
+        font-family: 'Merriweather Sans',Helvetica,Tahoma,Arial,'PingFang SC','Hiragino Sans GB','Microsoft Yahei','WenQuanYi Micro Hei',sans-serif;
+        padding-left: 6px;
+        font-size: 1.03em;
+    }
+    
+    .entry-content h3:after {
+        content: "#";
+        left: 0;
+        position: absolute;
+        color: #ff6d6d;
+    }
+    
+    .entry-content h4:after {
+        content: "▌";
+        left: 0;
+        position: absolute;
+        color: #ff6d6d;
+    }
+    
+    .entry-content h5:after {
+        content: "♯";
+        left: 0;
+        position: absolute;
+        color: #ff6d6d;
+    }
+    
+    .entry-content a {
+        color: #e67474;
+        position: relative;
+    }
+    
+    .entry-content a:hover {
+        color: orange;
+        text-decoration: none;
+    }
+    
+    .entry-content a:after {
+        content: '';
+        position: absolute;
+        width: 100%;
+        transform: scaleX(0);
+        height: 2px;
+        bottom: 0;
+        left: 0;
+        background-color: orange;
+        transform-origin: bottom right;
+        transition: transform .25s ease-out;
+    }
+    
+    .entry-content a:hover:after {
+        transform: scaleX(1);
+        transform-origin: bottom left;
+    }
+    
+    .entry-content p {
+        color: #797979;
+        margin: 15px 0 22px;
+        line-height: 30px;
+    }
+    
+    .entry-content hr {
+        max-width: 100%;
+        height: 50px;
+        background: url(${hr}) 100% no-repeat;
+        border: none;
+        margin-top: 15px;
+        margin-bottom: 15px;
+    }
+    
+    .entry-content .post-password-form {
+        text-align: center;
+    }
+    
+    .entry-content a img.alignleft,.entry-content a img.alignright,.entry-content a img.aligncenter {
+        cursor: -webkit-zoom-in;
+    }
+    
+    .entry-content img{
+        max-width:100%;
+    }
+    
+    pre{
+        position: relative;
+        background: #2b3940;
+        border-radius: 5px;
+        line-height: 1.6;
+        margin-bottom: 1.6em;
+        font-size: 15px;
+        max-width: 100%;
+        overflow: auto;
+        text-shadow: none;
+        color: #000;
+        padding: 20px;
+        box-shadow: 0 10px 30px 0px rgba(0,0,0,.4);
+    }
+    
+    code{
+        color: #fff;
+        word-break: break-word;
+        padding: 2px;
+        text-shadow: none;
+        border-radius: 0 0 5px 5px;
+    }
+    
+    .example {
+        text-align: center;
+        border-radius: 4px;
+        margin-bottom: 20px;
+        padding: 30px 50px;
+        margin: 20px 0;
+        i{
+            background-color: #FE9600;
+        }
+    }
+    .toc{
+        width:200px;
+        h3{
+            padding: 7px 0 7px 16px;
+            line-height: 1.143;
+            font-size: 16px;
+            font-weight: bold;
+            color: #FE9600;
+        }
+        .ant-anchor-link-active > .ant-anchor-link-title{
+            color: #FE9600;
+        }
+        .ant-anchor-link-title:hover{
+            color: #FE9600;
+        }
+        .ant-anchor-ink-ball{
+            border: 2px solid #FE9600;
+        }
+    }
+    .flex-items{
+        align-items: initial;
+    }
+    
+    .single-reward{
+        position: relative;
+        width: 100%;
+        margin: 35px auto;
+        text-align: center;
+        z-index: 999;
+        .reward-open {
+            position: relative;
+            width: 40px;
+            height: 40px;
+            font-size: 18px;
+            padding: 7px;
+            color: #fff;
+            text-align: center;
+            display: inline-block;
+            border-radius: 100%;
+            background: #d34836;
+            cursor: pointer;
+        }
+        .reward-open:hover .reward-main{
+            display: block;
+        }
+        .reward-main{
+            position: absolute;
+            top: 40px;
+            left: -157px;
+            margin: 0;
+            padding: 15px 0 0;
+            width: 355px;
+            background: 0 0;
+            display: none;
+            animation: main .4s;
+        }
+        .reward-row{
+            list-style: disc;
+            border: 1px dashed #e4e4e4;
+            margin: 0 auto;
+            padding: 20px 15px 10px;
+            background: #f5f5f5;
+            display: inline-block;
+            border-radius: 4px;
+            cursor: auto;
+            li{
+                list-style-type: none;
+                padding: 0 12px;
+                display: inline-block;
+                img{
+                    width: 130px;
+                    max-width: 130px;
+                    border-radius: 3px;
+                    position: relative;
+                }
+                p{
+                    color: #666666;
+                    font-size: 12px;
+                    text-align: center;
+                }
+            }
+        }
+    }
+    
+    @media(max-width:768px){
+        padding:10px;
+        min-height:400px;
+        .cell{
+            margin:0;
+        }
+        .toc{
+            display:none;
+        }
+    }
+`;
+
+export const CommentsWrapper = styled.div`
+    padding-top: 40px;
+    .comments-list-title{
+        width: 100%;
+        margin: 0 auto;
+        margin-bottom: 40px;
+        color: #7d7d7d;
+        font-weight: 400;
+        span{
+            font-size: 13px;
+            font-weight: 400;
+            color: #909090;
+        }
+    }
+    .commentwrap{
+        margin: 0 auto 30px;
+        .comment{
+            .commentinfo{
+                img{
+                    width:40px;
+                    height:40px;
+                    border-radius: 100%;
+                    box-shadow: 0 1px 10px -6px rgba(0,0,0,.5);
+                    margin-right: 15px;
+                }
+                .commeta{
+                    h2{
+                        color: #FE9600;
+                        font-size: 15px;
+                        font-weight: 600;
+                        line-height:20px;
+                    }
+                    h3{
+                        line-height:20px;
+                        font-size: 12px;
+                        letter-spacing: 0px;
+                        text-transform: none;
+                        color: rgba(0,0,0,.35);
+                    }
+                }
+                .comment-reply-link{
+                    font-size: 12px;
+                    display: block;
+                    margin-left: 10px;
+                    float: right;
+                    text-transform: uppercase;
+                    color: #fff;
+                    height: 20px;
+                    background-color: #FE9600;
+                    line-height: 20px;
+                    padding: 0 6px;
+                    border-radius: 3px;
+                    cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+                    opacity: 0;
+                    transition: color .2s ease-out,border .2s ease-out,opacity .2s ease-out;
+                }
+            }
+            &:hover .comment-reply-link{
+                opacity: .9;
+            }
+            .body{
+                line-height: 32px;
+                color: #63686d;
+                border-bottom: 1px solid rgba(0,0,0,.05);
+                position: relative;
+                p{
+                    font-size: 14px;
+                    line-height: 30px;
+                    margin-top: 10px;
+                    padding-bottom: 20px;
+                    padding-left: 3px;
+                    color: #63686d;
+                    span{
+                        font-size: 12px;
+                        color: #909090;
+                        margin-right:3px;
+                    }
+                }
+            }
+            hr{
+                height: 0;
+                width: 100%;
+                background: #eee;
+                border: 0;
+                margin: 40px 0
+            }
+        }
+    }
+    .pagination{
+        margin:20px 0;
+        .ant-pagination-item{
+            border:none;
+            font-family: inherit;
+            font-size: 15px;
+        }
+        .ant-pagination-item a{
+            font-family: inherit;
+            font-size: 15px;
+        }
+        .ant-pagination-item-active a{
+            color: #FE9600;
+        }
+        .ant-pagination-item:focus a, .ant-pagination-item:hover a{
+            color: #FE9600;
+        }
+        .ant-pagination-next,.ant-pagination-prev{
+            color: #FE9600;
+            font-family: inherit;
+            font-size: 15px;
+        }
+        .ant-pagination-next span,.ant-pagination-prev span{
+            color: #FE9600;
+            font-family: inherit;
+            font-size: 15px;
+        }
+        .ant-pagination-next:hover span,.ant-pagination-prev:hover span{
+            color: #FE9600;
+        }
+        .ant-pagination-disabled span{
+            color: rgba(0, 0, 0, 0.25);
+        }
+        .ant-pagination-disabled:hover span{
+            color: rgba(0, 0, 0, 0.25);
+        }
+    }
+    .text{
+        font-size:14px;
+        padding: 20px 0;
+    }
+`;
+
+export const CommentTextarea = styled.div`
+     position: relative
+     .commentbody{
+        width:100%;
+        background: #fff;
+        padding: 21px 21px 20px;
+        font-size: 14px;
+        display: block;
+        height: 180px;
+        margin-bottom: 10px;
+        color: #535a63;
+        border: 1px solid #ddd;
+        background-color: transparent;
+        background-image: url(https://cos.nosum.cn/sakura/comment-bg.png);
+        background-size: contain;
+        background-repeat: no-repeat;
+        background-position: right;
+        resize: vertical;
+        border-radius: 6px;
+        outline:none;
+     }
+    .commentbody:focus{
+        border: 1px solid #FE9600; 
+    }
+    .form-submit {
+        clear: both;
+        display: block;
+        overflow: hidden;
+        margin: 20px 0;
+        input{
+            background: #fff;
+            border-radius: 6px;
+            width:100%;
+            margin: 0;
+            padding: 15px 25px;
+            text-transform: none;
+            color: #535a63;
+            -webkit-transition: all .1s ease-out;
+            -moz-transition: all .1s ease-out;
+            transition: all .1s ease-out;
+            box-shadow: none;
+            border: 1px solid #ccc;
+            text-shadow: none;
+            cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+        }
+        input:hover{
+            border: 1px solid #fe9600;
+            border-color: #FE9600;
+            color: #FE9600;
+        }
+    }
+`;

+ 79 - 0
src/pages/article/tocify.js

@@ -0,0 +1,79 @@
+import React from 'react';
+import {Anchor} from 'antd';
+import {last} from 'lodash';
+
+const {Link} = Anchor;
+
+const handleClick = (e, link) => {
+    e.preventDefault();
+    // console.log(link);
+};
+
+export default class Tocify {
+    constructor() {
+        this.anchors = [];
+        this.tocItems = [];
+        this.index = 0;
+    }
+
+    add(text, level) {
+        const anchor = `toc${level}${++this.index}`;
+        this.anchors.push(anchor);
+        const item = {anchor, level, text};
+        const items = this.tocItems;
+
+        if (items.length === 0) { // 第一个 item 直接 push
+            items.push(item);
+        } else {
+            let lastItem = last(items); // 最后一个 item
+
+            if (item.level > lastItem.level) { // item 是 lastItem 的 children
+                for (let i = lastItem.level + 1; i <= 6; i++) {
+                    const {children} = lastItem;
+                    if (!children) { // 如果 children 不存在
+                        lastItem.children = [item];
+                        break;
+                    }
+
+                    lastItem = last(children); // 重置 lastItem 为 children 的最后一个 item
+
+                    if (item.level <= lastItem.level) { // item level 小于或等于 lastItem level 都视为与 children 同级
+                        children.push(item);
+                        break;
+                    }
+                }
+            } else { // 置于最顶级
+                items.push(item);
+            }
+        }
+
+        return anchor;
+    }
+
+    reset = () => {
+        this.tocItems = [];
+        this.anchors = [];
+        this.index = 0;
+    };
+
+    renderToc(items) { // 递归 render
+        return items.map(item => (
+            <Link key={item.anchor} href={`#${item.anchor}`} title={item.text}>
+                {item.children && this.renderToc(item.children)}
+            </Link>
+        ));
+    }
+
+    render() {
+        if (this.tocItems.length) {
+            return (
+                <Anchor className='toc' affix showInkInFixed onClick={handleClick} offsetTop={100}>
+                    <h3>文章目录</h3>
+                    {this.renderToc(this.tocItems)}
+                </Anchor>
+            );
+        } else {
+            return null
+        }
+    }
+}

+ 129 - 0
src/pages/category/index.js

@@ -0,0 +1,129 @@
+import React, {PureComponent} from "react";
+import {CategoryArticleWrapper, ArticleTop, MainWrapper} from './style';
+import {connect} from 'react-redux';
+import axios from "axios";
+import CatList from "../../components/List";
+import PagInation from '../../components/PagInation';
+
+class Category extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            timg: '',
+            id: props.match.params.id,
+            finished: false,
+            loading: true,
+            page: 1,
+            CategoryList: []
+        };
+        this.getList = this.getList.bind(this);
+    }
+
+    render() {
+        const {category} = this.props;
+        const {CategoryList, page, finished, loading, id} = this.state;
+        return (
+            <CategoryArticleWrapper>
+                <div className='pattern-center-blank'/>
+                <ArticleTop>
+                    <div className='pattern-attachment-img'>
+                        <img className='lazyload' src={this.state.timg} alt=""/>
+                    </div>
+                    <div className='pattern-header '>
+                        <h1>{this.setCategory(category)}</h1>
+                    </div>
+                </ArticleTop>
+                <MainWrapper>
+                    <header className="page-header">
+                        <h1 className="page-title">{`分类 “${this.setCategory(category)}” 下的文章`}</h1>
+                    </header>
+                    <CatList list={CategoryList}/>
+                    <PagInation page={page} id={id} finished={finished} loading={loading} getList={this.getList}/>
+                </MainWrapper>
+            </CategoryArticleWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.getList(1, this.state.id, true);
+        this.getTimg();
+    }
+
+    getList(page, id, override) {
+        this.setState({loading: true});
+        axios.get('/posts/posts/v1/list', {
+            params: {
+                page: page,
+                size: 10,
+                categoryId: id
+            }
+        }).then((res) => {
+            if (res.success === 1) {
+                let current = res.pageInfo.page * res.pageInfo.size;
+                let total = res.pageInfo.total;
+                const data = res.models;
+                const Img = this.props.ListImg;
+                let arr = [];
+                for (let i = 0; i < data.length; i++) {
+                    arr.push({
+                        id: data[i].id,
+                        title: data[i].title,
+                        thumbnail: data[i].thumbnail || Img[this.getrand(0, Img.length - 1)].img,
+                        comments: data[i].comments,
+                        status: data[i].status,
+                        summary: data[i].summary,
+                        views: data[i].views,
+                        createTime: data[i].createTime,
+                        syncStatus: data[i].syncStatus,
+                        author: data[i].author,
+                        categoryName: data[i].categoryName
+                    })
+                }
+                this.setState((prevState) => {
+                    return {
+                        CategoryList: override ? arr : [...prevState.CategoryList, ...arr],
+                        page: page + 1,
+                        loading: false
+                    }
+                });
+                if (current > total) {
+                    this.setState({
+                        finished: true
+                    })
+                }
+            }
+        });
+    }
+
+    getTimg() {
+        const list = this.props.topImg;
+        const num = this.getrand(0, list.length - 1);
+        this.setState({timg: list[num].img})
+    }
+
+    getrand(m, n) {
+        return Math.floor(Math.random() * (n - m + 1)) + m;
+    }
+
+    setCategory(category) {
+        const id = parseFloat(this.props.match.params.id);
+        let Text = '';
+        const list = category.toJS();
+        list.forEach((item) => {
+            if (!Text && item.id === id) {
+                Text = item.name;
+            }
+        });
+        return Text;
+    }
+}
+
+const mapState = (state) => {
+    return {
+        category: state.getIn(['header', 'category']),
+        topImg: state.getIn(['image', 'topImg']),
+        ListImg: state.getIn(['image', 'ListImg']),
+    }
+};
+
+export default connect(mapState)(Category)

+ 131 - 0
src/pages/category/style.js

@@ -0,0 +1,131 @@
+import styled from 'styled-components';
+
+export const CategoryArticleWrapper = styled.div`
+    .pattern-center-blank{
+        padding-top: 75px;
+        background-color: #fff;
+    }
+    @media(max-width:768px){
+        .pattern-center-blank{
+            padding-top: 50px;
+        }
+    }
+`;
+
+export const ArticleTop = styled.div`
+    position: relative;
+    top: 0;
+    left: 0;
+    width: 100%;
+    overflow: hidden;
+    &:before{
+        content: "";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: rgba(0,0,0,.3);
+    }
+    &:after{
+        content: '';
+        width: 150%;
+        height: 4.375rem;
+        background: #fff;
+        left: -25%;
+        bottom: -2.875rem;
+        border-radius: 100%;
+        position: absolute;
+    }
+    .pattern-attachment-img{
+        background-repeat: no-repeat;
+        background-size: cover;
+        background-position: center center;
+        background-origin: border-box;
+        width: 100%;
+        height: 400px;
+        img{
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            pointer-events: none;
+        }
+    }
+    .pattern-header {
+        position: absolute;
+        top: 45%;
+        left: 0;
+        right: 0;
+        text-align: center;
+        color: #fff;
+        z-index: 1;
+        h1{
+            color: #fff;
+            font-size: 40px;
+            font-weight: 500;
+            width: 80%;
+            margin: auto;
+            padding: 0;
+            border: 0;
+        }
+    }
+    @media(max-width:768px){
+        .pattern-attachment-img{
+            height:280px;
+        }
+        .pattern-header {
+            top:40%;
+            h1{
+                font-size:24px;
+            }
+        }
+    }
+`;
+
+export const MainWrapper = styled.div`
+    min-height:600px;
+    max-width: 900px;
+    padding: 0 10px;
+    margin-left: auto;
+    margin-right: auto;
+    padding-top:50px;
+    background-color: rgba(255,255,255,.8);
+    animation: main 1s;
+    @keyframes main {
+        0% {
+            opacity: 0;
+            transform: translateY(50px)
+        }
+        100% {
+            opacity: 1;
+            transform: translateY(0)
+        }
+    }
+    .page-header{
+        position: relative;
+        text-align: center;
+        margin-bottom: 50px;
+        color: #9C9C9C;
+        h1{
+            font-size: 20px;
+            font-weight: 400;
+            border: 1px dashed #ddd;
+            padding: 15px;
+            color: #828282;
+        }
+    }
+    @media(max-width:768px){
+        padding-top:30px;
+        min-height:400px;
+        .page-header{
+            margin-bottom: 30px;
+                h1{
+                    font-size: 16px;
+                    font-weight: 400;
+                    border: 1px dashed #ddd;
+                    padding:10px;
+                    color: #828282;
+                }
+        }
+    }
+`;

+ 20 - 0
src/pages/error/index.js

@@ -0,0 +1,20 @@
+import React from "react";
+import {Eroor} from './style';
+import {Link} from 'react-router-dom';
+
+const Error = () =>{
+    return(
+        <Eroor>
+            <div className='pattern-center-blank'/>
+            <div className="container">
+                <img src={require('../../statics/images/404.png')} alt=""/>
+                <h2>查找的页面已被移动、删除、重命名<br/>或许已经不存在了!</h2>
+                <div className='btn'>
+                    <Link to={'/'}>返回首页</Link>
+                </div>
+            </div>
+        </Eroor>
+    )
+};
+
+export default Error

+ 51 - 0
src/pages/error/style.js

@@ -0,0 +1,51 @@
+import styled from 'styled-components';
+
+export const Eroor = styled.div`
+    .container{
+        min-height: 600px;
+        max-width: 900px;
+        padding: 0 10px;
+        margin-left: auto;
+        margin-right: auto;
+        background-color: rgba(255,255,255,.8);
+        padding-top:50px;
+    }
+    .pattern-center-blank{
+        padding-top: 75px;
+        background-color: #fff;
+    }
+    .container{
+        height:900px;
+    }
+    img{
+        display: block;
+        margin:20px auto 0;
+        max-width:100%;
+    }
+    h2{
+        text-align: center;
+        color: #333;
+    }
+    .btn{
+        text-align: center;
+        padding:30px 0;
+        a{
+            padding: 10px 30px;
+            margin: 0 10px;
+            border: 1px solid orange;
+            color: orange;
+            border-radius: 50px;
+        }
+        a:hover{
+            box-shadow: 0 0 4px rgba(255,165,0,.85);
+        }
+    }
+    @media(max-width:768px){
+        .pattern-center-blank{
+            padding-top: 50px;
+        }
+        .container{
+            height:auto;
+        }
+    }
+`;

+ 120 - 0
src/pages/home/components/Banner.js

@@ -0,0 +1,120 @@
+import React, {PureComponent} from "react";
+import {BannerWrapper, Center, Focusinfo} from '../style';
+import {scrollAnimation} from '../../../lib/auth';
+import axios from "axios";
+
+class Banner extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            info: []
+        };
+    }
+
+    render() {
+        const {banner, innerHeight} = this.props;
+        const {introduction,avatar} = this.props.userInfo.toJS();
+        const {info} = this.state;
+        return (
+            <BannerWrapper>
+                <div className="waveWrapper waveAnimation">
+                    <div className="waveWrapperInner bgTop">
+                        <div className="wave waveTop"/>
+                    </div>
+                    <div className="waveWrapperInner bgMiddle">
+                        <div className="wave waveMiddle"/>
+                    </div>
+                    <div className="waveWrapperInner bgBottom">
+                        <div className="wave waveBottom"/>
+                    </div>
+                </div>
+                <div className='headertop-down animated'>
+                    <span onClick={headertop_down}><i className='iconfont icon-chevrondown'/></span>
+                </div>
+                <Center style={{backgroundImage: banner, height: innerHeight + 'px'}}>
+                    <Focusinfo>
+                        <div className='header-tou'>
+                            <img src={avatar}  alt=''/>
+                        </div>
+                        <h1 className='glitch' data-text="NOSUMBLOG!">NOSUMBLOG!</h1>
+                        <div className='header-info'>
+                            <p className='ellipsis'>
+                                <i className='iconfont icon-quote-left'/>
+                                <span>{introduction || 'You got to put the past behind you before you can move on.'}</span>
+                                <i className='iconfont icon-quoteright'/>
+                            </p>
+                            <div className='top-social_v2'>
+                                <li onClick={this.props.getBanner}>
+                                    <img className='flipx' src={require('../../../statics/images/next-b.svg')} alt=""/>
+                                </li>
+                                {
+                                    info.map((item, index) => {
+                                        if (item.showType === 1) {
+                                            return (
+                                                <li className='img' key={index}>
+                                                    <img src={item.icon} alt=""/>
+                                                    <div className='img-box'>
+                                                        <img src={item.content} alt=""/>
+                                                    </div>
+                                                </li>
+                                            )
+                                        } else if (item.showType === 2) {
+                                            return (
+                                                <li className='text' key={index}>
+                                                    <img src={item.icon} alt=""/>
+                                                    <div className='text-box'>
+                                                        <p>{item.content}</p>
+                                                    </div>
+                                                </li>
+                                            )
+                                        } else if (item.showType === 3) {
+                                            return (
+                                                <li className='link' key={index}>
+                                                    <a href={item.content} target={'_blank'} rel="noopener noreferrer">
+                                                        <img src={item.icon} alt=""/>
+                                                    </a>
+                                                </li>
+                                            )
+                                        }else {
+                                            return null
+                                        }
+                                    })
+                                }
+                                <li onClick={this.props.getBanner}>
+                                    <img src={require('../../../statics/images/next-b.svg')} alt=""/>
+                                </li>
+                            </div>
+                        </div>
+                    </Focusinfo>
+                </Center>
+            </BannerWrapper>
+        )
+    }
+
+    componentDidMount() {
+        axios.get('/social/social/v1/info').then((res) => {
+            if (res.success === 1) {
+                const {models} = res;
+                let array = [];
+                for (let i = 0; i < models.length; i++) {
+                    array.push({
+                        show: false,
+                        icon: models[i].icon,
+                        content: models[i].content,
+                        showType: models[i].showType,
+                    })
+                }
+                this.setState({
+                    info: array
+                })
+            }
+        });
+    }
+}
+
+function headertop_down() {
+    const content = document.getElementById('content').offsetTop;
+    scrollAnimation(0, content);
+}
+
+export default Banner

+ 48 - 0
src/pages/home/components/Feature.js

@@ -0,0 +1,48 @@
+import React from "react";
+import {Link} from 'react-router-dom';
+import {FeatureWrapper, FeatureTitle} from '../style';
+import {Row, Col} from 'antd';
+import {getrand} from "../../../lib/public";
+
+const featureList = (props) => {
+    const {featureList, ListImg} = props;
+    const list = featureList.toJS();
+    return (
+        <Row className='top-feature-row' gutter={16}>
+            {
+                list.map((item, index) => {
+                    return (
+                        <Col className="top-feature-v2" key={index} xs={24} sm={24} md={8} lg={8} xl={8}>
+                            <div className='top-feature-item'>
+                                <Link to={'/article/' + item.id}>
+                                    <div className='img-box'>
+                                        <img src={item.thumbnail || ListImg[getrand(0, ListImg.length - 1)].img}
+                                             alt=""/>
+                                    </div>
+                                    <div className='info'>
+                                        <h3 className='ellipsis'>{item.title}</h3>
+                                        <p className='ellipsis-two'>{item.summary}</p>
+                                    </div>
+                                </Link>
+                            </div>
+                        </Col>
+                    )
+                })
+            }
+        </Row>
+    )
+};
+
+const Feature =(props)=> {
+    return (
+        <FeatureWrapper>
+            <FeatureTitle>
+                <h1><i className='iconfont icon-anchor'/><span> START:DASH!!</span></h1>
+            </FeatureTitle>
+            {featureList(props)}
+        </FeatureWrapper>
+    )
+};
+
+
+export default Feature;

+ 102 - 0
src/pages/home/components/List.js

@@ -0,0 +1,102 @@
+import React, {PureComponent} from "react";
+import {connect} from 'react-redux';
+import {FeatureTitle, HomeList, BlogList} from "../style";
+import {actionCreators} from "../store";
+import {Link} from "react-router-dom";
+import {getTime} from "../../../lib/public";
+import PagInation from '../../../components/PagInation';
+
+const List = (props) => {
+    const {blogList} = props;
+    const list = blogList.toJS();
+    const Class = ['blog-item post-list-show left', 'blog-item post-list-show right'];
+    return (
+        <BlogList>
+            {list.map((item, index) => {
+                return (
+                    <div className={Class[index % Class.length]} key={index}>
+                        <div className='post-thumb'>
+                            <Link to={'/article/' + item.id}>
+                                <img src={item.thumbnail} alt=""/>
+                            </Link>
+                        </div>
+                        <div className='post-content-wrap'>
+                            <div className='post-content'>
+                                <div className='post-date'>
+                                    <i className='iconfont icon-time'/>
+                                    发布于 {getTime(item.createTime)}
+                                </div>
+                                <Link to={'/article/' + item.id} className='post-title'>
+                                    <h3>{item.title}</h3>
+                                </Link>
+                                <div className='post-meta'>
+                                    <span>
+                                        <i className='iconfont icon-attention'/>
+                                        {item.views} 热度
+                                    </span>
+                                    <span className='comments-number'>
+                                        <i className='iconfont icon-mark'/>
+                                        {item.comments} 评论
+                                    </span>
+                                    {item.categoryName && <span>
+                                        <i className='iconfont icon-file'/>
+                                        {item.categoryName}
+                                    </span>}
+                                </div>
+                                <div className='float-content'>
+                                    <p>{item.summary}</p>
+                                    <div className='post-bottom'>
+                                        <Link to={'/article/' + item.id}>
+                                            <i className='iconfont icon-caidan'/>
+                                        </Link>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                )
+            })}
+        </BlogList>
+    )
+};
+
+class ListWrapper extends PureComponent {
+    render() {
+        const {page, finished, loading} = this.props;
+        return (
+            <HomeList>
+                <FeatureTitle>
+                    <h1><i className='iconfont icon-envira'/><span> Discovery</span></h1>
+                </FeatureTitle>
+                {List(this.props)}
+                <PagInation page={page} finished={finished} loading={loading} getList={this.props.getBlogList}/>
+            </HomeList>
+        )
+    }
+
+    componentDidMount() {
+        if (!this.props.isList) {
+            this.props.getBlogList(1, true);
+        }
+    }
+}
+
+const mapState = (state) => {
+    return {
+        blogList: state.getIn(['home', 'blogList']),
+        page: state.getIn(['home', 'articlePage']),
+        finished: state.getIn(['home', 'finished']),
+        loading: state.getIn(['home', 'loading']),
+        isList: state.getIn(['home', 'isList']),
+    }
+};
+
+const mapDispatch = (dispatch) => {
+    return {
+        getBlogList(page, override) {
+            dispatch(actionCreators.getBlogList(page, override))
+        },
+    }
+};
+
+export default connect(mapState, mapDispatch)(ListWrapper);

+ 99 - 0
src/pages/home/index.js

@@ -0,0 +1,99 @@
+import React, {PureComponent} from 'react';
+import Banner from "./components/Banner";
+import ListWrapper from './components/List';
+import Feature from "./components/Feature";
+import {HomeWrapper, MainWrapper} from './style';
+import {actionCreators} from "./store";
+import {connect} from "react-redux";
+import {getrand} from "../../lib/public";
+
+class Home extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            banner: '',
+            bannerList: [
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(59).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(53).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(70).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(60).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(46).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(73).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(63).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(74).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(72).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(62).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(7).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(23).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(28).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(38).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(43).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(46).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(54).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(62).jpg'},
+                {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(73).jpg'}
+            ],
+            innerHeight: window.innerHeight,
+        };
+        this.getBanner = this.getBanner.bind(this);
+    }
+
+    render() {
+        const {banner, innerHeight} = this.state;
+        const {userInfo, featureList, ListImg, socialList} = this.props;
+        return (
+            <HomeWrapper>
+                <Banner banner={banner} innerHeight={innerHeight} getBanner={this.getBanner} userInfo={userInfo} socialList={socialList}/>
+                <MainWrapper id='content'>
+                    <Feature featureList={featureList} ListImg={ListImg}/>
+                    <ListWrapper/>
+                </MainWrapper>
+            </HomeWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.changeInnerHeight();
+        this.getBanner();
+        this.props.getFeature();
+        this.props.getSocialList()
+    }
+
+    getBanner() {
+        const banner = this.state.bannerList;
+        const num = getrand(0, banner.length - 1);
+        this.setState({
+            banner: `url('${banner[num].img}')`
+        })
+    }
+
+    changeInnerHeight() {
+        window.onresize = () => {
+            this.setState({
+                innerHeight: window.innerHeight,
+            })
+        }
+    }
+}
+
+const mapState = (state) => {
+    return {
+        userInfo: state.getIn(['header', 'userInfo']),
+        featureList: state.getIn(['home', 'featureList']),
+        ListImg: state.getIn(['image', 'ListImg']),
+        socialList: state.getIn(['home', 'socialList']),
+    }
+};
+
+const mapDispatch = (dispatch) => {
+    return {
+        getFeature() {
+            dispatch(actionCreators.getFeature());
+        },
+        getSocialList() {
+            dispatch(actionCreators.getSocialList());
+        },
+    }
+};
+
+export default connect(mapState, mapDispatch)(Home);

+ 67 - 0
src/pages/home/store/actionCreators.js

@@ -0,0 +1,67 @@
+import axios from 'axios';
+import * as constants from './constants';
+import {fromJS} from 'immutable';
+
+const setFeature = (data) => ({
+    type: constants.GET_FEATURE,
+    data: fromJS(data),
+});
+
+export const getFeature = () => {
+    return (dispatch) => {
+        axios.get('/posts/weight/v1/list', {
+            params: {
+                page: 1,
+                size: 3
+            }
+        }).then(function (res) {
+            if (res.success === 1) {
+                dispatch(setFeature(res.models));
+            }
+        });
+    }
+};
+
+const setBlogList = (data, nextPage, override) => ({
+    type: constants.GET_BLOGLIST,
+    data: fromJS(data),
+    nextPage,
+    override
+});
+
+const setfinished = () => ({
+    type: constants.SET_FINISHED,
+});
+
+export const getBlogList = (page, override) => {
+    return (dispatch) => {
+        dispatch({type: constants.LOADING_TRUE});
+        axios.get('/posts/posts/v1/list', {
+            params: {
+                page: page,
+                size: 10
+            }
+        }).then(function (res) {
+            if (res.success === 1) {
+                let current = res.pageInfo.page * res.pageInfo.size;
+                let total = res.pageInfo.total;
+                dispatch(setBlogList(res.models, page + 1, override));
+                if (current > total) dispatch(setfinished());
+            }
+        });
+    }
+};
+
+export const getSocialList = () => {
+    return (dispatch) => {
+        axios.get('/social/social/v1/socials').then((res) => {
+            if (res.success === 1) {
+                dispatch({
+                    type: constants.GET_SOCIAL_LIST,
+                    data: fromJS(res.models)
+                });
+            }
+        })
+    }
+};
+

+ 5 - 0
src/pages/home/store/constants.js

@@ -0,0 +1,5 @@
+export const GET_FEATURE = 'home/GET_FEATURE';
+export const GET_BLOGLIST = 'home/GET_BLOGLIST';
+export const SET_FINISHED = 'home/SET_FINISHED';
+export const LOADING_TRUE = 'home/LOADING_TRUE';
+export const GET_SOCIAL_LIST = 'home/GET_SOCIAL_LIST';

+ 5 - 0
src/pages/home/store/index.js

@@ -0,0 +1,5 @@
+import reducer from './reducer';
+import * as actionCreators from './actionCreators';
+import * as constants from './constants';
+
+export { reducer, actionCreators, constants };

+ 90 - 0
src/pages/home/store/reducer.js

@@ -0,0 +1,90 @@
+import {fromJS} from 'immutable';
+import * as constants from './constants';
+import {getrand} from '../../../lib/public'
+
+const defaultState = fromJS({
+    featureList: [],
+    blogList: [],
+    articlePage: 1,
+    thumbList: [
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(44).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(42).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(41).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(3).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(28).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(43).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(47).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(48).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(49).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(55).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(56).jpg'},
+        {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(57).jpg'}
+    ],
+    finished: false,
+    loading: true,
+    isList: false,
+    socialList:[]
+});
+
+const setFeature = (state, action) => {
+    return state.merge({
+        featureList: action.data
+    })
+};
+
+const list = (thumbList, data) => {
+    const list = data.toJS();
+    const Img = thumbList.toJS();
+    let arr = [];
+    for (let i = 0; i < list.length; i++) {
+        arr.push({
+            id: list[i].id,
+            title: list[i].title,
+            thumbnail: list[i].thumbnail || Img[getrand(0, Img.length - 1)].img,
+            comments: list[i].comments,
+            status: list[i].status,
+            summary: list[i].summary,
+            views: list[i].views,
+            createTime: list[i].createTime,
+            syncStatus: list[i].syncStatus,
+            author: list[i].author,
+            categoryName: list[i].categoryName
+        })
+    }
+    return arr
+};
+
+const setBlogList = (state, action) => {
+    const arr = list(state.get('thumbList'), action.data);
+    return action.override ? state.merge({
+        blogList: action.override ? fromJS(arr) : state.get('blogList').concat(fromJS(arr)),
+        articlePage: action.nextPage,
+        finished: false,
+        loading: false,
+        isList: true
+    }) : state.merge({
+        blogList: action.override ? fromJS(arr) : state.get('blogList').concat(fromJS(arr)),
+        articlePage: action.nextPage,
+        loading: false,
+        isList: true
+    })
+};
+
+export default (state = defaultState, action) => {
+    switch (action.type) {
+        case constants.GET_FEATURE:
+            return setFeature(state, action);
+        case constants.GET_BLOGLIST:
+            return setBlogList(state, action);
+        case constants.SET_FINISHED:
+            return state.set('finished', true);
+        case constants.LOADING_TRUE:
+            return state.set('loading', true);
+        case constants.GET_SOCIAL_LIST:
+            return state.merge({
+                socialList: action.data
+            });
+        default:
+            return state;
+    }
+}

+ 780 - 0
src/pages/home/style.js

@@ -0,0 +1,780 @@
+import styled from 'styled-components';
+import grid from '../../statics/images/grid.png';
+
+export const BannerWrapper = styled.div`
+	position: relative;
+    overflow: hidden;
+    height: auto;
+    &:before{
+        content: '';
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        z-index: 3;
+        background-attachment: fixed;
+        background-image: url(${grid});
+    }
+    @keyframes move_wave { 
+        0% { 
+            transform: translateX(0) translateZ(0) scaleY(1) 
+        } 
+        50% { 
+            transform: translateX(-25%) translateZ(0) scaleY(0.55) 
+        } 
+        100% { 
+            transform: translateX(-50%) translateZ(0) scaleY(1) 
+        } 
+    } 
+    .waveWrapper {
+        overflow: hidden; 
+        position: absolute; 
+        left: 0; 
+        right: 0; 
+        bottom: 0; 
+        top: 0; 
+        margin: auto; 
+    } 
+    .waveWrapperInner { 
+        position: absolute; 
+        width: 100%; 
+        overflow: hidden; 
+        height: 100%; 
+        bottom: -1px; 
+    } 
+    .bgTop { 
+        z-index: 15; 
+        opacity: 0.5; 
+    } 
+    .bgMiddle { 
+        z-index: 10; 
+        opacity: 0.75; 
+    } 
+    .bgBottom {
+        z-index: 5; 
+    } 
+    .wave{
+        position: absolute; 
+        left: 0; 
+        width: 200%; 
+        height: 100%; 
+        background-repeat: repeat no-repeat; 
+        background-position: 0 bottom; 
+        transform-origin: center bottom; 
+    } 
+    .waveTop { 
+        background-size: 50% 100px; 
+    } 
+    .waveAnimation .waveTop { 
+        animation: move-wave 3s; 
+        -webkit-animation: move-wave 3s; 
+        -webkit-animation-delay: 1s; 
+        animation-delay: 1s; 
+    } 
+    .waveMiddle { 
+        background-size: 50% 120px; 
+    } 
+    .waveAnimation .waveMiddle { 
+        animation: move_wave 10s linear infinite; 
+    } 
+    .waveBottom { 
+        background-size: 50% 100px; 
+    } 
+    .waveAnimation .waveBottom { 
+        animation: move_wave 15s linear infinite; 
+    } 
+    .waveTop{
+        background-image: url('${require('../../statics/images/wave-top.png')}');
+    }
+    .waveMiddle{
+        background-image: url('${require('../../statics/images/wave-mid.png')}');
+    }
+    .waveBottom{
+        background-image: url('${require('../../statics/images/wave-bot.png')}');
+    }
+    .headertop-down {
+        position: absolute;
+        bottom: 80px;
+        left: 50%;
+        cursor: pointer;
+        z-index: 90;
+        animation: float 2s linear infinite;
+        i{
+            font-size: 32px;
+            color: #fff;
+            -ms-transform: scale(1.5,1);
+            -webkit-transform: scale(1.5,1);
+            transform: scale(1.5,1)
+        }
+    }
+    @-webkit-keyframes float {
+        0% {
+            -webkit-transform: translateY(0);
+            transform: translateY(0)
+        }
+    
+        50% {
+            -webkit-transform: translateY(-6px);
+            transform: translateY(-6px)
+        }
+    
+        100% {
+            -webkit-transform: translateY(0);
+            transform: translateY(0)
+        }
+    }
+    
+    @keyframes float {
+        0% {
+            -webkit-transform: translateY(0);
+            -ms-transform: translateY(0);
+            transform: translateY(0)
+        }
+    
+        50% {
+            -webkit-transform: translateY(-6px);
+            -ms-transform: translateY(-6px);
+            transform: translateY(-6px)
+        }
+    
+        100% {
+            -webkit-transform: translateY(0);
+            -ms-transform: translateY(0);
+            transform: translateY(0)
+        }
+    }
+    @media (max-width: 768px) {
+        .waveWrapper{
+            display:none
+        }
+    }
+`;
+
+export const Center = styled.div`
+    width: 100%;
+    height: 550px;
+    height:1235px;
+    margin: 0;
+    padding: 0;
+    background-position: top center;
+    background-repeat: no-repeat;
+    background-attachment: fixed;
+    background-size: cover;
+    z-index: -1;
+`;
+
+export const Focusinfo = styled.div`
+    position: relative;
+    max-width: 800px;
+    padding: 0 10px;
+    top: 49.3%;
+    left: 50%;
+    transform: translate(-50%,-50%);
+    -webkit-transform: translate(-50%,-50%);
+    text-align: center;
+    z-index: 99;
+    -webkit-transition: .4s ease all;
+    -moz-transition: .4s ease all;
+    -o-transition: .4s ease all;
+    transition: .4s ease all;  
+    @media (max-width: 768px){
+        display: none;
+    }
+    .header-tou{
+        img{
+            box-shadow: inset 0 0 10px #000;
+            padding: 5px;
+            opacity: 1;
+            transform: rotate(0);
+            transition: all ease 1s;
+            width: 130px;
+            height: 130px;
+            border-radius: 100%;
+        }
+    }
+    .header-tou img:hover {
+        transform: rotate(360deg);
+    }
+    .glitch{
+        font-family: 'Ubuntu',sans-serif;
+        position: relative;
+        color: #fff;
+        mix-blend-mode: lighten;
+        margin: auto;
+        font-size: 80px;
+        text-transform: uppercase;
+        font-weight: bold;
+    }
+    .glitch:before, .glitch:after {
+        content: attr(data-text);
+        position: absolute;
+        top: 0;
+        width: 100%;
+        background: rgba(0,0,0,0);
+        clip: rect(0,0,0,0);
+    }
+    .glitch:before {
+        left: -1px;
+        text-shadow: 1px 0 #ff3f1a;
+    }
+    .glitch:after {
+        left: 1px;
+        text-shadow: -1px 0 #00a7e0;
+    }
+    .glitch:hover:before {
+        text-shadow: 4px 0 #ff3f1a;
+        animation: glitch-loop-1 .8s infinite ease-in-out alternate-reverse;
+    }
+    .glitch:hover:after {
+        text-shadow: -5px 0 #00a7e0;
+        animation: glitch-loop-2 .8s infinite ease-in-out alternate-reverse;
+    }
+    @-webkit-keyframes glitch-loop-1 {
+        0% {
+            clip: rect(36px,9999px,9px,0)
+        }
+    
+        25% {
+            clip: rect(25px,9999px,99px,0)
+        }
+    
+        50% {
+            clip: rect(50px,9999px,102px,0)
+        }
+    
+        75% {
+            clip: rect(30px,9999px,92px,0)
+        }
+    
+        100% {
+            clip: rect(91px,9999px,98px,0)
+        }
+    }
+    
+    @keyframes glitch-loop-1 {
+        0% {
+            clip: rect(36px,9999px,9px,0)
+        }
+    
+        25% {
+            clip: rect(25px,9999px,99px,0)
+        }
+    
+        50% {
+            clip: rect(50px,9999px,102px,0)
+        }
+    
+        75% {
+            clip: rect(30px,9999px,92px,0)
+        }
+    
+        100% {
+            clip: rect(91px,9999px,98px,0)
+        }
+    }
+    
+    @-webkit-keyframes glitch-loop-2 {
+        0% {
+            top: -1px;
+            left: 1px;
+            clip: rect(65px,9999px,119px,0)
+        }
+    
+        25% {
+            top: -6px;
+            left: 4px;
+            clip: rect(79px,9999px,19px,0)
+        }
+    
+        50% {
+            top: -3px;
+            left: 2px;
+            clip: rect(68px,9999px,11px,0)
+        }
+    
+        75% {
+            top: 0;
+            left: -4px;
+            clip: rect(95px,9999px,53px,0)
+        }
+    
+        100% {
+            top: -1px;
+            left: -1px;
+            clip: rect(31px,9999px,149px,0)
+        }
+    }
+    
+    @keyframes glitch-loop-2 {
+        0% {
+            top: -1px;
+            left: 1px;
+            clip: rect(65px,9999px,119px,0)
+        }
+    
+        25% {
+            top: -6px;
+            left: 4px;
+            clip: rect(79px,9999px,19px,0)
+        }
+    
+        50% {
+            top: -3px;
+            left: 2px;
+            clip: rect(68px,9999px,11px,0)
+        }
+    
+        75% {
+            top: 0;
+            left: -4px;
+            clip: rect(95px,9999px,53px,0)
+        }
+    
+        100% {
+            top: -1px;
+            left: -1px;
+            clip: rect(31px,9999px,149px,0)
+        }
+    }
+    .header-info {
+        position: relative;
+        width: 70%;
+        margin: auto;
+        font-size: 16px;
+        color: #eaeadf;
+        background: rgba(0,0,0,.5);
+        padding: 15px;
+        margin-top: 22px;
+        letter-spacing: 0;
+        line-height: 30px;
+        border-radius: 10px;
+        box-sizing: initial;
+        white-space: nowrap;
+    }
+    .header-info:before {
+        content: "";
+        position: absolute;
+        top: -30px;
+        left: 50%;
+        margin-left:-15px;
+        border-width: 15px;
+        border-style: solid;
+        border-color: transparent transparent rgba(0,0,0,.5) transparent;
+    }
+    .header-info p {
+        margin: 0;
+        font-family: 'Ubuntu',sans-serif;
+        font-weight: 700;
+        span{
+            margin:0 10px;
+        }
+    }
+    .top-social_v2 {
+        height: 35px;
+        margin-bottom:-10px;
+        list-style: none;
+        display: inline-block;
+    }
+    .top-social_v2 li {
+        height: 35px;
+        float: left;
+        margin:0 6px;
+        cursor: url(${require('../../statics/images/ayuda.cur')}),auto;
+        position: relative;
+    }
+    .top-social_v2 li {
+        .img-box{
+            position: absolute;
+            border-radius: 6px;
+            transition: .7s all ease;
+            width: 121px;
+            height: 121px;
+            padding: 4px;
+            background: rgba(0, 0, 0, .4);
+            top: 40px;
+            left: 50%;
+            transform: translate3d(0, 50px, 0) translateX(-50%);
+            opacity: 0;
+            &:before {
+                content: "";
+                position: absolute;
+                top: -30px;
+                left: 50%;
+                margin-left: -15px;
+                border-width: 15px;
+                border-style: solid;
+                border-color: transparent transparent rgba(0, 0, 0, .4) transparent;
+            }
+            img{
+                width: 100%;
+                height: 100%;
+            }
+        }
+        .text-box{
+            position: absolute;
+            border-radius: 4px;
+            transition: .7s all ease;
+            background: rgba(0, 0, 0, .4);
+            top: 40px;
+            left: 50%;
+            transform: translate3d(0, 50px, 0) translateX(-50%);
+            opacity: 0;
+            &:before {
+                content: "";
+                position: absolute;
+                top: -30px;
+                left: 50%;
+                margin-left: -15px;
+                border-width: 15px;
+                border-style: solid;
+                border-color: transparent transparent rgba(0, 0, 0, .4) transparent;
+            }
+            p{
+                color: #ffffff;
+                font-weight: normal;
+                font-size: 12px;
+                padding: 2px 5px;
+            }
+        }
+    }
+    .top-social_v2 li:hover .img-box{
+        transform: translate3d(0, 16px, 0) translateX(-50%);
+        opacity: 1;
+        visibility: visible;
+    }
+    .top-social_v2 li:hover .text-box{
+        transform: translate3d(0, 16px, 0) translateX(-50%);
+        opacity: 1;
+        visibility: visible;
+    }
+    .top-social_v2 li:hover .text-box{
+        display:block;
+    }
+    .top-social_v2 img {
+        height: 35px;
+        width: 35px;
+        padding: 6px;
+        background: 0 0;
+    }
+    .flipx {
+        -moz-transform: scaleX(-1);
+        -webkit-transform: scaleX(-1);
+        -o-transform: scaleX(-1);
+        transform: scaleX(-1);
+        filter: FlipH;
+    }
+`;
+
+export const HomeWrapper = styled.div`
+    width:100%;
+`;
+
+export const MainWrapper = styled.div`
+    width:100%;
+    max-width: 900px;
+    padding: 0 10px;
+    margin-left: auto;
+    margin-right: auto;
+    background-color: rgba(255,255,255,.8);
+    animation: main 1s;
+    @keyframes main {
+        0% {
+            opacity: 0;
+            transform: translateY(50px)
+        }
+        100% {
+            opacity: 1;
+            transform: translateY(0)
+        }
+    }
+`;
+
+export const BlogList = styled.div`
+    width: 100%;
+    .blog-item{
+        width: 100%;
+        height: 300px;
+        position: relative;
+        margin: 30px 0;
+        border-radius: 10px;
+        background-color: rgba(255,255,255,0);
+        box-shadow: 0 1px 20px -6px rgba(0,0,0,.5);
+        opacity: 0;
+        transition: box-shadow .3s ease;
+        @media (min-width: 768px){
+            &:hover{
+                box-shadow: 0 5px 10px 5px rgba(110,110,110,.4);
+            }
+            &:hover img{
+                transform: scale(1.1);
+            }
+        }
+        .post-thumb {
+            float: right;
+            width: 55%;
+            a{
+                height: 300px;
+                position: relative;
+                display: block;
+                background-repeat: no-repeat;
+                background-size: cover;
+                overflow: hidden;
+                border-radius: 0 10px 10px 0;
+                img{
+                    width: 100%;
+                    height: 100%;
+                    object-fit: cover;
+                    pointer-events: none;
+                    transition: all .6s;
+                    filter: blur(0px);
+                }
+            }
+        }
+    }
+    
+    .post-content-wrap {
+        position: relative;
+        display: inline-block;
+        float: right;
+        padding-right: 30px;
+        padding-left: 0;
+        width: 40%;
+        margin: 30px 10px 0;
+        .post-date, .post-meta, .post-meta a {
+            color: #888;
+            font-size: 14px;
+        }
+        i {
+            margin-right: 5px;
+            color: #989898;
+            font-size: 14px;
+        }
+        .post-title{
+            display:block;
+            margin:18px 0;
+            h3 {
+                text-overflow: ellipsis;
+                display: -webkit-box;
+                -webkit-box-orient: vertical;
+                -webkit-line-clamp: 2;
+                overflow: hidden;
+                word-wrap: break-word;
+                font-size: 16px;
+                font-weight: bold;
+                color: #504e4e;
+                transition: color .2s ease-out,border .2s ease-out,opacity .2s ease-out;
+            }
+            &:hover h3{
+                color: #FE9600;
+            }
+        } 
+        .comments-number{
+            margin: 0 10px;
+        }
+        .float-content {
+            position: relative;
+            width: 100%;
+            right: 0;
+            margin: 0;
+            padding: 0;
+            z-index: 50;
+            color: rgba(0,0,0,.66);
+            p{
+                display: -webkit-box;
+                -webkit-box-orient: vertical;
+                -webkit-line-clamp: 3;
+                height: 69px;
+                overflow: hidden;
+                margin:16px 0 22px 0;
+                font-size: 15px;
+                color: rgba(0,0,0,.66);
+                line-height:23px;
+            }
+            i {
+                font-size: 25px;
+                color: #666;
+                &:hover{
+                    color: #FE9600;
+                }
+            }
+        }
+    }
+    .right{
+        .post-thumb {
+            float: left;
+            a {
+                border-radius: 10px 0 0 10px;
+            }
+        }
+        .post-content-wrap {
+            float: left;
+            padding-left: 30px;
+            padding-right: 0;
+            text-align: right;
+            margin: 30px 10px 10px 0;
+        }
+    }
+    .post-list-show {
+        animation: post-list-show .5s;
+        -webkit-animation: post-list-show .5s;
+        opacity: 1;
+    }
+    @keyframes post-list-show {
+        0% {
+            opacity: 0;
+            -webkit-transform: translateY(80px);
+            transform: translateY(80px)
+        }
+        100% {
+            opacity: 1;
+            -webkit-transform: translateY(0);
+            transform: translateY(0)
+        }
+    }
+    @media (max-width: 768px){
+        .blog-item{
+            margin: 0;
+            height: auto;
+            padding: 0;
+            border: 0;
+            box-shadow: none;
+            .post-thumb{
+                width: 100%;
+                left: 0;
+                clear: initial;
+                a{
+                    height: 210px;
+                    border-radius: 10px;
+                }
+            }
+            .post-content-wrap{
+                clear: initial;
+                width: 100%;
+                left: 0;
+                text-align: left;
+                margin: 0;
+                padding: 20px;
+                float: none;
+                box-shadow: none;
+                border-top: 0;
+                .post-title{
+                    margin:10px 0;
+                    font-size: 14px;
+                }
+                .float-content{
+                    p{
+                        margin:10px 0;
+                        font-size: 14px;
+                    }
+                }
+            }
+        }
+    }
+`;
+
+export const FeatureWrapper = styled.div`
+    @media (max-width: 768px){
+        display: none;
+    }
+    .top-feature-row{
+        .top-feature-item{
+            position: relative;
+            height: 160px;
+            box-shadow: 1px 1px 3px rgba(0,0,0,.3);
+            overflow: hidden;
+            border-radius: 10px;
+            a{
+                display:block;
+                height:100%;
+            }
+            .img-box{
+                transition: all .35s ease-in-out;
+                transform: scale(1);
+                height:100%;
+                img{
+                    width:100%;
+                    height:100%;
+                }
+            }
+            &:hover .img-box{
+                transform: scale(1.2);
+            }
+            .info{
+                position: absolute;
+                top: 0;
+                bottom: 0;
+                left: 0;
+                right: 0;
+                text-align: center;
+                backface-visibility: hidden;
+                background: #333;
+                background: rgba(0,0,0,.6);
+                visibility: hidden;
+                opacity: 0;
+                transition: all .35s ease-in-out;
+                h3{
+                    text-transform: uppercase;
+                    color: #fff;
+                    text-align: center;
+                    font-size: 17px;
+                    padding: 10px;
+                    background: #111;
+                    margin: 40px 0 0;
+                    transition: all .35s ease-in-out;
+                    transform: translateX(-100%);
+                }
+                p{
+                    font-style: italic;
+                    font-size: 12px;
+                    position: relative;
+                    color: #bbb;
+                    padding:0 20px;
+                    text-align: center;
+                    transition: all .35s .1s linear;
+                    transform: translateX(100%);
+                    margin-top:15px;
+                    height:40px;
+                    line-height:20px;
+                }
+            }
+            &:hover .info{
+                visibility: visible;
+                opacity: 1;
+                h3{
+                    transform: translateX(0);
+                }
+                p{
+                    transform: translateX(0);
+                }
+            }
+        }
+    }
+`;
+
+export const FeatureTitle = styled.div`
+    width: 100%;
+    height: auto;
+    margin-top: 55px;
+    display: inline-block;
+    h1{
+        color: #666;
+        font-size: 16px;
+        font-weight: bold;
+        margin-top: 10px;
+        line-height:24px;
+        padding-bottom: 5px;
+        margin-bottom: 30px;
+        border-bottom: 1px dashed #ececec;
+    }
+    @media( max-width:768px ){
+        margin-top: 15px;
+        h1{
+            margin-bottom: 15px;
+        }
+    }
+`;
+
+export const HomeList = styled.div`
+    width: 100%;
+`;

+ 133 - 0
src/pages/links/index.js

@@ -0,0 +1,133 @@
+import React, {PureComponent} from "react";
+import {connect} from 'react-redux';
+import {LinksWrapper, LinksTop, MainWrapper} from './style';
+import axios from "axios";
+import {Spin, Anchor} from "antd";
+const {Link} = Anchor;
+
+const LinksList = (props) => {
+    const {list, loading} = props;
+    if (loading) {
+        return (
+            <div className="example">
+                <Spin size="large"/>
+            </div>
+        )
+    } else {
+        return (
+            <div className='links cell'>
+                {list.length ? <div className='extra'>
+                    {list.map((item, index) => {
+                        return (
+                            <div className='item' key={index}>
+                                <h2 className='link-title' id={`${item.title}`}><span className='fake-title'>{item.title}</span></h2>
+                                <ul className='link-items fontSmooth'>
+                                    {item.list.map((item2, index2) => {
+                                        return (
+                                            <li className='link-item post-list-show ' key={index2}>
+                                                <a href={item2.href} target={'_blank'} rel="noopener noreferrer">
+                                                    <img src={item2.logo} alt=""/>
+                                                    <span className="sitename">{item2.name}</span>
+                                                    <div className="linkdes">{item2.description}</div>
+                                                </a>
+                                            </li>
+                                        )
+                                    })}
+                                </ul>
+                            </div>
+                        )
+                    })}
+                </div> : <p>去找点新朋友玩吧...</p>}
+            </div>
+        )
+    }
+};
+
+class Links extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            timg: '',
+            list: [],
+            loading: true
+        }
+    }
+
+    render() {
+        const {list, loading} = this.state;
+        return (
+            <LinksWrapper>
+                <div className='pattern-center-blank'/>
+                <LinksTop>
+                    <div className='pattern-attachment-img'>
+                        <img className='lazyload' src={this.state.timg} alt=""/>
+                    </div>
+                    <div className='pattern-header '>
+                        <h1>友人帐</h1>
+                    </div>
+                </LinksTop>
+                <MainWrapper>
+                    <div className='flex-items'>
+                        <LinksList list={list} loading={loading}/>
+                        <div className='toc-box'>
+                            {list.length?<Anchor className='toc' affix showInkInFixed onClick={this.handleClick} offsetTop={100}>
+                                {list.map((item)=>{
+                                    return(
+                                        <Link key={item.title} href={`#${item.title}`} title={item.title}/>
+                                    )
+                                })}
+                            </Anchor>:null}
+                        </div>
+                    </div>
+                </MainWrapper>
+            </LinksWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.getLinks();
+        this.getTimg();
+    }
+
+    handleClick(e, link) {
+        e.preventDefault();
+    };
+
+    getLinks() {
+        this.setState({loading: true});
+        axios.get('/link/link/v2/list').then((res) => {
+            let {extra} = res;
+            let arrar = [];
+            for (let i in extra) {
+                arrar.push({
+                    title: i,
+                    list: extra[i]
+                });
+            }
+            if (res.success === 1) {
+                this.setState({
+                    list: arrar,
+                    loading: false
+                })
+            }
+        });
+    }
+
+    getTimg() {
+        const list = this.props.topImg;
+        const num = this.getrand(0, list.length - 1);
+        this.setState({timg: list[num].img})
+    }
+
+    getrand(m, n) {
+        return Math.floor(Math.random() * (n - m + 1)) + m;
+    }
+}
+
+const mapState = (state) => {
+    return {
+        topImg: state.getIn(['image', 'topImg'])
+    }
+};
+
+export default connect(mapState)(Links)

+ 288 - 0
src/pages/links/style.js

@@ -0,0 +1,288 @@
+import styled from "styled-components";
+
+export const LinksWrapper = styled.div`
+    .pattern-center-blank{
+        padding-top: 75px;
+        background-color: #fff;
+    }
+    @media(max-width:768px){
+        .pattern-center-blank{
+            padding-top: 50px;
+        }
+    }
+`;
+
+export const LinksTop = styled.div`
+    position: relative;
+    top: 0;
+    left: 0;
+    width: 100%;
+    overflow: hidden;
+    &:before{
+        content: "";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: rgba(0,0,0,.3);
+    }
+    &:after{
+        content: '';
+        width: 150%;
+        height: 4.375rem;
+        background: #fff;
+        left: -25%;
+        bottom: -2.875rem;
+        border-radius: 100%;
+        position: absolute;
+    }
+    .pattern-attachment-img{
+        background-repeat: no-repeat;
+        background-size: cover;
+        background-position: center center;
+        background-origin: border-box;
+        width: 100%;
+        height: 400px;
+        img{
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            pointer-events: none;
+        }
+    }
+    .pattern-header {
+        position: absolute;
+        top: 45%;
+        left: 0;
+        right: 0;
+        text-align: center;
+        color: #fff;
+        z-index: 1;
+        h1{
+            color: #fff;
+            font-size: 40px;
+            font-weight: 500;
+            width: 80%;
+            margin: auto;
+            padding: 0;
+            border: 0;
+        }
+    }
+    @media(max-width:768px){
+        .pattern-attachment-img{
+            height:280px;
+        }
+        .pattern-header {
+            top:40%;
+            h1{
+                font-size:24px;
+            }
+        }
+    }
+`;
+
+export const MainWrapper = styled.div`
+    min-height:600px;
+    max-width: 900px;
+    padding: 0 10px;
+    margin-left: auto;
+    margin-right: auto;
+    padding-top:50px;
+    background-color: rgba(255,255,255,.8);
+    animation: main 1s;
+    .flex-items{
+        align-items: start;
+    }
+    .toc{
+        .ant-anchor-link-active > .ant-anchor-link-title{
+            color: #FE9600;
+        }
+        .ant-anchor-link-title:hover{
+            color: #FE9600;
+        }
+        .ant-anchor-ink-ball{
+            border: 2px solid #FE9600;
+        }
+    }
+    @keyframes main {
+        0% {
+            opacity: 0;
+            transform: translateY(50px)
+        }
+        100% {
+            opacity: 1;
+            transform: translateY(0)
+        }
+    }
+    .example{
+        height:52px;
+        line-height:52px;
+        text-align: center;
+        i{
+            background-color: #FE9600;
+        }
+    }
+    .links>p{
+        width: 100%;
+        padding: 20px 0;
+        text-align: center;
+        margin: 40px 0 80px;
+        display: inline-block;
+        color: #989898;
+        font-size: 15px;
+    }
+    .links .link-title {
+        padding-left: 0;
+        border-left: none;
+        margin: 50px 0 20px;
+        .fake-title {
+            font-weight: 400;
+            color: #6d6d6d;
+            padding-left: 10px;
+            border-left: 3px solid orange;
+        }
+    }
+    .links ul {
+        margin: 0;
+        list-style: none;
+        padding: 0;
+        width: 100%;
+        display: inline-block;
+    }
+    .links ul li {
+        width: 32%;
+        float: left;
+        border: 1px solid #ececec;
+        padding: 10px 30px;
+        margin: 10px 4px;
+        position: relative;
+        overflow: hidden;
+        -webkit-transition: all .3s;
+        transition: all .3s;
+        border-radius: 10px;
+    }
+    .links ul li:before {
+        content: "";
+        background-color: #FE9600;
+        -webkit-transform: skew(45deg,0);
+        transform: skew(45deg,0);
+        width: 0;
+        height: 100%;
+        position: absolute;
+        top: 0;
+        left: -60px;
+        z-index: -1;
+        -webkit-transition: all .5s;
+        transition: all .5s;
+    }
+    .links ul li img {
+        float: right;
+        box-shadow: inset 0 0 10px #000;
+        padding: 5px;
+        opacity: 1;
+        transform: rotate(0deg);
+        -webkit-transform: rotate(0deg);
+        -moz-transform: rotate(0deg);
+        -o-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transition: all ease 1s;
+        -webkit-transition: all ease 1s;
+        -moz-transition: all ease 1s;
+        -o-transition: all ease 1s;
+        margin-top: 5px;
+        width: 65px;
+        height: 65px;
+        padding: 2px;
+        border-radius: 100%;
+    }
+    .links ul li .sitename {
+        color: #FE9600;
+        padding-bottom: 10px;
+        display: block;
+        -webkit-transition: all .3s;
+        transition: all .3s;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        -o-text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+    .linkdes {
+        color: #949494;
+        font-size: 13px;
+        padding: 10px 0;
+        min-height:46px;
+        border-top: 1px dashed #ddd;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+        line-height: 25px;
+        -webkit-transition: all .5s;
+        transition: all .5s;
+    }
+    .post-list-show {
+        animation: post-list-show .5s;
+        -webkit-animation: post-list-show .5s;
+        opacity: 1;
+    }
+    @keyframes post-list-show {
+        0% {
+            opacity: 0;
+            -webkit-transform: translateY(80px);
+            transform: translateY(80px)
+        }
+        100% {
+            opacity: 1;
+            -webkit-transform: translateY(0);
+            transform: translateY(0)
+        }
+    }
+    @media(min-width:768px){
+        .links ul li:hover {
+            color: #27323a;
+            border-color: #FE9600;
+            border: 1px solid #FE9600;
+        }
+        .links ul li:hover:before {
+            width: 180%;
+        }
+        .links ul li:hover img {
+            transform: rotate(360deg);
+            -webkit-transform: rotate(360deg);
+            -moz-transform: rotate(360deg);
+            -o-transform: rotate(360deg);
+            -ms-transform: rotate(360deg);
+        }
+        .links ul li:hover .linkdes {
+            border-top: 1px dashed #fff;
+        }
+    }
+    @media(max-width:768px){
+        padding-top:30px;
+        min-height:400px;
+        .toc-box{
+            display: none;
+        }
+        .page-header{
+            margin-bottom: 30px;
+                h1{
+                    font-size: 16px;
+                    font-weight: 400;
+                    border: 1px dashed #ddd;
+                    padding:10px;
+                    color: #828282;
+                }
+        }
+    }
+    @media (max-width: 768px){
+        .links ul li {
+            width: 48.6%;
+        }
+    }
+    @media (max-width: 630px){
+        .links ul li {
+            width: 100%;
+            margin:10px 0;
+        }
+    }
+`;

+ 98 - 0
src/pages/search/index.js

@@ -0,0 +1,98 @@
+import React, {PureComponent} from "react";
+import {connect} from "react-redux";
+import axios from "axios";
+import {SearchWrapper, MainWrapper} from './style';
+import CatList from "../../components/List";
+import PagInation from '../../components/PagInation';
+
+class Search extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            key: props.match.params.key,
+            finished: false,
+            loading: true,
+            page: 1,
+            list: []
+        };
+        this.getList = this.getList.bind(this);
+    }
+
+    render() {
+        const {list, page, finished, loading, key} = this.state;
+        return (
+            <SearchWrapper>
+                <div className='pattern-center-blank'/>
+                <MainWrapper>
+                    <header className="page-header">
+                        <h1 className="page-title">{`搜索结果:${key}`}</h1>
+                    </header>
+                    <CatList list={list}/>
+                    <PagInation page={page} id={key} finished={finished} loading={loading} getList={this.getList}/>
+                </MainWrapper>
+            </SearchWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.getList(1, this.state.key, true);
+    }
+
+    getList(page, key, override) {
+        this.setState({loading: true});
+        axios.get('/posts/posts/v1/list', {
+            params: {
+                page: page,
+                size: 10,
+                keywords: key
+            }
+        }).then((res) => {
+            if (res.success === 1) {
+                let current = res.pageInfo.page * res.pageInfo.size;
+                let total = res.pageInfo.total;
+                const data = res.models;
+                const Img = this.props.ListImg;
+                let arr = [];
+                data.forEach((item) => {
+                    arr.push({
+                        id: item.id,
+                        title: item.title,
+                        thumbnail: item.thumbnail || Img[this.getrand(0, Img.length - 1)].img,
+                        comments: item.comments,
+                        status: item.status,
+                        summary: item.summary,
+                        views: item.views,
+                        createTime: item.createTime,
+                        syncStatus: item.syncStatus,
+                        author: item.author,
+                        categoryName: item.categoryName
+                    })
+                });
+                this.setState((prevState) => {
+                    return {
+                        list: override ? arr : [...prevState.list, ...arr],
+                        page: page + 1,
+                        loading: false
+                    }
+                });
+                if (current > total) {
+                    this.setState({
+                        finished: true
+                    })
+                }
+            }
+        });
+    }
+
+    getrand(m, n) {
+        return Math.floor(Math.random() * (n - m + 1)) + m;
+    };
+}
+
+const mapState = (state) => {
+    return {
+        ListImg: state.getIn(['image', 'ListImg'])
+    }
+};
+
+export default connect(mapState)(Search)

+ 70 - 0
src/pages/search/style.js

@@ -0,0 +1,70 @@
+import styled from "styled-components";
+
+export const SearchWrapper = styled.div`
+    .pattern-center-blank{
+        padding-top: 75px;
+        background-color: #fff;
+    }
+    @media(max-width:768px){
+        .pattern-center-blank{
+            padding-top: 50px;
+        }
+    }
+`;
+
+export const MainWrapper = styled.div`
+    min-height:600px;
+    max-width: 900px;
+    padding: 0 10px;
+    margin-left: auto;
+    margin-right: auto;
+    padding-top:50px;
+    background-color: rgba(255,255,255,.8);
+    animation: main 1s;
+    .page-header{
+        position: relative;
+        text-align: center;
+        margin-bottom: 50px;
+        color: #9C9C9C;
+        h1{
+            font-size: 20px;
+            font-weight: 400;
+            border: 1px dashed #ddd;
+            padding: 15px;
+            color: #828282;
+            margin-bottom: 30px;
+        }
+    }
+    @keyframes main {
+        0% {
+            opacity: 0;
+            transform: translateY(50px)
+        }
+        100% {
+            opacity: 1;
+            transform: translateY(0)
+        }
+    }
+    .example{
+        height:52px;
+        line-height:52px;
+        text-align: center;
+        i{
+            background-color: #FE9600;
+        }
+    }
+    @media(max-width:768px){
+        padding-top:30px;
+        min-height:400px;
+        .page-header{
+            margin-bottom: 30px;
+                h1{
+                    font-size: 16px;
+                    font-weight: 400;
+                    border: 1px dashed #ddd;
+                    padding:10px;
+                    color: #828282;
+                }
+        }
+    }
+`;

+ 96 - 0
src/pages/tags/index.js

@@ -0,0 +1,96 @@
+import React, {PureComponent} from "react";
+import {Link} from 'react-router-dom';
+import {connect} from "react-redux";
+import axios from "axios";
+import {Spin, Tag} from "antd";
+import {TagsWrapper, TagsTop, MainWrapper} from './style';
+
+const TagsList = (props) => {
+    const {list, loading} = props;
+    const color = ['#23b7e5', '#3a3f51', '#27c24c', '#1c2b36', '#fad733', '#7266ba', '#f05050'];
+    if (loading) {
+        return (
+            <div className="example">
+                <Spin size="large"/>
+            </div>
+        )
+    } else {
+        return (
+            <div className='tags-list'>
+                {list.length?list.map((item, index) => {
+                    return (
+                        <Tag color={color[getrand(0, color.length - 1)]} key={index}>
+                            <Link to={'/tags/' + item.id}>{item.name}({item.postsTotal})</Link>
+                        </Tag>
+                    )
+                }):<p>你暂时还未添加标签...</p>}
+            </div>
+        )
+    }
+};
+
+const getrand = (m, n) => {
+    return Math.floor(Math.random() * (n - m + 1)) + m;
+};
+
+class Tags extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            timg: '',
+            list: [],
+            loading: true
+        }
+    }
+
+    render() {
+        const {timg, list, loading} = this.state;
+        return (
+            <TagsWrapper>
+                <div className='pattern-center-blank'/>
+                <TagsTop>
+                    <div className='pattern-attachment-img'>
+                        <img className='lazyload' src={timg} alt=""/>
+                    </div>
+                    <div className='pattern-header '>
+                        <h1>标签墙</h1>
+                    </div>
+                </TagsTop>
+                <MainWrapper>
+                    <TagsList list={list} loading={loading}/>
+                </MainWrapper>
+            </TagsWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.getTags();
+        this.getTimg();
+    }
+
+    getTags() {
+        this.setState({loading: true});
+        axios.get('/tags/tags-article-quantity/v1/list').then((res) => {
+            if (res.success === 1) {
+                this.setState({
+                    list: res.models,
+                    loading: false
+                })
+            }
+        });
+    }
+
+    getTimg() {
+        const list = this.props.topImg;
+        const num = getrand(0, list.length - 1);
+        this.setState({timg: list[num].img});
+    }
+}
+
+const mapState = (state) => {
+    return {
+        topImg: state.getIn(['image', 'topImg'])
+    }
+};
+
+export default connect(mapState)(Tags)

+ 112 - 0
src/pages/tags/list.js

@@ -0,0 +1,112 @@
+import React, {PureComponent} from "react";
+import {MainWrapper, TagsTop, TagsWrapper} from "./style";
+import axios from "axios";
+import {connect} from "react-redux";
+import CatList from "../../components/List";
+import PagInation from '../../components/PagInation';
+
+const getrand = (m, n) => {
+    return Math.floor(Math.random() * (n - m + 1)) + m;
+};
+
+class TagList extends PureComponent {
+    constructor(props) {
+        super(props);
+        this.state = {
+            id: props.match.params.id,
+            timg: '',
+            finished: false,
+            loading: true,
+            page: 1,
+            list: []
+        };
+        this.getList = this.getList.bind(this);
+    }
+
+    render() {
+        const {timg, list, page, finished, loading, id} = this.state;
+        return (
+            <TagsWrapper>
+                <div className='pattern-center-blank'/>
+                <TagsTop>
+                    <div className='pattern-attachment-img'>
+                        <img className='lazyload' src={timg} alt=""/>
+                    </div>
+                    <div className='pattern-header '>
+                        <h1>标签墙</h1>
+                    </div>
+                </TagsTop>
+                <MainWrapper>
+                    <CatList list={list}/>
+                    <PagInation page={page} id={id} finished={finished} loading={loading} getList={this.getList}/>
+                </MainWrapper>
+            </TagsWrapper>
+        )
+    }
+
+    componentDidMount() {
+        this.getList(1, this.state.id, true);
+        this.getTimg();
+    }
+
+    getList(page, id, override) {
+        this.setState({loading: true});
+        axios.get('/posts/posts/v1/list', {
+            params: {
+                page: page,
+                size: 10,
+                postsTagsId: id
+            }
+        }).then((res) => {
+            if (res.success === 1) {
+                let current = res.pageInfo.page * res.pageInfo.size;
+                let total = res.pageInfo.total;
+                const data = res.models;
+                const Img = this.props.ListImg;
+                let arr = [];
+                data.forEach((item) => {
+                    arr.push({
+                        id: item.id,
+                        title: item.title,
+                        thumbnail: item.thumbnail || Img[getrand(0, Img.length - 1)].img,
+                        comments: item.comments,
+                        status: item.status,
+                        summary: item.summary,
+                        views: item.views,
+                        createTime: item.createTime,
+                        syncStatus: item.syncStatus,
+                        author: item.author,
+                        categoryName: item.categoryName
+                    })
+                });
+                this.setState((prevState) => {
+                    return {
+                        list: override ? arr : [...prevState.list, ...arr],
+                        page: page + 1,
+                        loading: false
+                    }
+                });
+                if (current > total) {
+                    this.setState({
+                        finished: true
+                    })
+                }
+            }
+        });
+    }
+
+    getTimg() {
+        const list = this.props.topImg;
+        const num = getrand(0, list.length - 1);
+        this.setState({timg: list[num].img});
+    }
+}
+
+const mapState = (state) => {
+    return {
+        topImg: state.getIn(['image', 'topImg']),
+        ListImg: state.getIn(['image', 'ListImg']),
+    }
+};
+
+export default connect(mapState)(TagList)

+ 145 - 0
src/pages/tags/style.js

@@ -0,0 +1,145 @@
+import styled from "styled-components";
+
+export const TagsWrapper = styled.div`
+    .pattern-center-blank{
+        padding-top: 75px;
+        background-color: #fff;
+    }
+    @media(max-width:768px){
+        .pattern-center-blank{
+            padding-top: 50px;
+        }
+    }
+`;
+
+export const TagsTop = styled.div`
+    position: relative;
+    top: 0;
+    left: 0;
+    width: 100%;
+    overflow: hidden;
+    &:before{
+        content: "";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: rgba(0,0,0,.3);
+    }
+    &:after{
+        content: '';
+        width: 150%;
+        height: 4.375rem;
+        background: #fff;
+        left: -25%;
+        bottom: -2.875rem;
+        border-radius: 100%;
+        position: absolute;
+    }
+    .pattern-attachment-img{
+        background-repeat: no-repeat;
+        background-size: cover;
+        background-position: center center;
+        background-origin: border-box;
+        width: 100%;
+        height: 400px;
+        img{
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            pointer-events: none;
+        }
+    }
+    .pattern-header {
+        position: absolute;
+        top: 45%;
+        left: 0;
+        right: 0;
+        text-align: center;
+        color: #fff;
+        z-index: 1;
+        h1{
+            color: #fff;
+            font-size: 40px;
+            font-weight: 500;
+            width: 80%;
+            margin: auto;
+            padding: 0;
+            border: 0;
+        }
+    }
+    @media(max-width:768px){
+        .pattern-attachment-img{
+            height:280px;
+        }
+        .pattern-header {
+            top:40%;
+            h1{
+                font-size:24px;
+            }
+        }
+    }
+`;
+
+export const MainWrapper = styled.div`
+    min-height:600px;
+    max-width: 900px;
+    padding: 0 10px;
+    margin-left: auto;
+    margin-right: auto;
+    padding-top:50px;
+    background-color: rgba(255,255,255,.8);
+    animation: main 1s;
+    @keyframes main {
+        0% {
+            opacity: 0;
+            transform: translateY(50px)
+        }
+        100% {
+            opacity: 1;
+            transform: translateY(0)
+        }
+    }
+    .example{
+        height:52px;
+        line-height:52px;
+        text-align: center;
+        i{
+            background-color: #FE9600;
+        }
+    }
+    .tags-list{
+        span{
+            padding: 2px 20px;
+            margin-right:15px;
+            margin-bottom:15px;
+        }
+        a{
+            line-height:22px;
+        }
+        p{
+            width: 100%;
+            padding: 20px 0;
+            text-align: center;
+            margin: 40px 0 80px;
+            display: inline-block;
+            color: #989898;
+            font-size: 15px;
+        }
+    }
+    @media(max-width:768px){
+        padding-top:30px;
+        min-height:400px;
+        .page-header{
+            margin-bottom: 30px;
+                h1{
+                    font-size: 16px;
+                    font-weight: 400;
+                    border: 1px dashed #ddd;
+                    padding:10px;
+                    color: #828282;
+                }
+        }
+    }
+`;

+ 32 - 0
src/router.js

@@ -0,0 +1,32 @@
+import React,{PureComponent} from "react";
+import {Route, Switch} from "react-router-dom";
+import {withRouter} from 'react-router-dom';
+import Home from "./pages/home";
+import Article from "./pages/article";
+import Category from "./pages/category";
+import Archives from './pages/archives';
+import Links from "./pages/links";
+import Tags from "./pages/tags";
+import TagList from "./pages/tags/list";
+import Search from "./pages/search";
+import Error from "./pages/error";
+
+class Router extends PureComponent{
+    render() {
+        return(
+            <Switch key={this.props.location.key}>
+                <Route path='/' exact component={Home}/>
+                <Route path='/article/:id' exact component={Article}/>
+                <Route path='/category/:id' exact component={Category}/>
+                <Route path='/archives' exact component={Archives}/>
+                <Route path='/links' exact component={Links}/>
+                <Route path='/tags' exact component={Tags}/>
+                <Route path='/tags/:id' exact component={TagList}/>
+                <Route path='/search/:key' exact component={Search}/>
+                <Route component={Error}/>
+            </Switch>
+        )
+    }
+}
+
+export default withRouter(Router)

BIN
src/statics/iconfont/iconfont.eot


File diff suppressed because it is too large
+ 23 - 0
src/statics/iconfont/iconfont.js


File diff suppressed because it is too large
+ 38 - 0
src/statics/iconfont/iconfont.svg


BIN
src/statics/iconfont/iconfont.ttf


BIN
src/statics/iconfont/iconfont.woff


BIN
src/statics/iconfont/iconfont.woff2


BIN
src/statics/images/404.png


BIN
src/statics/images/ayuda.cur


File diff suppressed because it is too large
+ 1 - 0
src/statics/images/email.svg


BIN
src/statics/images/grid.png


BIN
src/statics/images/hr.gif


File diff suppressed because it is too large
+ 1 - 0
src/statics/images/next-b.svg


BIN
src/statics/images/normal.cur


+ 193 - 0
src/statics/images/nosum.svg

@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="189px" height="181px" viewBox="0 0 189 181" enable-background="new 0 0 189 181" xml:space="preserve">  <image id="image0" width="189" height="181" x="0" y="0"
+    href="data:image/png;base64,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" />
+</svg>

BIN
src/statics/images/qq.png


BIN
src/statics/images/scroll.png


BIN
src/statics/images/sina.png


BIN
src/statics/images/texto.cur


BIN
src/statics/images/twitter.png


BIN
src/statics/images/wave-bot.png


BIN
src/statics/images/wave-mid.png


BIN
src/statics/images/wave-top.png


+ 7 - 0
src/store/index.js

@@ -0,0 +1,7 @@
+import {createStore, applyMiddleware} from 'redux';
+import reducer from './reducer';
+import thunk from 'redux-thunk';
+
+const store = createStore(reducer, applyMiddleware(thunk));
+
+export default store;

+ 49 - 0
src/store/reducer.js

@@ -0,0 +1,49 @@
+import {combineReducers} from 'redux-immutable';
+import {reducer as headerReducer} from "../components/Header/store";
+import {reducer as homeReducer} from '../pages/home/store';
+
+const image = ()=>{
+    return{
+        topImg:[
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(59).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(53).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(70).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(60).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(46).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(73).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(63).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(74).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(72).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(62).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(7).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(23).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(38).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(43).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(46).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(54).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(73).jpg'}
+        ],
+        ListImg: [
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(44).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(42).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(41).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(3).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(4).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(43).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(47).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(48).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(49).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(55).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(56).jpg'},
+            {img: 'https://cos.nosum.cn/nosum/blog/1/nosum-blog-1%20(57).jpg'}
+        ],
+    }
+};
+
+const reducer = combineReducers({
+    header: headerReducer,
+    home: homeReducer,
+    image: image
+});
+
+export default reducer

+ 141 - 0
src/style.js

@@ -0,0 +1,141 @@
+import {createGlobalStyle} from 'styled-components';
+
+export const GlobalStyle = createGlobalStyle`
+    ::selection {
+        background: #A0DAD0;
+        color: #fff;
+    }
+    ::-webkit-scrollbar-track {
+        background-color: #ffffff;
+    }
+    ::-webkit-scrollbar-thumb {
+        border-radius: 10px;
+        background-color: #FE9600;
+    }
+    ::-webkit-scrollbar {
+        width: 5px;
+        height: 8px;
+        background-color: #B9B9B9;
+    }
+    *{
+        box-sizing: border-box;
+    }
+    body{
+        font-family: 'Noto Serif SC','Source Han Serif SC','Source Han Serif','source-han-serif-sc','PT Serif','SongTi SC','MicroSoft Yahei',Georgia,serif;
+        cursor: url(${require('./statics/images/normal.cur')}),auto
+    }
+	html, body, div, span, applet, object, iframe,
+	h1, h2, h3, h4, h5, h6, p, blockquote, 
+	a, abbr, acronym, address, big, cite, 
+	del, dfn, em, img, ins, kbd, q, s, samp,
+	small, strike, strong, sub, sup, tt, var,
+	b, u, i, center,
+	dl, dt, dd, ol, ul, li,
+	fieldset, form, label, legend,
+	table, caption, tbody, tfoot, thead, tr, th, td,
+	article, aside, canvas, details, embed, 
+	figure, figcaption, footer, header, hgroup, 
+	menu, nav, output, ruby, section, summary,
+	time, mark, audio, video {
+		margin: 0;
+		padding: 0;
+		border: 0;
+		font-size: 100%;
+		vertical-align: baseline;
+	}
+	/* HTML5 display-role reset for older browsers */
+	article, aside, details, figcaption, figure, 
+	footer, header, hgroup, menu, nav, section {
+		display: block;
+	}
+	.transition-color{
+	    transition: color .2s ease-out,border .2s ease-out,opacity .2s ease-out;
+	}
+	a{
+        transition: color .2s ease-out,border .2s ease-out,opacity .2s ease-out;
+        cursor: url(${require('./statics/images/ayuda.cur')}),auto;
+	}
+	a:hover{
+	    text-decoration: none;
+	}
+	ol, ul {
+		list-style: none;
+	}
+	ul,li{
+	    margin:0;
+	}
+	.lazyload {
+        filter: blur(0px);
+        transition: .3s filter linear,.3s -webkit-filter linear;
+    }
+    p{
+        cursor: url(${require('./statics/images/texto.cur')}),auto;
+    }
+	blockquote, q {
+		quotes: none;
+	}
+	blockquote:before, blockquote:after,
+	q:before, q:after {
+		content: '';
+		content: none;
+	}
+	
+	table {
+		border-collapse: collapse;
+		border-spacing: 0;
+	}
+	
+	.flex-items {
+        display: -webkit-flex; /* Safari */
+        -webkit-align-items: center; /* Safari 7.0+ */
+        display: flex;
+        align-items: center;
+    }
+    
+    .flex-start {
+        display: -webkit-flex; /* Safari */
+        -webkit-align-items: center; /* Safari 7.0+ */
+        display: flex;
+        align-items: center;
+        flex-wrap: wrap;
+    }
+    
+    .cell {
+        -webkit-box-flex: 1;
+        -webkit-flex: 1;
+        -ms-flex: 1;
+        flex: 1;
+        width: 0;
+    }
+    
+    .align-center {
+        -webkit-align-self: center;
+        align-self: center;
+    }
+    
+    .ellipsis {
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+    }
+    
+    .ellipsis-two {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        -webkit-line-clamp: 2;
+    }
+    #player .aplayer-lrc-current {
+        color: #fe9600;
+        font-size: 15px;
+        font-weight: bold;
+    }
+    #fireworks{
+        position: fixed; 
+        left: 0px; 
+        top: 0px; 
+        pointer-events: none; 
+        z-index: 999;
+    }
+`;

File diff suppressed because it is too large
+ 12476 - 0
yarn.lock