Department.js 9.5 KB

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