Browse Source

组织管理

shaogen1995 4 months ago
parent
commit
863155e124

+ 2 - 2
src/pages/A_workbench/A1dataSta/data.ts

@@ -209,8 +209,8 @@ export const echartsFu4 = (dom: HTMLDivElement) => {
   const myChart = echarts.getInstanceByDom(dom) || echarts.init(dom)
   const option = {
     grid: {
-      left: '10', //距左边边框的距离
-      right: '10', //距右边边框的距离
+      left: '30', //距左边边框的距离
+      right: '20', //距右边边框的距离
       bottom: '20', //距下面边框的距离
       top: '30', //距上面边框的距离
       containLabel: true

+ 214 - 0
src/pages/Z_system/Z4organization/Z4add.tsx

@@ -0,0 +1,214 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Cascader, Form, FormInstance, Input, InputNumber, Modal } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+import { MessageFu } from '@/utils/message'
+import { TypeZ4Tree } from './type'
+import { Z4_APIgetInfo, Z4_APIsave } from '@/store/action/Z4organization'
+import TextArea from 'antd/es/input/TextArea'
+
+export type Z4AddInfoType = {
+  id: string
+  txt: '新增' | '编辑'
+  acInfo: TypeZ4Tree
+}
+
+type Props = {
+  addInfo: Z4AddInfoType
+  addFu: () => void
+  closeFu: () => void
+}
+
+function Z4add({ addInfo, addFu, closeFu }: Props) {
+  const treeData = useSelector((state: RootState) => state.Z4organization.treeData)
+
+  // 级联选择器改变的时候 筛选当前级联的 信息出来
+  const [acCardInfo, setAcCardInfo] = useState({} as TypeZ4Tree)
+  const [parentIdArr, setParentIdArr] = useState<string[] | null>(null)
+
+  // useEffect(() => {
+  //   console.log(acCardInfo, parentIdArr)
+  // }, [acCardInfo, parentIdArr])
+
+  useEffect(() => {
+    setAcCardInfo(addInfo.acInfo)
+
+    let ids: string[] | null = addInfo.acInfo.id ? [addInfo.acInfo.id] : null
+    if (addInfo.acInfo.ancestor) ids = [...addInfo.acInfo.ancestor.split(','), addInfo.acInfo.id]
+
+    let idsRes = ids
+    if (ids && ids.length >= 1 && addInfo.txt === '编辑') {
+      ids.pop()
+    }
+
+    if (idsRes) idsRes = idsRes.filter((v, i) => v !== '0')
+
+    setParentIdArr(idsRes)
+  }, [addInfo.acInfo, addInfo.txt])
+
+  const cardChange = useCallback((aa: any, bb: any) => {
+    setParentIdArr(aa)
+
+    if (bb && bb.length) setAcCardInfo(bb[bb.length - 1])
+    else setAcCardInfo({} as TypeZ4Tree)
+  }, [])
+
+  const getInfoFu = useCallback(async (id: string) => {
+    const res = await Z4_APIgetInfo(id)
+    if (res.code === 0) {
+      FormBoxRef.current?.setFieldsValue({
+        ...res.data
+      })
+    }
+  }, [])
+  useEffect(() => {
+    if (addInfo.txt === '编辑') getInfoFu(addInfo.id)
+    else {
+      FormBoxRef.current?.setFieldsValue({
+        sort: 999
+      })
+    }
+  }, [addInfo.id, addInfo.txt, getInfoFu])
+
+  // 设置表单初始数据(区分编辑和新增)
+  const FormBoxRef = useRef<FormInstance>(null)
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {
+    // return MessageFu.warning("有表单不符号规则!");
+  }, [])
+
+  // 通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      let ancestor = ''
+      let parentId: string | null = null
+
+      if (parentIdArr && parentIdArr.length >= 5 && addInfo.txt === '新增')
+        return MessageFu.warning('最多支持五级!')
+
+      if (acCardInfo) parentId = addInfo.txt === '编辑' ? acCardInfo.parentId : acCardInfo.id
+      if (parentIdArr && parentId) ancestor = parentIdArr.filter(v => v !== addInfo.id).join(',')
+
+      let level = 1
+      if (parentIdArr) {
+        level = addInfo.txt === '新增' ? acCardInfo.level + 1 : acCardInfo.level
+      }
+
+      // 补0
+      if (addInfo.id === '-1') {
+        // 新增
+        if (!parentId && !ancestor) {
+          ancestor = '0'
+          parentId = '0'
+        } else ancestor = ancestor + '0,'
+      } else {
+        // 编辑
+        if (!ancestor) ancestor = '0'
+        else ancestor = ancestor + '0,'
+      }
+
+      const obj = {
+        ...values,
+        id: addInfo.id === '-1' ? null : addInfo.id,
+        ancestor,
+        level,
+        parentId
+      }
+      // console.log(123, obj)
+      // if (1 + 1 === 2) {
+      //   return
+      // }
+
+      const res = await Z4_APIsave(obj)
+
+      if (res.code === 0) {
+        MessageFu.success(addInfo.txt + '成功!')
+        addFu()
+        closeFu()
+      }
+    },
+    [acCardInfo, addFu, addInfo.id, addInfo.txt, closeFu, parentIdArr]
+  )
+
+  return (
+    <Modal
+      wrapClassName={styles.Z4add}
+      open={true}
+      title={addInfo.txt}
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className='Z4aMain'>
+        <Form
+          scrollToFirstError={true}
+          ref={FormBoxRef}
+          name='basic'
+          labelCol={{ span: 3 }}
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete='off'
+        >
+          <div className='fromRow'>
+            <div className='fromRowll'>上级部门:</div>
+            <div className='fromRowrr'>
+              <Cascader
+                style={{ width: 658 }}
+                disabled={addInfo.txt === '编辑'}
+                changeOnSelect
+                fieldNames={{ label: 'name', value: 'id', children: 'children' }}
+                options={treeData}
+                placeholder={addInfo.txt === '编辑' ? '空' : '请选择'}
+                value={parentIdArr ? [...parentIdArr] : []}
+                onChange={cardChange}
+              />
+            </div>
+          </div>
+
+          <Form.Item
+            label='部门名称'
+            name='name'
+            rules={[{ required: true, message: '请输入部门名称!' }]}
+            getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
+          >
+            <Input maxLength={20} showCount placeholder='请输入内容' />
+          </Form.Item>
+
+          <Form.Item label='部门说明' name='description'>
+            <TextArea maxLength={100} showCount placeholder='请输入内容' />
+          </Form.Item>
+
+          <div className='fromRow2'>
+            <Form.Item
+              label='排序值'
+              name='sort'
+              rules={[{ required: true, message: '请输入排序值!' }]}
+            >
+              <InputNumber min={1} max={999} precision={0} placeholder='请输入' />
+            </Form.Item>
+            <div className='fromRowTit'>
+              请输入1~999的数字。数字越小,排序越靠前。数字相同时,更新发布的内容排在前面
+            </div>
+          </div>
+
+          {/* 确定和取消按钮 */}
+          <br />
+          <Form.Item wrapperCol={{ offset: 9, span: 16 }}>
+            <Button type='primary' htmlType='submit'>
+              提交
+            </Button>
+            &emsp;
+            <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+          </Form.Item>
+        </Form>
+      </div>
+    </Modal>
+  )
+}
+
+const MemoZ4add = React.memo(Z4add)
+
+export default MemoZ4add

