Browse Source

Merge branch 'master' of ssh://10.10.100.21/source/platform-bi-web

zhuth 7 years ago
parent
commit
6b1635ef87

+ 2 - 0
.gitignore

@@ -7,5 +7,7 @@ src/**/*.css
 src/demo.jsx
 gitbook/dev/_book
 gitbook/dev/node_modules
+gitbook/product/_book
+gitbook/product/node_modules
 gitbook/BI商业智能平台 - 产品文档.pdf
 gitbook/BI商业智能平台 - 研发设计文档.pdf

BIN
gitbook/product/asset/image/dataAccess-UI-2.png


BIN
gitbook/product/asset/image/userManagement.png


+ 31 - 0
gitbook/product/permission-implementation.md

@@ -0,0 +1,31 @@
+# 用户模块业务实现方式(初稿、待更新第二版)
+
+## 用户登录验证
+1. 复现现有UAS系统的用户模块,使用相同的密码加盐算法和验证流程,使用相同的token
+2. 验证通过后,获取用户对应的部门和岗位信息
+
+## 对数据源执行数据开放策略
+- 查看可用数据源列表
+    1. 分析者发出查看可用数据源列表的请求后,服务端根据token获取用户的部门、岗位、用户组信息
+    2. 对分析者所请求的数据源执行一次权限判断,筛选出分析者有权使用的数据源
+    3. 为每个符合条件的数据源获得对应的开放策略(包括行策略和列策略),一并返回前端
+    4. 前端提供按钮供分析者查看生效中的数据开放策略
+- 查看单个数据源的数据列信息
+    1. 分析者发出查看单个数据源的数据列信息请求后,服务端根据token获取用户的部门、岗位、用户组信息
+    2. 对分析者所请求的数据源执行一次权限判断流程:
+        - 如果分析者不属于开放对象,则直接报错(备注:理论上分析者在前台无法请求自身完全无开放权限的数据源的数据列信息)
+        - 如果分析者属于开放对象,则获取对应的`列开放策略`,然后筛选出开放的数据列
+
+## 对图表/看板执行图表/看板分发策略
+- 查看图表/看板列表
+    1. 阅读者发出查询图表/看板列表的请求后,服务端根据token获取用户的部门、岗位、用户组信息
+    2. 对列表中每个图表/看板执行一次权限判断流程,筛选出阅读者有权限访问的图表或看板后返回前端
+- 查看单个图表/看板
+    1. 阅读者发出查看单个图表/看板的请求后,服务端根据token获取用户的部门、岗位、用户组信息
+    2. 对阅读者所请求的单个图表/看板执行一次权限判断流程,如果阅读者无权限访问应当返回403,有权限则进入正常业务流程
+
+## 对看板刷新请求执行数据开放策略
+- 分发看板请求刷新数据时应当附带看板作者的用户信息,服务端进行两次判断:
+    1. 先以当前阅读者的身份信息判断其是否有该看板的阅读权限
+    2. 上一条通过后,对`看板作者`的用户信息执行数据开放权限判断(而不是当前用户的图表阅读权限判断)
+- 这种方案可以让看板作者在分发看板时无需考虑阅读者是否有看板内各图表的阅读权限。

+ 132 - 72
gitbook/product/permission.md

@@ -1,111 +1,171 @@
 # 用户权限模型的设计概念(初稿)
