Преглед изворни кода

增加购物车管理模块

zhanghe пре 6 година
родитељ
комит
8609115436
100 измењених фајлова са 7386 додато и 3885 уклоњено
  1. 3 0
      package.json
  2. 19 19
      src/common/menu.js
  3. 185 83
      src/common/router.js
  4. 209 0
      src/components/RBDragSortTable/index.js
  5. 24 0
      src/components/RBDragSortTable/index.less
  6. 33 26
      src/components/RBList/StandardTableList.js
  7. 0 3
      src/components/RBList/StandardTableList.less
  8. 1 1
      src/components/RBRemoteSelect/index.js
  9. 1 5
      src/components/RBSelectTable/RBSingleSelectTable.js
  10. 144 0
      src/components/RBTableSelector/MultipleSelectTable.js
  11. 32 0
      src/components/RBTableSelector/MultipleSelectTable.less
  12. 245 0
      src/components/RBTableSelector/Selector.js
  13. 62 0
      src/components/RBTableSelector/Selector.less
  14. 93 0
      src/components/RBTableSelector/SingleSelectTable.js
  15. 24 0
      src/components/RBTableSelector/SingleSelectTable.less
  16. 255 0
      src/components/RBTableSelector/columnsMap.js
  17. 22 0
      src/components/RBTableSelector/columnsMap.less
  18. 9 0
      src/index.less
  19. 26 7
      src/layouts/BasicLayout.js
  20. 12 1
      src/models/campus.js
  21. 104 0
      src/models/cmsUser.js
  22. 105 0
      src/models/courseware.js
  23. 0 49
      src/models/error.js
  24. 0 47
      src/models/form.js
  25. 105 0
      src/models/lesson.js
  26. 0 41
      src/models/list.js
  27. 27 3
      src/models/merchant.js
  28. 201 0
      src/models/product.js
  29. 0 38
      src/models/profile.js
  30. 192 0
      src/models/shelves.js
  31. 105 0
      src/models/tag.js
  32. 105 0
      src/models/tagGroup.js
  33. 177 0
      src/models/terminal.js
  34. 78 0
      src/models/trade.js
  35. 165 137
      src/routes/Campus/CampusCreate.js
  36. 1 5
      src/routes/Campus/CampusCreate.less
  37. 54 51
      src/routes/Campus/CampusList.js
  38. 1 1
      src/routes/Campus/CampusList.less
  39. 0 7
      src/routes/Exception/style.less
  40. 0 65
      src/routes/Exception/triggerException.js
  41. 0 290
      src/routes/Forms/AdvancedForm.js
  42. 0 188
      src/routes/Forms/BasicForm.js
  43. 0 122
      src/routes/Forms/StepForm/Step1.js
  44. 0 114
      src/routes/Forms/StepForm/Step2.js
  45. 0 59
      src/routes/Forms/StepForm/Step3.js
  46. 0 53
      src/routes/Forms/StepForm/index.js
  47. 0 75
      src/routes/Forms/StepForm/style.less
  48. 0 227
      src/routes/Forms/TableForm.js
  49. 391 0
      src/routes/Frontend/Tag/TagCreate.js
  50. 43 0
      src/routes/Frontend/Tag/TagCreate.less
  51. 174 0
      src/routes/Frontend/Tag/TagList.js
  52. 16 0
      src/routes/Frontend/Tag/TagList.less
  53. 5 4
      src/routes/Merchant/Merchant.js
  54. 307 0
      src/routes/Frontend/TagGroup/TagGroupCreate.js
  55. 34 0
      src/routes/Frontend/TagGroup/TagGroupCreate.less
  56. 185 0
      src/routes/Frontend/TagGroup/TagGroupList.js
  57. 16 0
      src/routes/Frontend/TagGroup/TagGroupList.less
  58. 5 4
      src/routes/Campus/Campus.js
  59. 0 200
      src/routes/List/Applications.js
  60. 0 52
      src/routes/List/Applications.less
  61. 0 236
      src/routes/List/Articles.js
  62. 0 65
      src/routes/List/Articles.less
  63. 0 148
      src/routes/List/BasicList.js
  64. 0 176
      src/routes/List/BasicList.less
  65. 0 90
      src/routes/List/CardList.js
  66. 0 111
      src/routes/List/CardList.less
  67. 0 79
      src/routes/List/List.js
  68. 0 173
      src/routes/List/Projects.js
  69. 0 55
      src/routes/List/Projects.less
  70. 0 415
      src/routes/List/TableList.js
  71. 0 48
      src/routes/List/TableList.less
  72. 154 161
      src/routes/Merchant/MerchantCreate.js
  73. 105 0
      src/routes/Merchant/MerchantDeposit.js
  74. 0 0
      src/routes/Merchant/MerchantDeposit.less
  75. 0 112
      src/routes/Merchant/MerchantDespoit.js
  76. 25 22
      src/routes/Merchant/MerchantList.js
  77. 1 1
      src/routes/Merchant/MerchantList.less
  78. 576 0
      src/routes/Product/Course/CourseCreate.js
  79. 34 0
      src/routes/Product/Course/CourseCreate.less
  80. 182 0
      src/routes/Product/Course/CourseList.js
  81. 16 0
      src/routes/Product/Course/CourseList.less
  82. 5 4
      src/routes/Campus/Campus.js
  83. 364 0
      src/routes/Product/Courseware/CoursewareCreate.js
  84. 45 0
      src/routes/Product/Courseware/CoursewareCreate.less
  85. 182 0
      src/routes/Product/Courseware/CoursewareList.js
  86. 16 0
      src/routes/Product/Courseware/CoursewareList.less
  87. 5 4
      src/routes/Campus/Campus.js
  88. 284 0
      src/routes/Product/Lesson/LessonCreate.js
  89. 15 0
      src/routes/Product/Lesson/LessonCreate.less
  90. 182 0
      src/routes/Product/Lesson/LessonList.js
  91. 16 0
      src/routes/Product/Lesson/LessonList.less
  92. 5 4
      src/routes/Campus/Campus.js
  93. 394 0
      src/routes/Product/Package/PackageCreate.js
  94. 42 0
      src/routes/Product/Package/PackageCreate.less
  95. 181 0
      src/routes/Product/Package/PackageList.js
  96. 16 0
      src/routes/Product/Package/PackageList.less
  97. 5 4
      src/routes/Campus/Campus.js
  98. 508 0
      src/routes/Product/Support/SupportCreate.js
  99. 33 0
      src/routes/Product/Support/SupportCreate.less
  100. 0 0
      src/routes/Product/Support/SupportList.js

+ 3 - 0
package.json

@@ -32,6 +32,7 @@
     "enquire-js": "^0.1.1",
     "fastclick": "^1.0.6",
     "hls.js": "^0.9.1",
+    "immutability-helper": "^2.6.6",
     "lodash": "^4.17.4",
     "lodash-decorators": "^4.4.1",
     "moment": "^2.19.1",
@@ -43,6 +44,8 @@
     "rc-drawer-menu": "^0.5.0",
     "react": "^16.2.0",
     "react-container-query": "^0.9.1",
+    "react-dnd": "^2.6.0",
+    "react-dnd-html5-backend": "^2.6.0",
     "react-document-title": "^2.0.3",
     "react-dom": "^16.2.0",
     "react-fittext": "^1.0.0",

+ 19 - 19
src/common/menu.js

@@ -41,7 +41,7 @@ const menuData = () => {
       path: 'product',
       children: [{
         name: '制作课件',
-        path: 'ware',
+        path: 'courseware',
       }, {
         name: '制作课',
         path: 'lesson',
@@ -52,39 +52,36 @@ const menuData = () => {
         name: '制作配套',
         path: 'support',
       }, {
-        name: '制作套包',
+        name: '制作套包',
         path: 'package',
       }],
     }, {
       name: '产品出售',
       icon: 'shop',
-      path: 'goods',
+      path: 'shelves',
       children: [{
-        name: '虚拟课程',
-        path: 'virtual',
+        name: '上架课程',
+        path: 'course',
       }, {
-        name: '实体物品',
-        path: 'entity',
+        name: '上架配套',
+        path: 'support',
       }, {
-        name: '打包套装',
+        name: '上架套餐包',
         path: 'package',
       }],
     }, {
       name: '前端配置',
       icon: 'android-o',
-      path: 'app',
+      path: 'frontend',
       children: [{
         name: '首页入口',
         path: 'tagGroup',
       }, {
-        name: '标签栏目',
+        name: '侧边栏目',
         path: 'tag',
-      }, {
-        name: '推荐位配置',
-        path: 'recommend',
       }],
     }, {
-      name: '订单系统',
+      name: '交易管理',
       icon: 'trademark',
       path: 'trade',
       children: [{
@@ -112,20 +109,23 @@ const menuData = () => {
         path: 'list',
       }],
     }, {
-      name: '终端用户',
+      name: '终端管理',
       path: 'terminal',
       icon: <RBIcon type="terminal" />,
       children: [{
-        name: '终端列表',
-        path: 'list',
+        name: '终端用户',
+        path: 'user',
+      }, {
+        name: '白名单',
+        path: 'whitelist',
       }],
     }, {
       name: '系统管理',
       path: 'system',
       icon: <RBIcon type="systemuser" />,
       children: [{
-        name: '用户列表',
-        path: 'list',
+        name: '系统用户',
+        path: 'cms-user',
       }],
     }];
   } else if (plantform === 'PJ') {

+ 185 - 83
src/common/router.js

@@ -73,26 +73,7 @@ export const getRouterData = (app) => {
     '/': {
       component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),
     },
-    '/resource/image': {
-      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/PictureList')),
-    },
-    '/resource/imageCreate': {
-      component: dynamicWrapper(app, [], () => import('../routes/Resource/PictureCreate')),
-    },
-    '/resource/imageCreate/single': {
-      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/PictureCreateSingle')),
-      name: '单图上传',
-    },
-    '/resource/imageCreate/multiple': {
-      component: dynamicWrapper(app, [], () => import('../routes/Resource/PictureCreateMultiple')),
-      name: '多图上传',
-    },
-    '/resource/video': {
-      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/VideoList')),
-    },
-    '/merchant': {
-      component: dynamicWrapper(app, ['merchant'], () => import('../routes/Merchant/Merchant')),
-    },
+    // 厂商管理相关路由注册
     '/merchant/list': {
       component: dynamicWrapper(app, ['merchant'], () => import('../routes/Merchant/MerchantList')),
       name: '商户列表',
@@ -105,13 +86,11 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['merchant'], () => import('../routes/Merchant/MerchantCreate')),
       name: '编辑商户',
     },
-    '/merchant/despoit/:id': {
-      component: dynamicWrapper(app, ['merchant'], () => import('../routes/Merchant/MerchantDespoit')),
+    '/merchant/deposit/:id': {
+      component: dynamicWrapper(app, ['merchant'], () => import('../routes/Merchant/MerchantDeposit')),
       name: '余额充值',
     },
-    '/campus': {
-      component: dynamicWrapper(app, ['campus'], () => import('../routes/Campus/Campus')),
-    },
+    // 校区管理相关路由注册
     '/campus/list': {
       component: dynamicWrapper(app, ['campus', 'merchant'], () => import('../routes/Campus/CampusList')),
       name: '校区列表',
@@ -124,72 +103,206 @@ export const getRouterData = (app) => {
       component: dynamicWrapper(app, ['campus', 'merchant'], () => import('../routes/Campus/CampusCreate')),
       name: '编辑校区',
     },
-    '/dashboard/analysis': {
-      component: dynamicWrapper(app, ['chart'], () => import('../routes/Dashboard/Analysis')),
+    // 资源管理相关路由注册
+    '/resource/image': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/PictureList')),
     },
-    '/dashboard/monitor': {
-      component: dynamicWrapper(app, ['monitor'], () => import('../routes/Dashboard/Monitor')),
+    '/resource/imageCreate': {
+      component: dynamicWrapper(app, [], () => import('../routes/Resource/PictureCreate')),
     },
-    '/dashboard/workplace': {
-      component: dynamicWrapper(app, ['project', 'activities', 'chart'], () => import('../routes/Dashboard/Workplace')),
-      // hideInBreadcrumb: true,
-      // name: '工作台',
-      // authority: 'admin',
+    '/resource/imageCreate/single': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/PictureCreateSingle')),
+      name: '单图上传',
+    },
+    '/resource/imageCreate/multiple': {
+      component: dynamicWrapper(app, [], () => import('../routes/Resource/PictureCreateMultiple')),
+      name: '多图上传',
+    },
+    '/resource/video': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/VideoList')),
+    },
+    // 系统管理相关路由注册
+    '/system/cms-user': {
+      component: dynamicWrapper(app, ['cmsUser'], () => import('../routes/System/CmsUser')),
+    },
+    '/system/cms-user/list': {
+      component: dynamicWrapper(app, ['cmsUser'], () => import('../routes/System/CmsUser/CmsUserList')),
+    },
+    '/system/cms-user/create': {
+      component: dynamicWrapper(app, ['cmsUser', 'merchant'], () => import('../routes/System/CmsUser/CmsUserCreate')),
+    },
+    '/system/cms-user/edit/:id': {
+      component: dynamicWrapper(app, ['cmsUser'], () => import('../routes/System/CmsUser/CmsUserEdit')),
+    },
+    // 终端管理相关路由注册
+    '/terminal/user': {
+      component: dynamicWrapper(app, ['terminal', 'campus', 'merchant'], () => import('../routes/Terminal/User')),
+    },
+    '/terminal/user/list': {
+      component: dynamicWrapper(app, ['terminal', 'campus', 'merchant'], () => import('../routes/Terminal/User/TerminalList')),
     },
-    '/form/basic-form': {
-      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/BasicForm')),
+    '/terminal/user/create': {
+      component: dynamicWrapper(app, ['terminal', 'campus'], () => import('../routes/Terminal/User/TerminalCreate')),
     },
-    '/form/step-form': {
-      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm')),
+    '/terminal/user/edit/:id': {
+      component: dynamicWrapper(app, ['terminal'], () => import('../routes/Terminal/User/TerminalEdit')),
     },
-    '/form/step-form/info': {
-      name: '分步表单(填写转账信息)',
-      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step1')),
+    '/terminal/whitelist': {
+      component: dynamicWrapper(app, ['terminal'], () => import('../routes/Terminal/WhiteList')),
     },
-    '/form/step-form/confirm': {
-      name: '分步表单(确认转账信息)',
-      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step2')),
+    '/terminal/whitelist/list': {
+      component: dynamicWrapper(app, ['terminal'], () => import('../routes/Terminal/WhiteList/WhiteList')),
     },
-    '/form/step-form/result': {
-      name: '分步表单(完成)',
-      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step3')),
+    '/terminal/whitelist/create': {
+      component: dynamicWrapper(app, ['terminal'], () => import('../routes/Terminal/WhiteList/WhiteListCreate')),
     },
-    '/form/advanced-form': {
-      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/AdvancedForm')),
+    '/terminal/whitelist/edit/:id': {
+      component: dynamicWrapper(app, ['terminal'], () => import('../routes/Terminal/WhiteList/WhiteListCreate')),
     },
-    '/list/table-list': {
-      component: dynamicWrapper(app, ['rule'], () => import('../routes/List/TableList')),
+    // 产品管理相关路由注册
+    '/product/courseware': {
+      component: dynamicWrapper(app, [], () => import('../routes/Product/Courseware')),
     },
-    '/list/basic-list': {
-      component: dynamicWrapper(app, ['list'], () => import('../routes/List/BasicList')),
+    '/product/courseware/list': {
+      component: dynamicWrapper(app, ['courseware'], () => import('../routes/Product/Courseware/CoursewareList')),
     },
-    '/list/card-list': {
-      component: dynamicWrapper(app, ['list'], () => import('../routes/List/CardList')),
+    '/product/courseware/create': {
+      component: dynamicWrapper(app, ['courseware', 'resource'], () => import('../routes/Product/Courseware/CoursewareCreate')),
     },
-    '/list/search': {
-      component: dynamicWrapper(app, ['list'], () => import('../routes/List/List')),
+    '/product/courseware/edit/:id': {
+      component: dynamicWrapper(app, ['courseware', 'resource'], () => import('../routes/Product/Courseware/CoursewareCreate')),
     },
-    '/list/search/projects': {
-      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Projects')),
+    '/product/lesson': {
+      component: dynamicWrapper(app, ['lesson'], () => import('../routes/Product/Lesson')),
     },
-    '/list/search/applications': {
-      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Applications')),
+    '/product/lesson/list': {
+      component: dynamicWrapper(app, ['lesson'], () => import('../routes/Product/Lesson/LessonList')),
     },
-    '/list/search/articles': {
-      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Articles')),
+    '/product/lesson/create': {
+      component: dynamicWrapper(app, ['lesson', 'courseware'], () => import('../routes/Product/Lesson/LessonCreate')),
     },
-    '/profile/basic': {
-      component: dynamicWrapper(app, ['profile'], () => import('../routes/Profile/BasicProfile')),
+    '/product/lesson/edit/:id': {
+      component: dynamicWrapper(app, ['lesson', 'courseware'], () => import('../routes/Product/Lesson/LessonCreate')),
     },
-    '/profile/advanced': {
-      component: dynamicWrapper(app, ['profile'], () => import('../routes/Profile/AdvancedProfile')),
+    '/product/course': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Course')),
     },
-    '/result/success': {
-      component: dynamicWrapper(app, [], () => import('../routes/Result/Success')),
+    '/product/course/list': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Course/CourseList')),
     },
-    '/result/fail': {
-      component: dynamicWrapper(app, [], () => import('../routes/Result/Error')),
+    '/product/course/create': {
+      component: dynamicWrapper(app, ['lesson', 'resource', 'product', 'merchant'], () => import('../routes/Product/Course/CourseCreate')),
     },
+    '/product/course/edit/:id': {
+      component: dynamicWrapper(app, ['lesson', 'resource', 'product', 'merchant'], () => import('../routes/Product/Course/CourseCreate')),
+    },
+    '/product/support': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Support')),
+    },
+    '/product/support/list': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Support/SupportList')),
+    },
+    '/product/support/create': {
+      component: dynamicWrapper(app, ['resource', 'product', 'merchant'], () => import('../routes/Product/Support/SupportCreate')),
+    },
+    '/product/support/edit/:id': {
+      component: dynamicWrapper(app, ['resource', 'product', 'merchant'], () => import('../routes/Product/Support/SupportCreate')),
+    },
+    '/product/package': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Package')),
+    },
+    '/product/package/list': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Package/PackageList')),
+    },
+    '/product/package/create': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Package/PackageCreate')),
+    },
+    '/product/package/edit/:id': {
+      component: dynamicWrapper(app, ['product'], () => import('../routes/Product/Package/PackageCreate')),
+    },
+    // 产品出售相关路由注册
+    '/shelves/course': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Course')),
+    },
+    '/shelves/course/list': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Course/CourseList')),
+    },
+    '/shelves/course/create': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Course/CourseCreate')),
+    },
+    '/shelves/course/edit': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Course/CourseEdit')),
+    },
+    '/shelves/support': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Support')),
+    },
+    '/shelves/support/list': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Support/SupportList')),
+    },
+    '/shelves/support/create': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Support/SupportCreate')),
+    },
+    '/shelves/support/edit': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Support/SupportEdit')),
+    },
+    '/shelves/package': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Package')),
+    },
+    '/shelves/package/list': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Package/PackageList')),
+    },
+    '/shelves/package/create': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Package/PackageCreate')),
+    },
+    '/shelves/package/edit': {
+      component: dynamicWrapper(app, ['shelves'], () => import('../routes/Shelves/Package/PackageEdit')),
+    },
+    // 前端管理相关路由注册
+    '/frontend/tagGroup': {
+      component: dynamicWrapper(app, ['tagGroup'], () => import('../routes/Frontend/TagGroup')),
+    },
+    '/frontend/tagGroup/list': {
+      component: dynamicWrapper(app, ['tagGroup'], () => import('../routes/Frontend/TagGroup/TagGroupList')),
+    },
+    '/frontend/tagGroup/create': {
+      component: dynamicWrapper(app, ['tagGroup', 'merchant'], () => import('../routes/Frontend/TagGroup/TagGroupCreate')),
+    },
+    '/frontend/tagGroup/edit/:id': {
+      component: dynamicWrapper(app, ['tagGroup', 'merchant'], () => import('../routes/Frontend/TagGroup/TagGroupCreate')),
+    },
+    '/frontend/tag': {
+      component: dynamicWrapper(app, ['tag'], () => import('../routes/Frontend/Tag')),
+    },
+    '/frontend/tag/list': {
+      component: dynamicWrapper(app, ['tag'], () => import('../routes/Frontend/Tag/TagList')),
+    },
+    '/frontend/tag/create': {
+      component: dynamicWrapper(app, ['tag', 'tagGroup', 'shelves'], () => import('../routes/Frontend/Tag/TagCreate')),
+    },
+    '/frontend/tag/edit/:id': {
+      component: dynamicWrapper(app, ['tag', 'tagGroup', 'shelves'], () => import('../routes/Frontend/Tag/TagCreate')),
+    },
+    // 交易管理相关路由注册
+    '/trade/shopcart': {
+      component: dynamicWrapper(app, [], () => import('../routes/Trade/ShopCart')),
+    },
+    '/trade/shopcart/list': {
+      component: dynamicWrapper(app, ['terminal', 'campus', 'merchant'], () => import('../routes/Trade/ShopCart/ShopCartList')),
+    },
+    '/trade/shopcart/detail/:id': {
+      component: dynamicWrapper(app, ['trade'], () => import('../routes/Trade/ShopCart/ShopCartDetail')),
+    },
+    // 统计概览相关路由注册
+    '/dashboard/analysis': {
+      component: dynamicWrapper(app, ['chart'], () => import('../routes/Dashboard/Analysis')),
+    },
+    '/dashboard/monitor': {
+      component: dynamicWrapper(app, ['monitor'], () => import('../routes/Dashboard/Monitor')),
+    },
+    '/dashboard/workplace': {
+      component: dynamicWrapper(app, ['project', 'activities', 'chart'], () => import('../routes/Dashboard/Workplace')),
+    },
+    // 异常相关路由注册
     '/exception/403': {
       component: dynamicWrapper(app, [], () => import('../routes/Exception/403')),
     },
@@ -199,24 +312,13 @@ export const getRouterData = (app) => {
     '/exception/500': {
       component: dynamicWrapper(app, [], () => import('../routes/Exception/500')),
     },
-    '/exception/trigger': {
-      component: dynamicWrapper(app, ['error'], () => import('../routes/Exception/triggerException')),
-    },
+    // 登录相关路由注册
     '/user': {
       component: dynamicWrapper(app, [], () => import('../layouts/UserLayout')),
     },
     '/user/login': {
       component: dynamicWrapper(app, ['login'], () => import('../routes/User/Login')),
     },
-    '/user/register': {
-      component: dynamicWrapper(app, ['register'], () => import('../routes/User/Register')),
-    },
-    '/user/register-result': {
-      component: dynamicWrapper(app, [], () => import('../routes/User/RegisterResult')),
-    },
-    // '/user/:id': {
-    //   component: dynamicWrapper(app, [], () => import('../routes/User/SomeComponent')),
-    // },
   };
   // Get name from ./menu.js or just set it in the router data.
   const menuData = getFlatMenuData(getMenuData());

+ 209 - 0
src/components/RBDragSortTable/index.js

@@ -0,0 +1,209 @@
+import React, { PureComponent } from 'react';
+import { Table, Button } from 'antd';
+import { DragDropContext, DragSource, DropTarget } from 'react-dnd';
+import HTML5Backend from 'react-dnd-html5-backend';
+import update from 'immutability-helper';
+import styles from './index.less';
+
+class RBDragSortTable extends PureComponent {
+  handleMoveRow = (dragIndex, hoverIndex) => {
+    const { data, onChange } = this.props;
+    let newData = data;
+    newData[dragIndex] = newData.splice(hoverIndex, 1, newData[dragIndex])[0];
+    onChange(newData);
+  }
+  swapper = (arr, targetIndex, currentIndex) => {
+    let tmpArray = [...arr];
+    tmpArray[currentIndex] = tmpArray.splice(targetIndex, 1, tmpArray[currentIndex])[0];
+    return tmpArray;
+  }
+  handleMoveUp = (data, index) => {
+    if (index === 0) {
+      return;
+    }
+    this.props.onChange(this.swapper(data, index - 1, index));
+  }
+  handleMoveDown = (data, index) => {
+    if (index === data.length -1) {
+      return;
+    }
+    this.props.onChange(this.swapper(data, index + 1, index));
+  }
+  handleRemove = (data, index) => {
+    let tmpArray = [...data];
+    tmpArray.splice(index, 1);
+    this.props.onChange(tmpArray);
+  }
+
+  render() {
+    const { data, columns } = this.props;
+
+    let addExtraDatas = (data) => {
+      const newData = data.map((item, index) => ({
+        ...item,
+        key: item.id ? item.id : index + 1,
+        _location: index + 1,
+      }));
+      return newData;
+    }
+
+    let addExtraColumns = (columns) => {
+      const headColumn = {
+        title: '位置',
+        dataIndex: '_location',
+        key: '+01',
+        width: 60,
+      };
+      const tailColumn = {
+        title: '操作',
+        dataIndex: '_operation',
+        key: '-01',
+        width: 190,
+        render: (text, record, index) => (
+          <div className={styles.operation}>
+            <span>
+              <Button
+                size="small"
+                type="primary"
+                onClick={() => this.handleMoveUp(data, index)}
+              >上移
+              </Button>
+            </span>
+            <span>
+              <Button
+                size="small"
+                type="primary"
+                onClick={() => this.handleMoveDown(data, index)}
+              >下移
+              </Button>
+            </span>
+            <span>
+              <Button
+                size="small"
+                style={{background: '#f5222d', color: '#fff'}}
+                onClick={() => this.handleRemove(data, index)}
+              >删除
+              </Button>
+            </span>
+          </div>
+        ),
+      };
+      return [headColumn, ...columns, tailColumn];
+    }
+
+    let dragDirection = (
+      dragIndex,
+      hoverIndex,
+      initialClientOffset,
+      clientOffset,
+      sourceClientOffset,
+    ) => {
+      const hoverMiddleY = (initialClientOffset.y - sourceClientOffset.y) / 2;
+      const hoverClientY = clientOffset.y - sourceClientOffset.y;
+      if (dragIndex < hoverIndex && hoverClientY > hoverMiddleY) {
+        return 'downward';
+      }
+      if (dragIndex > hoverIndex && hoverClientY < hoverMiddleY) {
+        return 'upward';
+      }
+    }
+
+    let BodyRow = (props) => {
+      const {
+        isOver,
+        connectDragSource,
+        connectDropTarget,
+        moveRow,
+        dragRow,
+        clientOffset,
+        sourceClientOffset,
+        initialClientOffset,
+        ...restProps
+      } = props;
+      const style = { ...restProps.style, cursor: 'move' };
+
+      let className = restProps.className;
+      if (isOver && initialClientOffset) {
+        const direction = dragDirection(
+          dragRow.index,
+          restProps.index,
+          initialClientOffset,
+          clientOffset,
+          sourceClientOffset
+        );
+        if (direction === 'downward') {
+          className += ' drop-over-downward';
+        }
+        if (direction === 'upward') {
+          className += ' drop-over-upward';
+        }
+      }
+
+      return connectDragSource(
+        connectDropTarget(
+          <tr
+            {...restProps}
+            className={className}
+            style={style}
+          />
+        )
+      );
+    };
+
+    const rowSource = {
+      beginDrag(props) {
+        return {
+          index: props.index,
+        };
+      },
+    };
+
+    const rowTarget = {
+      drop(props, monitor) {
+        const dragIndex = monitor.getItem().index;
+        const hoverIndex = props.index;
+        if (dragIndex === hoverIndex) {
+          return;
+        }
+        props.moveRow(dragIndex, hoverIndex);
+        monitor.getItem().index = hoverIndex;
+      },
+    };
+
+    BodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({
+      connectDropTarget: connect.dropTarget(),
+      isOver: monitor.isOver(),
+      sourceClientOffset: monitor.getSourceClientOffset(),
+    }))(
+      DragSource('row', rowSource, (connect, monitor) => ({
+        connectDragSource: connect.dragSource(),
+        dragRow: monitor.getItem(),
+        clientOffset: monitor.getClientOffset(),
+        initialClientOffset: monitor.getInitialClientOffset(),
+      }))(BodyRow)
+    );
+
+    const components = {
+      body: {
+        row: BodyRow,
+      }
+    };
+
+    return (
+      <Table
+        bordered={true}
+        pagination={false}
+        className={styles.dragTable}
+        columns={addExtraColumns(columns)}
+        dataSource={addExtraDatas(data)}
+        components={components}
+        onRow={(record, index) => ({
+          index,
+          moveRow: this.handleMoveRow,
+        })}
+      />
+    );
+  }
+}
+
+export default DragDropContext(HTML5Backend)(RBDragSortTable);

+ 24 - 0
src/components/RBDragSortTable/index.less

@@ -0,0 +1,24 @@
+@import "~antd/lib/style/themes/default.less";
+
+.dragTable {
+  :global {
+    .ant-table-thead > tr > th {
+      padding: 10px 5px;
+    }
+    .ant-table-tbody > tr.drop-over-downward > td {
+      border-bottom: 2px dashed #1890ff;
+    }
+    .ant-table-tbody > tr.drop-over-upward > td {
+      border-top: 2px dashed #1890ff;
+    }
+    .ant-table-tbody > tr > td {
+      padding: 5px;
+    }
+  }
+}
+
+.operation {
+  span {
+    margin-left: 5px;
+  }
+}

+ 33 - 26
src/components/RBList/StandardTableList.js

@@ -77,16 +77,16 @@ export default class StandardTableList extends PureComponent {
           <div className={styles.basicSearch}>
             <div className={styles.left}>
               {showStatusSelect && (
-              <Select
-                onChange={this.handleStatusChange}
-                value={this.state.selectedStatusKey}
-                style={{ width: '20%', marginRight: 10 }}
-              >
-                <Select.Option key="all" value="ALL">全部状态</Select.Option>
-                <Select.Option key="normal" value="NORMAL">正常</Select.Option>
-                <Select.Option key="delete" value="DEL">已删除</Select.Option>
-              </Select>
-)}
+                <Select
+                  onChange={this.handleStatusChange}
+                  value={this.state.selectedStatusKey}
+                  style={{ width: '20%', marginRight: 10 }}
+                >
+                  <Select.Option key="all" value="ALL">全部状态</Select.Option>
+                  <Select.Option key="normal" value="NORMAL">正常</Select.Option>
+                  <Select.Option key="delete" value="DEL">已删除</Select.Option>
+                </Select>
+              )}
               <Input.Search
                 value={this.state.searchInputValue}
                 style={{ width: '75%' }}
@@ -113,25 +113,25 @@ export default class StandardTableList extends PureComponent {
             </div>
             <div className={styles.right}>
               {onAdvanceFilterClick && (
-              <a
-                className={styles.searchLevel}
-                onClick={onAdvanceFilterClick}
-              >高级筛选
-              </a>
-)}
+                <a
+                  className={styles.searchLevel}
+                  onClick={onAdvanceFilterClick}
+                >高级筛选
+                </a>
+              )}
               <Button icon="sync" onClick={this.handleRefreshBtnClick}>刷新</Button>
               {/* noCreate 参数控制是否显示新建按钮 */}
               {onCreateClick !== undefined && (
-              <Authorized authority="root" noMatch={null}>
-                <Button
-                  icon="plus"
-                  type="primary"
-                  style={{ marginLeft: 5 }}
-                  onClick={onCreateClick}
-                >新建
-                </Button>
-              </Authorized>
-)}
+                <Authorized authority="root" noMatch={null}>
+                  <Button
+                    icon="plus"
+                    type="primary"
+                    style={{ marginLeft: 5 }}
+                    onClick={onCreateClick}
+                  >新建
+                  </Button>
+                </Authorized>
+              )}
             </div>
           </div>
         </div>
@@ -307,6 +307,10 @@ export default class StandardTableList extends PureComponent {
   }
   handleRowSelectChange = (selectedKeys) => {
     this.setState({ selectedKeys });
+    // 把selectedKeys传递给父组件
+    if (this.props.rowSelectChange) {
+      this.props.rowSelectChange(selectedKeys);
+    }
   }
 
   render() {
@@ -323,6 +327,9 @@ export default class StandardTableList extends PureComponent {
     const rowSelection = {
       selectedKeys,
       onChange: this.handleRowSelectChange,
+      getCheckboxProps: record => ({
+        disabled: record.disabled,
+      }),
     };
     return (
       <Table

+ 0 - 3
src/components/RBList/StandardTableList.less

@@ -16,7 +16,6 @@
     }
   }
 }
-
 .header {
   .headerSearch {
     margin-bottom: 10px;
@@ -32,7 +31,6 @@
     }
   }
 }
-
 .footer {
   height: 35px;
   line-height: 35px;
@@ -46,7 +44,6 @@
     margin-right: 15px;
   }
 }
-
 .searchLevel {
   margin-right: 10px;
 }

+ 1 - 1
src/components/RBRemoteSelect/index.js

@@ -4,7 +4,7 @@ import { Select, Spin } from 'antd';
 
 export default class RBRemoteSelect extends PureComponent {
   static defaultProps = {
-    placeholder: '请输入检索内容进行选择',
+    placeholder: '请输入检索内容并根据检索结果进行选择',
   };
   static propTypes = {
     dataSource: PropTypes.array.isRequired,

+ 1 - 5
src/components/RBSelectTable/RBSingleSelectTable.js

@@ -136,16 +136,12 @@ export default class RBSingleSelectTable extends PureComponent {
       selectRowName: record.name,
     });
     if (this.props.onChange) {
-      this.props.onChange(record.key);
+      this.props.onChange(record);
     }
   }
   handleSearchBtnClick = () => {
     this.handleFilterOperation();
   }
