├── .gitignore
├── README.md
├── config-overrides.js
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── assets
│ ├── fonts
│ │ └── svg
│ │ │ ├── comment.svg
│ │ │ ├── goback-backup.svg
│ │ │ ├── goback.svg
│ │ │ ├── gotop.svg
│ │ │ ├── medropout.svg
│ │ │ ├── meedit.svg
│ │ │ ├── mescore.svg
│ │ │ ├── metime.svg
│ │ │ ├── meuser.svg
│ │ │ ├── nall.svg
│ │ │ ├── nask.svg
│ │ │ ├── ngood.svg
│ │ │ ├── nhome.svg
│ │ │ ├── nissue.svg
│ │ │ ├── njob.svg
│ │ │ ├── nnews.svg
│ │ │ ├── notfound.svg
│ │ │ ├── nshare.svg
│ │ │ ├── nuser.svg
│ │ │ ├── operat-del.svg
│ │ │ ├── operat-keep-active.svg
│ │ │ ├── operat-keep-default.svg
│ │ │ ├── page-views.svg
│ │ │ ├── send.svg
│ │ │ ├── thumbs.svg
│ │ │ └── write.svg
│ ├── images
│ │ ├── cnode-qrcode.png
│ │ ├── logo-backup.png
│ │ ├── logo.png
│ │ └── small-demo.gif
│ ├── sass
│ │ ├── _layout.scss
│ │ ├── _mian.scss
│ │ ├── _mixin.scss
│ │ ├── common
│ │ │ └── _reset.scss
│ │ └── global.scss
│ └── styles
│ │ └── css
│ │ └── global.css
├── component
│ ├── ArticleItem.jsx
│ ├── Header.jsx
│ ├── Menu.jsx
│ ├── SubHeader.jsx
│ ├── common
│ │ ├── LazyImage.jsx
│ │ ├── MaskPopups.jsx
│ │ └── SharedCompt.jsx
│ └── plugin
│ │ └── touch-loadmore
│ │ ├── LoadMore.jsx
│ │ ├── load-more.less
│ │ ├── react-touch-loader.jsx
│ │ └── touch-loader.less
├── index.js
├── script
│ ├── firstload.js
│ ├── history.js
│ ├── reuse.js
│ ├── routers.js
│ └── utils.js
├── serviceWorker.js
├── store
│ ├── actions
│ │ ├── ActionArticle.js
│ │ ├── ActionNews.js
│ │ ├── ActionReply.js
│ │ └── ActionTopicList.js
│ └── reducer
│ │ ├── ReduceTopicList.js
│ │ ├── ReducerArticle.js
│ │ ├── ReducerNews.js
│ │ └── ReducerRoot.js
└── view
│ ├── App.jsx
│ ├── NotFound.jsx
│ ├── home
│ ├── Article-Comment.jsx
│ ├── Article-DecCnt.jsx
│ ├── Article-Details.jsx
│ ├── Home.jsx
│ └── User-Comment.jsx
│ ├── info
│ └── News.jsx
│ ├── personal
│ └── Mine.jsx
│ └── pose
│ └── Issue.jsx
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-cnode-project
2 |
3 | ### React 版 Cnode 社区
4 |
5 | 技术栈: WebPack^3.6、React^16.3、React-Router-Dom^4.2、Antd^3.5、React-Dom^16.3、Redux、React-Redux、Es6、Flex、Sass
6 |
7 | 链接:[在线访问](//liuguanhua.github.io/cnode)
8 |
9 | 
10 |
11 | 
12 |
13 | ### 目录
14 |
15 | ```
16 | react-cnode-project/
17 | |
18 | ├──src/
19 | |
20 | ├──assets/ * 资源文件(图标、图片、样式)
21 | |
22 | ├──component/ * 公共组件
23 | │
24 | │──script/ * 脚本(Rem适配、Axios请求、路由配置、工具函数)
25 | |
26 | ├──store/ * Redux
27 | │
28 | ├──view/ * 视图展示页面
29 | │
30 | │__index.js * 入口文件
31 | │
32 | │__registerServiceWorker.js * ServiceWorker
33 | |
34 | ├──config-overrides.js * 增加WebPack配置及修改Antd主题颜色
35 | ```
36 |
37 | ### 功能
38 |
39 | - 登录、登出
40 | - 发表话题
41 | - 个人消息
42 | - 评论、点赞帖子
43 | - 查看用户资料
44 |
45 | ### 运行
46 |
47 | ```
48 | 下载:git clone https://github.com/liuguanhua/react-cnode-project.git
49 | 进入:cd react-cnode-project
50 | 安装:yarn install
51 | 开发:yarn start
52 | 生产:yarn build
53 | ```
54 |
55 | 访问 web 页时,需先修改 package.json 文件中 homepage 字段,再进行 yarn build [详见](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#building-for-relative-paths)
56 |
57 |
59 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const {
4 | override,
5 | fixBabelImports,
6 | addLessLoader,
7 | addWebpackAlias
8 | } = require('customize-cra')
9 |
10 | const appDirectory = fs.realpathSync(process.cwd())
11 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath)
12 |
13 | module.exports = {
14 | webpack: override(
15 | fixBabelImports('import', {
16 | libraryName: 'antd',
17 | libraryDirectory: 'es',
18 | style: 'css',
19 | style: true
20 | }),
21 | addLessLoader({
22 | javascriptEnabled: true,
23 | modifyVars: {
24 | '@primary-color': '#639'
25 | }
26 | }),
27 | addWebpackAlias({
28 | '@': resolveApp('src'),
29 | '@module': resolveApp('node_modules'),
30 | '@assets': resolveApp('src/assets'),
31 | '@images': resolveApp('src/assets/images'),
32 | '@fonts': resolveApp('src/assets/fonts'),
33 | '@view': resolveApp('src/view'),
34 | '@store': resolveApp('src/store'),
35 | '@component': resolveApp('src/component'),
36 | '@script': resolveApp('src/script')
37 | }),
38 | config => {
39 | //自定义svg loader方式
40 | const len = config.module.rules[2].oneOf.length
41 | config.module.rules[2].oneOf[1].options.plugins.shift()
42 | config.module.rules[2].oneOf[len - 1].exclude.push(/\.svg$/)
43 | config.module.rules[2].oneOf.push({
44 | test: /\.svg$/,
45 | exclude: /node_modules/,
46 | use: [
47 | {
48 | loader: 'svg-sprite-loader',
49 | options: {
50 | name: '[name]',
51 | prefixize: true,
52 | regExp: resolveApp('src/assets/fonts/svg/(.*)\\.svg')
53 | }
54 | }
55 | ]
56 | })
57 | return config
58 | }
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-cnode-project",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://liuguanhua.github.io/cnode/",
6 | "dependencies": {
7 | "antd": "^3.17.0",
8 | "axios": "^0.18.0",
9 | "babel-plugin-import": "^1.11.0",
10 | "classnames": "^2.2.6",
11 | "compass-sass-mixins": "^0.12.7",
12 | "cross-env": "^5.2.0",
13 | "github-markdown-css": "^3.0.1",
14 | "less": "^3.9.0",
15 | "less-loader": "^5.0.0",
16 | "node-sass": "^4.12.0",
17 | "npm-run-all": "^4.1.5",
18 | "react": "^16.8.6",
19 | "react-app-rewired": "^2.1.3",
20 | "react-dom": "^16.8.6",
21 | "react-lazy-image": "^1.1.0",
22 | "react-loadable": "^5.5.0",
23 | "react-redux": "^7.0.3",
24 | "react-router-dom": "^5.0.0",
25 | "react-scripts": "^3.0.0",
26 | "redux": "^4.0.1",
27 | "redux-thunk": "^2.3.0",
28 | "store": "^2.0.12",
29 | "svg-sprite-loader": "^4.1.6"
30 | },
31 | "scripts": {
32 | "clean": "rimraf build",
33 | "react-app-start": "cross-env PORT=8888 react-app-rewired start",
34 | "start": "rimraf build && npm-run-all -p react-app-start",
35 | "build": "react-app-rewired build",
36 | "test": "react-app-rewired test",
37 | "eject": "react-scripts eject"
38 | },
39 | "eslintConfig": {
40 | "extends": "react-app"
41 | },
42 | "browserslist": {
43 | "production": [
44 | ">0.2%",
45 | "not dead",
46 | "not op_mini all"
47 | ],
48 | "development": [
49 | "last 1 chrome version",
50 | "last 1 firefox version",
51 | "last 1 safari version"
52 | ]
53 | },
54 | "devDependencies": {
55 | "babel-jest": "24.7.1",
56 | "customize-cra": "^0.2.12"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuguanhua/react-cnode-project/c3a2f1e852c0c238c18f79aa870363af6b2e71bc/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
31 | react-cnode
32 |
33 |
34 |
35 |
36 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/comment.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/goback-backup.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/goback.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/gotop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/medropout.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/meedit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/mescore.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/metime.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/meuser.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/nall.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/nask.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/ngood.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/nhome.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/nissue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/njob.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/nnews.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/notfound.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/nshare.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/nuser.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/operat-del.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/operat-keep-active.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/operat-keep-default.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/page-views.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/send.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/thumbs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/svg/write.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/cnode-qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuguanhua/react-cnode-project/c3a2f1e852c0c238c18f79aa870363af6b2e71bc/src/assets/images/cnode-qrcode.png
--------------------------------------------------------------------------------
/src/assets/images/logo-backup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuguanhua/react-cnode-project/c3a2f1e852c0c238c18f79aa870363af6b2e71bc/src/assets/images/logo-backup.png
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuguanhua/react-cnode-project/c3a2f1e852c0c238c18f79aa870363af6b2e71bc/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/small-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuguanhua/react-cnode-project/c3a2f1e852c0c238c18f79aa870363af6b2e71bc/src/assets/images/small-demo.gif
--------------------------------------------------------------------------------
/src/assets/sass/_layout.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100%;
3 | padding: 1.9rem 0 2.7rem;
4 | }
5 |
6 | .container-top {
7 | padding-top: 2rem;
8 | }
9 |
10 | .container-bottom {
11 | padding-bottom: 2.7rem;
12 | }
13 |
14 | [data-layout-flex='0'] {
15 | @include flex(none);
16 | }
17 |
18 | [layout],
19 | [data-layout='layout'] {
20 | @include display-flex;
21 | }
22 |
23 | [data-layout-wrap],
24 | [flex] {
25 | @include flex(1);
26 | }
27 |
28 | [data-layout-wrap],
29 | [layout-wrap] {
30 | @include flex-wrap(wrap);
31 | }
32 |
33 | [data-layout='row'],
34 | [layout='row'] {
35 | @include flex-direction(row);
36 | }
37 |
38 | [data-layout='column'],
39 | [layout='column'] {
40 | @include flex-direction(column);
41 | }
42 |
43 | [data-layout-align='start start'],
44 | [data-layout-align='start center'],
45 | [data-layout-align='start end'] [layout-align='start start'],
46 | [layout-align='start center'],
47 | [layout-align='start end'] {
48 | @include justify-content(flex-start);
49 | }
50 |
51 | [data-layout-align='center start'],
52 | [data-layout-align='center center'],
53 | [data-layout-align='center end'],
54 | [layout-align='center start'],
55 | [layout-align='center center'],
56 | [layout-align='center end'] {
57 | @include justify-content(center);
58 | }
59 |
60 | [data-layout-align='end start'],
61 | [data-layout-align='end center'],
62 | [data-layout-align='end end'] [layout-align='end start'],
63 | [layout-align='end center'],
64 | [layout-align='end end'] {
65 | @include justify-content(flex-end);
66 | }
67 |
68 | [data-layout-align='space-between start'],
69 | [data-layout-align='space-between center'],
70 | [data-layout-align='space-between end'],
71 | [layout-align='space-between start'],
72 | [layout-align='space-between center'],
73 | [layout-align='space-between end'] {
74 | @include justify-content(space-between);
75 | }
76 |
77 | [data-layout-align='space-arround start'],
78 | [data-layout-align='space-arround center'],
79 | [data-layout-align='space-arround end'] [layout-align='space-arround start'],
80 | [layout-align='space-arround center'],
81 | [layout-align='space-arround end'] {
82 | @include justify-content(space-arround);
83 | }
84 |
85 | [data-layout-align='start start'],
86 | [data-layout-align='center start'],
87 | [data-layout-align='end start'],
88 | [data-layout-align='space-between start'],
89 | [data-layout-align='space-arround start'] [layout-align='start start'],
90 | [layout-align='center start'],
91 | [layout-align='end start'],
92 | [layout-align='space-between start'],
93 | [layout-align='space-arround start'] {
94 | @include align-items(flex-start);
95 | }
96 |
97 | [data-layout-align='start center'],
98 | [data-layout-align='center center'],
99 | [data-layout-align='end center'],
100 | [data-layout-align='space-between center'],
101 | [data-layout-align='space-arround center'] [layout-align='start center'],
102 | [layout-align='center center'],
103 | [layout-align='end center'],
104 | [layout-align='space-between center'],
105 | [layout-align='space-arround center'] {
106 | @include align-items(center);
107 | }
108 |
109 | [data-layout-align='start end'],
110 | [data-layout-align='center end'],
111 | [data-layout-align='end end'],
112 | [data-layout-align='space-between end'],
113 | [data-layout-align='space-arround end'] [layout-align='start end'],
114 | [layout-align='center end'],
115 | [layout-align='end end'],
116 | [layout-align='space-between end'],
117 | [layout-align='space-arround end'] {
118 | @include align-items(flex-end);
119 | }
120 |
--------------------------------------------------------------------------------
/src/assets/sass/_mian.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | /* ========== public ========== */
3 |
4 | .fl {
5 | @include float;
6 | }
7 |
8 | .fr {
9 | @include float-right;
10 | }
11 |
12 | .pr {
13 | position: relative;
14 | }
15 |
16 | .pa {
17 | position: absolute;
18 | }
19 |
20 | .pf {
21 | position: fixed;
22 | }
23 |
24 | .pl {
25 | padding-left: $pd;
26 | }
27 |
28 | .t0 {
29 | top: 0;
30 | }
31 |
32 | .b0 {
33 | bottom: 0;
34 | }
35 |
36 | .l0 {
37 | left: 0;
38 | }
39 |
40 | .r0 {
41 | right: 0;
42 | }
43 |
44 | .tl0 {
45 | top: 0;
46 | left: 0;
47 | }
48 |
49 | .bl0 {
50 | bottom: 0;
51 | left: 0;
52 | }
53 |
54 | .lr0 {
55 | right: 0;
56 | left: 0;
57 | margin: auto;
58 | }
59 |
60 | .tb0 {
61 | top: 0;
62 | bottom: 0;
63 | }
64 |
65 | .tblr {
66 | @extend %tblr;
67 | }
68 |
69 | .z1 {
70 | z-index: 1;
71 | }
72 |
73 | .z3 {
74 | z-index: 3;
75 | }
76 |
77 | .z5 {
78 | z-index: 5;
79 | }
80 |
81 | .wh100 {
82 | @include plwh(100%);
83 | }
84 |
85 | [hide],
86 | .hide {
87 | @extend %hide;
88 | }
89 |
90 | [show] {
91 | @extend %show;
92 | }
93 |
94 | .ilbk,
95 | .icons,
96 | .icon,
97 | .svg,
98 | [ilbk] {
99 | @include inline-block;
100 | }
101 |
102 | .mgt {
103 | margin-top: $pd;
104 | }
105 |
106 | .rdir-mgt {
107 | margin-top: -$pd;
108 | }
109 |
110 | .mgb {
111 | margin-bottom: $pd;
112 | }
113 |
114 | .mgl {
115 | margin-left: $pd;
116 | }
117 |
118 | .mgr {
119 | margin-right: $pd;
120 | }
121 |
122 | .mgtb {
123 | margin: $pd 0;
124 | }
125 |
126 | .mglr {
127 | margin: 0 $pd;
128 | }
129 |
130 | .mgt {
131 | margin-top: $pd;
132 | }
133 |
134 | .pd {
135 | padding: $pd;
136 | }
137 |
138 | .pdtb {
139 | padding: $pd 0;
140 | }
141 |
142 | .pdtr {
143 | padding-top: $pd;
144 | padding-right: $pd;
145 | }
146 |
147 | .pdlr {
148 | padding: 0 $pd;
149 | }
150 |
151 | .pdtl {
152 | padding-top: $pd;
153 | padding-left: $pd;
154 | }
155 |
156 | .pdlb {
157 | padding-bottom: $pd;
158 | padding-left: $pd;
159 | }
160 |
161 | .pdtbl {
162 | padding: $pd 0 $pd $pd;
163 | }
164 |
165 | .pdl {
166 | padding-left: $pd;
167 | }
168 |
169 | .pdt {
170 | padding-top: $pd;
171 | }
172 |
173 | .pdr {
174 | padding-right: $pd;
175 | }
176 |
177 | .pdb {
178 | padding-bottom: $pd;
179 | }
180 |
181 | .w-half {
182 | width: 50%;
183 | }
184 |
185 | .tc {
186 | @extend %tc;
187 | }
188 |
189 | .tr {
190 | @extend %tr;
191 | }
192 |
193 | .tl {
194 | @extend %tl;
195 | }
196 |
197 | .ys-white {
198 | @extend %ft-white;
199 | }
200 |
201 | .st-font {
202 | @extend %ft-sfm;
203 | }
204 |
205 | %fz8,
206 | .fz8 {
207 | font-size: 0.8rem;
208 | }
209 |
210 | [ellipsis],
211 | [data-ellipsis] {
212 | @include ellipsis;
213 | }
214 |
215 | [ft-color] {
216 | @extend %ft-color;
217 | }
218 |
219 | [ft-white],
220 | .ft-white {
221 | @extend %ft-white;
222 | }
223 |
224 | .ft-sgrey {
225 | @extend %ft-sgrey;
226 | }
227 |
228 | .ft-qgrey {
229 | @extend %ft-qgrey;
230 | }
231 |
232 | .ft-grey {
233 | @extend %ft-grey;
234 | }
235 |
236 | .ft-orange {
237 | @extend %ft-orange;
238 | }
239 |
240 | .ft-color {
241 | @extend %ft-color;
242 | }
243 |
244 | .ft-purple {
245 | color: #00f0ff;
246 | }
247 |
248 | .w100 {
249 | width: 100%;
250 | }
251 |
252 | .h100 {
253 | height: 100%;
254 | }
255 |
256 | .ft-bold {
257 | font-weight: 900;
258 | }
259 |
260 | .ft-normal {
261 | font-weight: normal;
262 | }
263 |
264 | [ft-hidden] {
265 | overflow: hidden;
266 | text-overflow: ellipsis;
267 | display: -webkit-box;
268 | -webkit-box-orient: vertical;
269 | }
270 |
271 | .vam {
272 | vertical-align: middle;
273 | }
274 |
275 | .img-tc {
276 | @extend %show;
277 | max-width: 100%;
278 | margin-left: auto;
279 | margin-right: auto;
280 | }
281 |
282 | .dp-flex {
283 | @include display-flex;
284 | }
285 |
286 | [bg-color] {
287 | @extend %bg-color;
288 | }
289 |
290 | .bg-sgrey {
291 | @extend %bg-sgrey;
292 | }
293 |
294 | .bg-qgrey {
295 | @extend %bg-qgrey;
296 | }
297 |
298 | .bg-grey {
299 | @extend %bg-grey;
300 | }
301 |
302 | .bg-white {
303 | @extend %bg-white;
304 | }
305 |
306 | .bg-orange {
307 | @extend %bg-orange;
308 | }
309 |
310 | .bg-color {
311 | @extend %bg-color;
312 | }
313 |
314 | .bg-black {
315 | @extend %bg-black;
316 | }
317 |
318 | .bg-qblack {
319 | @extend %bg-qblack;
320 | }
321 |
322 | .bg-transparent {
323 | background-color: rgba($ft-black, .5);
324 | }
325 |
326 | .bg-color-none {
327 | background-color: transparent;
328 | }
329 |
330 | .bdr-half {
331 | @include border-radius(50%);
332 | }
333 |
334 | /* ========== flex ========== */
335 |
336 | .numFt {
337 | font-family: 'Georgia'; //好看的数字字体
338 | }
339 |
340 | .bd-radius {
341 | border-radius: .3rem;
342 | }
343 |
344 | .bd-none {
345 | border: none;
346 | }
347 |
348 | .small-font {
349 | font-size: .6rem;
350 | }
351 |
352 | .tab-wrap {
353 | position: fixed;
354 | top: 0;
355 | left: 0;
356 | display: table;
357 | width: 100%;
358 | height: 100%;
359 | }
360 |
361 | .tab-wrap-cell {
362 | display: table-cell;
363 | vertical-align: middle;
364 | }
365 |
366 | %mask-tm {
367 | position: fixed;
368 | z-index: 11;
369 | top: 0;
370 | left: 0;
371 | width: 100%;
372 | height: 100%;
373 | background-color: #000;
374 | background-color: rgba(0, 0, 0, .5);
375 | }
376 |
377 | .mask-layer {
378 | @extend %mask-tm;
379 | }
380 |
381 | .mask-page {
382 | z-index: 1;
383 | @extend %mask-tm;
384 | }
385 |
--------------------------------------------------------------------------------
/src/assets/sass/_mixin.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | /*========== 主体颜色 ==========*/
3 |
4 | $pd: 0.4rem;
5 | $ft-color: #639;
6 | $ft-white: #fff;
7 | $ft-black: #000;
8 | $ft-qblack: #333;
9 | $ft-orange: #fcd667;
10 | $ft-grey: #909ba5;
11 | $ft-sgrey: #b6b6b6;
12 | $ft-qgrey: #f3f3f3;
13 | $ft-golden: #baa16c;
14 | $ft-blue: #3068b5;
15 | $ft-sblue: #123b77;
16 | $hv-color: #5f8c02;
17 | $mg10: 10px;
18 | $cheek: 1px solid $ft-grey;
19 | /*========== 网页字体 ==========*/
20 |
21 | %ft-wfm {
22 | font-family: "Helvetica Neue", Helvetica, STHeiTi, Arial, sans-serif;
23 | }
24 |
25 | %ft-sfm {
26 | font-family: "simsun", Helvetica Neue, Helvetica, STHeiTi, Arial, sans-serif;
27 | }
28 |
29 | /* ========== 字体颜色 ========== */
30 |
31 | %ft-color {
32 | color: $ft-color;
33 | }
34 |
35 | %ft-white {
36 | color: $ft-white;
37 | }
38 |
39 | %ft-black {
40 | color: $ft-black;
41 | }
42 |
43 | %ft-qblack {
44 | color: $ft-qblack;
45 | }
46 |
47 | %ft-grey {
48 | color: $ft-grey;
49 | }
50 |
51 | %ft-qgrey {
52 | color: $ft-qgrey;
53 | }
54 |
55 | %ft-sgrey {
56 | color: $ft-sgrey;
57 | }
58 |
59 | %ft-sblue {
60 | color: $ft-sblue;
61 | }
62 |
63 | %ft-golden {
64 | color: $ft-golden;
65 | }
66 |
67 | %ft-orange {
68 | color: $ft-orange;
69 | }
70 |
71 | %hv-ft-color {
72 | color: $ft-color;
73 | }
74 |
75 | /* ========== 字体大小 ========== */
76 |
77 | %ft-big {
78 | font-size: 16px;
79 | }
80 |
81 | %ft-small {
82 | font-size: 12px;
83 | line-height: 22px;
84 | }
85 |
86 | /* ========== 文本位置 ========== */
87 |
88 | %tc {
89 | text-align: center;
90 | }
91 |
92 | %tr {
93 | text-align: right;
94 | }
95 |
96 | %tl {
97 | text-align: left;
98 | }
99 |
100 | /* ========== 背景颜色 ========== */
101 |
102 | %bg-black {
103 | background: $ft-black;
104 | }
105 |
106 | %bg-qblack {
107 | background: $ft-qblack;
108 | }
109 |
110 | %bg-grey {
111 | background: $ft-grey;
112 | }
113 |
114 | %bg-sgrey {
115 | background: $ft-sgrey;
116 | }
117 |
118 | %bg-qgrey {
119 | background: $ft-qgrey;
120 | }
121 |
122 | %bg-white {
123 | background-color: $ft-white;
124 | }
125 |
126 | %bg-color {
127 | background-color: $ft-color;
128 | }
129 |
130 | %bg-orange {
131 | background-color: $ft-orange;
132 | }
133 |
134 | %bg-blue {
135 | background-color: $ft-blue;
136 | }
137 |
138 | %bg-hv {
139 | background-color: $hv-color;
140 | }
141 |
142 | %bd-cheek {
143 | border: $cheek;
144 | }
145 |
146 | %fslse {
147 | content: "";
148 | }
149 |
150 | @mixin border($bdcolor) {
151 | border: 1px solid $bdcolor;
152 | }
153 |
154 | @mixin plborder($tblr, $bdcolor) {
155 | border-#{$tblr}: 1px solid $bdcolor;
156 | }
157 |
158 | @mixin plmargin($tblr, $mgpx) {
159 | margin-#{$tblr}: $mgpx;
160 | }
161 |
162 | @mixin margin($mg) {
163 | margin: $mg;
164 | }
165 |
166 | @mixin plpadding($tblr, $pdpx) {
167 | padding-#{$tblr}: $pdpx;
168 | }
169 |
170 | @mixin plhlh($hlh) {
171 | height: $hlh;
172 | line-height: $hlh;
173 | }
174 |
175 | %show {
176 | display: block;
177 | }
178 |
179 | %hide {
180 | display: none;
181 | }
182 |
183 | %tblr {
184 | position: absolute;
185 | top: 0;
186 | bottom: 0;
187 | left: 0;
188 | right: 0;
189 | margin: auto;
190 | }
191 |
192 | /* ========== clear ========== */
193 |
194 | @mixin clear {
195 | *zoom: 1;
196 | &:before,
197 | &:after {
198 | content: "";
199 | display: table;
200 | height: 0;
201 | }
202 | &:after {
203 | clear: both;
204 | }
205 | }
206 |
207 | %clear {
208 | @include clear;
209 | }
210 |
211 | /* ========== opacity ========== */
212 |
213 | @mixin plwh($wh) {
214 | width: $wh;
215 | height: $wh;
216 | }
217 |
218 | @mixin wh($w, $h) {
219 | width: $w;
220 | height: $h;
221 | }
222 |
223 | @mixin plwhr($wh) {
224 | width: $wh;
225 | height: $wh; // @include border-radius(50%);
226 | }
227 |
--------------------------------------------------------------------------------
/src/assets/sass/common/_reset.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | html {
3 | @extend %ft-black;
4 | -webkit-text-size-adjust: 100%;
5 | -ms-text-size-adjust: 100%;
6 | /* 禁用iPhone中Safari的字号自动调整 */
7 | text-rendering: optimizelegibility;
8 | max-width: 768px;
9 | margin: auto;
10 | @extend %bg-qgrey;
11 | touch-action: none;
12 | }
13 |
14 | body {
15 | /*解决上下拉动滚动条时卡顿、慢*/
16 | -webkit-overflow-scrolling: touch;
17 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
18 | overflow-scrolling: touch;
19 | @extend %bg-qgrey;
20 | }
21 |
22 | *,
23 | *:after,
24 | *:before {
25 | box-sizing: border-box;
26 | }
27 |
28 | article,
29 | aside,
30 | blockquote,
31 | body,
32 | button,
33 | code,
34 | dd,
35 | details,
36 | dl,
37 | dt,
38 | fieldset,
39 | figcaption,
40 | figure,
41 | footer,
42 | form,
43 | h1,
44 | h2,
45 | h3,
46 | h4,
47 | h5,
48 | h6,
49 | header,
50 | hr,
51 | input,
52 | legend,
53 | li,
54 | menu,
55 | nav,
56 | ol,
57 | p,
58 | pre,
59 | section,
60 | td,
61 | textarea,
62 | th,
63 | ul {
64 | margin: 0;
65 | padding: 0;
66 | }
67 |
68 | article,
69 | aside,
70 | details,
71 | figcaption,
72 | figure,
73 | footer,
74 | header,
75 | menu,
76 | nav,
77 | section {
78 | display: block;
79 | }
80 |
81 | audio,
82 | canvas,
83 | video {
84 | display: inline-block;
85 | }
86 |
87 | a,
88 | body,
89 | button,
90 | html,
91 | input,
92 | select,
93 | textarea {
94 | font: 0.7rem/0.8rem 'Helvetica Neue', Helvetica, STHeiTi, Arial, sans-serif;
95 | outline: none;
96 | }
97 |
98 | table {
99 | border-collapse: collapse;
100 | border-spacing: 0;
101 | }
102 |
103 | fieldset,
104 | iframe,
105 | img {
106 | border: 0;
107 | }
108 |
109 | abbr,
110 | acronym {
111 | border-bottom: 1px dotted;
112 | font-variant: normal;
113 | }
114 |
115 | abbr {
116 | cursor: help;
117 | }
118 |
119 | address,
120 | caption,
121 | cite,
122 | code,
123 | dfn,
124 | em,
125 | th,
126 | var {
127 | font-style: normal;
128 | }
129 |
130 | ol,
131 | ul {
132 | list-style: none;
133 | }
134 |
135 | caption,
136 | th {
137 | text-align: left;
138 | }
139 |
140 | img {
141 | vertical-align: middle;
142 | max-width: 100%;
143 | height: auto;
144 | width: auto\9;
145 | /* ie8 */
146 | -ms-interpolation-mode: bicubic;
147 | /*为了照顾ie图片缩放失真*/
148 | }
149 |
150 | em {
151 | font-style: normal;
152 | }
153 |
154 | a {
155 | border: none;
156 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
157 | -webkit-tap-highlight-color: transparent;
158 | outline: none;
159 | @extend %ft-black;
160 | text-decoration: none;
161 | }
162 |
163 | //IE下
164 | a:focus {
165 | outline: none;
166 | }
167 |
168 | //FF下
169 | a.hidefocus {
170 | outline: none;
171 | }
172 |
173 | a:hover {
174 | text-decoration: none;
175 | }
176 |
177 | mark {
178 | background: #fffdd1;
179 | border-bottom: 1px solid #ffedce;
180 | padding: 2px;
181 | margin: 0 5px;
182 | }
183 |
184 | code,
185 | pre,
186 | pre tt {
187 | font-family: Courier, 'Courier New', monospace;
188 | }
189 |
190 | figcaption,
191 | small {
192 | font-size: 14px;
193 | color: #888;
194 | }
195 |
196 | b,
197 | strong {
198 | font-weight: 700;
199 | }
200 |
201 | //[draggable]{cursor:move;}
202 | /*========== headline ==========*/
203 |
204 | h1,
205 | h2,
206 | h3,
207 | h4,
208 | h5,
209 | h6 {
210 | font-weight: normal;
211 | }
212 |
213 | h1 {
214 | font-size: 1.2rem;
215 | line-height: 1.6rem;
216 | }
217 |
218 | h2 {
219 | font-size: 0.85rem;
220 | line-height: 1.4rem;
221 | }
222 |
223 | h3 {
224 | font-size: 0.75rem;
225 | line-height: 1.2rem;
226 | }
227 |
228 | h4 {
229 | font-size: 0.6rem;
230 | line-height: 0.8rem;
231 | }
232 |
233 | h5 {
234 | font-size: 0.4rem;
235 | line-height: 0.6rem;
236 | }
237 |
238 | h6 {
239 | font-size: 0.2rem;
240 | line-height: 0.4rem;
241 | }
242 |
243 | /*========== clearfix ==========*/
244 |
245 | .clearfix {
246 | zoom: 1;
247 | display: inline-table;
248 | }
249 |
250 | /* Hides from IE-mac \*/
251 |
252 | [clearfix]:after,
253 | [clearfix]:before {
254 | content: '';
255 | display: table;
256 | }
257 |
258 | [clearfix]:after {
259 | clear: both;
260 | }
261 |
262 | * html [clearfix] {
263 | height: 1%;
264 | }
265 |
266 | [clearfix] {
267 | // display: block;
268 | }
269 |
270 | /* End hide from IE-mac */
271 |
272 | * + html [clearfix] {
273 | min-height: 1%;
274 | }
275 |
276 | /* IE7 */
277 |
278 | .clearFd {
279 | clear: both;
280 | }
281 |
282 | /*========== form ==========*/
283 |
284 | input[type='checkbox'] {
285 | vertical-align: middle;
286 | &:checked {
287 | appearance: none;
288 | }
289 | }
290 |
291 | button,
292 | input[type='button'],
293 | input[type='submit'],
294 | input[type='checkbox'],
295 | input[type='reset'] {
296 | cursor: pointer;
297 | }
298 |
299 | input[type='text']:focus,
300 | input[type='password']:focus,
301 | textarea:focus,
302 | input[type='email']:focus,
303 | input[type='number']:focus,
304 | input[type='search']:focus,
305 | button:focus,
306 | input[type='reset']:focus,
307 | input[type='submit']:focus,
308 | input[type='button']:focus {
309 | outline: none;
310 | }
311 |
312 | input[type='text']:focus,
313 | input[type='password']:focus,
314 | textarea:focus,
315 | input[type='email']:focus,
316 | input[type='number']:focus,
317 | input[type='search']:focus {
318 | // @include box-shadow(0 0 .2rem $ft-sgrey);
319 | }
320 |
321 | button::-moz-focus-inner,
322 | input::-moz-focus-inner {
323 | padding: 0;
324 | border: 0;
325 | }
326 |
327 | textarea {
328 | resize: none;
329 | overflow: auto;
330 | @include box-shadow(0 0 0 rgba(0, 0, 0, 0));
331 | -webkit-appearance: none;
332 | }
333 |
334 | /* 去除iPhone中默认的input样式 */
335 |
336 | input {
337 | resize: none;
338 | }
339 |
340 | button {
341 | border: none;
342 | }
343 |
344 | // -webkit-user-select: none;/*禁止复制、选中文本*/
345 | // -moz-user-select: none;
346 | // -khtml-user-select: none;
347 | // user-select: none;
348 | button,
349 | input {
350 | outline: none; //
351 | appearance: none; //去掉ios input 默认圆角
352 | -webkit-appearance: none;
353 | border-radius: 0; //去掉ios button 默认圆角
354 | -webkit-tap-highlight-color: transparent; //去掉ios在触发的时候,input会有一个灰色的背景色块
355 | -webkit-touch-callout: none;
356 | /*长时间按住页面出现闪退*/
357 | }
358 |
359 | /*========== Delete button under Firefox default style ==========*/
360 |
361 | input[type='reset']::-moz-focus-inner,
362 | input[type='button']::-moz-focus-inner,
363 | input[type='submit']::-moz-focus-inner,
364 | input[type='file'] > input[type='button']::-moz-focus-inner {
365 | border: none;
366 | padding: 0;
367 | }
368 |
--------------------------------------------------------------------------------
/src/assets/sass/global.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | @import 'mixin'; //mixin
3 | @import './node_modules/compass-sass-mixins/lib/compass';
4 | @import 'common/reset'; //重置浏览器页面样式
5 | @import 'layout'; //布局
6 | @import 'mian'; //主体样式
7 | #root {
8 | height: 100%;
9 | }
10 |
11 | a:hover {
12 | color: $ft-color;
13 | }
14 |
15 | .header {
16 | .head-logo {
17 | padding: $pd * 1.5 0;
18 | font-size: 0.8rem;
19 | }
20 | }
21 |
22 | .tab-nav-list,
23 | .footer-menu {
24 | li {
25 | text-align: center;
26 | }
27 | a {
28 | display: block;
29 | padding: $pd 0;
30 | }
31 | }
32 |
33 | .tab-nav-list {
34 | li {
35 | width: 20%;
36 | }
37 | }
38 |
39 | .nav-top-fixed {
40 | box-shadow: 0 0 5px rgba($ft-grey, 0.1);
41 | }
42 |
43 | .footer-menu {
44 | width: 100%; // background-image: linear-gradient(to bottom , #1f3c29,#0c0e34);
45 | background-color: #fff;
46 | box-shadow: 0 0 10px rgba($ft-grey, 0.05);
47 | li {
48 | width: 25%;
49 | }
50 | a {
51 | padding: $pd/2 0;
52 | }
53 | .svg {
54 | @include plwh(1.5rem);
55 | }
56 | .svg-nav-active {
57 | display: none;
58 | }
59 | .nav-selected {
60 | .svg-nav-active {
61 | display: inline-block;
62 | }
63 | .svg-nav-default {
64 | display: none;
65 | }
66 | p {
67 | color: $ft-color;
68 | }
69 | .news-number {
70 | background-color: $ft-color;
71 | }
72 | }
73 | .news-number {
74 | @include plwh(1rem);
75 | right: 1.2rem;
76 | top: 0;
77 | }
78 | }
79 |
80 | .photo {
81 | @include plwh(10rem);
82 | }
83 |
84 | .scroll-wrap {
85 | padding-top: 1.7rem;
86 | }
87 |
88 | .iscroll-wrapper.loadmore-wrap {
89 | top: 3.6rem;
90 | bottom: 2.6rem;
91 | }
92 |
93 | .loadmore-content {
94 | padding: 1.6rem 0 2.7rem;
95 | }
96 |
97 | .article-item {
98 | padding: $pd;
99 | background-color: #fff;
100 | @at-root .author-avatar {
101 | @include plwh(4rem);
102 | }
103 | @at-root .small-avatar-wrap {
104 | @include plwh(2.5rem);
105 | }
106 | @at-root .small-author-avatar {
107 | @include plwh(2rem);
108 | }
109 | .title-type {
110 | padding: 0 $pd / 2;
111 | }
112 | .svg {
113 | @include plwh(1rem);
114 | }
115 | }
116 |
117 | .ant-back-top {
118 | @include plwh(2.5rem);
119 | position: fixed;
120 | bottom: 4rem;
121 | right: 1rem;
122 | border-radius: 50%;
123 | .svg-gotop {
124 | @include plwh(1.8rem);
125 | }
126 | }
127 |
128 | //动画
129 | .view-main {
130 | transition: 0.5s;
131 | }
132 |
133 | .hideLeft {
134 | opacity: 0.75;
135 | transform: translate3d(-100%, 0, 0);
136 | transition: 0.5s;
137 | }
138 |
139 | .hideRight {
140 | opacity: 0.75;
141 | transform: translate3d(100%, 0, 0);
142 | transition: 0.5s;
143 | }
144 |
145 | .example-enter {
146 | opacity: 0;
147 | transform: translateX(100%);
148 | }
149 |
150 | .example-enter.example-enter-active {
151 | opacity: 1;
152 | transform: translateX(0);
153 | transition: all 0.5s ease-in;
154 | }
155 |
156 | .example-exit {
157 | opacity: 1;
158 | transform: translateX(0);
159 | }
160 |
161 | .example-exit.example-exit-active {
162 | opacity: 0;
163 | transform: translateX(-100%);
164 | transition: all 0.3s ease-out;
165 | }
166 |
167 | .example-appear {
168 | opacity: 0.01;
169 | }
170 |
171 | .example-appear.example-appear-active {
172 | opacity: 1;
173 | transition: opacity 0.5s ease-in;
174 | }
175 |
176 | $headerBar: header-bar;
177 | .#{$headerBar} {
178 | .svg-goback {
179 | padding: 0.3rem;
180 | @include plwh(2rem);
181 | }
182 | .#{$headerBar}-title {
183 | padding: 0.3rem;
184 | }
185 | .#{$headerBar}-antimg {
186 | margin-left: -2rem;
187 | }
188 | }
189 |
190 | .article-operat {
191 | .svg {
192 | @include plwh(1rem);
193 | }
194 | }
195 |
196 | .markdown-body {
197 | font-size: 14px;
198 | word-break: break-all;
199 | overflow: hidden;
200 | h1,
201 | h2 {
202 | margin: 0;
203 | }
204 | h1 {
205 | font-size: 1.8em;
206 | }
207 | img {
208 | max-width: 100%;
209 | }
210 | }
211 |
212 | // 评论
213 | .svg-comment,
214 | .svg-thumbs,
215 | [data-svg-wh] {
216 | @include plwh(1rem);
217 | }
218 |
219 | .comment-title {
220 | padding: 0.4rem 0;
221 | }
222 |
223 | .article-comment-list {
224 | li {
225 | padding: $pd;
226 | border-bottom: 1px dashed $ft-qgrey;
227 | }
228 | .user-thumbs {
229 | margin-right: 1rem;
230 | }
231 | .user-comment-cnt {
232 | overflow: hidden;
233 | }
234 | }
235 |
236 | .fixed-bottom-wrap {
237 | height: 2.5rem;
238 | box-shadow: 0 0 10px rgba($ft-grey, 0.05);
239 | }
240 |
241 | .room-speak {
242 | button,
243 | input {
244 | border: none;
245 | }
246 | .ipt {
247 | height: 2.5rem;
248 | padding-left: 1rem;
249 | }
250 | .ipt-txt-wrap {
251 | &:after,
252 | &:before {
253 | content: '';
254 | position: absolute;
255 | top: 0;
256 | bottom: 0;
257 | margin: auto;
258 | width: 1px;
259 | height: 1.5rem;
260 | @extend %bg-grey;
261 | }
262 | &:after {
263 | right: 0;
264 | }
265 | &:before {
266 | left: 0.4rem;
267 | }
268 | }
269 | .btn-speak-submit {
270 | // width: 5.3rem;
271 | height: 2.5rem;
272 | color: #00f0ff;
273 | }
274 | .tm-switch-wrap {
275 | width: 22%;
276 | }
277 | .speak-submit-wrap {
278 | width: 15%;
279 | }
280 | }
281 |
282 | //未登录
283 | .not-login-wrap {
284 | a {
285 | margin-left: 0.4rem;
286 | padding: 0.2rem 0.4rem;
287 | border: 1px solid $ft-color;
288 | font-size: 0.8rem;
289 | border-radius: 0.3rem;
290 | }
291 | }
292 |
293 | .not-login-photo {
294 | width: 30%;
295 | margin: 1rem 0;
296 | }
297 |
298 | .user-login-form {
299 | width: 80%;
300 | margin: 60% auto 0;
301 | input {
302 | width: 100%;
303 | height: 2rem;
304 | }
305 | .ipt-row {
306 | margin-top: 10%;
307 | }
308 | }
309 |
310 | .plw {
311 | width: 80%;
312 | margin: 0 auto;
313 | }
314 |
315 | .public-button {
316 | height: 2rem;
317 | }
318 |
319 | //发表
320 | .issue-content-wrap {
321 | input[type='text'],
322 | textarea {
323 | border-bottom: 1px solid $ft-sgrey;
324 | }
325 | textarea {
326 | height: 8rem;
327 | }
328 | @at-root.radio-wrap >span {
329 | position: relative;
330 | @include plwh(1rem);
331 | border: 1px solid $ft-sgrey;
332 | border-radius: 50%;
333 | }
334 | input[type='radio'] {
335 | @extend %tblr;
336 | opacity: 0;
337 | }
338 | .select-radio-active > span {
339 | &:before {
340 | content: '';
341 | @extend %tblr;
342 | @include plwh(0.6rem);
343 | border-radius: 50%;
344 | background: $ft-color;
345 | }
346 | }
347 | .theme-issue-submit {
348 | margin-top: 0.8rem;
349 | }
350 | }
351 |
352 | //用户中心
353 | .user-center-info {
354 | background-image: linear-gradient(180deg, #7b57bb, $ft-color);
355 | .svg {
356 | @include plwh(1rem);
357 | margin-right: 0.2rem;
358 | }
359 | .user-info-row {
360 | > p {
361 | margin-top: 0.2rem;
362 | }
363 | }
364 | }
365 |
366 | //user tab
367 | .news-content {
368 | .ant-tabs-tab {
369 | width: 50%;
370 | }
371 | }
372 |
373 | .user-center {
374 | .ant-tabs-tab {
375 | width: 33.33%;
376 | }
377 | }
378 |
379 | .clear-unread-msg {
380 | padding: 0.5rem 0.6rem;
381 | }
382 |
383 | .ant-tabs-nav {
384 | width: 100%;
385 | background-color: #fff;
386 | div.ant-tabs-tab {
387 | padding: 0.6rem 0;
388 | text-align: center;
389 | margin-right: 0;
390 | }
391 | .ant-tabs-tab-active,
392 | .ant-tabs-tab:hover {
393 | color: $ft-color;
394 | }
395 | @at-root div.ant-tabs-bar {
396 | margin-bottom: 0;
397 | }
398 | .ant-tabs-ink-bar {
399 | background-color: $ft-color;
400 | }
401 | }
402 |
403 | .title-cnt {
404 | width: 12rem;
405 | }
406 |
407 | .form-msg {
408 | position: fixed;
409 | z-index: 5;
410 | left: 0;
411 | right: 0;
412 | top: 50%;
413 | margin: auto;
414 | padding: 0.6rem 0;
415 | max-width: 10rem;
416 | width: 50%;
417 | background-color: rgba($ft-black, 0.3);
418 | @extend %ft-white;
419 | font-size: 0.8rem;
420 | @include border-radius(0.2rem);
421 | @include translateY(-50%);
422 | }
423 |
424 | .confirm-popups {
425 | width: 70%;
426 | margin: 0 auto;
427 | @extend %bg-white;
428 | @include border-radius(0.2rem);
429 | .confirm-title {
430 | padding: 1.5rem 0;
431 | border-bottom: 1px solid $ft-grey;
432 | }
433 | .confirm-btn {
434 | button {
435 | width: 50%;
436 | padding: 0.8rem 0;
437 | border: none;
438 | background-color: transparent;
439 | }
440 | .confirm-btn-no {
441 | border-right: 1px solid $ft-grey;
442 | }
443 | }
444 | }
445 |
446 | .confirm-report-cnt {
447 | width: 90%;
448 | height: 4rem;
449 | padding: $pd; // border-right: 1px solid $ft-grey;
450 | @include border-radius(0.2rem);
451 | }
452 |
--------------------------------------------------------------------------------
/src/assets/styles/css/global.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | /*========== 主体颜色 ==========*/
3 | /*========== 网页字体 ==========*/
4 | .st-font {
5 | font-family: "simsun", Helvetica Neue, Helvetica, STHeiTi, Arial, sans-serif; }
6 |
7 | /* ========== 字体颜色 ========== */
8 | [ft-color], .ft-color {
9 | color: #639; }
10 |
11 | .ys-white, [ft-white],
12 | .ft-white, .form-msg {
13 | color: #fff; }
14 |
15 | html, a {
16 | color: #000; }
17 |
18 | .ft-grey {
19 | color: #909ba5; }
20 |
21 | .ft-qgrey {
22 | color: #f3f3f3; }
23 |
24 | .ft-sgrey {
25 | color: #b6b6b6; }
26 |
27 | .ft-orange {
28 | color: #fcd667; }
29 |
30 | /* ========== 字体大小 ========== */
31 | /* ========== 文本位置 ========== */
32 | .tc {
33 | text-align: center; }
34 |
35 | .tr {
36 | text-align: right; }
37 |
38 | .tl {
39 | text-align: left; }
40 |
41 | /* ========== 背景颜色 ========== */
42 | .bg-black {
43 | background: #000; }
44 |
45 | .bg-qblack {
46 | background: #333; }
47 |
48 | .bg-grey, .room-speak .ipt-txt-wrap:after, .room-speak .ipt-txt-wrap:before {
49 | background: #909ba5; }
50 |
51 | .bg-sgrey {
52 | background: #b6b6b6; }
53 |
54 | html, body, .bg-qgrey {
55 | background: #f3f3f3; }
56 |
57 | .bg-white, .confirm-popups {
58 | background-color: #fff; }
59 |
60 | [bg-color], .bg-color {
61 | background-color: #639; }
62 |
63 | .bg-orange {
64 | background-color: #fcd667; }
65 |
66 | [show], .img-tc {
67 | display: block; }
68 |
69 | [hide],
70 | .hide {
71 | display: none; }
72 |
73 | .tblr, .issue-content-wrap input[type='radio'], .issue-content-wrap .select-radio-active > span:before {
74 | position: absolute;
75 | top: 0;
76 | bottom: 0;
77 | left: 0;
78 | right: 0;
79 | margin: auto; }
80 |
81 | /* ========== clear ========== */
82 | /* ========== opacity ========== */
83 | html {
84 | -webkit-text-size-adjust: 100%;
85 | -ms-text-size-adjust: 100%;
86 | /* 禁用iPhone中Safari的字号自动调整 */
87 | text-rendering: optimizelegibility;
88 | max-width: 768px;
89 | margin: auto;
90 | touch-action: none; }
91 |
92 | body {
93 | /*解决上下拉动滚动条时卡顿、慢*/
94 | -webkit-overflow-scrolling: touch;
95 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
96 | overflow-scrolling: touch; }
97 |
98 | *,
99 | *:after,
100 | *:before {
101 | box-sizing: border-box; }
102 |
103 | article,
104 | aside,
105 | blockquote,
106 | body,
107 | button,
108 | code,
109 | dd,
110 | details,
111 | dl,
112 | dt,
113 | fieldset,
114 | figcaption,
115 | figure,
116 | footer,
117 | form,
118 | h1,
119 | h2,
120 | h3,
121 | h4,
122 | h5,
123 | h6,
124 | header,
125 | hr,
126 | input,
127 | legend,
128 | li,
129 | menu,
130 | nav,
131 | ol,
132 | p,
133 | pre,
134 | section,
135 | td,
136 | textarea,
137 | th,
138 | ul {
139 | margin: 0;
140 | padding: 0; }
141 |
142 | article,
143 | aside,
144 | details,
145 | figcaption,
146 | figure,
147 | footer,
148 | header,
149 | menu,
150 | nav,
151 | section {
152 | display: block; }
153 |
154 | audio,
155 | canvas,
156 | video {
157 | display: inline-block; }
158 |
159 | a,
160 | body,
161 | button,
162 | html,
163 | input,
164 | select,
165 | textarea {
166 | font: 0.7rem/0.8rem 'Helvetica Neue', Helvetica, STHeiTi, Arial, sans-serif;
167 | outline: none; }
168 |
169 | table {
170 | border-collapse: collapse;
171 | border-spacing: 0; }
172 |
173 | fieldset,
174 | iframe,
175 | img {
176 | border: 0; }
177 |
178 | abbr,
179 | acronym {
180 | border-bottom: 1px dotted;
181 | font-variant: normal; }
182 |
183 | abbr {
184 | cursor: help; }
185 |
186 | address,
187 | caption,
188 | cite,
189 | code,
190 | dfn,
191 | em,
192 | th,
193 | var {
194 | font-style: normal; }
195 |
196 | ol,
197 | ul {
198 | list-style: none; }
199 |
200 | caption,
201 | th {
202 | text-align: left; }
203 |
204 | img {
205 | vertical-align: middle;
206 | max-width: 100%;
207 | height: auto;
208 | width: auto\9;
209 | /* ie8 */
210 | -ms-interpolation-mode: bicubic;
211 | /*为了照顾ie图片缩放失真*/ }
212 |
213 | em {
214 | font-style: normal; }
215 |
216 | a {
217 | border: none;
218 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
219 | -webkit-tap-highlight-color: transparent;
220 | outline: none;
221 | text-decoration: none; }
222 |
223 | a:focus {
224 | outline: none; }
225 |
226 | a.hidefocus {
227 | outline: none; }
228 |
229 | a:hover {
230 | text-decoration: none; }
231 |
232 | mark {
233 | background: #fffdd1;
234 | border-bottom: 1px solid #ffedce;
235 | padding: 2px;
236 | margin: 0 5px; }
237 |
238 | code,
239 | pre,
240 | pre tt {
241 | font-family: Courier, 'Courier New', monospace; }
242 |
243 | figcaption,
244 | small {
245 | font-size: 14px;
246 | color: #888; }
247 |
248 | b,
249 | strong {
250 | font-weight: 700; }
251 |
252 | /*========== headline ==========*/
253 | h1,
254 | h2,
255 | h3,
256 | h4,
257 | h5,
258 | h6 {
259 | font-weight: normal; }
260 |
261 | h1 {
262 | font-size: 1.2rem;
263 | line-height: 1.6rem; }
264 |
265 | h2 {
266 | font-size: 0.85rem;
267 | line-height: 1.4rem; }
268 |
269 | h3 {
270 | font-size: 0.75rem;
271 | line-height: 1.2rem; }
272 |
273 | h4 {
274 | font-size: 0.6rem;
275 | line-height: 0.8rem; }
276 |
277 | h5 {
278 | font-size: 0.4rem;
279 | line-height: 0.6rem; }
280 |
281 | h6 {
282 | font-size: 0.2rem;
283 | line-height: 0.4rem; }
284 |
285 | /*========== clearfix ==========*/
286 | .clearfix {
287 | zoom: 1;
288 | display: inline-table; }
289 |
290 | /* Hides from IE-mac \*/
291 | [clearfix]:after,
292 | [clearfix]:before {
293 | content: '';
294 | display: table; }
295 |
296 | [clearfix]:after {
297 | clear: both; }
298 |
299 | * html [clearfix] {
300 | height: 1%; }
301 |
302 | /* End hide from IE-mac */
303 | * + html [clearfix] {
304 | min-height: 1%; }
305 |
306 | /* IE7 */
307 | .clearFd {
308 | clear: both; }
309 |
310 | /*========== form ==========*/
311 | input[type='checkbox'] {
312 | vertical-align: middle; }
313 | input[type='checkbox']:checked {
314 | appearance: none; }
315 |
316 | button,
317 | input[type='button'],
318 | input[type='submit'],
319 | input[type='checkbox'],
320 | input[type='reset'] {
321 | cursor: pointer; }
322 |
323 | input[type='text']:focus,
324 | input[type='password']:focus,
325 | textarea:focus,
326 | input[type='email']:focus,
327 | input[type='number']:focus,
328 | input[type='search']:focus,
329 | button:focus,
330 | input[type='reset']:focus,
331 | input[type='submit']:focus,
332 | input[type='button']:focus {
333 | outline: none; }
334 |
335 | button::-moz-focus-inner,
336 | input::-moz-focus-inner {
337 | padding: 0;
338 | border: 0; }
339 |
340 | textarea {
341 | resize: none;
342 | overflow: auto;
343 | -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
344 | -moz-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
345 | box-shadow: 0 0 0 rgba(0, 0, 0, 0);
346 | -webkit-appearance: none; }
347 |
348 | /* 去除iPhone中默认的input样式 */
349 | input {
350 | resize: none; }
351 |
352 | button {
353 | border: none; }
354 |
355 | button,
356 | input {
357 | outline: none;
358 | appearance: none;
359 | -webkit-appearance: none;
360 | border-radius: 0;
361 | -webkit-tap-highlight-color: transparent;
362 | -webkit-touch-callout: none;
363 | /*长时间按住页面出现闪退*/ }
364 |
365 | /*========== Delete button under Firefox default style ==========*/
366 | input[type='reset']::-moz-focus-inner,
367 | input[type='button']::-moz-focus-inner,
368 | input[type='submit']::-moz-focus-inner,
369 | input[type='file'] > input[type='button']::-moz-focus-inner {
370 | border: none;
371 | padding: 0; }
372 |
373 | .container {
374 | min-height: 100%;
375 | padding: 1.9rem 0 2.7rem; }
376 |
377 | .container-top {
378 | padding-top: 2rem; }
379 |
380 | .container-bottom {
381 | padding-bottom: 2.7rem; }
382 |
383 | [data-layout-flex='0'] {
384 | -webkit-flex: none;
385 | flex: none; }
386 |
387 | [layout],
388 | [data-layout='layout'] {
389 | display: -webkit-flex;
390 | display: flex; }
391 |
392 | [data-layout-wrap],
393 | [flex] {
394 | -webkit-flex: 1;
395 | flex: 1; }
396 |
397 | [data-layout-wrap],
398 | [layout-wrap] {
399 | -webkit-flex-wrap: wrap;
400 | flex-wrap: wrap; }
401 |
402 | [data-layout='row'],
403 | [layout='row'] {
404 | -webkit-flex-direction: row;
405 | flex-direction: row; }
406 |
407 | [data-layout='column'],
408 | [layout='column'] {
409 | -webkit-flex-direction: column;
410 | flex-direction: column; }
411 |
412 | [data-layout-align='start start'],
413 | [data-layout-align='start center'],
414 | [data-layout-align='start end'] [layout-align='start start'],
415 | [layout-align='start center'],
416 | [layout-align='start end'] {
417 | -webkit-justify-content: flex-start;
418 | justify-content: flex-start; }
419 |
420 | [data-layout-align='center start'],
421 | [data-layout-align='center center'],
422 | [data-layout-align='center end'],
423 | [layout-align='center start'],
424 | [layout-align='center center'],
425 | [layout-align='center end'] {
426 | -webkit-justify-content: center;
427 | justify-content: center; }
428 |
429 | [data-layout-align='end start'],
430 | [data-layout-align='end center'],
431 | [data-layout-align='end end'] [layout-align='end start'],
432 | [layout-align='end center'],
433 | [layout-align='end end'] {
434 | -webkit-justify-content: flex-end;
435 | justify-content: flex-end; }
436 |
437 | [data-layout-align='space-between start'],
438 | [data-layout-align='space-between center'],
439 | [data-layout-align='space-between end'],
440 | [layout-align='space-between start'],
441 | [layout-align='space-between center'],
442 | [layout-align='space-between end'] {
443 | -webkit-justify-content: space-between;
444 | justify-content: space-between; }
445 |
446 | [data-layout-align='space-arround start'],
447 | [data-layout-align='space-arround center'],
448 | [data-layout-align='space-arround end'] [layout-align='space-arround start'],
449 | [layout-align='space-arround center'],
450 | [layout-align='space-arround end'] {
451 | -webkit-justify-content: space-arround;
452 | justify-content: space-arround; }
453 |
454 | [data-layout-align='start start'],
455 | [data-layout-align='center start'],
456 | [data-layout-align='end start'],
457 | [data-layout-align='space-between start'],
458 | [data-layout-align='space-arround start'] [layout-align='start start'],
459 | [layout-align='center start'],
460 | [layout-align='end start'],
461 | [layout-align='space-between start'],
462 | [layout-align='space-arround start'] {
463 | -webkit-align-items: flex-start;
464 | align-items: flex-start; }
465 |
466 | [data-layout-align='start center'],
467 | [data-layout-align='center center'],
468 | [data-layout-align='end center'],
469 | [data-layout-align='space-between center'],
470 | [data-layout-align='space-arround center'] [layout-align='start center'],
471 | [layout-align='center center'],
472 | [layout-align='end center'],
473 | [layout-align='space-between center'],
474 | [layout-align='space-arround center'] {
475 | -webkit-align-items: center;
476 | align-items: center; }
477 |
478 | [data-layout-align='start end'],
479 | [data-layout-align='center end'],
480 | [data-layout-align='end end'],
481 | [data-layout-align='space-between end'],
482 | [data-layout-align='space-arround end'] [layout-align='start end'],
483 | [layout-align='center end'],
484 | [layout-align='end end'],
485 | [layout-align='space-between end'],
486 | [layout-align='space-arround end'] {
487 | -webkit-align-items: flex-end;
488 | align-items: flex-end; }
489 |
490 | /* ========== public ========== */
491 | .fl {
492 | display: inline;
493 | float: left; }
494 |
495 | .fr {
496 | display: inline;
497 | float: right; }
498 |
499 | .pr {
500 | position: relative; }
501 |
502 | .pa {
503 | position: absolute; }
504 |
505 | .pf {
506 | position: fixed; }
507 |
508 | .pl {
509 | padding-left: 0.4rem; }
510 |
511 | .t0 {
512 | top: 0; }
513 |
514 | .b0 {
515 | bottom: 0; }
516 |
517 | .l0 {
518 | left: 0; }
519 |
520 | .r0 {
521 | right: 0; }
522 |
523 | .tl0 {
524 | top: 0;
525 | left: 0; }
526 |
527 | .bl0 {
528 | bottom: 0;
529 | left: 0; }
530 |
531 | .lr0 {
532 | right: 0;
533 | left: 0;
534 | margin: auto; }
535 |
536 | .tb0 {
537 | top: 0;
538 | bottom: 0; }
539 |
540 | .z1 {
541 | z-index: 1; }
542 |
543 | .z3 {
544 | z-index: 3; }
545 |
546 | .z5 {
547 | z-index: 5; }
548 |
549 | .wh100 {
550 | width: 100%;
551 | height: 100%; }
552 |
553 | .ilbk,
554 | .icons,
555 | .icon,
556 | .svg,
557 | [ilbk] {
558 | display: -moz-inline-stack;
559 | display: inline-block;
560 | vertical-align: middle;
561 | *vertical-align: auto;
562 | zoom: 1;
563 | *display: inline; }
564 |
565 | .mgt {
566 | margin-top: 0.4rem; }
567 |
568 | .rdir-mgt {
569 | margin-top: -0.4rem; }
570 |
571 | .mgb {
572 | margin-bottom: 0.4rem; }
573 |
574 | .mgl {
575 | margin-left: 0.4rem; }
576 |
577 | .mgr {
578 | margin-right: 0.4rem; }
579 |
580 | .mgtb {
581 | margin: 0.4rem 0; }
582 |
583 | .mglr {
584 | margin: 0 0.4rem; }
585 |
586 | .mgt {
587 | margin-top: 0.4rem; }
588 |
589 | .pd {
590 | padding: 0.4rem; }
591 |
592 | .pdtb {
593 | padding: 0.4rem 0; }
594 |
595 | .pdtr {
596 | padding-top: 0.4rem;
597 | padding-right: 0.4rem; }
598 |
599 | .pdlr {
600 | padding: 0 0.4rem; }
601 |
602 | .pdtl {
603 | padding-top: 0.4rem;
604 | padding-left: 0.4rem; }
605 |
606 | .pdlb {
607 | padding-bottom: 0.4rem;
608 | padding-left: 0.4rem; }
609 |
610 | .pdtbl {
611 | padding: 0.4rem 0 0.4rem 0.4rem; }
612 |
613 | .pdl {
614 | padding-left: 0.4rem; }
615 |
616 | .pdt {
617 | padding-top: 0.4rem; }
618 |
619 | .pdr {
620 | padding-right: 0.4rem; }
621 |
622 | .pdb {
623 | padding-bottom: 0.4rem; }
624 |
625 | .w-half {
626 | width: 50%; }
627 |
628 |
629 | .fz8 {
630 | font-size: 0.8rem; }
631 |
632 | [ellipsis],
633 | [data-ellipsis] {
634 | white-space: nowrap;
635 | overflow: hidden;
636 | -ms-text-overflow: ellipsis;
637 | -o-text-overflow: ellipsis;
638 | text-overflow: ellipsis; }
639 |
640 | .ft-purple {
641 | color: #00f0ff; }
642 |
643 | .w100 {
644 | width: 100%; }
645 |
646 | .h100 {
647 | height: 100%; }
648 |
649 | .ft-bold {
650 | font-weight: 900; }
651 |
652 | .ft-normal {
653 | font-weight: normal; }
654 |
655 | [ft-hidden] {
656 | overflow: hidden;
657 | text-overflow: ellipsis;
658 | display: -webkit-box;
659 | -webkit-box-orient: vertical; }
660 |
661 | .vam {
662 | vertical-align: middle; }
663 |
664 | .img-tc {
665 | max-width: 100%;
666 | margin-left: auto;
667 | margin-right: auto; }
668 |
669 | .dp-flex {
670 | display: -webkit-flex;
671 | display: flex; }
672 |
673 | .bg-transparent {
674 | background-color: rgba(0, 0, 0, 0.5); }
675 |
676 | .bg-color-none {
677 | background-color: transparent; }
678 |
679 | .bdr-half {
680 | -webkit-border-radius: 50%;
681 | -moz-border-radius: 50%;
682 | -ms-border-radius: 50%;
683 | -o-border-radius: 50%;
684 | border-radius: 50%; }
685 |
686 | /* ========== flex ========== */
687 | .numFt {
688 | font-family: 'Georgia'; }
689 |
690 | .bd-radius {
691 | border-radius: .3rem; }
692 |
693 | .bd-none {
694 | border: none; }
695 |
696 | .small-font {
697 | font-size: .6rem; }
698 |
699 | .tab-wrap {
700 | position: fixed;
701 | top: 0;
702 | left: 0;
703 | display: table;
704 | width: 100%;
705 | height: 100%; }
706 |
707 | .tab-wrap-cell {
708 | display: table-cell;
709 | vertical-align: middle; }
710 |
711 | .mask-layer, .mask-page {
712 | position: fixed;
713 | z-index: 11;
714 | top: 0;
715 | left: 0;
716 | width: 100%;
717 | height: 100%;
718 | background-color: #000;
719 | background-color: rgba(0, 0, 0, 0.5); }
720 |
721 | .mask-page {
722 | z-index: 1; }
723 |
724 | #root {
725 | height: 100%; }
726 |
727 | a:hover {
728 | color: #639; }
729 |
730 | .header .head-logo {
731 | padding: 0.6rem 0;
732 | font-size: 0.8rem; }
733 |
734 | .tab-nav-list li,
735 | .footer-menu li {
736 | text-align: center; }
737 |
738 | .tab-nav-list a,
739 | .footer-menu a {
740 | display: block;
741 | padding: 0.4rem 0; }
742 |
743 | .tab-nav-list li {
744 | width: 20%; }
745 |
746 | .nav-top-fixed {
747 | box-shadow: 0 0 5px rgba(144, 155, 165, 0.1); }
748 |
749 | .footer-menu {
750 | width: 100%;
751 | background-color: #fff;
752 | box-shadow: 0 0 10px rgba(144, 155, 165, 0.05); }
753 | .footer-menu li {
754 | width: 25%; }
755 | .footer-menu a {
756 | padding: 0.2rem 0; }
757 | .footer-menu .svg {
758 | width: 1.5rem;
759 | height: 1.5rem; }
760 | .footer-menu .svg-nav-active {
761 | display: none; }
762 | .footer-menu .nav-selected .svg-nav-active {
763 | display: inline-block; }
764 | .footer-menu .nav-selected .svg-nav-default {
765 | display: none; }
766 | .footer-menu .nav-selected p {
767 | color: #639; }
768 | .footer-menu .nav-selected .news-number {
769 | background-color: #639; }
770 | .footer-menu .news-number {
771 | width: 1rem;
772 | height: 1rem;
773 | right: 1.2rem;
774 | top: 0; }
775 |
776 | .photo {
777 | width: 10rem;
778 | height: 10rem; }
779 |
780 | .scroll-wrap {
781 | padding-top: 1.7rem; }
782 |
783 | .iscroll-wrapper.loadmore-wrap {
784 | top: 3.6rem;
785 | bottom: 2.6rem; }
786 |
787 | .loadmore-content {
788 | padding: 1.6rem 0 2.7rem; }
789 |
790 | .article-item {
791 | padding: 0.4rem;
792 | background-color: #fff; }
793 | .author-avatar {
794 | width: 4rem;
795 | height: 4rem; }
796 | .small-avatar-wrap {
797 | width: 2.5rem;
798 | height: 2.5rem; }
799 | .small-author-avatar {
800 | width: 2rem;
801 | height: 2rem; }
802 | .article-item .title-type {
803 | padding: 0 0.2rem; }
804 | .article-item .svg {
805 | width: 1rem;
806 | height: 1rem; }
807 |
808 | .ant-back-top {
809 | width: 2.5rem;
810 | height: 2.5rem;
811 | position: fixed;
812 | bottom: 4rem;
813 | right: 1rem;
814 | border-radius: 50%; }
815 | .ant-back-top .svg-gotop {
816 | width: 1.8rem;
817 | height: 1.8rem; }
818 |
819 | .view-main {
820 | transition: 0.5s; }
821 |
822 | .hideLeft {
823 | opacity: 0.75;
824 | transform: translate3d(-100%, 0, 0);
825 | transition: 0.5s; }
826 |
827 | .hideRight {
828 | opacity: 0.75;
829 | transform: translate3d(100%, 0, 0);
830 | transition: 0.5s; }
831 |
832 | .example-enter {
833 | opacity: 0;
834 | transform: translateX(100%); }
835 |
836 | .example-enter.example-enter-active {
837 | opacity: 1;
838 | transform: translateX(0);
839 | transition: all 0.5s ease-in; }
840 |
841 | .example-exit {
842 | opacity: 1;
843 | transform: translateX(0); }
844 |
845 | .example-exit.example-exit-active {
846 | opacity: 0;
847 | transform: translateX(-100%);
848 | transition: all 0.3s ease-out; }
849 |
850 | .example-appear {
851 | opacity: 0.01; }
852 |
853 | .example-appear.example-appear-active {
854 | opacity: 1;
855 | transition: opacity 0.5s ease-in; }
856 |
857 | .header-bar .svg-goback {
858 | padding: 0.3rem;
859 | width: 2rem;
860 | height: 2rem; }
861 |
862 | .header-bar .header-bar-title {
863 | padding: 0.3rem; }
864 |
865 | .header-bar .header-bar-antimg {
866 | margin-left: -2rem; }
867 |
868 | .article-operat .svg {
869 | width: 1rem;
870 | height: 1rem; }
871 |
872 | .markdown-body {
873 | font-size: 14px;
874 | word-break: break-all;
875 | overflow: hidden; }
876 | .markdown-body h1,
877 | .markdown-body h2 {
878 | margin: 0; }
879 | .markdown-body h1 {
880 | font-size: 1.8em; }
881 | .markdown-body img {
882 | max-width: 100%; }
883 |
884 | .svg-comment,
885 | .svg-thumbs,
886 | [data-svg-wh] {
887 | width: 1rem;
888 | height: 1rem; }
889 |
890 | .comment-title {
891 | padding: 0.4rem 0; }
892 |
893 | .article-comment-list li {
894 | padding: 0.4rem;
895 | border-bottom: 1px dashed #f3f3f3; }
896 |
897 | .article-comment-list .user-thumbs {
898 | margin-right: 1rem; }
899 |
900 | .article-comment-list .user-comment-cnt {
901 | overflow: hidden; }
902 |
903 | .fixed-bottom-wrap {
904 | height: 2.5rem;
905 | box-shadow: 0 0 10px rgba(144, 155, 165, 0.05); }
906 |
907 | .room-speak button,
908 | .room-speak input {
909 | border: none; }
910 |
911 | .room-speak .ipt {
912 | height: 2.5rem;
913 | padding-left: 1rem; }
914 |
915 | .room-speak .ipt-txt-wrap:after, .room-speak .ipt-txt-wrap:before {
916 | content: '';
917 | position: absolute;
918 | top: 0;
919 | bottom: 0;
920 | margin: auto;
921 | width: 1px;
922 | height: 1.5rem; }
923 |
924 | .room-speak .ipt-txt-wrap:after {
925 | right: 0; }
926 |
927 | .room-speak .ipt-txt-wrap:before {
928 | left: 0.4rem; }
929 |
930 | .room-speak .btn-speak-submit {
931 | height: 2.5rem;
932 | color: #00f0ff; }
933 |
934 | .room-speak .tm-switch-wrap {
935 | width: 22%; }
936 |
937 | .room-speak .speak-submit-wrap {
938 | width: 15%; }
939 |
940 | .not-login-wrap a {
941 | margin-left: 0.4rem;
942 | padding: 0.2rem 0.4rem;
943 | border: 1px solid #639;
944 | font-size: 0.8rem;
945 | border-radius: 0.3rem; }
946 |
947 | .not-login-photo {
948 | width: 30%;
949 | margin: 1rem 0; }
950 |
951 | .user-login-form {
952 | width: 80%;
953 | margin: 60% auto 0; }
954 | .user-login-form input {
955 | width: 100%;
956 | height: 2rem; }
957 | .user-login-form .ipt-row {
958 | margin-top: 10%; }
959 |
960 | .plw {
961 | width: 80%;
962 | margin: 0 auto; }
963 |
964 | .public-button {
965 | height: 2rem; }
966 |
967 | .issue-content-wrap input[type='text'],
968 | .issue-content-wrap textarea {
969 | border-bottom: 1px solid #b6b6b6; }
970 |
971 | .issue-content-wrap textarea {
972 | height: 8rem; }
973 |
974 | .radio-wrap > span {
975 | position: relative;
976 | width: 1rem;
977 | height: 1rem;
978 | border: 1px solid #b6b6b6;
979 | border-radius: 50%; }
980 |
981 | .issue-content-wrap input[type='radio'] {
982 | opacity: 0; }
983 |
984 | .issue-content-wrap .select-radio-active > span:before {
985 | content: '';
986 | width: 0.6rem;
987 | height: 0.6rem;
988 | border-radius: 50%;
989 | background: #639; }
990 |
991 | .issue-content-wrap .theme-issue-submit {
992 | margin-top: 0.8rem; }
993 |
994 | .user-center-info {
995 | background-image: linear-gradient(180deg, #7b57bb, #639); }
996 | .user-center-info .svg {
997 | width: 1rem;
998 | height: 1rem;
999 | margin-right: 0.2rem; }
1000 | .user-center-info .user-info-row > p {
1001 | margin-top: 0.2rem; }
1002 |
1003 | .news-content .ant-tabs-tab {
1004 | width: 50%; }
1005 |
1006 | .user-center .ant-tabs-tab {
1007 | width: 33.33%; }
1008 |
1009 | .clear-unread-msg {
1010 | padding: 0.5rem 0.6rem; }
1011 |
1012 | .ant-tabs-nav {
1013 | width: 100%;
1014 | background-color: #fff; }
1015 | .ant-tabs-nav div.ant-tabs-tab {
1016 | padding: 0.6rem 0;
1017 | text-align: center;
1018 | margin-right: 0; }
1019 | .ant-tabs-nav .ant-tabs-tab-active,
1020 | .ant-tabs-nav .ant-tabs-tab:hover {
1021 | color: #639; }
1022 | div.ant-tabs-bar {
1023 | margin-bottom: 0; }
1024 | .ant-tabs-nav .ant-tabs-ink-bar {
1025 | background-color: #639; }
1026 |
1027 | .title-cnt {
1028 | width: 12rem; }
1029 |
1030 | .form-msg {
1031 | position: fixed;
1032 | z-index: 5;
1033 | left: 0;
1034 | right: 0;
1035 | top: 50%;
1036 | margin: auto;
1037 | padding: 0.6rem 0;
1038 | max-width: 10rem;
1039 | width: 50%;
1040 | background-color: rgba(0, 0, 0, 0.3);
1041 | font-size: 0.8rem;
1042 | -webkit-border-radius: 0.2rem;
1043 | -moz-border-radius: 0.2rem;
1044 | -ms-border-radius: 0.2rem;
1045 | -o-border-radius: 0.2rem;
1046 | border-radius: 0.2rem;
1047 | -webkit-transform: translateY(-50%);
1048 | -moz-transform: translateY(-50%);
1049 | -ms-transform: translateY(-50%);
1050 | -o-transform: translateY(-50%);
1051 | transform: translateY(-50%); }
1052 |
1053 | .confirm-popups {
1054 | width: 70%;
1055 | margin: 0 auto;
1056 | -webkit-border-radius: 0.2rem;
1057 | -moz-border-radius: 0.2rem;
1058 | -ms-border-radius: 0.2rem;
1059 | -o-border-radius: 0.2rem;
1060 | border-radius: 0.2rem; }
1061 | .confirm-popups .confirm-title {
1062 | padding: 1.5rem 0;
1063 | border-bottom: 1px solid #909ba5; }
1064 | .confirm-popups .confirm-btn button {
1065 | width: 50%;
1066 | padding: 0.8rem 0;
1067 | border: none;
1068 | background-color: transparent; }
1069 | .confirm-popups .confirm-btn .confirm-btn-no {
1070 | border-right: 1px solid #909ba5; }
1071 |
1072 | .confirm-report-cnt {
1073 | width: 90%;
1074 | height: 4rem;
1075 | padding: 0.4rem;
1076 | -webkit-border-radius: 0.2rem;
1077 | -moz-border-radius: 0.2rem;
1078 | -ms-border-radius: 0.2rem;
1079 | -o-border-radius: 0.2rem;
1080 | border-radius: 0.2rem; }
1081 |
--------------------------------------------------------------------------------
/src/component/ArticleItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import LazyImage from '@component/common/LazyImage'
4 |
5 | import { formatDate } from '@script/utils'
6 |
7 | import '@fonts/svg/write.svg'
8 | import '@fonts/svg/page-views.svg'
9 |
10 | const TitleType = props => (
11 | {props.type}
12 | )
13 | TitleType.defaultProps = {
14 | color: 'bg-sgrey'
15 | }
16 |
17 | const ArticleItem = props => {
18 | const item = props.info
19 | return (
20 |
23 | {props.isLinks ? (
24 |
33 |
34 |
35 | ) : (
36 |
37 | )}
38 |
39 | )
40 | }
41 |
42 | ArticleItem.defaultProps = {
43 | isLinks: true
44 | }
45 |
46 | const TopicItem = props => {
47 | const item = props.info
48 | return (
49 |
50 | {props.isLazyImg ? (
51 |
57 | ) : (
58 |

64 | )}
65 |
66 |
67 | {/* */}
77 | {(() => {
78 | if (item.top) {
79 | return
80 | }
81 | if (item.good) {
82 | return
83 | }
84 | switch (item.tab) {
85 | case 'share':
86 | return
87 | case 'good':
88 | return
89 | case 'ask':
90 | return
91 | case 'job':
92 | return
93 | case 'dev':
94 | return
95 | default:
96 | return ''
97 | }
98 | })()}
99 | {item.title}
100 |
101 |
106 |
107 |
110 | {item.author.loginname}
111 |
112 | {formatDate(item.create_at)}
113 |
114 |
119 |
120 |
123 | {item.reply_count}/{item.visit_count}
124 |
125 | {formatDate(item.last_reply_at)}
126 |
127 |
128 |
129 | )
130 | }
131 |
132 | TopicItem.defaultProps = {
133 | isLazyImg: true
134 | }
135 |
136 | export default ArticleItem
137 |
--------------------------------------------------------------------------------
/src/component/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | export default () => (
3 |
10 | )
11 |
--------------------------------------------------------------------------------
/src/component/Menu.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { NavLink, withRouter } from 'react-router-dom'
3 | import { bindActionCreators } from 'redux'
4 | import { connect } from 'react-redux'
5 |
6 | import ActionNews from '@store/actions/ActionNews'
7 |
8 | import '@fonts/svg/nall.svg'
9 | import '@fonts/svg/nask.svg'
10 | import '@fonts/svg/njob.svg'
11 | import '@fonts/svg/ngood.svg'
12 | import '@fonts/svg/nshare.svg'
13 | import '@fonts/svg/nhome.svg'
14 | import '@fonts/svg/nnews.svg'
15 | import '@fonts/svg/nissue.svg'
16 | import '@fonts/svg/nuser.svg'
17 |
18 | class TabFooNav extends React.Component {
19 | constructor(props) {
20 | super(props)
21 | this.state = {
22 | num: 0
23 | }
24 | this.isLogin = this.$getStorage()
25 | }
26 | /* componentWillReceiveProps(prevProps, prevState) {
27 | const {result} = this.props.ReadNews;
28 | result && result.success && this.setState({num: result.data});
29 | } */
30 | componentDidMount() {
31 | this.isLogin && this.props.actions.fetchData(this.isLogin.accesstoken)
32 | }
33 | render() {
34 | const { result } = this.props.ReadNews
35 | const tabmenu = this.props.tabnav.map((item, index) => {
36 | const isNewsTips = this.isLogin && item.type === 'news'
37 | return (
38 |
39 | {
40 |
49 |
52 |
55 | {isNewsTips && (
56 |
61 | {result && result.success ? result.data : 0}
62 |
63 | )}
64 | {item.name}
65 |
66 | }
67 |
68 | )
69 | })
70 | return (
71 |
74 | )
75 | }
76 | }
77 |
78 | class Menu extends React.Component {
79 | componentWillReceiveProps(nextProps) {}
80 | render() {
81 | const getLogin = this.$getStorage()
82 | // console.log(this.props.rTopicList.topicType)
83 | const meLink = getLogin && getLogin.loginname ? `/user` : '/user/login'
84 | const tabnav = [
85 | {
86 | name: '首页',
87 | type: 'home',
88 | link: '/',
89 | search: `?tab=${this.props.rTopicList.topicType}`
90 | },
91 | {
92 | name: '消息',
93 | type: 'news',
94 | link: '/news'
95 | },
96 | {
97 | name: '发表',
98 | type: 'issue',
99 | link: '/share'
100 | },
101 | {
102 | name: '我的',
103 | type: 'user',
104 | link: meLink,
105 | search: getLogin ? `?name=${getLogin.loginname}` : ''
106 | }
107 | ]
108 | return (
109 |
110 |
111 |
112 | )
113 | }
114 | }
115 |
116 | const mapStateToProps = state => {
117 | return { ReadNews: state.ReadNews, rTopicList: state.rTopicList }
118 | }
119 | const actions = {
120 | ...ActionNews
121 | }
122 | const mapDispatchToProps = dispatch => ({
123 | actions: bindActionCreators(actions, dispatch)
124 | })
125 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Menu))
126 |
--------------------------------------------------------------------------------
/src/component/SubHeader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route } from 'react-router-dom'
3 | import '@fonts/svg/goback.svg'
4 | const SubHeader = props => (
5 | {
7 | const arrPathName = ['/share', '/', '/news', '/user']
8 | const isDefaultPath = arrPathName.includes(location.pathname)
9 | return (
10 |
15 | {(!isDefaultPath || props.isBack) && (
16 |
19 | )}
20 | {props.children}
21 |
22 |
23 | {props.title}
24 |
25 |
26 |
27 | )
28 | }}
29 | />
30 | )
31 | SubHeader.defaultProps = {
32 | title: '默认标题',
33 | isBack: false
34 | }
35 | export default SubHeader
36 |
--------------------------------------------------------------------------------
/src/component/common/LazyImage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Image from 'react-lazy-image'
3 | export default props => (
4 |
9 | )
10 |
--------------------------------------------------------------------------------
/src/component/common/MaskPopups.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | export default class MaskPopups extends React.Component {
3 | constructor(props) {
4 | super(props)
5 | this.state = {
6 | isPopups: false
7 | }
8 | }
9 | deterMine() {
10 | this.props.SendDeterMine()
11 | }
12 | cancelRun() {
13 | this.props.SendCancelRun()
14 | }
15 | render() {
16 | return (
17 |
18 |
19 |
20 |
21 |
{this.props.title}
22 |
27 |
33 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 | }
47 |
48 | MaskPopups.defaultProps = {
49 | title: '默认弹窗标题',
50 | SendCancelRun: () => {},
51 | SendDeterMine: () => {}
52 | }
53 |
--------------------------------------------------------------------------------
/src/component/common/SharedCompt.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | // import PropTypes from "prop-types";
4 |
5 | import { setStorage } from '@script/utils'
6 |
7 | export class UserLogin extends React.Component {
8 | // static contextTypes = { router: PropTypes.object }
9 | constructor(props) {
10 | super(props)
11 | this.state = {
12 | btnval: '登录'
13 | }
14 | this.loginSubmit = this.loginSubmit.bind(this)
15 | }
16 | enterSumit(e) {
17 | Object.is(e.keyCode, 13) && this.loginSubmit()
18 | }
19 | loginSubmit() {
20 | const UserTokenVal = this.refs.userToken.value
21 | if (!UserTokenVal) {
22 | return this.$showMsg('亲,token不能为空!')
23 | }
24 | this.setState({ btnval: '登录中...' })
25 | this.$request({
26 | method: 'post',
27 | url: `accesstoken`,
28 | data: {
29 | accesstoken: UserTokenVal
30 | },
31 | success: result => {
32 | if (result.success) {
33 | this.$showMsg('登录成功!')
34 | result['accesstoken'] = UserTokenVal
35 | setStorage('USER_INFO', result)
36 | this.props.history.push({
37 | pathname: '/user',
38 | search: `?name=${result.loginname}`
39 | })
40 | } else {
41 | this.$showMsg('登录失败!')
42 | this.setState({ btnval: '登录' })
43 | }
44 | },
45 | error: () => {
46 | this.$showMsg('登录失败!')
47 | this.setState({ btnval: '登录' })
48 | }
49 | })
50 | }
51 | render() {
52 | return (
53 |
74 | )
75 | }
76 | }
77 |
78 | export const NotLogin = props => {
79 | return (
80 |
81 | {props.isLogo && (
82 |
})
87 | )}
88 |
89 | 还未登录,现在就去
90 | 登录
91 |
92 |
93 |
94 | )
95 | }
96 |
97 | NotLogin.defaultProps = {
98 | isLogo: true
99 | }
100 |
101 | export const DetectLogin = props => {
102 | class LoginStatus extends React.Component {
103 | // constructor(props) {
104 | // super(props)
105 | // }
106 | render() {
107 | if (this.$getStorage()) return
108 | return
109 | }
110 | }
111 | return LoginStatus
112 | }
113 |
114 | export const LoadLoop = () => 玩命加载中...
115 | export const LoadFail = () => 加载失败!
116 | export const NotResult = props => {props.text}
117 |
118 | NotResult.defaultProps = {
119 | text: '暂无数据!'
120 | }
121 |
--------------------------------------------------------------------------------
/src/component/plugin/touch-loadmore/LoadMore.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import Tloader from './react-touch-loader'
4 | import ArticleItem from '@component/ArticleItem'
5 | import aHomeTopic from '@store/actions/ActionTopicList'
6 | import './load-more.less'
7 | class LoadMore extends React.Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | canRefreshResolve: 1,
12 | hasMore: 1,
13 | infoList: props.rTopicList.result,
14 | isLoadData: true,
15 | initializing: props.rTopicList.result.length ? 2 : 1,
16 | refreshedAt: Date.now()
17 | }
18 | this.refScroll = null
19 | }
20 | componentWillReceiveProps(nextProps) {
21 | !Object.is(nextProps.type, this.props.type) &&
22 | this.setState(
23 | {
24 | // infoList: [],
25 | hasMore: 1
26 | },
27 | () => {
28 | this.refresh()
29 | }
30 | )
31 | }
32 | componentDidMount() {
33 | const { page, scrollTop } = this.props.rTopicList
34 | !page && this.loadData()
35 | !this.refScroll && (this.refScroll = this.refs.scrollCnt.refs.panel)
36 | scrollTop && (this.refScroll.scrollTop = scrollTop)
37 | }
38 | refresh(resolve, reject) {
39 | this.setState(
40 | {
41 | hasMore: 1
42 | },
43 | () => {
44 | this.loadData(resolve, 'reset')
45 | }
46 | )
47 | }
48 | componentWillUnmount() {
49 | const { dispatch } = this.props
50 | dispatch(aHomeTopic.recordScrollSite(this.refScroll.scrollTop)) //保存滚动条位置
51 | }
52 |
53 | loadData(resolve, reset) {
54 | //加载数据
55 | const { infoList } = this.state
56 | const isReset = reset === 'reset'
57 | const page = isReset ? 1 : Math.ceil(infoList.length / 10) + 1
58 | this.$request({
59 | url: 'topics',
60 | params: {
61 | page,
62 | tab: this.props.type,
63 | limit: 10
64 | },
65 | success: result => {
66 | if (this.state.isLoadData) {
67 | const { data } = result
68 | this.setState({
69 | infoList: isReset ? data : infoList.concat(data),
70 | hasMore: 1,
71 | initializing: 2,
72 | // initialized
73 | loadState: 'loaded'
74 | })
75 | const { dispatch, rTopicList, type } = this.props
76 | ;(!Object.is(rTopicList.page, page) || isReset) &&
77 | dispatch(
78 | aHomeTopic.keepTopic({
79 | data,
80 | page,
81 | type,
82 | isReset
83 | })
84 | )
85 | if (isReset) {
86 | //不同类型 or 刷新重置滚动条
87 | window.scrollTo(0, 0)
88 | this.refScroll.scrollTop = 0
89 | }
90 | // if (Math.ceil(infoList.length / 10) === 2) {
91 | if (data.length < 10) {
92 | this.setState({ hasMore: false })
93 | }
94 | resolve && resolve()
95 | }
96 | }
97 | })
98 | }
99 | render() {
100 | const { hasMore, initializing } = this.state
101 | const { infoList } = this.state
102 | return (
103 |
104 |
112 |
113 | {infoList.map((item, index) => (
114 |
120 | ))}
121 |
122 |
123 |
124 | )
125 | }
126 | }
127 |
128 | export default connect((state, action) => {
129 | return {
130 | rTopicList: state.rTopicList
131 | }
132 | })(LoadMore)
133 |
--------------------------------------------------------------------------------
/src/component/plugin/touch-loadmore/load-more.less:
--------------------------------------------------------------------------------
1 | :not(input):not(textarea):not([contenteditable]) {
2 | -webkit-touch-callout: none;
3 | user-select: none;
4 | }
5 |
6 | .load-more-view {
7 | position: absolute;
8 | top: 10%;
9 | left: 0;
10 | right: 0;
11 | height: 100%;
12 | overflow: hidden;
13 | background-color: #EFEFF4;
14 | display: flex;
15 | flex-direction: column; // header, footer
16 | h1,
17 | h2 {
18 | margin: 0;
19 | background-color: #333;
20 | color: #eee;
21 | font-size: 1em;
22 | line-height: 3;
23 | a {
24 | color: inherit;
25 | margin: 0 1em;
26 | }
27 | }
28 | .main {
29 | flex: 1;
30 | overflow-x: hidden;
31 | overflow-y: scroll;
32 | -webkit-overflow-scrolling: touch; // enhance ios scrolling
33 | ul {
34 | list-style: none;
35 | margin: 0;
36 | padding: 0;
37 | li {
38 | background-color: #fff;
39 | border-bottom: 1px solid #ccc;
40 | p {
41 | margin: 0;
42 | line-height: 3em;
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/component/plugin/touch-loadmore/react-touch-loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './touch-loader.less'
3 |
4 | const STATS = {
5 | init: '',
6 | pulling: 'pulling',
7 | enough: 'pulling enough',
8 | refreshing: 'refreshing',
9 | refreshed: 'refreshed',
10 | reset: 'reset',
11 |
12 | loading: 'loading' // loading more
13 | }
14 |
15 | // pull to refresh
16 | // tap bottom to load more
17 | class Tloader extends React.Component {
18 | constructor() {
19 | super()
20 | this.state = {
21 | loaderState: STATS.init,
22 | pullHeight: 0,
23 | progressed: 0
24 | }
25 | this.loadMore = this.loadMore.bind(this)
26 | }
27 |
28 | setInitialTouch(touch) {
29 | this._initialTouch = {
30 | clientY: touch.clientY
31 | }
32 | }
33 | calculateDistance(touch) {
34 | return touch.clientY - this._initialTouch.clientY
35 | }
36 | // 拖拽的缓动公式 - easeOutSine
37 | easing(distance) {
38 | // t: current time, b: begInnIng value, c: change In value, d: duration
39 | var t = distance
40 | var b = 0
41 | var d = window.screen.availHeight // 允许拖拽的最大距离
42 | var c = d / 2.5 // 提示标签最大有效拖拽距离
43 |
44 | return c * Math.sin(t / d * (Math.PI / 2)) + b
45 | }
46 | canRefresh() {
47 | return (
48 | this.props.onRefresh &&
49 | [STATS.refreshing, STATS.loading].indexOf(this.state.loaderState) < 0
50 | )
51 | }
52 |
53 | touchStart(e) {
54 | if (!this.canRefresh()) return
55 | if (e.touches.length === 1)
56 | this._initialTouch = {
57 | clientY: e.touches[0].clientY,
58 | scrollTop: this.refs.panel.scrollTop
59 | }
60 | }
61 | touchMove(e) {
62 | if (!this.canRefresh()) return
63 | var scrollTop = this.refs.panel.scrollTop
64 | var distance = this.calculateDistance(e.touches[0])
65 |
66 | if (distance > 0 && scrollTop <= 0) {
67 | var pullDistance = distance - this._initialTouch.scrollTop
68 | if (pullDistance < 0) {
69 | // 修复 webview 滚动过程中 touchstart 时计算panel.scrollTop不准
70 | pullDistance = 0
71 | this._initialTouch.scrollTop = distance
72 | }
73 | var pullHeight = this.easing(pullDistance)
74 | if (pullHeight) e.preventDefault() // 减弱滚动
75 |
76 | this.setState({
77 | loaderState:
78 | pullHeight > this.props.distanceToRefresh
79 | ? STATS.enough
80 | : STATS.pulling,
81 | pullHeight: pullHeight
82 | })
83 | }
84 | }
85 | touchEnd() {
86 | if (!this.canRefresh()) return
87 | var endState = {
88 | loaderState: STATS.reset,
89 | pullHeight: 0
90 | }
91 |
92 | if (this.state.loaderState === STATS.enough) {
93 | // refreshing
94 | this.setState({
95 | loaderState: STATS.refreshing,
96 | pullHeight: 0
97 | })
98 |
99 | // trigger refresh action
100 | this.props.onRefresh(
101 | () => {
102 | // resolve
103 | this.setState({
104 | loaderState: STATS.refreshed,
105 | pullHeight: 0
106 | })
107 | },
108 | () => {
109 | // reject
110 | this.setState(endState) // reset
111 | }
112 | )
113 | } else this.setState(endState) // reset
114 | }
115 |
116 | loadMore() {
117 | this.setState({
118 | loaderState: STATS.loading
119 | })
120 | this.props.onLoadMore(() => {
121 | // resolve
122 | this.setState({
123 | loaderState: STATS.init
124 | })
125 | })
126 | }
127 | onScroll(e) {
128 | if (
129 | this.props.autoLoadMore &&
130 | this.props.hasMore &&
131 | this.state.loaderState !== STATS.loading
132 | ) {
133 | let panel = e.currentTarget
134 | let scrollBottom =
135 | panel.scrollHeight - panel.clientHeight - panel.scrollTop
136 |
137 | if (scrollBottom < 5) this.loadMore()
138 | }
139 | }
140 |
141 | componentWillReceiveProps(nextProps) {
142 | if (nextProps.initializing < 2)
143 | this.setState({
144 | progressed: 0 // reset progress animation state
145 | })
146 | }
147 | animationEnd() {
148 | var newState = {}
149 |
150 | if (this.state.loaderState === STATS.refreshed)
151 | newState.loaderState = STATS.init
152 | if (this.props.initializing > 1) newState.progressed = 1
153 |
154 | this.setState(newState)
155 | }
156 | render() {
157 | const { className, hasMore, initializing } = this.props
158 | const { loaderState, pullHeight, progressed } = this.state
159 |
160 | var footer = hasMore ? (
161 |
162 |
163 |
164 |
165 |
166 |
167 | ) : null
168 |
169 | var style = pullHeight
170 | ? {
171 | WebkitTransform: `translate3d(0,${pullHeight}px,0)`
172 | }
173 | : null
174 |
175 | var progressClassName = ''
176 | if (!progressed) {
177 | if (initializing > 0) progressClassName += ' tloader-progress'
178 | if (initializing > 1) progressClassName += ' ed'
179 | }
180 |
181 | return (
182 | this.onScroll(e)}
186 | onTouchStart={e => this.touchStart(e)}
187 | onTouchMove={e => this.touchMove(e)}
188 | onTouchEnd={e => this.touchEnd(e)}
189 | onAnimationEnd={e => this.animationEnd(e)}
190 | >
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | {this.props.children}
201 |
202 | {footer}
203 |
204 | )
205 | }
206 | }
207 |
208 | Tloader.defaultProps = {
209 | distanceToRefresh: 60,
210 | autoLoadMore: 1
211 | }
212 |
213 | export default Tloader
214 |
--------------------------------------------------------------------------------
/src/component/plugin/touch-loadmore/touch-loader.less:
--------------------------------------------------------------------------------
1 | @bg-dark: #EFEFF4;
2 | @progress-color: #08BF06;
3 |
4 | @height: 3rem;
5 | @fontSize: 11px;
6 | @fontColor: darken(@bg-dark, 40%);// state hint
7 | @btnColor: darken(@bg-dark, 60%);// load more
8 |
9 | @pullingMsg: '下拉刷新';
10 | @pullingEnoughMsg: '松开刷新';
11 | @refreshingMsg: '正在刷新...';
12 | @refreshedMsg: '刷新成功';
13 | @loadingMsg: '正在加载...';
14 | @btnLoadMore: '点击加载更多';
15 | @transition-duration: .2s;
16 |
17 | .tloader-msg:after{
18 | content: @pullingMsg;
19 |
20 | .state-pulling.enough &{
21 | content: @pullingEnoughMsg;
22 | }
23 |
24 | .state-refreshed &{
25 | content: @refreshedMsg;
26 | }
27 | }
28 | .tloader-loading:after{
29 | content: @loadingMsg;
30 |
31 | .tloader-symbol &{
32 | content: @refreshingMsg;
33 | }
34 | }
35 | .tloader-btn:after{
36 | content: @btnLoadMore;
37 | }
38 |
39 |
40 | .tloader{
41 | position: relative;
42 |
43 | &.state-pulling{
44 | overflow-y: hidden;// 拖拽时临时阻止ios的overscroll
45 | }
46 | }
47 |
48 | // pull to refresh
49 | .tloader-symbol{
50 | position: absolute;
51 | top: 0;
52 | left: 0;
53 | right: 0;
54 | color: @fontColor;
55 | text-align: center;
56 | height: @height;
57 | overflow: hidden;
58 |
59 | // 隐藏刷新提示标签
60 | .state- &, .state-reset &{
61 | height: 0;
62 | }
63 | // 延迟至reset完成,隐藏刷新提示标签
64 | .state-reset &{
65 | transition: height 0s @transition-duration;
66 | }
67 |
68 | // hide when loading more
69 | .state-loading &{
70 | display: none;
71 | }
72 | }
73 |
74 | // 拖拽提示信息
75 | .tloader-msg{
76 | line-height: @height;
77 | font-size: @fontSize;
78 |
79 | i{
80 | // arrow down icon
81 | .state-pulling &, .state-reset &{
82 | display: inline-block;
83 | font-size: 2em;
84 | margin-right: .6em;
85 | vertical-align: middle;
86 | height: 1em;
87 | border-left: 1px solid;
88 | position: relative;
89 | transition: transform .3s ease;
90 |
91 | &:before, &:after{
92 | content: '';
93 | position: absolute;
94 | font-size: .5em;
95 | width: 1em;
96 | bottom: 0px;
97 | border-top: 1px solid;
98 | }
99 | &:before{
100 | right: 1px;
101 | transform: rotate(50deg);
102 | transform-origin: right;
103 | }
104 | &:after{
105 | left: 0px;
106 | transform: rotate(-50deg);
107 | transform-origin: left;
108 | }
109 | }
110 |
111 | // arrow up
112 | .state-pulling.enough &{
113 | transform: rotate(180deg);
114 | }
115 | }
116 |
117 | // 刷新中,隐藏提示信息 以切换至loading动画
118 | .state-refreshing &{
119 | height: 0;
120 | opacity: 0;
121 | }
122 | // 刷新成功提示消息
123 | .state-refreshed &{
124 | opacity: 1;
125 | transition: opacity 1s;
126 |
127 | // √ icon
128 | i{
129 | display: inline-block;
130 | box-sizing: content-box;
131 | vertical-align: middle;
132 | margin-right: 10px;
133 | font-size: 20px;
134 | height: 1em;
135 | width: 1em;
136 | border: 1px solid;
137 | border-radius: 100%;
138 | position: relative;
139 |
140 | &:before{
141 | content: '';
142 | position: absolute;
143 | top: 3px;
144 | left: 7px;
145 | height: 11px;
146 | width: 5px;
147 | border: solid;
148 | border-width: 0 1px 1px 0;
149 | transform: rotate(40deg);
150 | }
151 | }
152 | }
153 | }
154 |
155 | .tloader-body{
156 | // active the scrollbar of ios
157 | margin-top: -1px;
158 | padding-top: 1px;
159 |
160 | .state-refreshing &{
161 | transform: translate3d(0,@height,0);
162 | transition: transform @transition-duration;
163 | }
164 |
165 | .state-refreshed &{
166 | // handle resolve within .3s
167 | animation: refreshed @transition-duration*2;
168 | }
169 |
170 | .state-reset &{
171 | transition: transform @transition-duration;
172 | }
173 | }
174 | @keyframes refreshed {
175 | 0%{transform: translate3d(0,@height,0);}
176 | 50%{transform: translate3d(0,@height,0);}
177 | }
178 |
179 | // touch to load more
180 | .tloader-footer{
181 | .state-refreshing &{
182 | display: none;
183 | }
184 |
185 | .tloader-btn{
186 | color: @btnColor;
187 | font-size: .9em;
188 | text-align: center;
189 | line-height: 3rem;
190 |
191 | .state-loading &{
192 | display: none;
193 | }
194 | }
195 | }
196 |
197 | .tloader-loading{
198 | display: none;
199 | text-align: center;
200 | line-height: @height;
201 | font-size: @fontSize;
202 | color: @fontColor;
203 |
204 | .ui-loading{
205 | font-size: 20px;
206 | margin-right: .6rem;
207 | }
208 |
209 | .state-refreshing .tloader-symbol &, .state-loading .tloader-footer &{
210 | display: block;
211 | }
212 | }
213 |
214 | // loading效果
215 | @keyframes circle {
216 | 100% { transform: rotate(360deg); }
217 | }
218 | .ui-loading{
219 | display: inline-block;
220 | vertical-align: middle;
221 | font-size: 1.5rem;
222 | width: 1em;
223 | height: 1em;
224 | border: 2px solid darken(@bg-dark, 30%);
225 | border-top-color: #fff;
226 | border-radius: 100%;
227 | animation: circle .8s infinite linear;
228 |
229 | #ui-waiting &{
230 | border: 2px solid #fff;
231 | border-top-color: darken(@bg-dark, 30%);
232 | }
233 | }
234 |
235 | // 进度条加载效果
236 | @keyframes tloader-progressing {
237 | 0% { width: 0; }
238 | 10%{ width: 40%; }
239 | 20%{ width: 75%; }
240 | 30%{ width: 95%; }
241 | }
242 | @keyframes tloader-progressed {
243 | 0% {
244 | opacity: 1;
245 | }
246 | }
247 | .tloader-progress {
248 | position: relative;
249 |
250 | &:before{
251 | content: "";
252 | z-index: 1000;
253 | position: absolute;
254 | top: 0;
255 | left: 0;
256 | height: 2px;
257 | background-color: @progress-color;
258 | width: 99%;
259 | animation: tloader-progressing 9s ease-out;
260 |
261 | .ed&{
262 | opacity: 0;
263 | width: 100%;
264 | animation: tloader-progressed 1s;
265 | }
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Router, HashRouter } from 'react-router-dom'
4 | import { createStore, applyMiddleware } from 'redux'
5 | import { Provider } from 'react-redux'
6 | import thunk from 'redux-thunk'
7 |
8 | import reducers from '@store/reducer/ReducerRoot'
9 | import history from '@script/history'
10 | import '@script/firstload'
11 | import '@script/reuse'
12 | import * as serviceWorker from './serviceWorker'
13 |
14 | import App from '@view/App'
15 | import '@assets/sass/global.scss'
16 |
17 | const store = createStore(reducers, applyMiddleware(thunk))
18 | //Router 发布模式刷新会找不到页面,需配置服务端
19 | const ModeRouter = ({ children }) => {
20 | return Object.is(process.env.NODE_ENV, 'development') ? (
21 | {children}
22 | ) : (
23 | {children}
24 | )
25 | }
26 |
27 | render(
28 |
29 |
30 |
31 |
32 | ,
33 | document.getElementById('root')
34 | )
35 | serviceWorker.unregister()
36 |
--------------------------------------------------------------------------------
/src/script/firstload.js:
--------------------------------------------------------------------------------
1 | ({
2 | el: document.documentElement,
3 | init() {
4 | this.fontAdapt()
5 | window.onresize = () => this.fontAdapt()
6 | },
7 | fontAdapt() {
8 | const w = this.el.offsetWidth
9 | const fz = w / 20
10 | this.el.style.fontSize = (fz < 20 ? fz : 20) + 'px'
11 | }
12 | }.init())
13 |
--------------------------------------------------------------------------------
/src/script/history.js:
--------------------------------------------------------------------------------
1 | // import createHistory from 'history/createBrowserHistory'
2 | // export default createHistory()
3 | import { createBrowserHistory } from 'history'
4 | export default createBrowserHistory()
5 |
--------------------------------------------------------------------------------
/src/script/reuse.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import axios from 'axios'
3 | import { getStorage, showMsg } from '@script/utils'
4 | const isDev = Object.is(process.env.NODE_ENV, 'development')
5 | const apiUrl = 'https://cnodejs.org/api/v1/'
6 | /**
7 | * 公共请求数据
8 | * @param {Object} options
9 | * @returns
10 | */
11 | export const request = options => {
12 | options.method = options.method || 'get'
13 | const sendData = Object.is(options.method.toLowerCase(), 'post')
14 | ? {
15 | data: options.data
16 | }
17 | : {}
18 | return new Promise((resolve, reject) => {
19 | axios({
20 | method: options.method,
21 | url: `${apiUrl}${options.url}`,
22 | params: options.params || {},
23 | ...sendData
24 | })
25 | .then(response => {
26 | const result = response.data
27 | options.success && options.success(result)
28 | resolve(result)
29 | })
30 | .catch(error => {
31 | isDev && console.error(error)
32 | if (error.response) {
33 | const { data, status } = error.response
34 | options.error && options.error(data, status)
35 | !data.success && data.error_msg && showMsg(data.error_msg)
36 | reject(data, status)
37 | }
38 | })
39 | })
40 | }
41 |
42 | Object.assign(React.Component.prototype, {
43 | $request: request,
44 | $showMsg: showMsg,
45 | $getStorage: getStorage
46 | })
47 |
--------------------------------------------------------------------------------
/src/script/routers.js:
--------------------------------------------------------------------------------
1 | import Loadable from 'react-loadable'
2 |
3 | /* const ShowLoadingComponent = props => {
4 | if (props.isLoading) {
5 | if (props.timedOut) {
6 | return 加载超时
7 | }
8 | return props.pastDelay ? 玩命加载中...
: null
9 | }
10 | return props.error ? Error! Component failed to load
: null
11 | } */
12 | const AsyncLoadable = opts => {
13 | return Loadable({
14 | loading: () => null, //ShowLoadingComponent,
15 | delay: 200,
16 | timeout: 1000,
17 | ...opts
18 | })
19 | }
20 |
21 | export default [{
22 | path: '/',
23 | exact: true,
24 | component: AsyncLoadable({
25 | loader: () =>
26 | import ('@view/home/Home')
27 | }),
28 | name: 'NodeJS论坛'
29 | },
30 | {
31 | path: '/index/article-details',
32 | component: AsyncLoadable({
33 | loader: () =>
34 | import ('@view/home/Article-Details')
35 | }),
36 | name: '详情'
37 | },
38 | {
39 | path: '/news',
40 | exact: true,
41 | component: AsyncLoadable({
42 | loader: () =>
43 | import ('@view/info/News')
44 | }),
45 | name: '消息'
46 | },
47 | {
48 | path: '/share',
49 | component: AsyncLoadable({
50 | loader: () =>
51 | import ('@view/pose/Issue')
52 | }),
53 | name: '分享'
54 | },
55 | {
56 | path: '/user',
57 | exact: true,
58 | component: AsyncLoadable({
59 | loader: () =>
60 | import ('@view/personal/Mine')
61 | }),
62 | name: '我的'
63 | },
64 | {
65 | path: '/user/login',
66 | component: AsyncLoadable({
67 | loader: () =>
68 | import ('@component/common/SharedCompt').then(module => module.UserLogin)
69 | }),
70 | name: '登录'
71 | },
72 | {
73 | component: AsyncLoadable({
74 | loader: () =>
75 | import ('@view/NotFound')
76 | }),
77 | name: '未找到'
78 | }
79 | ]
80 |
--------------------------------------------------------------------------------
/src/script/utils.js:
--------------------------------------------------------------------------------
1 | import Rstore from 'store'
2 |
3 | /**
4 | * 算时间差
5 | * @param {Date} hisTime 历史时间
6 | * @param {Date} nowTime 当前时间
7 | * @returns
8 | */
9 |
10 | export const formatDate = (hisTime, nowTime = new Date()) => {
11 | let result = null
12 | const diffValue = +new Date(nowTime) - +new Date(hisTime)
13 | const minute = 1000 * 60
14 | const hour = minute * 60
15 | const day = hour * 24
16 | // const halfamonth = day * 15
17 | const month = day * 30
18 | const year = month * 12
19 |
20 | const [_year, _month, _week, _day, _hour, _min] = [
21 | diffValue / year,
22 | diffValue / month,
23 | diffValue / (7 * day),
24 | diffValue / day,
25 | diffValue / hour,
26 | diffValue / minute
27 | ]
28 | if (_year >= 1) result = parseInt(_year, 10) + '年前'
29 | else if (_month >= 1) result = parseInt(_month, 10) + '个月前'
30 | else if (_week >= 1) result = parseInt(_week, 10) + '周前'
31 | else if (_day >= 1) result = parseInt(_day, 10) + '天前'
32 | else if (_hour >= 1) result = parseInt(_hour, 10) + '个小时前'
33 | else if (_min >= 1) result = parseInt(_min, 10) + '分钟前'
34 | else result = '刚刚'
35 | return result
36 | }
37 | /**
38 | * 提示弹窗
39 | * @param {String} msg 提示信息
40 | */
41 | let tipTime = null
42 | export const showMsg = msg => {
43 | tipTime && clearTimeout(tipTime)
44 | const formMsg = document.getElementById('form-msg')
45 | if (formMsg) {
46 | formMsg.style.display = 'block'
47 | formMsg.innerHTML =
48 | "" + msg + '
'
49 | } else {
50 | const sDiv = document.createElement('div')
51 | sDiv.id = 'form-msg'
52 | sDiv.className = 'tc form-msg'
53 | sDiv.innerHTML =
54 | "" + msg + '
'
55 | document.getElementsByTagName('body')[0].appendChild(sDiv)
56 | }
57 | tipTime = setTimeout(
58 | () => (document.getElementById('form-msg').style.display = 'none'),
59 | 1000
60 | )
61 | }
62 | /**
63 | * 存储数据
64 | * @param {String} key 名称
65 | * @param {Object} value 数据
66 | */
67 | export const setStorage = (key, value) => {
68 | value['setKeyTime'] = +new Date()
69 | Rstore.set(key, value)
70 | }
71 |
72 | /**
73 | * 获取用户存储信息
74 | *
75 | * @returns
76 | */
77 | export const getStorage = () => {
78 | const userInfo = Rstore.get('USER_INFO')
79 | if (userInfo && userInfo.accesstoken) {
80 | return userInfo
81 | }
82 | return false
83 | }
84 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/store/actions/ActionArticle.js:
--------------------------------------------------------------------------------
1 | import { request } from '@script/reuse'
2 | export default {
3 | fetchData(id) {
4 | return (dispatch, getState) => {
5 | dispatch(this.fetchStart())
6 | //获取文章详情
7 | request({
8 | url: `topic/${id}`,
9 | success: result => {
10 | dispatch(this.fetchDone(result))
11 | },
12 | error: err => {
13 | dispatch(this.fetchFail(err))
14 | }
15 | })
16 | }
17 | },
18 | fetchStart() {
19 | return { type: 'FETCH_START' }
20 | },
21 | fetchDone(result) {
22 | return { type: 'FETCH_DONE', result: result }
23 | },
24 | fetchFail(err) {
25 | return { type: 'FETCH_Fail', result: err }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/actions/ActionNews.js:
--------------------------------------------------------------------------------
1 | import { request } from '@script/reuse'
2 | export default {
3 | fetchData(accesstoken) {
4 | return (dispatch, getState) => {
5 | //获取消息条数
6 | request({
7 | url: 'message/count',
8 | params: {
9 | accesstoken
10 | },
11 | success: result => {
12 | dispatch(this.fetchDone(result))
13 | }
14 | })
15 | }
16 | },
17 | fetchDone(result) {
18 | return { type: 'UPDATE_NEWS', result: result }
19 | // return {
20 | // type: 'UPDATE_NEWS',
21 | // result: {
22 | // success: true,
23 | // data: 2
24 | // }
25 | // }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/actions/ActionReply.js:
--------------------------------------------------------------------------------
1 | import { request } from '@script/reuse'
2 | export default {
3 | fetchData(opts, callback) {
4 | // opts.content = `${opts.content}FROMCNODE
`;
6 | return (dispatch, getState) => {
7 | const topicId = getState().Article.result.data.id
8 | ;(async () => {
9 | const getresult = await request({
10 | method: 'post',
11 | url: `/topic/${topicId}/replies`,
12 | data: opts
13 | }).catch(() => {
14 | this.$showMsg('亲,评论失败!')
15 | })
16 | if (getresult) {
17 | //评论后刷新
18 | request({
19 | url: `topic/${topicId}`,
20 | success: result => {
21 | dispatch(this.fetchDone(result))
22 | callback && callback()
23 | }
24 | })
25 | }
26 | })()
27 | }
28 | },
29 | fetchDone(result) {
30 | return { type: 'UPDATE_REPLY', result: result }
31 | },
32 | sendCnt(data) {
33 | return { type: 'GET_REPLY_CNT', reply_content: data }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/store/actions/ActionTopicList.js:
--------------------------------------------------------------------------------
1 | export default {
2 | keepTopic: res => (dispatch, getState) => {
3 | const { page, data, type, isReset } = res
4 | return dispatch(
5 | (() => {
6 | return {
7 | type: 'SAVE_TOPIC',
8 | page,
9 | topicType: type,
10 | result: isReset ? data : getState().rTopicList.result.concat(data)
11 | }
12 | })()
13 | )
14 | },
15 | recordScrollSite: scrollTop => (dispatch, getState) => {
16 | return dispatch(
17 | (() => {
18 | return {
19 | type: 'SCROLL_TOP_SITE',
20 | scrollTop
21 | }
22 | })()
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/store/reducer/ReduceTopicList.js:
--------------------------------------------------------------------------------
1 | export default (
2 | state = {
3 | result: [],
4 | page: 0,
5 | scrollTop: 0,
6 | topicType: 'all'
7 | },
8 | action
9 | ) => {
10 | switch (action.type) {
11 | case 'SAVE_TOPIC':
12 | return {
13 | ...state,
14 | result: action.result,
15 | page: action.page,
16 | topicType: action.topicType
17 | }
18 | case 'SCROLL_TOP_SITE':
19 | return {
20 | ...state,
21 | scrollTop: action.scrollTop
22 | }
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/store/reducer/ReducerArticle.js:
--------------------------------------------------------------------------------
1 | export default (
2 | state = {
3 | isLoad: true
4 | },
5 | action
6 | ) => {
7 | switch (action.type) {
8 | case 'FETCH_START':
9 | return {
10 | ...state,
11 | isLoad: true
12 | }
13 | case 'FETCH_DONE':
14 | return { isLoad: false, result: action.result }
15 | case 'FETCH_Fail':
16 | return {
17 | ...state,
18 | isLoad: false
19 | }
20 | case 'UPDATE_REPLY':
21 | return {
22 | ...state,
23 | result: action.result
24 | }
25 | case 'GET_REPLY_CNT':
26 | state.result.data.reply_content = action.reply_content
27 | return {
28 | ...state,
29 | result: state.result
30 | }
31 | default:
32 | return state
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/store/reducer/ReducerNews.js:
--------------------------------------------------------------------------------
1 | export default (state = {}, action) => {
2 | switch (action.type) {
3 | case 'UPDATE_NEWS':
4 | return { result: action.result }
5 | default:
6 | return state
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/store/reducer/ReducerRoot.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import Article from './ReducerArticle'
3 | import ReadNews from './ReducerNews'
4 | import rTopicList from './ReduceTopicList'
5 | export default combineReducers({ Article, ReadNews, rTopicList })
6 |
--------------------------------------------------------------------------------
/src/view/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Switch, Route, Redirect } from 'react-router-dom'
3 | import { BackTop } from 'antd'
4 | import classNames from 'classnames'
5 | import qs from 'qs'
6 |
7 | import { getStorage } from '@script/utils'
8 | import RouteConfig from '@script/routers'
9 |
10 | import Menu from '@component/Menu'
11 | import SubHeader from '@component/SubHeader'
12 |
13 | import '@fonts/svg/gotop.svg'
14 |
15 | const RouteWithSubRoutes = route => (
16 | }
20 | />
21 | )
22 | const findMatch = path => RouteConfig.find(f => f.path === path)
23 |
24 | export default () => {
25 | let isUserCur = true
26 | let queryName = null
27 | const arrPathName = ['/index/article-details', '/', '/news', '/user']
28 | return (
29 | {
31 | const { pathname, search } = location
32 | const getRoutes = findMatch(pathname)
33 | const isBackTop = arrPathName.includes(pathname)
34 | const isAdd = getRoutes && pathname.split('/').length < 3
35 | const classMain = classNames('tl0 w100 view-main', {
36 | 'pa h100': Object.is(pathname, '/'),
37 | container: isAdd,
38 | 'container-top': !isAdd,
39 | 'bg-white': Object.is(pathname, '/share')
40 | })
41 | //当前用户
42 | if (Object.is(pathname, '/user')) {
43 | const userInfo = getStorage()
44 | queryName = qs.parse(search.slice(1)).name
45 | isUserCur = userInfo && Object.is(userInfo.loginname, queryName)
46 | }
47 | return (
48 |
49 | {getRoutes && (
50 |
54 | )}
55 |
56 | {RouteConfig.map((route, index) => (
57 |
58 | ))}
59 |
60 |
61 | {isAdd && isUserCur && }
62 | {isBackTop && (
63 |
68 |
71 |
72 | )}
73 |
74 | )
75 | }}
76 | />
77 | )
78 | }
79 |
--------------------------------------------------------------------------------
/src/view/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import '@fonts/svg/notfound.svg'
4 | export default () => {
5 | return (
6 |
7 |
10 | 您找的页面到了火星,送我
15 | 回地球!
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/view/home/Article-Comment.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import LazyImage from '@component/common/LazyImage'
4 | import classNames from 'classnames'
5 |
6 | import '@fonts/svg/thumbs.svg'
7 | import '@fonts/svg/comment.svg'
8 | class CommentList extends React.Component {
9 | constructor(props) {
10 | super(props)
11 | this.state = {
12 | isLogin: this.$getStorage(),
13 | userTotalUps: this.props.item.ups
14 | }
15 | }
16 | UserLike(loginname, id, i) {
17 | //用户点赞
18 | const { isLogin } = this.state
19 | const uid = isLogin && isLogin.id
20 | if (!isLogin) {
21 | return this.props.history.push('/user/login')
22 | } else if (isLogin && isLogin.loginname === loginname) {
23 | return this.$showMsg('亲,不能给自己点赞!')
24 | }
25 | this.$request({
26 | method: 'post',
27 | url: `/reply/${id}/ups`,
28 | data: {
29 | accesstoken: isLogin.accesstoken
30 | },
31 | success: result => {
32 | let curUps = this.props.item.ups
33 | if (result.success) {
34 | if (result.action === 'down') {
35 | //取消点赞
36 | curUps.map((item, index) => {
37 | return item === uid && curUps.splice(index, 1)
38 | })
39 | } else {
40 | curUps.push(uid)
41 | }
42 | this.setState({ userTotalUps: curUps })
43 | }
44 | }
45 | })
46 | }
47 | userIsUp(arr) {
48 | //是否点赞
49 | const id = this.state.isLogin ? this.state.isLogin.id : ''
50 | for (let i = 0, len = arr.length; i < len; i++) {
51 | if (arr[i] === id) return true
52 | }
53 | return false
54 | }
55 | replyTopic(name, reply_id, isAtmuch) {
56 | //用户评论
57 | const { isLogin } = this.state
58 | if (!isLogin) {
59 | return this.props.history.push('/user/login')
60 | }
61 | this.props.sendcnt({ name, reply_id })
62 | }
63 | render() {
64 | const { author, content, ups, id } = this.props.item
65 | let index = this.props.index
66 | const createMarkup = () => {
67 | return { __html: content }
68 | }
69 | const isUp = this.userIsUp(ups)
70 | return (
71 |
79 |
80 |
86 |
91 |
92 |
{++index}楼
93 |
94 |
95 |
96 | {author.loginname}
97 |
98 |
102 |
103 |
104 |
110 | {ups.length ? ups.length : ''}
111 |
112 |
118 |
119 |
120 |
121 | )
122 | }
123 | }
124 |
125 | export default class ArticleComment extends React.Component {
126 | componentDidMount() {
127 | this.props.getElHeight(this.refs.replyList.scrollHeight)
128 | }
129 | render() {
130 | const { replies, reply_count } = this.props.info
131 | return (
132 |
133 |
138 |
141 |
142 | 共{reply_count}条评论!
143 |
144 |
145 |
146 | {replies.map((item, index) => {
147 | return (
148 |
155 | )
156 | })}
157 |
158 |
159 | )
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/view/home/Article-DecCnt.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { bindActionCreators } from 'redux'
3 | import { connect } from 'react-redux'
4 |
5 | import ActionReply from '@store/actions/ActionReply'
6 | import ArticleItem from '@component/ArticleItem'
7 | import { NotLogin } from '@component/common/SharedCompt'
8 | import MaskPopups from '@component/common/MaskPopups'
9 |
10 | import ArticleComment from './Article-Comment'
11 | import UserComment from './User-Comment'
12 |
13 | import '@fonts/svg/operat-del.svg'
14 | import '@fonts/svg/operat-keep-default.svg'
15 | import '@fonts/svg/operat-keep-active.svg'
16 | import '@fonts/svg/meedit.svg'
17 |
18 | /* class ThemeKeep extends React.Component {
19 | constructor(props) {
20 | super(props)
21 | this.state = {
22 | is_collect: props.isKeep
23 | }
24 | console.log(props.isKeep)
25 | }
26 | topicCollect(id, is_keep) {
27 | console.log(is_keep)
28 | if (is_keep) return this.userWithCollect(id, 'collect') //收藏
29 | return this.userWithCollect(id, 'de_collect') //取消收藏
30 | }
31 | userWithCollect(id, url, callback) {
32 | const isLogin = this.$getStorage()
33 |
34 | isLogin &&
35 | this.$request({
36 | method: 'post',
37 | url: `topic_collect/${url}`,
38 | data: {
39 | accesstoken: isLogin.accesstoken,
40 | topic_id: id
41 | },
42 | success: result => {
43 | result.success && this.setState({ is_collect: true })
44 | }
45 | })
46 | }
47 | render() {
48 | const { topicId } = this.props
49 | return (
50 |
51 | {this.state.is_collect ? (
52 |
53 |
56 | 取消收藏
57 |
58 | ) : (
59 |
60 |
63 | 收藏
64 |
65 | )}
66 |
67 | )
68 | }
69 | } */
70 |
71 | class ArticleDecCnt extends React.Component {
72 | constructor(props) {
73 | super(props)
74 | this.state = {
75 | isPopups: false,
76 | listh: 0
77 | }
78 | this.userInfo = this.$getStorage()
79 | }
80 | SendCancelRun() {
81 | this.setState({ isPopups: false })
82 | }
83 | SendDeterMine(id) {
84 | this.$request({
85 | method: 'post',
86 | url: `topic_collect/de_collect`,
87 | data: {
88 | accesstoken: this.userInfo.accesstoken,
89 | topic_id: this.props.info.data.id
90 | },
91 | success: result => {
92 | // this .props .history .push({pathname: '/'});
93 | }
94 | })
95 | }
96 | getElHeight(h) {
97 | this.setState({ listh: h })
98 | }
99 | render() {
100 | const { data } = this.props.info
101 | const createMarkup = () => {
102 | return { __html: data.content }
103 | }
104 | const isLogin = this.userInfo
105 | return (
106 |
112 |
113 | {/* {isLogin &&
114 | {data.author_id === isLogin.id
115 | ?
116 |
117 |
120 | 编辑
121 |
122 |
{
124 | this.setState({isPopups: true})
125 | }}>
126 |
129 | 删除
130 |
131 |
132 |
133 | :
134 |
135 |
136 | }
137 |
} */}
138 |
142 |
147 |
156 | {isLogin ? (
157 |
163 | ) : (
164 |
165 | )}
166 |
167 | {this.state.isPopups && (
168 |
173 | )}
174 |
175 | )
176 | }
177 | }
178 |
179 | const mapStateToProps = state => {
180 | return { Article: state.Article }
181 | }
182 | const actions = {
183 | ...ActionReply
184 | }
185 | const mapDispatchToProps = dispatch => ({
186 | actions: bindActionCreators(actions, dispatch)
187 | })
188 | export default connect(mapStateToProps, mapDispatchToProps)(ArticleDecCnt)
189 |
--------------------------------------------------------------------------------
/src/view/home/Article-Details.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { bindActionCreators } from 'redux'
3 | import { connect } from 'react-redux'
4 | import qs from 'qs'
5 |
6 | import ActionArticle from '@store/actions/ActionArticle'
7 |
8 | import ArticleDecCnt from './Article-DecCnt'
9 | import { LoadLoop, LoadFail } from '@component/common/SharedCompt'
10 |
11 | import 'github-markdown-css'
12 | class ArticleDeatils extends React.Component {
13 | constructor(props) {
14 | super(props)
15 | this.props.actions.fetchData(
16 | qs.parse(this.props.location.search.slice(1)).id
17 | )
18 | }
19 | render() {
20 | const { isLoad } = this.props.Article
21 | return isLoad ? (
22 |
23 | ) : this.props.Article.result && this.props.Article.result.success ? (
24 |
25 | ) : (
26 |
27 | )
28 | }
29 | }
30 | const mapStateToProps = state => {
31 | return { Article: state.Article }
32 | }
33 | const actions = {
34 | ...ActionArticle
35 | }
36 | const mapDispatchToProps = dispatch => ({
37 | actions: bindActionCreators(actions, dispatch)
38 | })
39 | export default connect(mapStateToProps, mapDispatchToProps)(ArticleDeatils)
40 |
--------------------------------------------------------------------------------
/src/view/home/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { NavLink } from 'react-router-dom'
3 | import qs from 'qs'
4 |
5 | import LoadMoreInfo from '@component/plugin/touch-loadmore/LoadMore'
6 |
7 | const arrTabHead = [
8 | {
9 | name: '全部',
10 | link: '/',
11 | type: 'all'
12 | },
13 | {
14 | name: '精华',
15 | link: '/?tab=good',
16 | type: 'good'
17 | },
18 | {
19 | name: '分享',
20 | link: '/?tab=share',
21 | type: 'share'
22 | },
23 | {
24 | name: '问答',
25 | link: '/?tab=ask',
26 | type: 'ask'
27 | },
28 | {
29 | name: '招聘',
30 | link: '/?tab=job',
31 | type: 'job'
32 | },
33 | {
34 | name: '测试',
35 | link: '/?tab=dev',
36 | type: 'dev'
37 | }
38 | ]
39 |
40 | /* const ListPhoto = props => {
41 | const tabName = props.text || []
42 | const dataItem = tabName.length
43 | ? tabName.map((item, index) => (
44 |
45 | {index}
46 | {item.slide_name}
47 |
48 | ))
49 | : '加载中...'
50 | return
51 | } */
52 | const TabNav = props => {
53 | const activeTab = {}
54 | activeTab[props.type] = 'ft-color'
55 | return (
56 |
71 | )
72 | }
73 |
74 | export default class Home extends React.Component {
75 | componentDidMount() {
76 | // console.log(this.refs.ArticleList)
77 | }
78 | shouldComponentUpdate(nextProps, nextState) {
79 | const curPath = qs.parse(nextProps.location.search.slice(1)).tab || 'all'
80 | const goPath = qs.parse(this.props.location.search.slice(1)).tab || 'all'
81 | return curPath !== goPath
82 | }
83 | render() {
84 | const tabType = qs.parse(this.props.location.search.slice(1)).tab || 'all'
85 | return (
86 |
87 |
88 |
89 |
90 | )
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/view/home/User-Comment.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import '@fonts/svg/send.svg'
4 | export default class UserComment extends React.Component {
5 | constructor(props) {
6 | super(props)
7 | const isLogin = this.$getStorage()
8 | this.state = {
9 | accesstoken: isLogin && isLogin.accesstoken,
10 | content: '',
11 | reply_id: ''
12 | }
13 | }
14 | newCommentSubmit() {
15 | const { state } = this
16 | if (!state.content) {
17 | return this.$showMsg('亲,忘了输内容了!')
18 | }
19 | this.props.actions.fetchData(
20 | {
21 | ...state,
22 | reply_id: this.state.reply_id
23 | },
24 | () => {
25 | this.setState({ content: '' })
26 | window.scrollTo(
27 | 0,
28 | document.documentElement.scrollHeight || this.props.listh
29 | )
30 | }
31 | )
32 | }
33 | handleChange(e) {
34 | this.setState({ content: e.target.value })
35 | }
36 | EnterSubmit(e) {
37 | if (e.keyCode === 13) this.newCommentSubmit()
38 | }
39 | /* shouldComponentUpdate(nextProps, nextState) {
40 | const {reply_content} = nextProps.info.data;
41 | if (reply_content) {
42 | const isUpdate = (this.state.value !=== `@${reply_content.name}`) || (this.state.reply_id !=== `${reply_content.reply_id}`);
43 | return isUpdate;
44 | }
45 | return true;
46 | } */
47 | componentWillReceiveProps(prevProps, prevState) {
48 | const { reply_content } = prevProps.info.data
49 | if (reply_content && this.state.reply_id !== `${reply_content.reply_id}`) {
50 | this.setState({
51 | content: `@${reply_content.name} `,
52 | reply_id: `${reply_content.reply_id}`
53 | })
54 | }
55 | }
56 | render() {
57 | return (
58 |
59 |
60 |
66 |
72 |

77 |
78 |
79 |
80 |
91 |
92 |
93 |
103 |
104 |
105 |
106 | )
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/view/info/News.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { DetectLogin, LoadLoop, NotResult } from '@component/common/SharedCompt'
4 | import { Tabs } from 'antd'
5 | import { formatDate } from '@script/utils'
6 | import { bindActionCreators } from 'redux'
7 | import { connect } from 'react-redux'
8 |
9 | import ActionNews from '@store/actions/ActionNews'
10 |
11 | const TabPane = Tabs.TabPane
12 |
13 | class UserNews extends React.Component {
14 | constructor(props) {
15 | super(props)
16 | this.userInfo = this.$getStorage()
17 | this.state = {
18 | info: {}
19 | }
20 | }
21 | componentDidMount() {
22 | this.fetchData()
23 | }
24 | fetchData() {
25 | this.$request({
26 | url: `messages`,
27 | params: {
28 | mdrender: false,
29 | accesstoken: this.userInfo.accesstoken
30 | },
31 | success: result => {
32 | this.setState({ info: result })
33 | }
34 | })
35 | }
36 | render() {
37 | const { info } = this.state
38 | return (
39 |
40 | {info.data ? (
41 |
46 | ) : (
47 |
48 | )}
49 |
50 | )
51 | }
52 | }
53 |
54 | class NewsContent extends React.Component {
55 | constructor(props) {
56 | super(props)
57 | this.userInfo = this.$getStorage()
58 | }
59 | clearReadMsg() {
60 | this.$request({
61 | method: 'post',
62 | url: `message/mark_all`,
63 | data: {
64 | accesstoken: this.userInfo.accesstoken
65 | },
66 | success: result => {
67 | console.log(result)
68 | this.props.afresh()
69 | this.props.actions.fetchData()
70 | this.userInfo && this.props.actions.fetchData(this.userInfo.accesstoken)
71 | }
72 | })
73 | }
74 | render() {
75 | const { data } = this.props
76 | const [hasMsgLen, hasNotMsgLen] = [
77 | data.has_read_messages.length,
78 | data.hasnot_read_messages.length
79 | ]
80 | return (
81 |
82 |
83 |
84 | {hasNotMsgLen ? (
85 | data.hasnot_read_messages.map((item, index) => {
86 | return
87 | })
88 | ) : (
89 |
90 | )}
91 |
92 |
93 | {hasMsgLen ? (
94 | data.has_read_messages.map((item, index) => {
95 | return
96 | })
97 | ) : (
98 |
99 | )}
100 | {/*
101 |
106 |
*/}
107 |
108 |
109 |
110 | )
111 | }
112 | }
113 | const NewsList = props => {
114 | const { item } = props
115 | const createMarkup = () => {
116 | return { __html: item.reply.content }
117 | }
118 | return (
119 |
120 |
121 |

127 |
128 |
133 |
134 | {item.author.loginname}
135 |
136 | {formatDate(item.create_at)}
137 |
138 |
139 |
140 | 在话题
150 | {item.topic.title}
151 |
152 | {item.type === 'at' ? '中@了你!' : '回复了你!'}
153 |
157 |
158 |
159 |
160 |
161 |
162 | )
163 | }
164 |
165 | // export default DetectLogin({realuser: UserNews})
166 | const mapStateToProps = state => {
167 | return { ReadNews: state.ReadNews }
168 | }
169 | const actions = {
170 | ...ActionNews
171 | }
172 | const mapDispatchToProps = dispatch => ({
173 | actions: bindActionCreators(actions, dispatch)
174 | })
175 | export default connect(mapStateToProps, mapDispatchToProps)(
176 | DetectLogin({ realuser: UserNews })
177 | )
178 |
--------------------------------------------------------------------------------
/src/view/personal/Mine.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { Tabs } from 'antd'
4 | import Rstore from 'store'
5 | import qs from 'qs'
6 | import classNames from 'classnames'
7 |
8 | import ArticleItem from '@component/ArticleItem'
9 | import LazyImage from '@component/common/LazyImage'
10 | import MaskPopups from '@component/common/MaskPopups'
11 |
12 | import { formatDate } from '@script/utils'
13 | import { LoadLoop, NotResult } from '@component/common/SharedCompt'
14 |
15 | import '@fonts/svg/meuser.svg'
16 | import '@fonts/svg/metime.svg'
17 | import '@fonts/svg/mescore.svg'
18 | import '@fonts/svg/write.svg'
19 | import '@fonts/svg/page-views.svg'
20 | import '@fonts/svg/medropout.svg'
21 | import '@fonts/svg/meedit.svg'
22 |
23 | const checkNum = num => (+num > 10 ? num : `0${num}`)
24 | const dateFormat = time => {
25 | const date = new Date(time)
26 | const [year, month, day] = [
27 | date.getFullYear(),
28 | date.getMonth() + 1,
29 | date.getDate()
30 | ]
31 | return `${year} / ${checkNum(month)} / ${checkNum(day)}`
32 | }
33 |
34 | class Mine extends React.Component {
35 | constructor(props) {
36 | super(props)
37 | this.userInfo = this.$getStorage()
38 | this.state = {
39 | info: {}
40 | }
41 | this.userName = qs.parse(this.props.location.search.slice(1)).name
42 | if (this.userInfo) {
43 | this.isUserCur = this.userInfo.loginname === this.userName
44 | }
45 | }
46 | async componentDidMount() {
47 | const getUserInfo = await this.$request({
48 | url: `user/${this.userName}`
49 | // success: (result) => { this.setState({info: result.data}) }
50 | })
51 | if (getUserInfo) {
52 | this.$request({
53 | url: `topic_collect/${this.userName}`,
54 | success: result => {
55 | const { info } = this.state
56 | info['collect'] = result.data
57 | const data = Object.assign({}, info, getUserInfo.data)
58 | this.setState({ info: data })
59 | }
60 | })
61 | }
62 | }
63 | render() {
64 | const { info } = this.state
65 | return info.loginname ? (
66 |
72 | ) : (
73 |
74 | )
75 | }
76 | }
77 |
78 | const TabPane = Tabs.TabPane
79 |
80 | class UserCenter extends React.Component {
81 | constructor(props) {
82 | super(props)
83 | this.state = {
84 | isPopups: false
85 | }
86 | }
87 | SendCancelRun() {
88 | this.setState({ isPopups: false })
89 | }
90 | SendDeterMine() {
91 | Rstore.remove('USER_INFO')
92 | this.props.history.push({ pathname: '/' })
93 | }
94 | render() {
95 | const { result, isUserCur } = this.props
96 | return (
97 |
98 |
99 |
106 |
112 |
113 |
114 |
117 | {result.loginname}
118 |
119 |
120 |
123 | {result.score}分
124 |
125 |
126 |
129 | {dateFormat(result.create_at)}
130 |
131 |
132 | {isUserCur && (
133 |
134 |
135 |
138 |
139 | 发布话题
140 |
141 |
142 |
{
144 | this.setState({ isPopups: true })
145 | }}
146 | >
147 |
150 | 退出
151 |
152 |
153 | )}
154 |
155 |
156 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | {result.collect.length ? (
168 | result.collect.map((item, index) => (
169 |
175 | ))
176 | ) : (
177 |
178 | )}
179 |
180 |
181 | {this.state.isPopups && (
182 |
187 | )}
188 |
189 | )
190 | }
191 | }
192 |
193 | const UserTopicList = props => {
194 | const { info } = props
195 | return (
196 |
197 | {info.length ? (
198 | info.map((item, index) => {
199 | return (
200 |
201 |
212 |

218 |
219 |
{item.title}
220 |
225 |
226 |
229 | {item.author.loginname}
230 |
231 | {formatDate(item.last_reply_at)}
232 |
233 |
234 |
235 |
236 | )
237 | })
238 | ) : (
239 |
240 | )}
241 |
242 | )
243 | }
244 |
245 | export default Mine
246 |
--------------------------------------------------------------------------------
/src/view/pose/Issue.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { DetectLogin } from '@component/common/SharedCompt'
3 |
4 | class Issue extends React.Component {
5 | constructor(props) {
6 | super(props)
7 | const isLogin = this.$getStorage()
8 | this.state = {
9 | title: '',
10 | content: '',
11 | tab: 'dev',
12 | accesstoken: isLogin && isLogin.accesstoken,
13 | arrRadio: [
14 | {
15 | type: 'ask',
16 | name: '问答',
17 | isChecked: true
18 | },
19 | {
20 | type: 'share',
21 | name: '分享',
22 | isChecked: false
23 | },
24 | {
25 | type: 'job',
26 | name: '招聘',
27 | isChecked: false
28 | },
29 | {
30 | type: 'dev',
31 | name: '测试',
32 | isChecked: false
33 | }
34 | ]
35 | }
36 | const { state } = this
37 | this.themeTitle = e => {
38 | state.title = e.target.value
39 | }
40 | this.themeContent = e => {
41 | state.content = e.target.value
42 | }
43 | this.issueSubmit = () => {
44 | if (state.title.length < 10) {
45 | return this.$showMsg('亲,请输入10字以上的标题!')
46 | } else if (!state.content) {
47 | return this.$showMsg('亲,请输入内容!')
48 | }
49 | const { accesstoken, title, tab, content } = state
50 | this.$request({
51 | method: 'post',
52 | url: 'topics',
53 | data: { accesstoken, title, tab, content },
54 | success: result => {
55 | if (result.success) {
56 | this.$showMsg('发表成功!')
57 | this.props.history.push({
58 | pathname: '/index/article-details',
59 | search: `?id=${result.topic_id}`
60 | })
61 | } else {
62 | this.$showMsg('发表失败!')
63 | }
64 | },
65 | error: () => {
66 | this.$showMsg('发表失败!')
67 | }
68 | })
69 | }
70 | }
71 | selectThemeCate(i, e) {
72 | //类型选择
73 | const { arrRadio } = this.state
74 | if (arrRadio[i].isChecked) return
75 | arrRadio.map((item, index) => {
76 | return (item.isChecked = Object.is(i, index) ? true : false)
77 | })
78 | this.setState(
79 | {
80 | tab: arrRadio[i].type,
81 | arrRadio: arrRadio
82 | },
83 | () => {
84 | // console.log(this.state.tab)
85 | }
86 | )
87 | }
88 | render() {
89 | return (
90 |
91 |
92 |
99 |
100 |
101 |
107 |
108 |
114 |
115 | )
116 | }
117 | }
118 |
119 | class ThemeCate extends React.Component {
120 | render() {
121 | return (
122 |
123 |
128 | 选择类别{' '}
129 | {this.props.arrRadio.map((item, index) => {
130 | return (
131 |
139 |
140 |
147 |
148 |
149 |
150 | )
151 | })}
152 |
153 |
154 |
160 |
161 |
162 | )
163 | }
164 | }
165 |
166 | export default DetectLogin({ realuser: Issue })
167 |
--------------------------------------------------------------------------------