+## 角色
+- 阅读者:系统全体用户均为阅读者,可以作为图表/看板的分发对象,阅读分析者制作的图表/看板信息。
+- 分析者:由管理员定义,可以制作图表和看板,使用图表/看板分发策略将作品分发给指定用户(第三期预定:可以上传csv/xlsx作为数据源)。
+- 管理员:系统部署后的根账号为始祖管理员,后续可以新增管理员;可以新增数据源,并使用数据开放策略将数据源开放给分析者,供分析者制作图表/看板。
+    - 注:管理员同时也具有分析者的所有权限
+
+## 策略
+- 策略是本系统的核心概念,共有三种不同的类型:数据开放策略、图表分发策略、看板分发策略。
+- 一条策略由策略对象和策略规则组成,策略对象确定该策略适用的对象,策略规则确定对策略对象进行开放/分发时的具体规则。
+- 每条策略都可以单独设置是否启用,也可以删除单条策略;为操作方便,策略管理界面中可以点击按钮复制策略。
+- 每个数据源都有一套`数据开放策略`,其策略对象决定了谁有权限查看并使用该数据源制作图表,策略规则决定了其获得的数据源开放的程度。
+    - 创建数据源后会进行数据开放策略初始化:数据源创建者成为该数据源的第一条开放策略的策略对象,对应的策略规则是“所有数据”。
+    - 数据源创建完成之后,创建者需进一步添加新策略将数据开放给系统中的分析者;只有分析者可以成为数据开放策略的对象。
+    - 客户端尝试获取可用数据源列表时,服务端会验证该用户是否是分析者,验证失败则拒绝服务,验证成功则会查询对该分析者开放的数据源并形成列表返回。
+    - 客户端尝试使用一个数据源时,服务端会验证请求者是否为该数据源的开放对象,验证失败则拒绝服务,验证成功则会获取对应的数据开放策略规则,对数据进行处理后返回。
+- 每个图表都有一套`图表分发策略`, 其策略对象决定了谁有权限查看该图表,策略规则决定了其查看图表时对应的数据开放的程度。
+    - 创建数据源后会进行图表分发策略初始化:图表的拥有者成为该图表的第一条分发策略的策略对象,对应的策略规则是“所有数据”。
+    - 图表创建完成之后,拥有者需进一步添加新策略将图表分发给系统中的阅读者。
+    - 客户端尝试获取可用图表列表时,服务端会查询分发给该阅读者的图表并形成列表返回。
+    - 客户端尝试访问一个图表时,后端会验证请求者是否为该图表的分发对象,验证失败则拒绝服务,验证成功则会获取对应的图表分发策略规则以及该图表的拥有者所适用的数据开放策略,对数据进行处理后返回。
+    - 备注:
+        - 客户端访问图表时的数据形态相较于数据源经过了两次过滤处理:
+            - 1)数据源开放给分析者时经过了数据开放策略规则处理; 
+            - 2)图表分发给阅读者时经过了图表分发策略规则处理。
+        - 基于这种设计,整个流程以继承的方式实现了对数据流动的控制:阅读者在图表中`最终所接触到的数据`是`对该图表作者开放的数据`的子集,而`对该图表作者开放的数据`是`该数据源所有数据`的子集
+- 每个看板都有一套`看板分发策略`,其策略对象决定了谁有权限查看该图表,策略规则决定了其查看看板时对应的数据开放的程度。
+    - 创建数据源后会进行看板分发策略初始化:看板的拥有者成为该看板的第一条分发策略的策略对象,对应的策略规则是“所有数据”。
+    - 看板创建完成之后,拥有者需进一步添加新策略将看板分发给系统中的阅读者。
+    - 客户端尝试获取可用看板列表时,服务端会查询分发给该阅读者的看板并形成列表返回。
+    - 客户端尝试访问一个看板时,后端会验证请求者是否为该看板的分发对象,验证失败则拒绝服务,验证成功则会获取对应的看板分发策略规则以及该看板的拥有者所适用的数据开放策略,对数据进行处理后返回。
+        - 备注:
+        - 客户端访问看板时的数据形态相较于数据源经过了两次过滤处理:
+            - 1)数据源开放给分析者时经过了数据开放策略规则处理; 
+            - 2)看板分发给阅读者时经过了看板分发策略规则处理。
+        - 基于这种设计,整个流程以继承的方式实现了对数据流动的控制:阅读者在看板中的某一个图表元素`最终所接触到的数据`是`对该图表作者开放的与该图表元素对应的数据`的子集,而`对该图表作者开放的与该图表元素对应的数据`是`与该图表元素对应的该数据源所有数据`的子集。
+        - 此处考虑单个看板中的图表元素可能对应不同数据源的情况,每个图表元素的请求均相互独立;设计看板分发策略时,每个使用到的数据源都需要设置分发规则
+        - 与请求图表时不同,每个图表元素请求数据时,服务端无需验证请求者是否是该图表对应元素的分发对象,而是验证该看板的拥有者是否该图表对应元素的分发对象。
+            - 因此,看板分发到阅读者,只要看板的拥有者有正常查看对应图表的权限,阅读者应当能正常查看该图表元素,无需另行分发图表。
+
+
 ## 人员信息的导入与整理
 1. 使用UAS的用户验证机制校验密码,并且获取用户对应的姓名、部门和岗位字段
 2. 引入用户组概念,用户表下以用户组ID列表的方式标注其归属的用户组,一个用户可以属于多个组
 
-## 设置用户组
-1. 用户系统接入后,应当指定一个用户为管理员(即属于`管理员`用户组),由管理员来初始化`分析者`用户组
-2. 管理员设置用户组时,可以用以下方式筛选用户:
+## 用户组概念与管理
+1. 系统部署后,自动设有一个根账号作为(原始)管理员(管理员也可以创建其他管理员),
+2. 系统中每个用户都是`阅读者`;在此之上,管理员可以将任何用户设定为`分析者`。
+3. 为了实现系统的策略设计,系统引入了用户组的概念用于定位策略对象,由管理员管理用户组
+4. 引入用户组概念后,设置策略对象时可以用以下方式筛选用户:
     1. 用户名/姓名
     2. 部门
     3. 部门 + 岗位
     4. 用户组
-3. 勾选目标用户后可以批量加入或移出用户组
+5. 勾选目标用户后可以批量加入或移出用户组
 
-## 业务流程
+## 核心业务流程
 1. 管理员配置好数据源后,管理员默认获得所有的权限
 2. 管理员设计数据开放策略,将数据源分发给分析者
 3. 分析者基于自己拥有的数据权限制作图表,然后设计图表分发策略将信息将图表分发给阅读者
+4. 分析者基于自己制作的图表制作看板,然后设计看板分发策略将看板分发给阅读者
 
 ## 数据开放策略设计流程
-1. 设置开放对象(分析者):
+1. 使用对象匹配器设置开放对象(分析者):
     1. 开放对象必须属于`分析者`用户组
-    2. 开放对象由多条判断语句定义,每条判断语句匹配到具体的用户,最后取其与并集
-    3. 判断语句中可以按照四种方式定位应用对象:
-        1. 用户名/姓名
-        2. 部门
-        3. 部门 + 岗位
-        4. 用户组
+    2. `注:需进一步讨论对象匹配器设计`开放对象由多条判断语句定义,每条判断语句匹配到具体的用户,最后取其与并集
+    3. 判断语句中可以按照以下方式定位应用对象:
+        1. 用户名
+        2. 姓名
+        3. 部门
+        4. 部门 + 岗位
+        5. 用户组
 2. 设置开放策略:
