index.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <template>
  2. <div class="oa-sys-list-view">
  3. <div class="title">
  4. <div>各板块/部门周日报完成率</div>
  5. <div>
  6. <el-button
  7. class="remind-btn"
  8. type="primary"
  9. plain
  10. round
  11. :disabled="isDisabled"
  12. @click="handleClick"
  13. >
  14. {{ isDisabled ? `请稍候 (${countdown}s)` : '一键催报' }}
  15. </el-button>
  16. <el-date-picker
  17. v-model="selectDate"
  18. type="month"
  19. placeholder="选择月"
  20. style="width: 200px"
  21. />
  22. </div>
  23. </div>
  24. <div class="table-box">
  25. <el-table
  26. class="el-table"
  27. :data="dataSource"
  28. style="width: 100%"
  29. row-key="id"
  30. v-loading="loading"
  31. :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
  32. stripe
  33. default-expand-all
  34. border
  35. header-row-class-name="table-header"
  36. :span-method="objectSpanMethod"
  37. >
  38. <el-table-column prop="deptFather" label="板块" align="center" class-name="dept-father" />
  39. <el-table-column label="板块完成率" align="center">
  40. <template #default="scope">
  41. <el-tag effect="plain" type="primary">{{ computedRate(scope.row.fatherRate) }}</el-tag>
  42. </template>
  43. </el-table-column>
  44. <el-table-column prop="deptName" label="部门" align="center" />
  45. <el-table-column label="部门完成率" align="center">
  46. <template #default="scope">
  47. <el-progress :percentage="computedRate(scope.row.fillRate, true)" />
  48. </template>
  49. </el-table-column>
  50. <el-table-column label="操作" fixed="right" align="center">
  51. <template #default="scope">
  52. <el-button type="primary" plain round @click="handleView(scope.row)">查看</el-button>
  53. </template>
  54. </el-table-column>
  55. </el-table>
  56. </div>
  57. </div>
  58. </template>
  59. <script lang="ts" setup>
  60. /**
  61. * @description 部门完成率
  62. */
  63. import request from '@/config/axios'
  64. import { handleTree } from '@/utils/tree'
  65. import moment from 'moment'
  66. defineOptions({ name: 'CompletionRate' })
  67. const loading = ref(false)
  68. const dataSource = ref([])
  69. const selectDate = ref(moment().format('YYYY-MM'))
  70. onMounted(() => {
  71. getRateData()
  72. })
  73. watch(selectDate, () => {
  74. getRateData()
  75. })
  76. // 获取完成率数据
  77. const getRateData = async () => {
  78. loading.value = true
  79. const searchDate = selectDate.value ?? moment().format('YYYY-MM')
  80. request
  81. .get({
  82. url: '/adm/reportStatistics/query-dept-report-statistics',
  83. params: {
  84. year: moment(searchDate).format('YYYY'),
  85. month: moment(searchDate).format('M')
  86. }
  87. })
  88. .then((res) => {
  89. ElMessage.success(`${moment(searchDate).format('YYYY年M月')} 数据查询成功`)
  90. const rateTree = handleTree(res, 'deptId')
  91. dataSource.value = setRateData(rateTree)
  92. loading.value = false
  93. })
  94. .catch(() => {
  95. ElMessage.error('查询失败,请稍后重试!')
  96. loading.value = false
  97. })
  98. // console.log('rateData', rateData)
  99. }
  100. // 处理完成率数据
  101. const rowSpanList: any = ref([])
  102. const setRateData = (rateTree) => {
  103. const dataSource = rateTree?.[0].children ?? []
  104. // 处理职能部门数据
  105. if (rateTree?.[1]) {
  106. dataSource.unshift(rateTree[1])
  107. }
  108. // 处理要渲染的数据
  109. const rataData: any = []
  110. for (let index = 0; index < dataSource.length; index++) {
  111. const data = dataSource[index]
  112. if (data.children && data.children.length > 0) {
  113. for (const item of data.children) {
  114. rataData.push({
  115. ...item,
  116. deptFather: data.deptName,
  117. fatherRate: data.fillRate,
  118. deptName: item.deptName,
  119. fillRate: item.fillRate,
  120. deptId: item.deptId
  121. })
  122. }
  123. } else if (data.deptName === '北京分公司') {
  124. // 因为北京分公司暂无下属部门,以防后期添加,先这样处理
  125. rataData.push({
  126. ...data,
  127. deptFather: data.deptName,
  128. fatherRate: data.fillRate,
  129. deptName: data.deptName,
  130. fillRate: data.fillRate,
  131. deptId: data.deptId
  132. })
  133. }
  134. // else {
  135. // // 总经理室不展示
  136. // rataData.push({
  137. // deptFather: '总经理室',
  138. // fatherRate: data.fillRate,
  139. // deptName: data.deptName,
  140. // fillRate: data.fillRate,
  141. // deptId: data.deptId
  142. // })
  143. // }
  144. }
  145. rowSpanList.value = getDeptFatherCountsAndIndices(rataData)
  146. return rataData
  147. }
  148. // 获取相同内容的数组下标
  149. const getDeptFatherCountsAndIndices = (data) => {
  150. const countsAndIndices: any = []
  151. let currentDeptFather = null
  152. let startIndex = 0
  153. for (let i = 0; i < data.length; i++) {
  154. const item = data[i]
  155. if (item.deptFather === currentDeptFather) {
  156. continue
  157. } else {
  158. if (currentDeptFather !== null) {
  159. countsAndIndices.push({
  160. deptFather: currentDeptFather,
  161. count: i - startIndex,
  162. startIndex
  163. })
  164. }
  165. currentDeptFather = item.deptFather
  166. startIndex = i
  167. }
  168. }
  169. if (currentDeptFather !== null) {
  170. countsAndIndices.push({
  171. deptFather: currentDeptFather,
  172. count: data.length - startIndex,
  173. startIndex
  174. })
  175. }
  176. return countsAndIndices
  177. }
  178. // 设置表格单元格布局,按照相同数组下标进行分类
  179. const objectSpanMethod = ({ rowIndex, columnIndex }) => {
  180. if (columnIndex === 0 || columnIndex === 1) {
  181. const rowSpans = rowSpanList.value
  182. const isSplit = rowSpans.find((item) => item.startIndex == rowIndex)
  183. if (isSplit) {
  184. const { count, startIndex, deptFather } = isSplit
  185. return { rowspan: count, colspan: 1 }
  186. } else {
  187. return {
  188. rowspan: 0,
  189. colspan: 0
  190. }
  191. }
  192. }
  193. }
  194. // 计算完成率
  195. const computedRate = (value, number?) => {
  196. let rate = value ? (value * 100).toFixed(2) : 0
  197. if (number) {
  198. return rate
  199. } else {
  200. return rate + ' %'
  201. }
  202. }
  203. // 点击查看跳转详情
  204. const { push } = useRouter()
  205. const handleView = (row) => {
  206. // 还需判断是跳转周报还是日报
  207. if (row.reportType == 'weekly') {
  208. push(
  209. `/oaSystem/DeptCenter/weeklyStatistic?deptId=${row.deptId}&date=${moment(
  210. selectDate.value
  211. ).format('YYYY-MM')}`
  212. )
  213. } else {
  214. push(
  215. `/oaSystem/DeptCenter/dailyStatistic?deptId=${row.deptId}&date=${moment(
  216. selectDate.value
  217. ).format('YYYY-MM')}`
  218. )
  219. }
  220. }
  221. /** 一键催报 ↓ */
  222. const isDisabled = ref(false)
  223. const countdown = ref(0)
  224. const sendRemind = () => {
  225. const searchDate = selectDate.value ?? moment().format('YYYY-MM')
  226. request
  227. .get({
  228. url: '/adm/reportStatistics/sendFillRemind',
  229. params: {
  230. year: moment(searchDate).format('YYYY'),
  231. month: moment(searchDate).format('M')
  232. }
  233. })
  234. .then(() => {
  235. ElMessage.success('已成功向未填报周日报员工发送催报消息!')
  236. })
  237. .catch(() => {
  238. ElMessage.error('催报消息发送失败,请稍后重试!')
  239. })
  240. }
  241. const handleClick = () => {
  242. if (!isDisabled.value) {
  243. isDisabled.value = true
  244. countdown.value = 15
  245. sendRemind()
  246. const timer = setInterval(() => {
  247. countdown.value -= 1
  248. if (countdown.value <= 0) {
  249. clearInterval(timer)
  250. isDisabled.value = false
  251. }
  252. }, 1000)
  253. }
  254. }
  255. </script>
  256. <style scoped lang="scss">
  257. .title {
  258. height: 32px;
  259. font-weight: bold;
  260. font-size: 24px;
  261. color: #121518;
  262. display: flex;
  263. justify-content: space-between;
  264. .remind-btn {
  265. margin-right: 50px;
  266. }
  267. }
  268. .table-box {
  269. position: relative;
  270. width: 100%;
  271. margin-top: 20px;
  272. flex: 1;
  273. .el-table {
  274. width: 100%;
  275. height: calc(100vh - 280px);
  276. display: block;
  277. overflow: hidden;
  278. // background-color: none;
  279. }
  280. }
  281. :deep(.table-header) {
  282. th.el-table__cell {
  283. font-weight: 400 !important;
  284. color: #121518 !important;
  285. background-color: #cfdae2 !important;
  286. }
  287. }
  288. :deep(.dept-father) {
  289. font-family:
  290. Microsoft YaHei,
  291. Microsoft YaHei;
  292. font-weight: bold;
  293. font-size: 16px;
  294. color: #121518;
  295. }
  296. </style>