+ 121 - 0
src/pages/Z_system/Z4organization/index.module.scss

@@ -6,5 +6,126 @@
     .Z4top {
       text-align: right;
     }
+
+    .Z4main {
+      margin-top: 15px;
+      height: calc(100% - 50px);
+      & > div {
+        height: 100%;
+      }
+      .Z4Null {
+        width: 100%;
+        height: 300px;
+        font-size: 24px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+      .Z4m1 {
+        display: flex;
+        justify-content: space-between;
+        .Z4m1ll {
+          width: 50%;
+          overflow-y: auto;
+          .site-tree-search-value {
+            color: red;
+            font-weight: 700;
+          }
+          .ant-tree {
+            min-height: 100%;
+            padding: 10px;
+          }
+        }
+        .Z4m1rr {
+          width: calc(50% - 24px);
+
+          .Z4mr2 {
+            .Z4mr2Row {
+              display: flex;
+              align-items: center;
+              margin-bottom: 10px;
+              font-size: 20px;
+              .Z4mr2Row1 {
+                font-weight: 700;
+                font-size: 20px;
+              }
+
+              .ant-btn {
+                font-size: 20px;
+                height: 46px;
+              }
+
+              .Z4mr2Row2 {
+                width: calc(100% - 105px);
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+              }
+            }
+            .Z4mr2Row0 {
+              margin-bottom: 0;
+            }
+            .Z4mr2RowLast {
+              align-items: self-start;
+              .Z4mr2Row2 {
+                display: flex;
+                flex-wrap: wrap;
+              }
+            }
+          }
+
+          .Z4mr3 {
+            margin-top: 15px;
+            height: calc(100% - 100px);
+            background-color: red;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 新增弹窗页面
+.Z4add {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .ant-modal {
+      width: 800px !important;
+    }
+
+    .ant-modal-body {
+      border-top: 1px solid #ccc;
+    }
+
+    .Z4aMain {
+      padding-top: 15px;
+      .fromRow2 {
+        position: relative;
+
+        .fromRowTit {
+          position: absolute;
+          left: 200px;
+          top: 5px;
+          color: #999;
+          font-size: 12px;
+        }
+      }
+      .fromRow {
+        display: flex;
+        align-items: center;
+        margin-bottom: 24px;
+        .fromRowll {
+          width: 94px;
+          text-align: right;
+          position: relative;
+        }
+        .fromRowrr {
+          width: calc(100% - 94px);
+        }
+      }
+    }
   }
 }

+ 206 - 6
src/pages/Z_system/Z4organization/index.tsx

@@ -1,21 +1,221 @@
-import React, { useEffect } from 'react'
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
 import styles from './index.module.scss'
-import { Button } from 'antd'
-import { useDispatch } from 'react-redux'
-import { D4_APIgetTree } from '@/store/action/Z4organization'
+import { Button, Input, Tree, TreeDataNode } from 'antd'
+import { useDispatch, useSelector } from 'react-redux'
+import { D4_APIgetTree, Z4_APIdel, Z4_APIgetUserById } from '@/store/action/Z4organization'
+import { RootState } from '@/store'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { MessageFu } from '@/utils/message'
+import { treeResIdFu } from '../Z1dict/data'
+import { TypeZ4Tree, TypeZ4UserList } from './type'
+import Z4add, { Z4AddInfoType } from './Z4add'
 function Z4organization() {
   const dispatch = useDispatch()
 
+  const [loding, setLoding] = useState(false)
+
   useEffect(() => {
-    dispatch(D4_APIgetTree())
+    dispatch(
+      D4_APIgetTree(data => {
+        setLoding(true)
+        if (data && data.length) setAcShu(data[0].id)
+      })
+    )
   }, [dispatch])
 
+  const { treeData: treeDataTemp } = useSelector((state: RootState) => state.Z4organization)
+
+  // 当前选中的树节点ID
+  const [acShu, setAcShu] = useState('')
+
+  // 获取相关成员列表
+  const [userList, setUserList] = useState<TypeZ4UserList[]>([])
+
+  const getUserByIdFu = useCallback(async (id: string) => {
+    const res = await Z4_APIgetUserById(id)
+    if (res.code === 0) {
+      setUserList(res.data)
+    }
+  }, [])
+
+  useEffect(() => {
+    if (acShu) getUserByIdFu(acShu)
+  }, [acShu, getUserByIdFu])
+
+  // 点击树节点
+  const onSelect = (id: any) => {
+    if (id[0]) setAcShu(id[0])
+  }
+
+  // 搜索高亮
+  const [value, setValue] = useState('')
+
+  const treeData = useMemo(() => {
+    const loop = (dataTemp: any[]): TreeDataNode[] => {
+      const data = dataTemp || []
+
+      return data.map(item => {
+        const strTitle = ((item.num ? item.num + ' ' : '') + item.name) as string
+
+        const index = strTitle.indexOf(value)
+
+        const beforeStr = strTitle.substring(0, index)
+        const afterStr = strTitle.slice(index + value.length)
+        const title =
+          index > -1 ? (
+            <span key={item.id}>
+              {beforeStr}
+              <span className='site-tree-search-value'>{value}</span>
+              {afterStr}
+            </span>
+          ) : (
+            <span key={item.id}>{strTitle}</span>
+          )
+        if (item.children) {
+          return { title, key: item.id, children: loop(item.children) }
+        }
+        return {
+          title,
+          key: item.id
+        }
+      })
+    }
+
+    return loop(treeDataTemp)
+  }, [treeDataTemp, value])
+
+  // 右侧信息
+  const rightData = useMemo(() => {
+    let obj = {} as TypeZ4Tree
+
+    obj = treeResIdFu(treeDataTemp, acShu)
+
+    return obj || {}
+  }, [acShu, treeDataTemp])
+
+  // 点击删除
+  const delTree = useCallback(
+    async (id: string) => {
+      const res = await Z4_APIdel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        dispatch(D4_APIgetTree())
+      }
+    },
+    [dispatch]
+  )
+
+  // 点击新增和编辑
+  const [addInfo, setAddInfo] = useState({} as Z4AddInfoType)
+
+  const addSonFu = useCallback(
+    (id: string) => {
+      setAddInfo({
+        id,
+        txt: id === '-1' ? '新增' : '编辑',
+        acInfo: rightData
+      })
+    },
+    [rightData]
+  )
+
   return (
     <div className={styles.Z4organization}>
       <div className='pageTitle'>组织管理</div>
       <div className='Z4top'>
-        <Button type='primary'>新增</Button>
+        <Input value={value} onChange={e => setValue(e.target.value)} hidden />
+        <Button
+          type='primary'
+          onClick={() => setAddInfo({ id: '-1', txt: '新增', acInfo: rightData })}
+        >
+          新增
+        </Button>
+      </div>
+
+      {/* 主体 */}
+      <div className='Z4main'>
+        <div className='Z4m1'>
+          <div className='Z4m1ll'>
+            {treeDataTemp && treeDataTemp.length ? (
+              <Tree
+                // 默认全部展开
+                defaultExpandAll={true}
+                // 数据
+                treeData={treeData}
+                // 自定义字段
+                // fieldNames={{ title: 'title', key: 'key', children: 'children' }}
+                // 选中
+                selectedKeys={[acShu]}
+                // 点击
+                onSelect={onSelect}
+              />
+            ) : loding ? (
+              <div className='Z4Null'>暂无数据</div>
+            ) : null}
+          </div>
+          <div className='Z4m1rr'>
+            {rightData.id ? (
+              <>
+                <div className='Z4mr2'>
+                  <div className='Z4mr2Row Z4mr2Row0'>
+                    <div className='Z4mr2Row1'>操作:</div>
+                    <div>
+                      <Button type='text' onClick={() => addSonFu(rightData.id)}>
+                        编辑
+                      </Button>
+                      &emsp;
+                      <MyPopconfirm txtK='删除' onConfirm={() => delTree(rightData.id)} />
+                    </div>
+                  </div>
+
+                  <div className='Z4mr2Row'>
+                    <div className='Z4mr2Row1'>部门名称:</div>
+                    <div>{rightData.name}</div>
+                  </div>
+
+                  <div className='Z4mr2Row'>
+                    <div className='Z4mr2Row1'>部门说明:</div>
+                    <div className='Z4mr2Row2'>{rightData.description}</div>
+                  </div>
+
+                  <div className='Z4mr2Row'>
+                    <div className='Z4mr2Row1'>层级:</div>
+                    <div>{rightData.level}</div>
+                  </div>
+
+                  <div className='Z4mr2Row'>
+                    <div className='Z4mr2Row1'>排序值:</div>
+                    <div>{rightData.sort}</div>
+                  </div>
+
+                  <div className='Z4mr2Row Z4mr2RowLast'>
+                    <div className='Z4mr2Row1'>相关成员:</div>
+                    <div className='Z4mr2Row2'>
+                      {userList.length
+                        ? userList.map((v, i) => (
+                            <div key={v.userId}>
+                              {v.userName}
+                              {v.isLeader === 1 ? '(部门主管)' : ''}
+                              {i < userList.length - 1 ? ',' : ''}
+                            </div>
+                          ))
+                        : '(空)'}
+                    </div>
+                  </div>
+                </div>
+              </>
+            ) : null}
+          </div>
+        </div>
       </div>
+      {/* 新增/编辑页面 中图法分类 */}
+      {addInfo.id ? (
+        <Z4add
+          addInfo={addInfo}
+          addFu={() => dispatch(D4_APIgetTree())}
+          closeFu={() => setAddInfo({} as Z4AddInfoType)}
+        />
+      ) : null}
     </div>
   )
 }

+ 17 - 0
src/pages/Z_system/Z4organization/type.d.ts

@@ -0,0 +1,17 @@
+export type TypeZ4Tree = {
+  id: string
+  parentId: string
+  sort: number
+  name: string
+  ancestor: string
+  level: number
+  description: string
+  children?: TypeZ4Tree[]
+}
+
+export type TypeZ4UserList = {
+  deptId: number
+  isLeader: number
+  userId: number
+  userName: string
+}

+ 32 - 4
src/store/action/Z4organization.ts

@@ -1,16 +1,44 @@
 import http from '@/utils/http'
 import { AppDispatch } from '..'
+import { TypeZ4Tree } from '@/pages/Z_system/Z4organization/type'
 
 /**
  * 组织管理 - 获取树列表
  */
-export const D4_APIgetTree = (): any => {
+export const D4_APIgetTree = (callFu?: (data: TypeZ4Tree[]) => void): any => {
   return async (dispatch: AppDispatch) => {
     const res = await http.get('sys/dept/getTree')
     if (res.code === 0) {
-      console.log(123, res)
-
-      // dispatch({ type: 'D2/getList', payload: obj })
+      callFu && callFu(res.data)
+      dispatch({ type: 'Z4/getTree', payload: res.data })
     }
   }
 }
+
+/**
+ * 组织管理 - 删除
+ */
+export const Z4_APIdel = (id: string) => {
+  return http.get(`sys/dept/remove/${id}`)
+}
+
+/**
+ * 组织管理 - 获取详情
+ */
+export const Z4_APIgetInfo = (id: string) => {
+  return http.get(`sys/dept/detail/${id}`)
+}
+
+/**
+ * 组织管理 - 新增或修改
+ */
+export const Z4_APIsave = (data: any) => {
+  return http.post('sys/dept/save', data)
+}
+
+/**
+ * 组织管理 - 获取相关成员
+ */
+export const Z4_APIgetUserById = (id: string) => {
+  return http.get(`sys/dept/user/list/${id}`)
+}

+ 25 - 0
src/store/reducer/Z4organization.ts

@@ -0,0 +1,25 @@
+import { TypeZ4Tree } from '@/pages/Z_system/Z4organization/type'
+
+// 初始化状态
+const initState = {
+  // 树数据
+  treeData: [] as TypeZ4Tree[]
+}
+
+// 定义 action 类型
+type Props = {
+  type: 'Z4/getTree'
+  payload: TypeZ4Tree[]
+}
+
+// reducer
+export default function Reducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取树数据
+    case 'Z4/getTree':
+      return { ...state, treeData: action.payload }
+
+    default:
+      return state
+  }
+}

+ 2 - 0
src/store/reducer/index.ts

@@ -6,6 +6,7 @@ import A0Layout from './layout'
 import D2storSet from './D2storSet'
 import Z1dict from './Z1dict'
 import Z2numRule from './Z2numRule'
+import Z4organization from './Z4organization'
 import Z6user from './Z6user'
 import Z7log from './Z7log'
 
@@ -15,6 +16,7 @@ const rootReducer = combineReducers({
   D2storSet,
   Z1dict,
   Z2numRule,
+  Z4organization,
   Z6user,
   Z7log
 })