-    1. 数据开放策略由`行策略`和`列策略`组成
+    1. 数据开放策略由`行策略`和`列策略`组成。`(注: 第二期暂时不实装定义列策略)`
     2. 行策略由多条判断语句定义,以筛选的方式作用于当前的数据,最后取其交集
     3. 列策略由多选框定义,默认开放全部列,通过操作定向屏蔽列
     4. 可以引入部门/岗位/用户组作为变量用于编写判断语句
 3. 备注:
-    1. 若出现分析者适用多条数据开放策略的情况,其具体获得的数据取并集
-        - 例如: 假设策略规定部门A的分析者只能获取部门A的数据、部门B的分析者只能获取部门B的数据, 若有员工既属于部门A也属于部门B, 则该员工能获取部门A和部门B的数据。
+    1. 根据常见场景需要,数据开放策略初始化后,创建者可以选择启用`“完全开放”`选项,启用后原有的策略不再生效,而是执行一条对象为全体而规则为所有数据的策略。
+    2. 当管理员创建一个数据源却没有改动数据开放策略的话,提醒管理员“按照当前策略,只有管理员自己能够使用这个数据源。如果需要对其他人开放的话需要添加策略。”
+    3. `注:第二期中只考虑适用单条开放策略的情况`~~若出现分析者适用多条数据开放策略的情况,其具体获得的数据取并集~~
+        - ~~例如: 假设策略规定部门A的分析者只能获取部门A的数据、部门B的分析者只能获取部门B的数据, 若有员工既属于部门A也属于部门B, 则该员工能获取部门A和部门B的数据。~~
 
 
 ## 图表分发策略设计流程
-1. 设置分发对象(阅读者):
-    1. 分发对象由多条判断语句定义,每条判断语句匹配到具体的用户,最后取其并集
-    2. 判断语句中可以按照四种方式定位分发对象:
-        1. 用户名/姓名
-        2. 部门
-        3. 部门 + 岗位
-        4. 用户组
+1. 使用对象匹配器设置分发对象(阅读者):
+    1. `注:需进一步讨论对象匹配器设计`分发对象由多条判断语句定义,每条判断语句匹配到具体的用户,最后取其并集
+    2. 判断语句中可以按照以下方式定位分发对象:
+        1. 用户名
+        2. 姓名
+        3. 部门
+        4. 部门 + 岗位
+        5. 用户组
 2. 设置分发策略:
-    1. 分发策略只是行策略,没有列策略
-    2. 分发策略由多条判断语句定义,以筛选的方式作用于当前的数据,最后取其交集
-    3. 可以引入部门/岗位/用户组作为变量用于编写判断语句
+    1. 分发策略只是行策略,没有列策略
+    2. 分发策略由多条判断语句定义,以筛选的方式作用于当前的数据,最后取其交集
+    3. 可以引入部门/岗位/用户组作为变量用于编写判断语句
 3. 备注:
-    1. 若出现阅读者适用多条图表分发策略的情况,其具体可阅读的图表取并集
-        - 例如: 假设策略规定部门A的阅读者只能阅读图表1、部门B的阅读者只能阅读图表2, 若有员工既属于部门A也属于部门B, 则该员工能获阅读图表1和图表2。
+    1. 分发对象打开图表后请求数据时,服务端验证请求者是否属于分发对象,若不属于则拒绝请求,若属于则获取对应的分发策略筛选条件和该图表拥有者适用的数据开放策略,对数据进行处理后返回。
+    2. 当分析者创建一个图表却没有改动图表分发策略的话,提醒分析者“按照当前策略,只有你自己能够查看这个图表。如果需要分发给其他用户需要添加策略。”
+    3. `注:第二期中只考虑适用图表分发策略的情况` ~~若出现阅读者适用多条图表分发策略的情况,其具体可阅读的图表取并集。~~
+        - ~~例如: 假设策略规定部门A的阅读者只能阅读图表1、部门B的阅读者只能阅读图表2, 若有员工既属于部门A也属于部门B, 则该员工能获阅读图表1和图表2。~~
 
 
 ## 看板分发策略设计流程
-1. 设置分发对象(阅读者):
-    1. 分发对象由多条判断语句定义,每条判断语句匹配到具体的用户,最后取其并集
-    2. 判断语句中可以按照四种方式定位应用对象:
-        1. 用户名/姓名
-        2. 部门
-        3. 部门 + 岗位
-        4. 用户组
+1. 使用对象匹配器设置分发对象(阅读者):
+    1. `注:需进一步讨论对象匹配器设计`分发对象由多条判断语句定义,每条判断语句匹配到具体的用户,最后取其并集
+    2. 判断语句中可以按照以下方式定位应用对象:
+        1. 用户名
+        2. 姓名
+        3. 部门
+        4. 部门 + 岗位
+        5. 用户组
 2. 设置分发策略:
     1. 分发策略由多条判断语句定义,以筛选的方式作用于当前的数据,最后取其交集
     2. 可以引入部门/岗位/用户组作为变量用于编写判断语句
 3. 备注:
-    1. 若出现阅读者适用多条看板分发策略的情况,其具体可阅读的看板取并集
-        - 例如: 假设策略规定部门A的阅读者只能阅读看板1、部门B的阅读者只能阅读看板2, 若有员工既属于部门A也属于部门B, 则该员工能获阅读看板1和看板2。
-
-
-# 业务实现方式
-
-## 用户登录验证
-1. 复现现有UAS系统的用户模块,使用相同的密码加盐算法和验证流程,使用相同的token
-2. 验证通过后,获取用户对应的部门和岗位信息
-
-## 对数据源执行数据开放策略
-- 查看可用数据源列表
-    1. 分析者发出查看可用数据源列表的请求后,服务端根据token获取用户的部门、岗位、用户组信息
-    2. 对分析者所请求的数据源执行一次权限判断,筛选出分析者有权使用的数据源
-    3. 为每个符合条件的数据源获得对应的开放策略(包括行策略和列策略),一并返回前端
-    4. 前端提供按钮供分析者查看生效中的数据开放策略
-- 查看单个数据源的数据列信息
-    1. 分析者发出查看单个数据源的数据列信息请求后,服务端根据token获取用户的部门、岗位、用户组信息
-    2. 对分析者所请求的数据源执行一次权限判断流程:
-        - 如果分析者不属于开放对象,则直接报错(备注:理论上分析者在前台无法请求自身完全无开放权限的数据源的数据列信息)
-        - 如果分析者属于开放对象,则获取对应的`列开放策略`,然后筛选出开放的数据列
-
-## 对图表/看板执行图表/看板分发策略
-- 查看图表/看板列表
-    1. 阅读者发出查询图表/看板列表的请求后,服务端根据token获取用户的部门、岗位、用户组信息
-    2. 对列表中每个图表/看板执行一次权限判断流程,筛选出阅读者有权限访问的图表或看板后返回前端
-- 查看单个图表/看板
-    1. 阅读者发出查看单个图表/看板的请求后,服务端根据token获取用户的部门、岗位、用户组信息
-    2. 对阅读者所请求的单个图表/看板执行一次权限判断流程,如果阅读者无权限访问应当返回403,有权限则进入正常业务流程
-
-## 对看板刷新请求执行数据开放策略
-- 分发看板请求刷新数据时应当附带看板作者的用户信息,服务端进行两次判断:
-    1. 先以当前阅读者的身份信息判断其是否有该看板的阅读权限
-    2. 上一条通过后,对`看板作者`的用户信息执行数据开放权限判断(而不是当前用户的图表阅读权限判断)
-- 这种方案可以让看板作者在分发看板时无需考虑阅读者是否有看板内各图表的阅读权限。
+    1. 当分析者创建一个看板却没有改动看板分发策略的话,提醒管理员“按照当前策略,只有你自己能够查看这个看板。如果需要分发给其他用户需要添加策略。”
+    2. `注:第二期中只考虑适用看板分发策略的情况`~~若出现阅读者适用多条看板分发策略的情况,其具体可阅读的看板取并集~~
+        - ~~例如: 假设策略规定部门A的阅读者只能阅读看板1、部门B的阅读者只能阅读看板2, 若有员工既属于部门A也属于部门B, 则该员工能获阅读看板1和看板2。~~
+
+# 辅助性流程
+## 移交
+- 当需要移除/注销/禁用用户时,需确保该账号下没有(作者为该账号的)图表;如果不想删除该图表,可以选择使用移交功能
+- 当被移除/注销/禁用的用户是某个数据源的拥有者(例如需要转交管理员,或者用户有上传个人数据源),也需要先删除或移交数据源
+- 移交数据源时,除了数据源的拥有者需要改变之外,应当自动触发一个流程将移交对象直接升级到开放所有数据的策略对象中。
+    - 此处原拥有者不属于数据开放对象, 例如:A是原拥有者;A将数据源移交给B后,B成为拥有者,只有B在开放所有数据的策略对象中
+    - 由于B已经成为拥有者,若有需要,B可以改变A所适用的数据开放策略。
+- 此处数据源和图表的移交过程相互独立是为了充分解耦。如果过程中出现了权限问题,应由管理员去协调。
+
+## 管理员兼任分析员
+- 理论上管理员和分析员是解耦的,但实际情况中往往会出现管理员需要兼任分析员
+- 此时管理员应当充分意识到自己拥有数据源的全部权限(数据源必然对其拥有者开放所有的数据),在分发图表/看板时妥善设计分发策略
+
+## 管理员帮助分析员编辑其图表/看板
+- 虽然管理员与分析员解耦,实际场景中往往会出现分析员制作的图表/看板无法正常工作的情况,此时管理员应当有权限强行通过认证修改图表(建议设计一个“技术支持模式”)
+- 此时管理员能单独修改改图表对应的数据源的数据开放策略以及图表/看板分发策略,包括策略对象与策略规则。
+
+
 
 
 # 界面元素和原型图(待更新)
 ### 数据开放策略设置界面
-![数据开放策略设置界面](asset/image/dataAccess-UI.png)
+![数据开放策略设置界面-1](asset/image/dataAccess-UI.png)
+![数据开放策略设计界面-2](asset/image/dataAccess-UI-2.png)
 
 ### 图表/看板分发策略设置界面
