Department.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import React, { useEffect, useState, useRef } from 'react';
  2. import { connect } from 'dva';
  3. import { Form, Table, DatePicker, Input, Button, Empty, Card, Affix, TreeSelect } from 'antd';
  4. import styles from './report.less';
  5. import UserRptModal from './UserRptModal';
  6. import DepCompareModal from './DepCompareModal';
  7. import moment from 'moment';
  8. import { downloadFile, getToken } from '@/utils/utils.js';
  9. import * as echarts from 'echarts';
  10. import { CloseOutlined } from '@ant-design/icons';
  11. const { RangePicker } = DatePicker;
  12. //按天取前月26-当月25
  13. var initDate;
  14. if (moment().date() > 25) {
  15. initDate = [
  16. moment()
  17. .subtract(1, 'month')
  18. .set('date', 26),
  19. moment().set('date', 25),
  20. ];
  21. } else {
  22. initDate = [
  23. moment()
  24. .subtract(2, 'month')
  25. .set('date', 26),
  26. moment()
  27. .subtract(1, 'month')
  28. .set('date', 25),
  29. ];
  30. }
  31. function Department(props) {
  32. const { dispatch, loading, dep } = props;
  33. const [form] = Form.useForm();
  34. const [visible, setVisible] = useState(false);
  35. const [modalFilter, setModalFilter] = useState({});
  36. const [current, setCurrent] = useState(null);
  37. const [currentDep, setCurrentDep] = useState(null);
  38. const chartRef = useRef(null);
  39. const filterRef = useRef({ pageSize: 99999 });
  40. //控制图表部门选择参数
  41. const [loadedDepKey, setLoadedDepKey] = useState([]);
  42. const [expandedDepKey, setExpandedDepKey] = useState([]);
  43. const columns = [
  44. {
  45. title: '部门名称',
  46. render: record => <a onClick={() => showUserModal(record)}>{record.dep_name}</a>,
  47. // render: record => <a onClick={() => setCurrent(record)}>{record.dep_name}</a>,
  48. width: '32%',
  49. },
  50. {
  51. title: '有效利用率',
  52. dataIndex: 'usage_percent',
  53. render: percent => (percent * 100).toFixed(2) + '%',
  54. },
  55. {
  56. title: '执行项目人日',
  57. dataIndex: 'type_project_cnt',
  58. },
  59. {
  60. title: '售前支持',
  61. dataIndex: 'type_sale_cnt',
  62. },
  63. {
  64. title: '市场品牌',
  65. dataIndex: 'type_market_cnt',
  66. },
  67. {
  68. title: '日常',
  69. dataIndex: 'type_normal_cnt',
  70. },
  71. {
  72. title: '标准化',
  73. dataIndex: 'type_standardize_cnt',
  74. },
  75. {
  76. title: '研发',
  77. dataIndex: 'type_rd_cnt',
  78. },
  79. // {
  80. // title: '漏填工时',
  81. // dataIndex: 'type_lost_cnt',
  82. // },
  83. {
  84. title: '应填报总工时',
  85. dataIndex: 'total_cnt',
  86. },
  87. // {
  88. // title: '操作',
  89. // width: 80,
  90. // render: item => <a onClick={() => showDepCompare(item)}>详情</a>,
  91. // },
  92. // {
  93. // title: '付费工时数',
  94. // dataIndex: 'pay_workload_cnt',
  95. // },
  96. // {
  97. // title: '付费工时率',
  98. // dataIndex: 'pay_workload_percent',
  99. // render: percent => (percent * 100).toFixed(2) + '%',
  100. // },
  101. ];
  102. const handleSearch = () => {
  103. const { time } = form.getFieldsValue();
  104. filterRef.current.s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : '';
  105. filterRef.current.e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : '';
  106. //重置图表部门选择
  107. setLoadedDepKey([]);
  108. setExpandedDepKey([]);
  109. dispatch({
  110. type: 'report/queryDepReport',
  111. payload: {
  112. filter: filterRef.current,
  113. },
  114. callback: list => handleChangeCurrent(list[0]),
  115. });
  116. };
  117. const handleDownload = finance => {
  118. const token = getToken();
  119. const { time } = form.getFieldsValue();
  120. let s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : '';
  121. let e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : '';
  122. if (finance)
  123. downloadFile(
  124. `/api/v2/workload/finance/people/export?JWT-TOKEN=${token}&s_time=${s_time}&e_time=${e_time}`,
  125. `财务报表_部门${moment().format('YYYYMMDDHHMMSS')}.xlsx`
  126. );
  127. else
  128. downloadFile(
  129. `/api/v2/workload/rpt/dep/export2excel?JWT-TOKEN=${token}&s_time=${s_time}&e_time=${e_time}`,
  130. `部门报表${moment().format('YYYYMMDDHHMMSS')}.xlsx`
  131. );
  132. };
  133. const renderSearch = () => {
  134. return (
  135. <Form layout="inline" form={form}>
  136. <Form.Item label="时间" name="time" initialValue={initDate}>
  137. <RangePicker placeholder="选择时间" allowClear={false} />
  138. </Form.Item>
  139. <Form.Item>
  140. <Button type="primary" loading={loading} onClick={handleSearch}>
  141. 查询
  142. </Button>
  143. </Form.Item>
  144. </Form>
  145. );
  146. };
  147. //控制图表部门选择展开/加载
  148. const onExpandDep = keys => {
  149. setExpandedDepKey(keys);
  150. };
  151. const onLoadDep = (expanded, record) => {
  152. return new Promise(resolve => {
  153. if (expanded && !record.isLoad) {
  154. dispatch({
  155. type: 'report/queryDepReport',
  156. payload: {
  157. filter: filterRef.current,
  158. record: record,
  159. },
  160. callback: resolve,
  161. });
  162. } else {
  163. resolve();
  164. }
  165. });
  166. };
  167. const showUserModal = item => {
  168. // const showDepCompare = item => {
  169. const { s_time, e_time } = filterRef.current;
  170. setModalFilter({
  171. s_time: s_time,
  172. e_time: e_time,
  173. dep_id: item.dep_id,
  174. });
  175. setVisible(true);
  176. };
  177. const renderChart = () => {
  178. current;
  179. chartRef.current.setOption({
  180. tooltip: {
  181. trigger: 'item',
  182. },
  183. graphic: {
  184. type: 'text',
  185. left: 'center',
  186. top: 'center',
  187. style: {
  188. text: `有效利用率\n${
  189. current.usage_percent ? (current.usage_percent * 100).toFixed(2) : '-'
  190. }%`,
  191. textAlign: 'center',
  192. },
  193. },
  194. series: [
  195. {
  196. type: 'pie',
  197. radius: ['40%', '70%'],
  198. data: current.data,
  199. hoverAnimation: false,
  200. itemStyle: { shadowColor: 'rgba(0, 0, 0, 0.5)', shadowBlur: 5 },
  201. },
  202. ],
  203. });
  204. };
  205. const handleChangeCurrent = item => {
  206. let data = [
  207. { value: item.type_project_cnt, name: '执行项目人日' },
  208. { value: item.type_sale_cnt, name: '售前支持' },
  209. { value: item.type_market_cnt, name: '市场品牌' },
  210. { value: item.type_normal_cnt, name: '日常' },
  211. { value: item.type_standardize_cnt, name: '标准化' },
  212. { value: item.type_rd_cnt, name: '研发' },
  213. ];
  214. setCurrentDep(item.dep_id);
  215. // 过滤为0的值
  216. data = data.filter(item => item.value);
  217. if (data.length > 0) {
  218. data.push({
  219. value: item.type_lost_cnt,
  220. name: '漏填工时',
  221. tooltip: {
  222. backgroundColor: 'transparent',
  223. formatter: () => ' ',
  224. },
  225. itemStyle: { color: '#fff' },
  226. emphasis: {
  227. label: { show: false },
  228. labelLine: { show: false },
  229. itemStyle: { color: '#fff' },
  230. },
  231. label: { show: false },
  232. labelLine: { show: false },
  233. selected: false,
  234. });
  235. setCurrent({ data, usage_percent: item.usage_percent });
  236. } else {
  237. setCurrent(null);
  238. }
  239. };
  240. const renderDepSelect = () => {
  241. return (
  242. <TreeSelect
  243. showSearch
  244. allowClear
  245. placeholder="请选择部门"
  246. style={{ width: '80%' }}
  247. multiple={false}
  248. treeData={dep.list}
  249. fieldNames={{
  250. label: 'dep_name',
  251. value: 'dep_id',
  252. }}
  253. filterTreeNode={(input, option) => {
  254. return option.props.dep_name.includes(input);
  255. }}
  256. onSelect={(_, node) => {
  257. handleChangeCurrent(node);
  258. }}
  259. treeExpandedKeys={expandedDepKey}
  260. onTreeExpand={keys => onExpandDep(keys)}
  261. treeLoadedKeys={loadedDepKey}
  262. loadData={node => onLoadDep(true, node)}
  263. value={currentDep}
  264. />
  265. );
  266. };
  267. useEffect(() => {
  268. // dispatch({
  269. // type: 'report/queryUserReport',
  270. // });
  271. handleSearch();
  272. chartRef.current = echarts.init(document.getElementById('chart'));
  273. }, []);
  274. useEffect(() => {
  275. if (current) {
  276. renderChart();
  277. }
  278. }, [current]);
  279. return (
  280. <div>
  281. <div className={styles.topPart}>
  282. {renderSearch()}
  283. <div>
  284. <Button type="primary" onClick={() => handleDownload(1)} style={{ marginRight: '10px' }}>
  285. 财务报表导出
  286. </Button>
  287. <Button type="primary" onClick={() => handleDownload(0)}>
  288. 导出
  289. </Button>
  290. </div>
  291. </div>
  292. <div style={{ marginTop: 20, display: 'flex' }}>
  293. <Table
  294. loading={loading}
  295. rowKey="dep_id"
  296. style={{ width: '100%' }}
  297. columns={columns}
  298. dataSource={dep.list}
  299. pagination={false}
  300. onExpand={onLoadDep}
  301. />
  302. <Affix offsetTop={20}>
  303. <Card
  304. // extra={<CloseOutlined onClick={() => setCurrent(null)} />}
  305. title={renderDepSelect()}
  306. style={{ display: 'block', marginLeft: 20 }}
  307. >
  308. {!current && <Empty style={{ width: 400 }} />}
  309. <div
  310. id="chart"
  311. style={{ width: 400, height: 340, display: current ? 'block' : 'none' }}
  312. ></div>
  313. </Card>
  314. </Affix>
  315. </div>
  316. {/* <UserRptModal filter={modalFilter} visible={visible} onCancel={() => setVisible(false)} /> */}
  317. <DepCompareModal filter={modalFilter} visible={visible} onCancel={() => setVisible(false)} />
  318. </div>
  319. );
  320. }
  321. export default connect(({ report, loading }) => ({
  322. dep: report.dep,
  323. loading: loading.models.report,
  324. }))(Department);