ソースを参照

Merge branch 'work20230919' of http://120.55.44.4:10080/xujunjie/WorkloadWeb into work20230919

xujunjie 1 年間 前
コミット
34a795b1d4

+ 6 - 6
src/components/charts/BarChartModule.js

@@ -100,14 +100,14 @@ const defaultOption = {
       show: false,
     },
     axisLabel: {
-      color: '#fff',
+      color: '#000',
     },
   },
   legend: {
-    icon: 'circle',
-    right: '20%',
+    // icon: 'circle',
+    left: '20%',
     textStyle: {
-      color: '#fff',
+      color: '#000',
     },
   },
   series: [
@@ -118,7 +118,7 @@ const defaultOption = {
       label: {
         show: true,
         position: 'top',
-        color: '#fff',
+        color: '#000',
       },
       barGap: '0',
       barMaxWidth: '10%',
@@ -130,7 +130,7 @@ const defaultOption = {
       label: {
         show: true,
         position: 'top',
-        color: '#fff',
+        color: '#000',
       },
       barGap: '0',
       barMaxWidth: '10%',

+ 6 - 2
src/components/charts/PieChartModule.js

@@ -68,8 +68,12 @@ const defaultOption = {
     trigger: 'item',
     formatter: '{b} : {d}% ({c})',
   },
-  textStyle: {
-    color: '#fff',
+  // textStyle: {
+  //   color: '#fff',
+  // },
+  legend: {
+    bottom: 10,
+    left: 'center',
   },
   series: [
     {

+ 1 - 1
src/models/user.js

@@ -106,7 +106,7 @@ export default {
         }
         const resRole = yield call(queryUserRole, user.ID);
         let roleList = getRoleList(resRole.data.Dep);
-        console.log(roleList);
+        // console.log(roleList);
 
         yield put({
           type: 'saveCurrentUser',

+ 4 - 0
src/pages/PurchaseAdmin/PurchaseList/Approval/ApprovalModal.js

@@ -153,6 +153,10 @@ function AddModal(props) {
             <Select
               style={{ width: '100%' }}
               onChange={e => form.setFieldsValue({ supplier_id: e })}
+              showSearch
+              filterOption={(input, option) => {
+                return (option?.children[0] ?? '').includes(input);
+              }}
             >
               {supplierList.map(item => (
                 <Option key={item.id} value={Number(item.id)}>

+ 1 - 0
src/pages/PurchaseAdmin/PurchaseList/Approval/List.js

@@ -77,6 +77,7 @@ function List(props) {
   const [addFirmVisible, setAddFirmVisible] = useState(false);
   const [recordVisible, setRecordVisible] = useState(false);
   const [isEdit, setIsEdit] = useState(false);
+
   const columns = [
     {
       title: '项目编号',

+ 58 - 18
src/pages/PurchaseAdmin/PurchaseList/Approval/Statistic.js

@@ -1,21 +1,57 @@
-import { useEffect, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
 import styles from './index.less';
 import echarts from 'echarts';
 import BarChartModule from '@/components/charts/BarChartModule';
 import { Radio } from 'antd';
-const Statistic = () => {
+import { useRequest } from 'ahooks';
+import { workloadDepProjectChart, workloadDepProjectTypeChart } from '@/services/record';
+import PieChartModule from '@/components/charts/PieChartModule';
+import { STATUS } from './List';
+import { connect } from 'dva';
+const Statistic = props => {
+  const { typeList, dispatch } = props;
+  const { data, loading } = useRequest(workloadDepProjectChart);
+  const { data: typeData, run: runTypeData, loading: typeLoading } = useRequest(
+    workloadDepProjectTypeChart
+  ); //1  月 2 季度 3 年
+  useEffect(() => {
+    dispatch({
+      type: 'approval/queryType',
+    });
+  }, []);
+  const pieData = useMemo(() => {
+    return data?.status_chart?.map(item => {
+      const name = STATUS.find(cur => cur.value == item.status)?.label || '';
+      return { value: item.num, name };
+    });
+  }, [data]);
+
+  const barData = useMemo(() => {
+    if (!typeData || typeData?.length == 0) return;
+    const newData = {};
+    newData.xData = typeData.map(item => item.create_time);
+    const types = [...new Set(typeData.map(item => item.type))];
+    newData.dataList = types.map(type => {
+      const name = typeList?.find(cur => cur.id == type)?.name;
+      const data = typeData.filter(item => item.type == type);
+      return { name, data: data?.map(item => item.num) };
+    });
+    return newData;
+  }, [typeData]);
+  console.log('------------piedata -----', pieData, barData);
+
   const options = [
     {
       label: '月',
-      value: 0,
+      value: 1,
     },
     {
       label: '季度',
-      value: 1,
+      value: 2,
     },
     {
       label: '年',
-      value: 2,
+      value: 3,
     },
   ];
   return (
@@ -24,16 +60,20 @@ const Statistic = () => {
         <div style={{ fontSize: '22px' }}>项目统计</div>
         <div style={{ display: 'flex', width: '100%', justifyContent: 'space-around' }}>
           <div style={{ textAlign: 'center' }}>
-            <div style={{ color: '#f5a41f', fontSize: '32px' }}>110</div>
-            <div>大部分看开点</div>
+            <div style={{ color: '#f5a41f', fontSize: '32px' }}>{data?.total}</div>
+            <div>项目总数</div>
           </div>
           <div style={{ textAlign: 'center' }}>
-            <div style={{ color: '#f5a41f', fontSize: '32px' }}>110</div>
-            <div>大部分看开点</div>
+            <div style={{ color: '#f5a41f', fontSize: '32px' }}>{data?.pass}</div>
+            <div>审核通过项目数</div>
           </div>
           <div style={{ textAlign: 'center' }}>
-            <div style={{ color: '#f5a41f', fontSize: '32px' }}>110</div>
-            <div>大部分看开点</div>
+            <div style={{ color: '#f5a41f', fontSize: '32px' }}>{data?.audit}</div>
+            <div>审核中项目数</div>
+          </div>
+          <div style={{ textAlign: 'center' }}>
+            <div style={{ color: '#f5a41f', fontSize: '32px' }}>{data?.add}</div>
+            <div>本月新增立项项目</div>
           </div>
         </div>
       </div>
@@ -41,7 +81,7 @@ const Statistic = () => {
         <div className={styles.boxCon} style={{ width: '49.2%' }}>
           <div style={{ fontSize: '22px' }}>项目状态统计</div>
           <div style={{ height: '300px' }}>
-            <BarChartModule />
+            {data?.status_chart?.length > 0 && <PieChartModule data={pieData} />}
           </div>
         </div>
         <div className={styles.boxCon} style={{ width: '49.2%', position: 'relative' }}>
@@ -49,18 +89,18 @@ const Statistic = () => {
           <div style={{ position: 'absolute', top: '16px', right: '20px' }}>
             <Radio.Group
               options={options}
-              onChange={() => {}}
-              value={0}
+              onChange={e => runTypeData({ t: e.target.value })}
+              defaultValue={1}
               optionType="button"
               buttonStyle="solid"
             />
           </div>
-          <div style={{ height: '300px' }}>
-            <BarChartModule />
-          </div>
+          <div style={{ height: '300px' }}>{typeData && <BarChartModule {...barData} />}</div>
         </div>
       </div>
     </div>
   );
 };
-export default Statistic;
+export default connect(({ approval, user, loading }) => ({
+  typeList: approval.typeList,
+}))(Statistic);

+ 9 - 1
src/pages/PurchaseAdmin/PurchaseList/DailyRecord/components/RecordDetail.js

@@ -3,7 +3,15 @@ import styles from './index.less';
 import { useEffect, useState } from 'react';
 import { DeleteOutlined } from '@ant-design/icons';
 
-const RecordDetailModal = ({ visible, user, projects, loading = false, onOk, onCancel }) => {
+const RecordDetailModal = ({
+  detail,
+  visible,
+  user,
+  projects,
+  loading = false,
+  onOk,
+  onCancel,
+}) => {
   const RenderItem = item => {
     return (
       <div className={styles.itemCon}>

+ 67 - 12
src/pages/PurchaseAdmin/PurchaseList/DailyRecord/components/WriteRecordModal.js

@@ -4,16 +4,36 @@ import { useEffect, useMemo, useState } from 'react';
 import { DeleteOutlined } from '@ant-design/icons';
 import { approvalLog } from '@/services/record';
 
-const WriteRecordModal = ({ visible, user, projects, loading = false, onOk, onCancel }) => {
+const WriteRecordModal = ({
+  defaultLogList = [],
+  visible,
+  editMode = false,
+  user,
+  projects,
+  onOk,
+  onCancel,
+}) => {
+  const defaultData = { id: '', code_id: '', title: '', content: '' };
+
   const [form] = Form.useForm();
-  const defaultData = { code_id: '', title: '', content: '' };
+
   const [list, setList] = useState([defaultData]);
-  console.log(user, list);
 
+  // 每次关闭前 默认恢复到新增状态
   useEffect(() => {
-    if (!visible) setList([defaultData]);
+    if (!visible) {
+      setList([defaultData]);
+    }
   }, [visible]);
 
+  useEffect(() => {
+    if (defaultLogList.length) {
+      setList(defaultLogList);
+    } else {
+      setList([defaultData]);
+    }
+  }, [defaultLogList]);
+
   const handleAddClick = () => {
     setList([...list, defaultData]);
   };
@@ -30,20 +50,40 @@ const WriteRecordModal = ({ visible, user, projects, loading = false, onOk, onCa
     newList.splice(idx, 1);
     setList(newList);
   };
-  console.log('00000000000---------', list);
 
   const handleOk = () => {
-    onOk(list);
+    // 新建的不需要传id字段
+    const params = list.map(item => {
+      if (item.id === '') {
+        return {
+          code_id: item.code_id,
+          title: item.title,
+          content: item.content,
+        };
+      } else {
+        return item;
+      }
+    });
+    onOk(params);
   };
 
   return (
-    <Modal title="写日志" open={visible} width={800} onOk={handleOk} onCancel={onCancel}>
+    <Modal
+      title="写日志"
+      open={visible}
+      width={800}
+      onOk={handleOk}
+      onCancel={onCancel}
+      destroyOnClose
+    >
       <Form
         labelCol={{ span: 4 }}
         wrapperCol={{ span: 18 }}
+        labelAlign="left"
         width="100%"
         style={{ marginBottom: 20 }}
         form={form}
+        disabled={!editMode}
       >
         <Form.Item
           label="日志标题"
@@ -65,6 +105,7 @@ const WriteRecordModal = ({ visible, user, projects, loading = false, onOk, onCa
               idx={idx}
               projects={projects}
               data={item}
+              showDeleteIcon={idx !== 0 && editMode}
               onChange={handleChangeItem}
               onDelete={handleDelItem}
             />
@@ -83,14 +124,22 @@ const WriteRecordModal = ({ visible, user, projects, loading = false, onOk, onCa
 
 export default WriteRecordModal;
 
-const RenderItem = ({ idx, data, projects, onChange, onDelete }) => {
+const RenderItem = ({ idx, data, showDeleteIcon, projects, onChange, onDelete }) => {
   const [form] = Form.useForm();
+
   const projectName = useMemo(() => {
     return projects.find(item => item.ID == data.code_id)?.Name || '';
   }, [data.code_id]);
+
   return (
     <div className={styles.itemContent}>
-      <Form labelCol={{ span: 7 }} wrapperCol={{ span: 17 }} width="100%" form={form}>
+      <Form
+        labelCol={{ span: 8 }}
+        wrapperCol={{ span: 16 }}
+        labelAlign="left"
+        width="100%"
+        form={form}
+      >
         <Row>
           <Col span={12}>
             <Form.Item
@@ -105,6 +154,10 @@ const RenderItem = ({ idx, data, projects, onChange, onDelete }) => {
                   return { label: item.Name, value: item.ID };
                 })}
                 onChange={id => onChange(idx, { ...data, code_id: id })}
+                filterOption={(input, option) => {
+                  return option.label.includes(input);
+                }}
+                showSearch
               />
             </Form.Item>
           </Col>
@@ -124,8 +177,8 @@ const RenderItem = ({ idx, data, projects, onChange, onDelete }) => {
         </Row>
         <Form.Item
           style={{ marginBottom: 0 }}
-          labelCol={{ span: 3 }}
-          wrapperCol={{ span: 21 }}
+          labelCol={{ span: 4 }}
+          wrapperCol={{ span: 20 }}
           label="日志详情"
           initialValue={data.content}
           name="content"
@@ -137,7 +190,9 @@ const RenderItem = ({ idx, data, projects, onChange, onDelete }) => {
           />
         </Form.Item>
       </Form>
-      <DeleteOutlined className={styles.delIcon} onClick={idx => onDelete(idx)} />
+      {showDeleteIcon && (
+        <DeleteOutlined className={styles.delIcon} onClick={idx => onDelete(idx)} />
+      )}
     </div>
   );
 };

+ 173 - 50
src/pages/PurchaseAdmin/PurchaseList/DailyRecord/index.js

@@ -1,113 +1,236 @@
-import { Button, DatePicker, Space, Table } from 'antd';
-import { useEffect, useState } from 'react';
+import { Button, DatePicker, Space, Table, Modal, message, Spin } from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { useEffect, useMemo, useState } from 'react';
 import WriteRecordModal from './components/WriteRecordModal';
 import { connect } from 'dva';
 import RecordDetailModal from './components/RecordDetail';
 import styles from './index.less';
 import { useRequest } from 'ahooks';
-import { approvalAddLog, approvalEditLog, approvalLogOwnList } from '@/services/record';
+import {
+  approvalAddLog,
+  approvalDeleteLog,
+  approvalEditLog,
+  approvalLogDetail,
+  approvalLogOwnList,
+} from '@/services/record';
+import record from './models/record';
+import { log } from 'lodash-decorators/utils';
+import moment from 'moment';
+
+const { confirm } = Modal;
 
 const DailyRecord = props => {
-  const { currentUser, projects, dispatch } = props;
-  const [date, setDate] = useState('');
-  const [visible, setVisible] = useState(false);
-  const [detailOpen, setDetailOpen] = useState(false);
+  const { currentUser, projects, loading: propsLoading, dispatch } = props;
+
+  const [date, setDate] = useState({});
+  const [pageInfo, setPageInfo] = useState({ current: 1, pageSize: 10, total: 999 });
+  const [writeVisible, setWriteVisible] = useState(false);
+  const [detailVisible, setDetailVisible] = useState(false);
+  const [editMode, setEditMode] = useState(false);
+  const [selectedLogID, setselectedLogID] = useState('');
+
+  const [mockLogDetail, setMockLogDetail] = useState([]);
+
+  // 请求列表
+  const { data, run, loading: listLoading } = useRequest(approvalLogOwnList, {
+    defaultParams: [{ ...date, ...pageInfo }],
+    onSuccess: data => {
+      if (data?.pagination) {
+        setPageInfo(data.pagination);
+      }
+    },
+  });
 
-  //请求列表
-  const { data, run, loading } = useRequest(date => approvalLogOwnList(date), {});
+  // 查询日志详情
+  const { data: logDetail, run: runDetail, loading: detailLoading } = useRequest(
+    logID => approvalLogDetail(logID),
+    {
+      manual: true,
+    }
+  );
 
-  //添加日志
-  const { run: runAdd } = useRequest(approvalAddLog, {
+  // 添加日志
+  const { run: runAdd, loading: addLoading } = useRequest(approvalAddLog, {
     manual: true,
     onSuccess: () => {
-      message('添加日志成功');
+      message.success('添加日志成功');
+      setWriteVisible(false);
       run();
     },
   });
 
-  //编辑日志
-  const { run: runEdit } = useRequest(approvalEditLog, {
+  // 删除日志
+  const { run: runDelete, loading: deleteLoading } = useRequest(logID => approvalDeleteLog(logID), {
     manual: true,
     onSuccess: () => {
-      message('编辑日志成功');
+      message.success('删除日志成功');
       run();
     },
   });
 
+  // 编辑日志
+  const { run: runEdit, loading: editLoading } = useRequest(approvalEditLog, {
+    manual: true,
+    onSuccess: () => {
+      message.success('编辑日志成功');
+      run();
+    },
+  });
+
+  // 统一管理 加载状态
+  const allLoading = useMemo(() => {
+    const loadingList = [propsLoading, listLoading, deleteLoading, addLoading, editLoading];
+    if (loadingList.find(item => item)) {
+      return true;
+    }
+    return false;
+  }, [propsLoading, listLoading, deleteLoading, addLoading, editLoading]);
+
+  const editLog = record => {
+    setWriteVisible(true);
+    setEditMode(true);
+    setMockLogDetail([
+      {
+        id: 'gsdfgZ',
+        code_id: 12,
+        title: 'mock',
+        content: 'mock mock mock mock mock mock mock mock',
+      },
+      { id: 'bnsrqre', code_id: 13, title: 'mock', content: 'mock mock ' },
+      { id: 'hnsfggrqe', code_id: 14, title: 'mock', content: 'mock mock ' },
+    ]);
+  };
+
+  const toDetail = record => {
+    // runDetail(record.log_id);
+    setWriteVisible(true);
+    setEditMode(false);
+    setMockLogDetail([
+      { id: 'gsdfgZ', code_id: 12, title: 'mock', content: 'mock mock ' },
+      { id: 'bnsrqre', code_id: 13, title: 'mock', content: 'mock mock ' },
+      { id: 'hnsfggrqe', code_id: 14, title: 'mock', content: 'mock mock ' },
+    ]);
+  };
+
+  const deleteRecord = record => {
+    confirm({
+      title: '提示',
+      icon: <ExclamationCircleOutlined />,
+      content: '确认删除此条日志?',
+      onOk() {
+        runDelete(record.log_id);
+      },
+    });
+  };
+
   const columns = [
     {
       title: '日志标题',
-      dataIndex: 'title',
+      key: 'log_title',
       width: '20%',
-    },
-    {
-      title: '日志概述',
-      dataIndex: 'doc',
-      render: doc => <div className={styles.doc}>{doc}</div>,
+      render: record => {
+        return `${record.author_name}的金科环境项目日志`;
+      },
     },
     {
       title: '提交人',
-      dataIndex: 'name',
+      dataIndex: 'author_name',
       width: '16%',
     },
     {
       title: '提交时间',
-      dataIndex: 'time',
+      dataIndex: 'c_time',
       width: '20%',
+      render: time => {
+        if (!time) {
+          return '--';
+        }
+        return moment(time).format('YYYY-DD-MM HH:mm');
+      },
     },
     {
       title: '操作',
       width: '20%',
-      render: () => (
-        <Space>
-          <a>编辑</a>
-          <a onClick={() => setDetailOpen(true)}>详情</a>
-          <a>删除</a>
-        </Space>
-      ),
+      render: record => {
+        return (
+          <Space>
+            <a onClick={() => editLog(record)}>编辑</a>
+            <a onClick={() => toDetail(record)}>详情</a>
+            <a onClick={() => deleteRecord(record)}>删除</a>
+          </Space>
+        );
+      },
     },
   ];
 
-  // const dataSource = [
-  //   {
-  //     name: '管理员',
-  //     title: '管理员的金科环境项目日志',
-  //     time: '1993-49-49 22:23:99',
-  //     doc:
-  //       '十八大不愧是你打卡是你打八大不愧是你打卡是你打卡机阿萨你打看那看手八大不愧是你打卡是你打卡机阿萨你打看那看手八大不愧是你打卡是你打卡机阿萨你打看那看手八大不愧是你打卡是你打卡机阿萨你打看那看手卡机阿萨你打看那看手打',
-  //   },
-  // ];
-
+  // 加载项目列表,如果没有
   useEffect(() => {
-    dispatch({ type: 'record/queryProject' });
+    if (!projects?.length) {
+      dispatch({ type: 'record/queryProject' });
+    }
   }, []);
 
-  const onChange = (date, dateString) => {
-    console.log(date, dateString);
-    run({ s_time: `${dateString} 00:00:00`, e_time: `${dateString} 23:59:59` });
+  const handleDataPicked = (_date, dateString) => {
+    let params = '';
+    if (dateString) {
+      params = { ...pageInfo, s_time: `${dateString} 00:00:00`, e_time: `${dateString} 23:59:59` };
+    }
+    run(params);
+    setDate(params);
   };
+
+  const handlePageChange = (page, pageSize) => {
+    console.log(page, pageSize);
+    let params = '';
+    if (date) {
+      params = date;
+    }
+    params.currentPage = page;
+    params.pageSize = pageSize;
+    run(params);
+    setPageInfo({ ...pageInfo, current: page, pageSize: pageSize });
+  };
+
   return (
     <div>
       <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '20px' }}>
         <Space size={24}>
-          <DatePicker onChange={onChange} />
+          <DatePicker onChange={handleDataPicked} />
           <Button type="primary" onClick={() => {}}>
             查询
           </Button>
         </Space>
-        <Button type="primary" onClick={() => setVisible(true)}>
+        <Button
+          type="primary"
+          onClick={() => {
+            setWriteVisible(true);
+            setEditMode(true);
+          }}
+        >
           写日志
         </Button>
       </div>
-      <Table columns={columns} dataSource={data?.list} pagination={data?.pagination} />
+      <Spin spinning={allLoading}>
+        <Table
+          columns={columns}
+          dataSource={data?.list}
+          pagination={{ ...pageInfo, onChange: handlePageChange }}
+        />
+      </Spin>
       <WriteRecordModal
-        visible={visible}
+        defaultLogList={mockLogDetail}
+        visible={writeVisible}
+        editMode={editMode}
         user={currentUser}
         projects={projects}
         onOk={runAdd}
-        onCancel={() => setVisible(false)}
+        onCancel={() => setWriteVisible(false)}
+      />
+      <RecordDetailModal
+        visible={detailVisible}
+        onCancel={() => setDetailVisible(false)}
+        detail={logDetail}
       />
-      <RecordDetailModal visible={detailOpen} onCancel={() => setDetailOpen(false)} />
     </div>
   );
 };

+ 47 - 4
src/services/record.js

@@ -1,22 +1,41 @@
 import request from '@/utils/request';
 import { stringify } from 'qs';
-//新增日志接口
+// 新增日志
 export async function approvalAddLog(params) {
   return request(`/api/v2/approval/log`, {
     method: 'POST',
     body: params,
   });
 }
-//编辑日志接口
+
+// 删除日志
+export async function approvalDeleteLog(logID) {
+  return request(`/api/v2/approval/log/${logID}`, {
+    method: 'DELETE',
+  });
+}
+
+// 编辑日志
 export async function approvalEditLog(params) {
   return request(`/api/v2/approval/log/${params.id}`, {
     method: 'PUT',
     body: params,
   });
 }
-//请求个人日志列表
+// 请求个人日志列表
 export async function approvalLogOwnList(params) {
-  return request(`/api/v2/approval/log-own/list?${stringify(params)}`);
+  const res = await request(`/api/v2/approval/log-own/list?${stringify(params)}`);
+  return res?.data || { list: [], pagination: {} };
+}
+
+/**
+ * 查询日志详情
+ * @param {string} log_id
+ * @returns Promise<logDetail[]>
+ */
+export async function approvalLogDetail(logID) {
+  const res = await request(`/api/v2/approval/log/${logID}`);
+  return res?.data || [];
 }
 
 //项目日志列表
@@ -24,3 +43,27 @@ export async function approvalLogProjectsList(params) {
   const res = await request(`/api/v2/approval/log-projects/${params.id}?${stringify(params)}`);
   return res?.data;
 }
+
+//统计分析接口
+export async function workloadDepProjectChart() {
+  const res = await request(`/api/v2/workload/dep/project/chart`);
+  // if (res?.data) {
+  //   const data = res?.data;
+  //   data.pieData = data?.status_chart?.map(item => {
+  //     return { value: item.num, name: item.status };
+  //   });
+  //   return data;
+  // }
+  return res?.data;
+}
+//统计分析接口 项目分类统计
+export async function workloadDepProjectTypeChart(params) {
+  const res = await request(`/api/v2/workload/dep/project-type/chart?${stringify(params)}`);
+  // if (res?.data) {
+  //   const newData = res.data;
+  //   newData.xData = res.data.map(item => item.create_time);
+  //   newData.dataList = { name: '项目分类统计', data: res.data.map(item => item.num) };
+  //   return newData;
+  // }
+  return res?.data;
+}