-![数据开放策略设置界面-1](asset/image/distributeAccess-UI-1.png)
-![数据开放策略设置界面-2](asset/image/distributeAccess-UI-2.png)
+![数据开放策略设置界面-2](asset/image/distributeAccess-UI-2.png)
+
+
+### 系统管理员 - 用户管理
+![用户管理](asset/image/userManagement.png)
+
+
+
+
+### 其他
+- 数据源层面数据列的启用与否是为了排除无用字段,而数据开放策略中的列开放策略屏蔽与否是为了控制数据机密性
+- 分析者对数据源无法操作,因此隐藏下拉菜单中属性设置的选项
+- 除管理员外其他人没有添加数据库型数据源的权限,只能添加文件
+- 除分发外应当有一个功能可以暂时性地直接通过链接授权访问,暂定名称为“共享”
+
+
+##### BI系统接入UAS系统
+用户注册时可以选择使用UAS账号注册(在UAS系统里相应的位置可以直接提供连接) 点击后验证UAS账号密码,验证通过后将部门岗位姓名等信息带过来储存于BI的用户系统
+- 如果发生UAS内部门岗位信息变化的情况,可以请求同步信息
+
+
+
+
+

+ 18 - 1
src/components/admin/userGroupManagement.jsx

@@ -1,10 +1,27 @@
 import React from 'react';
 