-  handleTableChange = (pagination) => {
-    const { current } = pagination;
-    this.handleFilterOperation({ pageNo: current });
-  }
   handleListPageChange = (page, pageSize) => {
     this.handleFilterOperation({
       pageSize,

+ 144 - 0
src/components/RBTableSelector/MultipleSelectTable.js

@@ -0,0 +1,144 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Table, Pagination, Button } from 'antd';
+import styles from './MultipleSelectTable.less';
+
+export default class MultipleSelectTable extends Component {
+  static defaultProps = {
+    loading: false,
+    columns: [],
+    dataSource: [],
+    pagination: false,
+  };
+  static propTypes = {
+    loading: PropTypes.bool,
+    columns: PropTypes.array,
+    dataSource: PropTypes.array,
+    pagination: PropTypes.oneOfType([
+      PropTypes.object,
+      PropTypes.bool,
+    ]),
+  };
+  constructor(props) {
+    super(props);
+    this.state = {
+      selectedRows: props.selectedRows,
+      selectedRowKeys: props.selectedRowKeys,
+    };
+  }
+
+  /**
+   * 全选操作回调
+   */
+  handleCheckAllOperation = () => {
+    const allSelectableRows = this.props.dataSource.filter(rowData => !rowData.disabled);
+    const allSelectableRowKeys = allSelectableRows.map(rowData => rowData.key);
+    this.setState({
+      selectedRows: allSelectableRows,
+      selectedRowKeys: allSelectableRowKeys,
+    });
+  }
+  handleTransferOperation = () => {
+    this.props.onTransfer(this.state.selectedRows);
+    this.setState({
+      selectedRows: [],
+      selectedRowKeys: [],
+    });
+  }
+  /**
+   * 移除操作回调
+   */
+  handleRemoveOperation = () => {
+    this.props.onRemove(this.state.selectedRows)
+  }
+  /**
+   * 选中项变化回调
+   * @param selectedRowKeys
+   * @param selectedRows
+   */
+  handleRowSelectChange = (selectedRowKeys, selectedRows) => {
+    this.setState({
+      selectedRows,
+      selectedRowKeys,
+    });
+  }
+  /**
+   * 待选table页码变化回调
+   * @param page
+   * @param pageSize
+   */
+  handleTableChange = (page, pageSize) => {
+    this.props.onChange(page, pageSize);
+  }
+
+  render() {
+    const { loading, columns, dataSource, pagination, tableType } = this.props;
+    const { selectedRowKeys } = this.state;
+
+    const rowSelection = {
+      selectedRowKeys,
+      onChange: this.handleRowSelectChange,
+      getCheckboxProps: record => ({
+        disabled: record.disabled,
+      }),
+    };
+
+    const renderTableFooter = (paginationProps) => {
+      if (paginationProps) {
+        return (
+          <Pagination
+            {...paginationProps}
+            onChange={this.handleTableChange}
+          />
+        );
+      } else {
+        return false;
+      }
+    }
+
+    return (
+      <div className={styles.container}>
+        <div className={styles.tableWrapper}>
+          <Table
+            bordered
+            className={styles.table}
+            pagination={false}
+            loading={loading}
+            columns={columns}
+            dataSource={dataSource}
+            footer={() => renderTableFooter(pagination)}
+            rowKey={record => record.key}
+            rowSelection={rowSelection}
+            onChange={this.handleTableChange}
+            scroll={{ y: 350 }}
+          />
+        </div>
+        <div className={styles.buttonWrapper}>
+          <Button
+            size="small"
+            onClick={this.handleCheckAllOperation}
+            style={{ marginRight: 10 }}
+          >全选
+          </Button>
+          {tableType === 'waiting' ?
+            (
+              <Button
+                size="small"
+                type="primary"
+                onClick={this.handleTransferOperation}
+              >确定
+              </Button>
+            ) : (
+              <Button
+                size="small"
+                type="primary"
+                onClick={this.handleRemoveOperation}
+              >移除
+              </Button>
+            )
+          }
+        </div>
+      </div>
+    );
+  }
+}

+ 32 - 0
src/components/RBTableSelector/MultipleSelectTable.less

@@ -0,0 +1,32 @@
+@import "~antd/lib/style/themes/default.less";
+
+.container {
+  .tableWrapper {
+  }
+  .buttonWrapper {
+    height: 50px;
+    line-height: 50px;
+    text-align: center;
+    vertical-align: middle;
+    border-top: 2px dashed #eee;
+    margin-top: 10px;
+  }
+}
+
+.table {
+  :global {
+    .ant-table-wrapper {
+      height: 300px;
+    }
+    .ant-table-tbody > tr > td {
+      padding: 5px;
+    }
+    .ant-table-thead > tr > th {
+      padding: 10px 5px;
+    }
+    .ant-table-footer {
+      height: 46px;
+      padding: 5px;
+    }
+  }
+}

+ 245 - 0
src/components/RBTableSelector/Selector.js

@@ -0,0 +1,245 @@
+import React, { PureComponent } from 'react';
+import { Select, Input, Button, Icon } from 'antd';
+import MultipleSelectTable from './MultipleSelectTable';
+import SingleSelectTable from './SingleSelectTable';
+import { getSelectorColumns } from './columnsMap';
+import { addRowKey } from '../../utils/utils';
+import styles from './Selector.less';
+
+function getRowKeys(rows) {
+  const rowKeys = rows.map((item) => item.key);
+  return rowKeys;
+}
+
+export default class Selector extends PureComponent {
+  constructor(props) {
+    super(props);
+    const rows = addRowKey(props.selectedRows || []);
+    const rowKeys = getRowKeys(props.selectedRowKeys || []);
+    this.state = {
+      searchKey: 'name',
+      searchValue: '',
+      selectedRows: rows,
+      selectedRowKeys: rowKeys,
+    };
+  }
+  /**
+   * 选择搜索字段
+   * @param value <code|name>
+   */
+  handleSelectChange = (value) => {
+    this.setState({
+      searchKey: value,
+    });
+  }
+  /**
+   * 搜索输入框内容改变回调
+   * @param e
+   */
+  handleInputChange = (e) => {
+    this.setState({
+      searchValue: e.target.value,
+    });
+  }
+  /**
+   * 过滤内容
+   * @param page
+   * @param pageSize
+   */
+  handleFilterOperation = (page, pageSize) => {
+    const { searchKey, searchValue } = this.state;
+    this.props.onChange({
+      pageSize,
+      pageNo: page,
+      [searchKey]: searchValue,
+    });
+  }
+  /**
+   * 点击`搜索`按钮回调
+   */
+  handleSearchOperation = () => {
+    const { searchKey, searchValue } = this.state;
+    this.props.onChange({
+      [searchKey]: searchValue,
+    });
+  }
+  /**
+   * 清除搜索框内容回调
+   */
+  handleInputClearOperation = () => {
+    this.setState({
+      searchValue: '',
+    });
+    this.props.onChange({});
+  }
+  /**
+   * 待选列表中的选中项放到已选列表中
+   * @params rows
+   */
+  handleMultipleTransfer = (rows) => {
+    const { selectedRowKeys, selectedRows } = this.state;
+    const newSelectedRowKeys = [];
+    const newSelectedRows = rows.filter((item) => {
+      if (selectedRowKeys.indexOf(item.key) === -1) {
+        newSelectedRowKeys.push(item.key);
+        return true;
+      }
+      return false;
+    });
+    this.setState({
+      selectedRows: [...selectedRows, ...newSelectedRows],
+      selectedRowKeys: [...selectedRowKeys, ...newSelectedRowKeys],
+    });
+  }
+  /**
+   * 单选模式选中的项,更新到state
+   * @param row
+   */
+  handleSingleTransfer = (row) => {
+    this.setState({
+      selectedRows: [row],
+    });
+  }
+  /**
+   * 点击`移除`按钮回调
+   * @params removeRows
+   */
+  handleRemoveOperation = (removeRows) => {
+    const { selectedRows } = this.state;
+    const newSelectedRowKeys = [];
+    const removeRowKeys = removeRows.map(item => item.key);
+    const newSelectedRows = selectedRows.filter((item) => {
+      if (removeRowKeys.indexOf(item.key) === -1) {
+        newSelectedRowKeys.push(item.key);
+        return true;
+      }
+      return false;
+    });
+    this.setState({
+      selectedRows: newSelectedRows,
+      selectedRowKeys: newSelectedRowKeys,
+    });
+  }
+  /**
+   * 点击`完成`按钮回调
+   */
+  handleFinishOperation = () => {
+    const { selectedRows } = this.state;
+    this.props.onFinish(selectedRows);
+  }
+  /**
+   * 点击`取消`按钮回调
+   */
+  handleCancelOperation = () => {
+    this.props.onCancel();
+  }
+
+  render() {
+    const {
+      pageNo, pageSize, totalSize, loading, list, multiple, selectorName, fixedName,
+    } = this.props;
+    const { selectedRows, searchKey, searchValue } = this.state;
+
+    const pagination = {
+      pageSize,
+      current: pageNo,
+      total: totalSize,
+    };
+
+    const renderMultipleListScope = () => {
+      return (
+        <div className={styles.content}>
+          <div className={styles.left}>
+            <MultipleSelectTable
+              tableType="waiting"
+              dataSource={addRowKey(list)}
+              loading={loading}
+              pagination={pagination}
+              onChange={this.handleFilterOperation}
+              onTransfer={this.handleMultipleTransfer}
+              columns={getSelectorColumns(selectorName)}
+            />
+          </div>
+          <div className={styles.center}>
+            <Icon className={styles.icon} type="forward" />
+          </div>
+          <div className={styles.right}>
+            <MultipleSelectTable
+              tableType="selected"
+              loading={false}
+              pagination={false}
+              dataSource={selectedRows}
+              onRemove={this.handleRemoveOperation}
+              columns={getSelectorColumns(fixedName || selectorName)}
+            />
+          </div>
+        </div>
+      );
+    }
+    const renderSingleListScope = () => {
+      return (
+        <div className={styles.content}>
+          <SingleSelectTable
+            loading={loading}
+            dataSource={addRowKey(list)}
+            pagination={pagination}
+            columns={getSelectorColumns(selectorName)}
+            onChange={this.handleFilterOperation}
+            onSingleTransfer={this.handleSingleTransfer}
+          />
+        </div>
+      );
+    }
+
+    return (
+      <div className={styles.wrapper}>
+        <div className={styles.header}>
+          {/* 这里写死了,搜索的字段一般就name和code */}
+          <div className={styles.select}>
+            <Select value={searchKey} onChange={this.handleSelectChange}>
+              <Select.Option key="code" value="code">编号</Select.Option>
+              <Select.Option key="name" value="name">名称</Select.Option>
+            </Select>
+          </div>
+          <div className={styles.input}>
+            <Input
+              suffix={
+                searchValue ?
+                  <Icon
+                    type="close-circle"
+                    onClick={this.handleInputClearOperation}
+                  />
+                : null
+              }
+              value={searchValue}
+              onChange={this.handleInputChange}
+              onPressEnter={this.handleSearchOperation}
+              placeholder="请输入"
+            />
+          </div>
+          <div className={styles.button}>
+            <Button
+              type="primary"
+              icon="search"
+              onClick={this.handleSearchOperation}
+            >搜索
+            </Button>
+          </div>
+        </div>
+        {multiple ? renderMultipleListScope() : renderSingleListScope()}
+        <div className={styles.footer}>
+          <Button
+            onClick={this.handleCancelOperation}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            style={{marginLeft: 20}}
+            onClick={this.handleFinishOperation}
+          >完成
+          </Button>
+        </div>
+      </div>
+    );
+  }
+}

+ 62 - 0
src/components/RBTableSelector/Selector.less

@@ -0,0 +1,62 @@
+@import "~antd/lib/style/themes/default.less";
+
+.wrapper {
+  width: 100%;
+  text-align: left;
+  .header {
+    width: 100%;
+    height: 40px;
+    line-height: 40px;
+    vertical-align: middle;
+    .select {
+      display: inline-block;
+    }
+    .input {
+      width: 250px;
+      margin-left: 10px;
+      display: inline-block;
+    }
+    .button {
+      margin-left: 10px;
+      display: inline-block;
+    }
+  }
+  .content {
+    margin-top: 10px;
+    .left {
+      width: 47%;
+      height: 500px;
+      padding: 5px;
+      display: inline-block;
+      vertical-align: middle;
+      border-radius: 5px;
+      box-shadow: 1px 1px 6px #999 inset;
+    }
+    .center {
+      width: 6%;
+      text-align: center;
+      display: inline-block;
+      :global {
+        .anticon {
+          font-size: 20px;
+        }
+      }
+    }
+    .right {
+      width: 47%;
+      height: 500px;
+      padding: 5px;
+      display: inline-block;
+      vertical-align: middle;
+      border-radius: 5px;
+      box-shadow: 1px 1px 6px #999 inset;
+    }
+  }
+  .footer {
+    height: 60px;
+    margin-top: 10px;
+    line-height: 60px;
+    vertical-align: middle;
+    text-align: center;
+  }
+}

+ 93 - 0
src/components/RBTableSelector/SingleSelectTable.js

@@ -0,0 +1,93 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Table, Radio, Pagination } from 'antd';
+import styles from './SingleSelectTable.less';
+
+export default class SingleSelectTable extends Component {
+  static defaultProps = {
+    loading: false,
+    columns: [],
+    dataSource: [],
+    pagination: false,
+  };
+  static propTypes = {
+    loading: PropTypes.bool,
+    columns: PropTypes.array,
+    dataSource: PropTypes.array,
+    pagination: PropTypes.oneOfType([
+      PropTypes.object,
+      PropTypes.bool,
+    ]),
+  };
+  state = {
+    selectedRowKey: null,
+    selectedRow: null,
+  };
+
+  handleRowClick = (record) => {
+    this.setState({
+      selectedRow: record,
+      selectedRowKey: record.key
+    });
+    this.props.onSingleTransfer(record);
+  }
+  handleTableChange = (page, pageSize) => {
+    this.props.onChange(page, pageSize);
+  }
+
+  render() {
+    const { loading, columns, dataSource, pagination } = this.props;
+    const { selectedRowKey } = this.state;
+
+    // 给首列加入单选按钮
+    const addColumnOnFirst = (cols) => {
+      const newColumns = [...cols];
+      newColumns.unshift({
+        key: '-1',
+        dataIndex: 'key',
+        width: 40,
+        render: (text) => {
+          return (
+            <Radio
+              key={text}
+              checked={selectedRowKey === text}
+            />
+          );
+        },
+      });
+      return newColumns;
+    };
+    const onRowClick = (record) => {
+      return {
+        onClick: () => this.handleRowClick(record),
+      };
+    };
+    const renderTableFooter = (paginationProps) => {
+      if (paginationProps) {
+        return (
+          <Pagination
+            {...paginationProps}
+            onChange={this.handleTableChange}
+          />
+        );
+      } else {
+        return false;
+      }
+    }
+    return (
+      <Table
+        bordered={true}
+        loading={loading}
+        footer={() => renderTableFooter(pagination)}
+        columns={addColumnOnFirst(columns)}
+        dataSource={dataSource}
+        pagination={false}
+        rowKey={record => record.key}
+        onRow={onRowClick}
+        onChange={this.handleTableChange}
+        className={styles.table}
+        scroll={{y:400}}
+      />
+    );
+  }
+}

+ 24 - 0
src/components/RBTableSelector/SingleSelectTable.less

@@ -0,0 +1,24 @@
+@import "~antd/lib/style/themes/default.less";
+
+.table {
+  :global {
+    .ant-table-wrapper {
+      height: 300px;
+    }
+    .ant-table-tbody > tr > td {
+      padding: 5px;
+    }
+    .ant-table-thead > tr > th {
+      padding: 10px 5px;
+    }
+    .ant-table-footer {
+      height: 46px;
+      padding: 5px;
+    }
+    .ant-table-row {
+      &:hover {
+        cursor: pointer;
+      }
+    }
+  }
+}

+ 255 - 0
src/components/RBTableSelector/columnsMap.js

@@ -0,0 +1,255 @@
+import React from 'react';
+import {
+  genAbsolutePicUrl,
+  renderProductType,
+  renderVideoQuality,
+  renderCategory, renderStatus,
+} from '../../utils/utils';
+import styles from './columnsMap.less';
+
+function renderPicture(path) {
+  return (
+    <div className={styles.thumb}>
+      <img src={genAbsolutePicUrl(path)} alt="" />
+    </div>
+  );
+}
+function renderPictureData(record) {
+  const { code, name } = record;
+  return (
+    <div className={styles.data}>
+      <p>{code}</p>
+      <p>{name}</p>
+    </div>
+  );
+}
+
+const clMap = {
+  Picture: {
+    columns: [{
+      title: '图片',
+      key: 1,
+      dataIndex: 'path',
+      render: path => renderPicture(path),
+      width: '25%',
+    }, {
+      title: '编号/名称',
+      key: 2,
+      dataIndex: 'record',
+      render: (_, record) => renderPictureData(record),
+    }],
+  },
+  PictureSingle: {
+    columns: [{
+      title: '图片',
+      key: 1,
+      dataIndex: 'path',
+      render: path => renderPicture(path),
+      width: '12%',
+    }, {
+      title: '图片编号',
+      key: 2,
+      dataIndex: 'code',
+      width: '25%',
+    }, {
+      title: '图片名称',
+      dataIndex: 'name',
+      key: 3,
+      width: '35%',
+    }, {
+      title: '图片格式',
+      dataIndex: 'format',
+      key: 4,
+      width: '12%',
+    }, {
+      title: '图片大小',
+      dataIndex: 'size',
+      key: 5,
+    }],
+  },
+  Video: {
+    columns: [{
+      title: '视频编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '30%',
+    }, {
+      title: '视频名称',
+      key: 2,
+      dataIndex: 'name',
+      width: '40%',
+    }, {
+      title: '视频格式',
+      key: 3,
+      dataIndex: 'format',
+      width: '15%',
+    }, {
+      title: '视频品质',
+      key: 4,
+      dataIndex: 'quality',
+      render: text => renderVideoQuality(text),
+    }],
+  },
+  Courseware: {
+    columns: [{
+      title: '课件编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '40%',
+    }, {
+      title: '课件名称',
+      key: 2,
+      dataIndex: 'title',
+    }],
+  },
+  Lesson: {
+    columns: [{
+      title: '课编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '40%',
+    }, {
+      title: '课名称',
+      key: 2,
+      dataIndex: 'title',
+    }],
+  },
+  Course: {
+    columns: [{
+      title: '课程编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '40%',
+    }, {
+      title: '课程名称',
+      key: 2,
+      dataIndex: 'name',
+    }],
+  },
+  Support: {
+    columns: [{
+      title: '配套编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '40%',
+    }, {
+      title: '配套名称',
+      key: 2,
+      dataIndex: 'name',
+    }],
+  },
+  Product: {
+    columns: [{
+      title: '产品编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '25%',
+    }, {
+      title: '产品名称',
+      key: 2,
+      dataIndex: 'name',
+      width: '45%',
+    }, {
+      title: '产品类型',
+      key: 3,
+      dataIndex: 'type',
+      render: text => renderProductType(text),
+    }],
+  },
+  Merchant: {
+    columns: [{
+      title: '厂商编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '30%',
+    }, {
+      title: '厂商名称',
+      key: 2,
+      dataIndex: 'name',
+      width: '30%',
+    }, {
+      title: '厂商类型',
+      key: 3,
+      dataIndex: 'domain',
+      render: text => renderCategory(text),
+    }],
+  },
+  Campus: {
+    columns: [{
+      title: '校区编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '20%',
+    }, {
+      title: '校区名称',
+      key: 2,
+      dataIndex: 'name',
+      width: '40%',
+    }, {
+      title: '所属厂商',
+      key: 3,
+      dataIndex: 'merchantName',
+    }],
+  },
+  Terminal: {
+    columns: [{
+      title: '终端编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '15%',
+    }, {
+      title: '终端名称',
+      key: 2,
+      dataIndex: 'name',
+      width: '15%',
+    }, {
+      title: '所属校区',
+      key: 3,
+      dataIndex: 'campusName',
+      width: '40%',
+    }, {
+      title: '所属渠道',
+      key: 4,
+      dataIndex: 'merchantName',
+    }],
+  },
+  TagGroup: {
+    columns: [{
+      title: '标签组编号',
+      key: 1,
+      dataIndex: 'code',
+      width: '45%',
+    }, {
+      title: '标签组名称',
+      key: 2,
+      dataIndex: 'name',
+      width: '20%',
+    }, {
+      title: '所属渠道名称',
+      key: 3,
+      dataIndex: 'merchantName',
+    }],
+  },
+  Tag: {
+    columns: [{
+      title: '标签名称',
+      key: 1,
+      dataIndex: 'name',
+      width: '40%',
+    }, {
+      title: '所属厂商',
+      key: 2,
+      dataIndex: 'merchantName',
+      width: '20%',
+    }, {
+      title: '标签状态',
+      key: 3,
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+    }],
+  },
+};
+
+export function getSelectorColumns(name) {
+  return clMap[name].columns;
+}

+ 22 - 0
src/components/RBTableSelector/columnsMap.less

@@ -0,0 +1,22 @@
+@import "~antd/lib/style/themes/default.less";
+
+.thumb {
+  position: relative;
+  vertical-align: middle;
+  text-align: center;
+  width: 90px;
+  height: 100px;
+  line-height: 100px;
+  img {
+    max-height: 100%;
+    max-width: 100%;
+    vertical-align: middle;
+    height: auto;
+  }
+}
+
+.data {
+  p {
+    font-weight: 500;
+  }
+}

+ 9 - 0
src/index.less

@@ -31,10 +31,19 @@ body {
   .ant-card-body {
     padding: 16px !important;
   }
+  .ant-card-head {
+    padding-left: 16px !important;
+  }
   .ant-message {
     width: unset !important;
     top: 50px !important;
     right: 10px;
     left: unset !important;
   }
+  .ant-message-notice {
+    text-align: right !important;
+  }
+  .ant-table-tbody > tr.ant-table-row-selected td {
+    background: #ffffb8 !important;
+  }
 }

+ 26 - 7
src/layouts/BasicLayout.js

@@ -38,6 +38,25 @@ const getRedirect = (item) => {
 };
 getMenuData().forEach(getRedirect);
 
+/**
+ * 获取面包屑映射
+ * @param {Object} menuData 菜单配置
+ * @param {Object} routerData 路由配置
+ */
+const getBreadcrumbNameMap = (menuData, routerData) => {
+  const result = {};
+  const childResult = {};
+  for (const i of menuData) {
+    if (!routerData[i.path]) {
+      result[i.path] = i;
+    }
+    if (i.children) {
+      Object.assign(childResult, getBreadcrumbNameMap(i.children, routerData));
+    }
+  }
+  return Object.assign({}, routerData, result, childResult);
+};
+
 const query = {
   'screen-xs': {
     maxWidth: 575,
@@ -68,7 +87,7 @@ class BasicLayout extends React.PureComponent {
   static childContextTypes = {
     location: PropTypes.object,
     breadcrumbNameMap: PropTypes.object,
-  }
+  };
   state = {
     isMobile,
   };
@@ -76,7 +95,7 @@ class BasicLayout extends React.PureComponent {
     const { location, routerData } = this.props;
     return {
       location,
-      breadcrumbNameMap: routerData,
+      breadcrumbNameMap: getBreadcrumbNameMap(getMenuData(), routerData),
     };
   }
   componentDidMount() {
@@ -112,34 +131,34 @@ class BasicLayout extends React.PureComponent {
       return '/dashboard/analysis';
     }
     return redirect;
-  }
+  };
   handleMenuCollapse = (collapsed) => {
     this.props.dispatch({
       type: 'global/changeLayoutCollapsed',
       payload: collapsed,
     });
-  }
+  };
   handleNoticeClear = (type) => {
     message.success(`清空了${type}`);
     this.props.dispatch({
       type: 'global/clearNotices',
       payload: type,
     });
-  }
+  };
   handleMenuClick = ({ key }) => {
     if (key === 'logout') {
       this.props.dispatch({
         type: 'login/logout',
       });
     }
-  }
+  };
   handleNoticeVisibleChange = (visible) => {
     if (visible) {
       this.props.dispatch({
         type: 'global/fetchNotices',
       });
     }
-  }
+  };
   render() {
     const {
       currentUser, collapsed, fetchingNotices, notices, routerData, match, location,

+ 12 - 1
src/models/campus.js

@@ -86,9 +86,20 @@ export default {
         ...action.payload,
       };
     },
-    cleanItemState(state) {
+    fixCurrentItem(state, action) {
+      const { currentItem } = state;
       return {
         ...state,
+        currentItem: {
+          ...currentItem,
+          ...action.payload,
+        },
+      };
+    },
+    cleanState(state) {
+      return {
+        ...state,
+        list: [],
         currentItem: {},
       };
     },

+ 104 - 0
src/models/cmsUser.js

@@ -0,0 +1,104 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  queryCmsUserList,
+  createCmsUserItem,
+  updateCmsUserItem,
+  deleteCmsUserItem,
+} from '../services/cmsUser';
+
+export default {
+  namespace: 'cmsUser',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchCmsUserList({ payload }, { call, put }) {
+      const response = yield call(queryCmsUserList, payload);
+      if (response.success) {
+        message.success('加载系统用户列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *createCmsUserItem({ payload, state }, { call, put }) {
+      const response = yield call(createCmsUserItem, payload);
+      if (response.success) {
+        message.success('创建系统用户成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/system/cms-user/list',
+        }));
+      }
+    },
+    *deleteCmsUserItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteCmsUserItem, payload);
+      if (response.success) {
+        message.success('禁用系统用户成功');
+        yield put({
+          type: 'fetchCmsUserList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *recoverCmsUserItem({ payload, states }, { call, put }) {
+      const response = yield call(updateCmsUserItem, payload);
+      if (response.success) {
+        message.success('解禁系统用户成功');
+        yield put({
+          type: 'fetchCmsUserList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *updateCmsUserItem({ payload, states }, { call, put }) {
+      const response = yield call(updateCmsUserItem, payload);
+      if (response.success) {
+        message.success('修改系统用户成功');
+        yield put(routerRedux.push({
+          pathname: '/system/cms-user/list',
+          state: states,
+        }));
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixCurrentItem(state, action) {
+      const { currentItem } = state;
+      return {
+        ...state,
+        currentItem: {
+          ...currentItem,
+          ...action.payload,
+        },
+      };
+    },
+    cleanState(state) {
+      return {
+        ...state,
+        list: [],
+        currentItem: {},
+      };
+    },
+  },
+};

+ 105 - 0
src/models/courseware.js

@@ -0,0 +1,105 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  queryCoursewareList,
+  queryCoursewareItem,
+  createCoursewareItem,
+  updateCoursewareItem,
+  deleteCoursewareItem,
+} from '../services/courseware';
+
+export default {
+  namespace: 'courseware',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchCoursewareList({ payload }, { call, put }) {
+      const response = yield call(queryCoursewareList, payload);
+      if (response.success) {
+        message.success('加载课件列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchCoursewareItem({ payload }, { call, put }) {
+      const response = yield call(queryCoursewareItem, payload);
+      if (response.success) {
+        message.success('加载课件详情成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data || {},
+          },
+        });
+      }
+    },
+    *createCoursewareItem({ payload, state }, { call, put }) {
+      const response = yield call(createCoursewareItem, payload);
+      if (response.success) {
+        message.success('创建课件成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/product/courseware',
+        }));
+      }
+    },
+    *deleteCoursewareItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteCoursewareItem, payload);
+      if (response.success) {
+        message.success('删除课件成功');
+        yield put({
+          type: 'fetchCoursewareList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *updateCoursewareItem({ payload, states }, { call, put }) {
+      const response = yield call(updateCoursewareItem, payload);
+      if (response.success) {
+        message.success('修改课件成功');
+        yield put(routerRedux.push({
+          pathname: '/product/courseware',
+          state: states,
+        }));
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixResourceList(state, action) {
+      return {
+        ...state,
+        currentItem: {
+          ...state.currentItem,
+          resourceList: action.payload,
+        }
+      };
+    },
+    cleanItemState(state) {
+      return {
+        ...state,
+        currentItem: {},
+      };
+    },
+  },
+};

+ 0 - 49
src/models/error.js

@@ -1,49 +0,0 @@
-// import { query403, query401, query404, query500 } from '../services/error';
-
-export default {
-  namespace: 'error',
-
-  state: {
-    error: '',
-    isloading: false,
-  },
-
-  effects: {
-    *query403(_, { call, put }) {
-      // yield call(query403);
-      yield put({
-        type: 'trigger',
-        payload: '403',
-      });
-    },
-    *query401(_, { call, put }) {
-      // yield call(query401);
-      yield put({
-        type: 'trigger',
-        payload: '401',
-      });
-    },
-    *query500(_, { call, put }) {
-      // yield call(query500);
-      yield put({
-        type: 'trigger',
-        payload: '500',
-      });
-    },
-    *query404(_, { call, put }) {
-      // yield call(query404);
-      yield put({
-        type: 'trigger',
-        payload: '404',
-      });
-    },
-  },
-
-  reducers: {
-    trigger(state, action) {
-      return {
-        error: action.payload,
-      };
-    },
-  },
-};

+ 0 - 47
src/models/form.js

@@ -1,47 +0,0 @@
-import { routerRedux } from 'dva/router';
-import { message } from 'antd';
-import { fakeSubmitForm } from '../services/api';
-
-export default {
-  namespace: 'form',
-
-  state: {
-    step: {
-      payAccount: 'ant-design@alipay.com',
-      receiverAccount: 'test@example.com',
-      receiverName: 'Alex',
-      amount: '500',
-    },
-  },
-
-  effects: {
-    *submitRegularForm({ payload }, { call }) {
-      yield call(fakeSubmitForm, payload);
-      message.success('提交成功');
-    },
-    *submitStepForm({ payload }, { call, put }) {
-      yield call(fakeSubmitForm, payload);
-      yield put({
-        type: 'saveStepFormData',
-        payload,
-      });
-      yield put(routerRedux.push('/form/step-form/result'));
-    },
-    *submitAdvancedForm({ payload }, { call }) {
-      yield call(fakeSubmitForm, payload);
-      message.success('提交成功');
-    },
-  },
-
-  reducers: {
-    saveStepFormData(state, { payload }) {
-      return {
-        ...state,
-        step: {
-          ...state.step,
-          ...payload,
-        },
-      };
-    },
-  },
-};

+ 105 - 0
src/models/lesson.js

@@ -0,0 +1,105 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  queryLessonList,
+  queryLessonItem,
+  createLessonItem,
+  updateLessonItem,
+  deleteLessonItem,
+} from '../services/lesson';
+
+export default {
+  namespace: 'lesson',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchLessonList({ payload }, { call, put }) {
+      const response = yield call(queryLessonList, payload);
+      if (response.success) {
+        message.success('加载课列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchLessonItem({ payload }, { call, put }) {
+      const response = yield call(queryLessonItem, payload);
+      if (response.success) {
+        message.success('加载课详情成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data || {},
+          },
+        });
+      }
+    },
+    *createLessonItem({ payload, state }, { call, put }) {
+      const response = yield call(createLessonItem, payload);
+      if (response.success) {
+        message.success('创建课成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/product/lesson',
+        }));
+      }
+    },
+    *deleteLessonItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteLessonItem, payload);
+      if (response.success) {
+        message.success('删除课成功');
+        yield put({
+          type: 'fetchLessonList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *updateLessonItem({ payload, states }, { call, put }) {
+      const response = yield call(updateLessonItem, payload);
+      if (response.success) {
+        message.success('修改课成功');
+        yield put(routerRedux.push({
+          pathname: '/product/lesson',
+          state: states,
+        }));
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixCoursewareList(state, action) {
+      return {
+        ...state,
+        currentItem: {
+          ...state.currentItem,
+          wareList: action.payload,
+        }
+      };
+    },
+    cleanItemState(state) {
+      return {
+        ...state,
+        currentItem: {},
+      };
+    },
+  },
+};

+ 0 - 41
src/models/list.js

@@ -1,41 +0,0 @@
-import { queryFakeList } from '../services/api';
-
-export default {
-  namespace: 'list',
-
-  state: {
-    list: [],
-  },
-
-  effects: {
-    *fetch({ payload }, { call, put }) {
-      const response = yield call(queryFakeList, payload);
-      yield put({
-        type: 'queryList',
-        payload: Array.isArray(response) ? response : [],
-      });
-    },
-    *appendFetch({ payload }, { call, put }) {
-      const response = yield call(queryFakeList, payload);
-      yield put({
-        type: 'appendList',
-        payload: Array.isArray(response) ? response : [],
-      });
-    },
-  },
-
-  reducers: {
-    queryList(state, action) {
-      return {
-        ...state,
-        list: action.payload,
-      };
-    },
-    appendList(state, action) {
-      return {
-        ...state,
-        list: state.list.concat(action.payload),
-      };
-    },
-  },
-};

+ 27 - 3
src/models/merchant.js

@@ -6,7 +6,9 @@ import {
   createMerchantItem,
   updateMerchantItem,
   deleteMerchantItem,
-  despoitMerchantItem,
+  depositMerchantItem,
+  queryMerchantRecommend,
+  updateMerchantRecommend,
 } from '../services/merchant';
 
 export default {
@@ -18,6 +20,7 @@ export default {
     pageSize: 15,
     totalSize: 0,
     currentItem: {},
+    recommendList: [],
   },
 
   effects: {
@@ -78,8 +81,8 @@ export default {
         }));
       }
     },
-    *despoitMerchantItem({ payload, states }, { call, put }) {
-      const response = yield call(despoitMerchantItem, payload);
+    *depositMerchantItem({ payload, states }, { call, put }) {
+      const response = yield call(depositMerchantItem, payload);
       if (response.success) {
         message.success('账户充值成功');
         yield put(routerRedux.push({
@@ -88,6 +91,27 @@ export default {
         }));
       }
     },
+    *queryMerchantRecommend({ payload }, { call, put }) {
+      const response = yield call(queryMerchantRecommend, payload);
+      if (response.success) {
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            recommendList: response.data || [],
+          },
+        });
+      }
+    },
+    *updateMerchantRecommend({ payload, states }, { call, put }) {
+      const response = yield call(updateMerchantRecommend, payload);
+      if (response.success) {
+        message.success('推荐位列表更新成功');
+        yield put(routerRedux.push({
+          pathname: '/merchant/list',
+          state: states,
+        }));
+      }
+    },
   },
 
   reducers: {

+ 201 - 0
src/models/product.js

@@ -0,0 +1,201 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  queryProductList,
+  queryProductItem,
+  createProductItem,
+  updateProductItem,
+  deleteProductItem,
+} from '../services/product';
+import {
+  PRODUCT_COURSE,
+  PRODUCT_SUPPORT,
+  PRODUCT_PACKAGE,
+} from '../utils/config';
+
+export default {
+  namespace: 'product',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchCourseList({ payload }, { call, put }) {
+      const response = yield call(queryProductList, {type: PRODUCT_COURSE, ...payload});
+      if (response.success) {
+        message.success('加载课程列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchSupportList({ payload }, { call, put }) {
+      const response = yield call(queryProductList, {type: PRODUCT_SUPPORT, ...payload});
+      if (response.success) {
+        message.success('加载配套列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchPackageList({ payload }, { call, put }) {
+      const response = yield call(queryProductList, {type: PRODUCT_PACKAGE, ...payload});
+      if (response.success) {
+        message.success('加载套餐包列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchProductItem({ payload }, { call, put }) {
+      const response = yield call(queryProductItem, payload);
+      if (response.success) {
+        message.success('加载详情成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data || {},
+          },
+        });
+      }
+    },
+    *deleteCourseItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteProductItem, payload);
+      if (response.success) {
+        message.success('删除课程成功');
+        yield put({
+          type: 'fetchCourseList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *deleteSupportItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteProductItem, payload);
+      if (response.success) {
+        message.success('删除配套成功');
+        yield put({
+          type: 'fetchSupportList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *deletePackageItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteProductItem, payload);
+      if (response.success) {
+        message.success('删除套餐包成功');
+        yield put({
+          type: 'fetchPackageList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *createCourseItem({ payload, state }, { call, put }) {
+      const response = yield call(createProductItem, {type: PRODUCT_COURSE, ...payload});
+      if (response.success) {
+        message.success('创建课程成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/product/course',
+        }));
+      }
+    },
+    *createSupportItem({ payload, state }, { call, put }) {
+      const response = yield call(createProductItem, {type: PRODUCT_SUPPORT, ...payload});
+      if (response.success) {
+        message.success('创建配套成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/product/support',
+        }));
+      }
+    },
+    *createPackageItem({ payload, state }, { call, put }) {
+      const response = yield call(createProductItem, {type: PRODUCT_PACKAGE, ...payload});
+      if (response.success) {
+        message.success('创建套餐包成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/product/package',
+        }));
+      }
+    },
+    *updateCourseItem({ payload, states }, { call, put }) {
+      const response = yield call(updateProductItem, {type: PRODUCT_COURSE, ...payload});
+      if (response.success) {
+        message.success('修改课程成功');
+        yield put(routerRedux.push({
+          pathname: '/product/course',
+          state: states,
+        }));
+      }
+    },
+    *updateSupportItem({ payload, states }, { call, put }) {
+      const response = yield call(updateProductItem, {type: PRODUCT_SUPPORT, ...payload});
+      if (response.success) {
+        message.success('修改配套成功');
+        yield put(routerRedux.push({
+          pathname: '/product/support',
+          state: states,
+        }));
+      }
+    },
+    *updatePackageItem({ payload, states }, { call, put }) {
+      const response = yield call(updateProductItem, {type: PRODUCT_PACKAGE, ...payload});
+      if (response.success) {
+        message.success('修改套餐包成功');
+        yield put(routerRedux.push({
+          pathname: '/product/package',
+          state: states,
+        }));
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    cleanItemState(state) {
+      return {
+        ...state,
+        currentItem: {},
+      };
+    },
+    fixCurrentItem(state, action) {
+      const { currentItem } = state;
+      return {
+        ...state,
+        currentItem: {
+          ...currentItem,
+          ...action.payload,
+        },
+      };
+    },
+  },
+};

+ 0 - 38
src/models/profile.js

@@ -1,38 +0,0 @@
-import { queryBasicProfile, queryAdvancedProfile } from '../services/api';
-
-export default {
-  namespace: 'profile',
-
-  state: {
-    basicGoods: [],
-    advancedOperation1: [],
-    advancedOperation2: [],
-    advancedOperation3: [],
-  },
-
-  effects: {
-    *fetchBasic(_, { call, put }) {
-      const response = yield call(queryBasicProfile);
-      yield put({
-        type: 'show',
-        payload: response,
-      });
-    },
-    *fetchAdvanced(_, { call, put }) {
-      const response = yield call(queryAdvancedProfile);
-      yield put({
-        type: 'show',
-        payload: response,
-      });
-    },
-  },
-
-  reducers: {
-    show(state, { payload }) {
-      return {
-        ...state,
-        ...payload,
-      };
-    },
-  },
-};

+ 192 - 0
src/models/shelves.js

@@ -0,0 +1,192 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  createItem,
+  deleteItem,
+  recoverItem,
+  queryItemList,
+  queryItemDetail,
+  createItemPrice,
+  updateItemPrice,
+  deleteItemPrice,
+  updateItemTags,
+} from '../services/shelves';
+import {
+  PRODUCT_COURSE,
+  PRODUCT_SUPPORT,
+  PRODUCT_PACKAGE,
+} from '../utils/config';
+
+export default {
+  namespace: 'shelves',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchItemList({ payload }, { call, put }) {
+      const response = yield call(queryItemList, payload);
+      if (response.success) {
+        message.success('加载商品列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchCourseItemList({ payload }, { call, put }) {
+      const response = yield call(queryItemList, { type: PRODUCT_COURSE, ...payload });
+      if (response.success) {
+        message.success('加载课程商品列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchSupportItemList({ payload }, { call, put }) {
+      const response = yield call(queryItemList, { type: PRODUCT_SUPPORT, ...payload });
+      if (response.success) {
+        message.success('加载配套商品列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchPackageItemList({ payload }, { call, put }) {
+      const response = yield call(queryItemList, { type: PRODUCT_PACKAGE, ...payload });
+      if (response.success) {
+        message.success('加载套餐包商品列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchItemDetail({ payload }, { call, put }) {
+      const response = yield call(queryItemDetail, payload);
+      if (response.success) {
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data || {},
+          },
+        });
+      }
+    },
+    *createItem({ payload, states }, { call, put }) {
+      const { itemType, ...restState } = states;
+      const response = yield call(createItem, payload);
+      if (response.success) {
+        message.success('上架成功');
+        yield put(routerRedux.push({
+          pathname: `/shelves/${itemType}`,
+          state: restState,
+        }));
+      }
+    },
+    *deleteItem({ payload }, { call, put }) {
+      const response = yield call(deleteItem, payload);
+      if (response.success) {
+        message.success('下架成功');
+      }
+    },
+    *recoverItem({ payload }, { call, put }) {
+      const response = yield call(recoverItem, payload);
+      if (response.success) {
+        message.success('恢复上架成功');
+      }
+    },
+    *createItemPrice({ payload, states }, { call, put }) {
+      const response = yield call(createItemPrice, payload);
+      if (response.success) {
+        message.success('添加价格成功');
+        yield put({
+          type: 'fetchItemDetail',
+          payload: states,
+        });
+      }
+    },
+    *updateItemPrice({ payload, states }, { call, put }) {
+      const response = yield call(updateItemPrice, payload);
+      if (response.success) {
+        message.success('修改价格成功');
+        yield put({
+          type: 'fetchItemDetail',
+          payload: states,
+        });
+      }
+    },
+    *deleteItemPrice({ payload, states }, { call, put }) {
+      const response = yield call(deleteItemPrice, payload);
+      if (response.success) {
+        message.success('删除价格成功');
+        yield put({
+          type: 'fetchItemDetail',
+          payload: states,
+        });
+      }
+    },
+    *updateItemTags({ payload, states }, { call, put }) {
+      const response = yield call(updateItemTags, payload);
+      if (response.success) {
+        message.success('修改上架栏目成功');
+        yield put({
+          type: 'fetchItemDetail',
+          payload: states,
+        });
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    cleanItemState(state) {
+      return {
+        ...state,
+        currentItem: {},
+      };
+    },
+    fixCurrentItem(state, action) {
+      const { currentItem } = state;
+      return {
+        ...state,
+        currentItem: {
+          ...currentItem,
+          ...action.payload,
+        }
+      };
+    }
+  },
+};

+ 105 - 0
src/models/tag.js

@@ -0,0 +1,105 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  queryTagList,
+  queryTagItem,
+  createTagItem,
+  updateTagItem,
+  deleteTagItem,
+} from '../services/tag';
+
+export default {
+  namespace: 'tag',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchTagList({ payload }, { call, put }) {
+      const response = yield call(queryTagList, payload);
+      if (response.success) {
+        message.success('加载标签表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchTagItem({ payload }, { call, put }) {
+      const response = yield call(queryTagItem, payload);
+      if (response.success) {
+        message.success('加载标签详情成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data || {},
+          },
+        });
+      }
+    },
+    *createTagItem({ payload, state }, { call, put }) {
+      const response = yield call(createTagItem, payload);
+      if (response.success) {
+        message.success('创建标签成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/frontend/tag',
+        }));
+      }
+    },
+    *deleteTagItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteTagItem, payload);
+      if (response.success) {
+        message.success('删除标签成功');
+        yield put({
+          type: 'fetchTagList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *updateTagItem({ payload, states }, { call, put }) {
+      const response = yield call(updateTagItem, payload);
+      if (response.success) {
+        message.success('修改标签成功');
+        yield put(routerRedux.push({
+          pathname: '/frontend/tag',
+          state: states,
+        }));
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixCurrentItem(state, action) {
+      return {
+        ...state,
+        currentItem: {
+          ...state.currentItem,
+          ...action.payload,
+        },
+      };
+    },
+    cleanItemState(state) {
+      return {
+        ...state,
+        currentItem: {},
+      };
+    },
+  },
+};

+ 105 - 0
src/models/tagGroup.js

@@ -0,0 +1,105 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  queryTagGroupList,
+  queryTagGroupItem,
+  createTagGroupItem,
+  updateTagGroupItem,
+  deleteTagGroupItem,
+} from '../services/tagGroup';
+
+export default {
+  namespace: 'tagGroup',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchTagGroupList({ payload }, { call, put }) {
+      const response = yield call(queryTagGroupList, payload);
+      if (response.success) {
+        message.success('加载标签组表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchTagGroupItem({ payload }, { call, put }) {
+      const response = yield call(queryTagGroupItem, payload);
+      if (response.success) {
+        message.success('加载标签组详情成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data || {},
+          },
+        });
+      }
+    },
+    *createTagGroupItem({ payload, state }, { call, put }) {
+      const response = yield call(createTagGroupItem, payload);
+      if (response.success) {
+        message.success('创建标签组成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/frontend/tagGroup',
+        }));
+      }
+    },
+    *deleteTagGroupItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteTagGroupItem, payload);
+      if (response.success) {
+        message.success('删除标签组成功');
+        yield put({
+          type: 'fetchTagGroupList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *updateTagGroupItem({ payload, states }, { call, put }) {
+      const response = yield call(updateTagGroupItem, payload);
+      if (response.success) {
+        message.success('修改标签组成功');
+        yield put(routerRedux.push({
+          pathname: '/frontend/tagGroup',
+          state: states,
+        }));
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixCurrentItem(state, action) {
+      return {
+        ...state,
+        currentItem: {
+          ...state.currentItem,
+          ...action.payload,
+        },
+      };
+    },
+    cleanItemState(state) {
+      return {
+        ...state,
+        currentItem: {},
+      };
+    },
+  },
+};

+ 177 - 0
src/models/terminal.js

@@ -0,0 +1,177 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  deviceUnbound,
+  queryTerminalList,
+  createTerminalItem,
+  updateTerminalItem,
+  deleteTerminalItem,
+  querySpecialTerminalList,
+  querySpecialTerminalItem,
+  createSpecialTerminalItem,
+  updateSpecialTerminalItem,
+  deleteSpecialTerminalItem,
+} from '../services/terminal';
+
+export default {
+  namespace: 'terminal',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchTerminalList({ payload }, { call, put }) {
+      const response = yield call(queryTerminalList, payload);
+      if (response.success) {
+        message.success('加载终端列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *createTerminalItem({ payload, state }, { call, put }) {
+      const response = yield call(createTerminalItem, payload);
+      if (response.success) {
+        message.success('创建终端成功');
+        yield put(routerRedux.push({
+          state,
+          pathname: '/terminal/user/list',
+        }));
+      }
+    },
+    *deleteTerminalItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteTerminalItem, payload);
+      if (response.success) {
+        message.success('禁用终端成功');
+        yield put({
+          type: 'fetchTerminalList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *recoverTerminalItem({ payload, states }, { call, put }) {
+      const response = yield call(updateTerminalItem, payload);
+      if (response.success) {
+        message.success('解禁终端成功');
+        yield put({
+          type: 'fetchTerminalList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *updateTerminalItem({ payload, states }, { call, put }) {
+      const response = yield call(updateTerminalItem, payload);
+      if (response.success) {
+        message.success('修改终端成功');
+        yield put(routerRedux.push({
+          pathname: '/terminal/user/list',
+          state: states,
+        }));
+      }
+    },
+    *fetchSpecialTerminalList({ payload }, { call, put }) {
+      const response = yield call(querySpecialTerminalList, payload);
+      if (response.success) {
+        message.success('加载白名单列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data.list || [],
+            pageSize: response.data.pageSize,
+            totalSize: response.data.totalSize,
+            pageNo: response.data.pageNo,
+          },
+        });
+      }
+    },
+    *fetchSpecialTerminalItem({ payload }, { call, put }) {
+      const response = yield call(querySpecialTerminalItem, payload);
+      if (response.success) {
+        message.success('加载白名单用户详情成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            currentItem: response.data,
+          },
+        });
+      }
+    },
+    *createSpecialTerminalItem({ payload, states }, { call, put }) {
+      const response = yield call(createSpecialTerminalItem, payload);
+      if (response.success) {
+        message.success('创建白名单用户成功');
+        yield put(routerRedux.push({
+          state: states,
+          pathname: '/terminal/whitelist/list',
+        }));
+      }
+    },
+    *deleteSpecialTerminalItem({ payload, states }, { call, put }) {
+      const response = yield call(deleteSpecialTerminalItem, payload);
+      if (response.success) {
+        message.success('删除白名单用户成功');
+        yield put({
+          type: 'fetchSpecialTerminalList',
+          payload: states.Queryers,
+        });
+      }
+    },
+    *updateSpecialTerminalItem({ payload, states }, { call, put }) {
+      const response = yield call(updateSpecialTerminalItem, payload);
+      if (response.success) {
+        message.success('修改白名单用户成功');
+        yield put(routerRedux.push({
+          pathname: '/terminal/whitelist/list',
+          state: states,
+        }));
+      }
+    },
+    *deviceUnbound({ payload, states }, { call, put }) {
+      const response = yield call(deviceUnbound, payload);
+      if (response.success) {
+        message.success('账号已成功解绑');
+        yield put({
+          type: 'fetchSpecialTerminalList',
+          payload: states.Queryers,
+        });
+      }
+    }
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixCurrentItem(state, action) {
+      const { currentItem } = state;
+      return {
+        ...state,
+        currentItem: {
+          ...currentItem,
+          ...action.payload,
+        },
+      };
+    },
+    cleanState(state) {
+      return {
+        ...state,
+        currentItem: {},
+        list: [],
+      };
+    },
+  },
+};

+ 78 - 0
src/models/trade.js

@@ -0,0 +1,78 @@
+import { message } from 'antd';
+import { routerRedux } from 'dva/router';
+import {
+  queryTerminalBuyMsg,
+  queryOrderList,
+  queryOrderItem,
+  querySubOrderItem,
+  createOrderItem,
+  invalidOrderItem,
+  payOrderItem,
+  sendOrderItem,
+  receiveOrderItem,
+} from '../services/trade';
+
+export default {
+  namespace: 'trade',
+
+  state: {
+    list: [],
+    pageNo: 1,
+    pageSize: 15,
+    totalSize: 0,
+    currentItem: {},
+  },
+
+  effects: {
+    *fetchTerminalBuyMsg({ payload }, { call, put }) {
+      const response = yield call(queryTerminalBuyMsg, payload);
+      if (response.success) {
+        message.success('加载终端购物车列表成功');
+        yield put({
+          type: 'querySuccess',
+          payload: {
+            list: response.data || [],
+          },
+        });
+      }
+    },
+    *createOrderItem({ payload }, { call }) {
+      const response = yield call(createOrderItem, payload);
+      if (response.success) {
+        message.success('创建订单成功');
+      }
+    },
+  },
+
+  reducers: {
+    querySuccess(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixList(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+    fixCurrentItem(state, action) {
+      const { currentItem } = state;
+      return {
+        ...state,
+        currentItem: {
+          ...currentItem,
+          ...action.payload,
+        },
+      };
+    },
+    cleanState(state) {
+      return {
+        ...state,
+        currentItem: {},
+        list: [],
+      };
+    },
+  },
+};

+ 165 - 137
src/routes/Campus/CampusCreate.js

@@ -1,19 +1,21 @@
+/* eslint-disable no-trailing-spaces */
 import React, { PureComponent } from 'react';
 import pathToRegexp from 'path-to-regexp';
-import { Card, Row, Col, Form, Input, Button, Popover, Icon } from 'antd';
+import { message, Card, Modal, List, Form, Input, Button, Popover, Icon } from 'antd';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
+import Selector from '../../components/RBTableSelector/Selector';
 import RBCityCascader from '../../components/RBCityCascader';
 import FooterToolbar from '../../components/FooterToolbar';
-import { RBSingleSelectTable } from '../../components/RBSelectTable';
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
 import {
-  renderStatus,
-  renderCategory,
   provinceCodeToName,
   provinceNameToCode,
 } from '../../utils/utils';
 import styles from './CampusCreate.less';
 
+const Message = message;
+
 const fieldLabels = {
   merchant: '所属商户',
   cityName: '所在城市',
@@ -25,19 +27,30 @@ const fieldLabels = {
   bankAccount: '银行账户',
 };
 
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 7 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 11 },
+  },
+};
+
 @Form.create()
 @connect(({ loading, merchant, campus }) => ({
   campus,
   merchant,
+  submitting: loading.models.campus,
   loading: loading.models.merchant,
 }))
 export default class CampusCreatePage extends PureComponent {
+  state = {
+    merchantSelectorDestroy: true,
+  };
   componentDidMount() {
-    // 加载商户列表
-    this.props.dispatch({
-      type: 'merchant/fetchMerchantList',
-      payload: {},
-    });
     // 如果是编辑校区,加载校区详情
     const matchId = this.isEdit();
     if (matchId) {
@@ -49,7 +62,7 @@ export default class CampusCreatePage extends PureComponent {
   }
   componentWillUnmount() {
     this.props.dispatch({
-      type: 'campus/cleanItemState',
+      type: 'campus/cleanState',
       payload: {},
     });
   }
@@ -61,7 +74,37 @@ export default class CampusCreatePage extends PureComponent {
     }
     return false;
   }
-  handleMerchantTableChange = (params) => {
+  handleMerchantSelectorModalShow = () => {
+    this.setState({
+      merchantSelectorDestroy: false,
+    });
+    this.props.dispatch({
+      type: 'merchant/fetchMerchantList',
+      payload: {},
+    });
+  }
+  handleMerchantSelectorFinish = (rows) => {
+    this.setState({
+      merchantSelectorDestroy: true,
+    });
+    if (!rows || !rows.length) {
+      return;
+    }
+    const { id, name } = rows[0];
+    this.props.dispatch({
+      type: 'campus/fixCurrentItem',
+      payload: {
+        merchantId: id,
+        merchantName: name,
+      },
+    });
+  };
+  handleMerchantSelectorCancel = () => {
+    this.setState({
+      merchantSelectorDestroy: true,
+    });
+  };
+  handleMerchantSelectorChange = (params) => {
     this.props.dispatch({
       type: 'merchant/fetchMerchantList',
       payload: params,
@@ -70,10 +113,19 @@ export default class CampusCreatePage extends PureComponent {
   handlePageSubmit = () => {
     this.props.form.validateFieldsAndScroll((error, values) => {
       if (!error) {
+        const { campus } = this.props;
+        const { currentItem } = campus;
+        const { merchantId } = currentItem;
+        if (!merchantId) {
+          Message.error('请选择该校区所属厂商');
+          return;
+        }
+
         const { cityName, ...restProps } = values;
         const [province, city] = cityName;
         restProps.provinceCode = provinceNameToCode(province);
         restProps.cityName = city;
+        restProps.merchantId = merchantId;
         const matchId = this.isEdit();
         if (matchId) {
           restProps.id = matchId;
@@ -100,36 +152,11 @@ export default class CampusCreatePage extends PureComponent {
   }
 
   render() {
-    const { form, merchant, loading, campus } = this.props;
+    const { merchantSelectorDestroy } = this.state;
+    const { form, merchant, mLoading, campus, submitting } = this.props;
     const { getFieldDecorator, getFieldsError } = form;
-    const { list, totalSize, pageNo, pageSize } = merchant;
     const { currentItem } = campus;
-
-    const pagination = { totalSize, pageSize, pageNo };
-    const cardProps = {
-      bordered: false,
-      className: styles.cardItem,
-    };
-
-    const merchantColumns = [{
-      title: '商户编号',
-      dataIndex: 'code',
-      key: 1,
-    }, {
-      title: '商户名称',
-      dataIndex: 'name',
-      key: 2,
-    }, {
-      title: '商户类型',
-      key: 3,
-      dataIndex: 'domain',
-      render: text => renderCategory(text),
-    }, {
-      title: '该条状态',
-      key: 4,
-      dataIndex: 'status',
-      render: text => renderStatus(text),
-    }];
+    const { merchantName } = currentItem;
 
     const renderCityName = () => {
       const { provinceCode, cityName } = currentItem;
@@ -183,119 +210,120 @@ export default class CampusCreatePage extends PureComponent {
       );
     };
 
+    const getMerchantModal = () => {
+      return (
+        <Modal
+          visible
+          width={1100}
+          footer={null}
+          title="厂商列表"
+          maskClosable={false}
+          onCancel={this.handleMerchantSelectorCancel}
+        >
+          <Selector
+            multiple={false}
+            loading={mLoading}
+            selectorName="Merchant"
+            list={merchant.list}
+            pageNo={merchant.pageNo}
+            pageSize={merchant.pageSize}
+            totalSize={merchant.totalSize}
+            onCancel={this.handleMerchantSelectorCancel}
+            onChange={this.handleMerchantSelectorChange}
+            onFinish={this.handleMerchantSelectorFinish}
+          />
+        </Modal>
+      );
+    };
+
     return (
-      <div>
-        <Card title={fieldLabels.merchant} {...cardProps} style={{ marginBottom: 16 }}>
+      <PageHeaderLayout>
+        <Card style={{ marginBottom: 70 }}>
           <Form>
-            <Form.Item className={styles.firstFormItem}>
-              {getFieldDecorator('merchantId', {
-                rules: [{ required: true, message: '请选择商户!' }],
-                initialValue: currentItem.merchantId,
+            <Form.Item
+              {...formItemLayout}
+              label={!this.isEdit() ? <a onClick={this.handleMerchantSelectorModalShow}>所属厂商</a> : '所属厂商'}
+            >
+              <List
+                bordered
+                size="small"
+                dataSource={[
+                  `${merchantName || '请选择'}`,
+                ]}
+                renderItem={item => <List.Item>{item}</List.Item>}
+              />
+            </Form.Item>
+            <Form.Item label={fieldLabels.cityName} {...formItemLayout}>
+              {getFieldDecorator('cityName', {
+                rules: [{ required: true, message: '请选择校区地址!' }],
+                initialValue: renderCityName(),
               })(
-                <RBSingleSelectTable
-                  currentName={currentItem.merchantName}
-                  columns={merchantColumns}
-                  loading={loading}
-                  dataSource={list}
-                  pagination={pagination}
-                  onFilterClick={this.handleMerchantTableChange}
-                />
+                <RBCityCascader />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.zoneName} {...formItemLayout}>
+              {getFieldDecorator('zoneName', {
+                rules: [{ required: true, message: '校区名不能为空!' }],
+                initialValue: currentItem.zoneName,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.contactName} {...formItemLayout}>
+              {getFieldDecorator('contactName', {
+                rules: [{ required: true, message: '联系人不能为空!' }],
+                initialValue: currentItem.contactName,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.mobile} {...formItemLayout}>
+              {getFieldDecorator('mobile', {
+                rules: [
+                  {
+                    required: true, message: '联系电话不能为空!',
+                  }, {
+                    pattern: /^[1][34578][0-9]{9}$/g, message: '请输入11位有效手机号!',
+                  }],
+                initialValue: currentItem.mobile,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.depositBank} {...formItemLayout}>
+              {getFieldDecorator('depositBank', {
+                initialValue: currentItem.depositBank,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.bankAccount} {...formItemLayout}>
+              {getFieldDecorator('bankAccount', {
+                initialValue: currentItem.bankAccount,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.address} {...formItemLayout}>
+              {getFieldDecorator('address', {
+                initialValue: currentItem.address,
+              })(
+                <Input placeholder="请输入" />
               )}
             </Form.Item>
           </Form>
-        </Card>
-        <Card title="基本信息" {...cardProps} style={{ marginBottom: 70 }}>
-          <Form>
-            <Row gutter={16}>
-              <Col lg={8} md={12} sm={24}>
-                <Form.Item label={fieldLabels.cityName}>
-                  {getFieldDecorator('cityName', {
-                    rules: [{ required: true, message: '请选择校区地址!' }],
-                    initialValue: renderCityName(),
-                  })(
-                    <RBCityCascader />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col lg={8} md={12} sm={24}>
-                <Form.Item label={fieldLabels.zoneName}>
-                  {getFieldDecorator('zoneName', {
-                    rules: [{ required: true, message: '校区名不能为空!' }],
-                    initialValue: currentItem.zoneName,
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col lg={8} md={12} sm={24}>
-                <Form.Item label={fieldLabels.contactName}>
-                  {getFieldDecorator('contactName', {
-                    rules: [{ required: true, message: '联系人不能为空!' }],
-                    initialValue: currentItem.contactName,
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={16}>
-              <Col lg={8} md={12} sm={24}>
-                <Form.Item label={fieldLabels.mobile}>
-                  {getFieldDecorator('mobile', {
-                    rules: [
-                      {
-                        required: true, message: '联系电话不能为空!',
-                      }, {
-                        pattern: /^[1][3,4,5,7,8][0-9]{9}$/g, message: '请输入11位有效手机号!',
-                      }],
-                    initialValue: currentItem.mobile,
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col lg={8} md={12} sm={24}>
-                <Form.Item label={fieldLabels.depositBank}>
-                  {getFieldDecorator('depositBank', {
-                    initialValue: currentItem.depositBank,
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col lg={8} md={12} sm={24}>
-                <Form.Item label={fieldLabels.bankAccount}>
-                  {getFieldDecorator('bankAccount', {
-                    initialValue: currentItem.bankAccount,
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={16}>
-              <Col lg={8} md={12} sm={24}>
-                <Form.Item label={fieldLabels.address}>
-                  {getFieldDecorator('address', {
-                    initialValue: currentItem.address,
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-            </Row>
-          </Form>
+          {!merchantSelectorDestroy && getMerchantModal()}
         </Card>
         <FooterToolbar style={{ width: '100%' }}>
           {getErrorInfo()}
           <Button onClick={this.handlePageBack} style={{ marginRight: 10 }}>
             取消
           </Button>
-          <Button type="primary" onClick={this.handlePageSubmit}>
+          <Button type="primary" onClick={this.handlePageSubmit} loading={submitting}>
             提交
           </Button>
         </FooterToolbar>
-      </div>
+      </PageHeaderLayout>
     );
   }
 }

+ 1 - 5
src/routes/Campus/CampusCreate.less

@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import "../../../node_modules/antd/lib/style/themes/default.less";
 
 .cardItem {
   :global {
@@ -8,10 +8,6 @@
   }
 }
 
-.form {
-  margin-bottom: 0;
-}
-
 .errorIcon {
   cursor: pointer;
   color: @error-color;

+ 54 - 51
src/routes/Campus/CampusList.js

@@ -5,6 +5,7 @@ import { routerRedux } from 'dva/router';
 import { Card, Modal, Form, Button, message } from 'antd';
 import { StandardTableList } from '../../components/RBList';
 import RBRemoteSelect from '../../components/RBRemoteSelect';
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
 import { addRowKey } from '../../utils/utils';
 import styles from './CampusList.less';
 
@@ -44,7 +45,7 @@ export default class CampusListPage extends Component {
     this.state = {
       UIParams: (state || {}).UIParams, // 组件的状态参数
       Queryers: (state || {}).Queryers, // 查询的条件参数
-      filterModalDestory: true,
+      filterModalDestroy: true,
     };
   }
   componentDidMount() {
@@ -85,16 +86,16 @@ export default class CampusListPage extends Component {
         merchantId: value[0],
       },
     });
-    this.handleFilterModalDestory();
+    this.handleFilterModalDestroy();
   }
   handleBatchOperation = () => {
     Message.info('暂不支持批量操作!');
   }
   handleFilterModalShow = () => {
-    this.setState({ filterModalDestory: false });
+    this.setState({ filterModalDestroy: false });
   }
-  handleFilterModalDestory = () => {
-    this.setState({ filterModalDestory: true });
+  handleFilterModalDestroy = () => {
+    this.setState({ filterModalDestroy: true });
   }
   handleRemoteSelectSearch = (value) => {
     this.props.dispatch({
@@ -199,52 +200,54 @@ export default class CampusListPage extends Component {
       width: '6%',
     }];
     return (
-      <Card>
-        <StandardTableList
-          columns={columns}
-          loading={loading}
-          dataSource={addRowKey(list)}
-          header={{
-            basicSearch,
-            onAdvanceFilterClick: this.handleFilterModalShow,
-            onFilterClick: this.handleFilterOperation,
-            onCreateClick: this.handleCreateOperation,
-          }}
-          footer={{
-            pagination,
-            batchActions,
-            onBatchClick: this.handleBatchOperation,
-          }}
-          keepUIState={{ ...this.state.UIParams }}
-          showStatusSelect={false}
-        />
-        {!this.state.filterModalDestory && (
-        <Modal
-          width={600}
-          visible
-          title="高级筛选"
-          okText="筛选"
-          cancelText="取消"
-          maskClosable={false}
-          onCancel={this.handleFilterModalDestory}
-          onOk={this.handleModalFilterOperation}
-        >
-          <Form>
-            <Form.Item label="所属商户" {...formItemLayout}>
-              {getFieldDecorator('merchantId', {
-                  initialValue: [],
-                })(
-                  <RBRemoteSelect
-                    fetching={fetching}
-                    dataSource={merchantDataFormatter(merchant.list)}
-                    onSearch={this.handleRemoteSelectSearch}
-                  />
-                )}
-            </Form.Item>
-          </Form>
-        </Modal>
-)}
-      </Card>
+      <PageHeaderLayout>
+        <Card>
+          <StandardTableList
+            columns={columns}
+            loading={loading}
+            dataSource={addRowKey(list)}
+            header={{
+              basicSearch,
+              onAdvanceFilterClick: this.handleFilterModalShow,
+              onFilterClick: this.handleFilterOperation,
+              onCreateClick: this.handleCreateOperation,
+            }}
+            footer={{
+              pagination,
+              batchActions,
+              onBatchClick: this.handleBatchOperation,
+            }}
+            keepUIState={{ ...this.state.UIParams }}
+            showStatusSelect={false}
+          />
+          {!this.state.filterModalDestroy && (
+            <Modal
+              width={600}
+              visible
+              title="高级筛选"
+              okText="筛选"
+              cancelText="取消"
+              maskClosable={false}
+              onCancel={this.handleFilterModalDestroy}
+              onOk={this.handleModalFilterOperation}
+            >
+              <Form>
+                <Form.Item label="所属商户" {...formItemLayout}>
+                  {getFieldDecorator('merchantId', {
+                      initialValue: [],
+                    })(
+                      <RBRemoteSelect
+                        fetching={fetching}
+                        dataSource={merchantDataFormatter(merchant.list)}
+                        onSearch={this.handleRemoteSelectSearch}
+                      />
+                    )}
+                </Form.Item>
+              </Form>
+            </Modal>
+          )}
+        </Card>
+      </PageHeaderLayout>
     );
   }
 }

+ 1 - 1
src/routes/Campus/CampusList.less

@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import "../../../node_modules/antd/lib/style/themes/default.less";
 
 .link {
   font-weight: 500;

+ 0 - 7
src/routes/Exception/style.less

@@ -1,7 +0,0 @@
-.trigger {
-  background: "red";
-  :global(.ant-btn) {
-    margin-right: 8px;
-    margin-bottom: 12px;
-  }
-}

+ 0 - 65
src/routes/Exception/triggerException.js

@@ -1,65 +0,0 @@
-import React, { PureComponent } from 'react';
-import { Button, Spin, Card } from 'antd';
-import { connect } from 'dva';
-import styles from './style.less';
-
-@connect(state => ({
-  isloading: state.error.isloading,
-}))
-export default class TriggerException extends PureComponent {
-  state={
-    isloading: false,
-  }
-  trigger401 = () => {
-    this.setState({
-      isloading: true,
-    });
-    this.props.dispatch({
-      type: 'error/query401',
-    });
-  };
-  trigger403 = () => {
-    this.setState({
-      isloading: true,
-    });
-    this.props.dispatch({
-      type: 'error/query403',
-    });
-  };
-  trigger500 = () => {
-    this.setState({
-      isloading: true,
-    });
-    this.props.dispatch({
-      type: 'error/query500',
-    });
-  };
-  trigger404 = () => {
-    this.setState({
-      isloading: true,
-    });
-    this.props.dispatch({
-      type: 'error/query404',
-    });
-  };
-  render() {
-    return (
-      <Card>
-        <Spin spinning={this.state.isloading} wrapperClassName={styles.trigger}>
-          <Button type="danger" onClick={this.trigger401}>
-            触发401
-          </Button>
-          <Button type="danger" onClick={this.trigger403}>
-            触发403
-          </Button>
-          <Button type="danger" onClick={this.trigger500}>
-            触发500
-          </Button>
-          <Button type="danger" onClick={this.trigger404}>
-            触发404
-          </Button>
-        </Spin>
-      </Card>
-    );
-  }
-}

+ 0 - 290
src/routes/Forms/AdvancedForm.js

@@ -1,290 +0,0 @@
-import React, { PureComponent } from 'react';
-import { Card, Button, Form, Icon, Col, Row, DatePicker, TimePicker, Input, Select, Popover } from 'antd';
-import { connect } from 'dva';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import FooterToolbar from '../../components/FooterToolbar';
-import TableForm from './TableForm';
-import styles from './style.less';
-
-const { Option } = Select;
-const { RangePicker } = DatePicker;
-
-const fieldLabels = {
-  name: '仓库名',
-  url: '仓库域名',
-  owner: '仓库管理员',
-  approver: '审批人',
-  dateRange: '生效日期',
-  type: '仓库类型',
-  name2: '任务名',
-  url2: '任务描述',
-  owner2: '执行人',
-  approver2: '责任人',
-  dateRange2: '生效日期',
-  type2: '任务类型',
-};
-
-const tableData = [{
-  key: '1',
-  workId: '00001',
-  name: 'John Brown',
-  department: 'New York No. 1 Lake Park',
-}, {
-  key: '2',
-  workId: '00002',
-  name: 'Jim Green',
-  department: 'London No. 1 Lake Park',
-}, {
-  key: '3',
-  workId: '00003',
-  name: 'Joe Black',
-  department: 'Sidney No. 1 Lake Park',
-}];
-
-class AdvancedForm extends PureComponent {
-  state = {
-    width: '100%',
-  };
-  componentDidMount() {
-    window.addEventListener('resize', this.resizeFooterToolbar);
-  }
-  componentWillUnmount() {
-    window.removeEventListener('resize', this.resizeFooterToolbar);
-  }
-  resizeFooterToolbar = () => {
-    const sider = document.querySelectorAll('.ant-layout-sider')[0];
-    const width = `calc(100% - ${sider.style.width})`;
-    if (this.state.width !== width) {
-      this.setState({ width });
-    }
-  }
-  render() {
-    const { form, dispatch, submitting } = this.props;
-    const { getFieldDecorator, validateFieldsAndScroll, getFieldsError } = form;
-    const validate = () => {
-      validateFieldsAndScroll((error, values) => {
-        if (!error) {
-          // submit the values
-          dispatch({
-            type: 'form/submitAdvancedForm',
-            payload: values,
-          });
-        }
-      });
-    };
-    const errors = getFieldsError();
-    const getErrorInfo = () => {
-      const errorCount = Object.keys(errors).filter(key => errors[key]).length;
-      if (!errors || errorCount === 0) {
-        return null;
-      }
-      const scrollToField = (fieldKey) => {
-        const labelNode = document.querySelector(`label[for="${fieldKey}"]`);
-        if (labelNode) {
-          labelNode.scrollIntoView(true);
-        }
-      };
-      const errorList = Object.keys(errors).map((key) => {
-        if (!errors[key]) {
-          return null;
-        }
-        return (
-          <li key={key} className={styles.errorListItem} onClick={() => scrollToField(key)}>
-            <Icon type="cross-circle-o" className={styles.errorIcon} />
-            <div className={styles.errorMessage}>{errors[key][0]}</div>
-            <div className={styles.errorField}>{fieldLabels[key]}</div>
-          </li>
-        );
-      });
-      return (
-        <span className={styles.errorIcon}>
-          <Popover
-            title="表单校验信息"
-            content={errorList}
-            overlayClassName={styles.errorPopover}
-            trigger="click"
-            getPopupContainer={trigger => trigger.parentNode}
-          >
-            <Icon type="exclamation-circle" />
-          </Popover>
-          {errorCount}
-        </span>
-      );
-    };
-    return (
-      <PageHeaderLayout
-        title="高级表单"
-        content="高级表单常见于一次性输入和提交大批量数据的场景。"
-        wrapperClassName={styles.advancedForm}
-      >
-        <Card title="仓库管理" className={styles.card} bordered={false}>
-          <Form layout="vertical" hideRequiredMark>
-            <Row gutter={16}>
-              <Col lg={6} md={12} sm={24}>
-                <Form.Item label={fieldLabels.name}>
-                  {getFieldDecorator('name', {
-                    rules: [{ required: true, message: '请输入仓库名称' }],
-                  })(
-                    <Input placeholder="请输入仓库名称" />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
-                <Form.Item label={fieldLabels.url}>
-                  {getFieldDecorator('url', {
-                    rules: [{ required: true, message: '请选择' }],
-                  })(
-                    <Input
-                      style={{ width: '100%' }}
-                      addonBefore="http://"
-                      addonAfter=".com"
-                      placeholder="请输入"
-                    />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
-                <Form.Item label={fieldLabels.owner}>
-                  {getFieldDecorator('owner', {
-                    rules: [{ required: true, message: '请选择管理员' }],
-                  })(
-                    <Select placeholder="请选择管理员">
-                      <Option value="xiao">付晓晓</Option>
-                      <Option value="mao">周毛毛</Option>
-                    </Select>
-                  )}
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={16}>
-              <Col lg={6} md={12} sm={24}>
-                <Form.Item label={fieldLabels.approver}>
-                  {getFieldDecorator('approver', {
-                    rules: [{ required: true, message: '请选择审批员' }],
-                  })(
-                    <Select placeholder="请选择审批员">
-                      <Option value="xiao">付晓晓</Option>
-                      <Option value="mao">周毛毛</Option>
-                    </Select>
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
-                <Form.Item label={fieldLabels.dateRange}>
-                  {getFieldDecorator('dateRange', {
-                    rules: [{ required: true, message: '请选择生效日期' }],
-                  })(
-                    <RangePicker placeholder={['开始日期', '结束日期']} style={{ width: '100%' }} />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
-                <Form.Item label={fieldLabels.type}>
-                  {getFieldDecorator('type', {
-                    rules: [{ required: true, message: '请选择仓库类型' }],
-                  })(
-                    <Select placeholder="请选择仓库类型">
-                      <Option value="private">私密</Option>
-                      <Option value="public">公开</Option>
-                    </Select>
-                  )}
-                </Form.Item>
-              </Col>
-            </Row>
-          </Form>
-        </Card>
-        <Card title="任务管理" className={styles.card} bordered={false}>
-          <Form layout="vertical" hideRequiredMark>
-            <Row gutter={16}>
-              <Col lg={6} md={12} sm={24}>
-                <Form.Item label={fieldLabels.name2}>
-                  {getFieldDecorator('name2', {
-                    rules: [{ required: true, message: '请输入' }],
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
-                <Form.Item label={fieldLabels.url2}>
-                  {getFieldDecorator('url2', {
-                    rules: [{ required: true, message: '请选择' }],
-                  })(
-                    <Input placeholder="请输入" />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
-                <Form.Item label={fieldLabels.owner2}>
-                  {getFieldDecorator('owner2', {
-                    rules: [{ required: true, message: '请选择管理员' }],
-                  })(
-                    <Select placeholder="请选择管理员">
-                      <Option value="xiao">付晓晓</Option>
-                      <Option value="mao">周毛毛</Option>
-                    </Select>
-                  )}
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={16}>
-              <Col lg={6} md={12} sm={24}>
-                <Form.Item label={fieldLabels.approver2}>
-                  {getFieldDecorator('approver2', {
-                    rules: [{ required: true, message: '请选择审批员' }],
-                  })(
-                    <Select placeholder="请选择审批员">
-                      <Option value="xiao">付晓晓</Option>
-                      <Option value="mao">周毛毛</Option>
-                    </Select>
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
-                <Form.Item label={fieldLabels.dateRange2}>
-                  {getFieldDecorator('dateRange2', {
-                    rules: [{ required: true, message: '请输入' }],
-                  })(
-                    <TimePicker
-                      placeholder="提醒时间"
-                      style={{ width: '100%' }}
-                      getPopupContainer={trigger => trigger.parentNode}
-                    />
-                  )}
-                </Form.Item>
-              </Col>
-              <Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
-                <Form.Item label={fieldLabels.type2}>
-                  {getFieldDecorator('type2', {
-                    rules: [{ required: true, message: '请选择仓库类型' }],
-                  })(
-                    <Select placeholder="请选择仓库类型">
-                      <Option value="private">私密</Option>
-                      <Option value="public">公开</Option>
-                    </Select>
-                  )}
-                </Form.Item>
-              </Col>
-            </Row>
-          </Form>
-        </Card>
-        <Card title="成员管理" bordered={false}>
-          {getFieldDecorator('members', {
-            initialValue: tableData,
-          })(<TableForm />)}
-        </Card>
-        <FooterToolbar style={{ width: this.state.width }}>
-          {getErrorInfo()}
-          <Button type="primary" onClick={validate} loading={submitting}>
-            提交
-          </Button>
-        </FooterToolbar>
-      </PageHeaderLayout>
-    );
-  }
-}
-
-export default connect(({ global, loading }) => ({
-  collapsed: global.collapsed,
-  submitting: loading.effects['form/submitAdvancedForm'],
-}))(Form.create()(AdvancedForm));

+ 0 - 188
src/routes/Forms/BasicForm.js

@@ -1,188 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'dva';
-import {
-  Form, Input, DatePicker, Select, Button, Card, InputNumber, Radio, Icon, Tooltip,
-} from 'antd';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import styles from './style.less';
-
-const FormItem = Form.Item;
-const { Option } = Select;
-const { RangePicker } = DatePicker;
-const { TextArea } = Input;
-
-@connect(({ loading }) => ({
-  submitting: loading.effects['form/submitRegularForm'],
-}))
-@Form.create()
-export default class BasicForms extends PureComponent {
-  handleSubmit = (e) => {
-    e.preventDefault();
-    this.props.form.validateFieldsAndScroll((err, values) => {
-      if (!err) {
-        this.props.dispatch({
-          type: 'form/submitRegularForm',
-          payload: values,
-        });
-      }
-    });
-  }
-  render() {
-    const { submitting } = this.props;
-    const { getFieldDecorator, getFieldValue } = this.props.form;
-
-    const formItemLayout = {
-      labelCol: {
-        xs: { span: 24 },
-        sm: { span: 7 },
-      },
-      wrapperCol: {
-        xs: { span: 24 },
-        sm: { span: 12 },
-        md: { span: 10 },
-      },
-    };
-
-    const submitFormLayout = {
-      wrapperCol: {
-        xs: { span: 24, offset: 0 },
-        sm: { span: 10, offset: 7 },
-      },
-    };
-
-    return (
-      <PageHeaderLayout title="基础表单" content="表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。">
-        <Card bordered={false}>
-          <Form
-            onSubmit={this.handleSubmit}
-            hideRequiredMark
-            style={{ marginTop: 8 }}
-          >
-            <FormItem
-              {...formItemLayout}
-              label="标题"
-            >
-              {getFieldDecorator('title', {
-                rules: [{
-                  required: true, message: '请输入标题',
-                }],
-              })(
-                <Input placeholder="给目标起个名字" />
-              )}
-            </FormItem>
-            <FormItem
-              {...formItemLayout}
-              label="起止日期"
-            >
-              {getFieldDecorator('date', {
-                rules: [{
-                  required: true, message: '请选择起止日期',
-                }],
-              })(
-                <RangePicker style={{ width: '100%' }} placeholder={['开始日期', '结束日期']} />
-              )}
-            </FormItem>
-            <FormItem
-              {...formItemLayout}
-              label="目标描述"
-            >
-              {getFieldDecorator('goal', {
-                rules: [{
-                  required: true, message: '请输入目标描述',
-                }],
-              })(
-                <TextArea style={{ minHeight: 32 }} placeholder="请输入你的阶段性工作目标" rows={4} />
-              )}
-            </FormItem>
-            <FormItem
-              {...formItemLayout}
-              label="衡量标准"
-            >
-              {getFieldDecorator('standard', {
-                rules: [{
-                  required: true, message: '请输入衡量标准',
-                }],
-              })(
-                <TextArea style={{ minHeight: 32 }} placeholder="请输入衡量标准" rows={4} />
-              )}
-            </FormItem>
-            <FormItem
-              {...formItemLayout}
-              label={
-                <span>
-                  客户
-                  <em className={styles.optional}>
-                    (选填)
-                    <Tooltip title="目标的服务对象">
-                      <Icon type="info-circle-o" style={{ marginRight: 4 }} />
-                    </Tooltip>
-                  </em>
-                </span>
-              }
-            >
-              {getFieldDecorator('client')(
-                <Input placeholder="请描述你服务的客户,内部客户直接 @姓名/工号" />
-              )}
-            </FormItem>
-            <FormItem
-              {...formItemLayout}
-              label={<span>邀评人<em className={styles.optional}>(选填)</em></span>}
-            >
-              {getFieldDecorator('invites')(
-                <Input placeholder="请直接 @姓名/工号,最多可邀请 5 人" />
-              )}
-            </FormItem>
-            <FormItem
-              {...formItemLayout}
-              label={<span>权重<em className={styles.optional}>(选填)</em></span>}
-            >
-              {getFieldDecorator('weight')(
-                <InputNumber placeholder="请输入" min={0} max={100} />
-              )}
-              <span>%</span>
-            </FormItem>
-            <FormItem
-              {...formItemLayout}
-              label="目标公开"
-              help="客户、邀评人默认被分享"
-            >
-              <div>
-                {getFieldDecorator('public', {
-                  initialValue: '1',
-                })(
-                  <Radio.Group>
-                    <Radio value="1">公开</Radio>
-                    <Radio value="2">部分公开</Radio>
-                    <Radio value="3">不公开</Radio>
-                  </Radio.Group>
-                )}
-                <FormItem style={{ marginBottom: 0 }}>
-                  {getFieldDecorator('publicUsers')(
-                    <Select
-                      mode="multiple"
-                      placeholder="公开给"
-                      style={{
-                        margin: '8px 0',
-                        display: getFieldValue('public') === '2' ? 'block' : 'none',
-                      }}
-                    >
-                      <Option value="1">同事甲</Option>
-                      <Option value="2">同事乙</Option>
-                      <Option value="3">同事丙</Option>
-                    </Select>
-                  )}
-                </FormItem>
-              </div>
-            </FormItem>
-            <FormItem {...submitFormLayout} style={{ marginTop: 32 }}>
-              <Button type="primary" htmlType="submit" loading={submitting}>
-                提交
-              </Button>
-              <Button style={{ marginLeft: 8 }}>保存</Button>
-            </FormItem>
-          </Form>
-        </Card>
-      </PageHeaderLayout>
-    );
-  }
-}

+ 0 - 122
src/routes/Forms/StepForm/Step1.js

@@ -1,122 +0,0 @@
-import React, { Fragment } from 'react';
-import { connect } from 'dva';
-import { Form, Input, Button, Select, Divider } from 'antd';
-import { routerRedux } from 'dva/router';
-import styles from './style.less';
-
-const { Option } = Select;
-
-const formItemLayout = {
-  labelCol: {
-    span: 5,
-  },
-  wrapperCol: {
-    span: 19,
-  },
-};
-
-@Form.create()
-class Step1 extends React.PureComponent {
-  render() {
-    const { form, dispatch, data } = this.props;
-    const { getFieldDecorator, validateFields } = form;
-    const onValidateForm = () => {
-      validateFields((err, values) => {
-        if (!err) {
-          dispatch({
-            type: 'form/saveStepFormData',
-            payload: values,
-          });
-          dispatch(routerRedux.push('/form/step-form/confirm'));
-        }
-      });
-    };
-    return (
-      <Fragment>
-        <Form layout="horizontal" className={styles.stepForm} hideRequiredMark>
-          <Form.Item
-            {...formItemLayout}
-            label="付款账户"
-          >
-            {getFieldDecorator('payAccount', {
-              initialValue: data.payAccount,
-              rules: [{ required: true, message: '请选择付款账户' }],
-            })(
-              <Select placeholder="test@example.com">
-                <Option value="ant-design@alipay.com">ant-design@alipay.com</Option>
-              </Select>
-            )}
-          </Form.Item>
-          <Form.Item
-            {...formItemLayout}
-            label="收款账户"
-          >
-            <Input.Group compact>
-              <Select defaultValue="alipay" style={{ width: 100 }}>
-                <Option value="alipay">支付宝</Option>
-                <Option value="bank">银行账户</Option>
-              </Select>
-              {getFieldDecorator('receiverAccount', {
-                initialValue: data.receiverAccount,
-                rules: [
-                  { required: true, message: '请输入收款人账户' },
-                  { type: 'email', message: '账户名应为邮箱格式' },
-                ],
-              })(
-                <Input style={{ width: 'calc(100% - 100px)' }} placeholder="test@example.com" />
-              )}
-            </Input.Group>
-          </Form.Item>
-          <Form.Item
-            {...formItemLayout}
-            label="收款人姓名"
-          >
-            {getFieldDecorator('receiverName', {
-              initialValue: data.receiverName,
-              rules: [{ required: true, message: '请输入收款人姓名' }],
-            })(
-              <Input placeholder="请输入收款人姓名" />
-            )}
-          </Form.Item>
-          <Form.Item
-            {...formItemLayout}
-            label="转账金额"
-          >
-            {getFieldDecorator('amount', {
-              initialValue: data.amount,
-              rules: [
-                { required: true, message: '请输入转账金额' },
-                { pattern: /^(\d+)((?:\.\d+)?)$/, message: '请输入合法金额数字' },
-              ],
-            })(
-              <Input prefix="¥" placeholder="请输入金额" />
-            )}
-          </Form.Item>
-          <Form.Item
-            wrapperCol={{
-              xs: { span: 24, offset: 0 },
-              sm: { span: formItemLayout.wrapperCol.span, offset: formItemLayout.labelCol.span },
-            }}
-            label=""
-          >
-            <Button type="primary" onClick={onValidateForm}>
-              下一步
-            </Button>
-          </Form.Item>
-        </Form>
-        <Divider style={{ margin: '40px 0 24px' }} />
-        <div className={styles.desc}>
-          <h3>说明</h3>
-          <h4>转账到支付宝账户</h4>
-          <p>如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。</p>
-          <h4>转账到银行卡</h4>
-          <p>如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。</p>
-        </div>
-      </Fragment>
-    );
-  }
-}
-
-export default connect(({ form }) => ({
-  data: form.step,
-}))(Step1);

+ 0 - 114
src/routes/Forms/StepForm/Step2.js

@@ -1,114 +0,0 @@
-import React from 'react';
-import { connect } from 'dva';
-import { Form, Input, Button, Alert, Divider } from 'antd';
-import { routerRedux } from 'dva/router';
-import { digitUppercase } from '../../../utils/utils';
-import styles from './style.less';
-
-const formItemLayout = {
-  labelCol: {
-    span: 5,
-  },
-  wrapperCol: {
-    span: 19,
-  },
-};
-
-@Form.create()
-class Step2 extends React.PureComponent {
-  render() {
-    const { form, data, dispatch, submitting } = this.props;
-    const { getFieldDecorator, validateFields } = form;
-    const onPrev = () => {
-      dispatch(routerRedux.push('/form/step-form'));
-    };
-    const onValidateForm = (e) => {
-      e.preventDefault();
-      validateFields((err, values) => {
-        if (!err) {
-          dispatch({
-            type: 'form/submitStepForm',
-            payload: {
-              ...data,
-              ...values,
-            },
-          });
-        }
-      });
-    };
-    return (
-      <Form layout="horizontal" className={styles.stepForm}>
-        <Alert
-          closable
-          showIcon
-          message="确认转账后,资金将直接打入对方账户,无法退回。"
-          style={{ marginBottom: 24 }}
-        />
-        <Form.Item
-          {...formItemLayout}
-          className={styles.stepFormText}
-          label="付款账户"
-        >
-          {data.payAccount}
-        </Form.Item>
-        <Form.Item
-          {...formItemLayout}
-          className={styles.stepFormText}
-          label="收款账户"
-        >
-          {data.receiverAccount}
-        </Form.Item>
-        <Form.Item
-          {...formItemLayout}
-          className={styles.stepFormText}
-          label="收款人姓名"
-        >
-          {data.receiverName}
-        </Form.Item>
-        <Form.Item
-          {...formItemLayout}
-          className={styles.stepFormText}
-          label="转账金额"
-        >
-          <span className={styles.money}>{data.amount}</span>
-          <span className={styles.uppercase}>({digitUppercase(data.amount)})</span>
-        </Form.Item>
-        <Divider style={{ margin: '24px 0' }} />
-        <Form.Item
-          {...formItemLayout}
-          label="支付密码"
-          required={false}
-        >
-          {getFieldDecorator('password', {
-            initialValue: '123456',
-            rules: [{
-              required: true, message: '需要支付密码才能进行支付',
-            }],
-          })(
-            <Input type="password" autoComplete="off" style={{ width: '80%' }} />
-          )}
-        </Form.Item>
-        <Form.Item
-          style={{ marginBottom: 8 }}
-          wrapperCol={{
-            xs: { span: 24, offset: 0 },
-            sm: { span: formItemLayout.wrapperCol.span, offset: formItemLayout.labelCol.span },
-          }}
-          label=""
-        >
-          <Button type="primary" onClick={onValidateForm} loading={submitting}>
-            提交
-          </Button>
-          <Button onClick={onPrev} style={{ marginLeft: 8 }}>
-            上一步
-          </Button>
-        </Form.Item>
-      </Form>
-    );
-  }
-}
-
-export default connect(({ form, loading }) => ({
-  submitting: loading.effects['form/submitStepForm'],
-  data: form.step,
-}))(Step2);

+ 0 - 59
src/routes/Forms/StepForm/Step3.js

@@ -1,59 +0,0 @@
-import React, { Fragment } from 'react';
-import { connect } from 'dva';
-import { Button, Row, Col } from 'antd';
-import { routerRedux } from 'dva/router';
-import Result from '../../../components/Result';
-import styles from './style.less';
-
-class Step3 extends React.PureComponent {
-  render() {
-    const { dispatch, data } = this.props;
-    const onFinish = () => {
-      dispatch(routerRedux.push('/form/step-form'));
-    };
-    const information = (
-      <div className={styles.information}>
-        <Row>
-          <Col span={8} className={styles.label}>付款账户:</Col>
-          <Col span={16}>{data.payAccount}</Col>
-        </Row>
-        <Row>
-          <Col span={8} className={styles.label}>收款账户:</Col>
-          <Col span={16}>{data.receiverAccount}</Col>
-        </Row>
-        <Row>
-          <Col span={8} className={styles.label}>收款人姓名:</Col>
-          <Col span={16}>{data.receiverName}</Col>
-        </Row>
-        <Row>
-          <Col span={8} className={styles.label}>转账金额:</Col>
-          <Col span={16}><span className={styles.money}>{data.amount}</span> 元</Col>
-        </Row>
-      </div>
-    );
-    const actions = (
-      <Fragment>
-        <Button type="primary" onClick={onFinish}>
-          再转一笔
-        </Button>
-        <Button>
-          查看账单
-        </Button>
-      </Fragment>
-    );
-    return (
-      <Result
-        type="success"
-        title="操作成功"
-        description="预计两小时内到账"
-        extra={information}
-        actions={actions}
-        className={styles.result}
-      />
-    );
-  }
-}
-
-export default connect(({ form }) => ({
-  data: form.step,
-}))(Step3);

+ 0 - 53
src/routes/Forms/StepForm/index.js

@@ -1,53 +0,0 @@
-import React, { PureComponent, Fragment } from 'react';
-import { Route, Redirect, Switch } from 'dva/router';
-import { Card, Steps } from 'antd';
-import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
-import NotFound from '../../Exception/404';
-import { getRoutes } from '../../../utils/utils';
-import styles from '../style.less';
-
-const { Step } = Steps;
-
-export default class StepForm extends PureComponent {
-  getCurrentStep() {
-    const { location } = this.props;
-    const { pathname } = location;
-    const pathList = pathname.split('/');
-    switch (pathList[pathList.length - 1]) {
-      case 'info': return 0;
-      case 'confirm': return 1;
-      case 'result': return 2;
-      default: return 0;
-    }
-  }
-  render() {
-    const { match, routerData } = this.props;
-    return (
-      <PageHeaderLayout title="分步表单" content="将一个冗长或用户不熟悉的表单任务分成多个步骤,指导用户完成。">
-        <Card bordered={false}>
-          <Fragment>
-            <Steps current={this.getCurrentStep()} className={styles.steps}>
-              <Step title="填写转账信息" />
-              <Step title="确认转账信息" />
-              <Step title="完成" />
-            </Steps>
-            <Switch>
-              {
-                getRoutes(match.path, routerData).map(item => (
-                  <Route
-                    key={item.key}
-                    path={item.path}
-                    component={item.component}
-                    exact={item.exact}
-                  />
-                ))
-              }
-              <Redirect exact from="/form/step-form" to="/form/step-form/info" />
-              <Route render={NotFound} />
-            </Switch>
-          </Fragment>
-        </Card>
-      </PageHeaderLayout>
-    );
-  }
-}

+ 0 - 75
src/routes/Forms/StepForm/style.less

@@ -1,75 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-
-.stepForm {
-  margin: 40px auto 0;
-  max-width: 500px;
-}
-
-.stepFormText {
-  margin-bottom: 24px;
-  :global {
-    .ant-form-item-label,
-    .ant-form-item-control {
-      line-height: 22px;
-    }
-  }
-}
-
-.result {
-  margin: 0 auto;
-  max-width: 560px;
-  padding: 24px 0 8px;
-}
-
-.desc {
-  padding: 0 56px;
-  color: @text-color-secondary;
-  h3 {
-    font-size: 16px;
-    margin: 0 0 12px 0;
-    color: @text-color-secondary;
-    line-height: 32px;
-  }
-  h4 {
-    margin: 0 0 4px 0;
-    color: @text-color-secondary;
-    font-size: 14px;
-    line-height: 22px;
-  }
-  p {
-    margin-top: 0;
-    margin-bottom: 12px;
-    line-height: 22px;
-  }
-}
-
-@media screen and (max-width: @screen-md) {
-  .desc {
-    padding: 0;
-  }
-}
-
-.information {
-  line-height: 22px;
-  :global {
-    .ant-row:not(:last-child) {
-      margin-bottom: 24px;
-    }
-  }
-  .label {
-    color: @heading-color;
-    text-align: right;
-    padding-right: 8px;
-  }
-}
-
-.money {
-  font-family: "Helvetica Neue", sans-serif;
-  font-weight: 500;
-  font-size: 20px;
-  line-height: 14px;
-}
-
-.uppercase {
-  font-size: 12px;
-}

+ 0 - 227
src/routes/Forms/TableForm.js

@@ -1,227 +0,0 @@
-import React, { PureComponent, Fragment } from 'react';
-import { Table, Button, Input, message, Popconfirm, Divider } from 'antd';
-import styles from './style.less';
-
-export default class TableForm extends PureComponent {
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      data: props.value,
-      loading: false,
-    };
-  }
-  componentWillReceiveProps(nextProps) {
-    if ('value' in nextProps) {
-      this.setState({
-        data: nextProps.value,
-      });
-    }
-  }
-  getRowByKey(key, newData) {
-    return (newData || this.state.data).filter(item => item.key === key)[0];
-  }
-  index = 0;
-  cacheOriginData = {};
-  toggleEditable=(e, key) => {
-    e.preventDefault();
-    const newData = this.state.data.map(item => ({ ...item }));
-    const target = this.getRowByKey(key, newData);
-    if (target) {
-      // 进入编辑状态时保存原始数据
-      if (!target.editable) {
-        this.cacheOriginData[key] = { ...target };
-      }
-      target.editable = !target.editable;
-      this.setState({ data: newData });
-    }
-  }
-  remove(key) {
-    const newData = this.state.data.filter(item => item.key !== key);
-    this.setState({ data: newData });
-    this.props.onChange(newData);
-  }
-  newMember = () => {
-    const newData = this.state.data.map(item => ({ ...item }));
-    newData.push({
-      key: `NEW_TEMP_ID_${this.index}`,
-      workId: '',
-      name: '',
-      department: '',
-      editable: true,
-      isNew: true,
-    });
-    this.index += 1;
-    this.setState({ data: newData });
-  }
-  handleKeyPress(e, key) {
-    if (e.key === 'Enter') {
-      this.saveRow(e, key);
-    }
-  }
-  handleFieldChange(e, fieldName, key) {
-    const newData = this.state.data.map(item => ({ ...item }));
-    const target = this.getRowByKey(key, newData);
-    if (target) {
-      target[fieldName] = e.target.value;
-      this.setState({ data: newData });
-    }
-  }
-  saveRow(e, key) {
-    e.persist();
-    this.setState({
-      loading: true,
-    });
-    setTimeout(() => {
-      if (this.clickedCancel) {
-        this.clickedCancel = false;
-        return;
-      }
-      const target = this.getRowByKey(key) || {};
-      if (!target.workId || !target.name || !target.department) {
-        message.error('请填写完整成员信息。');
-        e.target.focus();
-        this.setState({
-          loading: false,
-        });
-        return;
-      }
-      delete target.isNew;
-      this.toggleEditable(e, key);
-      this.props.onChange(this.state.data);
-      this.setState({
-        loading: false,
-      });
-    }, 500);
-  }
-  cancel(e, key) {
-    this.clickedCancel = true;
-    e.preventDefault();
-    const newData = this.state.data.map(item => ({ ...item }));
-    const target = this.getRowByKey(key, newData);
-    if (this.cacheOriginData[key]) {
-      Object.assign(target, this.cacheOriginData[key]);
-      target.editable = false;
-      delete this.cacheOriginData[key];
-    }
-    this.setState({ data: newData });
-    this.clickedCancel = false;
-  }
-  render() {
-    const columns = [{
-      title: '成员姓名',
-      dataIndex: 'name',
-      key: 'name',
-      width: '20%',
-      render: (text, record) => {
-        if (record.editable) {
-          return (
-            <Input
-              value={text}
-              autoFocus
-              onChange={e => this.handleFieldChange(e, 'name', record.key)}
-              onKeyPress={e => this.handleKeyPress(e, record.key)}
-              placeholder="成员姓名"
-            />
-          );
-        }
-        return text;
-      },
-    }, {
-      title: '工号',
-      dataIndex: 'workId',
-      key: 'workId',
-      width: '20%',
-      render: (text, record) => {
-        if (record.editable) {
-          return (
-            <Input
-              value={text}
-              onChange={e => this.handleFieldChange(e, 'workId', record.key)}
-              onKeyPress={e => this.handleKeyPress(e, record.key)}
-              placeholder="工号"
-            />
-          );
-        }
-        return text;
-      },
-    }, {
-      title: '所属部门',
-      dataIndex: 'department',
-      key: 'department',
-      width: '40%',
-      render: (text, record) => {
-        if (record.editable) {
-          return (
-            <Input
-              value={text}
-              onChange={e => this.handleFieldChange(e, 'department', record.key)}
-              onKeyPress={e => this.handleKeyPress(e, record.key)}
-              placeholder="所属部门"
-            />
-          );
-        }
-        return text;
-      },
-    }, {
-      title: '操作',
-      key: 'action',
-      render: (text, record) => {
-        if (!!record.editable && this.state.loading) {
-          return null;
-        }
-        if (record.editable) {
-          if (record.isNew) {
-            return (
-              <span>
-                <a onClick={e => this.saveRow(e, record.key)}>添加</a>
-                <Divider type="vertical" />
-                <Popconfirm title="是否要删除此行?" onConfirm={() => this.remove(record.key)}>
-                  <a>删除</a>
-                </Popconfirm>
-              </span>
-            );
-          }
-          return (
-            <span>
-              <a onClick={e => this.saveRow(e, record.key)}>保存</a>
-              <Divider type="vertical" />
-              <a onClick={e => this.cancel(e, record.key)}>取消</a>
-            </span>
-          );
-        }
-        return (
-          <span>
-            <a onClick={e => this.toggleEditable(e, record.key)}>编辑</a>
-            <Divider type="vertical" />
-            <Popconfirm title="是否要删除此行?" onConfirm={() => this.remove(record.key)}>
-              <a>删除</a>
-            </Popconfirm>
-          </span>
-        );
-      },
-    }];
-
-    return (
-      <Fragment>
-        <Table
-          loading={this.state.loading}
-          columns={columns}
-          dataSource={this.state.data}
-          pagination={false}
-          rowClassName={(record) => {
-            return record.editable ? styles.editable : '';
-          }}
-        />
-        <Button
-          style={{ width: '100%', marginTop: 16, marginBottom: 8 }}
-          type="dashed"
-          onClick={this.newMember}
-          icon="plus"
-        >
-          新增成员
-        </Button>
-      </Fragment>
-    );
-  }
-}

+ 391 - 0
src/routes/Frontend/Tag/TagCreate.js

@@ -0,0 +1,391 @@
+import React, { Component } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { message, Form, Table, Modal, Card, Button, Input, Switch, Radio } from 'antd';
+import { statusToBool, boolToStatus } from '../../../utils/utils';
+import RBDragSortTable from '../../../components/RBDragSortTable/index';
+import Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar/index';
+import styles from './TagCreate.less';
+
+const Message = message;
+
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 2 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@connect(({ loading, tag, tagGroup, shelves }) => ({
+  tag,
+  shelves,
+  tagGroup,
+  submitting: loading.models.tag,
+  sLoading: loading.models.shelves,
+  tLoading: loading.models.tagGroup,
+}))
+@Form.create()
+export default class TagCreatePage extends Component {
+  state = {
+    tagGroupSelectorDestroy: true,
+    shelvesSelectorDestroy: true,
+    productType: 'Course',
+  };
+  componentWillMount() {
+    const match = pathToRegexp('/frontend/tag/create').exec(this.props.location.pathname);
+    if (match) {
+      this.cleanPageState();
+    }
+  }
+  componentDidMount() {
+    const matchId = this.isEdit();
+    if (matchId) {
+      this.props.dispatch({
+        type: 'tag/fetchTagItem',
+        payload: { tagId: matchId },
+      });
+    }
+  }
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/frontend/tag/edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
+  cleanPageState=() => {
+    this.props.dispatch({
+      type: 'tag/cleanItemState',
+      payload: {},
+    });
+  }
+  handleRadioChange=(e) => {
+    this.setState({ productType: e.target.value });
+    this.selectorDataFetcher(e.target.value, {});
+  }
+  selectorDataFetcher=(name, params) => {
+    switch (name) {
+      case 'Course':
+        this.props.dispatch({
+          type: 'shelves/fetchCourseItemList',
+          payload: params,
+        });
+        break;
+      case 'Support':
+        this.props.dispatch({
+          type: 'shelves/fetchSupportItemList',
+          payload: params,
+        });
+        break;
+      case 'TagGroup':
+        this.props.dispatch({
+          type: 'tagGroup/fetchTagGroupList',
+          payload: params,
+        });
+        break;
+      default:
+        break;
+    }
+  }
+  handleTagGroupSelectorModalShow = () => {
+    this.setState({
+      tagGroupSelectorDestroy: false,
+    });
+    this.selectorDataFetcher('TagGroup');
+  }
+  handleShelvesSelectorModalShow = () => {
+    const { tag } = this.props;
+    const { currentItem } = tag;
+    const { merchantId } = currentItem;
+    if (!merchantId) {
+      Message.error('请先选择标签所属的标签组!');
+      return;
+    }
+    const params = { merchantId };
+    this.setState({
+      shelvesSelectorDestroy: false,
+    });
+    this.selectorDataFetcher(this.state.productType, params);
+  }
+  handleTagGroupSelectorChange = (params) => {
+    this.selectorDataFetcher('TagGroup', params);
+  }
+  handleShelvesSelectorChange = (params) => {
+    const { tag } = this.props;
+    const { currentItem } = tag;
+    const { merchantId } = currentItem;
+    this.selectorDataFetcher(this.state.productType, { ...params, merchantId });
+  }
+  handleTagGroupSelectorCancel = () => {
+    this.setState({
+      tagGroupSelectorDestroy: true,
+    });
+  }
+  handleShelvesSelectorCancel = () => {
+    this.setState({
+      shelvesSelectorDestroy: true,
+    });
+  }
+  handleTagGroupSelectorFinish = (rows) => {
+    this.setState({
+      tagGroupSelectorDestroy: true,
+    });
+    if (!rows || !rows.length) {
+      return;
+    }
+    const { id, name, merchantName, merchantId } = rows[0];
+    this.props.dispatch({
+      type: 'tag/fixCurrentItem',
+      payload: {
+        merchantId,
+        merchantName,
+        groupId: id,
+        groupName: name,
+      },
+    });
+  }
+  handleShelvesSelectorFinish = (rows) => {
+    this.setState({
+      shelvesSelectorDestroy: true,
+    });
+    if (!rows || !rows.length) {
+      return;
+    }
+    this.props.dispatch({
+      type: 'tag/fixCurrentItem',
+      payload: { productList: rows },
+    });
+  }
+  handleDragSortTableChange = (rows) => {
+    this.props.dispatch({
+      type: 'tag/fixCurrentItem',
+      payload: { productList: rows },
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/frontend/tag',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        const { name, status } = values;
+        const { tag } = this.props;
+        const { currentItem } = tag;
+        const { productList = [], groupId } = currentItem;
+        const id = this.isEdit();
+        if (id) {
+          this.props.dispatch({
+            type: 'tag/updateTagItem',
+            payload: {
+              id,
+              name,
+              groupId,
+              status: boolToStatus(status),
+              productList: productList.map(item => item.pid),
+            },
+            states: this.props.location.state,
+          });
+        } else {
+          this.props.dispatch({
+            type: 'tag/createTagItem',
+            payload: {
+              name,
+              groupId,
+              status: boolToStatus(status),
+              productList: productList.map(item => item.pid),
+            },
+            states: this.props.location.state,
+          });
+        }
+      }
+    });
+  }
+
+  render() {
+    const { form, submitting, tLoading, sLoading, shelves, tag, tagGroup } = this.props;
+    const { shelvesSelectorDestroy, tagGroupSelectorDestroy, productType } = this.state;
+    const { currentItem } = tag;
+    const { status, name, groupName, merchantName, productList = [] } = currentItem;
+    const { getFieldDecorator } = form;
+
+    const tagGroupData = [{
+      fieldName: '标签组名称',
+      value: groupName,
+      key: 'groupName',
+    }, {
+      fieldName: '渠道名称',
+      value: merchantName,
+      key: 'merchantName',
+    }];
+    const tagGroupColumns = [{
+      dataIndex: 'fieldName',
+      key: 1,
+      width: '30%',
+    }, {
+      dataIndex: 'value',
+      key: 2,
+      width: '70%',
+    }];
+    const productColumns = [{
+      title: '产品编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '35%',
+    }, {
+      title: '产品名称',
+      dataIndex: 'name',
+      key: 2,
+    }];
+    const renderProductModalTitle = () => {
+      return (
+        <Radio.Group
+          value={productType}
+          onChange={this.handleRadioChange}
+          className={styles.radio}
+        >
+          <Radio.Button value="Course">课程</Radio.Button>
+          <Radio.Button value="Support">配套</Radio.Button>
+        </Radio.Group>
+      );
+    };
+    const getTagGroupModal = () => {
+      return (
+        <Modal
+          width={1100}
+          footer={null}
+          visible
+          title="标签组"
+          maskClosable={false}
+          onCancel={this.handleTagGroupSelectorCancel}
+        >
+          <Selector
+            multiple={false}
+            loading={tLoading}
+            selectorName="TagGroup"
+            list={tagGroup.list}
+            pageNo={tagGroup.pageNo}
+            pageSize={tagGroup.pageSize}
+            totalSize={tagGroup.totalSize}
+            onCancel={this.handleTagGroupSelectorCancel}
+            onChange={this.handleTagGroupSelectorChange}
+            onFinish={this.handleTagGroupSelectorFinish}
+          />
+        </Modal>
+      );
+    };
+    const getProductModal = () => {
+      return (
+        <Modal
+          width={1100}
+          footer={null}
+          visible
+          title={renderProductModalTitle()}
+          maskClosable={false}
+          onCancel={this.handleShelvesSelectorCancel}
+        >
+          <Selector
+            multiple
+            loading={sLoading}
+            selectorName={productType}
+            list={shelves.list}
+            pageNo={shelves.pageNo}
+            pageSize={shelves.pageSize}
+            totalSize={shelves.totalSize}
+            onCancel={this.handleShelvesSelectorCancel}
+            onChange={this.handleShelvesSelectorChange}
+            onFinish={this.handleShelvesSelectorFinish}
+          />
+        </Modal>
+      );
+    };
+
+    const renderTagGroupCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a disabled={!!this.isEdit()} onClick={this.handleTagGroupSelectorModalShow}>所属标签组</a>
+          </span>
+        </div>
+      );
+    };
+    const renderProductCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={this.handleShelvesSelectorModalShow}>上架产品</a>
+          </span>
+        </div>
+      );
+    };
+    return (
+      <div>
+        {/* 基础信息Card */}
+        <Card title="基础信息" style={{ marginBottom: 16 }}>
+          <Form>
+            <Form.Item hasFeedback label="标签名称" {...formItemLayout}>
+              {getFieldDecorator('name', {
+                rules: [{ required: true, message: '请填写标签名称' }],
+                initialValue: name,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label="标签状态" {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: statusToBool(status),
+              })(
+                <Switch checkedChildren="正常" unCheckedChildren="删除" />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        {/* 标签组选择Card */}
+        <Card title={renderTagGroupCardName()} style={{ marginBottom: 16 }}>
+          <Table
+            bordered
+            showHeader={false}
+            pagination={false}
+            columns={tagGroupColumns}
+            dataSource={tagGroupData}
+          />
+          {!tagGroupSelectorDestroy && getTagGroupModal()}
+        </Card>
+        {/* 产品选择Card */}
+        <Card title={renderProductCardName()} style={{ marginBottom: 70 }}>
+          <RBDragSortTable
+            data={productList}
+            columns={productColumns}
+            onChange={this.handleDragSortTableChange}
+          />
+          {!shelvesSelectorDestroy && getProductModal()}
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button
+            onClick={this.handlePageBack}
+            style={{ marginRight: 10 }}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            loading={submitting}
+            onClick={this.handlePageSubmit}
+          >提交
+          </Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 43 - 0
src/routes/Frontend/Tag/TagCreate.less

@@ -0,0 +1,43 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.cardName {
+  & > span {
+    display: inline-block;
+    height: 24px;
+    padding: 0 7px;
+    vertical-align: bottom;
+  }
+  :global {
+    .ant-btn-primary {
+      margin-left: 10px;
+    }
+  }
+}
+
+.cover {
+  width: 100%;
+  height: 240px;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.background {
+  width: 100%;
+  height: 240px;
+  line-height: 240px;
+  vertical-align: middle;
+  img {
+    width: 100%;
+  }
+}
+
+.radio {
+  :global {
+    .ant-radio-button-wrapper-checked {
+      background-color: @primary-color;
+      color: #fff;
+    }
+  }
+}

+ 174 - 0
src/routes/Frontend/Tag/TagList.js

@@ -0,0 +1,174 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Button, message } from 'antd';
+import { StandardTableList } from '../../../components/RBList/index';
+import { renderStatus, addRowKey } from '../../../utils/utils';
+import styles from './TagList.less';
+
+const Message = message;
+
+@connect(({ loading, tag }) => ({
+  tag,
+  loading: loading.models.tag,
+}))
+export default class TagListPage extends Component {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    this.state = {
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
+    };
+  }
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'tag/fetchTagList',
+      payload: { ...this.state.Queryers },
+    });
+  }
+  handleCreateOperation = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/frontend/tag/create',
+      state: this.state,
+    }));
+  }
+  handleDeleteOperation = (item) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定要删除该标签吗?',
+      onOk: () => {
+        this.props.dispatch({
+          type: 'tag/deleteTagItem',
+          payload: { tagId: item.id },
+          states: this.state,
+        });
+      },
+    });
+  }
+  handleEditOperation = (item) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/frontend/tag/edit/${item.id}`,
+      state: this.state,
+    }));
+  }
+  handleFilterOperation = (params, states) => {
+    this.props.dispatch({
+      type: 'tag/fetchTagList',
+      payload: params,
+    });
+    this.setState({
+      UIParams: states,
+      Queryers: params,
+    });
+  }
+  handleBatchOperation = () => {
+    Message.info('暂不支持批量操作!');
+  }
+
+  render() {
+    const { loading, tag } = this.props;
+    const { list, totalSize, pageSize, pageNo } = tag;
+
+    const renderOperation = (item) => {
+      return (
+        <div>
+          <Button
+            size="small"
+            className={styles.editBtn}
+            onClick={() => this.handleEditOperation(item)}
+          >编辑
+          </Button>
+          <Button
+            size="small"
+            className={styles.delBtn}
+            onClick={() => this.handleDeleteOperation(item)}
+          >删除
+          </Button>
+        </div>
+      );
+    };
+
+    const batchActions = [{
+      key: 'delete',
+      name: '批量删除',
+    }, {
+      key: 'recovery',
+      name: '批量恢复',
+    }];
+    const basicSearch = {
+      keys: [{
+        name: '标签名称',
+        field: 'name',
+      }],
+    };
+    const pagination = {
+      pageNo,
+      pageSize,
+      totalSize,
+    };
+    const columns = [{
+      title: '标签名称',
+      key: 1,
+      dataIndex: 'name',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '20%',
+    }, {
+      title: '所属标签组',
+      key: 2,
+      dataIndex: 'groupName',
+      width: '20%',
+    }, {
+      title: '所属渠道',
+      key: 3,
+      dataIndex: 'merchantName',
+      width: '15%',
+    }, {
+      title: '标签状态',
+      key: 4,
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+      width: '12%',
+    }, {
+      title: '更新时间',
+      key: 5,
+      dataIndex: 'gmtModified',
+      render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      width: '15%',
+    }, {
+      title: '操作',
+      key: 6,
+      render: (_, record) => renderOperation(record),
+      width: '13%',
+    }];
+    return (
+      <Card>
+        <StandardTableList
+          columns={columns}
+          loading={loading}
+          dataSource={addRowKey(list)}
+          header={{
+            basicSearch,
+            onFilterClick: this.handleFilterOperation,
+            onCreateClick: this.handleCreateOperation,
+          }}
+          footer={{
+            pagination,
+            batchActions,
+            onBatchClick: this.handleBatchOperation,
+          }}
+          keepUIState={{ ...this.state.UIParams }}
+        />
+      </Card>
+    );
+  }
+}

+ 16 - 0
src/routes/Frontend/Tag/TagList.less

@@ -0,0 +1,16 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.link {
+  font-weight: 500;
+}
+.editBtn {
+  margin-right: 10px;
+  background: @primary-5;
+  color: #fff;
+  font-weight: 500;
+}
+.delBtn {
+  background: #f5222d;
+  color: #fff;
+  font-weight: 500;
+}

+ 5 - 4
src/routes/Merchant/Merchant.js

@@ -1,11 +1,11 @@
 import React, { Component } from 'react';
-import { Route, Switch } from 'dva/router';
+import { Redirect, Route, Switch } from 'dva/router';
 import { connect } from 'dva';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import { getRoutes } from '../../utils/utils';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import { getRoutes } from '../../../utils/utils';
 
 @connect()
-export default class Merchant extends Component {
+export default class Tag extends Component {
   render() {
     const { match, routerData } = this.props;
     const routes = getRoutes(match.path, routerData);
@@ -25,6 +25,7 @@ export default class Merchant extends Component {
               )
             )
           }
+          <Redirect exact from="/frontend/tag" to="/frontend/tag/list" />
         </Switch>
       </PageHeaderLayout>
     );

+ 307 - 0
src/routes/Frontend/TagGroup/TagGroupCreate.js

@@ -0,0 +1,307 @@
+import React, { Component } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Form, Table, Modal, Card, Button, Input, Switch } from 'antd';
+import { renderStatus, statusToBool, boolToStatus } from '../../../utils/utils';
+import RBDragSortTable from '../../../components/RBDragSortTable';
+import Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar';
+import styles from './TagGroupCreate.less';
+
+
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 3 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@connect(({ loading, tagGroup, merchant }) => ({
+  merchant,
+  tagGroup,
+  submitting: loading.models.tagGroup,
+  mLoading: loading.models.merchant,
+}))
+@Form.create()
+export default class TagCreatePage extends Component {
+  state = {
+    merchantSelectorDestroy: true,
+  };
+  componentWillMount() {
+    const match = pathToRegexp('/frontend/tagGroup/create').exec(this.props.location.pathname);
+    if (match) {
+      this.cleanPageState();
+    }
+  }
+  componentDidMount() {
+    const matchId = this.isEdit();
+    if (matchId) {
+      this.props.dispatch({
+        type: 'tagGroup/fetchTagGroupItem',
+        payload: { groupId: matchId },
+      });
+    }
+  }
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/frontend/tagGroup/edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
+  cleanPageState=() => {
+    this.props.dispatch({
+      type: 'tagGroup/cleanItemState',
+      payload: {},
+    });
+  }
+  selectorDataFetcher=(name, params) => {
+    switch (name) {
+      case 'Merchant':
+        this.props.dispatch({
+          type: 'merchant/fetchMerchantList',
+          payload: params,
+        });
+        break;
+      case 'Tag':
+        this.props.dispatch({
+          type: 'tag/fetchTagList',
+          payload: params,
+        });
+        break;
+      default:
+        break;
+    }
+  }
+  handleMerchantSelectorModalShow = () => {
+    this.setState({
+      merchantSelectorDestroy: false,
+    });
+    this.selectorDataFetcher('Merchant');
+  }
+  handleMerchantSelectorChange = (params) => {
+    this.selectorDataFetcher('Merchant', params);
+  }
+  handleMerchantSelectorCancel = () => {
+    this.setState({
+      merchantSelectorDestroy: true,
+    });
+  }
+  handleMerchantSelectorFinish = (rows) => {
+    this.setState({
+      merchantSelectorDestroy: true,
+    });
+    if (!rows || !rows.length) {
+      return;
+    }
+    const { id, name } = rows[0];
+    this.props.dispatch({
+      type: 'tagGroup/fixCurrentItem',
+      payload: {
+        merchantId: id,
+        merchantName: name,
+      },
+    });
+  }
+  handleDragSortTableChange = (rows) => {
+    this.props.dispatch({
+      type: 'tagGroup/fixCurrentItem',
+      payload: { tagList: rows },
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/frontend/tagGroup',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        const { code, name, status } = values;
+        const { tagGroup } = this.props;
+        const { currentItem } = tagGroup;
+        const { tagList = [], merchantId } = currentItem;
+        const id = this.isEdit();
+        if (id) {
+          this.props.dispatch({
+            type: 'tagGroup/updateTagGroupItem',
+            payload: {
+              id,
+              code,
+              name,
+              merchantId,
+              status: boolToStatus(status),
+              tagList: tagList.map(item => item.id),
+            },
+            states: this.props.location.state,
+          });
+        } else {
+          this.props.dispatch({
+            type: 'tagGroup/createTagGroupItem',
+            payload: {
+              code,
+              name,
+              merchantId,
+              status: boolToStatus(status),
+              tagList: tagList.map(item => item.id),
+            },
+            states: this.props.location.state,
+          });
+        }
+      }
+    });
+  }
+
+  render() {
+    const { form, submitting, mLoading, merchant, tagGroup } = this.props;
+    const { merchantSelectorDestroy } = this.state;
+    const { currentItem } = tagGroup;
+    const { status, code, name, merchantName, tagList = [] } = currentItem;
+    const { getFieldDecorator } = form;
+
+    const merchantData = [{
+      fieldName: '渠道名称',
+      value: merchantName,
+      key: 'merchantName',
+    }];
+    const merchantColumns = [{
+      dataIndex: 'fieldName',
+      key: 1,
+      width: '30%',
+    }, {
+      dataIndex: 'value',
+      key: 2,
+      width: '70%',
+    }];
+    const tagColumns = [{
+      title: '标签名称',
+      dataIndex: 'name',
+      key: 1,
+      width: '35%',
+    }, {
+      title: '标签状态',
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+      key: 2,
+    }];
+    const getMerchantModal = () => {
+      return (
+        <Modal
+          width={1100}
+          footer={null}
+          visible
+          title="厂商列表"
+          maskClosable={false}
+          onCancel={this.handleMerchantSelectorCancel}
+        >
+          <Selector
+            multiple={false}
+            loading={mLoading}
+            selectorName="Merchant"
+            list={merchant.list}
+            pageNo={merchant.pageNo}
+            pageSize={merchant.pageSize}
+            totalSize={merchant.totalSize}
+            onCancel={this.handleMerchantSelectorCancel}
+            onChange={this.handleMerchantSelectorChange}
+            onFinish={this.handleMerchantSelectorFinish}
+          />
+        </Modal>
+      );
+    };
+    const renderMerchantCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a disabled={!!this.isEdit()} onClick={this.handleMerchantSelectorModalShow}>所属渠道</a>
+          </span>
+        </div>
+      );
+    };
+    return (
+      <div>
+        {/* 基础信息Card */}
+        <Card title="基础信息" style={{ marginBottom: 16 }}>
+          <Form>
+            <Form.Item hasFeedback label="标签组编号" {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [
+                  {
+                    required: true, message: '请填写标签组编号',
+                  }, {
+                    pattern: /^[a-zA-Z0-9|_|-]+$/g, message: '编号包含非法字符',
+                  },
+                ],
+                initialValue: code,
+              })(
+                <Input
+                  placeholder="请输入"
+                  disabled={!!this.isEdit()}
+                />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label="标签组名称" {...formItemLayout}>
+              {getFieldDecorator('name', {
+                rules: [{ required: true, message: '请填写标签名称' }],
+                initialValue: name,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label="标签组状态" {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: statusToBool(status),
+              })(
+                <Switch checkedChildren="正常" unCheckedChildren="删除" />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        {/* 厂商选择Card */}
+        <Card title={renderMerchantCardName()} style={{ marginBottom: 16 }}>
+          <Table
+            bordered
+            showHeader={false}
+            pagination={false}
+            columns={merchantColumns}
+            dataSource={merchantData}
+          />
+          {!merchantSelectorDestroy && getMerchantModal()}
+        </Card>
+        {/* 标签排序Card */}
+        {this.isEdit() &&
+          <Card title="标签列表" style={{marginBottom: 70}}>
+            <RBDragSortTable
+              data={tagList}
+              columns={tagColumns}
+              onChange={this.handleDragSortTableChange}
+            />
+          </Card>
+        }
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button
+            onClick={this.handlePageBack}
+            style={{ marginRight: 10 }}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            loading={submitting}
+            onClick={this.handlePageSubmit}
+          >提交
+          </Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 34 - 0
src/routes/Frontend/TagGroup/TagGroupCreate.less

@@ -0,0 +1,34 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.cardName {
+  & > span {
+    display: inline-block;
+    height: 24px;
+    padding: 0 7px;
+    vertical-align: bottom;
+  }
+  :global {
+    .ant-btn-primary {
+      margin-left: 10px;
+    }
+  }
+}
+
+.cover {
+  width: 100%;
+  height: 240px;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.background {
+  width: 100%;
+  height: 240px;
+  line-height: 240px;
+  vertical-align: middle;
+  img {
+    width: 100%;
+  }
+}

+ 185 - 0
src/routes/Frontend/TagGroup/TagGroupList.js

@@ -0,0 +1,185 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Button, message } from 'antd';
+import { StandardTableList } from '../../../components/RBList/index';
+import { renderStatus, addRowKey } from '../../../utils/utils';
+import styles from './TagGroupList.less';
+
+const Message = message;
+
+@connect(({ loading, tagGroup }) => ({
+  tagGroup,
+  loading: loading.models.tagGroup,
+}))
+export default class TagGroupListPage extends Component {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    this.state = {
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
+    };
+  }
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'tagGroup/fetchTagGroupList',
+      payload: { ...this.state.Queryers },
+    });
+  }
+  handleCreateOperation = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/frontend/tagGroup/create',
+      state: this.state,
+    }));
+  }
+  handleDeleteOperation = (item) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定要删除该标签组吗?',
+      onOk: () => {
+        this.props.dispatch({
+          type: 'tagGroup/deleteTagGroupItem',
+          payload: { groupId: item.id },
+          states: this.state,
+        });
+      },
+    });
+  }
+  handleEditOperation = (item) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/frontend/tagGroup/edit/${item.id}`,
+      state: this.state,
+    }));
+  }
+  handleFilterOperation = (params, states) => {
+    this.props.dispatch({
+      type: 'tagGroup/fetchTagGroupList',
+      payload: params,
+    });
+    this.setState({
+      UIParams: states,
+      Queryers: params,
+    });
+  }
+  handleBatchOperation = () => {
+    Message.info('暂不支持批量操作!');
+  }
+
+  render() {
+    const { loading, tagGroup } = this.props;
+    const { list, totalSize, pageSize, pageNo } = tagGroup;
+
+    const renderOperation = (item) => {
+      return (
+        <div>
+          <Button
+            size="small"
+            className={styles.editBtn}
+            onClick={() => this.handleEditOperation(item)}
+          >编辑
+          </Button>
+          <Button
+            size="small"
+            className={styles.delBtn}
+            onClick={() => this.handleDeleteOperation(item)}
+          >删除
+          </Button>
+        </div>
+      );
+    };
+
+    const batchActions = [{
+      key: 'delete',
+      name: '批量删除',
+    }, {
+      key: 'recovery',
+      name: '批量恢复',
+    }];
+    const basicSearch = {
+      keys: [{
+        name: '标签组编号',
+        field: 'code',
+      }, {
+        name: '标签组名称',
+        field: 'name',
+      }],
+    };
+    const pagination = {
+      pageNo,
+      pageSize,
+      totalSize,
+    };
+    const columns = [{
+      title: '标签组编号',
+      key: 1,
+      dataIndex: 'code',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '20%',
+    }, {
+      title: '标签组名称',
+      key: 2,
+      dataIndex: 'name',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '20%',
+    }, {
+      title: '所属渠道',
+      key: 3,
+      dataIndex: 'merchantName',
+      width: '15%',
+    }, {
+      title: '标签组状态',
+      key: 4,
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+      width: '12%',
+    }, {
+      title: '更新时间',
+      key: 5,
+      dataIndex: 'gmtModified',
+      render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      width: '20%',
+    }, {
+      title: '操作',
+      key: 6,
+      render: (_, record) => renderOperation(record),
+      width: '13%',
+    }];
+    return (
+      <Card>
+        <StandardTableList
+          columns={columns}
+          loading={loading}
+          dataSource={addRowKey(list)}
+          header={{
+            basicSearch,
+            onFilterClick: this.handleFilterOperation,
+            onCreateClick: this.handleCreateOperation,
+          }}
+          footer={{
+            pagination,
+            batchActions,
+            onBatchClick: this.handleBatchOperation,
+          }}
+          keepUIState={{ ...this.state.UIParams }}
+        />
+      </Card>
+    );
+  }
+}

+ 16 - 0
src/routes/Frontend/TagGroup/TagGroupList.less

@@ -0,0 +1,16 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.link {
+  font-weight: 500;
+}
+.editBtn {
+  margin-right: 10px;
+  background: @primary-5;
+  color: #fff;
+  font-weight: 500;
+}
+.delBtn {
+  background: #f5222d;
+  color: #fff;
+  font-weight: 500;
+}

+ 5 - 4
src/routes/Campus/Campus.js

@@ -1,11 +1,11 @@
 import React, { Component } from 'react';
-import { Route, Switch } from 'dva/router';
+import { Redirect, Route, Switch } from 'dva/router';
 import { connect } from 'dva';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import { getRoutes } from '../../utils/utils';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import { getRoutes } from '../../../utils/utils';
 
 @connect()
-export default class Campus extends Component {
+export default class TagGroup extends Component {
   render() {
     const { match, routerData } = this.props;
     const routes = getRoutes(match.path, routerData);
@@ -25,6 +25,7 @@ export default class Campus extends Component {
               )
             )
           }
+          <Redirect exact from="/frontend/tagGroup" to="/frontend/tagGroup/list" />
         </Switch>
       </PageHeaderLayout>
     );

+ 0 - 200
src/routes/List/Applications.js

@@ -1,200 +0,0 @@
-import React, { PureComponent } from 'react';
-import numeral from 'numeral';
-import { connect } from 'dva';
-import { Row, Col, Form, Card, Select, Icon, Avatar, List, Tooltip, Dropdown, Menu } from 'antd';
-
-import StandardFormRow from '../../components/StandardFormRow';
-import TagSelect from '../../components/TagSelect';
-
-import styles from './Applications.less';
-
-const { Option } = Select;
-const FormItem = Form.Item;
-
-const formatWan = (val) => {
-  const v = val * 1;
-  if (!v || isNaN(v)) return '';
-
-  let result = val;
-  if (val > 10000) {
-    result = Math.floor(val / 10000);
-    result = <span>{result}<em className={styles.wan}>万</em></span>;
-  }
-  return result;
-};
-
-/* eslint react/no-array-index-key: 0 */
-@Form.create()
-@connect(({ list, loading }) => ({
-  list,
-  loading: loading.models.list,
-}))
-export default class FilterCardList extends PureComponent {
-  componentDidMount() {
-    this.props.dispatch({
-      type: 'list/fetch',
-      payload: {
-        count: 8,
-      },
-    });
-  }
-
-  handleFormSubmit = () => {
-    const { form, dispatch } = this.props;
-    // setTimeout 用于保证获取表单值是在所有表单字段更新完毕的时候
-    setTimeout(() => {
-      form.validateFields((err) => {
-        if (!err) {
-          // eslint-disable-next-line
-          dispatch({
-            type: 'list/fetch',
-            payload: {
-              count: 8,
-            },
-          });
-        }
-      });
-    }, 0);
-  }
-
-  render() {
-    const { list: { list }, loading, form } = this.props;
-    const { getFieldDecorator } = form;
-
-    const CardInfo = ({ activeUser, newUser }) => (
-      <div className={styles.cardInfo}>
-        <div>
-          <p>活跃用户</p>
-          <p>{activeUser}</p>
-        </div>
-        <div>
-          <p>新增用户</p>
-          <p>{newUser}</p>
-        </div>
-      </div>
-    );
-
-    const formItemLayout = {
-      wrapperCol: {
-        xs: { span: 24 },
-        sm: { span: 16 },
-      },
-    };
-
-    const itemMenu = (
-      <Menu>
-        <Menu.Item>
-          <a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a>
-        </Menu.Item>
-        <Menu.Item>
-          <a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a>
-        </Menu.Item>
-        <Menu.Item>
-          <a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">3d menu item</a>
-        </Menu.Item>
-      </Menu>
-    );
-
-    return (
-      <div className={styles.filterCardList}>
-        <Card bordered={false}>
-          <Form layout="inline">
-            <StandardFormRow title="所属类目" block style={{ paddingBottom: 11 }}>
-              <FormItem>
-                {getFieldDecorator('category')(
-                  <TagSelect onChange={this.handleFormSubmit} expandable>
-                    <TagSelect.Option value="cat1">类目一</TagSelect.Option>
-                    <TagSelect.Option value="cat2">类目二</TagSelect.Option>
-                    <TagSelect.Option value="cat3">类目三</TagSelect.Option>
-                    <TagSelect.Option value="cat4">类目四</TagSelect.Option>
-                    <TagSelect.Option value="cat5">类目五</TagSelect.Option>
-                    <TagSelect.Option value="cat6">类目六</TagSelect.Option>
-                    <TagSelect.Option value="cat7">类目七</TagSelect.Option>
-                    <TagSelect.Option value="cat8">类目八</TagSelect.Option>
-                    <TagSelect.Option value="cat9">类目九</TagSelect.Option>
-                    <TagSelect.Option value="cat10">类目十</TagSelect.Option>
-                    <TagSelect.Option value="cat11">类目十一</TagSelect.Option>
-                    <TagSelect.Option value="cat12">类目十二</TagSelect.Option>
-                  </TagSelect>
-                )}
-              </FormItem>
-            </StandardFormRow>
-            <StandardFormRow
-              title="其它选项"
-              grid
-              last
-            >
-              <Row gutter={16}>
-                <Col lg={8} md={10} sm={10} xs={24}>
-                  <FormItem
-                    {...formItemLayout}
-                    label="作者"
-                  >
-                    {getFieldDecorator('author', {})(
-                      <Select
-                        onChange={this.handleFormSubmit}
-                        placeholder="不限"
-                        style={{ maxWidth: 200, width: '100%' }}
-                      >
-                        <Option value="lisa">王昭君</Option>
-                      </Select>
-                    )}
-                  </FormItem>
-                </Col>
-                <Col lg={8} md={10} sm={10} xs={24}>
-                  <FormItem
-                    {...formItemLayout}
-                    label="好评度"
-                  >
-                    {getFieldDecorator('rate', {})(
-                      <Select
-                        onChange={this.handleFormSubmit}
-                        placeholder="不限"
-                        style={{ maxWidth: 200, width: '100%' }}
-                      >
-                        <Option value="good">优秀</Option>
-                        <Option value="normal">普通</Option>
-                      </Select>
-                    )}
-                  </FormItem>
-                </Col>
-              </Row>
-            </StandardFormRow>
-          </Form>
-        </Card>
-        <List
-          rowKey="id"
-          style={{ marginTop: 24 }}
-          grid={{ gutter: 24, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}
-          loading={loading}
-          dataSource={list}
-          renderItem={item => (
-            <List.Item key={item.id}>
-              <Card
-                hoverable
-                bodyStyle={{ paddingBottom: 20 }}
-                actions={[
-                  <Tooltip title="下载"><Icon type="download" /></Tooltip>,
-                  <Tooltip title="编辑"><Icon type="edit" /></Tooltip>,
-                  <Tooltip title="分享"><Icon type="share-alt" /></Tooltip>,
-                  <Dropdown overlay={itemMenu}><Icon type="ellipsis" /></Dropdown>,
-                ]}
-              >
-                <Card.Meta
-                  avatar={<Avatar size="small" src={item.avatar} />}
-                  title={item.title}
-                />
-                <div className={styles.cardItemContent}>
-                  <CardInfo
-                    activeUser={formatWan(item.activeUser)}
-                    newUser={numeral(item.newUser).format('0,0')}
-                  />
-                </div>
-              </Card>
-            </List.Item>
-          )}
-        />
-      </div>
-    );
-  }
-}

+ 0 - 52
src/routes/List/Applications.less

@@ -1,52 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.filterCardList {
-  margin-bottom: -24px;
-  :global {
-    .ant-card-meta-content {
-      margin-top: 0;
-    }
-    // disabled white space
-    .ant-card-meta-avatar {
-      font-size: 0;
-    }
-    .ant-card-actions {
-      background: #f7f9fa;
-    }
-    .ant-list .ant-list-item-content-single {
-      max-width: 100%;
-    }
-  }
-  .cardInfo {
-    .clearfix();
-    margin-top: 16px;
-    margin-left: 40px;
-    & > div {
-      position: relative;
-      text-align: left;
-      float: left;
-      width: 50%;
-      p {
-        line-height: 32px;
-        font-size: 24px;
-        margin: 0;
-      }
-      p:first-child {
-        color: @text-color-secondary;
-        font-size: 12px;
-        line-height: 20px;
-        margin-bottom: 4px;
-      }
-    }
-  }
-}
-
-.wan {
-  position: relative;
-  top: -2px;
-  font-size: @font-size-base;
-  font-style: normal;
-  line-height: 20px;
-  margin-left: 2px;
-}

+ 0 - 236
src/routes/List/Articles.js

@@ -1,236 +0,0 @@
-import React, { Component, Fragment } from 'react';
-import moment from 'moment';
-import { connect } from 'dva';
-import { Form, Card, Select, List, Tag, Icon, Avatar, Row, Col, Button } from 'antd';
-
-import StandardFormRow from '../../components/StandardFormRow';
-import TagSelect from '../../components/TagSelect';
-import styles from './Articles.less';
-
-const { Option } = Select;
-const FormItem = Form.Item;
-
-const pageSize = 5;
-
-@Form.create()
-@connect(({ list, loading }) => ({
-  list,
-  loading: loading.models.list,
-}))
-export default class SearchList extends Component {
-  componentDidMount() {
-    this.fetchMore();
-  }
-
-  setOwner = () => {
-    const { form } = this.props;
-    form.setFieldsValue({
-      owner: ['wzj'],
-    });
-  }
-
-  fetchMore = () => {
-    this.props.dispatch({
-      type: 'list/appendFetch',
-      payload: {
-        count: pageSize,
-      },
-    });
-  }
-
-  render() {
-    const { form, list: { list }, loading } = this.props;
-    const { getFieldDecorator } = form;
-
-    const owners = [
-      {
-        id: 'wzj',
-        name: '我自己',
-      },
-      {
-        id: 'wjh',
-        name: '吴家豪',
-      },
-      {
-        id: 'zxx',
-        name: '周星星',
-      },
-      {
-        id: 'zly',
-        name: '赵丽颖',
-      },
-      {
-        id: 'ym',
-        name: '姚明',
-      },
-    ];
-
-    const IconText = ({ type, text }) => (
-      <span>
-        <Icon type={type} style={{ marginRight: 8 }} />
-        {text}
-      </span>
-    );
-
-    const ListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
-      <div className={styles.listContent}>
-        <div className={styles.description}>{content}</div>
-        <div className={styles.extra}>
-          <Avatar src={avatar} size="small" /><a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a>
-          <em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em>
-        </div>
-      </div>
-    );
-
-    const formItemLayout = {
-      wrapperCol: {
-        xs: { span: 24 },
-        sm: { span: 24 },
-        md: { span: 12 },
-      },
-    };
-
-    const loadMore = list.length > 0 ? (
-      <div style={{ textAlign: 'center', marginTop: 16 }}>
-        <Button onClick={this.fetchMore} style={{ paddingLeft: 48, paddingRight: 48 }}>
-          {loading ? <span><Icon type="loading" /> 加载中...</span> : '加载更多'}
-        </Button>
-      </div>
-    ) : null;
-
-    return (
-      <Fragment>
-        <Card bordered={false}>
-          <Form layout="inline">
-            <StandardFormRow title="所属类目" block style={{ paddingBottom: 11 }}>
-              <FormItem>
-                {getFieldDecorator('category')(
-                  <TagSelect onChange={this.handleFormSubmit} expandable>
-                    <TagSelect.Option value="cat1">类目一</TagSelect.Option>
-                    <TagSelect.Option value="cat2">类目二</TagSelect.Option>
-                    <TagSelect.Option value="cat3">类目三</TagSelect.Option>
-                    <TagSelect.Option value="cat4">类目四</TagSelect.Option>
-                    <TagSelect.Option value="cat5">类目五</TagSelect.Option>
-                    <TagSelect.Option value="cat6">类目六</TagSelect.Option>
-                    <TagSelect.Option value="cat7">类目七</TagSelect.Option>
-                    <TagSelect.Option value="cat8">类目八</TagSelect.Option>
-                    <TagSelect.Option value="cat9">类目九</TagSelect.Option>
-                    <TagSelect.Option value="cat10">类目十</TagSelect.Option>
-                    <TagSelect.Option value="cat11">类目十一</TagSelect.Option>
-                    <TagSelect.Option value="cat12">类目十二</TagSelect.Option>
-                  </TagSelect>
-                )}
-              </FormItem>
-            </StandardFormRow>
-            <StandardFormRow
-              title="owner"
-              grid
-            >
-              <Row>
-                <Col lg={16} md={24} sm={24} xs={24}>
-                  <FormItem>
-                    {getFieldDecorator('owner', {
-                      initialValue: ['wjh', 'zxx'],
-                    })(
-                      <Select
-                        mode="multiple"
-                        style={{ maxWidth: 286, width: '100%' }}
-                        placeholder="选择 owner"
-                      >
-                        {
-                          owners.map(owner =>
-                            <Option key={owner.id} value={owner.id}>{owner.name}</Option>
-                          )
-                        }
-                      </Select>
-                    )}
-                    <a className={styles.selfTrigger} onClick={this.setOwner}>只看自己的</a>
-                  </FormItem>
-                </Col>
-              </Row>
-            </StandardFormRow>
-            <StandardFormRow
-              title="其它选项"
-              grid
-              last
-            >
-              <Row gutter={16}>
-                <Col xl={8} lg={10} md={12} sm={24} xs={24}>
-                  <FormItem
-                    {...formItemLayout}
-                    label="活跃用户"
-                  >
-                    {getFieldDecorator('user', {})(
-                      <Select
-                        onChange={this.handleFormSubmit}
-                        placeholder="不限"
-                        style={{ maxWidth: 200, width: '100%' }}
-                      >
-                        <Option value="lisa">李三</Option>
-                      </Select>
-                    )}
-                  </FormItem>
-                </Col>
-                <Col xl={8} lg={10} md={12} sm={24} xs={24}>
-                  <FormItem
-                    {...formItemLayout}
-                    label="好评度"
-                  >
-                    {getFieldDecorator('rate', {})(
-                      <Select
-                        onChange={this.handleFormSubmit}
-                        placeholder="不限"
-                        style={{ maxWidth: 200, width: '100%' }}
-                      >
-                        <Option value="good">优秀</Option>
-                      </Select>
-                    )}
-                  </FormItem>
-                </Col>
-              </Row>
-            </StandardFormRow>
-          </Form>
-        </Card>
-        <Card
-          style={{ marginTop: 24 }}
-          bordered={false}
-          bodyStyle={{ padding: '8px 32px 32px 32px' }}
-        >
-          <List
-            size="large"
-            loading={list.length === 0 ? loading : false}
-            rowKey="id"
-            itemLayout="vertical"
-            loadMore={loadMore}
-            dataSource={list}
-            renderItem={item => (
-              <List.Item
-                key={item.id}
-                actions={[
-                  <IconText type="star-o" text={item.star} />,
-                  <IconText type="like-o" text={item.like} />,
-                  <IconText type="message" text={item.message} />,
-                ]}
-                extra={<div className={styles.listItemExtra} />}
-              >
-                <List.Item.Meta
-                  title={(
-                    <a className={styles.listItemMetaTitle} href={item.href}>{item.title}</a>
-                  )}
-                  description={
-                    <span>
-                      <Tag>Ant Design</Tag>
-                      <Tag>设计语言</Tag>
-                      <Tag>蚂蚁金服</Tag>
-                    </span>
-                  }
-                />
-                <ListContent data={item} />
-              </List.Item>
-            )}
-          />
-        </Card>
-      </Fragment>
-    );
-  }
-}

+ 0 - 65
src/routes/List/Articles.less

@@ -1,65 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.listContent {
-  .description {
-    line-height: 22px;
-    max-width: 720px;
-  }
-  .extra {
-    color: @text-color-secondary;
-    margin-top: 16px;
-    line-height: 22px;
-    & > :global(.ant-avatar) {
-      vertical-align: top;
-      margin-right: 8px;
-      width: 20px;
-      height: 20px;
-      position: relative;
-      top: 1px;
-    }
-    & > em {
-      color: @disabled-color;
-      font-style: normal;
-      margin-left: 16px;
-    }
-  }
-}
-a.listItemMetaTitle {
-  color: @heading-color;
-}
-.listItemExtra {
-  width: 272px;
-  height: 1px;
-}
-.selfTrigger {
-  margin-left: 12px;
-}
-
-@media screen and (max-width: @screen-xs) {
-  .selfTrigger {
-    display: block;
-    margin-left: 0;
-  }
-  .listContent {
-    .extra {
-      & > em {
-        display: block;
-        margin-left: 0;
-        margin-top: 8px;
-      }
-    }
-  }
-}
-@media screen and (max-width: @screen-md) {
-  .selfTrigger {
-    display: block;
-    margin-left: 0;
-  }
-}
-@media screen and (max-width: @screen-lg) {
-  .listItemExtra {
-    width: 0;
-    height: 1px;
-  }
-}

+ 0 - 148
src/routes/List/BasicList.js

@@ -1,148 +0,0 @@
-import React, { PureComponent } from 'react';
-import moment from 'moment';
-import { connect } from 'dva';
-import { List, Card, Row, Col, Radio, Input, Progress, Button, Icon, Dropdown, Menu, Avatar } from 'antd';
-
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-
-import styles from './BasicList.less';
-
-const RadioButton = Radio.Button;
-const RadioGroup = Radio.Group;
-const { Search } = Input;
-
-@connect(({ list, loading }) => ({
-  list,
-  loading: loading.models.list,
-}))
-export default class BasicList extends PureComponent {
-  componentDidMount() {
-    this.props.dispatch({
-      type: 'list/fetch',
-      payload: {
-        count: 5,
-      },
-    });
-  }
-
-  render() {
-    const { list: { list }, loading } = this.props;
-
-    const Info = ({ title, value, bordered }) => (
-      <div className={styles.headerInfo}>
-        <span>{title}</span>
-        <p>{value}</p>
-        {bordered && <em />}
-      </div>
-    );
-
-    const extraContent = (
-      <div className={styles.extraContent}>
-        <RadioGroup defaultValue="all">
-          <RadioButton value="all">全部</RadioButton>
-          <RadioButton value="progress">进行中</RadioButton>
-          <RadioButton value="waiting">等待中</RadioButton>
-        </RadioGroup>
-        <Search
-          className={styles.extraContentSearch}
-          placeholder="请输入"
-          onSearch={() => ({})}
-        />
-      </div>
-    );
-
-    const paginationProps = {
-      showSizeChanger: true,
-      showQuickJumper: true,
-      pageSize: 5,
-      total: 50,
-    };
-
-    const ListContent = ({ data: { owner, createdAt, percent, status } }) => (
-      <div className={styles.listContent}>
-        <div className={styles.listContentItem}>
-          <span>Owner</span>
-          <p>{owner}</p>
-        </div>
-        <div className={styles.listContentItem}>
-          <span>开始时间</span>
-          <p>{moment(createdAt).format('YYYY-MM-DD HH:mm')}</p>
-        </div>
-        <div className={styles.listContentItem}>
-          <Progress percent={percent} status={status} strokeWidth={6} style={{ width: 180 }} />
-        </div>
-      </div>
-    );
-
-    const menu = (
-      <Menu>
-        <Menu.Item>
-          <a>编辑</a>
-        </Menu.Item>
-        <Menu.Item>
-          <a>删除</a>
-        </Menu.Item>
-      </Menu>
-    );
-
-    const MoreBtn = () => (
-      <Dropdown overlay={menu}>
-        <a>
-          更多 <Icon type="down" />
-        </a>
-      </Dropdown>
-    );
-
-    return (
-      <PageHeaderLayout>
-        <div className={styles.standardList}>
-          <Card bordered={false}>
-            <Row>
-              <Col sm={8} xs={24}>
-                <Info title="我的待办" value="8个任务" bordered />
-              </Col>
-              <Col sm={8} xs={24}>
-                <Info title="本周任务平均处理时间" value="32分钟" bordered />
-              </Col>
-              <Col sm={8} xs={24}>
-                <Info title="本周完成任务数" value="24个任务" />
-              </Col>
-            </Row>
-          </Card>
-
-          <Card
-            className={styles.listCard}
-            bordered={false}
-            title="标准列表"
-            style={{ marginTop: 24 }}
-            bodyStyle={{ padding: '0 32px 40px 32px' }}
-            extra={extraContent}
-          >
-            <Button type="dashed" style={{ width: '100%', marginBottom: 8 }} icon="plus">
-              添加
-            </Button>
-            <List
-              size="large"
-              rowKey="id"
-              loading={loading}
-              pagination={paginationProps}
-              dataSource={list}
-              renderItem={item => (
-                <List.Item
-                  actions={[<a>编辑</a>, <MoreBtn />]}
-                >
-                  <List.Item.Meta
-                    avatar={<Avatar src={item.logo} shape="square" size="large" />}
-                    title={<a href={item.href}>{item.title}</a>}
-                    description={item.subDescription}
-                  />
-                  <ListContent data={item} />
-                </List.Item>
-              )}
-            />
-          </Card>
-        </div>
-      </PageHeaderLayout>
-    );
-  }
-}

+ 0 - 176
src/routes/List/BasicList.less

@@ -1,176 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.standardList {
-  :global {
-    .ant-card-head {
-      border-bottom: none;
-    }
-    .ant-card-head-title {
-      line-height: 32px;
-      padding: 24px 0;
-    }
-    .ant-card-extra {
-      padding: 24px 0;
-    }
-    .ant-list-pagination {
-      text-align: right;
-      margin-top: 24px;
-    }
-    .ant-avatar-lg {
-      width: 48px;
-      height: 48px;
-      line-height: 48px;
-    }
-  }
-  .headerInfo {
-    position: relative;
-    text-align: center;
-    & > span {
-      color: @text-color-secondary;
-      display: inline-block;
-      font-size: @font-size-base;
-      line-height: 22px;
-      margin-bottom: 4px;
-    }
-    & > p {
-      color: @heading-color;
-      font-size: 24px;
-      line-height: 32px;
-      margin: 0;
-    }
-    & > em {
-      background-color: @border-color-split;
-      position: absolute;
-      height: 56px;
-      width: 1px;
-      top: 0;
-      right: 0;
-    }
-  }
-  .listContent {
-    font-size: 0;
-    .listContentItem {
-      color: @text-color-secondary;
-      display: inline-block;
-      vertical-align: middle;
-      font-size: @font-size-base;
-      margin-left: 40px;
-      > span {
-        line-height: 20px;
-      }
-      > p {
-        margin-top: 4px;
-        margin-bottom: 0;
-        line-height: 22px;
-      }
-    }
-  }
-  .extraContentSearch {
-    margin-left: 16px;
-    width: 272px;
-  }
-}
-
-@media screen and (max-width: @screen-xs) {
-  .standardList {
-    :global {
-      .ant-list-item-content {
-        display: block;
-        flex: none;
-        width: 100%;
-      }
-      .ant-list-item-action {
-        margin-left: 0;
-      }
-    }
-    .listContent {
-      margin-left: 0;
-      & > div {
-        margin-left: 0;
-      }
-    }
-    .listCard {
-      :global {
-        .ant-card-head-title {
-          overflow: visible;
-        }
-      }
-    }
-  }
-}
-
-@media screen and (max-width: @screen-sm) {
-  .standardList {
-    .extraContentSearch {
-      margin-left: 0;
-      width: 100%;
-    }
-    .headerInfo {
-      margin-bottom: 16px;
-      & > em {
-        display: none;
-      }
-    }
-  }
-}
-
-@media screen and (max-width: @screen-md) {
-  .standardList {
-    .listContent {
-      & > div {
-        display: block;
-      }
-      & > div:last-child {
-        top: 0;
-        width: 100%;
-      }
-    }
-  }
-  .listCard {
-    :global {
-      .ant-radio-group {
-        display: block;
-        margin-bottom: 8px;
-      }
-    }
-  }
-}
-
-@media screen and (max-width: @screen-lg) and (min-width: @screen-md) {
-  .standardList {
-    .listContent {
-      & > div {
-        display: block;
-      }
-      & > div:last-child {
-        top: 0;
-        width: 100%;
-      }
-    }
-  }
-}
-
-@media screen and (max-width: @screen-xl) {
-  .standardList {
-    .listContent {
-      & > div {
-        margin-left: 24px;
-      }
-      & > div:last-child {
-        top: 0;
-      }
-    }
-  }
-}
-
-@media screen and (max-width: 1400px) {
-  .standardList {
-    .listContent {
-      text-align: right;
-      & > div:last-child {
-        top: 0;
-      }
-    }
-  }
-}

+ 0 - 90
src/routes/List/CardList.js

@@ -1,90 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'dva';
-import { Card, Button, Icon, List } from 'antd';
-
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import Ellipsis from '../../components/Ellipsis';
-
-import styles from './CardList.less';
-
-@connect(({ list, loading }) => ({
-  list,
-  loading: loading.models.list,
-}))
-export default class CardList extends PureComponent {
-  componentDidMount() {
-    this.props.dispatch({
-      type: 'list/fetch',
-      payload: {
-        count: 8,
-      },
-    });
-  }
-
-  render() {
-    const { list: { list }, loading } = this.props;
-
-    const content = (
-      <div className={styles.pageHeaderContent}>
-        <p>
-          段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,
-          提供跨越设计与开发的体验解决方案。
-        </p>
-        <div className={styles.contentLink}>
-          <a>
-            <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg" /> 快速开始
-          </a>
-          <a>
-            <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg" /> 产品简介
-          </a>
-          <a>
-            <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg" /> 产品文档
-          </a>
-        </div>
-      </div>
-    );
-
-    const extraContent = (
-      <div className={styles.extraImg}>
-        <img alt="这是一个标题" src="https://gw.alipayobjects.com/zos/rmsportal/RzwpdLnhmvDJToTdfDPe.png" />
-      </div>
-    );
-
-    return (
-      <PageHeaderLayout
-        title="卡片列表"
-        content={content}
-        extraContent={extraContent}
-      >
-        <div className={styles.cardList}>
-          <List
-            rowKey="id"
-            loading={loading}
-            grid={{ gutter: 24, lg: 3, md: 2, sm: 1, xs: 1 }}
-            dataSource={['', ...list]}
-            renderItem={item => (item ? (
-              <List.Item key={item.id}>
-                <Card hoverable className={styles.card} actions={[<a>操作一</a>, <a>操作二</a>]}>
-                  <Card.Meta
-                    avatar={<img alt="" className={styles.cardAvatar} src={item.avatar} />}
-                    title={<a href="#">{item.title}</a>}
-                    description={(
-                      <Ellipsis className={styles.item} lines={3}>{item.description}</Ellipsis>
-                    )}
-                  />
-                </Card>
-              </List.Item>
-              ) : (
-                <List.Item>
-                  <Button type="dashed" className={styles.newButton}>
-                    <Icon type="plus" /> 新增产品
-                  </Button>
-                </List.Item>
-              )
-            )}
-          />
-        </div>
-      </PageHeaderLayout>
-    );
-  }
-}

+ 0 - 111
src/routes/List/CardList.less

@@ -1,111 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.cardList {
-  margin-bottom: -24px;
-
-  .card {
-    :global {
-      .ant-card-meta-title {
-        margin-bottom: 12px;
-        & > a {
-          color: @heading-color;
-        }
-      }
-      .ant-card-actions {
-        background: #f7f9fa;
-      }
-      .ant-card-body:hover {
-        .ant-card-meta-title > a {
-          color: @primary-color;
-        }
-      }
-    }
-  }
-  .item {
-    height: 64px;
-  }
-
-  :global {
-    .ant-list .ant-list-item-content-single {
-      max-width: 100%;
-    }
-  }
-}
-
-.extraImg {
-  margin-top: -60px;
-  text-align: center;
-  width: 195px;
-  img {
-    width: 100%;
-  }
-}
-
-.newButton {
-  background-color: #fff;
-  border-color: @border-color-base;
-  border-radius: @border-radius-sm;
-  color: @text-color-secondary;
-  width: 100%;
-  height: 188px;
-}
-
-.cardAvatar {
-  width: 48px;
-  height: 48px;
-  border-radius: 48px;
-}
-
-.cardDescription {
-  .textOverflowMulti();
-}
-
-.pageHeaderContent {
-  position: relative;
-}
-
-.contentLink {
-  margin-top: 16px;
-  a {
-    margin-right: 32px;
-    img {
-      width: 24px;
-    }
-  }
-  img {
-    vertical-align: middle;
-    margin-right: 8px;
-  }
-}
-
-@media screen and (max-width: @screen-lg) {
-  .contentLink {
-    a {
-      margin-right: 16px;
-    }
-  }
-}
-@media screen and (max-width: @screen-md) {
-  .extraImg {
-    display: none;
-  }
-}
-
-@media screen and (max-width: @screen-sm) {
-  .pageHeaderContent {
-    padding-bottom: 30px;
-  }
-  .contentLink {
-    position: absolute;
-    left: 0;
-    bottom: -4px;
-    width: 1000px;
-    a {
-      margin-right: 16px;
-    }
-    img {
-      margin-right: 4px;
-    }
-  }
-}

+ 0 - 79
src/routes/List/List.js

@@ -1,79 +0,0 @@
-import React, { Component } from 'react';
-import { routerRedux, Route, Switch } from 'dva/router';
-import { connect } from 'dva';
-import { Input } from 'antd';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import { getRoutes } from '../../utils/utils';
-
-@connect()
-export default class SearchList extends Component {
-  handleTabChange = (key) => {
-    const { dispatch, match } = this.props;
-    switch (key) {
-      case 'articles':
-        dispatch(routerRedux.push(`${match.url}/articles`));
-        break;
-      case 'applications':
-        dispatch(routerRedux.push(`${match.url}/applications`));
-        break;
-      case 'projects':
-        dispatch(routerRedux.push(`${match.url}/projects`));
-        break;
-      default:
-        break;
-    }
-  }
-
-  render() {
-    const tabList = [{
-      key: 'articles',
-      tab: '文章',
-    }, {
-      key: 'applications',
-      tab: '应用',
-    }, {
-      key: 'projects',
-      tab: '项目',
-    }];
-
-    const mainSearch = (
-      <div style={{ textAlign: 'center' }}>
-        <Input.Search
-          placeholder="请输入"
-          enterButton="搜索"
-          size="large"
-          onSearch={this.handleFormSubmit}
-          style={{ width: 522 }}
-        />
-      </div>
-    );
-
-    const { match, routerData, location } = this.props;
-    const routes = getRoutes(match.path, routerData);
-
-    return (
-      <PageHeaderLayout
-        title="搜索列表"
-        content={mainSearch}
-        tabList={tabList}
-        tabActiveKey={location.pathname.replace(`${match.path}/`, '')}
-        onTabChange={this.handleTabChange}
-      >
-        <Switch>
-          {
-            routes.map(item =>
-              (
-                <Route
-                  key={item.key}
-                  path={item.path}
-                  component={item.component}
-                  exact={item.exact}
-                />
-              )
-            )
-          }
-        </Switch>
-      </PageHeaderLayout>
-    );
-  }
-}

+ 0 - 173
src/routes/List/Projects.js

@@ -1,173 +0,0 @@
-import React, { PureComponent } from 'react';
-import moment from 'moment';
-import { connect } from 'dva';
-import { Row, Col, Form, Card, Select, List } from 'antd';
-
-import StandardFormRow from '../../components/StandardFormRow';
-import TagSelect from '../../components/TagSelect';
-import AvatarList from '../../components/AvatarList';
-import Ellipsis from '../../components/Ellipsis';
-
-import styles from './Projects.less';
-
-const { Option } = Select;
-const FormItem = Form.Item;
-
-/* eslint react/no-array-index-key: 0 */
-@Form.create()
-@connect(({ list, loading }) => ({
-  list,
-  loading: loading.models.list,
-}))
-export default class CoverCardList extends PureComponent {
-  componentDidMount() {
-    this.props.dispatch({
-      type: 'list/fetch',
-      payload: {
-        count: 8,
-      },
-    });
-  }
-
-  handleFormSubmit = () => {
-    const { form, dispatch } = this.props;
-    // setTimeout 用于保证获取表单值是在所有表单字段更新完毕的时候
-    setTimeout(() => {
-      form.validateFields((err) => {
-        if (!err) {
-          // eslint-disable-next-line
-          dispatch({
-            type: 'list/fetch',
-            payload: {
-              count: 8,
-            },
-          });
-        }
-      });
-    }, 0);
-  }
-
-  render() {
-    const { list: { list = [] }, loading, form } = this.props;
-    const { getFieldDecorator } = form;
-
-    const cardList = list ? (
-      <List
-        rowKey="id"
-        loading={loading}
-        grid={{ gutter: 24, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}
-        dataSource={list}
-        renderItem={item => (
-          <List.Item>
-            <Card
-              className={styles.card}
-              hoverable
-              cover={<img alt={item.title} src={item.cover} height={154} />}
-            >
-              <Card.Meta
-                title={<a href="#">{item.title}</a>}
-                description={<Ellipsis lines={2}>{item.subDescription}</Ellipsis>}
-              />
-              <div className={styles.cardItemContent}>
-                <span>{moment(item.updatedAt).fromNow()}</span>
-                <div className={styles.avatarList}>
-                  <AvatarList size="mini">
-                    {
-                      item.members.map((member, i) => (
-                        <AvatarList.Item
-                          key={`${item.id}-avatar-${i}`}
-                          src={member.avatar}
-                          tips={member.name}
-                        />
-                      ))
-                    }
-                  </AvatarList>
-                </div>
-              </div>
-            </Card>
-          </List.Item>
-        )}
-      />
-    ) : null;
-
-    const formItemLayout = {
-      wrapperCol: {
-        xs: { span: 24 },
-        sm: { span: 16 },
-      },
-    };
-
-    return (
-      <div className={styles.coverCardList}>
-        <Card bordered={false}>
-          <Form layout="inline">
-            <StandardFormRow title="所属类目" block style={{ paddingBottom: 11 }}>
-              <FormItem>
-                {getFieldDecorator('category')(
-                  <TagSelect onChange={this.handleFormSubmit} expandable>
-                    <TagSelect.Option value="cat1">类目一</TagSelect.Option>
-                    <TagSelect.Option value="cat2">类目二</TagSelect.Option>
-                    <TagSelect.Option value="cat3">类目三</TagSelect.Option>
-                    <TagSelect.Option value="cat4">类目四</TagSelect.Option>
-                    <TagSelect.Option value="cat5">类目五</TagSelect.Option>
-                    <TagSelect.Option value="cat6">类目六</TagSelect.Option>
-                    <TagSelect.Option value="cat7">类目七</TagSelect.Option>
-                    <TagSelect.Option value="cat8">类目八</TagSelect.Option>
-                    <TagSelect.Option value="cat9">类目九</TagSelect.Option>
-                    <TagSelect.Option value="cat10">类目十</TagSelect.Option>
-                    <TagSelect.Option value="cat11">类目十一</TagSelect.Option>
-                    <TagSelect.Option value="cat12">类目十二</TagSelect.Option>
-                  </TagSelect>
-                )}
-              </FormItem>
-            </StandardFormRow>
-            <StandardFormRow
-              title="其它选项"
-              grid
-              last
-            >
-              <Row gutter={16}>
-                <Col lg={8} md={10} sm={10} xs={24}>
-                  <FormItem
-                    {...formItemLayout}
-                    label="作者"
-                  >
-                    {getFieldDecorator('author', {})(
-                      <Select
-                        onChange={this.handleFormSubmit}
-                        placeholder="不限"
-                        style={{ maxWidth: 200, width: '100%' }}
-                      >
-                        <Option value="lisa">王昭君</Option>
-                      </Select>
-                    )}
-                  </FormItem>
-                </Col>
-                <Col lg={8} md={10} sm={10} xs={24}>
-                  <FormItem
-                    {...formItemLayout}
-                    label="好评度"
-                  >
-                    {getFieldDecorator('rate', {})(
-                      <Select
-                        onChange={this.handleFormSubmit}
-                        placeholder="不限"
-                        style={{ maxWidth: 200, width: '100%' }}
-                      >
-                        <Option value="good">优秀</Option>
-                        <Option value="normal">普通</Option>
-                      </Select>
-                    )}
-                  </FormItem>
-                </Col>
-              </Row>
-            </StandardFormRow>
-          </Form>
-        </Card>
-        <div className={styles.cardList}>
-          {cardList}
-        </div>
-      </div>
-    );
-  }
-}

+ 0 - 55
src/routes/List/Projects.less

@@ -1,55 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.coverCardList {
-  margin-bottom: -24px;
-
-  .card {
-    :global {
-      .ant-card-meta-title {
-        margin-bottom: 4px;
-        & > a {
-          color: @heading-color;
-        }
-      }
-      .ant-card-meta-description {
-        height: 44px;
-        line-height: 22px;
-        overflow: hidden;
-      }
-    }
-
-    &:hover {
-      :global {
-        .ant-card-meta-title > a {
-          color: @primary-color;
-        }
-      }
-    }
-  }
-
-  .cardItemContent {
-    display: flex;
-    margin-top: 16px;
-    margin-bottom: -4px;
-    line-height: 20px;
-    height: 20px;
-    & > span {
-      color: @text-color-secondary;
-      flex: 1;
-      font-size: 12px;
-    }
-    .avatarList {
-      flex: 0 1 auto;
-    }
-  }
-  .cardList {
-    margin-top: 24px;
-  }
-
-  :global {
-    .ant-list .ant-list-item-content-single {
-      max-width: 100%;
-    }
-  }
-}

+ 0 - 415
src/routes/List/TableList.js

@@ -1,415 +0,0 @@
-import React, { PureComponent, Fragment } from 'react';
-import { connect } from 'dva';
-import moment from 'moment';
-import { Row, Col, Card, Form, Input, Select, Icon, Button, Dropdown, Menu, InputNumber, DatePicker, Modal, message, Badge, Divider } from 'antd';
-import StandardTable from '../../components/StandardTable';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-
-import styles from './TableList.less';
-
-const FormItem = Form.Item;
-const { Option } = Select;
-const getValue = obj => Object.keys(obj).map(key => obj[key]).join(',');
-const statusMap = ['default', 'processing', 'success', 'error'];
-const status = ['关闭', '运行中', '已上线', '异常'];
-const columns = [
-  {
-    title: '规则编号',
-    dataIndex: 'no',
-  },
-  {
-    title: '描述',
-    dataIndex: 'description',
-  },
-  {
-    title: '服务调用次数',
-    dataIndex: 'callNo',
-    sorter: true,
-    align: 'right',
-    render: val => `${val} 万`,
-    // mark to display a total number
-    needTotal: true,
-  },
-  {
-    title: '状态',
-    dataIndex: 'status',
-    filters: [
-      {
-        text: status[0],
-        value: 0,
-      },
-      {
-        text: status[1],
-        value: 1,
-      },
-      {
-        text: status[2],
-        value: 2,
-      },
-      {
-        text: status[3],
-        value: 3,
-      },
-    ],
-    render(val) {
-      return <Badge status={statusMap[val]} text={status[val]} />;
-    },
-  },
-  {
-    title: '更新时间',
-    dataIndex: 'updatedAt',
-    sorter: true,
-    render: val => <span>{moment(val).format('YYYY-MM-DD HH:mm:ss')}</span>,
-  },
-  {
-    title: '操作',
-    render: () => (
-      <Fragment>
-        <a href="">配置</a>
-        <Divider type="vertical" />
-        <a href="">订阅警报</a>
-      </Fragment>
-    ),
-  },
-];
-
-const CreateForm = Form.create()((props) => {
-  const { modalVisible, form, handleAdd, handleModalVisible } = props;
-  const okHandle = () => {
-    form.validateFields((err, fieldsValue) => {
-      if (err) return;
-      form.resetFields();
-      handleAdd(fieldsValue);
-    });
-  };
-  return (
-    <Modal
-      title="新建规则"
-      visible={modalVisible}
-      onOk={okHandle}
-      onCancel={() => handleModalVisible()}
-    >
-      <FormItem
-        labelCol={{ span: 5 }}
-        wrapperCol={{ span: 15 }}
-        label="描述"
-      >
-        {form.getFieldDecorator('desc', {
-          rules: [{ required: true, message: 'Please input some description...' }],
-        })(
-          <Input placeholder="请输入" />
-        )}
-      </FormItem>
-    </Modal>
-  );
-});
-
-@connect(({ rule, loading }) => ({
-  rule,
-  loading: loading.models.rule,
-}))
-@Form.create()
-export default class TableList extends PureComponent {
-  state = {
-    modalVisible: false,
-    expandForm: false,
-    selectedRows: [],
-    formValues: {},
-  };
-
-  componentDidMount() {
-    const { dispatch } = this.props;
-    dispatch({
-      type: 'rule/fetch',
-    });
-  }
-
-  handleStandardTableChange = (pagination, filtersArg, sorter) => {
-    const { dispatch } = this.props;
-    const { formValues } = this.state;
-
-    const filters = Object.keys(filtersArg).reduce((obj, key) => {
-      const newObj = { ...obj };
-      newObj[key] = getValue(filtersArg[key]);
-      return newObj;
-    }, {});
-
-    const params = {
-      currentPage: pagination.current,
-      pageSize: pagination.pageSize,
-      ...formValues,
-      ...filters,
-    };
-    if (sorter.field) {
-      params.sorter = `${sorter.field}_${sorter.order}`;
-    }
-
-    dispatch({
-      type: 'rule/fetch',
-      payload: params,
-    });
-  }
-
-  handleFormReset = () => {
-    const { form, dispatch } = this.props;
-    form.resetFields();
-    this.setState({
-      formValues: {},
-    });
-    dispatch({
-      type: 'rule/fetch',
-      payload: {},
-    });
-  }
-
-  toggleForm = () => {
-    this.setState({
-      expandForm: !this.state.expandForm,
-    });
-  }
-
-  handleMenuClick = (e) => {
-    const { dispatch } = this.props;
-    const { selectedRows } = this.state;
-
-    if (!selectedRows) return;
-
-    switch (e.key) {
-      case 'remove':
-        dispatch({
-          type: 'rule/remove',
-          payload: {
-            no: selectedRows.map(row => row.no).join(','),
-          },
-          callback: () => {
-            this.setState({
-              selectedRows: [],
-            });
-          },
-        });
-        break;
-      default:
-        break;
-    }
-  }
-
-  handleSelectRows = (rows) => {
-    this.setState({
-      selectedRows: rows,
-    });
-  }
-
-  handleSearch = (e) => {
-    e.preventDefault();
-
-    const { dispatch, form } = this.props;
-
-    form.validateFields((err, fieldsValue) => {
-      if (err) return;
-
-      const values = {
-        ...fieldsValue,
-        updatedAt: fieldsValue.updatedAt && fieldsValue.updatedAt.valueOf(),
-      };
-
-      this.setState({
-        formValues: values,
-      });
-
-      dispatch({
-        type: 'rule/fetch',
-        payload: values,
-      });
-    });
-  }
-
-  handleModalVisible = (flag) => {
-    this.setState({
-      modalVisible: !!flag,
-    });
-  }
-
-  handleAdd = (fields) => {
-    this.props.dispatch({
-      type: 'rule/add',
-      payload: {
-        description: fields.desc,
-      },
-    });
-
-    message.success('添加成功');
-    this.setState({
-      modalVisible: false,
-    });
-  }
-
-  renderSimpleForm() {
-    const { getFieldDecorator } = this.props.form;
-    return (
-      <Form onSubmit={this.handleSearch} layout="inline">
-        <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
-          <Col md={8} sm={24}>
-            <FormItem label="规则编号">
-              {getFieldDecorator('no')(
-                <Input placeholder="请输入" />
-              )}
-            </FormItem>
-          </Col>
-          <Col md={8} sm={24}>
-            <FormItem label="使用状态">
-              {getFieldDecorator('status')(
-                <Select placeholder="请选择" style={{ width: '100%' }}>
-                  <Option value="0">关闭</Option>
-                  <Option value="1">运行中</Option>
-                </Select>
-              )}
-            </FormItem>
-          </Col>
-          <Col md={8} sm={24}>
-            <span className={styles.submitButtons}>
-              <Button type="primary" htmlType="submit">查询</Button>
-              <Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>重置</Button>
-              <a style={{ marginLeft: 8 }} onClick={this.toggleForm}>
-                展开 <Icon type="down" />
-              </a>
-            </span>
-          </Col>
-        </Row>
-      </Form>
-    );
-  }
-
-  renderAdvancedForm() {
-    const { getFieldDecorator } = this.props.form;
-    return (
-      <Form onSubmit={this.handleSearch} layout="inline">
-        <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
-          <Col md={8} sm={24}>
-            <FormItem label="规则编号">
-              {getFieldDecorator('no')(
-                <Input placeholder="请输入" />
-              )}
-            </FormItem>
-          </Col>
-          <Col md={8} sm={24}>
-            <FormItem label="使用状态">
-              {getFieldDecorator('status')(
-                <Select placeholder="请选择" style={{ width: '100%' }}>
-                  <Option value="0">关闭</Option>
-                  <Option value="1">运行中</Option>
-                </Select>
-              )}
-            </FormItem>
-          </Col>
-          <Col md={8} sm={24}>
-            <FormItem label="调用次数">
-              {getFieldDecorator('number')(
-                <InputNumber style={{ width: '100%' }} />
-              )}
-            </FormItem>
-          </Col>
-        </Row>
-        <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
-          <Col md={8} sm={24}>
-            <FormItem label="更新日期">
-              {getFieldDecorator('date')(
-                <DatePicker style={{ width: '100%' }} placeholder="请输入更新日期" />
-              )}
-            </FormItem>
-          </Col>
-          <Col md={8} sm={24}>
-            <FormItem label="使用状态">
-              {getFieldDecorator('status3')(
-                <Select placeholder="请选择" style={{ width: '100%' }}>
-                  <Option value="0">关闭</Option>
-                  <Option value="1">运行中</Option>
-                </Select>
-              )}
-            </FormItem>
-          </Col>
-          <Col md={8} sm={24}>
-            <FormItem label="使用状态">
-              {getFieldDecorator('status4')(
-                <Select placeholder="请选择" style={{ width: '100%' }}>
-                  <Option value="0">关闭</Option>
-                  <Option value="1">运行中</Option>
-                </Select>
-              )}
-            </FormItem>
-          </Col>
-        </Row>
-        <div style={{ overflow: 'hidden' }}>
-          <span style={{ float: 'right', marginBottom: 24 }}>
-            <Button type="primary" htmlType="submit">查询</Button>
-            <Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>重置</Button>
-            <a style={{ marginLeft: 8 }} onClick={this.toggleForm}>
-              收起 <Icon type="up" />
-            </a>
-          </span>
-        </div>
-      </Form>
-    );
-  }
-
-  renderForm() {
-    return this.state.expandForm ? this.renderAdvancedForm() : this.renderSimpleForm();
-  }
-
-  render() {
-    const { rule: { data }, loading } = this.props;
-    const { selectedRows, modalVisible } = this.state;
-
-    const menu = (
-      <Menu onClick={this.handleMenuClick} selectedKeys={[]}>
-        <Menu.Item key="remove">删除</Menu.Item>
-        <Menu.Item key="approval">批量审批</Menu.Item>
-      </Menu>
-    );
-
-    const parentMethods = {
-      handleAdd: this.handleAdd,
-      handleModalVisible: this.handleModalVisible,
-    };
-
-    return (
-      <PageHeaderLayout title="查询表格">
-        <Card bordered={false}>
-          <div className={styles.tableList}>
-            <div className={styles.tableListForm}>
-              {this.renderForm()}
-            </div>
-            <div className={styles.tableListOperator}>
-              <Button icon="plus" type="primary" onClick={() => this.handleModalVisible(true)}>
-                新建
-              </Button>
-              {
-                selectedRows.length > 0 && (
-                  <span>
-                    <Button>批量操作</Button>
-                    <Dropdown overlay={menu}>
-                      <Button>
-                        更多操作 <Icon type="down" />
-                      </Button>
-                    </Dropdown>
-                  </span>
-                )
-              }
-            </div>
-            <StandardTable
-              selectedRows={selectedRows}
-              loading={loading}
-              data={data}
-              columns={columns}
-              onSelectRow={this.handleSelectRows}
-              onChange={this.handleStandardTableChange}
-            />
-          </div>
-        </Card>
-        <CreateForm
-          {...parentMethods}
-          modalVisible={modalVisible}
-        />
-      </PageHeaderLayout>
-    );
-  }
-}

+ 0 - 48
src/routes/List/TableList.less

@@ -1,48 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.tableList {
-  .tableListOperator {
-    margin-bottom: 16px;
-    button {
-      margin-right: 8px;
-    }
-  }
-}
-
-.tableListForm {
-  :global {
-    .ant-form-item {
-      margin-bottom: 24px;
-      margin-right: 0;
-      display: flex;
-      > .ant-form-item-label {
-        width: auto;
-        line-height: 32px;
-        padding-right: 8px;
-      }
-      .ant-form-item-control {
-        line-height: 32px;
-      }
-    }
-    .ant-form-item-control-wrapper {
-      flex: 1;
-    }
-  }
-  .submitButtons {
-    white-space: nowrap;
-    margin-bottom: 24px;
-  }
-}
-
-@media screen and (max-width: @screen-lg) {
-  .tableListForm :global(.ant-form-item) {
-    margin-right: 24px;
-  }
-}
-
-@media screen and (max-width: @screen-md) {
-  .tableListForm :global(.ant-form-item) {
-    margin-right: 8px;
-  }
-}

+ 154 - 161
src/routes/Merchant/MerchantCreate.js

@@ -3,19 +3,21 @@ import pathToRegexp from 'path-to-regexp';
 import { Card, Form, Input, Button, Radio, Switch } from 'antd';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import FooterToolbar from '../../components/FooterToolbar';
 import {
   DOMAIN_CP,
   DOMAIN_LJ,
   DOMAIN_PJ,
-  STATUS_NORMAL,
-  STATUS_DELETE,
 } from '../../utils/config';
+import { statusToBool, boolToStatus } from '../../utils/utils';
 import styles from './MerchantCreate.less';
 
 const fieldLabels = {
   type: '商户类型',
   name: '商户名称',
   code: '商户编号',
+  simple: '商户简称',
   contactName: '商户联系人',
   mobile: '联系电话',
   depositBank: '开户银行',
@@ -52,12 +54,6 @@ const formItemLayout = {
     md: { span: 12 },
   },
 };
-const submitFormLayout = {
-  wrapperCol: {
-    xs: { span: 24, offset: 0 },
-    sm: { span: 12, offset: 6 },
-  },
-};
 
 @Form.create()
 @connect(({ loading, merchant }) => ({
@@ -109,11 +105,7 @@ export default class MerchantCreatePage extends PureComponent {
       if (!err) {
         const { status, ...rest } = values;
         const matchId = this.isEdit();
-        if (status) {
-          rest.status = STATUS_NORMAL;
-        } else {
-          rest.status = STATUS_DELETE;
-        }
+        rest.status = boolToStatus(status);
         if (matchId) {
           rest.id = matchId;
           this.props.dispatch({
@@ -137,160 +129,161 @@ export default class MerchantCreatePage extends PureComponent {
     const { getFieldDecorator } = form;
     const { currentItem } = merchant;
 
-    const isChecked = (status) => {
-      if (status === STATUS_NORMAL) {
-        return true;
-      } else if (status === STATUS_DELETE) {
-        return false;
-      } else {
-        return true;
-      }
-    };
+    // 是否可编辑
+    const isEditable = this.isEdit() ? true : false;
 
     return (
-      <Card>
-        <Form onSubmit={this.handlePageSubmit}>
-          <Form.Item label={fieldLabels.type} {...formItemLayout}>
-            {getFieldDecorator('domain', {
-              rules: [{ required: true, message: '请选择商户类型!' }],
-              initialValue: currentItem.domain || DOMAIN_PJ,
-            })(
-              <Radio.Group className={styles.radio}>
-                {
-                  domains.map(item =>
-                    (
-                      <Radio.Button
-                        key={item.value}
-                        value={item.value}
-                      >{item.title}
-                      </Radio.Button>
+      <PageHeaderLayout>
+        <Card>
+          <Form>
+            <Form.Item label={fieldLabels.type} {...formItemLayout}>
+              {getFieldDecorator('domain', {
+                rules: [{ required: true, message: '请选择商户类型!' }],
+                initialValue: currentItem.domain || DOMAIN_PJ,
+              })(
+                <Radio.Group disabled={isEditable} className={styles.radio}>
+                  {
+                    domains.map(item =>
+                      (
+                        <Radio.Button
+                          key={item.value}
+                          value={item.value}
+                        >{item.title}
+                        </Radio.Button>
+                      )
                     )
-                  )
-                }
-              </Radio.Group>
-            )}
-          </Form.Item>
-          <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
-            {getFieldDecorator('code', {
-              rules: [
-                {
-                  required: true, message: '请给商户编号!',
-                }, {
-                  pattern: /^[0-9]{4,6}$/g, message: '请输入4-6位数字编号!',
-                },
-              ],
-              initialValue: currentItem.code,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item hasFeedback label={fieldLabels.name} {...formItemLayout}>
-            {getFieldDecorator('name', {
-              rules: [{ required: true, message: '请给商户命名!' }],
-              initialValue: currentItem.name,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item hasFeedback label={fieldLabels.contactName} {...formItemLayout}>
-            {getFieldDecorator('contactName', {
-              rules: [{ required: true, message: '请填写商户联系人!' }],
-              initialValue: currentItem.contactName,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item hasFeedback label={fieldLabels.mobile} {...formItemLayout}>
-            {getFieldDecorator('mobile', {
-              rules: [
-                {
-                  required: true, message: '请填写联系电话!',
+                  }
+                </Radio.Group>
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [
+                  {
+                    required: true, message: '请填写商户编号!',
+                  }, {
+                    pattern: /^[0-9]{4,6}$/g, message: '请输入4-6位数字编号!',
+                  },
+                ],
+                initialValue: currentItem.code,
+              })(
+                <Input disabled={isEditable} placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.name} {...formItemLayout}>
+              {getFieldDecorator('name', {
+                rules: [{ required: true, message: '请填写商户名称!' }],
+                initialValue: currentItem.name,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.simple} {...formItemLayout}>
+              {getFieldDecorator('simple', {
+                rules: [{ required: true, message: '请填写商户简称!' }],
+                initialValue: currentItem.simple,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.contactName} {...formItemLayout}>
+              {getFieldDecorator('contactName', {
+                rules: [{ required: true, message: '请填写商户联系人!' }],
+                initialValue: currentItem.contactName,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.mobile} {...formItemLayout}>
+              {getFieldDecorator('mobile', {
+                rules: [
+                  {
+                    required: true, message: '请填写联系电话!',
+                  }, {
+                    pattern: /^[1][3,4,5,7,8][0-9]{9}$/g, message: '请输入11位有效手机号!',
+                  }],
+                initialValue: currentItem.mobile,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.depositBank} {...formItemLayout}>
+              {getFieldDecorator('depositBank', {
+                rules: [{
+                  required: true, message: '开户银行不能为空!',
+                }],
+                initialValue: currentItem.depositBank,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.bankAccount} {...formItemLayout}>
+              {getFieldDecorator('bankAccount', {
+                rules: [{
+                  required: true, message: '银行账号不能为空!',
                 }, {
-                  pattern: /^[1][3,4,5,7,8][0-9]{9}$/g, message: '请输入11位有效手机号!',
+                  pattern: /^[0-9]{1,22}$/g, message: '请输入有效的银行账户!',
                 }],
-              initialValue: currentItem.mobile,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item hasFeedback label={fieldLabels.depositBank} {...formItemLayout}>
-            {getFieldDecorator('depositBank', {
-              rules: [{
-                required: true, message: '开户银行不能为空!',
-              }],
-              initialValue: currentItem.depositBank,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item hasFeedback label={fieldLabels.bankAccount} {...formItemLayout}>
-            {getFieldDecorator('bankAccount', {
-              rules: [{
-                required: true, message: '银行账号不能为空!',
-              }, {
-                pattern: /^[0-9]{1,22}$/g, message: '请输入有效的银行账户!',
-              }],
-              initialValue: currentItem.bankAccount,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item label={fieldLabels.licenseId} {...formItemLayout}>
-            {getFieldDecorator('licenseId', {
-              initialValue: currentItem.licenseId,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item label={fieldLabels.taxNumber} {...formItemLayout}>
-            {getFieldDecorator('taxNumber', {
-              initialValue: currentItem.taxNumber,
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item label={fieldLabels.receiptType} {...formItemLayout}>
-            {getFieldDecorator('receiptType', {
-              initialValue: currentItem.receiptType || 'COMMON',
-            })(
-              <Radio.Group className={styles.radio}>
-                {
-                  receipts.map(item =>
-                    (
-                      <Radio.Button
-                        key={item.value}
-                        value={item.value}
-                      >{item.title}
-                      </Radio.Button>
+                initialValue: currentItem.bankAccount,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.licenseId} {...formItemLayout}>
+              {getFieldDecorator('licenseId', {
+                initialValue: currentItem.licenseId,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.taxNumber} {...formItemLayout}>
+              {getFieldDecorator('taxNumber', {
+                initialValue: currentItem.taxNumber,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.receiptType} {...formItemLayout}>
+              {getFieldDecorator('receiptType', {
+                initialValue: currentItem.receiptType || 'COMMON',
+              })(
+                <Radio.Group className={styles.radio}>
+                  {
+                    receipts.map(item =>
+                      (
+                        <Radio.Button
+                          key={item.value}
+                          value={item.value}
+                        >{item.title}
+                        </Radio.Button>
+                      )
                     )
-                  )
-                }
-              </Radio.Group>
-            )}
-          </Form.Item>
-          <Form.Item label="状态" {...formItemLayout}>
-            {getFieldDecorator('status', {
-              valuePropName: 'checked',
-              initialValue: isChecked(currentItem.status),
-            })(
-              <Switch
-                checkedChildren="启用"
-                unCheckedChildren="不启用"
-              />
-            )}
-          </Form.Item>
-          <Form.Item {...submitFormLayout} style={{ marginTop: 32 }}>
-            <Button onClick={this.handlePageBack}>取消</Button>
-            <Button
-              type="primary"
-              htmlType="submit"
-              loading={submitting}
-              style={{ marginLeft: 8 }}
-            >提交
-            </Button>
-          </Form.Item>
-        </Form>
-      </Card>
+                  }
+                </Radio.Group>
+              )}
+            </Form.Item>
+            <Form.Item label="状态" {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: status ? statusToBool(currentItem.status) : true,
+              })(
+                <Switch
+                  checkedChildren="启用"
+                  unCheckedChildren="不启用"
+                />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button onClick={this.handlePageBack} style={{ marginRight: 10 }}>
+            取消
+          </Button>
+          <Button type="primary" onClick={this.handlePageSubmit} loading={submitting}>
+            提交
+          </Button>
+        </FooterToolbar>
+      </PageHeaderLayout>
     );
   }
 }

+ 105 - 0
src/routes/Merchant/MerchantDeposit.js

@@ -0,0 +1,105 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Form, Input, Button } from 'antd';
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import FooterToolbar from '../../components/FooterToolbar';
+import { toDecimal2 } from '../../utils/utils';
+import styles from './MerchantDeposit.less';
+
+const fieldLabels = {
+  code: '商户编号',
+  name: '商户名称',
+  balance: '账户余额',
+  quantity: '充值金额',
+  note: '备注',
+};
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 6 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@Form.create()
+@connect(({ loading, merchant }) => ({
+  merchant,
+  submitting: loading.models.merchant,
+}))
+export default class MerchantDepositPage extends PureComponent {
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/merchant/list',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        const { note, quantity } = values;
+        const { id } = this.props.location.state.currentItem;
+        this.props.dispatch({
+          type: 'merchant/depositMerchantItem',
+          payload: {
+            note,
+            quantity,
+            merchantId: id,
+          },
+          states: this.props.location.state,
+        });
+      }
+    });
+  }
+  render() {
+    const { location, form, submitting } = this.props;
+    const { currentItem } = location.state;
+    const { getFieldDecorator } = form;
+    return (
+      <PageHeaderLayout>
+        <Card style={{ marginBottom: 70 }}>
+          <Form>
+            <Form.Item label={fieldLabels.name} {...formItemLayout}>
+              <span>{`${currentItem.name}【${currentItem.code}】`}</span>
+            </Form.Item>
+            <Form.Item label={fieldLabels.balance} {...formItemLayout}>
+              <span className={styles.balance}>{toDecimal2(currentItem.balance)}元</span>
+            </Form.Item>
+            <Form.Item label={fieldLabels.quantity} {...formItemLayout}>
+              {getFieldDecorator('quantity', {
+                rules: [
+                  {
+                    required: true, type: 'string', message: '充值金额不能为空!',
+                  }, {
+                    pattern: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/, message: '充值金额格式错误!',
+                  },
+                ],
+              })(
+                <Input placeholder="请输入充值金额" addonAfter="元" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.note} {...formItemLayout}>
+              {getFieldDecorator('note', {
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button onClick={this.handlePageBack} style={{ marginRight: 10 }}>
+            取消
+          </Button>
+          <Button type="primary" onClick={this.handlePageSubmit} loading={submitting}>
+            提交
+          </Button>
+        </FooterToolbar>
+      </PageHeaderLayout>
+    );
+  }
+}

src/routes/Merchant/MerchantDespoit.less → src/routes/Merchant/MerchantDeposit.less


+ 0 - 112
src/routes/Merchant/MerchantDespoit.js

@@ -1,112 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'dva';
-import { routerRedux } from 'dva/router';
-import { Card, Form, Input, Button } from 'antd';
-import { toDecimal2 } from '../../utils/utils';
-import styles from './MerchantDespoit.less';
-
-const fieldLabels = {
-  code: '商户编号',
-  name: '商户名称',
-  balance: '账户余额',
-  quantity: '充值金额',
-  note: '备注',
-};
-const formItemLayout = {
-  labelCol: {
-    xs: { span: 24 },
-    sm: { span: 6 },
-  },
-  wrapperCol: {
-    xs: { span: 24 },
-    sm: { span: 14 },
-    md: { span: 12 },
-  },
-};
-const submitFormLayout = {
-  wrapperCol: {
-    xs: { span: 24, offset: 0 },
-    sm: { span: 12, offset: 6 },
-  },
-};
-
-@Form.create()
-@connect(({ loading, merchant }) => ({
-  merchant,
-  submitting: loading.models.merchant,
-}))
-export default class MerchantDespoitPage extends PureComponent {
-  handlePageBack = () => {
-    this.props.dispatch(routerRedux.push({
-      pathname: '/merchant/list',
-      state: this.props.location.state,
-    }));
-  }
-  handlePageSubmit = (e) => {
-    e.preventDefault();
-    this.props.form.validateFieldsAndScroll((err, values) => {
-      if (!err) {
-        const { note, quantity } = values;
-        const { id } = this.props.location.state.currentItem;
-        this.props.dispatch({
-          type: 'merchant/despoitMerchantItem',
-          payload: {
-            note,
-            quantity,
-            merchantId: id,
-          },
-          states: this.props.location.state,
-        });
-      }
-    });
-  }
-  render() {
-    const { location, form, submitting } = this.props;
-    const { currentItem } = location.state;
-    const { getFieldDecorator } = form;
-    return (
-      <Card>
-        <Form onSubmit={this.handlePageSubmit}>
-          <Form.Item label={fieldLabels.name} {...formItemLayout}>
-            <span>{`${currentItem.name}【${currentItem.code}】`}</span>
-          </Form.Item>
-          <Form.Item label={fieldLabels.balance} {...formItemLayout}>
-            <span className={styles.balance}>{toDecimal2(currentItem.balance)}元</span>
-          </Form.Item>
-          <Form.Item label={fieldLabels.quantity} {...formItemLayout}>
-            {getFieldDecorator('quantity', {
-              rules: [
-                {
-                  required: true, type: 'string', message: '充值金额不能为空!',
-                }, {
-                  pattern: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/, message: '充值金额格式错误!',
-                },
-              ],
-            })(
-              <Input placeholder="请输入充值金额" addonAfter="元" />
-            )}
-          </Form.Item>
-          <Form.Item label={fieldLabels.note} {...formItemLayout}>
-            {getFieldDecorator('note', {
-            })(
-              <Input placeholder="请输入" />
-            )}
-          </Form.Item>
-          <Form.Item {...submitFormLayout} style={{ marginTop: 32 }}>
-            <Button
-              onClick={this.handlePageBack}
-            >取消
-            </Button>
-            <Button
-              type="primary"
-              htmlType="submit"
-              loading={submitting}
-              style={{ marginLeft: 8 }}
-            >提交
-            </Button>
-          </Form.Item>
-        </Form>
-      </Card>
-    );
-  }
-}

+ 25 - 22
src/routes/Merchant/MerchantList.js

@@ -4,6 +4,7 @@ import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
 import { Card, Modal, Button, message } from 'antd';
 import { StandardTableList } from '../../components/RBList';
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
 import Authorized from '../../utils/Authorized';
 import { renderStatus, renderCategory, addRowKey } from '../../utils/utils';
 import styles from './MerchantList.less';
@@ -65,9 +66,9 @@ export default class MerchantListPage extends Component {
       Queryers: params,
     });
   }
-  handleDespoitOperation = (item) => {
+  handleDepositOperation = (item) => {
     this.props.dispatch(routerRedux.push({
-      pathname: `/merchant/despoit/${item.id}`,
+      pathname: `/merchant/deposit/${item.id}`,
       state: { ...this.state, currentItem: item },
     }));
   }
@@ -85,8 +86,8 @@ export default class MerchantListPage extends Component {
           <Authorized authority="root" noMatch={null}>
             <Button
               size="small"
-              className={styles.despoitBtn}
-              onClick={() => this.handleDespoitOperation(item)}
+              className={styles.depositBtn}
+              onClick={() => this.handleDepositOperation(item)}
             >充值
             </Button>
           </Authorized>
@@ -184,24 +185,26 @@ export default class MerchantListPage extends Component {
       width: '18%',
     }];
     return (
-      <Card>
-        <StandardTableList
-          columns={columns}
-          loading={loading}
-          dataSource={addRowKey(list)}
-          header={{
-            basicSearch,
-            onFilterClick: this.handleFilterOperation,
-            onCreateClick: this.handleCreateOperation,
-          }}
-          footer={{
-            pagination,
-            batchActions,
-            onBatchClick: this.handleBatchOperation,
-          }}
-          keepUIState={{ ...this.state.UIParams }}
-        />
-      </Card>
+      <PageHeaderLayout>
+        <Card>
+          <StandardTableList
+            columns={columns}
+            loading={loading}
+            dataSource={addRowKey(list)}
+            header={{
+              basicSearch,
+              onFilterClick: this.handleFilterOperation,
+              onCreateClick: this.handleCreateOperation,
+            }}
+            footer={{
+              pagination,
+              batchActions,
+              onBatchClick: this.handleBatchOperation,
+            }}
+            keepUIState={{ ...this.state.UIParams }}
+          />
+        </Card>
+      </PageHeaderLayout>
     );
   }
 }

+ 1 - 1
src/routes/Merchant/MerchantList.less

@@ -10,7 +10,7 @@
   color: #fff;
   font-weight: 500;
 }
-.despoitBtn {
+.depositBtn {
   margin-right: 10px;
   background: #a0d911;
   color: #fff;

+ 576 - 0
src/routes/Product/Course/CourseCreate.js

@@ -0,0 +1,576 @@
+import React, { Component } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import {
+  Form, Modal, Card, Button, Input, Switch, Select, Row, Col,
+} from 'antd';
+import {
+  renderStatus, statusToBool, boolToStatus, genAbsolutePicUrl,
+} from '../../../utils/utils';
+import { PRODUCT_LESSON } from '../../../utils/config';
+import RBDragSortTable from '../../../components/RBDragSortTable/index';
+import Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar/index';
+import styles from './CourseCreate.less';
+
+const fieldLabels = {
+  code: '课程编号',
+  title: '课程标题',
+  subTitle: '课程副标题',
+  name: '课程全称',
+  breadCrumb: '顶部导航名',
+  merchant: '内容提供商',
+  digest: '课程概要',
+  detail: '课程详情',
+  coverUrl: '课程封面图',
+  bgUrl: '课程背景图',
+  status: '课程状态',
+};
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 3 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@connect(({ loading, product, lesson, resource, merchant }) => ({
+  lesson,
+  product,
+  resource,
+  merchant,
+  lLoading: loading.models.lesson,
+  pLoading: loading.models.product,
+  rLoading: loading.models.resource,
+  submitting: loading.models.product,
+}))
+@Form.create()
+export default class CourseItemCreatePage extends Component {
+  state = {
+    bgSelectorDestroy: true,
+    coverSelectorDestroy: true,
+    lessonSelectorDestroy: true,
+    supportSelectorDestroy: true,
+  };
+  componentWillMount() {
+    const match = pathToRegexp('/product/course/create').exec(this.props.location.pathname);
+    if (match) {
+      this.cleanPageState();
+    }
+  }
+  componentDidMount() {
+    const matchId = this.isEdit();
+    if (matchId) {
+      this.props.dispatch({
+        type: 'product/fetchProductItem',
+        payload: { pid: matchId },
+      });
+    }
+    this.props.dispatch({
+      type: 'merchant/fetchMerchantList',
+      payload: { pageSize: 1000 }, // TODO 以后商户多了需要改写交互样式
+    });
+  }
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/product/course/edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
+  cleanPageState = () => {
+    this.props.dispatch({
+      type: 'product/cleanItemState',
+      payload: {},
+    });
+  }
+  selectorDataFetcher = (name, params) => {
+    switch (name) {
+      case 'cover':
+        this.props.dispatch({
+          type: 'resource/fetchImageList',
+          payload: params,
+        });
+        return;
+      case 'bg':
+        this.props.dispatch({
+          type: 'resource/fetchImageList',
+          payload: params,
+        });
+        return;
+      case 'lesson':
+        this.props.dispatch({
+          type: 'lesson/fetchLessonList',
+          payload: params,
+        });
+        return;
+      case 'support':
+        this.props.dispatch({
+          type: 'product/fetchSupportList',
+          payload: params,
+        });
+    }
+  }
+  currentItemFormatter = (name, rows) => {
+    let payload;
+    switch (name) {
+      case 'cover':
+        payload = { coverUrl: rows[0].path };
+        break;
+      case 'bg':
+        payload = { bgUrl: rows[0].path };
+        break;
+      case 'lesson':
+        payload = { subItemList: rows };
+        break;
+      case 'support':
+        payload = { supportList: rows };
+        break;
+    }
+    return payload;
+  }
+  handleSelectorModalShow = (name) => {
+    this.setState({
+      [`${name}SelectorDestroy`]: false,
+    });
+    this.selectorDataFetcher(name);
+  }
+  handleSelectorChange = (name, params) => {
+    this.selectorDataFetcher(name, params);
+  }
+  handleSelectorFinish = (name, rows) => {
+    this.setState({
+      [`${name}SelectorDestroy`]: true,
+    });
+    const payload = this.currentItemFormatter(name, rows);
+    this.props.dispatch({
+      payload,
+      type: 'product/fixCurrentItem',
+    });
+  }
+  handleDragSortTableChange = (name, rows) => {
+    const payload = this.currentItemFormatter(name, rows);
+    this.props.dispatch({
+      payload,
+      type: 'product/fixCurrentItem',
+    });
+  }
+  handleSelectorCancel = (name) => {
+    this.setState({
+      [`${name}SelectorDestroy`]: true,
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/course',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        // 提取表单字段
+        let { title, subTitle, name, status, ...rest } = values;
+        name = `${title}_${subTitle}`;
+        values = { title, subTitle, name, status: boolToStatus(status), ...rest };
+
+        const { product } = this.props;
+        const { currentItem } = product;
+        const { bgUrl, coverUrl, subItemList, supportList } = currentItem;
+
+        // 构造subList,supportIdList
+        let subList;
+        let supportIdList;
+        if (subItemList) {
+          subList = subItemList.map(
+            item => ({ id: item.id, type: PRODUCT_LESSON })
+          );
+        }
+        if (supportList) {
+          supportIdList = supportList.map(item => item.id);
+        }
+
+        const matchId = this.isEdit();
+        if (matchId) {
+          const params = {
+            bgUrl,
+            coverUrl,
+            id: matchId,
+            subItemList: subList,
+            supportList: supportIdList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'product/updateCourseItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        } else {
+          const params = {
+            bgUrl,
+            coverUrl,
+            subItemList: subList,
+            supportList: supportIdList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'product/createCourseItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        }
+      }
+    });
+  }
+
+  render() {
+    const {
+      form, submitting, rLoading, lLoading, pLoading, lesson, product, resource, merchant,
+    } = this.props;
+    const {
+      lessonSelectorDestroy, supportSelectorDestroy, coverSelectorDestroy, bgSelectorDestroy,
+    } = this.state;
+    const { currentItem } = product;
+    const {
+      code, title, subTitle, name, digest, detail, status, coverUrl, bgUrl, cpId,
+      breadCrumb, subItemList = [], supportList = [],
+    } = currentItem;
+    const { getFieldDecorator } = form;
+
+    const lessonColumns = [{
+      title: '课编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '20%',
+    }, {
+      title: '课名称',
+      dataIndex: 'title',
+      key: 2,
+      width: '30%',
+    }, {
+      title: '状态',
+      dataIndex: 'status',
+      key: 3,
+      render: text => renderStatus(text),
+    }];
+    const supportColumns = [{
+      title: '配套编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '20%',
+    }, {
+      title: '配套名称',
+      dataIndex: 'name',
+      key: 2,
+      width: '30%',
+    }, {
+      title: '配套状态',
+      dataIndex: 'status',
+      key: 3,
+      render: text => renderStatus(text),
+    }];
+
+    const getMerchants = () => {
+      const { list } = merchant;
+      const options = list.map(item => ({
+        text: item.name,
+        key: item.id,
+      }));
+      return options;
+    };
+
+    const getResourceModal = (isCover) => {
+      return (
+        <Modal
+          width={900}
+          footer={null}
+          visible
+          title="图片资源"
+          maskClosable={false}
+          onCancel={() => this.handleSelectorCancel(isCover ? 'cover' : 'bg')}
+        >
+          <Selector
+            multiple={false}
+            loading={rLoading}
+            selectorName="PictureSingle"
+            list={resource.list}
+            pageNo={resource.pageNo}
+            pageSize={resource.pageSize}
+            totalSize={resource.totalSize}
+            onCancel={() => this.handleSelectorCancel(isCover ? 'cover' : 'bg')}
+            onChange={data => this.handleSelectorChange(isCover ? 'cover' : 'bg', data)}
+            onFinish={rows => this.handleSelectorFinish(isCover ? 'cover' : 'bg', rows)}
+          />
+        </Modal>
+      );
+    };
+    const getLessonModal = () => {
+      return (
+        <Modal
+          width={1100}
+          footer={null}
+          visible
+          title="课资源"
+          maskClosable={false}
+          onCancel={() => this.handleSelectorCancel('lesson')}
+        >
+          <Selector
+            multiple
+            loading={lLoading}
+            selectorName="Lesson"
+            list={lesson.list}
+            pageNo={lesson.pageNo}
+            pageSize={lesson.pageSize}
+            totalSize={lesson.totalSize}
+            onCancel={() => this.handleSelectorCancel('lesson')}
+            onChange={data => this.handleSelectorChange('lesson', data)}
+            onFinish={rows => this.handleSelectorFinish('lesson', rows)}
+          />
+        </Modal>
+      );
+    };
+    const getSupportModal = () => {
+      return (
+        <Modal
+          width={1100}
+          footer={null}
+          visible
+          title="配套资源"
+          maskClosable={false}
+          onCancel={() => this.handleSelectorCancel('support')}
+        >
+          <Selector
+            multiple
+            loading={pLoading}
+            selectorName="Support"
+            list={product.list}
+            pageNo={product.pageNo}
+            pageSize={product.pageSize}
+            totalSize={product.totalSize}
+            onCancel={() => this.handleSelectorCancel('support')}
+            onChange={data => this.handleSelectorChange('support', data)}
+            onFinish={rows => this.handleSelectorFinish('support', rows)}
+          />
+        </Modal>
+      );
+    };
+
+    const renderLessonCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={() => this.handleSelectorModalShow('lesson')}>课列表</a>
+          </span>
+        </div>
+      );
+    };
+    const renderSupportCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={() => this.handleSelectorModalShow('support')}>配套列表</a>
+          </span>
+        </div>
+      );
+    };
+    const renderCoverCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={() => this.handleSelectorModalShow('cover')}>更换封面</a>
+          </span>
+        </div>
+      );
+    };
+    const renderbgCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={() => this.handleSelectorModalShow('bg')}>更换背景</a>
+          </span>
+        </div>
+      );
+    };
+    return (
+      <div>
+        {/* 基础信息Card */}
+        <Card title="基础信息" style={{ marginBottom: 16 }}>
+          <Form>
+            <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [
+                  {
+                    required: true, message: '请填写课程编号',
+                  }, {
+                    pattern: /^[a-zA-Z0-9|-]+$/g, message: '编号包含非法字符',
+                  },
+                ],
+                initialValue: code,
+              })(
+                <Input
+                  placeholder="请输入"
+                  disabled={!!this.isEdit()}
+                />
+              )}
+            </Form.Item>
+            {this.isEdit() && (
+            <Form.Item label={fieldLabels.name} {...formItemLayout}>
+              {getFieldDecorator('name', {
+                  initialValue: name,
+                })(
+                  <Input disabled />
+                )}
+            </Form.Item>
+)}
+            <Form.Item hasFeedback label={fieldLabels.title} {...formItemLayout}>
+              {getFieldDecorator('title', {
+                rules: [{ required: true, message: '请填写课程标题' }],
+                initialValue: title,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.subTitle} {...formItemLayout}>
+              {getFieldDecorator('subTitle', {
+                rules: [{ required: true, message: '请填写课程副标题' }],
+                initialValue: subTitle,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.breadCrumb} {...formItemLayout}>
+              {getFieldDecorator('breadCrumb', {
+                rules: [{ required: true, message: '请填写顶部导航名' }],
+                initialValue: breadCrumb,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.merchant} {...formItemLayout}>
+              {getFieldDecorator('cpId', {
+                rules: [{ required: true, message: '请选择供应商' }],
+                initialValue: cpId,
+              })(
+                <Select placeholder="请选择">
+                  {
+                    getMerchants().map(item => (
+                      <Select.Option key={item.key} value={item.key}>
+                        {item.text}
+                      </Select.Option>
+))
+                  }
+                </Select>
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.digest} {...formItemLayout}>
+              {getFieldDecorator('digest', {
+                initialValue: digest,
+              })(
+                <Input.TextArea rows={4} placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.detail} {...formItemLayout}>
+              {getFieldDecorator('detail', {
+                initialValue: detail,
+              })(
+                <Input.TextArea rows={6} placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.status} {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: statusToBool(status),
+              })(
+                <Switch
+                  checkedChildren="正常"
+                  unCheckedChildren="删除"
+                />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        {/* 封面及背景图选择Card */}
+        <Card title="封面 | 背景" style={{ marginBottom: 16 }}>
+          <Row gutter={16}>
+            <Col
+              md={{ span: 9, offset: 1 }}
+              lg={{ span: 7, offset: 2 }}
+              xl={{ span: 5, offset: 4 }}
+              xxl={{ span: 3, offset: 6 }}
+            >
+              <Card
+                hoverable
+                title={renderCoverCardName()}
+              >
+                {coverUrl && (
+                <div className={styles.cover}>
+                  <img src={genAbsolutePicUrl(coverUrl)} alt="" />
+                </div>
+)}
+                {!coverSelectorDestroy && getResourceModal(true)}
+              </Card>
+            </Col>
+            <Col
+              md={{ span: 9, offset: 2 }}
+              lg={{ span: 7, offset: 4 }}
+              xl={{ span: 5, offset: 4 }}
+              xxl={{ span: 3, offset: 5 }}
+            >
+              <Card
+                hoverable
+                title={renderbgCardName()}
+                cover={
+                  bgUrl ? (
+                    <div className={styles.background}>
+                      <img src={genAbsolutePicUrl(bgUrl)} alt="" />
+                    </div>
+                  ) : null
+                }
+              >
+                {!bgSelectorDestroy && getResourceModal(false)}
+              </Card>
+            </Col>
+          </Row>
+        </Card>
+        {/* 课列表选择Card */}
+        <Card title={renderLessonCardName()} style={{ marginBottom: 16 }}>
+          <RBDragSortTable
+            columns={lessonColumns}
+            data={subItemList}
+            onChange={rows => this.handleDragSortTableChange('lesson', rows)}
+          />
+          {!lessonSelectorDestroy && getLessonModal()}
+        </Card>
+        {/* 周边配套选择Card */}
+        <Card title={renderSupportCardName()} style={{ marginBottom: 70 }}>
+          <RBDragSortTable
+            columns={supportColumns}
+            data={supportList}
+            onChange={rows => this.handleDragSortTableChange('support', rows)}
+          />
+          {!supportSelectorDestroy && getSupportModal()}
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button
+            onClick={this.handlePageBack}
+            style={{ marginRight: 10 }}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            loading={submitting}
+            onClick={this.handlePageSubmit}
+          >提交
+          </Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 34 - 0
src/routes/Product/Course/CourseCreate.less

@@ -0,0 +1,34 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.cardName {
+  & > span {
+    display: inline-block;
+    height: 24px;
+    padding: 0 7px;
+    vertical-align: bottom;
+  }
+  :global {
+    .ant-btn-primary {
+      margin-left: 10px;
+    }
+  }
+}
+
+.cover {
+  width: 100%;
+  height: 240px;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.background {
+  width: 100%;
+  height: 240px;
+  line-height: 240px;
+  vertical-align: middle;
+  img {
+    width: 100%;
+  }
+}

+ 182 - 0
src/routes/Product/Course/CourseList.js

@@ -0,0 +1,182 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Button, message } from 'antd';
+import { StandardTableList } from '../../../components/RBList/index';
+import { renderStatus, addRowKey } from '../../../utils/utils';
+import styles from './CourseList.less';
+
+const Message = message;
+
+@connect(({ loading, product }) => ({
+  product,
+  loading: loading.models.product,
+}))
+export default class CourseListPage extends Component {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    this.state = {
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
+    };
+  }
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'product/fetchCourseList',
+      payload: { ...this.state.Queryers },
+    });
+  }
+  handleCreateOperation = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/course/create',
+      state: this.state,
+    }));
+  }
+  handleDeleteOperation = (item) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定要删除该课程吗?',
+      content: '如果该课程已经被某些套餐包关联,则将导致删除失败,需要手动解除与这些套餐包的关联才可进行删除',
+      onOk: () => {
+        this.props.dispatch({
+          type: 'product/deleteCourseItem',
+          payload: { id: item.id },
+          states: this.state,
+        });
+      },
+    });
+  }
+  handleEditOperation = (item) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/product/course/edit/${item.pid}`,
+      state: this.state,
+    }));
+  }
+  handleFilterOperation = (params, states) => {
+    this.props.dispatch({
+      type: 'product/fetchCourseList',
+      payload: params,
+    });
+    this.setState({
+      UIParams: states,
+      Queryers: params,
+    });
+  }
+  handleBatchOperation = () => {
+    Message.info('暂不支持批量操作!');
+  }
+
+  render() {
+    const { loading, product } = this.props;
+    const { list, totalSize, pageSize, pageNo } = product;
+
+    const renderOperation = (item) => {
+      return (
+        <div>
+          <Button
+            size="small"
+            className={styles.editBtn}
+            onClick={() => this.handleEditOperation(item)}
+          >编辑
+          </Button>
+          <Button
+            size="small"
+            className={styles.delBtn}
+            onClick={() => this.handleDeleteOperation(item)}
+          >删除
+          </Button>
+        </div>
+      );
+    };
+
+    const batchActions = [{
+      key: 'delete',
+      name: '批量删除',
+    }, {
+      key: 'recovery',
+      name: '批量恢复',
+    }];
+    const basicSearch = {
+      keys: [{
+        name: '课程编号',
+        field: 'code',
+      }, {
+        name: '课程名称',
+        field: 'name',
+      }],
+    };
+    const pagination = {
+      pageNo,
+      pageSize,
+      totalSize,
+    };
+    const columns = [{
+      title: '课程编号',
+      key: 1,
+      dataIndex: 'code',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '25%',
+    }, {
+      title: '课程名称',
+      key: 2,
+      dataIndex: 'name',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '30%',
+    }, {
+      title: '课程状态',
+      key: 3,
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+      width: '12%',
+    }, {
+      title: '更新时间',
+      key: 4,
+      dataIndex: 'gmtModified',
+      render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      width: '20%',
+    }, {
+      title: '操作',
+      key: 5,
+      dataIndex: 'operation',
+      render: (_, record) => renderOperation(record),
+      width: '13%',
+    }];
+    return (
+      <Card>
+        <StandardTableList
+          columns={columns}
+          loading={loading}
+          dataSource={addRowKey(list)}
+          header={{
+            basicSearch,
+            onFilterClick: this.handleFilterOperation,
+            onCreateClick: this.handleCreateOperation,
+          }}
+          footer={{
+            pagination,
+            batchActions,
+            onBatchClick: this.handleBatchOperation,
+          }}
+          keepUIState={{ ...this.state.UIParams }}
+        />
+      </Card>
+    );
+  }
+}

+ 16 - 0
src/routes/Product/Course/CourseList.less

@@ -0,0 +1,16 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.link {
+  font-weight: 500;
+}
+.editBtn {
+  margin-right: 10px;
+  background: @primary-5;
+  color: #fff;
+  font-weight: 500;
+}
+.delBtn {
+  background: #f5222d;
+  color: #fff;
+  font-weight: 500;
+}

+ 5 - 4
src/routes/Campus/Campus.js

@@ -1,11 +1,11 @@
 import React, { Component } from 'react';
-import { Route, Switch } from 'dva/router';
+import { Redirect, Route, Switch } from 'dva/router';
 import { connect } from 'dva';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import { getRoutes } from '../../utils/utils';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import { getRoutes } from '../../../utils/utils';
 
 @connect()
-export default class Campus extends Component {
+export default class Course extends Component {
   render() {
     const { match, routerData } = this.props;
     const routes = getRoutes(match.path, routerData);
@@ -25,6 +25,7 @@ export default class Campus extends Component {
               )
             )
           }
+          <Redirect exact from="/product/course" to="/product/course/list" />
         </Switch>
       </PageHeaderLayout>
     );

+ 364 - 0
src/routes/Product/Courseware/CoursewareCreate.js

@@ -0,0 +1,364 @@
+import React, { Component } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Form, Modal, Card, Button, Input, Radio, Switch } from 'antd';
+import RBVideoPlayer from '../../../components/RBVideoPlayer/index';
+import RBDragSortTable from '../../../components/RBDragSortTable/index';
+import Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar/index';
+import { RESOURCE_VIDEO } from '../../../utils/config';
+import {
+  genAbsolutePicUrl,
+  renderStatus,
+  statusToBool,
+  boolToStatus,
+} from '../../../utils/utils';
+import styles from './CoursewareCreate.less';
+
+const fieldLabels = {
+  code: '课件编号',
+  title: '课件名称',
+  category: '环节名称',
+  digest: '课件简述',
+  resourceList: '资源列表',
+  status: '课件状态',
+};
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 2 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@connect(({ loading, courseware, resource }) => ({
+  resource,
+  courseware,
+  loading: loading.models.resource,
+  submitting: loading.models.courseware,
+}))
+@Form.create()
+export default class CoursewareCreatePage extends Component {
+  state = {
+    modalSelectorDestroy: true,
+    resourceType: 'Picture',
+  };
+  componentWillMount() {
+    // 进入页面前清空下model中state内容,防止上次内容造成干扰
+    const match = pathToRegexp('/product/courseware/create').exec(this.props.location.pathname);
+    if (match) {
+      this.cleanPageState();
+    }
+  }
+  componentDidMount() {
+    // 组件挂载完成,检查是否是编辑操作,决定是否加载该课件数据
+    const matchId = this.isEdit();
+    if (matchId) {
+      this.props.dispatch({
+        type: 'courseware/fetchCoursewareItem',
+        payload: { id: matchId },
+      });
+    }
+  }
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/product/courseware/edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
+  cleanPageState = () => {
+    this.props.dispatch({
+      type: 'courseware/cleanItemState',
+      payload: {},
+    });
+  }
+  selectorDataFetcher = (params) => {
+    const { resourceType } = this.state;
+    if (resourceType === 'Picture') {
+      this.props.dispatch({
+        type: 'resource/fetchImageList',
+        payload: params,
+      });
+    }
+    if (resourceType === 'Video') {
+      this.props.dispatch({
+        type: 'resource/fetchVideoList',
+        payload: params,
+      });
+    }
+  }
+  handleSelectorModalShow = () => {
+    this.setState({
+      modalSelectorDestroy: false,
+    });
+    this.selectorDataFetcher();
+  }
+  handleSelectorCancel = () => {
+    this.setState({
+      modalSelectorDestroy: true,
+    });
+  }
+  handleSelectorFinish = (rows) => {
+    this.setState({
+      modalSelectorDestroy: true,
+    });
+    this.props.dispatch({
+      type: 'courseware/fixResourceList',
+      payload: rows,
+    });
+  }
+  handleSelectorChange = (params) => {
+    this.selectorDataFetcher(params);
+  }
+  handleRadioChange = (e) => {
+    this.setState({
+      resourceType: e.target.value,
+    }, () => this.selectorDataFetcher());
+  }
+  handleDragSortTableChange = (rows) => {
+    this.props.dispatch({
+      type: 'courseware/fixResourceList',
+      payload: rows,
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/courseware',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        const { status, ...rest } = values;
+        values = { status: boolToStatus(status), ...rest };
+        const { courseware } = this.props;
+        const { currentItem } = courseware;
+        const { resourceList } = currentItem;
+
+        let resourceIdList;
+        if (resourceIdList) {
+          resourceIdList = resourceList.map(item => item.id);
+        }
+
+        const matchId = this.isEdit();
+        if (matchId) {
+          const params = {
+            id: matchId,
+            resourceList: resourceIdList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'courseware/updateCoursewareItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        } else {
+          const params = {
+            resourceList: resourceIdList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'courseware/createCoursewareItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        }
+      }
+    });
+  }
+
+  render() {
+    const { submitting, courseware, form, loading, resource } = this.props;
+    const { modalSelectorDestroy, resourceType } = this.state;
+    const { getFieldDecorator } = form;
+    const { currentItem } = courseware;
+    const { code, title, digest, category, status, resourceList = [] } = currentItem;
+
+    const imageColumns = [{
+      title: '图片',
+      dataIndex: 'path',
+      key: 1,
+      render: text => (
+        <div className={styles.picture}>
+          <img src={genAbsolutePicUrl(text)} alt="" />
+        </div>
+      ),
+      width: '10%',
+    }, {
+      title: '编号',
+      dataIndex: 'code',
+      key: 2,
+      width: '20%',
+    }, {
+      title: '名称',
+      dataIndex: 'name',
+      key: 3,
+      width: '30%',
+    }, {
+      title: '格式',
+      dataIndex: 'format',
+      key: 4,
+    }, {
+      title: '状态',
+      dataIndex: 'status',
+      key: 5,
+      render: text => renderStatus(text),
+    }];
+
+    const renderCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={this.handleSelectorModalShow}>资源列表</a>
+          </span>
+        </div>
+      );
+    };
+    const renderModalTitle = () => {
+      return (
+        <Radio.Group
+          value={resourceType}
+          onChange={this.handleRadioChange}
+          className={styles.radio}
+        >
+          <Radio.Button value="Picture">图片</Radio.Button>
+          <Radio.Button value="Video">视频</Radio.Button>
+        </Radio.Group>
+      );
+    };
+    // 根据选定的资源列表类型决定渲染样式
+    const renderResourceList = () => {
+      if (!resourceList.length) {
+        return (
+          <h3 style={{ color: 'red' }}>你还未选择任何资源,请点击上方"资源列表"进行选择!</h3>
+        );
+      } else if (resourceList[0].type === RESOURCE_VIDEO) {
+        const videoItem = resourceList[0];
+        return (
+          <div className={styles.video}>
+            <RBVideoPlayer
+              width="100%"
+              height="100%"
+              url={videoItem.url}
+              isM3U8={videoItem.format === 'm3u8'}
+            />
+          </div>
+        );
+      } else {
+        return (
+          <RBDragSortTable
+            columns={imageColumns}
+            data={resourceList}
+            onChange={this.handleDragSortTableChange}
+          />
+        );
+      }
+    };
+    return (
+      <div>
+        <Card title="基础信息" style={{ marginBottom: 16 }}>
+          <Form>
+            <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [
+                  {
+                    required: true, message: '请填写课件编号',
+                  }, {
+                    pattern: /^[a-zA-Z0-9|-]+$/g, message: '编号包含非法字符',
+                  },
+                ],
+                initialValue: code,
+              })(
+                <Input
+                  placeholder="请输入(课件一旦创建完成,编号不可修改)"
+                  disabled={!!this.isEdit()}
+                />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.title} {...formItemLayout}>
+              {getFieldDecorator('title', {
+                rules: [{ required: true, message: '请填写课件名称' }],
+                initialValue: title,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.category} {...formItemLayout}>
+              {getFieldDecorator('category', {
+                initialValue: category,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.digest} {...formItemLayout}>
+              {getFieldDecorator('digest', {
+                initialValue: digest,
+              })(
+                <Input.TextArea rows={4} placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.status} {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: statusToBool(status),
+              })(
+                <Switch
+                  checkedChildren="正常"
+                  unCheckedChildren="删除"
+                />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        <Card title={renderCardName()} style={{ marginBottom: 70 }}>
+          {renderResourceList()}
+          {!modalSelectorDestroy && (
+            <Modal
+              visible
+              width={1100}
+              footer={null}
+              title={renderModalTitle()}
+              maskClosable={false}
+              onCancel={this.handleSelectorCancel}
+            >
+              <Selector
+                multiple={resourceType === 'Picture'}
+                loading={loading}
+                selectorName={resourceType}
+                list={resource.list}
+                pageNo={resource.pageNo}
+                pageSize={resource.pageSize}
+                totalSize={resource.totalSize}
+                onCancel={this.handleSelectorCancel}
+                onChange={this.handleSelectorChange}
+                onFinish={this.handleSelectorFinish}
+              />
+            </Modal>
+          )}
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button
+            onClick={this.handlePageBack}
+            style={{ marginRight: 10 }}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            loading={submitting}
+            onClick={this.handlePageSubmit}
+          >提交
+          </Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 45 - 0
src/routes/Product/Courseware/CoursewareCreate.less

@@ -0,0 +1,45 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.cardName {
+  & > span {
+    display: inline-block;
+    height: 24px;
+    padding: 0 7px;
+    vertical-align: bottom;
+  }
+  :global {
+    .ant-btn-primary {
+      margin-left: 10px;
+    }
+  }
+}
+
+.picture {
+  position: relative;
+  vertical-align: middle;
+  text-align: center;
+  width: 100px;
+  height: 80px;
+  line-height: 80px;
+  img {
+    max-height: 100%;
+    max-width: 100%;
+    vertical-align: middle;
+    height: auto;
+  }
+}
+
+.video {
+  width: 600px;
+  height: 340px;
+  text-align: center;
+}
+
+.radio {
+  :global {
+    .ant-radio-button-wrapper-checked {
+      background-color: @primary-color;
+      color: #fff;
+    }
+  }
+}

+ 182 - 0
src/routes/Product/Courseware/CoursewareList.js

@@ -0,0 +1,182 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Button, message } from 'antd';
+import { StandardTableList } from '../../../components/RBList/index';
+import { renderStatus, addRowKey } from '../../../utils/utils';
+import styles from './CoursewareList.less';
+
+const Message = message;
+
+@connect(({ loading, courseware }) => ({
+  courseware,
+  loading: loading.models.courseware,
+}))
+export default class CoursewareListPage extends Component {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    this.state = {
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
+    };
+  }
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'courseware/fetchCoursewareList',
+      payload: { ...this.state.Queryers },
+    });
+  }
+  handleCreateOperation = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/courseware/create',
+      state: this.state,
+    }));
+  }
+  handleDeleteOperation = (item) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定要删除该课件吗?',
+      content: '如果该课件已经被某些课关联,则将导致删除失败,需要手动解除与这些课的关联才可进行删除',
+      onOk: () => {
+        this.props.dispatch({
+          type: 'courseware/deleteCoursewareItem',
+          payload: { id: item.id },
+          states: this.state,
+        });
+      },
+    });
+  }
+  handleEditOperation = (item) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/product/courseware/edit/${item.id}`,
+      state: this.state,
+    }));
+  }
+  handleFilterOperation = (params, states) => {
+    this.props.dispatch({
+      type: 'courseware/fetchCoursewareList',
+      payload: params,
+    });
+    this.setState({
+      UIParams: states,
+      Queryers: params,
+    });
+  }
+  handleBatchOperation = () => {
+    Message.info('暂不支持批量操作!');
+  }
+
+  render() {
+    const { loading, courseware } = this.props;
+    const { list, totalSize, pageSize, pageNo } = courseware;
+
+    const renderOperation = (item) => {
+      return (
+        <div>
+          <Button
+            size="small"
+            className={styles.editBtn}
+            onClick={() => this.handleEditOperation(item)}
+          >编辑
+          </Button>
+          <Button
+            size="small"
+            className={styles.delBtn}
+            onClick={() => this.handleDeleteOperation(item)}
+          >删除
+          </Button>
+        </div>
+      );
+    };
+
+    const batchActions = [{
+      key: 'delete',
+      name: '批量删除',
+    }, {
+      key: 'recovery',
+      name: '批量恢复',
+    }];
+    const basicSearch = {
+      keys: [{
+        name: '课件编号',
+        field: 'code',
+      }, {
+        name: '课件名称',
+        field: 'name',
+      }],
+    };
+    const pagination = {
+      pageNo,
+      pageSize,
+      totalSize,
+    };
+    const columns = [{
+      title: '课件编号',
+      key: 1,
+      dataIndex: 'code',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '25%',
+    }, {
+      title: '课件名称',
+      key: 2,
+      dataIndex: 'title',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '30%',
+    }, {
+      title: '课件状态',
+      key: 3,
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+      width: '12%',
+    }, {
+      title: '更新时间',
+      key: 4,
+      dataIndex: 'gmtModified',
+      render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      width: '20%',
+    }, {
+      title: '操作',
+      key: 5,
+      dataIndex: 'operation',
+      render: (_, record) => renderOperation(record),
+      width: '13%',
+    }];
+    return (
+      <Card>
+        <StandardTableList
+          columns={columns}
+          loading={loading}
+          dataSource={addRowKey(list)}
+          header={{
+            basicSearch,
+            onFilterClick: this.handleFilterOperation,
+            onCreateClick: this.handleCreateOperation,
+          }}
+          footer={{
+            pagination,
+            batchActions,
+            onBatchClick: this.handleBatchOperation,
+          }}
+          keepUIState={{ ...this.state.UIParams }}
+        />
+      </Card>
+    );
+  }
+}

+ 16 - 0
src/routes/Product/Courseware/CoursewareList.less

@@ -0,0 +1,16 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.link {
+  font-weight: 500;
+}
+.editBtn {
+  margin-right: 10px;
+  background: @primary-5;
+  color: #fff;
+  font-weight: 500;
+}
+.delBtn {
+  background: #f5222d;
+  color: #fff;
+  font-weight: 500;
+}

+ 5 - 4
src/routes/Campus/Campus.js

@@ -1,11 +1,11 @@
 import React, { Component } from 'react';
-import { Route, Switch } from 'dva/router';
+import { Redirect, Route, Switch } from 'dva/router';
 import { connect } from 'dva';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import { getRoutes } from '../../utils/utils';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import { getRoutes } from '../../../utils/utils';
 
 @connect()
-export default class Campus extends Component {
+export default class Courseware extends Component {
   render() {
     const { match, routerData } = this.props;
     const routes = getRoutes(match.path, routerData);
@@ -25,6 +25,7 @@ export default class Campus extends Component {
               )
             )
           }
+          <Redirect exact from="/product/courseware" to="/product/courseware/list" />
         </Switch>
       </PageHeaderLayout>
     );

+ 284 - 0
src/routes/Product/Lesson/LessonCreate.js

@@ -0,0 +1,284 @@
+import React, { Component } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Form, Modal, Card, Button, Input, Switch } from 'antd';
+import RBDragSortTable from '../../../components/RBDragSortTable/index';
+import Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar/index';
+import { renderStatus, statusToBool, boolToStatus } from '../../../utils/utils';
+import styles from './LessonCreate.less';
+
+const fieldLabels = {
+  code: '课编号',
+  title: '课名称',
+  digest: '课简述',
+  status: '课状态',
+};
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 2 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@connect(({ loading, lesson, courseware }) => ({
+  lesson,
+  courseware,
+  loading: loading.models.courseware,
+  submitting: loading.models.lesson,
+}))
+@Form.create()
+export default class LessonCreatePage extends Component {
+  state = {
+    modalSelectorDestroy: true,
+  };
+  componentWillMount() {
+    const match = pathToRegexp('/product/lesson/create').exec(this.props.location.pathname);
+    if (match) {
+      this.cleanPageState();
+    }
+  }
+  componentDidMount() {
+    const matchId = this.isEdit();
+    if (matchId) {
+      this.props.dispatch({
+        type: 'lesson/fetchLessonItem',
+        payload: { id: matchId },
+      });
+    }
+  }
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/product/lesson/edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
+  cleanPageState = () => {
+    this.props.dispatch({
+      type: 'lesson/cleanItemState',
+      payload: {},
+    });
+  }
+  selectorDataFetcher = (params) => {
+    this.props.dispatch({
+      type: 'courseware/fetchCoursewareList',
+      payload: params,
+    });
+  }
+  handleSelectorModalShow = () => {
+    this.setState({
+      modalSelectorDestroy: false,
+    });
+    this.selectorDataFetcher();
+  }
+  handleSelectorCancel = () => {
+    this.setState({
+      modalSelectorDestroy: true,
+    });
+  }
+  handleSelectorFinish = (rows) => {
+    this.setState({
+      modalSelectorDestroy: true,
+    });
+    this.props.dispatch({
+      type: 'lesson/fixCoursewareList',
+      payload: rows,
+    });
+  }
+  handleSelectorChange = (params) => {
+    this.selectorDataFetcher(params);
+  }
+  handleDragSortTableChange = (rows) => {
+    this.props.dispatch({
+      type: 'lesson/fixCoursewareList',
+      payload: rows,
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/lesson',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        // 从表单中提取相关字段
+        const { status, ...rest } = values;
+        values = { status: boolToStatus(status), ...rest };
+        const { lesson } = this.props;
+        const { currentItem } = lesson;
+        const { wareList } = currentItem;
+
+        // 提取wareIdList
+        let wareIdList;
+        if (wareList) {
+          wareIdList = wareList.map(item => item.id);
+        }
+
+        const matchId = this.isEdit();
+        if (matchId) {
+          const params = {
+            id: matchId,
+            wareList: wareIdList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'lesson/updateLessonItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        } else {
+          const params = {
+            wareList: wareIdList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'lesson/createLessonItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        }
+      }
+    });
+  }
+
+  render() {
+    const { submitting, lesson, form, loading, courseware } = this.props;
+    const { list, pageSize, pageNo, totalSize } = courseware;
+    const { modalSelectorDestroy } = this.state;
+    const { getFieldDecorator } = form;
+    const { currentItem } = lesson;
+    const { code, title, digest, status, wareList = [] } = currentItem;
+
+    const coursewareColumns = [{
+      title: '编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '20%',
+    }, {
+      title: '名称',
+      dataIndex: 'title',
+      key: 2,
+      width: '30%',
+    }, {
+      title: '状态',
+      dataIndex: 'status',
+      key: 3,
+      render: (text) => renderStatus(text),
+    }];
+
+    const renderCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={this.handleSelectorModalShow}>课件列表</a>
+          </span>
+        </div>
+      );
+    }
+    return (
+      <div>
+        <Card title="基础信息" style={{marginBottom: 16}}>
+          <Form>
+            <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [
+                  {
+                    required: true, message: '请填写课编号',
+                  }, {
+                    pattern: /^[a-zA-Z0-9|-]+$/g, message: '编号包含非法字符',
+                  }
+                ],
+                initialValue: code,
+              })(
+                <Input
+                  placeholder="请输入(课一旦创建完成,编号不可修改)"
+                  disabled={this.isEdit() ? true : false}
+                />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.title} {...formItemLayout}>
+              {getFieldDecorator('title', {
+                rules: [{ required: true, message: '请填写课名称' }],
+                initialValue: title,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.digest} {...formItemLayout}>
+              {getFieldDecorator('digest', {
+                initialValue: digest,
+              })(
+                <Input.TextArea rows={4} placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.status} {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: statusToBool(status),
+              })(
+                <Switch
+                  checkedChildren="正常"
+                  unCheckedChildren="删除"
+                />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        <Card title={renderCardName()} style={{marginBottom: 70}}>
+          <RBDragSortTable
+            columns={coursewareColumns}
+            data={wareList}
+            onChange={this.handleDragSortTableChange}
+          />
+          {!modalSelectorDestroy &&
+            <Modal
+              visible
+              width={1100}
+              footer={null}
+              title="课件资源"
+              maskClosable={false}
+              onCancel={this.handleSelectorCancel}
+            >
+              <Selector
+                multiple
+                list={list}
+                loading={loading}
+                pageNo={pageNo}
+                pageSize={pageSize}
+                totalSize={totalSize}
+                selectorName="Courseware"
+                onChange={this.handleSelectorChange}
+                onCancel={this.handleSelectorCancel}
+                onFinish={this.handleSelectorFinish}
+              />
+            </Modal>
+          }
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button
+            onClick={this.handlePageBack}
+            style={{ marginRight: 10 }}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            loading={submitting}
+            onClick={this.handlePageSubmit}
+          >提交
+          </Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 15 - 0
src/routes/Product/Lesson/LessonCreate.less

@@ -0,0 +1,15 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.cardName {
+  & > span {
+    display: inline-block;
+    height: 24px;
+    padding: 0 7px;
+    vertical-align: bottom;
+  }
+  :global {
+    .ant-btn-primary {
+      margin-left: 10px;
+    }
+  }
+}

+ 182 - 0
src/routes/Product/Lesson/LessonList.js

@@ -0,0 +1,182 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Button, message } from 'antd';
+import { StandardTableList } from '../../../components/RBList/index';
+import { renderStatus, addRowKey } from '../../../utils/utils';
+import styles from './LessonList.less';
+
+const Message = message;
+
+@connect(({ loading, lesson }) => ({
+  lesson,
+  loading: loading.models.lesson,
+}))
+export default class LessonListPage extends Component {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    this.state = {
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
+    };
+  }
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'lesson/fetchLessonList',
+      payload: { ...this.state.Queryers },
+    });
+  }
+  handleCreateOperation = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/lesson/create',
+      state: this.state,
+    }));
+  }
+  handleDeleteOperation = (item) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定要删除该课吗?',
+      content: '如果该课已经被某些课程关联,则将导致删除失败,需要手动解除与这些课程的关联才可进行删除',
+      onOk: () => {
+        this.props.dispatch({
+          type: 'lesson/deleteLessonItem',
+          payload: { id: item.id },
+          states: this.state,
+        });
+      },
+    });
+  }
+  handleEditOperation = (item) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/product/lesson/edit/${item.id}`,
+      state: this.state,
+    }));
+  }
+  handleFilterOperation = (params, states) => {
+    this.props.dispatch({
+      type: 'lesson/fetchLessonList',
+      payload: params,
+    });
+    this.setState({
+      UIParams: states,
+      Queryers: params,
+    });
+  }
+  handleBatchOperation = () => {
+    Message.info('暂不支持批量操作!');
+  }
+
+  render() {
+    const { loading, lesson } = this.props;
+    const { list, totalSize, pageSize, pageNo } = lesson;
+
+    const renderOperation = (item) => {
+      return (
+        <div>
+          <Button
+            size="small"
+            className={styles.editBtn}
+            onClick={() => this.handleEditOperation(item)}
+          >编辑
+          </Button>
+          <Button
+            size="small"
+            className={styles.delBtn}
+            onClick={() => this.handleDeleteOperation(item)}
+          >删除
+          </Button>
+        </div>
+      );
+    };
+
+    const batchActions = [{
+      key: 'delete',
+      name: '批量删除',
+    }, {
+      key: 'recovery',
+      name: '批量恢复',
+    }];
+    const basicSearch = {
+      keys: [{
+        name: '课编号',
+        field: 'code',
+      }, {
+        name: '课名称',
+        field: 'name',
+      }],
+    };
+    const pagination = {
+      pageNo,
+      pageSize,
+      totalSize,
+    };
+    const columns = [{
+      title: '课编号',
+      key: 1,
+      dataIndex: 'code',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '25%',
+    }, {
+      title: '课名称',
+      key: 2,
+      dataIndex: 'title',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '30%',
+    }, {
+      title: '课状态',
+      key: 3,
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+      width: '12%',
+    }, {
+      title: '更新时间',
+      key: 4,
+      dataIndex: 'gmtModified',
+      render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      width: '20%',
+    }, {
+      title: '操作',
+      key: 5,
+      dataIndex: 'operation',
+      render: (_, record) => renderOperation(record),
+      width: '13%',
+    }];
+    return (
+      <Card>
+        <StandardTableList
+          columns={columns}
+          loading={loading}
+          dataSource={addRowKey(list)}
+          header={{
+            basicSearch,
+            onFilterClick: this.handleFilterOperation,
+            onCreateClick: this.handleCreateOperation,
+          }}
+          footer={{
+            pagination,
+            batchActions,
+            onBatchClick: this.handleBatchOperation,
+          }}
+          keepUIState={{ ...this.state.UIParams }}
+        />
+      </Card>
+    );
+  }
+}

+ 16 - 0
src/routes/Product/Lesson/LessonList.less

@@ -0,0 +1,16 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.link {
+  font-weight: 500;
+}
+.editBtn {
+  margin-right: 10px;
+  background: @primary-5;
+  color: #fff;
+  font-weight: 500;
+}
+.delBtn {
+  background: #f5222d;
+  color: #fff;
+  font-weight: 500;
+}

+ 5 - 4
src/routes/Campus/Campus.js

@@ -1,11 +1,11 @@
 import React, { Component } from 'react';
-import { Route, Switch } from 'dva/router';
+import { Redirect, Route, Switch } from 'dva/router';
 import { connect } from 'dva';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import { getRoutes } from '../../utils/utils';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import { getRoutes } from '../../../utils/utils';
 
 @connect()
-export default class Campus extends Component {
+export default class Lesson extends Component {
   render() {
     const { match, routerData } = this.props;
     const routes = getRoutes(match.path, routerData);
@@ -25,6 +25,7 @@ export default class Campus extends Component {
               )
             )
           }
+          <Redirect exact from="/product/lesson" to="/product/lesson/list" />
         </Switch>
       </PageHeaderLayout>
     );

+ 394 - 0
src/routes/Product/Package/PackageCreate.js

@@ -0,0 +1,394 @@
+import React, { Component } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { message, Form, Modal, Card, Button, Input, InputNumber, Table, Switch, Select, Radio } from 'antd';
+import { renderProductType, statusToBool, boolToStatus, addRowKey } from '../../../utils/utils';
+import Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar/index';
+import styles from './PackageCreate.less';
+
+const Message = message;
+
+const fieldLabels = {
+  code: '套餐包编号',
+  name: '套餐包名称',
+  status: '套餐包状态',
+};
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 3 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@connect(({loading, product}) => ({
+  product,
+  pLoading: loading.models.product,
+}))
+@Form.create()
+export default class PackageCreatePage extends Component {
+  state = {
+    productType: 'Course',
+    selectorModalDestroy: true,
+  };
+  componentWillMount() {
+    const match = pathToRegexp('/product/package/create').exec(this.props.location.pathname);
+    if (match) {
+      this.cleanPageState();
+    }
+  }
+  componentDidMount() {
+    const matchId = this.isEdit();
+    if (matchId) {
+      this.props.dispatch({
+        type: 'product/fetchProductItem',
+        payload: { pid: matchId },
+      });
+    }
+  }
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/product/package/edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
+  cleanPageState = () => {
+    this.props.dispatch({
+      type: 'product/cleanItemState',
+      payload: {},
+    });
+  }
+  selectorDataFetcher = (name, params) => {
+    switch (name) {
+      case 'Course':
+        this.props.dispatch({
+          type: 'product/fetchCourseList',
+          payload: params,
+        });
+        return;
+      case 'Support':
+        this.props.dispatch({
+          type: 'product/fetchSupportList',
+          payload: params,
+        });
+        return;
+    }
+  }
+  handleRadioChange = (e) => {
+    this.setState({
+      productType: e.target.value,
+    });
+    this.selectorDataFetcher(e.target.value);
+  }
+  handleSelectorModalShow = () => {
+    this.setState({
+      selectorModalDestroy: false,
+    });
+    this.selectorDataFetcher(this.state.productType);
+  }
+  handleSelectorChange = (params) => {
+    this.selectorDataFetcher(
+      this.state.productType,
+      params
+    );
+  }
+  handleSelectorFinish = (rows) => {
+    this.setState({
+      selectorModalDestroy: true,
+    });
+    this.props.dispatch({
+      type: 'product/fixCurrentItem',
+      payload: {
+        products: rows,
+      },
+    });
+  }
+  handleSelectorCancel = () => {
+    this.setState({
+      selectorModalDestroy: true,
+    });
+  }
+  handleDragSortTableChange = (rows) => {
+    this.props.dispatch({
+      type: 'product/fixCurrentItem',
+      payload: {
+        products: rows
+      },
+    });
+  }
+  handlePriceInputChange  = (field, key, value) => {
+    const { product } = this.props;
+    const { currentItem } = product;
+    const { products = [] } = currentItem;
+    let newData =[...products]
+    newData = newData.map(item => {
+      if (item.id === key) {
+        return {
+          ...item,
+          [`${field}Price`]: value,
+        };
+      } else {
+        return {...item};
+      }
+    });
+    this.props.dispatch({
+      type: 'product/fixCurrentItem',
+      payload: {
+        products: newData
+      }
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/package',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        // 表单字段处理
+        const { status, ...rest } = values;
+        values = { status: boolToStatus(status), ...rest };
+
+        // products字段处理
+        const { product } = this.props;
+        const { currentItem } = product;
+        const { products } = currentItem;
+        let productList = [];
+        let submittable = true;
+        if (products) {
+          for (let index in products) {
+            let product = products[index];
+            if (!product.cpPrice || !product.merchantPrice) {
+              Message.error('还有价格未填写!');
+              submittable = false;
+              break;
+            }
+            productList.push({
+              pid: product.pid,
+              type: product.type,
+              cpPrice: product.cpPrice,
+              merchantPrice: product.merchantPrice,
+            });
+          }
+        }
+
+        // 参数未正确填写不可提交
+        if (!submittable) {
+          return;
+        }
+
+        const matchId = this.isEdit();
+        if (matchId) {
+          const params = {
+            id: matchId,
+            products: productList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'product/updatePackageItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        } else {
+          const params = {
+            products: productList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'product/createPackageItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        }
+      }
+    });
+  }
+
+  render() {
+    const { form, pLoading, product } = this.props;
+    const { selectorModalDestroy, productType } = this.state;
+    const { currentItem } = product;
+    const { code, name, status, products = [] } = currentItem;
+    const { getFieldDecorator } = form;
+
+    const productColumns = [{
+      title: '序号',
+      dataIndex: 'index',
+      key: 0,
+      align: 'center',
+      render: (text, record, index) => index + 1,
+      width: 50,
+    }, {
+      title: '产品编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '22%',
+    }, {
+      title: '产品名称',
+      dataIndex: 'name',
+      key: 2,
+    }, {
+      title: '产品类型',
+      dataIndex: 'type',
+      key: 3,
+      align: 'center',
+      render: (text) => renderProductType(text),
+      width: '10%',
+    }, {
+      title: '供应商价格(¥)',
+      dataIndex: 'cpPrice',
+      key: 4,
+      align: 'center',
+      render: (text, record) => (
+        <InputNumber
+          min={0}
+          value={text}
+          style={{width: '100%'}}
+          onChange={(value) => this.handlePriceInputChange('cp', record.key, value)}
+        />
+      ),
+      width: '15%',
+    }, {
+      title: '平台方价格(¥)',
+      dataIndex: 'merchantPrice',
+      key: 5,
+      align: 'center',
+      render: (text, record) => (
+        <InputNumber
+          min={0}
+          value={text}
+          style={{width: '100%'}}
+          onChange={(value) => this.handlePriceInputChange('merchant', record.key, value)}
+        />
+      ),
+      width: '15%',
+    }];
+
+    const renderCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={this.handleSelectorModalShow}>课程配套</a>
+          </span>
+        </div>
+      );
+    }
+    const renderModalTitle = () => {
+      return (
+        <Radio.Group
+          value={productType}
+          onChange={this.handleRadioChange}
+          className={styles.radio}
+        >
+          <Radio.Button value="Course">课程</Radio.Button>
+          <Radio.Button value="Support">配套</Radio.Button>
+        </Radio.Group>
+      );
+    }
+
+    const getProductModal = () => {
+      return (
+        <Modal
+          visible
+          width={1100}
+          footer={null}
+          title={renderModalTitle()}
+          maskClosable={false}
+          onCancel={this.handleSelectorCancel}
+        >
+          <Selector
+            multiple
+            loading={pLoading}
+            selectorName={productType}
+            fixedName="Product"
+            list={product.list}
+            pageNo={product.pageNo}
+            pageSize={product.pageSize}
+            totalSize={product.totalSize}
+            onCancel={this.handleSelectorCancel}
+            onChange={this.handleSelectorChange}
+            onFinish={this.handleSelectorFinish}
+          />
+        </Modal>
+      );
+    }
+    return (
+      <div>
+        {/* 基础信息Card */}
+        <Card title="基础信息" style={{marginBottom: 16}}>
+          <Form>
+            <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [
+                  {
+                    required: true, message: '请填写套餐包编号',
+                  }, {
+                    pattern: /^[a-zA-Z0-9|-]+$/g, message: '编号包含非法字符',
+                  }
+                ],
+                initialValue: code,
+              })(
+                <Input
+                  placeholder="请输入"
+                  disabled={this.isEdit() ? true : false}
+                />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.name} {...formItemLayout}>
+              {getFieldDecorator('name', {
+                rules: [{ required: true, message: '请填写套餐包名称' }],
+                initialValue: name,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.status} {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: statusToBool(status),
+              })(
+                <Switch
+                  checkedChildren="正常"
+                  unCheckedChildren="删除"
+                />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        <Card title={renderCardName()} style={{marginBottom: 70}}>
+          <Table
+            bordered
+            pagination={false}
+            className={styles.table}
+            dataSource={addRowKey(products)}
+            columns={productColumns}
+          />
+          {!selectorModalDestroy && getProductModal()}
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button
+            onClick={this.handlePageBack}
+            style={{ marginRight: 10 }}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            onClick={this.handlePageSubmit}
+          >提交
+          </Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 42 - 0
src/routes/Product/Package/PackageCreate.less

@@ -0,0 +1,42 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.cardName {
+  & > span {
+    display: inline-block;
+    height: 24px;
+    padding: 0 7px;
+    vertical-align: bottom;
+  }
+  :global {
+    .ant-btn-primary {
+      margin-left: 10px;
+    }
+  }
+}
+
+.radio {
+  :global {
+    .ant-radio-button-wrapper-checked {
+      background-color: @primary-color;
+      color: #fff;
+    }
+  }
+}
+
+.table {
+  :global {
+    .ant-table-wrapper {
+      height: 300px;
+    }
+    .ant-table-tbody > tr > td {
+      padding: 5px;
+    }
+    .ant-table-thead > tr > th {
+      padding: 10px 5px;
+    }
+    .ant-table-footer {
+      height: 46px;
+      padding: 5px;
+    }
+  }
+}

+ 181 - 0
src/routes/Product/Package/PackageList.js

@@ -0,0 +1,181 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Card, Modal, Button, message } from 'antd';
+import { StandardTableList } from '../../../components/RBList/index';
+import { renderStatus, addRowKey } from '../../../utils/utils';
+import styles from './PackageList.less';
+
+const Message = message;
+
+@connect(({ loading, product }) => ({
+  product,
+  loading: loading.models.product,
+}))
+export default class PackageListPage extends Component {
+  constructor(props) {
+    super(props);
+    const { state } = props.location;
+    this.state = {
+      UIParams: (state || {}).UIParams, // 组件的状态参数
+      Queryers: (state || {}).Queryers, // 查询的条件参数
+    };
+  }
+  componentDidMount() {
+    this.props.dispatch({
+      type: 'product/fetchPackageList',
+      payload: { ...this.state.Queryers },
+    });
+  }
+  handleCreateOperation = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/package/create',
+      state: this.state,
+    }));
+  }
+  handleDeleteOperation = (item) => {
+    Modal.confirm({
+      okText: '确定',
+      cancelText: '取消',
+      title: '你确定要删除该套餐包吗?',
+      onOk: () => {
+        this.props.dispatch({
+          type: 'product/deletePackageItem',
+          payload: { id: item.id },
+          states: this.state,
+        });
+      },
+    });
+  }
+  handleEditOperation = (item) => {
+    this.props.dispatch(routerRedux.push({
+      pathname: `/product/package/edit/${item.pid}`,
+      state: this.state,
+    }));
+  }
+  handleFilterOperation = (params, states) => {
+    this.props.dispatch({
+      type: 'product/fetchPackageList',
+      payload: params,
+    });
+    this.setState({
+      UIParams: states,
+      Queryers: params,
+    });
+  }
+  handleBatchOperation = () => {
+    Message.info('暂不支持批量操作!');
+  }
+
+  render() {
+    const { loading, product } = this.props;
+    const { list, totalSize, pageSize, pageNo } = product;
+
+    const renderOperation = (item) => {
+      return (
+        <div>
+          <Button
+            size="small"
+            className={styles.editBtn}
+            onClick={() => this.handleEditOperation(item)}
+          >编辑
+          </Button>
+          <Button
+            size="small"
+            className={styles.delBtn}
+            onClick={() => this.handleDeleteOperation(item)}
+          >删除
+          </Button>
+        </div>
+      );
+    };
+
+    const batchActions = [{
+      key: 'delete',
+      name: '批量删除',
+    }, {
+      key: 'recovery',
+      name: '批量恢复',
+    }];
+    const basicSearch = {
+      keys: [{
+        name: '套餐包编号',
+        field: 'code',
+      }, {
+        name: '套餐包名称',
+        field: 'name',
+      }],
+    };
+    const pagination = {
+      pageNo,
+      pageSize,
+      totalSize,
+    };
+    const columns = [{
+      title: '套餐包编号',
+      key: 1,
+      dataIndex: 'code',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '25%',
+    }, {
+      title: '套餐包名称',
+      key: 2,
+      dataIndex: 'name',
+      render: (text, record) => (
+        <a
+          className={styles.link}
+          onClick={() => this.handleEditOperation(record)}
+        >
+          {text}
+        </a>
+      ),
+      width: '30%',
+    }, {
+      title: '套餐包状态',
+      key: 3,
+      dataIndex: 'status',
+      render: text => renderStatus(text),
+      width: '12%',
+    }, {
+      title: '更新时间',
+      key: 4,
+      dataIndex: 'gmtModified',
+      render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      width: '20%',
+    }, {
+      title: '操作',
+      key: 5,
+      dataIndex: 'operation',
+      render: (_, record) => renderOperation(record),
+      width: '13%',
+    }];
+    return (
+      <Card>
+        <StandardTableList
+          columns={columns}
+          loading={loading}
+          dataSource={addRowKey(list)}
+          header={{
+            basicSearch,
+            onFilterClick: this.handleFilterOperation,
+            onCreateClick: this.handleCreateOperation,
+          }}
+          footer={{
+            pagination,
+            batchActions,
+            onBatchClick: this.handleBatchOperation,
+          }}
+          keepUIState={{ ...this.state.UIParams }}
+        />
+      </Card>
+    );
+  }
+}

+ 16 - 0
src/routes/Product/Package/PackageList.less

@@ -0,0 +1,16 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.link {
+  font-weight: 500;
+}
+.editBtn {
+  margin-right: 10px;
+  background: @primary-5;
+  color: #fff;
+  font-weight: 500;
+}
+.delBtn {
+  background: #f5222d;
+  color: #fff;
+  font-weight: 500;
+}

+ 5 - 4
src/routes/Campus/Campus.js

@@ -1,11 +1,11 @@
 import React, { Component } from 'react';
-import { Route, Switch } from 'dva/router';
+import { Redirect, Route, Switch } from 'dva/router';
 import { connect } from 'dva';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import { getRoutes } from '../../utils/utils';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import { getRoutes } from '../../../utils/utils';
 
 @connect()
-export default class Campus extends Component {
+export default class Package extends Component {
   render() {
     const { match, routerData } = this.props;
     const routes = getRoutes(match.path, routerData);
@@ -25,6 +25,7 @@ export default class Campus extends Component {
               )
             )
           }
+          <Redirect exact from="/product/package" to="/product/package/list" />
         </Switch>
       </PageHeaderLayout>
     );

+ 508 - 0
src/routes/Product/Support/SupportCreate.js

@@ -0,0 +1,508 @@
+import React, { Component } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import { routerRedux } from 'dva/router';
+import { Form, Modal, Card, Button, Input, Switch, Row, Col, Carousel, Select } from 'antd';
+import { renderStatus, statusToBool, boolToStatus, genAbsolutePicUrl } from '../../../utils/utils';
+import RBDragSortTable from '../../../components/RBDragSortTable/index';
+import Selector from '../../../components/RBTableSelector/Selector';
+import FooterToolbar from '../../../components/FooterToolbar/index';
+import styles from './SupportCreate.less';
+
+const fieldLabels = {
+  code: '配套编号',
+  title: '配套标题',
+  subTitle: '配套副标题',
+  name: '配套全称',
+  merchant: '内容提供商',
+  digest: '配套概要',
+  detail: '配套详情',
+  coverUrl: '配套封面图',
+  imgList: '配套滚动图册',
+  status: '配套状态',
+};
+const formItemLayout = {
+  labelCol: {
+    xs: { span: 24 },
+    sm: { span: 3 },
+  },
+  wrapperCol: {
+    xs: { span: 24 },
+    sm: { span: 14 },
+    md: { span: 12 },
+  },
+};
+
+@connect(({loading, product, resource, merchant}) => ({
+  product,
+  resource,
+  merchant,
+  pLoading: loading.models.product,
+  rLoading: loading.models.resource,
+  submitting: loading.models.product,
+}))
+@Form.create()
+export default class SupportCreatePage extends Component {
+  state = {
+    coverSelectorDestroy: true,
+    carouselSelectorDestroy: true,
+    supportSelectorDestroy: true,
+  };
+  componentWillMount() {
+    const match = pathToRegexp('/product/support/create').exec(this.props.location.pathname);
+    if (match) {
+      this.cleanPageState();
+    }
+  }
+  componentDidMount() {
+    const matchId = this.isEdit();
+    if (matchId) {
+      this.props.dispatch({
+        type: 'product/fetchProductItem',
+        payload: { pid: matchId },
+      });
+    }
+    this.props.dispatch({
+      type: 'merchant/fetchMerchantList',
+      payload: {pageSize: 1000}, // TODO 以后商户多了需要改写交互样式
+    });
+  }
+  isEdit = () => {
+    const { location } = this.props;
+    const match = pathToRegexp('/product/support/edit/:id').exec(location.pathname);
+    if (match) {
+      return match[1];
+    }
+    return false;
+  }
+  cleanPageState = () => {
+    this.props.dispatch({
+      type: 'product/cleanItemState',
+      payload: {},
+    });
+  }
+  selectorDataFetcher = (name, params) => {
+    switch (name) {
+      case 'cover':
+        this.props.dispatch({
+          type: 'resource/fetchImageList',
+          payload: params,
+        });
+        return;
+      case 'carousel':
+        this.props.dispatch({
+          type: 'resource/fetchImageList',
+          payload: params,
+        });
+        return;
+      case 'support':
+        this.props.dispatch({
+          type: 'product/fetchSupportList',
+          payload: params,
+        });
+        return;
+    }
+  }
+  currentItemFormatter = (name, rows) => {
+    let payload;
+    switch (name) {
+      case 'cover':
+        payload = {coverUrl: rows[0].path};
+        break;
+      case 'carousel':
+        payload = {imgList: rows.map(row => row.path)};
+        break;
+      case 'support':
+        payload = {supportList: rows};
+        break;
+    }
+    return payload;
+  }
+  handleSelectorModalShow = (name) => {
+    this.setState({
+      [`${name}SelectorDestroy`]: false,
+    });
+    this.selectorDataFetcher(name);
+  }
+  handleSelectorChange = (name, params) => {
+    this.selectorDataFetcher(name, params);
+  }
+  handleSelectorFinish = (name, rows) => {
+    this.setState({
+      [`${name}SelectorDestroy`]: true,
+    });
+    const payload = this.currentItemFormatter(name, rows);
+    this.props.dispatch({
+      payload,
+      type: 'product/fixCurrentItem',
+    });
+  }
+  handleDragSortTableChange = (name, rows) => {
+    const payload = this.currentItemFormatter(name, rows);
+    this.props.dispatch({
+      payload,
+      type: 'product/fixCurrentItem',
+    });
+  }
+  handleSelectorCancel = (name) => {
+    this.setState({
+      [`${name}SelectorDestroy`]: true,
+    });
+  }
+  handlePageBack = () => {
+    this.props.dispatch(routerRedux.push({
+      pathname: '/product/support',
+      state: this.props.location.state,
+    }));
+  }
+  handlePageSubmit = (e) => {
+    e.preventDefault();
+    this.props.form.validateFieldsAndScroll((err, values) => {
+      if (!err) {
+        // 从表单提取基础信息字段
+        let { status, title, subTitle, name, ...rest } = values;
+        name = `${title}_${subTitle}`;
+        values = { name, title, subTitle, status: boolToStatus(status), ...rest };
+
+        // 从props中提取coverUrl、imgList、supportList字段
+        const { product } = this.props;
+        const { currentItem } = product;
+        const { imgList, supportList, coverUrl } = currentItem;
+
+        // 防止supportList为空
+        let supportIdList;
+        if (supportList && supportList.length) {
+          supportIdList = supportList.map(item => item.id);
+        }
+
+        // 更新或者创建操作
+        const matchId = this.isEdit();
+        if (matchId) {
+          const params = {
+            imgList,
+            coverUrl,
+            supportList: supportIdList,
+            id: matchId,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'product/updateSupportItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        } else {
+          const params = {
+            imgList,
+            coverUrl,
+            supportList: supportIdList,
+            ...values,
+          };
+          this.props.dispatch({
+            type: 'product/createSupportItem',
+            payload: params,
+            states: this.props.location.state,
+          });
+        }
+      }
+    });
+  }
+
+  render() {
+    const {
+      form, submitting, rLoading, pLoading, product, resource, merchant
+    } = this.props;
+    const {
+      supportSelectorDestroy, coverSelectorDestroy, carouselSelectorDestroy,
+    } = this.state;
+    const {
+      currentItem
+    } = product;
+    const {
+      code, title, subTitle, name, digest, detail, status, coverUrl, cpId,
+      imgList = [], supportList = [],
+    } = currentItem;
+    const {
+      getFieldDecorator
+    } = form;
+
+    const lessonColumns = [{
+      title: '课编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '20%',
+    }, {
+      title: '课名称',
+      dataIndex: 'title',
+      key: 2,
+      width: '30%',
+    }, {
+      title: '状态',
+      dataIndex: 'status',
+      key: 3,
+      render: (text) => renderStatus(text),
+    }];
+    const supportColumns = [{
+      title: '配套编号',
+      dataIndex: 'code',
+      key: 1,
+      width: '20%',
+    }, {
+      title: '配套名称',
+      dataIndex: 'name',
+      key: 2,
+      width: '30%',
+    }, {
+      title: '配套状态',
+      dataIndex: 'status',
+      key: 3,
+      render: (text) => renderStatus(text),
+    }];
+
+    const getMerchants = () => {
+      let { list } = merchant;
+      let options = list.map(item => ({
+        text: item.name,
+        key: item.id,
+      }));
+      return options;
+    }
+
+    const getResourceModal = (isCover) => {
+      return (
+        <Modal
+          width={isCover ? 900 : 1100}
+          footer={null}
+          visible={true}
+          title="图片资源"
+          maskClosable={false}
+          onCancel={() => this.handleSelectorCancel(isCover ? 'cover' : 'carousel')}
+        >
+          <Selector
+            loading={rLoading}
+            list={resource.list}
+            pageNo={resource.pageNo}
+            pageSize={resource.pageSize}
+            totalSize={resource.totalSize}
+            multiple={isCover ? false : true}
+            selectorName={isCover ? 'PictureSingle' : 'Picture'}
+            onCancel={() => this.handleSelectorCancel(isCover ? 'cover' : 'carousel')}
+            onChange={(data) => this.handleSelectorChange(isCover ? 'cover' : 'carousel', data)}
+            onFinish={(rows) => this.handleSelectorFinish(isCover ? 'cover' : 'carousel', rows)}
+          />
+        </Modal>
+      );
+    }
+    const getSupportModal = () => {
+      return (
+        <Modal
+          width={1100}
+          footer={null}
+          visible={true}
+          title="配套资源"
+          maskClosable={false}
+          onCancel={() => this.handleSelectorCancel('support')}
+        >
+          <Selector
+            multiple={true}
+            loading={pLoading}
+            selectorName="Support"
+            list={product.list}
+            pageNo={product.pageNo}
+            pageSize={product.pageSize}
+            totalSize={product.totalSize}
+            onCancel={() => this.handleSelectorCancel('support')}
+            onChange={(data) => this.handleSelectorChange('support', data)}
+            onFinish={(rows) => this.handleSelectorFinish('support', rows)}
+          />
+        </Modal>
+      );
+    }
+
+    const renderSupportCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={() => this.handleSelectorModalShow('support')}>相关配套</a>
+          </span>
+        </div>
+      );
+    }
+    const renderCoverCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={() => this.handleSelectorModalShow('cover')}>配套封面</a>
+          </span>
+        </div>
+      );
+    }
+    const renderCarouselCardName = () => {
+      return (
+        <div className={styles.cardName}>
+          <span>
+            <a onClick={() => this.handleSelectorModalShow('carousel')}>详情图册</a>
+          </span>
+        </div>
+      );
+    }
+    return (
+      <div>
+        {/* 基础信息Card */}
+        <Card title="基础信息" style={{marginBottom: 16}}>
+          <Form>
+            <Form.Item hasFeedback label={fieldLabels.code} {...formItemLayout}>
+              {getFieldDecorator('code', {
+                rules: [
+                  {
+                    required: true, message: '请填写配套编号',
+                  }, {
+                    pattern: /^[a-zA-Z0-9|-]+$/g, message: '编号包含非法字符',
+                  }
+                ],
+                initialValue: code,
+              })(
+                <Input
+                  placeholder="请输入"
+                  disabled={this.isEdit() ? true : false}
+                />
+              )}
+            </Form.Item>
+            {this.isEdit() &&
+              <Form.Item label={fieldLabels.name} {...formItemLayout}>
+                {getFieldDecorator('name', {
+                  initialValue: name,
+                })(
+                  <Input disabled={true} />
+                )}
+              </Form.Item>
+            }
+            <Form.Item hasFeedback label={fieldLabels.title} {...formItemLayout}>
+              {getFieldDecorator('title', {
+                rules: [{ required: true, message: '请填写配套标题' }],
+                initialValue: title,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.subTitle} {...formItemLayout}>
+              {getFieldDecorator('subTitle', {
+                rules: [{ required: true, message: '请填写配套副标题' }],
+                initialValue: subTitle,
+              })(
+                <Input placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item hasFeedback label={fieldLabels.merchant} {...formItemLayout}>
+              {getFieldDecorator('cpId', {
+                rules: [{ required: true, message: '请选择供应商' }],
+                initialValue: cpId,
+              })(
+                <Select placeholder="请选择">
+                  {
+                    getMerchants().map(item =>
+                      <Select.Option key={item.key} value={item.key}>
+                        {item.text}
+                      </Select.Option>
+                    )
+                  }
+                </Select>
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.digest} {...formItemLayout}>
+              {getFieldDecorator('digest', {
+                initialValue: digest,
+              })(
+                <Input.TextArea rows={4} placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.detail} {...formItemLayout}>
+              {getFieldDecorator('detail', {
+                initialValue: detail,
+              })(
+                <Input.TextArea rows={6} placeholder="请输入" />
+              )}
+            </Form.Item>
+            <Form.Item label={fieldLabels.status} {...formItemLayout}>
+              {getFieldDecorator('status', {
+                valuePropName: 'checked',
+                initialValue: statusToBool(status),
+              })(
+                <Switch
+                  checkedChildren="正常"
+                  unCheckedChildren="删除"
+                />
+              )}
+            </Form.Item>
+          </Form>
+        </Card>
+        {/* 封面及走马灯选择Card */}
+        <Card title="封面 | 图册" style={{marginBottom: 16}}>
+          <Row gutter={16}>
+            <Col
+              md={{span: 10, offset: 1}}
+              lg={{span: 8, offset: 2}}
+              xl={{span: 6, offset: 4}}
+              xxl={{span: 4, offset: 5}}
+            >
+              <Card
+                hoverable
+                title={renderCoverCardName()}
+              >
+                <div className={styles.cover}>
+                  <img src={coverUrl && genAbsolutePicUrl(coverUrl)} alt="" />
+                </div>
+                {!coverSelectorDestroy && getResourceModal(true)}
+              </Card>
+            </Col>
+            <Col
+              md={{span: 10, offset: 2}}
+              lg={{span: 8, offset: 4}}
+              xl={{span: 6, offset: 4}}
+              xxl={{span: 4, offset: 5}}
+            >
+              <Card
+                hoverable
+                title={renderCarouselCardName()}
+              >
+                <div className={styles.carousel}>
+                  <Carousel autoplay>
+                    {
+                      imgList.map(
+                        (path,index) => (
+                          <img key={index} src={genAbsolutePicUrl(path)} alt="" />
+                        )
+                      )
+                    }
+                  </Carousel>
+                </div>
+                {!carouselSelectorDestroy && getResourceModal(false)}
+              </Card>
+            </Col>
+          </Row>
+        </Card>
+        {/* 相关周边配套选择Card */}
+        <Card title={renderSupportCardName()} style={{marginBottom: 70}}>
+          <RBDragSortTable
+            columns={supportColumns}
+            data={supportList}
+            onChange={(rows) => this.handleDragSortTableChange('support', rows)}
+          />
+          {!supportSelectorDestroy && getSupportModal()}
+        </Card>
+        <FooterToolbar style={{ width: '100%' }}>
+          <Button
+            onClick={this.handlePageBack}
+            style={{ marginRight: 10 }}
+          >取消
+          </Button>
+          <Button
+            type="primary"
+            loading={submitting}
+            onClick={this.handlePageSubmit}
+          >提交
+          </Button>
+        </FooterToolbar>
+      </div>
+    );
+  }
+}

+ 33 - 0
src/routes/Product/Support/SupportCreate.less

@@ -0,0 +1,33 @@
+@import "../../../../node_modules/antd/lib/style/themes/default.less";
+
+.cardName {
+  & > span {
+    display: inline-block;
+    height: 24px;
+    padding: 0 7px;
+    vertical-align: bottom;
+  }
+  :global {
+    .ant-btn-primary {
+      margin-left: 10px;
+    }
+  }
+}
+
+.cover {
+  width: 100%;
+  height: 240px;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.carousel {
+  width: 100%;
+  height: 240px;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}

+ 0 - 0
src/routes/Product/Support/SupportList.js


Неке датотеке нису приказане због велике количине промена