Browse Source

feat: 增加工作台角色权限控制

lanjianrong 4 years ago
parent
commit
e01ca0664b

+ 8 - 2
config/routes.ts

@@ -43,8 +43,14 @@
       {
         path: '/role/hr',
         name: 'hr',
-        component: './role/Hr'
-        // access: 'authRouteFilter'
+        component: './role/Hr',
+        access: 'authRouteFilter'
+      },
+      {
+        path: '/role/workbench',
+        name: 'workbench',
+        component: './role/workbench',
+        access: 'authRouteFilter'
       }
     ]
   },

+ 1 - 0
src/locales/zh-CN/menu.ts

@@ -8,6 +8,7 @@ export default {
   'menu.role.system': '系统管理',
   'menu.role.customer': '客户',
   'menu.role.hr': '人资',
+  'menu.role.workbench': '工作台',
   'menu.business': '业务参数',
   'menu.business.attendance': '考勤',
   'menu.business.contact': '客户',

+ 3 - 3
src/pages/Role/System/index.tsx

@@ -193,11 +193,11 @@ const System = () => {
                           options={[
                             { value: 'system', label: '系统管理', disabled: !showSystem },
                             { value: 'customer', label: '客户', disabled: !showSystem },
-                            { value: 'business', label: '商机', disabled: !showSystem }
-                            // { value: '3', label: '产品', disabled: !showAuth },
+                            { value: 'business', label: '商机', disabled: !showSystem },
+                            { value: 'hr', label: '人资', disabled: !showSystem },
+                            { value: 'workbench', label: '工作台', disabled: !showSystem }
                             // { value: '4', label: '开票合同', disabled: !showAuth },
                             // { value: '5', label: '考勤', disabled: !showAuth },
-                            // { value: '6', label: '人资', disabled: !showAuth },
                             // { value: '7', label: '财务费用', disabled: !showAuth }
                           ]}
                         />

+ 155 - 0
src/pages/Role/Workbench/components/ConnectModal/index.tsx

@@ -0,0 +1,155 @@
+import React, { useState, useRef, useEffect } from 'react'
+import { useRequest, request } from 'umi'
+import { Button, Input, message, Modal } from 'antd'
+import { fetchStaffList } from '@/services/user/api'
+import { LoadingOutlined, MoreOutlined } from '@ant-design/icons'
+
+interface ConnectModalProps {
+  postUrl: string
+  showButton?: boolean
+  title?: string
+  dataId?: string
+  onReload?: () => void
+  show?: boolean
+  onShowChange?: (isShow: boolean) => void
+}
+const ConnectModal: React.FC<ConnectModalProps> = ({
+  title,
+  dataId,
+  onReload,
+  show,
+  onShowChange,
+  postUrl,
+  showButton = true
+}) => {
+  const containerRef = useRef<HTMLDivElement>(null)
+  const [visible, setVisible] = useState(false)
+  const [searchVal, setSearchVal] = useState('')
+
+  const { run: tryConnectStaff } = useRequest(
+    (params: API.AddRoleStaffCommonParams) =>
+      request(postUrl, {
+        method: 'POST',
+        data: params
+      }),
+    {
+      manual: true,
+      onSuccess: async () => {
+        message.success('关联成功')
+        if (!show) {
+          setVisible(false)
+        }
+        await onReload()
+      },
+      onError: e => {
+        message.error(e.message)
+      }
+    }
+  )
+
+  const {
+    run: tryQueryStaffList,
+    data,
+    noMore,
+    loadMore,
+    loadingMore
+  } = useRequest(
+    result =>
+      fetchStaffList({
+        current: result?.list.length / 10 + 1 || 1,
+        pageSize: 10,
+        search: searchVal
+      }),
+    {
+      manual: true,
+      loadMore: true,
+      ref: containerRef,
+      isNoMore: (res: API.BasicFetchResult<API.StaffItem>) => res.list.length >= res.total,
+      refreshDeps: [searchVal]
+    }
+  )
+
+  const handleSearch = (value: string) => {
+    setSearchVal(value)
+    setTimeout(() => {
+      tryQueryStaffList()
+    }, 250)
+  }
+
+  const { list = [] }: { list: API.StaffItem[] } = data || {}
+  useEffect(() => {
+    if (show || visible) {
+      tryQueryStaffList()
+    }
+  }, [show, visible])
+
+  const handleOnCancel = () => {
+    if (onShowChange !== undefined) {
+      onShowChange(false)
+    } else {
+      setVisible(false)
+    }
+  }
+
+  const itemSelectHandler = async (staffId: string) => {
+    const params = { staffId }
+    if (dataId) {
+      params.id = dataId
+    }
+    await tryConnectStaff(params)
+    handleOnCancel()
+  }
+  return (
+    <>
+      {showButton && (
+        <Button type="primary" onClick={() => setVisible(true)}>
+          {title}
+        </Button>
+      )}
+      <Modal
+        visible={show === undefined ? visible : show}
+        onCancel={handleOnCancel}
+        getContainer={false}
+        width="60vw"
+        title={
+          <Input.Search
+            placeholder="搜索员工(姓名)"
+            onSearch={value => handleSearch(value)}
+            onPressEnter={e => handleSearch(e.currentTarget.value)}
+            style={{ width: '95%' }}
+            allowClear={true}
+          />
+        }>
+        <div ref={containerRef} className="h-60vh overflow-y-auto overflow-x-hidden">
+          {list.map(item => (
+            <div className="group-item-card" key={item.id}>
+              <div className="w-4/3 flex justify-between">
+                <span className="w-1/5">{item.username}</span>
+                <span className="w-2/5">{item.phone}</span>
+                <span className="w-1/5">{item.position}</span>
+                <span className="w-1/5">{item.category}</span>
+              </div>
+              <div className="w-1/4 flex justify-end">
+                <span className="btn-outline" onClick={() => itemSelectHandler(item.id)}>
+                  选择ta
+                </span>
+              </div>
+            </div>
+          ))}
+          {noMore && <div className="text-center text-gray-400">已到底部</div>}
+          {!noMore && (
+            <div className="text-center mt-3 cursor-pointer">
+              {loadingMore ? (
+                <LoadingOutlined />
+              ) : (
+                <MoreOutlined rotate={90} style={{ fontSize: 24 }} onClick={loadMore} />
+              )}
+            </div>
+          )}
+        </div>
+      </Modal>
+    </>
+  )
+}
+
+export default ConnectModal

+ 166 - 0
src/pages/Role/Workbench/components/RoleMenu/index.tsx

@@ -0,0 +1,166 @@
+import React, { useState, useEffect } from 'react'
+import { PlusOutlined, DownOutlined } from '@ant-design/icons'
+import { Button, message, Popconfirm, Popover, Input } from 'antd'
+import { useRequest } from 'umi'
+import {
+  createRoleWithMenuId,
+  fetchRoleListByMenuId,
+  deleteRole,
+  updateStaff
+} from '@/services/user/api'
+import { ModalForm, ProFormText } from '@ant-design/pro-form'
+
+type RoleMenuProps = {
+  menuId: string
+  onSelect?: (id: string) => void
+  itemCount?: number
+}
+
+const RoleMenu: React.FC<RoleMenuProps> = ({ menuId, onSelect, itemCount }) => {
+  const [state, setState] = useState({
+    value: ''
+  })
+  const [activeId, setActiveId] = useState('')
+  const [menuRoles, setMenuRoles] = useState<API.MenuRoleItem[]>([])
+  const { run: tryFetchMenuRoles } = useRequest((id: string) => fetchRoleListByMenuId(id), {
+    manual: true,
+    onSuccess: result => {
+      setMenuRoles(result)
+    }
+  })
+
+  const { run: tryDeleteRole } = useRequest(
+    (id: string) => {
+      if (activeId === id) {
+        setActiveId('')
+      }
+      return deleteRole({ id })
+    },
+    {
+      manual: true,
+      onSuccess: () => {
+        tryFetchMenuRoles(menuId)
+      }
+    }
+  )
+  const { run: tryUpdateStaff } = useRequest(
+    (id: string, name: string) => {
+      if (activeId === id) {
+        setActiveId('')
+      }
+      return updateStaff({ id, name })
+    },
+    {
+      manual: true,
+      onSuccess: () => {
+        message.success('修改成功')
+        tryFetchMenuRoles(menuId)
+      }
+    }
+  )
+
+  const onChangeName = value => {
+    setState({ ...state, name: value })
+  }
+
+  const { run: tryAddRole } = useRequest(
+    (params: API.CreateRoleParams) => createRoleWithMenuId(params),
+    {
+      manual: true,
+      onSuccess: () => {
+        tryFetchMenuRoles(menuId)
+      }
+    }
+  )
+  useEffect(() => {
+    tryFetchMenuRoles(menuId)
+  }, [])
+
+  const handleItemClick = (id: string) => {
+    setActiveId(id)
+    if (onSelect) {
+      onSelect(id)
+    }
+  }
+
+  return (
+    <div className="h-full w-max-234px rounded-4px">
+      <div className="p-4 border-b-1 border-solid border-black border-opacity-10 bg-[#f7f9fa] flex justify-around items-center">
+        <span className="text-[0.9375rem]">角色列表</span>
+
+        <ModalForm<{ name: string; backstageMenuId: string }>
+          title="添加新的角色"
+          width="500px"
+          trigger={
+            <Button size="small" type="primary" ghost>
+              <PlusOutlined />
+              创建角色
+            </Button>
+          }
+          onFinish={async values => {
+            await tryAddRole(values)
+            message.success('添加成功')
+            return true
+          }}
+          initialValues={{ backstageMenuId: menuId }}>
+          <ProFormText name="backstageMenuId" hidden />
+          <ProFormText name="name" rules={[{ required: true, message: '请输入角色名' }]} />
+        </ModalForm>
+      </div>
+      <div className="p-4 bg-white" style={{ height: 'calc(100% - 1rem*2 - 20px)' }}>
+        <ul className="p-0 m-0 list-none text-primary flex flex-col flex-1">
+          {menuRoles.map(item => (
+            <li
+              key={item.id}
+              className={[
+                'flex justify-between items-center py-2 px-5 cursor-pointer',
+                item.id === activeId ? 'scale-up-center' : ''
+              ].join(' ')}
+              onClick={() => handleItemClick(item.id)}>
+              <span>{item.name}</span>
+              <Popover
+                placement="bottomRight"
+                content={
+                  <div className="popoverList">
+                    <ul>
+                      <Popconfirm
+                        title={
+                          <Input
+                            placeholder="角色名称"
+                            name="name"
+                            onChange={e => onChangeName(e.currentTarget.value)}
+                          />
+                        }
+                        okText="确认"
+                        cancelText="取消"
+                        onConfirm={() => tryUpdateStaff(item.id, state.name)}
+                        icon="">
+                        <li>编辑</li>
+                      </Popconfirm>
+                      <Popconfirm
+                        title="确认删除吗?"
+                        okText="确认"
+                        cancelText="取消"
+                        onConfirm={() => {
+                          if (itemCount && itemCount !== 0) {
+                            return message.warning('请先移除已经关联的员工')
+                          }
+                          return tryDeleteRole(item.id)
+                        }}>
+                        <li className="text-red-500">删除</li>
+                      </Popconfirm>
+                    </ul>
+                  </div>
+                }
+                trigger="click">
+                <DownOutlined />
+              </Popover>
+            </li>
+          ))}
+        </ul>
+      </div>
+    </div>
+  )
+}
+
+export default RoleMenu

+ 298 - 0
src/pages/Role/Workbench/index.tsx

@@ -0,0 +1,298 @@
+import { useModel, useRequest } from 'umi'
+import { Delete } from '@icon-park/react'
+import ProForm from '@ant-design/pro-form'
+import type { FormInstance } from 'antd'
+import { Radio, Space } from 'antd'
+import { message, Table, Tabs, Popconfirm, Form } from 'antd'
+import React, { useRef, useMemo, useState, useEffect } from 'react'
+import RoleMenu from './components/RoleMenu'
+import {
+  fetchRoleStaffListByRoleId,
+  // updateRolePermission,
+  getRolePermissions,
+  deleteStaff,
+  updatePermDataByRoleId
+} from '@/services/user/api'
+import type { ColumnsType } from 'antd/lib/table'
+import ConnectModal from './components/ConnectModal'
+// import { formatPermission } from '@/utils/utils'
+
+const permData = [
+  {
+    label: '本人',
+    value: 'oneself'
+  },
+  {
+    label: '本人及下属',
+    value: 'underling'
+  },
+  {
+    label: '本部门',
+    value: 'department'
+  },
+  {
+    label: '本部门及下属部门',
+    value: 'departmentAll'
+  },
+  {
+    label: '全部',
+    value: 'all'
+  }
+]
+
+const Workbench = () => {
+  const { TabPane } = Tabs
+  // const formRef = useRef<FormInstance>(null)
+  const formRef2 = useRef<FormInstance>(null)
+  const { initialState } = useModel('@@initialState')
+  const menuId = useMemo(() => {
+    return initialState?.menuList?.find(item => item.name === '工作台')?.id
+  }, initialState.menuList)
+
+  const [state, setState] = useState({
+    id: '',
+    roleStaff: [],
+    rolePermission: {},
+    activeKey: ''
+  })
+  const onSelect = (id: string) => {
+    setState({ ...state, id })
+  }
+
+  const { run: tryGetRoleStaffList } = useRequest(
+    (id: string) => fetchRoleStaffListByRoleId({ id }),
+    {
+      manual: true,
+      onSuccess: result => {
+        setState({ ...state, roleStaff: result })
+      }
+    }
+  )
+
+  const { run: tryGetRolePermissions } = useRequest((id: string) => getRolePermissions({ id }), {
+    manual: true,
+    onSuccess: (result: API.GetRolePermissionResultModel) => {
+      // const values = { system: [], business: [], ...formatPermission('init', result.permission) }
+      setState({
+        ...state,
+        // rolePermission: values,
+        dataPermission: result.dataPermission ? JSON.parse(result.dataPermission) : null
+      })
+
+      // formRef.current?.setFieldsValue({ ...values })
+
+      formRef2.current?.setFieldsValue(
+        result.dataPermission ? JSON.parse(result.dataPermission) : null
+      )
+    }
+  })
+
+  const { run: tryDeleteStaff } = useRequest(
+    (params: API.DeleteStaff) => {
+      return deleteStaff(params)
+    },
+    {
+      manual: true,
+      onSuccess: () => {
+        message.success('移除员工成功')
+        tryGetRoleStaffList(state.id)
+      }
+    }
+  )
+
+  useEffect(() => {
+    if (state.id) {
+      tryGetRoleStaffList(state.id)
+      tryGetRolePermissions(state.id)
+    }
+    // if (state.activeKey === '2') {
+    //   formRef.current?.setFieldsValue({ ...state.rolePermission })
+    // }
+    if (state.activeKey === '3') {
+      formRef2.current?.setFieldsValue({ ...state.dataPermission })
+    }
+  }, [state.id, state.activeKey])
+
+  const columns: ColumnsType<API.RoleStaffListItem> = [
+    {
+      title: '用户',
+      dataIndex: 'username',
+      width: '15%'
+    },
+    {
+      title: '手机',
+      dataIndex: 'phone',
+      width: '20%'
+    },
+    {
+      title: '部门',
+      dataIndex: 'departmentName',
+      width: '20%'
+    },
+    {
+      title: '岗位',
+      dataIndex: 'position',
+      width: '15%'
+    },
+    {
+      title: '角色',
+      dataIndex: 'age',
+      width: '20%'
+    },
+    {
+      title: '操作',
+      dataIndex: 'opreate',
+      width: '20%',
+      render: (_, record) => (
+        // console.log(record.staffId)
+        <>
+          {state.id && (
+            <Popconfirm
+              title="确认删除吗?"
+              okText="确认"
+              cancelText="取消"
+              onConfirm={() => tryDeleteStaff({ id: state.id, staffId: record.staffId })}>
+              <span className="hover:text-hex-e7026e cursor-pointer">
+                <Delete fill="#fd3995" />
+              </span>
+            </Popconfirm>
+          )}
+        </>
+      )
+    }
+  ]
+
+  return (
+    <div className="h-full w-full flex flex-row">
+      <RoleMenu menuId={menuId} onSelect={onSelect} itemCount={state.roleStaff?.length || 0} />
+      <div className="w-max-3/4">
+        <div className="ml-8 bg-white p-4 shadow-md shadow-hex-3e2c5a relative">
+          <div className="absolute right-4 top-4 z-100">
+            {state.id && (
+              <ConnectModal
+                title="关联员工"
+                dataId={state.id}
+                onReload={() => tryGetRoleStaffList(state.id)}
+                postUrl="/role/staff/add"
+              />
+            )}
+          </div>
+          <Tabs
+            defaultActiveKey="1"
+            type="card"
+            onChange={key => setState({ ...state, activeKey: key })}>
+            <TabPane tab="员工列表" key="1">
+              <Table<API.RoleStaffListItem>
+                dataSource={state.roleStaff}
+                columns={columns}
+                rowKey={row => row.staffId}
+              />
+            </TabPane>
+            {/* <TabPane tab="角色权限" key="2">
+              <div className="ml-4">
+                {state.id && (
+                  <ProForm
+                    layout="horizontal"
+                    formRef={formRef}
+                    onFinish={async values => {
+                      const newValues = formatPermission('submit', values)
+                      await updateRolePermission({
+                        permission: JSON.stringify(newValues),
+                        id: state.id
+                      })
+                      message.success('设置成功')
+                    }}>
+                    <ProFormSwitch
+                      name="showSystem"
+                      fieldProps={{
+                        onChange(checked) {
+                          if (!checked) {
+                            formRef.current?.setFieldsValue({ system: [] })
+                          }
+                        }
+                      }}
+                      label={
+                        <span className="flex items-center">
+                          <EveryUser className="mr-1" className="flex items-baseline mr-1" />
+                          角色权限管理
+                        </span>
+                      }
+                    />
+                    <ProFormDependency name={['showSystem']}>
+                      {({ showSystem }) => (
+                        <ProFormCheckbox.Group
+                          wrapperCol={{ offset: 1 }}
+                          // initialValue={}
+                          name="system"
+                          options={[
+                            { value: 'system', label: '系统管理', disabled: !showSystem },
+                            { value: 'customer', label: '客户', disabled: !showSystem },
+                            { value: 'business', label: '商机', disabled: !showSystem }
+                          ]}
+                        />
+                      )}
+                    </ProFormDependency>
+                    <ProFormSwitch
+                      name="showBusiness"
+                      label={
+                        <span className="flex items-center">
+                          <Association className="mr-1" className="flex items-baseline mr-1" />
+                          业务参数
+                        </span>
+                      }
+                    />
+                    <ProFormDependency name={['showBusiness']}>
+                      {({ showBusiness }) => (
+                        <ProFormCheckbox.Group
+                          wrapperCol={{ offset: 1 }}
+                          name="business"
+                          options={[
+                            { value: 'attendance', label: '考勤', disabled: !showBusiness },
+                            { value: 'contact', label: '客户', disabled: !showBusiness }
+                          ]}
+                        />
+                      )}
+                    </ProFormDependency>
+                  </ProForm>
+                )}
+              </div>
+            </TabPane> */}
+            <TabPane tab="数据权限" key="3">
+              {state.id && (
+                <ProForm
+                  formRef={formRef2}
+                  layout="vertical"
+                  onFinish={async values => {
+                    try {
+                      await updatePermDataByRoleId({
+                        id: state.id,
+                        dataPermission: JSON.stringify(values)
+                      })
+                    } catch (error) {
+                      return message.error(error.toString())
+                    }
+                    message.success('设置成功')
+                    return true
+                  }}>
+                  <Form.Item name="access" label="联系人/客户可见" required>
+                    <Radio.Group>
+                      <Space direction="vertical">
+                        {permData.map(item => (
+                          <Radio key={item.value} value={item.value}>
+                            {item.label}
+                          </Radio>
+                        ))}
+                      </Space>
+                    </Radio.Group>
+                  </Form.Item>
+                </ProForm>
+              )}
+            </TabPane>
+          </Tabs>
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default Workbench