+const columns = [{
+    title: '用户组名',
+    dataIndex: 'userGroupName',
+}, {
+    title: '受保护',
+    dataIndex: 'protected',
+}, {
+    title: '操作',
+    dataIndex: 'operation'
+}];
+
+const data = [{
+    key: 1,
+    userGroupName: '管理员',
+    protected: true,
+}]
+
 class UserGroupManagement extends React.Component {
     constructor(props) {
         super(props);
         this.state = {
-
+            selectedRowKeys: []
         }
     }
 

+ 66 - 1
src/components/admin/userManagement.jsx

@@ -1,16 +1,81 @@
 import React from 'react';
+import { Table, Button, Tag } from 'antd'
+
+
+const columns = [{
+    title: '用户名',
+    dataIndex: 'username',
+}, {
+    title: '姓名',
+    dataIndex: 'fullname',
+}, {
+    title: '邮箱',
+    dataIndex: 'email',
+}, {
+    title: '部门',
+    dataIndex: 'department',
+}, {
+    title: '岗位',
+    dataIndex: 'position',
+}, {
+    title: '系统权限组',
+    dataIndex: 'systemGroup',
+    render: systemGroup => systemGroup.map(item => <Tag color="magenta">{item}</Tag> )
+
+}, {
+    title: '用户组',
+    dataIndex: 'userGroup',
+    render: userGroup => userGroup.map(item => <Tag color="magenta">{item}</Tag> )
+}];
+
+const data = [{
+    key: 1,
+    username: 'xiaoct',
+    fullname: '肖春腾',
+    email: 'xiaoct@usoftchina.com',
+    department: 'UAS开发部',
+    position: '软件工程师',
+    systemGroup: ['管理员', '分析者'],
+    userGroup: ['BI项目组', '产品组']
+}, ]
+
 
 class UserManagement extends React.Component {
     constructor(props) {
         super(props);
         this.state = {
+            selectedRowKeys: []
 
         }
     }
 
+    onSelectChange = (selectedRowKeys) => {
+        console.log('selectedRowKeys changed: ', selectedRowKeys);
+        this.setState({ selectedRowKeys });
+    }
+
     render() {
+        const { selectedRowKeys } = this.state;
+        const rowSelection = {
+            selectedRowKeys,
+            onChange: this.onSelectChange,
+        };
+        const hasSelected = selectedRowKeys.length > 0;
         return (
-            <div>用户管理</div>
+            <div>
+                <Table rowSelection={rowSelection} columns={columns} dataSource={data} title={() =>
+                    <div>
+                        <Button
+                            type="primary"
+                            // onClick={this.start}
+                            disabled={!hasSelected}
+                        // loading={loading}
+                        >
+                            设置用户组
+                        </Button>
+                    </div>    
+                }/>
+            </div>
         );
     }
 }

+ 15 - 5
src/components/chart/distributeBox.jsx

@@ -1,5 +1,5 @@
 import React from 'react'
-import { Modal, Table, Col, Row, Button, Input} from 'antd'
+import { Modal, Table, Col, Row, Button, Input, Icon} from 'antd'
 import { connect } from 'dva'
 
 const Search = Input.Search;
@@ -7,15 +7,25 @@ const Search = Input.Search;
 const columns = [{
     title: '策略名',
     dataIndex: 'policyName',
-    render: policyName => <span>{policyName}</span>
+    render: policyName => <span>{policyName}<Icon type='copy'/></span>
 }, {
     title: '分发对象',
     dataIndex: 'targets',
-    render: targets => {return targets.map(item => {return <p>{item.value}</p>})}
+    render: targets => {return (
+        <div>
+            {targets.map(item => {return <p>{item.value}</p>})}
+            <Button>添加对象</Button>
+        </div>
+    )}
 }, {
     title: '行开放策略',
     dataIndex: 'rowFilters',
-    render: rowFilters => {return rowFilters.map(filter => {return <p>{filter.value}</p>})}
+    render: rowFilters => {return (
+        <div>
+            {rowFilters.map(filter => {return <p>{filter.value}</p>})}
+            <Button>设置规则</Button>
+        </div>
+    )}
 }]
 
 const data = [{
@@ -33,7 +43,7 @@ class DistributeBox extends React.Component {
         this.state = {
             filterLabel: '',
         }
-    }
+    } 
     render() {
         const { selectedRecord } = this.state;
         const { dataSource, visibleDistributeBox, hideBox } = this.props

+ 104 - 49
src/components/datasource/accessConfig.jsx

@@ -1,64 +1,119 @@
 import React from 'react'
 import { connect } from 'dva'
 import '../../models/dataSource'
-import { Table, Input, Col, Button } from 'antd'
+import { Table, Input, Col, Button, Switch, Icon } from 'antd'
+import PolicyRuleBox from './policyRuleBox'
+import AccessObjectBox from './accessObjectBox'
 
 const Search = Input.Search;
 
-const DataSourceAccessConfig = ({ dataSource, dispatch }) => {
+class DataSourceAccessConfig extends React.Component{
+    constructor(props){
+        super(props);
+        this.state={
+            visibleAccessObjectBox: false,
+            visiblePolicyRuleBox: false
+        }
+    } 
 
-    const columns = [{
-        title: '策略名',
-        dataIndex: 'policyName',
-        render: policyName => <span>{policyName}</span>
-    }, {
-        title: '开放对象',
-        dataIndex: 'targets',
-        render: targets => {return targets.map(item => {return <p>{item.value}</p>})}
-    }, {
-        title: '行开放策略',
-        dataIndex: 'rowFilters',
-        render: rowFilters => {return rowFilters.map(filter => {return <p>{filter.value}</p>})}
-    }, {
-        title: '列开放策略',
-        dataIndex: 'columns',
-        render: columns => {return columns.filter((column) => !column.enabled).map(column => {return <p>屏蔽:{column.alias}</p>})}
-    }];
+    hideAccessObjectBox= () => {
+        this.setState(
+            {visibleAccessObjectBox:false})
+    }
 
-    const data = [{
-        key: '1',
-        policyName: '开放策略1',
-        targets: [{value:'部门为销售'}, {value:'部门为售后'}],
-        rowFilters: [{value:'部门字段包含售后,销售'}, {value:'公司名包含公司A'}],
-        columns: [{name:'department', alias:'部门', enabled: true}, {name:'company', alias:'公司', enabled: false}]
-    }];
+    hidePolicyRuleBox= () => {
+        this.setState(
+            {visiblePolicyRuleBox:false})
+    }
 
-    return (
-        <Table
-            columns={columns}
-            dataSource={data}
-            bordered
-            title={() => {
+
+    render() {
+        const { dataSource, dispatch } = this.props
+        const { visibleAccessObjectBox, visiblePolicyRuleBox } = this.state
+        const columns = [{
+            title: '策略名',
+            dataIndex: 'policyName',
+            render: policyName => <span>{policyName}<Icon type='copy' /></span>
+        }, {
+            title: '开放对象',
+            dataIndex: 'targets',
+            render: targets => {
                 return (
-                    <div style={{display:"flex", justifyContent:"space-between"}}>
-                        <Col>
-                            <Search  
-                                style={{display:"inline"}}
-                                placeholder="input search text"
-                                onSearch={value => console.log(value)}
-                                enterButton
-                            />
-                        </Col>
-                        <Col>
-                            <Button>显示影响</Button>
-                            <Button>新增策略</Button>
-                        </Col>
-                    </div>) }
+                    <div>
+                        {targets.map((item,index) => { return <p key={index}>{item.value}</p> })}
+                        <Button onClick={() => {this.setState({visibleAccessObjectBox:true})}}>添加对象</Button>
+                    </div>
+                )
             }
-        />
-    );
-}
+        }, {
+            title: '行开放策略',
+            dataIndex: 'rowFilters',
+            render: rowFilters => {
+                return (
+                    <div>
+                        {rowFilters.map((filter, index) => { return <p key={index}>{filter.value}</p> })}
+                        <Button onClick={() => {this.setState({visiblePolicyRuleBox:true})}}>设置策略</Button>
+                    </div>
+                )
+            }
+        }, {
+            title: '列开放策略',
+            dataIndex: 'columns',
+            render: columns => { return columns.filter((column) => !column.enabled).map((column, index) => { return (<span key={index}>全部开放</span>) }) }
+        }];
+
+        const data = [{
+            key: '1',
+            policyName: '开放策略1',
+            targets: [{ value: '部门为销售' }, { value: '部门为售后' }],
+            rowFilters: [{ value: '部门字段包含售后,销售' }, { value: '公司名包含公司A' }],
+            columns: [{ name: 'department', alias: '部门', enabled: true }, { name: 'company', alias: '公司', enabled: false }]
+        }];
 
+        return (
+            <div>
+                <Table
+                    columns={columns}
+                    dataSource={data}
+                    bordered
+                    title={() => {
+                        return (
+                            <div style={{ display: "flex", justifyContent: "space-between" }}>
+                                <Col span={4}>
+                                    <Search
+                                        style={{ display: "inline" }}
+                                        placeholder="input search text"
+                                        onSearch={value => console.log(value)}
+                                        enterButton
+                                    />
+                                </Col>
+                                <Col span={4}>
+                                    <Switch></Switch><span>完全开放</span>
+                                </Col>
+                                <Col span={8}>
+                                </Col>
+                                <Col span={4}>
+                                    <Button>显示影响</Button>
+                                    <Button>新增策略</Button>
+                                </Col>
+                            </div>)
+                    }
+                    }
+                />
+            <AccessObjectBox 
+                visibleAccessObjectBox={visibleAccessObjectBox} 
+                onCancel={this.hideAccessObjectBox}
+            />
+            <PolicyRuleBox 
+                visiblePolicyRuleBox={visiblePolicyRuleBox}
+                onCancel={this.hidePolicyRuleBox}
+            
+            />
+            </div>
+        );
+            }
+        }
+        
 function mapStateToProps({ present: {dataSource} }) {
     return { dataSource }
 }

+ 19 - 0
src/components/datasource/accessObjectBox.jsx

@@ -0,0 +1,19 @@
+import React from 'react'
+import { Modal } from 'antd'
+
+
+class AccessObjectBox extends React.Component {
+    render() {
+        const { visibleAccessObjectBox, onCancel} = this.props
+        return (
+            <Modal
+                visible={visibleAccessObjectBox}
+                onCancel={onCancel}
+            >
+                <span>Test</span>
+            </Modal>
+        )
+    }
+}
+
+export default AccessObjectBox

+ 19 - 0
src/components/datasource/policyRuleBox.jsx

@@ -0,0 +1,19 @@
+import React from 'react'
+import { Modal } from 'antd'
+
+
+class PolicyRuleBox extends React.Component {
+    render() {
+        const { visiblePolicyRuleBox, onCancel } = this.props
+        return (
+            <Modal 
+                visible={visiblePolicyRuleBox}
+                onCancel={onCancel}
+            >
+                <span>Test</span>
+            </Modal>
+        )
+    }
+}
+
+export default PolicyRuleBox

+ 2 - 0
src/routes/router.js

@@ -9,6 +9,7 @@ import DashboardDesigner from '../components/dashboardDesigner/layout'
 // 由于 antd 组件的默认文案是英文,所以需要修改为中文
 import zhCN from 'antd/lib/locale-provider/zh_CN'
 import Demo from '../demo';
+import Xiaomi from '../xiaomi'
 
 if (!window.localStorage.getItem("isAuthenticated")) {
     window.localStorage.setItem("isAuthenticated", "false");
@@ -30,6 +31,7 @@ function RouterConfig({ history }) {
                     <Route sensitive path='/login' component={Login} />
                     <Route sensitive path='/register' component={Register} />
                     <Route sensitive path='/demo' component={Demo} />
+                    <Route sensitive path='/xiaomi' component={Xiaomi} />
                     <PrivateRoute sensitive path='/chart/:code' component={ChartDesigner} />
                     <PrivateRoute sensitive path='/dashboard/:code/' component={DashboardDesigner} />
                     <PrivateRoute path='/' component={MainLayout} />

+ 421 - 0
src/xiaomi.jsx

@@ -0,0 +1,421 @@
+import React from 'react'
+import { Table, Row, Col } from 'antd'
+import Echarts from 'echarts-for-react'
+
+const columns = [
+    { title: '订单号', dataIndex: 'orderID', key: 'orderID', width:'100px' },
+    { title: '订单状态', dataIndex: 'status', key: 'status', width:'100px' },
+    { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width:'100px'},
+    { title: '进度', dataIndex: 'progress', key: 'progress', render: (item, record) => { return (<Echarts style={{ height: '50px', width:'90%' }} option={record.progress}/>)} },
+];
+
+const data = [
+    { key: 1, orderID: '0000001', status: '包装中', customerName: '客户1', 
+        description: {
+            materialStatus: {
+                silent: true,
+                grid: {
+                    top: 0,
+                    left: 0,
+                    right: 0,
+                    bottom: 0,
+                    width: '800px',
+                    containLabel: false
+                },
+                xAxis:  {
+                    type: 'value',
+                    show: false,
+                    max:'dataMax'
+                },
+                yAxis: {
+                    type: 'category',
+                    data: ['备料'],
+                    axisLine:{show: false},
+                    axisLabel: {show: false},
+                    axisTick: {show: false},
+                    max: 'dataMax'
+                },
+                series: [
+                    {
+                        name: '库存齐套',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [380],
+                        barWidth:40,
+                        color: '#2bd54d',
+                    },
+                    {
+                        name: '在途中',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        color: '#e6b600',
+                        data: [820]
+                    },
+                    {
+                        name: '请购下达',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [300]
+                    },
+                ]
+            },
+            scheduleStatus: {
+                silent: true,
+                grid: {
+                    top: 0,
+                    left: 0,
+                    right: 0,
+                    bottom: 0,
+                    width: '800px',
+                    containLabel: false
+                },
+                xAxis:  {
+                    type: 'value',
+                    show: false
+                },
+                yAxis: {
+                    type: 'category',
+                    data: ['备料'],
+                    axisLine:{show: false},
+                    axisLabel: {show: false},
+                    axisTick: {show: false}
+                },
+                series: [
+                    {
+                        name: '工单齐套',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [360],
+                        barWidth:40,
+                        color: '#2bd54d',
+                    },
+            
+                    {
+                        name: '工单未齐套',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [840]
+                    },
+                ]
+            },
+            productionStatus: {
+                silent: true,
+                grid: {
+                    top: 0,
+                    left: 0,
+                    right: 0,
+                    bottom: 0,
+                    width: '800px',
+                    containLabel: false
+                },
+                xAxis:  {
+                    type: 'value',
+                    show: false
+                },
+                yAxis: {
+                    type: 'category',
+                    data: ['备料'],
+                    axisLine:{show: false},
+                    axisLabel: {show: false},
+                    axisTick: {show: false}
+                },
+                series: [
+                    {
+                        name: '已完成',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [340],
+                        color: '#2bd54d',
+                        barWidth:40
+                    },
+            
+                    {
+                        name: '生产中',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        color: '#e6b600',
+                        data: [860]
+                    },
+                    {
+                        name: '物料不足',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [300]
+                    },
+                ]
+            },
+            shipStatus: {
+                silent: true,
+                grid: {
+                    top: 0,
+                    left: 0,
+                    right: 0,
+                    bottom: 0,
+                    width: '800px',
+                    containLabel: false
+                },
+                xAxis:  {
+                    type: 'value',
+                    show: false
+                },
+                yAxis: {
+                    type: 'category',
+                    data: ['备料'],
+                    axisLine:{show: false},
+                    axisLabel: {show: false},
+                    axisTick: {show: false}
+                },
+                series: [
+                    {
+                        name: '已出货',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [400],
+                        color: '#2bd54d',
+                        barWidth:40
+                    },
+            
+                    {
+                        name: '待出货',
+                        type: 'bar',
+                        stack: '总量',
+                        color: '#93b7e3',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [600]
+                    },
+                    {
+                        name: '未完成',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            normal: {
+                                show: true,
+                                position: 'insideRight',
+                                formatter:'{a}:{c}'
+                            }
+                        },
+                        data: [200]
+                    },
+                ]
+            }
+        },
+        progress: {
+            silent: true,
+            grid: {
+                top: 0,
+                left: 0,
+                right: 0,
+                bottom: 0,
+                width: '100%',
+                containLabel: false
+            },
+            xAxis:  {
+                type: 'value',
+                show: false,
+                max:'dataMax'
+            },
+            yAxis: {
+                type: 'category',
+                data: ['备料'],
+                axisLine:{show: false},
+                axisLabel: {show: false},
+                axisTick: {show: false},
+            },
+            series: [
+                {
+                    name: '已出货',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        normal: {
+                            show: true,
+                            position: 'insideRight',
+                            formatter:'{a}:{c}'
+                        }
+                    },
+                    data: [380],
+                    color: '#2bd54d',
+                    barWidth:40
+                },
+                {
+                    name: '待出货',
+                    type: 'bar',
+                    stack: '总量',
+                    color: '#93b7e3',
+                    label: {
+                        normal: {
+                            show: true,
+                            position: 'insideRight',
+                            formatter:'{a}:{c}'
+                        }
+                    },
+                    data: [400]
+                },
+                {
+                    name: '生产中',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        normal: {
+                            show: true,
+                            position: 'insideRight',
+                            formatter:'{a}:{c}'
+                        }
+                    },
+                    color: '#e6b600',
+                    data: [300]
+                },
+                {
+                    name: '待排产',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        normal: {
+                            show: true,
+                            position: 'insideRight',
+                            formatter:'{a}:{c}'
+                        }
+                    },
+                    data: [300]
+                },
+                {
+                    name: '物料在途中',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        normal: {
+                            show: true,
+                            position: 'insideRight',
+                            formatter:'{a}:{c}'
+                        }
+                    },
+                    color: '#ffee51',
+                    data: [300]
+                },
+                {
+                    name: '物料已请购',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        normal: {
+                            show: true,
+                            position: 'insideRight',
+                            formatter:'{a}:{c}'
+                        }
+                    },
+                    data: [300]
+                },
+            ]
+        }
+    },
+];
+
+class Xiaomi extends React.Component {
+    render() {
+        return (
+            <Table
+                columns={columns}
+                expandedRowRender={record => 
+                    <div>
+                        <Row style={{marginBottom:'2px'}}>
+                            <Col span={4} ><span style={{lineHeight:'50px'}}>备料状况</span></Col>
+                            <Col span={20}
+                                ><Echarts style={{ height: '50px' }} option={record.description.materialStatus}/>
+                            </Col>
+                        </Row>
+                        <Row style={{marginBottom:'2px'}}>
+                            <Col span={4} ><span style={{lineHeight:'50px'}}>排产状况</span></Col>
+                            <Col span={20}
+                                ><Echarts style={{ height: '50px' }} option={record.description.scheduleStatus}/>
+                            </Col>
+                        </Row>
+                        <Row style={{marginBottom:'2px'}}>
+                            <Col span={4} ><span style={{lineHeight:'50px'}}>生产状况</span></Col>
+                            <Col span={20}
+                                ><Echarts style={{ height: '50px' }} option={record.description.productionStatus}/>
+                            </Col>
+                        </Row>
+                        <Row style={{marginBottom:'2px'}}>
+                            <Col span={4} ><span style={{lineHeight:'50px'}}>出货状况</span></Col>
+                            <Col span={20}
+                                ><Echarts style={{ height: '50px' }} option={record.description.shipStatus}/>
+                            </Col>
+                        </Row>
+                    </div>
+                }
+                dataSource={data}
+            /> 
+        )
+    }
+}
+
+export default Xiaomi