├── 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独显 内置

\n

99999

\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]

\n

JavaScript开发经典入门图书 打通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独显 内置

\n

99999

\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 | 98 | {/* 利用menu配置文件来动态生成menu菜单项,更具有灵活性,便于后期不同角色分配不同的菜单权限 */} 99 | {this.menu} 100 | {/* 101 | 102 | 首页 103 | 104 | 108 | 109 | 商品 110 | 111 | } 112 | > 113 | 114 | 115 | 品类管理 116 | 117 | 118 | 119 | 商品管理 120 | 121 | 122 | 123 | 124 | 用户登录 125 | 126 | 127 | 128 | 角色管理 129 | 130 | 134 | 135 | 图形图表 136 | 137 | } 138 | > 139 | 140 | 141 | 柱形图 142 | 143 | 144 | 145 | 折线图 146 | 147 | 148 | 149 | 饼图 150 | 151 | */} 152 | 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 |
推荐使用Google浏览器,可以获得更加页面操作体验
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 |
20 | 21 | { 22 | getFieldDecorator( 23 | 'parentId', { 24 | initialValue: parentId 25 | } 26 | )( 27 | 31 | ) 32 | } 33 | 34 | 35 | { 36 | getFieldDecorator( 37 | 'categoryName', { 38 | rules: [{required: true, message: '分类名称必须输入!'}], 39 | initialValue: '' 40 | } 41 | )( 42 | 43 | ) 44 | } 45 | 46 |
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 | 17 | 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 |
45 | 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 |
197 | 198 | {getFieldDecorator('name', { 199 | rules: [{required: true, message: '商品名称必须输入'}], 200 | initialValue: product.name 201 | })()} 202 | 203 | 204 | {getFieldDecorator('desc', { 205 | rules: [{required: true, message: '商品描述必须输入'}], 206 | initialValue: product.desc 207 | })()} 208 | 209 | 210 | {getFieldDecorator('price', { 211 | rules: [{required: true, message: '商品价格必须有'}, {validator: this.validatePrice}], 212 | initialValue: product.price 213 | })()} 214 | 215 | 216 | {getFieldDecorator('categoryIds', { 217 | rules: [{required: true, message: '商品必须有分类'}], 218 | initialValue: categoryIds 219 | })( 220 | 225 | )} 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 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 |
94 | 95 |
Upload
96 |
97 | ) 98 | return ( 99 |
100 | 109 | {fileList.length >= 3 ? null : uploadButton} 110 | 111 | 112 | example 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 | 23 | 24 | { 25 | getFieldDecorator('roleName', { 26 | rules: [{required: true, message: '角色名称必须输入'}] 27 | })( 28 | 29 | ) 30 | } 31 | 32 | 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 | 71 | 72 | 73 | 74 | 80 | 81 | {this.treeNodes} 82 | 83 | 84 | 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 | 34 | 35 | { 36 | getFieldDecorator('username', { 37 | rules: [{message: '用户名必须输入!', required: true}], 38 | initialValue: user.username 39 | })( 40 | 41 | ) 42 | } 43 | 44 | { 45 | user.password ? null : ( 46 | 47 | { 48 | getFieldDecorator('password', { 49 | rules: [{message: '密码必须输入!', required: true}] 50 | })( 51 | 52 | ) 53 | } 54 | 55 | ) 56 | } 57 | 58 | { 59 | getFieldDecorator('phone', { 60 | rules: [{message: '手机号必须输入!', required: true}], 61 | initialValue: user.phone 62 | })( 63 | 64 | ) 65 | } 66 | 67 | 68 | { 69 | getFieldDecorator('email', { 70 | rules: [{message: '邮箱必须输入!', required: true}], 71 | initialValue: user.email 72 | })( 73 | 74 | ) 75 | } 76 | 77 | 78 | { 79 | getFieldDecorator('role_id', { 80 | rules: [{required: true, message: '角色必须输入!'}], 81 | initialValue: user.role_id 82 | })( 83 | 86 | ) 87 | } 88 | 89 | 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 --------------------------------------------------------------------------------