├── API接口
└── README.md
├── README.md
├── react-admin-client
├── .gitignore
├── README.md
├── config-overrides.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
│ ├── App.js
│ ├── api
│ ├── ajax.js
│ └── index.js
│ ├── assets
│ └── images
│ │ ├── 404.png
│ │ ├── bg.jpg
│ │ └── logo.png
│ ├── components
│ ├── header
│ │ ├── Header.jsx
│ │ └── header.less
│ ├── left-nav
│ │ ├── LeftNav.jsx
│ │ └── leftNav.less
│ └── link-button
│ │ ├── LinkButton.jsx
│ │ └── link-button.less
│ ├── config
│ └── menuConfig.js
│ ├── index.js
│ ├── redux
│ ├── action-types.js
│ ├── actions.js
│ ├── reducer.js
│ └── store.js
│ ├── utils
│ ├── constance.js
│ ├── dateUtils.js
│ ├── memoryUtils.js
│ └── storageUtils.js
│ └── views
│ ├── admin
│ └── Admin.jsx
│ ├── category
│ ├── AddForm.jsx
│ ├── Category.jsx
│ └── UpdateForm.jsx
│ ├── charts
│ ├── Bar.jsx
│ ├── Line.jsx
│ └── Pie.jsx
│ ├── home
│ ├── Bar.jsx
│ ├── Home.jsx
│ ├── Line.jsx
│ └── home.less
│ ├── login
│ ├── Login.jsx
│ └── login.less
│ ├── not-found
│ ├── NotFound.jsx
│ └── NotFound.less
│ ├── product
│ ├── Product.jsx
│ ├── RichTextEditor.jsx
│ ├── add-update.jsx
│ ├── detail.jsx
│ ├── home.jsx
│ ├── picture-wall.jsx
│ └── product.less
│ ├── role
│ ├── Role.jsx
│ ├── add-form.jsx
│ └── auth-form.jsx
│ └── user
│ ├── User.jsx
│ └── user-form.jsx
└── react-admin-server
├── .gitignore
├── README.md
├── app.js
├── models
├── CategoryModel.js
├── ProductModel.js
├── RoleModel.js
└── UserModel.js
├── package-lock.json
├── package.json
├── public
├── css
│ └── reset.css
├── images
│ ├── 404.png
│ ├── bg.jpg
│ └── logo.png
└── upload
│ ├── image-1564476186270.png
│ ├── image-1564476288784.png
│ ├── image-1564476539374.png
│ ├── image-1564666001484.jpg
│ ├── image-1564666010571.png
│ ├── image-1566299317694.jpg
│ ├── image-1566301200182.png
│ ├── image-1566301329479.jpg
│ ├── image-1566301335233.png
│ ├── image-1566301793179.jpg
│ ├── image-1566357626804.png
│ ├── image-1566357653080.png
│ ├── image-1566461734181.png
│ └── image-1566462347560.png
└── router
├── file-upload.js
└── index.js
/API接口/README.md:
--------------------------------------------------------------------------------
1 | # 接口文档
2 |
3 | ## 目录:
4 | 1). 登陆
5 | 2). 添加用户
6 | 3). 更新用户
7 | 4). 获取所有用户列表
8 | 5). 删除用户
9 | 6). 获取一级或某个二级分类列表
10 | 7). 添加分类
11 | 8). 更新品类名称
12 | 9). 根据分类ID获取分类
13 | 10). 获取商品分页列表
14 | 11). 根据ID/Name搜索产品分页列表
15 | 12). 添加商品
16 | 13). 更新商品
17 | 14). 对商品进行上架/下架处理
18 | 15). 上传图片
19 | 16). 删除图片
20 | 17). 添加角色
21 | 18). 获取角色列表
22 | 19). 更新角色(给角色设置权限)
23 | 20). 获取天气信息(支持jsonp)
24 |
25 | ## 1. 登陆
26 |
27 | ### 请求URL:
28 | http://localhost:5000/login
29 |
30 | ### 请求方式:
31 | POST
32 |
33 | ### 参数类型
34 | |参数 |是否必选 |类型 |说明
35 | |username |Y |string |用户名
36 | |password |Y |string |密码
37 |
38 | ### 返回示例:
39 | 成功:
40 | {
41 | "status": 0,
42 | "data": {
43 | "_id": "5c3b297dea95883f340178b0",
44 | "password": "21232f297a57a5a743894a0e4a801fc3",
45 | "username": "admin",
46 | "create_time": 1547381117891,
47 | "__v": 0,
48 | "role": {
49 | "menus": []
50 | }
51 | }
52 | }
53 | 失败
54 | {
55 | "status": 1,
56 | "msg": "用户名或密码不正确!"
57 | }
58 |
59 | ## 2. 添加用户
60 |
61 | ### 请求URL:
62 | http://localhost:5000/manage/user/add
63 |
64 | ### 请求方式:
65 | POST
66 |
67 | ### 参数类型
68 | |参数 |是否必选 |类型 |说明
69 | |username |Y |string |用户名
70 | |password |Y |string |密码
71 | |phone |N |string |手机号
72 | |email |N |string |邮箱
73 | |role_id |N |string |角色ID
74 |
75 | ### 返回示例:
76 | 成功:
77 | {
78 | "status": 0,
79 | "data": {
80 | "_id": "5c3b382c82a14446f4ffb647",
81 | "username": "admin6",
82 | "password": "d7b79bb6d6f77e6cbb5df2d0d2478361",
83 | "phone": "13712341234",
84 | "email": "test@qq.com",
85 | "create_time": 1547384876804,
86 | "__v": 0
87 | }
88 | }
89 | 失败
90 | {
91 | "status": 1,
92 | "msg": "此用户已存在"
93 | }
94 |
95 | ## 3. 更新用户
96 | ### 请求URL:
97 | http://localhost:5000/manage/user/update
98 |
99 | ### 请求方式:
100 | POST
101 |
102 | ### 参数类型
103 |
104 | |参数 |是否必选 |类型 |说明
105 | |_id |Y |string |ID
106 | |username |N |string |用户名
107 | |phone |N |string |手机号
108 | |email |N |string |邮箱
109 | |role_id |N |string |角色ID
110 |
111 | ### 返回示例:
112 | 成功:
113 | {
114 | "status": 0,
115 | "data": {
116 | "_id": "5c3b382c82a14446f4ffb647",
117 | "username": "admin6",
118 | "password": "d7b79bb6d6f77e6cbb5df2d0d2478361",
119 | "phone": "13712341234",
120 | "email": "test@qq.com",
121 | "create_time": 1547384876804,
122 | "__v": 0
123 | }
124 | }
125 | 失败
126 | {
127 | "status": 1,
128 | "msg": "此用户已存在"
129 | }
130 |
131 | ## 4. 获取所有用户列表
132 | ### 请求URL:
133 | http://localhost:5000/manage/user/list
134 |
135 | ### 请求方式:
136 | GET
137 |
138 | ### 参数类型:
139 | 无
140 |
141 | ### 返回示例:
142 | {
143 | "status": 0,
144 | "data": {
145 | "users": [
146 | {
147 | "_id": "5cb05b4db6ed8c44f42c9af2",
148 | "username": "test",
149 | "password": "202cb962ac59075b964b07152d234b70",
150 | "phone": "123412342134",
151 | "email": "sd",
152 | "role_id": "5ca9eab0b49ef916541160d4",
153 | "create_time": 1555061581734,
154 | "__v": 0
155 | },
156 | {
157 | "_id": "5cb05b69b6ed8c44f42c9af3",
158 | "username": "ss22",
159 | "password": "123",
160 | "phone": "23343",
161 | "email": "df",
162 | "role_id": "5caf5444c61376319cef80a8",
163 | "create_time": 1555061609666,
164 | "__v": 0
165 | }
166 | ],
167 | "roles": [
168 | {
169 | "menus": [
170 | "/home",
171 | "/role",
172 | "/category",
173 | "/products",
174 | "/product",
175 | "/charts/bar"
176 | ],
177 | "_id": "5ca9eaa1b49ef916541160d3",
178 | "name": "测试",
179 | "create_time": 1554639521749,
180 | "__v": 0,
181 | "auth_time": 1555145863489,
182 | "auth_name": "admin"
183 | }
184 | ]
185 | }
186 | }
187 |
188 | ## 5. 删除用户
189 | ### 请求URL:
190 | http://localhost:5000/manage/user/delete
191 |
192 | ### 请求方式:
193 | POST
194 |
195 | ### 参数类型:
196 |
197 | |参数 |是否必选 |类型 |说明
198 | |userId |Y |string |用户ID
199 |
200 | ### 返回示例:
201 | {
202 | "status": 0
203 | }
204 |
205 | ## 6. 获取一级或某个二级分类列表
206 | ### 请求URL:
207 | http://localhost:5000/manage/category/list
208 |
209 | ### 请求方式:
210 | GET
211 |
212 | ### 参数类型: query
213 |
214 | |参数 |是否必选 |类型 |说明
215 | |parentId |Y |string |父级分类的ID
216 |
217 | ### 返回示例:
218 | 一级分类:
219 | {
220 | "status": 0,
221 | "data": [
222 | {
223 | "parentId": "0",
224 | "_id": "5c2ed631f352726338607046",
225 | "name": "分类001",
226 | "__v": 0
227 | },
228 | {
229 | "parentId": "0",
230 | "_id": "5c2ed647f352726338607047",
231 | "name": "分类2",
232 | "__v": 0
233 | },
234 | {
235 | "parentId": "0",
236 | "_id": "5c2ed64cf352726338607048",
237 | "name": "1分类3",
238 | "__v": 0
239 | }
240 | ]
241 | }
242 | 二级分类
243 | {
244 | "status": 0,
245 | "data": [
246 | {
247 | "parentId": "5c2ed64cf352726338607048",
248 | "_id": "5c2ed65df352726338607049",
249 | "name": "分类3333",
250 | "__v": 0
251 | },
252 | {
253 | "parentId": "5c2ed64cf352726338607048",
254 | "_id": "5c2ed66ff35272633860704a",
255 | "name": "分类34",
256 | "__v": 0
257 | }
258 | ]
259 | }
260 |
261 |
262 |
263 | ## 7. 添加分类
264 | ### 请求URL:
265 | http://localhost:5000/manage/category/add
266 |
267 | ### 请求方式:
268 | POST
269 |
270 | ### 参数类型:
271 |
272 | |参数 |是否必选 |类型 |说明
273 | |parentId |Y |string |父级分类的ID
274 | |categoryName |Y |string |名称
275 |
276 | ### 返回示例:
277 | 添加一级分类:
278 | {
279 | "status": 0,
280 | "data": {
281 | "parentId": "0",
282 | "_id": "5c3ec1534594a00e5877b841",
283 | "name": "分类9",
284 | "__v": 0
285 | }
286 | }
287 | 添加二级分类
288 | {
289 | "status": 0,
290 | "data": {
291 | "parentId": "5c2ed64cf352726338607048",
292 | "_id": "5c3ec1814594a00e5877b842",
293 | "name": "分类39",
294 | "__v": 0
295 | }
296 | }
297 |
298 |
299 | ## 8. 更新品类名称
300 | ### 请求URL:
301 | http://localhost:5000/manage/category/update
302 |
303 | ### 请求方式:
304 | POST
305 |
306 | ### 参数类型:
307 |
308 | |参数 |是否必选 |类型 |说明
309 | |categoryId |Y |string |父级分类的ID
310 | |categoryName |Y |string |名称
311 |
312 | ### 返回示例:
313 | {
314 | "status": 0
315 | }
316 |
317 |
318 | ## 9. 根据分类ID获取分类
319 | ### 请求URL:
320 | http://localhost:5000/manage/category/info
321 |
322 | ### 请求方式:
323 | GET
324 |
325 | ### 参数类型:
326 |
327 | |参数 |是否必选 |类型 |说明
328 | |categoryId |Y |string |父级分类的ID
329 |
330 | ### 返回示例:
331 | {
332 | "status": 0,
333 | "data": {
334 | "parentId": "0",
335 | "_id": "5c2ed631f352726338607046",
336 | "name": "分类001",
337 | "__v": 0
338 | }
339 | }
340 |
341 |
342 | ## 10. 获取商品分页列表
343 | ### 请求URL:
344 | http://localhost:5000/manage/product/list
345 |
346 | ### 请求方式:
347 | GET
348 |
349 | ### 参数类型:
350 |
351 | |参数 |是否必选 |类型 |说明
352 | |pageNum |Y |Number |页码
353 | |pageSize |Y |Number |每页条目数
354 |
355 | ### 返回示例:
356 | {
357 | "status": 0,
358 | "data": {
359 | "pageNum": 1,
360 | "total": 12,
361 | "pages": 3,
362 | "pageSize": 5,
363 | "list": [
364 | {
365 | "status": 1,
366 | "imgs": [
367 | "image-1559402396338.jpg"
368 | ],
369 | "_id": "5ca9e05db49ef916541160cd",
370 | "name": "联想ThinkPad 翼4809",
371 | "desc": "年度重量级新品,X390、T490全新登场 更加轻薄机身设计9",
372 | "price": 65999,
373 | "pCategoryId": "5ca9d6c0b49ef916541160bb",
374 | "categoryId": "5ca9db9fb49ef916541160cc",
375 | "detail": "
想你所需,超你所想!精致外观,轻薄便携带光驱,内置正版office杜绝盗版死机,全国联保两年! 222
\n联想(Lenovo)扬天V110 15.6英寸家用轻薄便携商务办公手提笔记本电脑 定制【E2-9010/4G/128G固态】 2G独显 内置
\n99999
\n",
376 | "__v": 0
377 | },
378 | {
379 | "status": 1,
380 | "imgs": [
381 | "image-1559402448049.jpg",
382 | "image-1559402450480.jpg"
383 | ],
384 | "_id": "5ca9e414b49ef916541160ce",
385 | "name": "华硕(ASUS) 飞行堡垒",
386 | "desc": "15.6英寸窄边框游戏笔记本电脑(i7-8750H 8G 256GSSD+1T GTX1050Ti 4G IPS)",
387 | "price": 6799,
388 | "pCategoryId": "5ca9d6c0b49ef916541160bb",
389 | "categoryId": "5ca9db8ab49ef916541160cb",
390 | "detail": "华硕(ASUS) 飞行堡垒6 15.6英寸窄边框游戏笔记本电脑(i7-8750H 8G 256GSSD+1T GTX1050Ti 4G IPS)火陨红黑
\n【4.6-4.7号华硕集体放价,大牌够品质!】1T+256G高速存储组合!超窄边框视野无阻,强劲散热一键启动!
\n",
391 | "__v": 0
392 | },
393 | {
394 | "status": 2,
395 | "imgs": [
396 | "image-1559402436395.jpg"
397 | ],
398 | "_id": "5ca9e4b7b49ef916541160cf",
399 | "name": "你不知道的JS(上卷)",
400 | "desc": "图灵程序设计丛书: [You Don't Know JS:Scope & Closures] JavaScript开发经典入门图书 打通JavaScript的任督二脉",
401 | "price": 35,
402 | "pCategoryId": "0",
403 | "categoryId": "5ca9d6c9b49ef916541160bc",
404 | "detail": "图灵程序设计丛书:你不知道的JavaScript(上卷) [You Don't Know JS:Scope & Closures]
\nJavaScript开发经典入门图书 打通JavaScript的任督二脉 领略语言内部的绝美风光
\n",
405 | "__v": 0
406 | },
407 | {
408 | "status": 2,
409 | "imgs": [
410 | "image-1554638240202.jpg"
411 | ],
412 | "_id": "5ca9e5bbb49ef916541160d0",
413 | "name": "美的(Midea) 213升-BCD-213TM",
414 | "desc": "爆款直降!大容量三口之家优选! *节能养鲜,自动低温补偿,36分贝静音呵护",
415 | "price": 1388,
416 | "pCategoryId": "5ca9d695b49ef916541160ba",
417 | "categoryId": "5ca9d9cfb49ef916541160c4",
418 | "detail": "美的(Midea) 213升 节能静音家用三门小冰箱 阳光米 BCD-213TM(E)
\n【4.8美的大牌秒杀日】爆款直降!大容量三口之家优选! *节能养鲜,自动低温补偿,36分贝静音呵护! *每天不到一度电,省钱又省心!
\n",
419 | "__v": 0
420 | },
421 | {
422 | "status": 1,
423 | "imgs": [
424 | "image-1554638403550.jpg"
425 | ],
426 | "_id": "5ca9e653b49ef916541160d1",
427 | "name": "美的(Midea)KFR-35GW/WDAA3",
428 | "desc": "正1.5匹 变频 智弧 冷暖 智能壁挂式卧室空调挂机",
429 | "price": 2499,
430 | "pCategoryId": "5ca9d695b49ef916541160ba",
431 | "categoryId": "5ca9da1ab49ef916541160c6",
432 | "detail": "美的(Midea)正1.5匹 变频 智弧 冷暖 智能壁挂式卧室空调挂机 KFR-35GW/WDAA3@
\n\n【4.8美的大牌秒杀日】提前加入购物车!2299元成交价!前50名下单送赠品加湿型电风扇,赠完即止!8日0点开抢!更有无风感柜挂组合套购立减500元!猛戳!!
\n",
433 | "__v": 0
434 | }
435 | ]
436 | }
437 | }
438 |
439 | ## 11. 根据ID/Name搜索产品分页列表
440 | ### 请求URL:
441 | http://localhost:5000/manage/product/search?pageNum=1&pageSize=5&productName=T
442 |
443 | ### 请求方式:
444 | GET
445 |
446 | ### 参数类型:
447 | |参数 |是否必选 |类型 |说明
448 | |pageNum |Y |Number |页码
449 | |pageSize |Y |Number |每页条目数
450 | |productName |N |String |根据商品名称搜索
451 | |productDesc |N |String |根据商品描述搜索
452 |
453 | ### 返回示例:
454 | {
455 | "status": 0,
456 | "data": {
457 | "pageNum": 1,
458 | "total": 3,
459 | "pages": 1,
460 | "pageSize": 5,
461 | "list": [
462 | {
463 | "status": 1,
464 | "imgs": [
465 | "image-1559402396338.jpg"
466 | ],
467 | "_id": "5ca9e05db49ef916541160cd",
468 | "name": "联想ThinkPad 翼4809",
469 | "desc": "年度重量级新品,X390、T490全新登场 更加轻薄机身设计9",
470 | "price": 65999,
471 | "pCategoryId": "5ca9d6c0b49ef916541160bb",
472 | "categoryId": "5ca9db9fb49ef916541160cc",
473 | "detail": "想你所需,超你所想!精致外观,轻薄便携带光驱,内置正版office杜绝盗版死机,全国联保两年! 222
\n联想(Lenovo)扬天V110 15.6英寸家用轻薄便携商务办公手提笔记本电脑 定制【E2-9010/4G/128G固态】 2G独显 内置
\n99999
\n",
474 | "__v": 0
475 | },
476 | {
477 | "status": 2,
478 | "imgs": [
479 | "image-1554638240202.jpg"
480 | ],
481 | "_id": "5ca9e5bbb49ef916541160d0",
482 | "name": "美的(Midea) 213升-BCD-213TM",
483 | "desc": "爆款直降!大容量三口之家优选! *节能养鲜,自动低温补偿,36分贝静音呵护",
484 | "price": 1388,
485 | "pCategoryId": "5ca9d695b49ef916541160ba",
486 | "categoryId": "5ca9d9cfb49ef916541160c4",
487 | "detail": "美的(Midea) 213升 节能静音家用三门小冰箱 阳光米 BCD-213TM(E)
\n【4.8美的大牌秒杀日】爆款直降!大容量三口之家优选! *节能养鲜,自动低温补偿,36分贝静音呵护! *每天不到一度电,省钱又省心!
\n",
488 | "__v": 0
489 | },
490 | {
491 | "status": 1,
492 | "imgs": [
493 | "image-1554638676149.jpg",
494 | "image-1554638683746.jpg"
495 | ],
496 | "_id": "5ca9e773b49ef916541160d2",
497 | "name": "联想ThinkPad X1 Carbon",
498 | "desc": "英特尔酷睿i5 14英寸轻薄笔记本电脑(i5-8250U 8G 256GSSD FHD)黑色",
499 | "price": 9999,
500 | "pCategoryId": "5ca9d6c0b49ef916541160bb",
501 | "categoryId": "5ca9db78b49ef916541160ca",
502 | "detail": "联想ThinkPad X1 Carbon 2018(09CD)英特尔酷睿i5 14英寸轻薄笔记本电脑(i5-8250U 8G 256GSSD FHD)黑色
\n年度重量级新品,X390、T490全新登场 更加轻薄机身设计,全面的配置升级,让工作更便捷,让生活更轻松4月9日京东震撼首发,火爆预约
\n",
503 | "__v": 0
504 | }
505 | ]
506 | }
507 | }
508 |
509 | ## 12. 添加商品
510 | ### 请求URL:
511 | http://localhost:5000/manage/product/add
512 |
513 | ### 请求方式:
514 | POST
515 |
516 | ### 参数类型:
517 | |参数 |是否必选 |类型 |说明
518 | |categoryId |Y |string |分类ID
519 | |pCategoryId |Y |string |父分类ID
520 | |name |Y |string |商品名称
521 | |desc |N |string |商品描述
522 | |price |N |string |商品价格
523 | |detail |N |string |商品详情
524 | |imgs |N |array |商品图片名数组
525 |
526 | ### 返回示例:
527 | {
528 | "status": 0,
529 | "data": {
530 | "status": 1,
531 | "imgs": [
532 | "image-1559467198366.jpg"
533 | ],
534 | "_id": "5cf394d29929a304dcc0c6eb",
535 | "name": "商品A",
536 | "desc": "一个笔记本",
537 | "price": 11111,
538 | "detail": "abc
\n",
539 | "pCategoryId": "5ca9d6c0b49ef916541160bb",
540 | "categoryId": "5ca9db78b49ef916541160ca",
541 | "__v": 0
542 | }
543 | }
544 |
545 | ## 13. 更新商品
546 | ### 请求URL:
547 | http://localhost:5000/manage/product/update
548 |
549 | ### 请求方式:
550 | POST
551 |
552 | ### 参数类型:
553 | |参数 |是否必选 |类型 |说明
554 | |_id |Y |string |商品ID
555 | |categoryId |Y |string |分类ID
556 | |pCategoryId |Y |string |父分类ID
557 | |name |Y |string |商品名称
558 | |desc |N |string |商品描述
559 | |price |N |string |商品价格
560 | |detail |N |string |商品详情
561 | |imgs |N |array |商品图片名数组
562 |
563 | ### 返回示例:
564 | {
565 | "status": 0
566 | }
567 |
568 | ## 14. 对商品进行上架/下架处理
569 | ### 请求URL:
570 | http://localhost:5000/manage/product/updateStatus
571 |
572 | ### 请求方式:
573 | POST
574 |
575 | ### 参数类型:
576 |
577 | |参数 |是否必选 |类型 |说明
578 | |productId |Y |string |商品名称
579 | |status |Y |number |商品状态值
580 |
581 | ### 返回示例:
582 | {
583 | "status": 0
584 | }
585 |
586 | ## 15. 上传图片
587 | ### 请求URL:
588 | http://localhost:5000/manage/img/upload
589 |
590 | ### 请求方式:
591 | POST
592 |
593 | ### 参数类型:
594 |
595 | |参数 |是否必选 |类型 |说明
596 | |image |Y |文件 |图片文件
597 |
598 | ### 返回示例:
599 | {
600 | "status": 0,
601 | "data": {
602 | "name": "image-1559466841118.jpg",
603 | "url": "http://localhost:5000/upload/image-1559466841118.jpg"
604 | }
605 | }
606 |
607 | ## 16. 删除图片
608 | ### 请求URL:
609 | http://localhost:5000/manage/img/delete
610 |
611 | ### 请求方式:
612 | POST
613 |
614 | ### 参数类型:
615 |
616 | |参数 |是否必选 |类型 |说明
617 | |name |Y |string |图片文件名
618 |
619 | ### 返回示例:
620 | {
621 | "status": 0
622 | }
623 |
624 | ## 17. 添加角色
625 |
626 | ### 请求URL:
627 | http://localhost:5000/manage/role/add
628 |
629 | ### 请求方式:
630 | POST
631 |
632 | ### 参数类型:
633 | |参数 |是否必选 |类型 |说明
634 | |roleName |Y |string |角色名称
635 |
636 | ### 返回示例:
637 | {
638 | "status": 0,
639 | "data": {
640 | "menus": [],
641 | "_id": "5cf39a319929a304dcc0c6ec",
642 | "name": "角色x",
643 | "create_time": 1559468593702,
644 | "__v": 0
645 | }
646 | }
647 |
648 | ## 18. 获取角色列表
649 | ### 请求URL:
650 | http://localhost:5000/manage/role/list
651 |
652 | ### 请求方式:
653 | GET
654 |
655 | ### 参数类型:
656 | 无
657 |
658 | ### 返回示例:
659 | {
660 | "status": 0,
661 | "data": [
662 | {
663 | "menus": [
664 | "/role",
665 | "/charts/bar",
666 | "/home",
667 | "/category"
668 | ],
669 | "_id": "5ca9eaa1b49ef916541160d3",
670 | "name": "测试",
671 | "create_time": 1554639521749,
672 | "__v": 0,
673 | "auth_time": 1558679920395,
674 | "auth_name": "test007"
675 | },
676 | {
677 | "menus": [
678 | "/role",
679 | "/charts/bar",
680 | "/home",
681 | "/charts/line",
682 | "/category",
683 | "/product",
684 | "/products"
685 | ],
686 | "_id": "5ca9eab0b49ef916541160d4",
687 | "name": "经理",
688 | "create_time": 1554639536419,
689 | "__v": 0,
690 | "auth_time": 1558506990798,
691 | "auth_name": "test008"
692 | },
693 | {
694 | "menus": [
695 | "/home",
696 | "/products",
697 | "/category",
698 | "/product",
699 | "/role"
700 | ],
701 | "_id": "5ca9eac0b49ef916541160d5",
702 | "name": "角色1",
703 | "create_time": 1554639552758,
704 | "__v": 0,
705 | "auth_time": 1557630307021,
706 | "auth_name": "admin"
707 | }
708 | ]
709 | }
710 |
711 | ## 19. 更新角色(给角色设置权限)
712 | ### 请求URL:
713 | http://localhost:5000/manage/role/update
714 |
715 | ### 请求方式:
716 | POST
717 |
718 | ### 参数类型:
719 |
720 | |参数 |是否必选 |类型 |说明
721 | |_id |Y |string |角色ID
722 | |menus |Y |array |权限key数组
723 | |auth_time |Y |number |权限时间
724 | |auth_name |Y |string |权限人姓名
725 |
726 | ### 返回示例:
727 | {
728 | "status": 0,
729 | "data": {
730 | "menus": [
731 | "/role",
732 | "/charts/bar",
733 | "/home",
734 | "/category",
735 | "/user"
736 | ],
737 | "_id": "5ca9eaa1b49ef916541160d3",
738 | "name": "测试",
739 | "create_time": 1554639521749,
740 | "__v": 0,
741 | "auth_time": 1559469116470,
742 | "auth_name": "admin"
743 | }
744 | }
745 |
746 | ## 20. 获取天气信息(支持jsonp)
747 | ### 请求URL:
748 | http://api.map.baidu.com/telematics/v3/weather
749 |
750 | ### 请求方式:
751 | GET
752 |
753 | ### 参数类型:
754 | |参数 |是否必选 |类型 |说明
755 | |location |Y |string |城市名称
756 | |output |Y |string |返回数据格式: json
757 | |ak |Y |string |唯一的应用key(3p49MVra6urFRGOT9s8UBWr2)
758 |
759 | ### 返回示例:
760 | {
761 | "error": 0,
762 | "status": "success",
763 | "date": "2019-06-02",
764 | "results": [
765 | {
766 | "currentCity": "北京",
767 | "pm25": "119",
768 | "index": [
769 | {
770 | "des": "建议着长袖T恤、衬衫加单裤等服装。年老体弱者宜着针织长袖衬衫、马甲和长裤。",
771 | "tipt": "穿衣指数",
772 | "title": "穿衣",
773 | "zs": "舒适"
774 | },
775 | {
776 | "des": "不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。",
777 | "tipt": "洗车指数",
778 | "title": "洗车",
779 | "zs": "不宜"
780 | },
781 | {
782 | "des": "各项气象条件适宜,无明显降温过程,发生感冒机率较低。",
783 | "tipt": "感冒指数",
784 | "title": "感冒",
785 | "zs": "少发"
786 | },
787 | {
788 | "des": "天气较好,赶快投身大自然参与户外运动,尽情感受运动的快乐吧。",
789 | "tipt": "运动指数",
790 | "title": "运动",
791 | "zs": "适宜"
792 | },
793 | {
794 | "des": "紫外线强度较弱,建议出门前涂擦SPF在12-15之间、PA+的防晒护肤品。",
795 | "tipt": "紫外线强度指数",
796 | "title": "紫外线强度",
797 | "zs": "弱"
798 | }
799 | ],
800 | "weather_data": [
801 | {
802 | "date": "周日 06月02日 (实时:30℃)",
803 | "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
804 | "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/leizhenyu.png",
805 | "weather": "多云转雷阵雨",
806 | "wind": "西南风3-4级",
807 | "temperature": "31 ~ 20℃"
808 | },
809 | {
810 | "date": "周一",
811 | "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
812 | "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png",
813 | "weather": "多云",
814 | "wind": "南风微风",
815 | "temperature": "34 ~ 20℃"
816 | },
817 | {
818 | "date": "周二",
819 | "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/leizhenyu.png",
820 | "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/leizhenyu.png",
821 | "weather": "雷阵雨",
822 | "wind": "东风微风",
823 | "temperature": "28 ~ 21℃"
824 | },
825 | {
826 | "date": "周三",
827 | "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
828 | "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png",
829 | "weather": "多云",
830 | "wind": "北风3-4级",
831 | "temperature": "33 ~ 19℃"
832 | }
833 | ]
834 | }
835 | ]
836 | }
837 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 工程说明
2 |
3 | 项目是 react 全栈项目,后端代码存放在 `react-admin-server` 文件夹,前端代码放在 `react-admin-client` 文件夹。
4 |
--------------------------------------------------------------------------------
/react-admin-client/.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 |
--------------------------------------------------------------------------------
/react-admin-client/README.md:
--------------------------------------------------------------------------------
1 | # React 后台管理客户端
2 |
3 | ## 技术栈
4 |
5 | * React 全家桶
6 |
7 | * create-react-app (React脚手架)
8 |
9 | * react-redux(react 组件共享状态数据管理)
10 |
11 | * store(封装localStorage)
12 |
13 | * echarts (百度开源可视化图表库)
14 |
15 | * bizcharts(阿里开源可视化图表库)
16 |
17 | * react-draft-wysiwyg (React 富文本编辑器插件)
18 |
19 | * ES6 (async/await)
20 |
21 | * jsonp(发jsonp请求)
22 |
23 | * axios (封装Ajax请求函数)
24 |
25 | * Ant Design(组件按需加载)
26 |
27 | * proxy (开发阶段利用代理解决跨域)
28 |
29 | * less
30 |
31 | ## 运行
32 |
33 | > 说明:运行前端项目之前,请先开启服务端 app
34 |
35 | 1. `git clone` -- 克隆代码到本地
36 |
37 | 2. `npm install` -- 安装项目所需依赖
38 |
39 | 3. `npm start` -- 开启本地服务运行 app
40 |
--------------------------------------------------------------------------------
/react-admin-client/config-overrides.js:
--------------------------------------------------------------------------------
1 | const { override, fixBabelImports, addLessLoader } = require('customize-cra')
2 | module.exports = override(
3 | fixBabelImports('import', {
4 | libraryName: 'antd',
5 | libraryDirectory: 'es',
6 | style: true,
7 | }),
8 | addLessLoader({
9 | javascriptEnabled: true,
10 | modifyVars: { '@primary-color': '#1DA57A' },
11 | }),
12 | )
--------------------------------------------------------------------------------
/react-admin-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-admin-client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@antv/data-set": "^0.10.2",
7 | "antd": "^3.20.3",
8 | "axios": "^0.19.0",
9 | "babel-plugin-import": "^1.12.0",
10 | "bizcharts": "^3.5.5",
11 | "customize-cra": "^0.2.14",
12 | "draftjs-to-html": "^0.8.4",
13 | "echarts": "^4.2.1",
14 | "echarts-for-react": "^2.0.15-beta.0",
15 | "html-to-draftjs": "^1.4.0",
16 | "jsonp": "^0.2.1",
17 | "less": "^3.9.0",
18 | "less-loader": "^5.0.0",
19 | "react": "^16.8.6",
20 | "react-app-rewired": "^2.1.3",
21 | "react-dom": "^16.8.6",
22 | "react-draft-wysiwyg": "^1.13.2",
23 | "react-redux": "^7.1.1",
24 | "react-router-dom": "^5.0.1",
25 | "react-scripts": "3.0.1",
26 | "redux": "^4.0.4",
27 | "redux-thunk": "^2.3.0",
28 | "store": "^2.0.12"
29 | },
30 | "scripts": {
31 | "start": "react-app-rewired start",
32 | "build": "react-app-rewired build",
33 | "test": "react-app-rewired test",
34 | "eject": "react-scripts eject"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | },
51 | "proxy": "http://localhost:5000",
52 | "devDependencies": {
53 | "redux-devtools-extension": "^2.13.8"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/react-admin-client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-client/public/favicon.ico
--------------------------------------------------------------------------------
/react-admin-client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | React Admin
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/react-admin-client/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 |
--------------------------------------------------------------------------------
/react-admin-client/src/App.js:
--------------------------------------------------------------------------------
1 | // 根组件
2 | import React, { Component } from 'react'
3 | import {HashRouter, Route, Switch} from 'react-router-dom'
4 | import Login from './views/login/Login'
5 | import Admin from './views/admin/Admin'
6 | export default class App extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/react-admin-client/src/api/ajax.js:
--------------------------------------------------------------------------------
1 | // 封装 axios 模块
2 | /*
3 | 优化:
4 | 1.全局统一处理请求出错,外部使用时无需考虑请求错误情况
5 | 2. 请求成功时,外部获取到的直接是接口返回数据,无需调用res.data
6 | */
7 | import {message} from 'antd'
8 | import axios from 'axios'
9 |
10 | function ajax (url, data={}, method='GET') {
11 | return new Promise((resolve, reject) => {
12 | // 执行异步任务
13 | // 定义一个变量用来接收 axios 返回的 promise对象
14 | let promise
15 | if (method === 'GET') {
16 | // 发送get 请求
17 | promise = axios.get(url, {params: data})
18 | } else {
19 | // 发送post 请求
20 | promise = axios.post(url, data)
21 | }
22 | promise.then(res => {
23 | // 请求成功,直接把接口数据(res.data)暴露出去,不要暴露 (res)
24 | resolve(res.data)
25 | }).catch(error => {
26 | // 请求失败, 这里不用 reject(error), 直接处理错误
27 | message.error(error.message)
28 | })
29 | })
30 | }
31 |
32 | // 向外暴露 ajax
33 |
34 | export default ajax
--------------------------------------------------------------------------------
/react-admin-client/src/api/index.js:
--------------------------------------------------------------------------------
1 | // 封装接口
2 |
3 | import ajax from './ajax'
4 | import jsonp from 'jsonp'
5 | import {message} from 'antd'
6 |
7 | // 登录
8 | export function loginIf ({username, password}) { return ajax('/login', {username, password}, 'POST' )}
9 |
10 | // 获取一级/二级分类列表
11 |
12 | export const categoryIf = (parentId) => ajax('/manage/category/list', {parentId})
13 |
14 | // 更新分类信息
15 |
16 | export const updateCategoryIf = (categoryId, categoryName) => ajax('/manage/category/update', {categoryId, categoryName}, 'POST')
17 |
18 | // 添加分类
19 |
20 | export const addCategoryIf = (parentId, categoryName) => ajax('/manage/category/add', {parentId, categoryName}, 'POST')
21 |
22 | // 获取分类名称(单个)
23 |
24 | export const getCategoryIf = (categoryId) => ajax('/manage/category/info', {categoryId})
25 |
26 | // 获取商品分页列表
27 |
28 | export const getProductListIf = (pageNum, pageSize) => ajax('/manage/product/list', {pageNum, pageSize})
29 |
30 | // 根据名称或描述搜索商品列表 (searchType:productName(名称)/productDesc(描述)) searchName:搜索关键字
31 |
32 | export const searchProductIf = (pageNum, pageSize, searchType, searchName) => ajax('/manage/product/search', {pageNum, pageSize, [searchType]: searchName})
33 |
34 | // 上/下架商品
35 |
36 | export const updateStatusIf = (productId, status) => ajax('/manage/product/updateStatus', {productId, status}, 'POST')
37 |
38 | // 删除商品图片
39 |
40 | export const deleteProductImgIf = (name) => ajax('/manage/img/delete', { name }, 'POST')
41 |
42 | // 更新商品信息
43 |
44 | export const updateProductInfoIf = (productInfo) => ajax('/manage/product/update', productInfo, 'POST')
45 |
46 | // 添加商品
47 |
48 | export const addProduct = (product) => ajax('/manage/product/add', product, 'POST')
49 |
50 | // 获取角色列表
51 |
52 | export const getRoles = () => ajax('/manage/role/list')
53 |
54 | // 添加角色
55 |
56 | export const addRole = (roleName) => ajax('/manage/role/add', {roleName}, 'POST')
57 |
58 | // 角色授权
59 |
60 | export const updateRole = (role) => ajax('/manage/role/update', role, 'POST' )
61 |
62 | // 获取用户信息
63 |
64 | export const getUsers = () => ajax('/manage/user/list')
65 |
66 | // 更新或添加用户
67 |
68 | export const addOrUpdateUser = (user) => ajax('/manage/user'+(user.password ? '/add' : '/update'), user, 'POST')
69 |
70 | // 删除用户
71 |
72 | export const deleteUser = (userId) => ajax('/manage/user/delete', {userId}, 'POST')
73 |
74 | // 获取天气
75 | export const weatherIf = (city) => {
76 | return new Promise((resolve, reject) => {
77 | const url = `http://api.map.baidu.com/telematics/v3/weather?location=${city}&output=json&ak=3p49MVra6urFRGOT9s8UBWr2`
78 | jsonp(url, {}, (error, data) => {
79 | if(!error && data.status === 'success') {
80 | // 获取数据成功
81 | const {weather, dayPictureUrl} = data.results[0].weather_data[0]
82 | resolve({weather, dayPictureUrl})
83 | } else {
84 | // 获取数据失败
85 | message.error('获取天气信息失败!')
86 | }
87 | })
88 | })
89 | }
--------------------------------------------------------------------------------
/react-admin-client/src/assets/images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-client/src/assets/images/404.png
--------------------------------------------------------------------------------
/react-admin-client/src/assets/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-client/src/assets/images/bg.jpg
--------------------------------------------------------------------------------
/react-admin-client/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-client/src/assets/images/logo.png
--------------------------------------------------------------------------------
/react-admin-client/src/components/header/Header.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { withRouter } from 'react-router-dom'
3 | import {connect} from 'react-redux'
4 | import {logout} from '../../redux/actions.js'
5 | import { Modal } from 'antd'
6 | import formatDate from '../../utils/dateUtils'
7 | // import menuList from '../../config/menuConfig'
8 | import { weatherIf } from '../../api/index'
9 | import './header.less'
10 | const { confirm } = Modal
11 | class Header extends Component {
12 | state = {
13 | currentTime: formatDate(Date.now()),
14 | weather: '',
15 | dayPictureUrl: ''
16 | }
17 | getTime = () => {
18 | // 每隔一秒钟获取当前时间
19 | this.timer = setInterval(() => {
20 | const currentTime = formatDate(Date.now())
21 | this.setState({currentTime})
22 | }, 1000)
23 | }
24 | // getTitle = (menuList) => {
25 | // const path = this.props.location.pathname.replace(/^\/product[\D]+/, '/product')
26 | // // 通过当前路由找到对应的菜单项名称
27 | // let title
28 | // menuList.forEach(item => {
29 | // if (item.children) {
30 | // // 有子级菜单
31 | // const cItem = item.children.find(item => {return item.key === path})
32 | // if (cItem) title = cItem.title
33 | // } else {
34 | // // 无子级菜单
35 | // if (item.key === path) title = item.title
36 | // }
37 | // })
38 | // return title
39 | // }
40 | getWeather = async () => {
41 | const {weather, dayPictureUrl} = await weatherIf('深圳')
42 | this.setState({weather, dayPictureUrl})
43 | }
44 | logout = () => {
45 | confirm({
46 | content: '确定退出吗?',
47 | okText: '确定',
48 | cancelText: '取消',
49 | onOk:() => {
50 | this.props.logout()
51 | }
52 | })
53 | }
54 | componentWillMount () {
55 | // 获取时间
56 | this.getTime()
57 | // 获取天气
58 | this.getWeather()
59 | }
60 | componentWillUnmount () {
61 | // 清除定时器
62 | clearInterval(this.timer)
63 | }
64 | render() {
65 | // 获取标题
66 | // this.title = this.getTitle(menuList)
67 | this.title = this.props.headTitle
68 | return (
69 |
70 |
71 | 欢迎,{this.props.user.username}
72 |
73 |
74 |
75 |
{this.title}
76 |
77 |
{this.state.currentTime}
78 |

79 |
{this.state.weather}
80 |
81 |
82 |
83 | )
84 | }
85 | }
86 |
87 | export default connect(
88 | state => ({headTitle: state.setHeadTitle, user: state.user}),
89 | {logout}
90 | )(withRouter(Header))
--------------------------------------------------------------------------------
/react-admin-client/src/components/header/header.less:
--------------------------------------------------------------------------------
1 | .Header{
2 | background-color: #fff;
3 | height: 80px;
4 | .Header-top {
5 | height: 40px;
6 | text-align: right;
7 | line-height: 40px;
8 | padding-right: 20px;
9 | border-bottom: 1px solid hsl(161, 70%, 38%);
10 | span{
11 | margin-right: 10px;
12 | }
13 | .link-btn{
14 | background-color: transparent;
15 | border: none;
16 | outline: none;
17 | color: #1da57a;
18 | cursor: pointer;
19 | }
20 | }
21 | .Header-bottom{
22 | height: 40px;
23 | display: flex;
24 | align-items: center;
25 | .Header-bottom-left{
26 | flex-basis: 25%;
27 | text-align: center;
28 | font-size: 20px;
29 | position: relative;
30 | }
31 | .Header-bottom-left::after{
32 | content: '';
33 | position: absolute;
34 | right: 50%;
35 | top: 100%;
36 | transform: translateX(50%);
37 | border-top: 20px solid #fff;
38 | border-right: 20px solid transparent;
39 | border-bottom: 20px solid transparent;
40 | border-left: 20px solid transparent;
41 | }
42 | .Header-bottom-right{
43 | flex-basis: 75%;
44 | text-align: right;
45 | margin-right: 20px;
46 | img{
47 | margin: 0 15px;
48 | width: 30px;
49 | height: 20px;
50 | }
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/react-admin-client/src/components/left-nav/LeftNav.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {connect} from 'react-redux'
3 | import {setHeadTitle} from '../../redux/actions.js'
4 | import { Link, withRouter } from 'react-router-dom'
5 | import { Menu, Icon } from 'antd'
6 | import menuList from '../../config/menuConfig'
7 | import './leftNav.less'
8 | const { SubMenu } = Menu
9 | class LeftNav extends Component {
10 |
11 | /*
12 | 判断当前用户是否对menuList中的item有对应权限
13 | */
14 | hasAuth = (item) => {
15 | /*
16 | 1. 当前用户是 admin ,无需判断,全部能访问;
17 | 2. 当前item有isPublic属性,且为true,无需判断,可以访问
18 | 3. 当前用户有此item的权限,item的key 在 当前用户的权限数组中
19 | 4. 当前用户有此item的孩子权限,显示item和item孩子菜单项
20 | */
21 | const { key, isPublic} = item
22 | const {username} = this.props.user
23 | const menus = this.props.user.role.menus
24 | if (username === 'admin' || isPublic || menus.includes(key)) {
25 | return true
26 | } else if (item.children) {
27 | // 4. 当前用户有此item的孩子权限,显示item和item孩子菜单项
28 | return !!item.children.find(child => menus.includes(child.key))
29 | }
30 | return false
31 | }
32 |
33 | handleClick = ({ item, key, keyPath, domEvent }) => {
34 | this.props.history.push(key)
35 | }
36 | getMenuNodes = (menuList) => {
37 | const path = this.props.location.pathname.replace(/^\/product[\D]+/, '/product')
38 | return (
39 | menuList.map(item => {
40 | // 做一些判断,判断当前item所对应的菜单项,当前用户是否具有权限访问,如果没有,则不显示,动态设置权限问题
41 | if (this.hasAuth(item)) {
42 | if (!item.children) {
43 | if (item.key === path) {
44 | this.props.setHeadTitle(item.title)
45 | }
46 | return (
47 | this.props.setHeadTitle(item.title)}>
48 |
49 | {item.title}
50 |
51 | )
52 | } else {
53 | if (item.children.find(cItem => {
54 | return cItem.key === path
55 | })) {
56 | this.openKey = item.key
57 | }
58 | return (
59 |
63 |
64 | {item.title}
65 |
66 | }
67 | >
68 | {this.getMenuNodes(item.children)}
69 |
70 | )
71 | }
72 | } else {
73 | return null
74 | }
75 | })
76 | )
77 | }
78 | componentWillMount () {
79 | this.menu = this.getMenuNodes(menuList)
80 | console.log(this.menu)
81 | }
82 | render() {
83 | console.log('leftNav--render')
84 | const path = this.props.location.pathname.replace(/^\/product[\D]+/, '/product')
85 | return (
86 |
87 |
88 |

89 |
硅谷管理
90 |
91 |
153 |
154 | )
155 | }
156 | }
157 |
158 | export default connect(
159 | state => ({user: state.user}),
160 | {setHeadTitle}
161 | )(withRouter(LeftNav))
--------------------------------------------------------------------------------
/react-admin-client/src/components/left-nav/leftNav.less:
--------------------------------------------------------------------------------
1 | .left-nav{
2 | .left-nav-header{
3 | display: flex;
4 | align-items: center;
5 | height: 80px;
6 | background-color: #002140;
7 | img{
8 | widows: 40px;
9 | height: 40px;
10 | margin: 0 15px;
11 | }
12 | h1{
13 | color: white;
14 | font-size: 20px;
15 | margin-bottom: 0;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/react-admin-client/src/components/link-button/LinkButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './link-button.less'
3 | export default function LinkButton(props) {
4 | return (
5 |
6 | )
7 | }
8 |
--------------------------------------------------------------------------------
/react-admin-client/src/components/link-button/link-button.less:
--------------------------------------------------------------------------------
1 | .link-button {
2 | background-color: transparent;
3 | border: none;
4 | outline: none;
5 | color: #1da57a;
6 | cursor: pointer;
7 | }
8 |
--------------------------------------------------------------------------------
/react-admin-client/src/config/menuConfig.js:
--------------------------------------------------------------------------------
1 | // 设置左侧菜单项的模块
2 |
3 | const menuList = [
4 | {
5 | title: '首页', // 菜单标题名称
6 | key: '/home', // 对应的path
7 | icon: 'home', // 图标名称
8 | isPublic: true, // 公开的
9 | },
10 | {
11 | title: '商品',
12 | key: '/products',
13 | icon: 'appstore',
14 | children: [ // 子菜单列表
15 | {
16 | title: '品类管理',
17 | key: '/category',
18 | icon: 'bars'
19 | },
20 | {
21 | title: '商品管理',
22 | key: '/product',
23 | icon: 'tool'
24 | },
25 | ]
26 | },
27 |
28 | {
29 | title: '用户管理',
30 | key: '/user',
31 | icon: 'user'
32 | },
33 | {
34 | title: '角色管理',
35 | key: '/role',
36 | icon: 'safety',
37 | },
38 |
39 | {
40 | title: '图形图表',
41 | key: '/charts',
42 | icon: 'area-chart',
43 | children: [
44 | {
45 | title: '柱形图',
46 | key: '/charts/bar',
47 | icon: 'bar-chart'
48 | },
49 | {
50 | title: '折线图',
51 | key: '/charts/line',
52 | icon: 'line-chart'
53 | },
54 | {
55 | title: '饼图',
56 | key: '/charts/pie',
57 | icon: 'pie-chart'
58 | },
59 | ]
60 | }
61 | ]
62 |
63 | export default menuList
64 |
--------------------------------------------------------------------------------
/react-admin-client/src/index.js:
--------------------------------------------------------------------------------
1 | // 入口文件
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import App from './App'
5 | import {Provider} from 'react-redux'
6 | import store from './redux/store'
7 | import storageUtils from './utils/storageUtils'
8 | import memoryUtils from './utils/memoryUtils'
9 |
10 | // 从 localStorage 中读取 product 数据,并保存到内存中,程序一上来就会执行
11 | // const user = storageUtils.getUser() || {}
12 | const product = storageUtils.getProduct() || {}
13 | // memoryUtils.user = user
14 | memoryUtils.product = product
15 | ReactDOM.render(
16 |
17 |
18 | ,
19 | document.getElementById('root')
20 | )
--------------------------------------------------------------------------------
/react-admin-client/src/redux/action-types.js:
--------------------------------------------------------------------------------
1 | // 包含多个 action 对象 type 属性值的变量模块
2 |
3 | export const SET_HEAD_TITLE = 'setHeadTitle' // 设置头部标题
4 | export const RECEIVE_USER = 'receive_user' // 接收用户信息
5 | export const ERROR_MSG = 'error_msg' // 保存错误信息
6 | export const RESET_USER = 'reset_user' // 重置 user 信息
--------------------------------------------------------------------------------
/react-admin-client/src/redux/actions.js:
--------------------------------------------------------------------------------
1 | // 包含多个 action creator 函数模块
2 | import {SET_HEAD_TITLE, RECEIVE_USER, ERROR_MSG, RESET_USER} from './action-types'
3 | import {loginIf} from '../api/index'
4 | import storageUtils from '../utils/storageUtils'
5 | // 同步action
6 | export const setHeadTitle = (HeadTitle) => ({type: SET_HEAD_TITLE, data: HeadTitle})
7 |
8 | // 接收用户信息的同步action
9 |
10 | export const receiveUser = (user) => ({type: RECEIVE_USER, user})
11 |
12 | // 保存错误信息的同步 action
13 |
14 | export const errorMsg = (errorMsg) => ({type: ERROR_MSG, errorMsg})
15 |
16 | // 封装一个重置 user 状态的同步action
17 | export const logout = () => {
18 | // 清除local storage 中的数据
19 | storageUtils.removeUser()
20 | // 返回重置user数据状态的action
21 | return ({type: RESET_USER})
22 | }
23 |
24 | // 封装一个异步登录 action
25 |
26 | export const login = (username, password) => {
27 | return async dispatch => {
28 | // 1. 做一些异步登录处理
29 | const ret = await loginIf({username, password})
30 | if (ret.status === 0) {
31 | // 保存用户信息到 storage
32 | const user = ret.data
33 | storageUtils.savaUser(user)
34 | // 登录成功,分发一个接收用户信息的action
35 | dispatch(receiveUser(user))
36 | } else {
37 | // 登录失败,分发一个保存错误信息的action
38 | const errormsg = ret.msg
39 | dispatch(errorMsg(errormsg))
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/react-admin-client/src/redux/reducer.js:
--------------------------------------------------------------------------------
1 | // 包含多个 reducer 函数模块。
2 | import {SET_HEAD_TITLE, RECEIVE_USER, ERROR_MSG, RESET_USER} from './action-types'
3 | import {combineReducers} from 'redux'
4 | import storageUtils from '../utils/storageUtils'
5 |
6 | // 设置头部标题的 reducer 函数 =》 管理 redux 中的 头部标题 状态
7 | const initHeadTitle = ''
8 | const setHeadTitle = (state=initHeadTitle, action) => {
9 | switch (action.type) {
10 | case SET_HEAD_TITLE:
11 | return action.data
12 | default:
13 | return state
14 | }
15 | }
16 |
17 | // 管理用户数据状态的 reducer 函数
18 | const initUser = storageUtils.getUser() ? storageUtils.getUser() : {}
19 | const user = (state=initUser, action) => {
20 | switch (action.type) {
21 | case RECEIVE_USER:
22 | return action.user
23 | case ERROR_MSG:
24 | return {...state, errorMsg: action.errorMsg}
25 | case RESET_USER:
26 | return {}
27 | default:
28 | return state
29 | }
30 | }
31 |
32 | // 将多个 reducer 函数包装成一个 最终的 reducer 函数,最终 redux 中的 store 状态数据结构 为:{setHeadTitle: xxx, user: {}}
33 | export default combineReducers({
34 | setHeadTitle,
35 | user
36 | })
--------------------------------------------------------------------------------
/react-admin-client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | // redux 核心 状态管理 store 模块
2 | import {createStore, applyMiddleware} from 'redux'
3 | import {composeWithDevTools} from 'redux-devtools-extension'
4 | import reducer from './reducer'
5 | import thunk from 'redux-thunk'
6 | export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
--------------------------------------------------------------------------------
/react-admin-client/src/utils/constance.js:
--------------------------------------------------------------------------------
1 | // 包含一些常量的模块
2 |
3 | export const PAGE_SIZE = 5 // 每页显示的条数
4 | export const BASE_IMG_URL = 'http://localhost:5000/upload/' // 图片url基地址
--------------------------------------------------------------------------------
/react-admin-client/src/utils/dateUtils.js:
--------------------------------------------------------------------------------
1 | // 格式化时间工具模块
2 |
3 | export default function (time) {
4 | if (!time) return ''
5 | let date = new Date(time)
6 | return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours()
7 | + ':' + date.getMinutes() + ':' + date.getSeconds()
8 | }
--------------------------------------------------------------------------------
/react-admin-client/src/utils/memoryUtils.js:
--------------------------------------------------------------------------------
1 | // 用来在内存中保存数据的工具模块
2 |
3 | export default {
4 | user: {}, // 用一个 user 对象来保存用户的个人信息
5 | product: {} // 用一个 product 对象来保存商品的信息
6 | }
--------------------------------------------------------------------------------
/react-admin-client/src/utils/storageUtils.js:
--------------------------------------------------------------------------------
1 | // 保存数据到 localstorage 的工具 模块
2 | import store from 'store'
3 | const USER_KEY = 'user_key'
4 | const PRODUCT_KEY = 'product'
5 | export default {
6 | // 保存
7 | savaUser (user) {
8 | store.set(USER_KEY, user)
9 | },
10 |
11 | // 删除
12 | removeUser () {
13 | store.remove(USER_KEY)
14 | },
15 |
16 | // 获取
17 |
18 | getUser () {
19 | return store.get(USER_KEY)
20 | },
21 |
22 | // 保存商品
23 | saveProduct (product) {
24 | store.set(PRODUCT_KEY, product)
25 | },
26 |
27 | // 获取商品
28 | getProduct () {
29 | return store.get(PRODUCT_KEY)
30 | }
31 | }
--------------------------------------------------------------------------------
/react-admin-client/src/views/admin/Admin.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Redirect, Route, Switch } from 'react-router-dom'
3 | import {connect} from 'react-redux'
4 | import { Layout } from 'antd'
5 | import Header from '../../components/header/Header'
6 | import LeftNav from '../../components/left-nav/LeftNav'
7 | import Home from '../../views/home/Home'
8 | import Category from '../../views/category/Category'
9 | import User from '../../views/user/User'
10 | import Product from '../../views/product/Product'
11 | import Role from '../../views/role/Role'
12 | import Pie from '../../views/charts/Pie'
13 | import Line from '../../views/charts/Line'
14 | import Bar from '../../views/charts/Bar'
15 | import NotFound from '../not-found/NotFound'
16 | const { Footer, Sider, Content } = Layout
17 | class Admin extends Component {
18 | render() {
19 | if (!this.props.user._id || !this.props.user.username) {
20 | // 没有登录,内存中无user数据
21 | return
22 | }
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 | }
48 |
49 | export default connect(
50 | state => ({user: state.user})
51 | )(Admin)
--------------------------------------------------------------------------------
/react-admin-client/src/views/category/AddForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Form, Input, Select } from 'antd'
3 | import PropTypes from 'prop-types'
4 | const Item = Form.Item
5 | const Option = Select.Option
6 | class AddForm extends Component {
7 | static propTypes = {
8 | category: PropTypes.array.isRequired,
9 | parentId: PropTypes.string.isRequired,
10 | setForm: PropTypes.func.isRequired
11 | }
12 | componentWillMount () {
13 | this.props.setForm(this.props.form)
14 | }
15 | render() {
16 | const { getFieldDecorator } = this.props.form
17 | const { parentId, category } = this.props
18 | return (
19 |
47 | )
48 | }
49 | }
50 |
51 | export default Form.create()(AddForm)
--------------------------------------------------------------------------------
/react-admin-client/src/views/category/Category.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {Card, Table, Button, Icon, message, Modal} from 'antd'
3 | import {categoryIf, addCategoryIf, updateCategoryIf} from '../../api/index'
4 | import AddForm from './AddForm'
5 | import UpdateForm from './UpdateForm'
6 | export default class Category extends Component {
7 | state = {
8 | category: [], // 一级分类列表数据
9 | subCategory: [], // 二级分类列表数据
10 | parentId: '0',
11 | parentName: '', // 当前二级分类的父类类名,默认一级分类没有父级分类
12 | loading: false, // 当前是否正在请求数据中
13 | visible: 0 // 对话框的显示与隐藏,0 :隐藏;1: 显示添加分类; 2: 显示修改分类
14 | }
15 | /*
16 | 显示添加分类对话框
17 | */
18 | showAdd = () => {
19 | this.setState({visible: 1})
20 | }
21 | /*
22 | 显示修改分类对话框
23 | */
24 | showUpdate = (category) => {
25 | this.setState({visible: 2})
26 | // 将当前分类名字保存到组件实例对象中
27 | this.categoryName = category.name
28 | this.categoryId = category._id
29 | }
30 | /*
31 | 添加分类
32 | */
33 | addCategory = () => {
34 | // 判断用户输入的是否符合表单验证规则
35 | this.form.validateFields(async (errors, values) => {
36 | if (!errors) {
37 | // 验证通过,关闭对话框
38 | this.form.resetFields()
39 | this.setState({visible: 0})
40 | const {parentId, categoryName} = values
41 | // 发送Ajax添加分类
42 | const ret = await addCategoryIf(parentId, categoryName)
43 | if (ret.status === 0) {
44 | // 添加分类成功
45 | if (parentId === '0') {
46 | // 说明添加的是一级分类列表,需要重新获取一级分类列表
47 | this.getCategory('0')
48 | } else if (parentId === this.state.parentId) {
49 | // 说明在当前二级分类列表下添加新的二级分类,需要重新获取当前二级分类列表
50 | this.getCategory()
51 | }
52 |
53 | }
54 | } else {
55 | console.log(errors)
56 | }
57 | })
58 | }
59 | /*
60 | 修改分类
61 | */
62 | updateCategory = () => {
63 | // 先判断表单数据是否验证成功
64 | this.form.validateFields(async (errors, values) => {
65 | if (!errors) {
66 | // 验证成功,隐藏对话框
67 | this.setState({visible: 0})
68 | this.form.resetFields()
69 | const {categoryName} = values
70 | const ret = await updateCategoryIf(this.categoryId, categoryName)
71 | if (ret.status === 0) {
72 | // 修改成功,重新获取分类列表信息
73 | this.getCategory()
74 | }
75 | } else {
76 | console.log(errors)
77 | }
78 | })
79 | }
80 | /*
81 | 隐藏对话框
82 | */
83 | handleCancel = () => {
84 | // 清空 form 表单的数据
85 | this.form.resetFields()
86 | // 设置对话框为隐藏状态
87 | this.setState({visible: 0})
88 | }
89 | /*
90 | 获取一级/二级分类
91 | */
92 | getCategory = async (parentId) => {
93 | // 如果没有传 parentId,则等于 state 中的 parentId 值
94 | parentId = parentId || this.state.parentId
95 | // 请求数据之前显示loading
96 | this.setState({loading: true})
97 | const ret = await categoryIf(parentId)
98 | // 请求数据完成之后,隐藏loading
99 | this.setState({loading: false})
100 | if (ret.status === 0) {
101 | if (parentId === '0') {
102 | // 获取到的是一级分类列表数据
103 | const category = ret.data
104 | this.setState({category})
105 | } else {
106 | // 获取到的是二级分类列表数据
107 | const subCategory = ret.data
108 | this.setState({subCategory})
109 | }
110 | } else {
111 | // 获取分类列表数据失败(此时,请求还是成功的)
112 | message.error('获取列表数据失败')
113 | }
114 | }
115 |
116 | /*
117 | 初始化 table 的列的数组
118 |
119 | */
120 | initColumn = () => {
121 | this.columns = [
122 | {
123 | title: '分类的名称',
124 | dataIndex: 'name',
125 | },
126 | {
127 | title: '操作',
128 | render: (category) => {
129 | return (
130 |
131 |
132 | {this.state.parentId === '0' ? : null}
133 |
134 | )
135 | },
136 | width: 300
137 | }
138 | ]
139 | }
140 | /*
141 | 显示一级分类列表
142 |
143 | */
144 | showCategory = () => {
145 | // 改变 state 中的 parentId 为 '0'
146 | this.setState({parentId: '0'})
147 | }
148 | /*
149 | 显示二级分类
150 | */
151 | showSubCategory = (category) => {
152 | // 修改 state 中的 parentId, parentName
153 | const parentId = category._id
154 | const parentName = category.name
155 | this.setState({parentId, parentName}, () => {
156 | // react 更新数据是异步完成的,所以只能在回调中调用 getCategory() 获取二级分类
157 | this.getCategory()
158 | })
159 | }
160 | componentWillMount () {
161 | // 获取一级分类列表,不传参数,默认 parentId 为 '0'
162 | this.getCategory()
163 | // 初始化 table 的列的数组
164 | this.initColumn()
165 | }
166 |
167 | render() {
168 | const {category, loading, parentId, parentName, subCategory, visible} = this.state
169 | const title = parentId === '0' ? '一级分类列表' : ({parentName})
170 | const extra = ()
171 | return (
172 |
173 |
181 |
189 | {this.form = form}}
193 | >
194 |
195 |
203 | {this.form = form}} categoryName={this.categoryName === undefined ? undefined : this.categoryName}>
204 |
205 |
206 | )
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/category/UpdateForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Form, Input } from 'antd'
3 | import PropTypes from 'prop-types'
4 | class UpdateForm extends Component {
5 | static propTypes = {
6 | setForm: PropTypes.func.isRequired,
7 | categoryName: PropTypes.string.isRequired
8 | }
9 | componentWillMount () {
10 | this.props.setForm(this.props.form)
11 | }
12 | render() {
13 | const { getFieldDecorator } = this.props.form
14 | const { categoryName } = this.props
15 | return (
16 |
18 | {
19 | getFieldDecorator(
20 | 'categoryName', {
21 | rules: [{required: true, message: '分类名称必须输入!'}],
22 | initialValue: categoryName
23 | }
24 | )(
25 |
26 | )
27 | }
28 |
29 |
30 | )
31 | }
32 | }
33 |
34 | export default Form.create()(UpdateForm)
--------------------------------------------------------------------------------
/react-admin-client/src/views/charts/Bar.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | Card, Button
4 | } from 'antd'
5 | import ReactEcharts from 'echarts-for-react'
6 | export default class Bar extends Component {
7 |
8 | state = {
9 | sales: [60, 20, 70, 30, 45, 60, 54], // 每月的销售量
10 | stores: [20, 34, 18, 50, 49, 20, 33] // 每月的库存
11 | }
12 |
13 | /*
14 | 更新库存和销量,(库存和销量在原先基础上 加 1)
15 | */
16 |
17 | update = () => {
18 | this.setState((state) => ({
19 | sales: state.sales.map(item => item + 1),
20 | stores: state.stores.reduce((prev, curr) => {
21 | prev.push(curr + 1)
22 | return prev
23 | }, [])
24 | }))
25 | }
26 |
27 | /*
28 | 指定图表的配置项和数据
29 | */
30 | getOption = (sales, stores) => {
31 | return {
32 | title: { text: 'ECharts 入门实例' },
33 | tooltip: {},
34 | xAxis: {
35 | type: 'category',
36 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
37 | },
38 | legend: {
39 | data: ['库存', '销量']
40 | },
41 | yAxis: {
42 | type: 'value'
43 | },
44 | series: [
45 | {
46 | name: '销量',
47 | type: 'bar',
48 | data: sales
49 | },
50 | {
51 | name: '库存',
52 | type: 'bar',
53 | data: stores
54 | }
55 | ]
56 | }
57 | }
58 |
59 | render() {
60 | const {stores, sales} = this.state
61 | return (
62 | 更新}
64 | >
65 |
68 |
69 | )
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/charts/Line.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 | import {Button, Card} from 'antd'
4 | export default class Line extends Component {
5 |
6 | state = {
7 | sales: [60, 20, 70, 30, 45, 60, 54], // 每月的销售量
8 | stores: [20, 34, 18, 50, 49, 20, 33] // 每月的库存
9 | }
10 |
11 | /*
12 | 更新库存和销量,(库存和销量在原先基础上 加 1)
13 | */
14 |
15 | update = () => {
16 | this.setState((state) => ({
17 | sales: state.sales.map(item => item + 1),
18 | stores: state.stores.reduce((prev, curr) => {
19 | prev.push(curr + 1)
20 | return prev
21 | }, [])
22 | }))
23 | }
24 |
25 |
26 | /*
27 | 指定图表的配置项和数据
28 | */
29 |
30 | getOption = (sales, stores) => ({
31 | title: {
32 | text: 'ECharts 入门实例'
33 | },
34 | tooltip: {},
35 | legend: {
36 | data: ['库存', '销量']
37 | },
38 | xAxis: {
39 | type: 'category',
40 | data: ['Mon', {value: 'Tue', textStyle: {fontSize: 20, color: '#1DA57A'}}, 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
41 | },
42 | yAxis: {
43 | type: 'value'
44 | },
45 | series: [
46 | {
47 | name: '库存',
48 | type: 'line',
49 | data: stores,
50 | itemStyle: {
51 | color: '#1DA57A'
52 | }
53 | },
54 | {
55 | name: '销量',
56 | type: 'line',
57 | data: sales
58 | }
59 | ]
60 | })
61 |
62 | render() {
63 | const {sales, stores} = this.state
64 | return (
65 | 更新}
67 | >
68 |
71 |
72 | )
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/charts/Pie.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {Card} from 'antd'
3 | import ReactEcharts from 'echarts-for-react'
4 | export default class Pie extends Component {
5 |
6 |
7 | /*
8 | 指定图表的配置项和数据
9 | */
10 |
11 | getOption = (n) => {
12 | if (n === 1) {
13 | // 饼图 1 提供配置对象
14 | return ({
15 | title : {
16 | text: '某站点用户访问来源',
17 | subtext: '纯属虚构',
18 | x:'center'
19 | },
20 | tooltip : {
21 | trigger: 'item',
22 | formatter: "{a}
{b} : {c} ({d}%)"
23 | },
24 | legend: {
25 | orient: 'vertical',
26 | left: 'left',
27 | data: ['直接访问','邮件营销','联盟广告','视频广告','搜索引擎']
28 | },
29 | series : [
30 | {
31 | name: '访问来源',
32 | type: 'pie',
33 | radius : '55%',
34 | center: ['50%', '60%'],
35 | data: [
36 | {value:335, name:'直接访问'},
37 | {value:310, name:'邮件营销'},
38 | {value:234, name:'联盟广告'},
39 | {value:135, name:'视频广告'},
40 | {value:1548, name:'搜索引擎'}
41 | ],
42 | itemStyle: {
43 | shadowBlur: 10,
44 | shadowOffsetX: 0,
45 | shadowColor: 'rgba(0, 0, 0, 0.5)'
46 | }
47 | }
48 | ]
49 | })
50 | } else {
51 | // 饼图 2 提供配置对象
52 | return ({
53 | backgroundColor: '#2c343c',
54 | title: {
55 | text: 'Customized Pie',
56 | left: 'center',
57 | top: 20,
58 | textStyle: {
59 | color: '#ccc'
60 | }
61 | },
62 | tooltip : {
63 | trigger: 'item',
64 | formatter: "{a}
{b} : {c} ({d}%)"
65 | },
66 | visualMap: {
67 | show: false,
68 | min: 80,
69 | max: 600,
70 | inRange: {
71 | colorLightness: [0, 1]
72 | }
73 | },
74 | series : [
75 | {
76 | name:'访问来源',
77 | type:'pie',
78 | radius : '55%',
79 | center: ['50%', '50%'],
80 | data:[
81 | {value:335, name:'直接访问'},
82 | {value:310, name:'邮件营销'},
83 | {value:274, name:'联盟广告'},
84 | {value:235, name:'视频广告'},
85 | {value:400, name:'搜索引擎'}
86 | ].sort(function (a, b) { return a.value - b.value }),
87 | roseType: 'radius',
88 | label: {
89 | normal: {
90 | textStyle: {
91 | color: 'rgba(255, 255, 255, 0.3)'
92 | }
93 | }
94 | },
95 | labelLine: {
96 | normal: {
97 | lineStyle: {
98 | color: 'rgba(255, 255, 255, 0.3)'
99 | },
100 | smooth: 0.2,
101 | length: 10,
102 | length2: 20
103 | }
104 | },
105 | itemStyle: {
106 | normal: {
107 | color: '#c23531',
108 | shadowBlur: 200,
109 | shadowColor: 'rgba(0, 0, 0, 0.5)'
110 | }
111 | },
112 | animationType: 'scale',
113 | animationEasing: 'elasticOut',
114 | animationDelay: function (idx) {
115 | return Math.random() * 200
116 | }
117 | }
118 | ]
119 | })
120 | }
121 | }
122 | render() {
123 | return (
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | )
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/home/Bar.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | Chart,
4 | Axis,
5 | Geom,
6 | Tooltip
7 | } from 'bizcharts'
8 | export default class Bar extends Component {
9 | render() {
10 | const data = [
11 | {
12 | year: "1月",
13 | sales: 38
14 | },
15 | {
16 | year: "2月",
17 | sales: 52
18 | },
19 | {
20 | year: "3月",
21 | sales: 61
22 | },
23 | {
24 | year: "4月",
25 | sales: 145
26 | },
27 | {
28 | year: "5月",
29 | sales: 48
30 | },
31 | {
32 | year: "6月",
33 | sales: 38
34 | },
35 | {
36 | year: "7月",
37 | sales: 28
38 | },
39 | {
40 | year: "8月",
41 | sales: 38
42 | },
43 | {
44 | year: "59月",
45 | sales: 68
46 | },
47 | {
48 | year: "10月",
49 | sales: 38
50 | },
51 | {
52 | year: "11月",
53 | sales: 58
54 | },
55 | {
56 | year: "12月",
57 | sales: 38
58 | }
59 | ]
60 | const cols = {
61 | sales: {
62 | tickInterval: 20
63 | }
64 | }
65 | return (
66 |
67 |
68 |
69 |
74 |
75 |
76 | )
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/home/Home.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Card, Icon, Statistic, DatePicker, Timeline } from 'antd'
3 | import moment from 'moment'
4 | import Line from './Line'
5 | import Bar from './Bar'
6 | import './home.less'
7 | const {RangePicker} = DatePicker
8 | const {Item} = Timeline
9 | const dateFormat = 'YYYY/MM/DD'
10 | export default class Home extends Component {
11 |
12 | state = {
13 | isVisited: true // 默认展示访问量选项卡
14 | }
15 |
16 | handleChange = (flag) => {
17 | this.setState({
18 | isVisited: flag
19 | })
20 | }
21 | render() {
22 | const {isVisited} = this.state
23 | return (
24 |
25 | } style={{ width: 250, float: "left"}} headStyle={{color: 'rgba(0, 0, 0, 0.45)'}}>
26 |
31 | %
}
36 | />
37 | %}
42 | />
43 |
44 |
45 |
48 | 访问量
50 | 销售量
51 | }
52 | extra={}
53 | >
54 | }
59 | >
60 |
61 |
62 | }
65 | className="home-table-right"
66 | >
67 |
68 | - 新版本迭代会
69 | - 完成网站设计初版
70 | -
71 |
联调接口
72 | 功能验收
73 |
74 | -
75 |
登录功能设计
76 | 权限验证
77 | 页面排版
78 |
79 |
80 |
81 |
82 |
83 | )
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/home/Line.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {
3 | Chart,
4 | Geom,
5 | Axis,
6 | Tooltip,
7 | Legend,
8 | } from "bizcharts"
9 | import DataSet from "@antv/data-set"
10 |
11 | export default class Line extends React.Component {
12 | render() {
13 | const data = [
14 | {
15 | month: "Jan",
16 | a: 7.0,
17 | b: 3.9,
18 | c: 5.9
19 | },
20 | {
21 | month: "Feb",
22 | a: 6.9,
23 | b: 4.2,
24 | c: 1.9
25 | },
26 | {
27 | month: "Mar",
28 | a: 9.5,
29 | b: 5.7,
30 | c: 3.9
31 | },
32 | {
33 | month: "Apr",
34 | a: 14.5,
35 | b: 8.5,
36 | c: 5.5
37 | },
38 | {
39 | month: "May",
40 | a: 18.4,
41 | b: 11.9,
42 | c: 8.9
43 | },
44 | {
45 | month: "Jun",
46 | a: 21.5,
47 | b: 15.2,
48 | c: 10.0
49 | },
50 | {
51 | month: "Jul",
52 | a: 25.2,
53 | b: 17.0,
54 | c: 12.9
55 | },
56 | {
57 | month: "Aug",
58 | a: 26.5,
59 | b: 16.6,
60 | c: 15.9
61 | },
62 | {
63 | month: "Sep",
64 | a: 23.3,
65 | b: 14.2,
66 | c: 20.7
67 | },
68 | {
69 | month: "Oct",
70 | a: 18.3,
71 | b: 10.3,
72 | c: 25.9
73 | },
74 | {
75 | month: "Nov",
76 | a: 13.9,
77 | b: 6.6,
78 | c: 30.9
79 | },
80 | {
81 | month: "Dec",
82 | a: 9.6,
83 | b: 4.8,
84 | c: 35.9
85 | }
86 | ]
87 | const ds = new DataSet()
88 | const dv = ds.createView().source(data)
89 | dv.transform({
90 | type: "fold",
91 | fields: ["a", "b", "c"],
92 | // 展开字段集
93 | key: "city",
94 | // key字段
95 | value: "temperature" // value字段
96 | })
97 | const cols = {
98 | month: {
99 | range: [0, 1]
100 | }
101 | }
102 | return (
103 |
104 |
105 |
106 |
107 | {
111 | return `${val}℃`
112 | }
113 | }}
114 | />
115 |
120 |
127 |
138 |
139 |
140 | )
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/home/home.less:
--------------------------------------------------------------------------------
1 | .home{
2 | padding: 24px;
3 | background: #fff;
4 | min-height: 850px;
5 | .home-content {
6 | position: absolute;
7 | top: 420px;
8 | width: 76%;
9 | border: 1px solid #e8e8e8;
10 | .home-menu {
11 | font-size: 20px;
12 | span {
13 | cursor: pointer;
14 | }
15 | .home-menu-active {
16 | border-bottom: 2px solid #1DA57A;
17 | color: #1DA57A;
18 | padding: 0 0 16px 0;
19 | }
20 | .home-menu-visited {
21 | margin-right: 40px;
22 | }
23 | }
24 | .home-table-left {
25 | float: left;
26 | width: 60%;
27 | }
28 | .home-table-right {
29 | float: right;
30 | width: 330px;
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/react-admin-client/src/views/login/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {Redirect} from 'react-router-dom'
3 | import {connect} from 'react-redux'
4 | import {login} from '../../redux/actions'
5 | import { Form, Icon, Input, Button} from 'antd'
6 | // import storageUtils from '../../utils/storageUtils'
7 | // import memoryUtils from '../../utils/memoryUtils'
8 | import './login.less'
9 | // import { loginIf } from '../../api/index'
10 | class Login extends Component {
11 | handleSubmit = (e) => {
12 | //表单有默认提交行为,阻止事件默认行为
13 | e.preventDefault()
14 | // 统一对表单数据进行一次验证
15 | this.props.form.validateFields((error, values) => {
16 | if (error) {
17 | console.log(error)
18 | } else {
19 | // 前台表单验证通过, 发送Ajax请求登录用户
20 | const {userName, password} = values
21 | this.props.login(userName, password)
22 | // 登录成功,跳转路由 '/home'
23 | this.props.history.replace('/home')
24 | }
25 | }
26 | )
27 | }
28 | render() {
29 | if (this.props.user._id && this.props.user.username) {
30 | // 用户已经登录,跳转到管理页(/home)
31 | return
32 | }
33 | const errorMsg = this.props.user.errorMsg
34 | const {getFieldDecorator} = this.props.form
35 | return (
36 |
37 |
38 |
39 | 后台管理系统
40 |
41 |
42 | {errorMsg}
43 | 用户登录
44 |
46 | {
47 | getFieldDecorator('userName', {
48 | rules: [{max: 12, message: '用户名至多12位'}, {min: 4, message: '用户名至少4位'}, {required: true, message: '用户名必须填写'}, {whitespace: true, message: '用户名不能包含空格'}],
49 | initialValue: 'admin'
50 | })(
51 | }
53 | placeholder="用户名"
54 | />
55 | )
56 | }
57 |
58 |
59 | {
60 | getFieldDecorator('password', {
61 | rules: [{max: 20, message: '密码至多20位'}, {min: 4, message: '密码至少4位'}, {required: true, message: '密码必须填写'}, {whitespace: true, message: '密码不能包含空格'}, {pattern: /^[A-Za-z_0-9]+$/, message: '密码必须由字母下划线或数字组成'}]
62 | })(
63 | }
65 | type="password"
66 | placeholder="密码"
67 | />
68 | )
69 | }
70 |
71 |
72 |
75 |
76 |
77 |
78 |
79 | )
80 | }
81 | }
82 | const WrapLogin = Form.create()(Login)
83 |
84 | export default connect(
85 | state => ({user: state.user}),
86 | {login}
87 | )(WrapLogin)
88 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/login/login.less:
--------------------------------------------------------------------------------
1 | .login{
2 | width: 100%;
3 | height: 100%;
4 | background: url(/images/bg.jpg);
5 | background-size: 100% 100%;
6 | .login-header{
7 | height: 80px;
8 | background-color: rgba(31, 20, 13, 0.5);
9 | display: flex;
10 | align-items: center;
11 | img{
12 | width: 40px;
13 | height: 40px;
14 | margin: 0 15px 0 50px;
15 | }
16 | h1{
17 | font-size: 30px;
18 | color: white;
19 | }
20 | }
21 | .login-content{
22 | position: relative;
23 | width: 400px;
24 | height: 300px;
25 | background-color: white;
26 | margin: 50px auto;
27 | padding: 20px 40px;
28 | position: relative;
29 | .error-msg{
30 | visibility: hidden;
31 | position: absolute;
32 | left: 0;
33 | top: 0;
34 | text-align: center;
35 | width: 100%;
36 | background-color: #f60c1a;
37 | color: #fff;
38 | font-size: 16px;
39 | transform: translateY(-30px);
40 | transition: all .3s;
41 | &.show{
42 | visibility: visible;
43 | transform: translateY(0);
44 | }
45 | }
46 | h2{
47 | font-size: 30px;
48 | text-align: center;
49 | font-weight: 600;
50 | margin-bottom: 20px;
51 | }
52 | .login-form{
53 | .login-form-button{
54 | width: 100%;
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/react-admin-client/src/views/not-found/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {connect} from 'react-redux'
3 | import {setHeadTitle} from '../../redux/actions.js'
4 | import {Row, Col, Button} from 'antd'
5 | import './NotFound.less'
6 | class NotFound extends Component {
7 |
8 | goHome = () => {
9 | // 跳转到首页
10 | this.props.setHeadTitle('首页')
11 | this.props.history.replace('/home')
12 | }
13 | render() {
14 | return (
15 |
16 |
17 |
18 | 404
19 | 抱歉,你要的页面暂时失联了...
20 |
21 |
22 |
23 | )
24 | }
25 | }
26 |
27 | export default connect(
28 | state => ({}),
29 | {setHeadTitle}
30 | )(NotFound)
--------------------------------------------------------------------------------
/react-admin-client/src/views/not-found/NotFound.less:
--------------------------------------------------------------------------------
1 | .not-found{
2 | background-color: #f0f2f5;
3 | height: 100%;
4 | .left{
5 | height: 100%;
6 | background: url('/images/404.png') no-repeat center;
7 | }
8 | .right{
9 | padding-left: 50px;
10 | height: 100%;
11 | div{
12 | margin-top: 50px;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/react-admin-client/src/views/product/Product.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {Switch, Route} from 'react-router-dom'
3 | import ProductDetail from './detail'
4 | import ProductHome from './home'
5 | import ProductAddUpdate from './add-update'
6 | import './product.less'
7 | import NotFound from '../not-found/NotFound'
8 | export default class Product extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/product/RichTextEditor.jsx:
--------------------------------------------------------------------------------
1 | // 富文本编辑器组件
2 | import React, { Component } from 'react'
3 | import propTypes from 'prop-types'
4 | import { EditorState, convertToRaw, ContentState } from 'draft-js'
5 | import { Editor } from 'react-draft-wysiwyg'
6 | import draftToHtml from 'draftjs-to-html'
7 | import htmlToDraft from 'html-to-draftjs'
8 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
9 |
10 | export default class RichTextEditor extends Component {
11 | static propTypes = {
12 | detail: propTypes.string
13 | }
14 |
15 | constructor (props) {
16 | super(props)
17 | const html = props.detail
18 | if (html) {
19 | // 如果商品详情有内容,则显示之前商品详情内容
20 | const contentBlock = htmlToDraft(html)
21 | if (contentBlock) {
22 | const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks)
23 | const editorState = EditorState.createWithContent(contentState)
24 | this.state = { editorState }
25 | }
26 | } else {
27 | // 商品无详情
28 | this.state = {editorState: EditorState.createEmpty()}
29 | }
30 | }
31 |
32 | /*
33 | 编辑器内容改变的回调
34 | */
35 | onEditorStateChange = (editorState) => {
36 | this.setState({
37 | editorState
38 | })
39 | }
40 |
41 | getDetail = () => {
42 | return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))
43 | }
44 |
45 | render() {
46 | const { editorState } = this.state
47 | return (
48 |
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/product/add-update.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | Card,
4 | Icon,
5 | Button,
6 | Form,
7 | Input,
8 | Cascader,
9 | message
10 | } from 'antd'
11 | import PictureWall from './picture-wall'
12 | import RichTextEditor from './RichTextEditor'
13 | import { categoryIf, updateProductInfoIf, addProduct } from '../../api/index'
14 | import memoryUtils from '../../utils/memoryUtils.js'
15 | const { Item } = Form
16 | const { TextArea } = Input
17 | class ProductAddUpdate extends Component {
18 | constructor (props) {
19 | super(props)
20 | // 创建用来保存ref标识的标签对象的容器
21 | this.pw = React.createRef()
22 | this.editor = React.createRef()
23 | }
24 | state = {
25 | options: [] // 商品分类级联选择的数据数组
26 | }
27 | /*
28 | 异步加载当前一级分类(一级联)下的二级分类(二级联)
29 | */
30 | loadData = async selectedOptions => {
31 | // 得到选择的option对象
32 | const targetOption = selectedOptions[selectedOptions.length - 1]
33 | // 显示loading
34 | targetOption.loading = true
35 | // 通过一级分类搜索二级分类
36 | const subCategories = await this.getCategories(targetOption.value)
37 | // 隐藏loading
38 | targetOption.loading = false
39 | if (subCategories && subCategories.length > 0) {
40 | // 说明当前一级分类有二级分类
41 | // 生成一个二级分类列表的options
42 | const childOptions = subCategories.map(c => ({
43 | value: c._id,
44 | label: c.name,
45 | isLeaf: true // 二级分类都是叶子
46 | }))
47 | // 关联到当前option上
48 | targetOption.children = childOptions
49 | } else {
50 | // 当前一级分类没有二级分类
51 | targetOption.isLeaf = true // 修改当前一级分类为叶子,因为默认一级不为叶子
52 | }
53 | // 更新 state 中的 options
54 | this.setState({
55 | options: [...this.state.options]
56 | })
57 |
58 | }
59 | /*
60 | 提交表单
61 | */
62 | submit = (e) => {
63 | e.preventDefault()
64 | // 对表单进行统一验证
65 | this.props.form.validateFields(async (errors, values) => {
66 | if (!errors) {
67 | // 表单验证成功,发送请求更新/添加商品
68 | const {name, desc, price, categoryIds} = values
69 | let pCategoryId, categoryId
70 | if (categoryIds.length > 1) {
71 | // 该商品是二级分类下的商品
72 | pCategoryId = categoryIds[0]
73 | categoryId = categoryIds[1]
74 | } else {
75 | // 该商品是一级分类下的商品
76 | pCategoryId = '0'
77 | categoryId = categoryIds[0]
78 | }
79 | const imgs = this.pw.current.getImgs()
80 | const detail = this.editor.current.getDetail()
81 | if (this.isUpdate) {
82 | // 更新商品信息
83 | const _id = this.product._id
84 | const productInfo = {_id, categoryId, pCategoryId, name, desc, price, imgs, detail}
85 | const ret = await updateProductInfoIf(productInfo)
86 | if (ret.status === 0) {
87 | message.success('更新商品成功!')
88 | this.props.history.replace('/product')
89 | } else {
90 | message.error('更新商品失败!')
91 | }
92 | } else {
93 | // 添加商品
94 | const product = {categoryId, pCategoryId, name, desc, price, imgs, detail}
95 | const ret = await addProduct(product)
96 | if (ret.status === 0) {
97 | message.success('添加商品成功!')
98 | this.props.history.replace('/product')
99 | } else {
100 | message.error('添加商品失败!')
101 | }
102 | }
103 | } else{
104 | console.log(errors)
105 | }
106 | })
107 | }
108 | /*
109 | 自定义商品价格输入字段验证规则
110 | */
111 | validatePrice = (rule, value, callback) => {
112 | if (+value <= 0) {
113 | callback('价格必须大于零')
114 | } else {
115 | callback()
116 | }
117 | }
118 | /*
119 | 初始化state 中的 options
120 | */
121 | initOptions = async (categories) => {
122 | const options = categories.map(c => ({
123 | value: c._id,
124 | label: c.name,
125 | isLeaf: false // 默认当前一级分类不是叶子
126 | }))
127 | const {product, isUpdate} = this
128 | const {pCategoryId} = product
129 | if (pCategoryId !== '0' && isUpdate) {
130 | // 当前商品属于二级分类下的商品,为该商品准备对应一级分类下的二级分类数据数组
131 | const subCategories = await this.getCategories(pCategoryId)
132 | const childOptions = subCategories.map(c => ({
133 | value: c._id,
134 | label: c.name,
135 | isLeaf: true // 二级分类都是叶子
136 | }))
137 | // 关联到当前一级分类 options 上面
138 | const targetOption = options.find(o => o.value === pCategoryId)
139 | targetOption.children = childOptions
140 | }
141 | // 更新options
142 | this.setState({options})
143 | }
144 | /*
145 | 获取商品分类(一级/二级)
146 | */
147 | getCategories = async (pCategoryId) => {
148 | const ret = await categoryIf(pCategoryId)
149 | if (ret.status === 0) {
150 | if (pCategoryId === '0') {
151 | // 获取的是一级分类
152 | const categories = ret.data
153 | this.initOptions(categories)
154 | } else {
155 | // 获取的是二级分类
156 | const subCategories = ret.data
157 | return subCategories
158 | }
159 | }
160 | }
161 | componentWillMount () {
162 | // 取出memoryUtils 中的 product
163 | const product = memoryUtils.product
164 | this.product = product
165 | // 利用JSON.stringify() 将对象转换成JSON字符串进行比较
166 | this.isUpdate = (JSON.stringify(product) === '{}' ? false : true)
167 | }
168 | componentDidMount () {
169 | // 获取商品的一级分类
170 | this.getCategories('0')
171 | }
172 | render() {
173 | const formItemLayout = {
174 | labelCol: { span: 2 },
175 | wrapperCol: { span: 8 },
176 | }
177 | const {getFieldDecorator} = this.props.form
178 | const {product, isUpdate} = this
179 | console.log(product)
180 | const {pCategoryId, categoryId, imgs, detail} = product
181 | const categoryIds = []
182 | if (isUpdate) {
183 | // 如果商品是一级分类商品
184 | if (pCategoryId === '0') {
185 | categoryIds.push(categoryId)
186 | } else {
187 | // 商品是二级分类下的商品
188 | categoryIds.push(pCategoryId)
189 | categoryIds.push(categoryId)
190 | }
191 |
192 | }
193 | const title = ({isUpdate ? '修改商品' : '添加商品'})
194 | return (
195 |
196 |
237 |
238 | )
239 | }
240 | }
241 |
242 | export default Form.create()(ProductAddUpdate)
--------------------------------------------------------------------------------
/react-admin-client/src/views/product/detail.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {List, Icon, Card, Button} from 'antd'
3 | import memoryUtils from '../../utils/memoryUtils.js'
4 | import { BASE_IMG_URL } from '../../utils/constance.js'
5 | import {getCategoryIf} from '../../api/index'
6 | const Item = List.Item
7 | export default class ProductDetail extends Component {
8 | state = {
9 | categoryName1: '', // 一级分类名称
10 | categoryName2: '', // 二级分类名称
11 | }
12 | async componentDidMount () {
13 | const {pCategoryId, categoryId} = memoryUtils.product
14 | // 发送请求获取商品的分类名称
15 | if (pCategoryId === '0') {
16 | // 当前商品属于一级分类
17 | const ret = await getCategoryIf(categoryId)
18 | if (ret.status === 0) {
19 | const categoryName1 = ret.data.name
20 | this.setState({categoryName1})
21 | }
22 | } else {
23 | // 当前商品属于二级分类
24 | const rets = await Promise.all([getCategoryIf(pCategoryId), getCategoryIf(categoryId)])
25 | const categoryName1 = rets[0].data.name
26 | const categoryName2 = rets[1].data.name
27 | this.setState({categoryName1, categoryName2})
28 | }
29 | }
30 | render() {
31 | console.log(memoryUtils.product)
32 | const {desc, detail, imgs, name, price} = memoryUtils.product
33 | const {categoryName1, categoryName2} = this.state
34 | const title = (商品详情)
35 | return (
36 |
37 |
38 | -
39 | 商品名称:
40 | {name}
41 |
42 | -
43 | 商品描述:
44 | {desc}
45 |
46 | -
47 | 商品价格:
48 | {price}元
49 |
50 | -
51 | 所属分类:
52 | {categoryName2 === '' ? categoryName1 : categoryName1 + '-->' + categoryName2}
53 |
54 | -
55 | 商品图片:
56 |
57 | {
58 | imgs.map(img =>
59 |
65 | )
66 | }
67 |
68 |
69 | -
70 | 商品详情:
71 |
72 |
73 |
74 |
75 | )
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/product/home.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {getProductListIf, searchProductIf, updateStatusIf} from '../../api'
3 | import {PAGE_SIZE} from '../../utils/constance'
4 | import {
5 | Card,
6 | Button,
7 | Table,
8 | Icon,
9 | Select,
10 | Input,
11 | message
12 | } from 'antd'
13 | import LinkButton from '../../components/link-button/LinkButton'
14 | import memoryUtils from '../../utils/memoryUtils.js'
15 | import storageUtils from '../../utils/storageUtils.js'
16 | const Option = Select.Option
17 | export default class ProductHome extends Component {
18 | state = {
19 | product: [], // 商品信息列表
20 | total: 0, // 商品的总数量
21 | loading: false, // 表格数据是否加载中
22 | searchType: 'productName', // 根据那个字段搜索
23 | searchName: '' // 搜索关键字
24 | }
25 | /*
26 | 对商品进行上下架
27 | */
28 | updateStatus = async (productId, status) => {
29 | // 发ajax对商品进行上下架
30 | const ret = await updateStatusIf(productId, status)
31 | if (ret.status === 0) {
32 | // 上下架成功
33 | message.success('更新成功!')
34 | this.getProductList(this.pageNum)
35 | }
36 | }
37 | /*
38 | 获取商品分页列表
39 | */
40 | getProductList = async (pageNum) => {
41 | // 保存当前页码
42 | this.pageNum = pageNum
43 | // 从 state 中获取 searchName searchType
44 | const {searchName, searchType} = this.state
45 | // 表格数据加载中
46 | this.setState({loading: true})
47 | let ret
48 | // 发送Ajax获取商品, 如果有搜索关键字就是搜索分页
49 | if (searchName) {
50 | ret = await searchProductIf(pageNum, PAGE_SIZE, searchType, searchName)
51 | } else {
52 | ret = await getProductListIf(pageNum, PAGE_SIZE)
53 | }
54 | this.setState({loading: false})
55 | if (ret.status === 0) {
56 | // 获取商品成功
57 | const {total, list} = ret.data
58 | // 更新 state
59 | this.setState({total, product: list})
60 | }
61 | }
62 | /*
63 | 初始化表格列的数组
64 | */
65 | initColumns = () => {
66 | this.columns = [
67 | {
68 | title: '商品名称',
69 | dataIndex: 'name'
70 | },
71 | {
72 | title: '商品描述',
73 | dataIndex: 'desc'
74 | },
75 | {
76 | title: '价格',
77 | render: (product) => ¥{product.price}
78 | },
79 | {
80 | title: '状态',
81 | render: (product) => {
82 | const {status, _id} = product
83 | return ({status === 1 ? '在售' : '已下架'})
84 | }
85 | },
86 | {
87 | title: '操作',
88 | render: (product) => 详情修改
89 | }
90 | ]
91 | }
92 | /*
93 | 显示商品详情路由
94 | */
95 | showDetail = (product) => {
96 | // 保存商品到local storage
97 | storageUtils.saveProduct(product)
98 | // 保存商品信息到内存
99 | memoryUtils.product = product
100 | // 跳转路由
101 | this.props.history.push('/product/detail')
102 | }
103 | /*
104 | 显示商品修改/添加路由
105 | */
106 | showUpdate = product => {
107 | // 保存商品到local storage
108 | storageUtils.saveProduct(product)
109 | // 保存商品信息到内存
110 | memoryUtils.product = product
111 | // 跳转路由
112 | this.props.history.push('/product/addupdate')
113 | }
114 | componentWillMount () {
115 | // 获取商品列表
116 | this.getProductList(1)
117 | // 初始化表格列
118 | this.initColumns()
119 | }
120 | render() {
121 | const {product, loading, total, searchName, searchType} = this.state
122 | const title = (
123 |
124 |
128 | this.setState({searchName: event.target.value})}/>
129 |
130 |
131 | )
132 | const extra =
133 | return (
134 |
135 |
143 |
144 | )
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/product/picture-wall.jsx:
--------------------------------------------------------------------------------
1 | // 展示商品图片的组件
2 | import React, { Component } from 'react'
3 | import { Upload, Icon, Modal, message} from 'antd'
4 | import propTypes from 'prop-types'
5 | import { BASE_IMG_URL } from '../../utils/constance'
6 | import { deleteProductImgIf } from '../../api/index'
7 | function getBase64(file) {
8 | return new Promise((resolve, reject) => {
9 | const reader = new FileReader()
10 | reader.readAsDataURL(file)
11 | reader.onload = () => resolve(reader.result)
12 | reader.onerror = error => reject(error)
13 | })
14 | }
15 | export default class PictureWall extends Component {
16 | static propTypes = {
17 | imgs: propTypes.array
18 | }
19 | constructor (props) {
20 | super(props)
21 | let fileList = []
22 | const { imgs } = this.props
23 | if (imgs && imgs.length > 0) {
24 | fileList = imgs.map((img, index) => ({
25 | uid: -index, // 文件唯一标识
26 | name: img, // 文件名
27 | status: 'done', // 图片的状态:done:已上传,uploading:上传中,error:错误,removed:已删除
28 | url: BASE_IMG_URL + img
29 | }))
30 | }
31 | // 初始化状态
32 | this.state = {
33 | previewVisible: false, // 是否预览大图
34 | previewImage: '', // 大图的url地址
35 | fileList // 已经上传的图片文件对象数组
36 | }
37 | }
38 |
39 | /*
40 | 获取所有已经上传的图片文件名数组
41 | */
42 | getImgs = () => {
43 | return this.state.fileList.map(file => file.name)
44 | }
45 | /*
46 | 操作预览大图
47 | */
48 | handlePreview = async file => {
49 | if (!file.url && !file.preview) {
50 | file.preview = await getBase64(file.originFileObj)
51 | }
52 | // 更新状态
53 | this.setState({
54 | previewImage: file.url || file.preview,
55 | previewVisible: true
56 | })
57 | }
58 | /*
59 | 隐藏Modal
60 | */
61 | handleCancel = () => {
62 | this.setState({previewVisible: false})
63 | }
64 | /*
65 | 上传的文件对象改变的回调
66 | */
67 | handleChange = async ({file, fileList}) => {
68 | if (file.status === 'done') {
69 | const result = file.response
70 | if(result.status === 0) {
71 | message.success('上传图片成功!')
72 | const { name, url } = result.data
73 | // 将当前上传的file的信息修正
74 | file = fileList[fileList.length -1]
75 | file.name = name
76 | file.url = url
77 | } else {
78 | message.error('上传图片失败!')
79 | }
80 | } else if (file.status === 'removed') {
81 | const response = await deleteProductImgIf(file.name)
82 | if (response.status === 0) {
83 | message.success('删除图片成功!')
84 | } else {
85 | message.error('删除图片失败!')
86 | }
87 | }
88 | this.setState({ fileList })
89 | }
90 | render() {
91 | const { fileList, previewImage, previewVisible } = this.state
92 | const uploadButton = (
93 |
97 | )
98 | return (
99 |
100 |
109 | {fileList.length >= 3 ? null : uploadButton}
110 |
111 |
112 |
113 |
114 |
115 | )
116 | }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/product/product.less:
--------------------------------------------------------------------------------
1 | .product-detail {
2 | .left {
3 | font-size: 20px;
4 | font-weight: bold;
5 | margin-right: 15px;
6 | }
7 | .productImg {
8 | width: 150px;
9 | height: 150px;
10 | border: 1px solid #999;
11 | margin-right: 10px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/role/Role.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import{
3 | Table,
4 | Button,
5 | Card,
6 | Modal,
7 | message
8 | } from 'antd'
9 | import {connect} from 'react-redux'
10 | import {logout} from '../../redux/actions'
11 | import AddForm from './add-form'
12 | import AuthForm from './auth-form'
13 | import { getRoles, addRole, updateRole } from '../../api/index'
14 | import {PAGE_SIZE} from '../../utils/constance'
15 | import formatDate from '../../utils/dateUtils.js'
16 | class Role extends Component {
17 | state = {
18 | roles: [], // 所有角色列表
19 | role: {}, // 选中的role
20 | visible: false, // modal 对话框是否可见(添加角色)
21 | authVisible: false // modal 对话框是否可见(角色授权)
22 | }
23 |
24 | constructor (props) {
25 | super(props)
26 | this.auth = React.createRef()
27 | }
28 |
29 | initColumns = () => {
30 | this.columns = [
31 | {
32 | dataIndex: 'name',
33 | title: '角色名称'
34 | },
35 | {
36 | dataIndex: 'create_time',
37 | title: '创建时间',
38 | render: formatDate
39 | },
40 | {
41 | dataIndex: 'auth_time',
42 | title: '授权时间',
43 | render: formatDate
44 | },
45 | {
46 | dataIndex: 'auth_name',
47 | title: '授权人'
48 | },
49 | ]
50 | }
51 |
52 | /*
53 | 获取角色列表
54 | */
55 |
56 | getRoles = async () => {
57 | const ret = await getRoles()
58 | if (ret.status === 0) {
59 | const roles = ret.data
60 | this.setState({roles})
61 | }
62 | }
63 |
64 | /*
65 | 表格行属性onRow
66 | */
67 | onRow = role => {
68 | return {
69 | onClick: event => {
70 | // 表格行点击时的回调
71 | this.setState({role})
72 | }
73 | }
74 | }
75 |
76 | /*
77 | 角色授权
78 | */
79 | handleAuthRole = async () => {
80 | // 收集子组件checkedKeys状态
81 | const menus = this.auth.current.getMenus()
82 | const {role} = this.state
83 | role.menus = menus
84 | role.auth_time = Date.now()
85 | role.auth_name = this.props.user.username
86 | const ret = await updateRole(role)
87 | if (ret.status === 0) {
88 | // 判断当前用户是否给自己的角色授权,是则强制重新登录
89 | if (role._id === this.props.user.role_id) {
90 | // 强制退出
91 | this.props.logout()
92 | message.success('您的角色被重新授权,请重新登录!')
93 | } else {
94 | message.success('授权成功!')
95 | this.setState({
96 | roles: [...this.state.roles],
97 | authVisible: false
98 | })
99 | }
100 | } else {
101 | message.error('授权失败!')
102 | }
103 | }
104 |
105 | /*
106 | 添加角色
107 | */
108 | handleAddRole = () => {
109 | // 对表单进行统一规则验证
110 | this.form.validateFields(async (error, values) => {
111 | if (!error) {
112 | // 验证通过
113 | const {roleName} = values
114 | const ret = await addRole(roleName)
115 | if (ret.status === 0) {
116 | message.success('添加角色成功!')
117 | this.getRoles()
118 | this.form.resetFields()
119 | this.setState({visible: false})
120 | } else {
121 | message.error('添加角色失败!')
122 | }
123 | } else {
124 | console.log(error)
125 | }
126 | })
127 | }
128 |
129 | componentWillMount () {
130 | this.initColumns()
131 | }
132 |
133 | componentDidMount () {
134 | this.getRoles()
135 | }
136 | render() {
137 | console.log('role.jsx---render()')
138 | const {roles, role, visible, authVisible} = this.state
139 | const title = ()
140 | return (
141 |
142 | this.setState({role})}
151 | }
152 | onRow={this.onRow}
153 | />
154 | {
159 | this.setState({visible: false})
160 | this.form.resetFields()
161 | }}
162 | okText="确认"
163 | cancelText="取消"
164 | >
165 | this.form = from}>
166 |
167 | this.setState({authVisible: false})}
172 | okText='确认'
173 | cancelText='取消'
174 | >
175 |
176 |
177 |
178 | )
179 | }
180 | }
181 |
182 | export default connect(
183 | state => ({user: state.user}),
184 | {logout}
185 | )(Role)
--------------------------------------------------------------------------------
/react-admin-client/src/views/role/add-form.jsx:
--------------------------------------------------------------------------------
1 | // 添加角色组件
2 | import React, { Component } from 'react'
3 | import {
4 | Form,
5 | Input
6 | } from 'antd'
7 | import propTypes from 'prop-types'
8 | const Item = Form.Item
9 | class AddForm extends Component {
10 | static propTypes = {
11 | getForm: propTypes.func.isRequired
12 | }
13 |
14 | componentWillMount () {
15 | // 在组件将要首次渲染时将自己的 this.props.form 传递给父组件
16 | this.props.getForm(this.props.form)
17 | }
18 |
19 | render() {
20 | const { getFieldDecorator } = this.props.form
21 | return (
22 |
33 | )
34 | }
35 | }
36 |
37 | export default Form.create()(AddForm)
38 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/role/auth-form.jsx:
--------------------------------------------------------------------------------
1 | // 角色授权组件
2 | import React, { PureComponent } from 'react'
3 | import {
4 | Form,
5 | Input,
6 | Tree
7 | } from 'antd'
8 | import menuList from '../../config/menuConfig.js'
9 | import propTypes from 'prop-types'
10 | const Item = Form.Item
11 | const { TreeNode } = Tree
12 |
13 | export default class AuthForm extends PureComponent {
14 |
15 | static propTypes = {
16 | role: propTypes.object.isRequired
17 | }
18 |
19 | constructor (props) {
20 | super(props)
21 | const {menus} = this.props.role
22 | this.state = {
23 | checkedKeys: menus // 初始授权状态
24 | }
25 | }
26 | /*
27 | 根据 menuList 数据数组生成 TreeNode 标签数组,采用 map()
28 | */
29 | getTreeNodes = (menuList) => {
30 | return menuList.map(item =>
31 |
32 | {item.children ? this.getTreeNodes(item.children) : null}
33 |
34 | )
35 | }
36 |
37 | /*
38 | 把自己已经选中的menus数组交给父组件
39 | */
40 | getMenus = () => this.state.checkedKeys
41 |
42 | /*
43 | 点击 TreeNode 前多选框的回调
44 | */
45 | onCheck = (checkedKeys) => {
46 | this.setState({checkedKeys})
47 | }
48 |
49 | componentWillMount () {
50 | this.treeNodes = this.getTreeNodes(menuList)
51 | }
52 |
53 | /*
54 | 当组件接受到新的props时候调用,第一次渲染不会调用
55 | */
56 | componentWillReceiveProps (nextProps) {
57 | const checkedKeys = nextProps.role.menus
58 | this.setState({checkedKeys})
59 | }
60 |
61 | render() {
62 | console.log('auth-form---render()')
63 | const {checkedKeys} = this.state
64 | const role = this.props.role
65 | const formItemLayout = {
66 | labelCol: { span: 4 }, // 左侧label的宽度
67 | wrapperCol: { span: 20 } // 右侧包裹的宽度
68 | }
69 | return (
70 |
85 | )
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/user/User.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | Button,
4 | Card,
5 | Table,
6 | Modal,
7 | message
8 | } from 'antd'
9 | import UserForm from './user-form'
10 | import {getUsers, addOrUpdateUser, deleteUser} from '../../api/index'
11 | import {PAGE_SIZE} from '../../utils/constance.js'
12 | import LinkButton from '../../components/link-button/LinkButton'
13 | import dateFormat from '../../utils/dateUtils.js'
14 | export default class User extends Component {
15 |
16 | state = {
17 | users: [], // 所有用户的信息列表
18 | roles: [], // 所有角色列表数组
19 | isShow: false // 添加或修改用户modal是否可见
20 | }
21 |
22 | initColumns = () => {
23 | this.columns = [
24 | {title: '用户名', dataIndex: 'username'},
25 | {title: '邮箱', dataIndex: 'email'},
26 | {title: '电话', dataIndex: 'phone'},
27 | {title: '注册时间', dataIndex: 'create_time', render: dateFormat},
28 | {title: '所属角色', dataIndex: 'role_id', render: (role_id) => this.roleNames[role_id]},
29 | {title: '操作', render: user => {
30 | return (修改删除)
31 | }}
32 | ]
33 | }
34 |
35 | /*
36 | 根据角色列表数组生成角色名的对象(属性名为角色ID)
37 | */
38 | initRoleNames = (roles) => {
39 | this.roleNames = roles ? roles.reduce((prev, curr) => {
40 | prev[curr._id] = curr.name
41 | return prev
42 | }, {}) : {}
43 | console.log(this.roleNames)
44 | }
45 |
46 | getUsers = async () => {
47 | const ret = await getUsers()
48 | if (ret.status === 0) {
49 | const {users, roles}= ret.data
50 | // 调用 initRoleNames() 生成所有角色名的对象
51 | this.initRoleNames(roles)
52 | this.setState({
53 | users,
54 | roles
55 | })
56 | }
57 | }
58 |
59 | /*
60 | 显示添加用户modal对话框
61 | */
62 | showAdd = () => {
63 | this.user = null
64 | this.setState({
65 | isShow: true
66 | })
67 | }
68 |
69 | /*
70 | 显示更新用户modal对话框
71 | */
72 | showUpdate = (user) => {
73 | this.user = user
74 | this.setState({
75 | isShow: true
76 | })
77 | }
78 |
79 | /*
80 | 添加或修改用户
81 | */
82 | addOrUpdateUser = () => {
83 | // 对表单进行统一验证
84 | this.form.validateFields(async (error, values) => {
85 | if (!error) {
86 | // 验证成功,发送请求修改或添加用户
87 | // 清除form表单数据,保证modal每次打开都是新的内容
88 | this.form.resetFields()
89 | this.setState({isShow: false})
90 | const {username, email, phone, password, role_id} = values
91 | let user
92 | if (this.user && this.user._id) {
93 | // 说明是更新用户
94 | user = {username, email, phone, role_id, _id: this.user._id}
95 | } else {
96 | // 说明是添加用户
97 | user = {username, email, phone, password, role_id}
98 | }
99 | const ret = await addOrUpdateUser(user)
100 | if (ret.status === 0) {
101 | message.success(`${ this.user ? '修改' : '添加' }成功!`)
102 | this.getUsers()
103 | }
104 | } else {
105 | console.log(error)
106 | }
107 | })
108 | }
109 |
110 | /*
111 | 删除用户
112 | */
113 | deleteUser = (user) => {
114 | Modal.confirm({
115 | title: `确定要删除${user.username}吗?`,
116 | okText: '确认',
117 | cancelText: '取消',
118 | onOk: async() => {
119 | const ret = await deleteUser(user._id)
120 | if (ret.status === 0) {
121 | message.success('删除成功!')
122 | this.getUsers()
123 | } else {
124 | message.error('删除用户失败!')
125 | }
126 | }
127 | })
128 | }
129 |
130 | componentWillMount () {
131 | // 为第一次render() 准备数据
132 | this.initColumns()
133 | }
134 |
135 | componentDidMount () {
136 | // 获取所有用户列表
137 | this.getUsers()
138 | }
139 |
140 | render() {
141 | const title = ()
142 | const {users, isShow, roles} = this.state
143 | const user = this.user || {}
144 | return (
145 |
146 |
153 | {
159 | this.setState({isShow: false})
160 | this.form.resetFields()
161 | }}
162 | onOk={this.addOrUpdateUser}
163 | >
164 | this.form = form} user={user}/>
165 |
166 |
167 | )
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/react-admin-client/src/views/user/user-form.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import propTypes from 'prop-types'
3 | import {
4 | Form,
5 | Input,
6 | Select
7 | } from 'antd'
8 | const Option = Select.Option
9 | const Item = Form.Item
10 | class UserForm extends Component {
11 |
12 | static propTypes = {
13 | roles: propTypes.array.isRequired,
14 | setForm: propTypes.func.isRequired,
15 | user: propTypes.object.isRequired
16 | }
17 |
18 | componentWillMount() {
19 | // 把自己的form属性传递给父组件
20 | this.props.setForm(this.props.form)
21 | }
22 |
23 | render() {
24 | console.log('userForm--render()')
25 | console.log(this.props.user)
26 | const {getFieldDecorator} = this.props.form
27 | const {roles, user} = this.props
28 | const formItemLayout = {
29 | labelCol: { span: 4 }, // 左侧label的宽度
30 | wrapperCol: { span: 20 } // 右侧包裹的宽度
31 | }
32 | return (
33 |
90 | )
91 | }
92 | }
93 |
94 | export default Form.create()(UserForm)
--------------------------------------------------------------------------------
/react-admin-server/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 |
4 | # dependencies
5 | /node_modules
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/react-admin-server/README.md:
--------------------------------------------------------------------------------
1 | # React 后台管理服务端
2 |
3 | 技术栈:
4 |
5 | * Node.js
6 |
7 | * Express
8 |
9 | * mongoDB
10 |
11 | * mongoose
12 |
13 | * md5
14 |
15 | * multer(express文件上传中间件Multer)
16 |
17 | ## 运行
18 |
19 | 说明:项目是基于 mongoDB 数据库,开启服务器之前请确保本地已经开启了 mongoDB 服务。
20 |
21 | * **git clone** 克隆代码到本地
22 |
23 | * **npm install** 安装项目依赖
24 |
25 | * **npm start** 开启服务器
26 |
--------------------------------------------------------------------------------
/react-admin-server/app.js:
--------------------------------------------------------------------------------
1 | // 引入express创建 http 服务
2 | const express = require('express')
3 | const mongoose = require('mongoose')
4 | const cookieParser = require('cookie-parser')
5 | const bodyParser = require('body-parser')
6 | const router = require('./router/index')
7 | const app = express()
8 | // 开放静态资源
9 | app.use(express.static('./public'))
10 | // 配置解析post请求中间件 body-parser
11 | app.use(bodyParser.urlencoded({ extended: false }))
12 | app.use(bodyParser.json())
13 | // 配置解析cookie数据的中间件
14 | app.use(cookieParser())
15 | // 挂载路由容器到app
16 | app.use(router)
17 | mongoose.connect('mongodb://localhost:27017/server_db2', {useNewUrlParser: true}).then(() => {
18 | console.log('数据库连接成功...')
19 | // 启动服务器
20 | app.listen('5000', () => {
21 | console.log('服务器启动成功...')
22 | })
23 | }).catch(error =>{
24 | console.error('连接数据库失败', error)
25 | })
26 |
27 |
--------------------------------------------------------------------------------
/react-admin-server/models/CategoryModel.js:
--------------------------------------------------------------------------------
1 | /*
2 | 能操作categorys集合数据的Model
3 | */
4 | // 1.引入mongoose
5 | const mongoose = require('mongoose')
6 |
7 | // 2.字义Schema(描述文档结构)
8 | const categorySchema = new mongoose.Schema({
9 | name: {type: String, required: true},
10 | parentId: {type: String, required: true, default: '0'}
11 | })
12 |
13 | // 3. 定义Model(与集合对应, 可以操作集合)
14 | const CategoryModel = mongoose.model('categorys', categorySchema)
15 |
16 | // 4. 向外暴露Model
17 | module.exports = CategoryModel
--------------------------------------------------------------------------------
/react-admin-server/models/ProductModel.js:
--------------------------------------------------------------------------------
1 | /*
2 | 能操作products集合数据的Model
3 | */
4 | // 1.引入mongoose
5 | const mongoose = require('mongoose')
6 |
7 | // 2.字义Schema(描述文档结构)
8 | const productSchema = new mongoose.Schema({
9 | categoryId: {type: String, required: true}, // 所属分类的id
10 | pCategoryId: {type: String, required: true}, // 所属分类的父分类id
11 | name: {type: String, required: true}, // 名称
12 | price: {type: Number, required: true}, // 价格
13 | desc: {type: String},
14 | status: {type: Number, default: 1}, // 商品状态: 1:在售, 2: 下架了
15 | imgs: {type: Array, default: []}, // n个图片文件名的json字符串
16 | detail: {type: String}
17 | })
18 |
19 |
20 | // 3. 定义Model(与集合对应, 可以操作集合)
21 | const ProductModel = mongoose.model('products', productSchema)
22 |
23 | // 4. 向外暴露Model
24 | module.exports = ProductModel
--------------------------------------------------------------------------------
/react-admin-server/models/RoleModel.js:
--------------------------------------------------------------------------------
1 | /*
2 | 能操作roles集合数据的Model
3 | */
4 | // 1.引入mongoose
5 | const mongoose = require('mongoose')
6 |
7 | // 2.字义Schema(描述文档结构)
8 | const roleSchema = new mongoose.Schema({
9 | name: {type: String, required: true}, // 角色名称
10 | auth_name: String, // 授权人
11 | auth_time: Number, // 授权时间
12 | create_time: {type: Number, default: Date.now}, // 创建时间
13 | menus: Array // 所有有权限操作的菜单path的数组
14 | })
15 |
16 | // 3. 定义Model(与集合对应, 可以操作集合)
17 | const RoleModel = mongoose.model('roles', roleSchema)
18 |
19 | // 4. 向外暴露Model
20 | module.exports = RoleModel
21 |
--------------------------------------------------------------------------------
/react-admin-server/models/UserModel.js:
--------------------------------------------------------------------------------
1 | /*
2 | 能操作users集合数据的Model
3 | */
4 | // 1.引入mongoose
5 | const mongoose = require('mongoose')
6 | const md5 = require('blueimp-md5')
7 |
8 | // 2.字义Schema(描述文档结构)
9 | const userSchema = new mongoose.Schema({
10 | username: {type: String, required: true}, // 用户名
11 | password: {type: String, required: true}, // 密码
12 | phone: String,
13 | email: String,
14 | create_time: {type: Number, default: Date.now},
15 | role_id: String
16 | })
17 |
18 | // 3. 定义Model(与集合对应, 可以操作集合)
19 | const UserModel = mongoose.model('users', userSchema)
20 |
21 | // 初始化默认创建一个超级管理员用户: admin/admin
22 | UserModel.findOne({username: 'admin'}).then(user => {
23 | if(!user) {
24 | UserModel.create({username: 'admin', password: md5('admin')})
25 | .then(user => {
26 | console.log('初始化超级管理员: 用户名: admin 密码为: admin')
27 | })
28 | }
29 | })
30 |
31 | // 4. 向外暴露Model
32 | module.exports = UserModel
--------------------------------------------------------------------------------
/react-admin-server/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-admin-server",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "accepts": {
8 | "version": "1.3.7",
9 | "resolved": "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz",
10 | "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=",
11 | "requires": {
12 | "mime-types": "~2.1.24",
13 | "negotiator": "0.6.2"
14 | }
15 | },
16 | "append-field": {
17 | "version": "1.0.0",
18 | "resolved": "https://registry.npm.taobao.org/append-field/download/append-field-1.0.0.tgz",
19 | "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
20 | },
21 | "array-flatten": {
22 | "version": "1.1.1",
23 | "resolved": "http://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz",
24 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
25 | },
26 | "async": {
27 | "version": "2.6.2",
28 | "resolved": "https://registry.npm.taobao.org/async/download/async-2.6.2.tgz",
29 | "integrity": "sha1-GDMOp+bjE4h/XS8qkEusb+TdU4E=",
30 | "requires": {
31 | "lodash": "^4.17.11"
32 | }
33 | },
34 | "bluebird": {
35 | "version": "3.5.1",
36 | "resolved": "https://registry.npm.taobao.org/bluebird/download/bluebird-3.5.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbluebird%2Fdownload%2Fbluebird-3.5.1.tgz",
37 | "integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk="
38 | },
39 | "blueimp-md5": {
40 | "version": "2.10.0",
41 | "resolved": "https://registry.npm.taobao.org/blueimp-md5/download/blueimp-md5-2.10.0.tgz",
42 | "integrity": "sha1-AvCEOSH5DcoU9biSCjhZMgHWlk0="
43 | },
44 | "body-parser": {
45 | "version": "1.19.0",
46 | "resolved": "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz",
47 | "integrity": "sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=",
48 | "requires": {
49 | "bytes": "3.1.0",
50 | "content-type": "~1.0.4",
51 | "debug": "2.6.9",
52 | "depd": "~1.1.2",
53 | "http-errors": "1.7.2",
54 | "iconv-lite": "0.4.24",
55 | "on-finished": "~2.3.0",
56 | "qs": "6.7.0",
57 | "raw-body": "2.4.0",
58 | "type-is": "~1.6.17"
59 | }
60 | },
61 | "bson": {
62 | "version": "1.1.1",
63 | "resolved": "https://registry.npm.taobao.org/bson/download/bson-1.1.1.tgz",
64 | "integrity": "sha1-QzD16ZEExOdR5zUYWeLUCCefLxM="
65 | },
66 | "buffer-from": {
67 | "version": "1.1.1",
68 | "resolved": "http://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz",
69 | "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8="
70 | },
71 | "busboy": {
72 | "version": "0.2.14",
73 | "resolved": "https://registry.npm.taobao.org/busboy/download/busboy-0.2.14.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbusboy%2Fdownload%2Fbusboy-0.2.14.tgz",
74 | "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
75 | "requires": {
76 | "dicer": "0.2.5",
77 | "readable-stream": "1.1.x"
78 | }
79 | },
80 | "bytes": {
81 | "version": "3.1.0",
82 | "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz",
83 | "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY="
84 | },
85 | "concat-stream": {
86 | "version": "1.6.2",
87 | "resolved": "http://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz",
88 | "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=",
89 | "requires": {
90 | "buffer-from": "^1.0.0",
91 | "inherits": "^2.0.3",
92 | "readable-stream": "^2.2.2",
93 | "typedarray": "^0.0.6"
94 | },
95 | "dependencies": {
96 | "isarray": {
97 | "version": "1.0.0",
98 | "resolved": "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz?cache=0&sync_timestamp=1562592096220&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fisarray%2Fdownload%2Fisarray-1.0.0.tgz",
99 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
100 | },
101 | "readable-stream": {
102 | "version": "2.3.6",
103 | "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freadable-stream%2Fdownload%2Freadable-stream-2.3.6.tgz",
104 | "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=",
105 | "requires": {
106 | "core-util-is": "~1.0.0",
107 | "inherits": "~2.0.3",
108 | "isarray": "~1.0.0",
109 | "process-nextick-args": "~2.0.0",
110 | "safe-buffer": "~5.1.1",
111 | "string_decoder": "~1.1.1",
112 | "util-deprecate": "~1.0.1"
113 | }
114 | },
115 | "string_decoder": {
116 | "version": "1.1.1",
117 | "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz",
118 | "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
119 | "requires": {
120 | "safe-buffer": "~5.1.0"
121 | }
122 | }
123 | }
124 | },
125 | "content-disposition": {
126 | "version": "0.5.3",
127 | "resolved": "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.3.tgz",
128 | "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=",
129 | "requires": {
130 | "safe-buffer": "5.1.2"
131 | }
132 | },
133 | "content-type": {
134 | "version": "1.0.4",
135 | "resolved": "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz",
136 | "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js="
137 | },
138 | "cookie": {
139 | "version": "0.4.0",
140 | "resolved": "https://registry.npm.taobao.org/cookie/download/cookie-0.4.0.tgz",
141 | "integrity": "sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo="
142 | },
143 | "cookie-parser": {
144 | "version": "1.4.4",
145 | "resolved": "https://registry.npm.taobao.org/cookie-parser/download/cookie-parser-1.4.4.tgz",
146 | "integrity": "sha1-5jY95OqYw975aXuTQhwJ8wz10Yg=",
147 | "requires": {
148 | "cookie": "0.3.1",
149 | "cookie-signature": "1.0.6"
150 | },
151 | "dependencies": {
152 | "cookie": {
153 | "version": "0.3.1",
154 | "resolved": "https://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz",
155 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
156 | }
157 | }
158 | },
159 | "cookie-signature": {
160 | "version": "1.0.6",
161 | "resolved": "http://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz",
162 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
163 | },
164 | "core-util-is": {
165 | "version": "1.0.2",
166 | "resolved": "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz",
167 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
168 | },
169 | "debug": {
170 | "version": "2.6.9",
171 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
172 | "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
173 | "requires": {
174 | "ms": "2.0.0"
175 | }
176 | },
177 | "depd": {
178 | "version": "1.1.2",
179 | "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz",
180 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
181 | },
182 | "destroy": {
183 | "version": "1.0.4",
184 | "resolved": "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz",
185 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
186 | },
187 | "dicer": {
188 | "version": "0.2.5",
189 | "resolved": "https://registry.npm.taobao.org/dicer/download/dicer-0.2.5.tgz",
190 | "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
191 | "requires": {
192 | "readable-stream": "1.1.x",
193 | "streamsearch": "0.1.2"
194 | }
195 | },
196 | "ee-first": {
197 | "version": "1.1.1",
198 | "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz",
199 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
200 | },
201 | "encodeurl": {
202 | "version": "1.0.2",
203 | "resolved": "http://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz",
204 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
205 | },
206 | "escape-html": {
207 | "version": "1.0.3",
208 | "resolved": "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz",
209 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
210 | },
211 | "etag": {
212 | "version": "1.8.1",
213 | "resolved": "http://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz",
214 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
215 | },
216 | "express": {
217 | "version": "4.17.1",
218 | "resolved": "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz",
219 | "integrity": "sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ=",
220 | "requires": {
221 | "accepts": "~1.3.7",
222 | "array-flatten": "1.1.1",
223 | "body-parser": "1.19.0",
224 | "content-disposition": "0.5.3",
225 | "content-type": "~1.0.4",
226 | "cookie": "0.4.0",
227 | "cookie-signature": "1.0.6",
228 | "debug": "2.6.9",
229 | "depd": "~1.1.2",
230 | "encodeurl": "~1.0.2",
231 | "escape-html": "~1.0.3",
232 | "etag": "~1.8.1",
233 | "finalhandler": "~1.1.2",
234 | "fresh": "0.5.2",
235 | "merge-descriptors": "1.0.1",
236 | "methods": "~1.1.2",
237 | "on-finished": "~2.3.0",
238 | "parseurl": "~1.3.3",
239 | "path-to-regexp": "0.1.7",
240 | "proxy-addr": "~2.0.5",
241 | "qs": "6.7.0",
242 | "range-parser": "~1.2.1",
243 | "safe-buffer": "5.1.2",
244 | "send": "0.17.1",
245 | "serve-static": "1.14.1",
246 | "setprototypeof": "1.1.1",
247 | "statuses": "~1.5.0",
248 | "type-is": "~1.6.18",
249 | "utils-merge": "1.0.1",
250 | "vary": "~1.1.2"
251 | }
252 | },
253 | "finalhandler": {
254 | "version": "1.1.2",
255 | "resolved": "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.2.tgz",
256 | "integrity": "sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=",
257 | "requires": {
258 | "debug": "2.6.9",
259 | "encodeurl": "~1.0.2",
260 | "escape-html": "~1.0.3",
261 | "on-finished": "~2.3.0",
262 | "parseurl": "~1.3.3",
263 | "statuses": "~1.5.0",
264 | "unpipe": "~1.0.0"
265 | }
266 | },
267 | "forwarded": {
268 | "version": "0.1.2",
269 | "resolved": "http://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz",
270 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
271 | },
272 | "fresh": {
273 | "version": "0.5.2",
274 | "resolved": "http://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz",
275 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
276 | },
277 | "http-errors": {
278 | "version": "1.7.2",
279 | "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz?cache=0&sync_timestamp=1561418493658&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-errors%2Fdownload%2Fhttp-errors-1.7.2.tgz",
280 | "integrity": "sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=",
281 | "requires": {
282 | "depd": "~1.1.2",
283 | "inherits": "2.0.3",
284 | "setprototypeof": "1.1.1",
285 | "statuses": ">= 1.5.0 < 2",
286 | "toidentifier": "1.0.0"
287 | }
288 | },
289 | "iconv-lite": {
290 | "version": "0.4.24",
291 | "resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz",
292 | "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=",
293 | "requires": {
294 | "safer-buffer": ">= 2.1.2 < 3"
295 | }
296 | },
297 | "inherits": {
298 | "version": "2.0.3",
299 | "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.3.tgz",
300 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
301 | },
302 | "ipaddr.js": {
303 | "version": "1.9.0",
304 | "resolved": "http://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.0.tgz",
305 | "integrity": "sha1-N9905DCg5HVQ/lSi3v4w2KzZX2U="
306 | },
307 | "isarray": {
308 | "version": "0.0.1",
309 | "resolved": "https://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz?cache=0&sync_timestamp=1562592096220&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fisarray%2Fdownload%2Fisarray-0.0.1.tgz",
310 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
311 | },
312 | "kareem": {
313 | "version": "2.3.0",
314 | "resolved": "https://registry.npm.taobao.org/kareem/download/kareem-2.3.0.tgz",
315 | "integrity": "sha1-7zPELpAk3OUR7q9EDNaE868fx2k="
316 | },
317 | "lodash": {
318 | "version": "4.17.14",
319 | "resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.14.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.14.tgz",
320 | "integrity": "sha1-nOSHrmbJYlT+ILWZ8htoFgKAeLo="
321 | },
322 | "media-typer": {
323 | "version": "0.3.0",
324 | "resolved": "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz",
325 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
326 | },
327 | "memory-pager": {
328 | "version": "1.5.0",
329 | "resolved": "https://registry.npm.taobao.org/memory-pager/download/memory-pager-1.5.0.tgz",
330 | "integrity": "sha1-2HUWVdItOEaCdByXLyw9bfo+ZrU=",
331 | "optional": true
332 | },
333 | "merge-descriptors": {
334 | "version": "1.0.1",
335 | "resolved": "http://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz",
336 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
337 | },
338 | "methods": {
339 | "version": "1.1.2",
340 | "resolved": "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz",
341 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
342 | },
343 | "mime": {
344 | "version": "1.6.0",
345 | "resolved": "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz?cache=0&sync_timestamp=1560034758817&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime%2Fdownload%2Fmime-1.6.0.tgz",
346 | "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE="
347 | },
348 | "mime-db": {
349 | "version": "1.40.0",
350 | "resolved": "https://registry.npm.taobao.org/mime-db/download/mime-db-1.40.0.tgz",
351 | "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI="
352 | },
353 | "mime-types": {
354 | "version": "2.1.24",
355 | "resolved": "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.24.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-types%2Fdownload%2Fmime-types-2.1.24.tgz",
356 | "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=",
357 | "requires": {
358 | "mime-db": "1.40.0"
359 | }
360 | },
361 | "minimist": {
362 | "version": "0.0.8",
363 | "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-0.0.8.tgz",
364 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
365 | },
366 | "mkdirp": {
367 | "version": "0.5.1",
368 | "resolved": "http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz",
369 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
370 | "requires": {
371 | "minimist": "0.0.8"
372 | }
373 | },
374 | "mongodb": {
375 | "version": "3.2.7",
376 | "resolved": "https://registry.npm.taobao.org/mongodb/download/mongodb-3.2.7.tgz",
377 | "integrity": "sha1-i6FJ5L5wglfK0N6nKuuyu7MRp6w=",
378 | "requires": {
379 | "mongodb-core": "3.2.7",
380 | "safe-buffer": "^5.1.2"
381 | }
382 | },
383 | "mongodb-core": {
384 | "version": "3.2.7",
385 | "resolved": "https://registry.npm.taobao.org/mongodb-core/download/mongodb-core-3.2.7.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmongodb-core%2Fdownload%2Fmongodb-core-3.2.7.tgz",
386 | "integrity": "sha1-qO8f52Shksl5JS2svGANyI134o8=",
387 | "requires": {
388 | "bson": "^1.1.1",
389 | "require_optional": "^1.0.1",
390 | "safe-buffer": "^5.1.2",
391 | "saslprep": "^1.0.0"
392 | }
393 | },
394 | "mongoose": {
395 | "version": "5.6.4",
396 | "resolved": "https://registry.npm.taobao.org/mongoose/download/mongoose-5.6.4.tgz?cache=0&sync_timestamp=1562636245196&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmongoose%2Fdownload%2Fmongoose-5.6.4.tgz",
397 | "integrity": "sha1-BJyorVrBalKMI6IDAVefeskv2w4=",
398 | "requires": {
399 | "async": "2.6.2",
400 | "bson": "~1.1.1",
401 | "kareem": "2.3.0",
402 | "mongodb": "3.2.7",
403 | "mongodb-core": "3.2.7",
404 | "mongoose-legacy-pluralize": "1.0.2",
405 | "mpath": "0.6.0",
406 | "mquery": "3.2.1",
407 | "ms": "2.1.2",
408 | "regexp-clone": "1.0.0",
409 | "safe-buffer": "5.1.2",
410 | "sift": "7.0.1",
411 | "sliced": "1.0.1"
412 | },
413 | "dependencies": {
414 | "ms": {
415 | "version": "2.1.2",
416 | "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz",
417 | "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk="
418 | }
419 | }
420 | },
421 | "mongoose-legacy-pluralize": {
422 | "version": "1.0.2",
423 | "resolved": "https://registry.npm.taobao.org/mongoose-legacy-pluralize/download/mongoose-legacy-pluralize-1.0.2.tgz",
424 | "integrity": "sha1-O6n5H6UHtRhtOZ+0CFS/8Y+1Y+Q="
425 | },
426 | "mpath": {
427 | "version": "0.6.0",
428 | "resolved": "https://registry.npm.taobao.org/mpath/download/mpath-0.6.0.tgz",
429 | "integrity": "sha1-qpIgKfyk8PZB82DnTFwbakxHB44="
430 | },
431 | "mquery": {
432 | "version": "3.2.1",
433 | "resolved": "https://registry.npm.taobao.org/mquery/download/mquery-3.2.1.tgz",
434 | "integrity": "sha1-iwWaSc2uCoqegEKE72TC9Y06wF0=",
435 | "requires": {
436 | "bluebird": "3.5.1",
437 | "debug": "3.1.0",
438 | "regexp-clone": "^1.0.0",
439 | "safe-buffer": "5.1.2",
440 | "sliced": "1.0.1"
441 | },
442 | "dependencies": {
443 | "debug": {
444 | "version": "3.1.0",
445 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz",
446 | "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
447 | "requires": {
448 | "ms": "2.0.0"
449 | }
450 | }
451 | }
452 | },
453 | "ms": {
454 | "version": "2.0.0",
455 | "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.0.0.tgz",
456 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
457 | },
458 | "multer": {
459 | "version": "1.4.1",
460 | "resolved": "https://registry.npm.taobao.org/multer/download/multer-1.4.1.tgz",
461 | "integrity": "sha1-JLEqQWoi/sKt6BBTkYS/E4cgFZ4=",
462 | "requires": {
463 | "append-field": "^1.0.0",
464 | "busboy": "^0.2.11",
465 | "concat-stream": "^1.5.2",
466 | "mkdirp": "^0.5.1",
467 | "object-assign": "^4.1.1",
468 | "on-finished": "^2.3.0",
469 | "type-is": "^1.6.4",
470 | "xtend": "^4.0.0"
471 | }
472 | },
473 | "negotiator": {
474 | "version": "0.6.2",
475 | "resolved": "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz",
476 | "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs="
477 | },
478 | "object-assign": {
479 | "version": "4.1.1",
480 | "resolved": "http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz",
481 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
482 | },
483 | "on-finished": {
484 | "version": "2.3.0",
485 | "resolved": "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz",
486 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
487 | "requires": {
488 | "ee-first": "1.1.1"
489 | }
490 | },
491 | "parseurl": {
492 | "version": "1.3.3",
493 | "resolved": "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz",
494 | "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ="
495 | },
496 | "path-to-regexp": {
497 | "version": "0.1.7",
498 | "resolved": "http://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz",
499 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
500 | },
501 | "process-nextick-args": {
502 | "version": "2.0.1",
503 | "resolved": "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz",
504 | "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I="
505 | },
506 | "proxy-addr": {
507 | "version": "2.0.5",
508 | "resolved": "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.5.tgz",
509 | "integrity": "sha1-NMvWSi2B9LH9IedvnwbIpFKZ7jQ=",
510 | "requires": {
511 | "forwarded": "~0.1.2",
512 | "ipaddr.js": "1.9.0"
513 | }
514 | },
515 | "qs": {
516 | "version": "6.7.0",
517 | "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fqs%2Fdownload%2Fqs-6.7.0.tgz",
518 | "integrity": "sha1-QdwaAV49WB8WIXdr4xr7KHapsbw="
519 | },
520 | "range-parser": {
521 | "version": "1.2.1",
522 | "resolved": "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz",
523 | "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE="
524 | },
525 | "raw-body": {
526 | "version": "2.4.0",
527 | "resolved": "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz",
528 | "integrity": "sha1-oc5vucm8NWylLoklarWQWeE9AzI=",
529 | "requires": {
530 | "bytes": "3.1.0",
531 | "http-errors": "1.7.2",
532 | "iconv-lite": "0.4.24",
533 | "unpipe": "1.0.0"
534 | }
535 | },
536 | "readable-stream": {
537 | "version": "1.1.14",
538 | "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freadable-stream%2Fdownload%2Freadable-stream-1.1.14.tgz",
539 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
540 | "requires": {
541 | "core-util-is": "~1.0.0",
542 | "inherits": "~2.0.1",
543 | "isarray": "0.0.1",
544 | "string_decoder": "~0.10.x"
545 | }
546 | },
547 | "regexp-clone": {
548 | "version": "1.0.0",
549 | "resolved": "https://registry.npm.taobao.org/regexp-clone/download/regexp-clone-1.0.0.tgz",
550 | "integrity": "sha1-Ii25Z2IydwViYLmSYmNUoEzpv2M="
551 | },
552 | "require_optional": {
553 | "version": "1.0.1",
554 | "resolved": "https://registry.npm.taobao.org/require_optional/download/require_optional-1.0.1.tgz",
555 | "integrity": "sha1-TPNaQkf2TKPfjC7yCMxJSxyo/C4=",
556 | "requires": {
557 | "resolve-from": "^2.0.0",
558 | "semver": "^5.1.0"
559 | }
560 | },
561 | "resolve-from": {
562 | "version": "2.0.0",
563 | "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-2.0.0.tgz",
564 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
565 | },
566 | "safe-buffer": {
567 | "version": "5.1.2",
568 | "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz",
569 | "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0="
570 | },
571 | "safer-buffer": {
572 | "version": "2.1.2",
573 | "resolved": "http://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz",
574 | "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo="
575 | },
576 | "saslprep": {
577 | "version": "1.0.3",
578 | "resolved": "https://registry.npm.taobao.org/saslprep/download/saslprep-1.0.3.tgz",
579 | "integrity": "sha1-TAL5RrVs9UKX40e6EJPnrKxM8iY=",
580 | "optional": true,
581 | "requires": {
582 | "sparse-bitfield": "^3.0.3"
583 | }
584 | },
585 | "semver": {
586 | "version": "5.7.0",
587 | "resolved": "https://registry.npm.taobao.org/semver/download/semver-5.7.0.tgz",
588 | "integrity": "sha1-eQp89v6lRZuslhELKbYEEtyP+Ws="
589 | },
590 | "send": {
591 | "version": "0.17.1",
592 | "resolved": "https://registry.npm.taobao.org/send/download/send-0.17.1.tgz",
593 | "integrity": "sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg=",
594 | "requires": {
595 | "debug": "2.6.9",
596 | "depd": "~1.1.2",
597 | "destroy": "~1.0.4",
598 | "encodeurl": "~1.0.2",
599 | "escape-html": "~1.0.3",
600 | "etag": "~1.8.1",
601 | "fresh": "0.5.2",
602 | "http-errors": "~1.7.2",
603 | "mime": "1.6.0",
604 | "ms": "2.1.1",
605 | "on-finished": "~2.3.0",
606 | "range-parser": "~1.2.1",
607 | "statuses": "~1.5.0"
608 | },
609 | "dependencies": {
610 | "ms": {
611 | "version": "2.1.1",
612 | "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.1.tgz",
613 | "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo="
614 | }
615 | }
616 | },
617 | "serve-static": {
618 | "version": "1.14.1",
619 | "resolved": "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz",
620 | "integrity": "sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk=",
621 | "requires": {
622 | "encodeurl": "~1.0.2",
623 | "escape-html": "~1.0.3",
624 | "parseurl": "~1.3.3",
625 | "send": "0.17.1"
626 | }
627 | },
628 | "setprototypeof": {
629 | "version": "1.1.1",
630 | "resolved": "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz",
631 | "integrity": "sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM="
632 | },
633 | "sift": {
634 | "version": "7.0.1",
635 | "resolved": "https://registry.npm.taobao.org/sift/download/sift-7.0.1.tgz",
636 | "integrity": "sha1-R9YsULFZ0xbxNy+LU/nBDNIaSwg="
637 | },
638 | "sliced": {
639 | "version": "1.0.1",
640 | "resolved": "https://registry.npm.taobao.org/sliced/download/sliced-1.0.1.tgz",
641 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
642 | },
643 | "sparse-bitfield": {
644 | "version": "3.0.3",
645 | "resolved": "https://registry.npm.taobao.org/sparse-bitfield/download/sparse-bitfield-3.0.3.tgz",
646 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
647 | "optional": true,
648 | "requires": {
649 | "memory-pager": "^1.0.2"
650 | }
651 | },
652 | "statuses": {
653 | "version": "1.5.0",
654 | "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz",
655 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
656 | },
657 | "streamsearch": {
658 | "version": "0.1.2",
659 | "resolved": "https://registry.npm.taobao.org/streamsearch/download/streamsearch-0.1.2.tgz",
660 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
661 | },
662 | "string_decoder": {
663 | "version": "0.10.31",
664 | "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz",
665 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
666 | },
667 | "toidentifier": {
668 | "version": "1.0.0",
669 | "resolved": "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz",
670 | "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM="
671 | },
672 | "type-is": {
673 | "version": "1.6.18",
674 | "resolved": "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz",
675 | "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=",
676 | "requires": {
677 | "media-typer": "0.3.0",
678 | "mime-types": "~2.1.24"
679 | }
680 | },
681 | "typedarray": {
682 | "version": "0.0.6",
683 | "resolved": "http://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz",
684 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
685 | },
686 | "unpipe": {
687 | "version": "1.0.0",
688 | "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz",
689 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
690 | },
691 | "util-deprecate": {
692 | "version": "1.0.2",
693 | "resolved": "http://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz",
694 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
695 | },
696 | "utils-merge": {
697 | "version": "1.0.1",
698 | "resolved": "http://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz",
699 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
700 | },
701 | "vary": {
702 | "version": "1.1.2",
703 | "resolved": "http://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz",
704 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
705 | },
706 | "xtend": {
707 | "version": "4.0.2",
708 | "resolved": "https://registry.npm.taobao.org/xtend/download/xtend-4.0.2.tgz",
709 | "integrity": "sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q="
710 | }
711 | }
712 | }
713 |
--------------------------------------------------------------------------------
/react-admin-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-admin-server",
3 | "version": "1.0.0",
4 | "description": "react-admin server project",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node app.js"
9 | },
10 | "author": "pubdreamcc (493310907@qq.com)",
11 | "license": "ISC",
12 | "dependencies": {
13 | "blueimp-md5": "^2.10.0",
14 | "body-parser": "^1.19.0",
15 | "cookie-parser": "^1.4.4",
16 | "express": "^4.17.1",
17 | "mongoose": "^5.6.4",
18 | "multer": "^1.4.1"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/react-admin-server/public/css/reset.css:
--------------------------------------------------------------------------------
1 | /*! minireset.css v0.0.4 | MIT License | github.com/jgthms/minireset.css */
2 | html,
3 | body,
4 | p,
5 | ol,
6 | ul,
7 | li,
8 | dl,
9 | dt,
10 | dd,
11 | blockquote,
12 | figure,
13 | fieldset,
14 | legend,
15 | textarea,
16 | pre,
17 | iframe,
18 | hr,
19 | h1,
20 | h2,
21 | h3,
22 | h4,
23 | h5,
24 | h6 {
25 | margin: 0;
26 | padding: 0;
27 | }
28 |
29 | h1,
30 | h2,
31 | h3,
32 | h4,
33 | h5,
34 | h6 {
35 | font-size: 100%;
36 | font-weight: normal;
37 | }
38 |
39 | ul {
40 | list-style: none;
41 | }
42 |
43 | button,
44 | input,
45 | select,
46 | textarea {
47 | margin: 0;
48 | }
49 |
50 | html {
51 | box-sizing: border-box;
52 | }
53 |
54 | *, *:before, *:after {
55 | box-sizing: inherit;
56 | }
57 |
58 | img,
59 | embed,
60 | iframe,
61 | object,
62 | video {
63 | height: auto;
64 | max-width: 100%;
65 | }
66 |
67 | audio {
68 | max-width: 100%;
69 | }
70 |
71 | iframe {
72 | border: 0;
73 | }
74 |
75 | table {
76 | border-collapse: collapse;
77 | border-spacing: 0;
78 | }
79 |
80 | td,
81 | th {
82 | padding: 0;
83 | text-align: left;
84 | }
85 |
86 | html, body {
87 | height: 100%;
88 | width: 100%;
89 | }
90 |
91 | #root {
92 | height: 100%;
93 | width: 100%;
94 | }
--------------------------------------------------------------------------------
/react-admin-server/public/images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/images/404.png
--------------------------------------------------------------------------------
/react-admin-server/public/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/images/bg.jpg
--------------------------------------------------------------------------------
/react-admin-server/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/images/logo.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1564476186270.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1564476186270.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1564476288784.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1564476288784.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1564476539374.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1564476539374.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1564666001484.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1564666001484.jpg
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1564666010571.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1564666010571.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566299317694.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566299317694.jpg
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566301200182.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566301200182.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566301329479.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566301329479.jpg
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566301335233.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566301335233.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566301793179.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566301793179.jpg
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566357626804.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566357626804.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566357653080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566357653080.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566461734181.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566461734181.png
--------------------------------------------------------------------------------
/react-admin-server/public/upload/image-1566462347560.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pubdreamcc/react-admin/f9ea6fc80db37c3ef4d3006eadb8100c1e5f95cf/react-admin-server/public/upload/image-1566462347560.png
--------------------------------------------------------------------------------
/react-admin-server/router/file-upload.js:
--------------------------------------------------------------------------------
1 | /*
2 | 处理文件上传的路由
3 | */
4 | const multer = require('multer')
5 | const path = require('path')
6 | const fs = require('fs')
7 |
8 | const dirPath = path.join(__dirname, '..', 'public/upload')
9 |
10 | const storage = multer.diskStorage({
11 | // destination: 'upload', //string时,服务启动将会自动创建文件夹
12 | destination: function (req, file, cb) { //函数需手动创建文件夹
13 | // console.log('destination()', file)
14 | if (!fs.existsSync(dirPath)) {
15 | fs.mkdir(dirPath, function (err) {
16 | if (err) {
17 | console.log(err)
18 | } else {
19 | cb(null, dirPath)
20 | }
21 | })
22 | } else {
23 | cb(null, dirPath)
24 | }
25 | },
26 | filename: function (req, file, cb) {
27 | // console.log('filename()', file)
28 | var ext = path.extname(file.originalname)
29 | cb(null, file.fieldname + '-' + Date.now() + ext)
30 | }
31 | })
32 | const upload = multer({storage})
33 | const uploadSingle = upload.single('image')
34 |
35 | module.exports = function fileUpload(router) {
36 |
37 | // 上传图片
38 | router.post('/manage/img/upload', (req, res) => {
39 | uploadSingle(req, res, function (err) { //错误处理
40 | if (err) {
41 | return res.send({
42 | status: 1,
43 | msg: '上传文件失败'
44 | })
45 | }
46 | var file = req.file
47 | res.send({
48 | status: 0,
49 | data: {
50 | name: file.filename,
51 | url: 'http://localhost:5000/upload/' + file.filename
52 | }
53 | })
54 |
55 | })
56 | })
57 |
58 | // 删除图片
59 | router.post('/manage/img/delete', (req, res) => {
60 | const {name} = req.body
61 | fs.unlink(path.join(dirPath, name), (err) => {
62 | if (err) {
63 | console.log(err)
64 | res.send({
65 | status: 1,
66 | msg: '删除文件失败'
67 | })
68 | } else {
69 | res.send({
70 | status: 0
71 | })
72 | }
73 | })
74 | })
75 | }
76 |
--------------------------------------------------------------------------------
/react-admin-server/router/index.js:
--------------------------------------------------------------------------------
1 | // 路由模块
2 | const express = require('express')
3 | const md5 = require('blueimp-md5')
4 | const fileUpload = require('./file-upload')
5 | // 引入模型构造函数,操作数据库
6 | const UserModel = require('../models/UserModel')
7 | const CategoryModel = require('../models/CategoryModel')
8 | const ProductModel = require('../models/ProductModel')
9 | const RoleModel = require('../models/RoleModel')
10 |
11 | // 得到路由器对象
12 | const router = express.Router()
13 |
14 | // 指定需要过滤的属性
15 | const filter = {password: 0, __v: 0}
16 |
17 | /*
18 | 得到指定数组的分页信息对象
19 | */
20 | function pageFilter(arr, pageNum, pageSize) {
21 | pageNum = pageNum * 1
22 | pageSize = pageSize * 1
23 | const total = arr.length
24 | const pages = Math.floor((total + pageSize - 1) / pageSize)
25 | const start = pageSize * (pageNum - 1)
26 | const end = start + pageSize <= total ? start + pageSize : total
27 | const list = []
28 | for (var i = start; i < end; i++) {
29 | list.push(arr[i])
30 | }
31 |
32 | return {
33 | pageNum,
34 | total,
35 | pages,
36 | pageSize,
37 | list
38 | }
39 | }
40 |
41 | /*
42 | 按照接口文档处理(设计)路由
43 | */
44 |
45 | // 登陆
46 |
47 | router.post('/login', (req, res) => {
48 | // 得到post请求体数据
49 | const {username, password} = req.body
50 | // 根据username,password查询数据库中集合users,如果查询成功则登陆成功,查询失败,返回错误消息
51 | UserModel.findOne({username, password: md5(password)})
52 | .then(user => {
53 | if (user) { // 登陆成功
54 | // 生成一个cookie(userid: user._id), 并交给浏览器保存
55 | res.cookie('userid', user._id, {maxAge: 1000 * 60 * 60 * 24})
56 | if (user.role_id) {
57 | RoleModel.findOne({_id: user.role_id})
58 | .then(role => {
59 | user._doc.role = role
60 | console.log('role user', user)
61 | res.send({status: 0, data: user})
62 | })
63 | } else {
64 | user._doc.role = {menus: []}
65 | // 返回登陆成功信息(包含user)
66 | res.send({status: 0, data: user})
67 | }
68 |
69 | } else {// 登陆失败
70 | res.send({status: 1, msg: '用户名或密码不正确!'})
71 | }
72 | })
73 | .catch(error => {
74 | console.error('登陆异常', error)
75 | res.send({status: 1, msg: '登陆异常, 请重新尝试'})
76 | })
77 | })
78 |
79 | // 添加用户
80 |
81 | router.post('/manage/user/add', (req, res) => {
82 | // 读取请求参数数据
83 | const {username, password} = req.body
84 | // 处理: 判断用户是否已经存在, 如果存在, 返回提示错误的信息, 如果不存在, 保存
85 | // 查询(根据username)
86 | UserModel.findOne({username})
87 | .then(user => {
88 | // 如果user有值(已存在)
89 | if (user) {
90 | // 返回提示错误的信息
91 | res.send({status: 1, msg: '此用户已存在'})
92 | return new Promise(() => {
93 | })
94 | } else { // 没值(不存在)
95 | // 保存
96 | return UserModel.create({...req.body, password: md5(password || 'pubdreamcc')})
97 | }
98 | })
99 | .then(user => {
100 | // 返回包含user的json数据
101 | res.send({status: 0, data: user})
102 | })
103 | .catch(error => {
104 | console.error('注册异常', error)
105 | res.send({status: 1, msg: '添加用户异常, 请重新尝试'})
106 | })
107 | })
108 |
109 | // 更新用户
110 |
111 | router.post('/manage/user/update', (req, res) => {
112 | const user = req.body
113 | UserModel.findOneAndUpdate({_id: user._id}, user)
114 | .then(oldUser => {
115 | const data = Object.assign(oldUser, user)
116 | // 返回
117 | res.send({status: 0, data})
118 | })
119 | .catch(error => {
120 | console.error('更新用户异常', error)
121 | res.send({status: 1, msg: '更新用户异常, 请重新尝试'})
122 | })
123 | })
124 |
125 | // 删除用户
126 |
127 | router.post('/manage/user/delete', (req, res) => {
128 | const {userId} = req.body
129 | UserModel.deleteOne({_id: userId})
130 | .then((doc) => {
131 | res.send({status: 0})
132 | })
133 | })
134 |
135 | // 获取所有用户列表
136 |
137 | router.get('/manage/user/list', (req, res) => {
138 | UserModel.find({username: {'$ne': 'admin'}})
139 | .then(users => {
140 | RoleModel.find().then(roles => {
141 | res.send({status: 0, data: {users, roles}})
142 | })
143 | })
144 | .catch(error => {
145 | console.error('获取用户列表异常', error)
146 | res.send({status: 1, msg: '获取用户列表异常, 请重新尝试'})
147 | })
148 | })
149 |
150 | // 添加分类
151 |
152 | router.post('/manage/category/add', (req, res) => {
153 | const {categoryName, parentId} = req.body
154 | CategoryModel.create({name: categoryName, parentId: parentId || '0'})
155 | .then(category => {
156 | res.send({status: 0, data: category})
157 | })
158 | .catch(error => {
159 | console.error('添加分类异常', error)
160 | res.send({status: 1, msg: '添加分类异常, 请重新尝试'})
161 | })
162 | })
163 |
164 | // 获取分类列表
165 |
166 | router.get('/manage/category/list', (req, res) => {
167 | const parentId = req.query.parentId || '0'
168 | CategoryModel.find({parentId})
169 | .then(categorys => {
170 | res.send({status: 0, data: categorys})
171 | })
172 | .catch(error => {
173 | console.error('获取分类列表异常', error)
174 | res.send({status: 1, msg: '获取分类列表异常, 请重新尝试'})
175 | })
176 | })
177 |
178 | // 更新分类名称
179 |
180 | router.post('/manage/category/update', (req, res) => {
181 | const {categoryId, categoryName} = req.body
182 | CategoryModel.findOneAndUpdate({_id: categoryId}, {name: categoryName})
183 | .then(oldCategory => {
184 | res.send({status: 0})
185 | })
186 | .catch(error => {
187 | console.error('更新分类名称异常', error)
188 | res.send({status: 1, msg: '更新分类名称异常, 请重新尝试'})
189 | })
190 | })
191 |
192 | // 根据分类ID获取分类
193 |
194 | router.get('/manage/category/info', (req, res) => {
195 | const categoryId = req.query.categoryId
196 | CategoryModel.findOne({_id: categoryId})
197 | .then(category => {
198 | res.send({status: 0, data: category})
199 | })
200 | .catch(error => {
201 | console.error('获取分类信息异常', error)
202 | res.send({status: 1, msg: '获取分类信息异常, 请重新尝试'})
203 | })
204 | })
205 |
206 | // 添加产品
207 | router.post('/manage/product/add', (req, res) => {
208 | const product = req.body
209 | ProductModel.create(product)
210 | .then(product => {
211 | res.send({status: 0, data: product})
212 | })
213 | .catch(error => {
214 | console.error('添加产品异常', error)
215 | res.send({status: 1, msg: '添加产品异常, 请重新尝试'})
216 | })
217 | })
218 |
219 | // 获取产品分页列表
220 | router.get('/manage/product/list', (req, res) => {
221 | const {pageNum, pageSize} = req.query
222 | ProductModel.find({})
223 | .then(products => {
224 | res.send({status: 0, data: pageFilter(products, pageNum, pageSize)})
225 | })
226 | .catch(error => {
227 | console.error('获取商品列表异常', error)
228 | res.send({status: 1, msg: '获取商品列表异常, 请重新尝试'})
229 | })
230 | })
231 |
232 | // 搜索产品列表
233 | router.get('/manage/product/search', (req, res) => {
234 | const {pageNum, pageSize, searchName, productName, productDesc} = req.query
235 | let contition = {}
236 | if (productName) {
237 | contition = {name: new RegExp(`^.*${productName}.*$`)}
238 | } else if (productDesc) {
239 | contition = {desc: new RegExp(`^.*${productDesc}.*$`)}
240 | }
241 | ProductModel.find(contition)
242 | .then(products => {
243 | res.send({status: 0, data: pageFilter(products, pageNum, pageSize)})
244 | })
245 | .catch(error => {
246 | console.error('搜索商品列表异常', error)
247 | res.send({status: 1, msg: '搜索商品列表异常, 请重新尝试'})
248 | })
249 | })
250 |
251 | // 更新产品
252 | router.post('/manage/product/update', (req, res) => {
253 | const product = req.body
254 | ProductModel.findOneAndUpdate({_id: product._id}, product)
255 | .then(oldProduct => {
256 | res.send({status: 0})
257 | })
258 | .catch(error => {
259 | console.error('更新商品异常', error)
260 | res.send({status: 1, msg: '更新商品名称异常, 请重新尝试'})
261 | })
262 | })
263 |
264 | // 更新产品状态(上架/下架)
265 | router.post('/manage/product/updateStatus', (req, res) => {
266 | const {productId, status} = req.body
267 | ProductModel.findOneAndUpdate({_id: productId}, {status})
268 | .then(oldProduct => {
269 | res.send({status: 0})
270 | })
271 | .catch(error => {
272 | console.error('更新产品状态异常', error)
273 | res.send({status: 1, msg: '更新产品状态异常, 请重新尝试'})
274 | })
275 | })
276 |
277 |
278 | // 添加角色
279 | router.post('/manage/role/add', (req, res) => {
280 | const {roleName} = req.body
281 | RoleModel.create({name: roleName})
282 | .then(role => {
283 | res.send({status: 0, data: role})
284 | })
285 | .catch(error => {
286 | console.error('添加角色异常', error)
287 | res.send({status: 1, msg: '添加角色异常, 请重新尝试'})
288 | })
289 | })
290 |
291 | // 获取角色列表
292 | router.get('/manage/role/list', (req, res) => {
293 | RoleModel.find()
294 | .then(roles => {
295 | res.send({status: 0, data: roles})
296 | })
297 | .catch(error => {
298 | console.error('获取角色列表异常', error)
299 | res.send({status: 1, msg: '获取角色列表异常, 请重新尝试'})
300 | })
301 | })
302 |
303 | // 更新角色(设置权限)
304 | router.post('/manage/role/update', (req, res) => {
305 | const role = req.body
306 | role.auth_time = Date.now()
307 | RoleModel.findOneAndUpdate({_id: role._id}, role)
308 | .then(oldRole => {
309 | // console.log('---', oldRole._doc)
310 | res.send({status: 0, data: {...oldRole._doc, ...role}})
311 | })
312 | .catch(error => {
313 | console.error('更新角色异常', error)
314 | res.send({status: 1, msg: '更新角色异常, 请重新尝试'})
315 | })
316 | })
317 |
318 | fileUpload(router)
319 |
320 | module.exports = router
--------------------------------------------------------------------------------