index.jsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import { useState, useEffect, useRef, useMemo } from 'react'
  2. import BasicTable from '@/components/Table'
  3. import { DatePicker, Select, TreeSelect, Table, Typography } from 'antd'
  4. import { querySoftwareUsageList, queryUsageSelectOptions } from '@/services/statistic'
  5. import consts from '@/consts'
  6. import { useDebounceFn } from 'ahooks'
  7. import dayjs from 'dayjs'
  8. import { Pie } from '@ant-design/charts'
  9. import { isMobile } from '@/utils/is'
  10. const { Text } = Typography
  11. const opMap = {
  12. STAFF: 'staff',
  13. DEPARTMENT: 'department'
  14. }
  15. const colLabelValToZH = {
  16. giftsCount: '赠送',
  17. lendCount: '借出',
  18. salesCount: '销售'
  19. }
  20. const Usage = () => {
  21. const tRef = useRef(null)
  22. const [state, setState] = useState({
  23. optionType: opMap.STAFF,
  24. opIds: [],
  25. department: null,
  26. staff: null,
  27. startMonth: [dayjs(new Date()), dayjs(new Date())]
  28. })
  29. const queryStaffAndDepartment = async () => {
  30. const { code = -1, data: { department = [], staff = [] } = {} } =
  31. await queryUsageSelectOptions()
  32. if (code === consts.RET_CODE.SUCCESS) {
  33. setState({
  34. ...state,
  35. department,
  36. staff
  37. })
  38. tRef.current?.reload()
  39. }
  40. }
  41. const mainParams = useMemo(() => {
  42. const p = { optionType: state.optionType, startMonth: state.startMonth[0].format('YYYY-MM') }
  43. if (state.optionType === opMap.STAFF) {
  44. p.staffIds = state.opIds
  45. } else {
  46. p.departmentIds = state.opIds
  47. }
  48. return p
  49. }, [state.optionType, state.opIds, state.startMonth])
  50. const options = useMemo(() => {
  51. if (state.optionType === opMap.STAFF) {
  52. return state.staff?.map(item => ({ label: item.username, value: item.id })) || []
  53. }
  54. return state.department || []
  55. }, [state.optionType, state.staff, state.department])
  56. const pieData = useMemo(() => {
  57. if (!state.chart || !state.sum) return {}
  58. const leftKeys = Object.keys(state.chart)
  59. const pieLeft = []
  60. leftKeys.forEach(item => {
  61. const v = state.chart[item]
  62. pieLeft.push({ label: item === 'unused' ? '未使用' : '已使用', value: v })
  63. })
  64. const rightKeys = Object.keys(state.sum)
  65. const pieRight = []
  66. rightKeys.forEach(item => {
  67. const v = state.sum[item]
  68. if (item !== 'receiveCount') {
  69. pieRight.push({ label: colLabelValToZH[item], value: v })
  70. }
  71. })
  72. return { pieLeft, pieRight }
  73. }, [state.chart])
  74. useEffect(() => {
  75. queryStaffAndDepartment()
  76. }, [])
  77. const { run: trySetOpIds } = useDebounceFn(
  78. ids => {
  79. setState({ ...state, opIds: ids })
  80. },
  81. { wait: 500 }
  82. )
  83. useEffect(() => {
  84. // 发起请求
  85. if (state.opIds?.length) {
  86. tRef.current?.reload()
  87. }
  88. }, [state.opIds])
  89. const columns = [
  90. {
  91. title: '名称',
  92. dataIndex: 'name',
  93. key: 'name',
  94. width: 100,
  95. fixed: 'left',
  96. align: 'center'
  97. },
  98. {
  99. title: '未使用',
  100. children: [
  101. {
  102. title: '生成',
  103. key: 'create',
  104. dataIndex: 'create',
  105. align: 'center'
  106. },
  107. {
  108. title: '接收',
  109. key: 'receive',
  110. dataIndex: 'receive',
  111. align: 'center'
  112. }
  113. ]
  114. },
  115. {
  116. title: '已使用',
  117. children: [
  118. {
  119. title: '借出',
  120. key: 'lend',
  121. dataIndex: 'lend',
  122. align: 'center'
  123. },
  124. {
  125. title: '销售',
  126. key: 'sales',
  127. dataIndex: 'sales',
  128. align: 'center'
  129. },
  130. {
  131. title: '赠送',
  132. key: 'gifts',
  133. dataIndex: 'gifts',
  134. align: 'center'
  135. }
  136. ]
  137. }
  138. ]
  139. const leftConfig = {
  140. appendPadding: 10,
  141. data: pieData.pieLeft,
  142. angleField: 'value',
  143. colorField: 'label',
  144. radius: 0.9,
  145. color: ['#40B7E6', '#79CE67'],
  146. interactions: [{ type: 'element-active' }]
  147. }
  148. const rightConfig = {
  149. appendPadding: 10,
  150. data: pieData.pieRight,
  151. angleField: 'value',
  152. colorField: 'label',
  153. radius: 0.9,
  154. color: ['#40B7E6', '#79CE67', '#FBD743', '#FB7A53'],
  155. interactions: [{ type: 'element-active' }]
  156. }
  157. return (
  158. <div className="flex flex-col">
  159. {!isMobile() && (
  160. <div className="flex flex-row justify-between mb-4">
  161. <div className="card-transition w-1/2 bg-white mr-2">
  162. {pieData.pieLeft ? <Pie {...leftConfig} /> : null}
  163. </div>
  164. <div className="card-transition w-1/2 bg-white ml-2">
  165. {pieData.pieRight ? <Pie {...rightConfig} /> : null}
  166. </div>
  167. </div>
  168. )}
  169. <BasicTable
  170. manualRequest
  171. search={false}
  172. columns={columns}
  173. actionRef={tRef}
  174. rowkey="id"
  175. toolbar={{
  176. title: [
  177. <Select
  178. key="Select"
  179. defaultValue="staff"
  180. options={[
  181. { label: '部门', value: 'department' },
  182. { label: '员工', value: 'staff' }
  183. ]}
  184. onChange={val => setState({ ...state, optionType: val })}
  185. />,
  186. state.optionType === opMap.STAFF ? (
  187. <Select
  188. key="StaffSelect"
  189. className="min-w-120px"
  190. defaultOpen
  191. dropdownMatchSelectWidth
  192. options={options}
  193. mode="multiple"
  194. onChange={value => trySetOpIds(value)}
  195. />
  196. ) : (
  197. <TreeSelect
  198. key="DepartmentSelect"
  199. defaultOpen
  200. treeData={options}
  201. className="min-w-120px"
  202. dropdownMatchSelectWidth
  203. multiple
  204. onChange={value => trySetOpIds(value)}
  205. />
  206. )
  207. ],
  208. actions: [
  209. <DatePicker.RangePicker
  210. picker="month"
  211. key="DatePicker"
  212. disabled={[false, true]}
  213. value={state.startMonth}
  214. onChange={val => setState({ ...state, startMonth: val })}
  215. />
  216. ]
  217. }}
  218. params={mainParams}
  219. pagination={false}
  220. bordered
  221. request={async (params, sort, filter) => {
  222. const {
  223. code = -1,
  224. data: { list, sum, chart, create }
  225. } = await querySoftwareUsageList({ ...params, ...sort, ...filter })
  226. setState({ ...state, sum, chart, create })
  227. return {
  228. data: list,
  229. success: code === consts.RET_CODE.SUCCESS,
  230. total: list?.length || 0
  231. }
  232. }}
  233. summary={data =>
  234. data?.length ? (
  235. <>
  236. <Table.Summary.Row>
  237. <Table.Summary.Cell className="text-center">小计</Table.Summary.Cell>
  238. <Table.Summary.Cell className="text-center">{state.create}</Table.Summary.Cell>
  239. <Table.Summary.Cell className="text-center">
  240. {state.sum?.receiveCount}
  241. </Table.Summary.Cell>
  242. <Table.Summary.Cell className="text-center">
  243. {state.sum?.lendCount}
  244. </Table.Summary.Cell>
  245. <Table.Summary.Cell className="text-center">
  246. {state.sum?.salesCount}
  247. </Table.Summary.Cell>
  248. <Table.Summary.Cell className="text-center">
  249. {state.sum?.giftsCount}
  250. </Table.Summary.Cell>
  251. </Table.Summary.Row>
  252. <Table.Summary.Row>
  253. <Table.Summary.Cell className="text-center">总计</Table.Summary.Cell>
  254. <Table.Summary.Cell colSpan={2} className="text-center">
  255. <Text type="danger">{state.create + state.sum?.receiveCount}</Text>
  256. </Table.Summary.Cell>
  257. <Table.Summary.Cell colSpan={3} className="text-center">
  258. <Text type="danger">
  259. {state.sum?.lendCount + state.sum?.salesCount + state.sum?.giftsCount}
  260. </Text>
  261. </Table.Summary.Cell>
  262. </Table.Summary.Row>
  263. </>
  264. ) : null
  265. }
  266. />
  267. </div>
  268. )
  269. }
  270. export default Usage