交通管理局公文项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

483 lines
16 KiB

2 months ago
1 week ago
1 week ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
  1. <template>
  2. <div style="height: calc(100vh - 234px); margin-top: 20px; padding-bottom: 20px; overflow-y: scroll;">
  3. <el-button :disabled="tableData.length===0" :loading="exportLoading" style="padding: 7px 10px; position: absolute; right: 6px; top: -6px;" @click="handleExport">
  4. <i class="iconfont icon-daochu" />
  5. 导出
  6. </el-button>
  7. <div>
  8. <div class="table-content">
  9. <div v-if="activeIndex===0" class="table-right-date">截止到{{ nowDate }}</div>
  10. <div v-if="activeIndex===1" class="table-right-date">选择年份
  11. <el-select v-model="yearValue" placeholder="请选择" style="width: 90px;" @change="handleYear">
  12. <el-option
  13. v-for="item in yearsOptions"
  14. :key="item.id"
  15. :label="item.label"
  16. :value="item.id"
  17. />
  18. </el-select>
  19. </div>
  20. <div v-if="activeIndex===2" class="table-right-date">自定义起止时间
  21. <el-date-picker
  22. v-model="customYear"
  23. type="datetimerange"
  24. value-format="yyyy-MM-dd HH:mm:ss"
  25. range-separator="至"
  26. start-placeholder="开始日期"
  27. end-placeholder="结束日期"
  28. @change="handleCustomYear"
  29. />
  30. </div>
  31. <el-table v-if="tableData.length!==0" ref="table" v-loading="tableDataLoading" :data="tableData" border :cell-style="tableCellStyle">
  32. <el-table-column
  33. :prop="getFirstColumnProp()"
  34. :label="getFirstColumnLabel()"
  35. width="100"
  36. align="center"
  37. show-overflow-tooltip
  38. />
  39. <el-table-column
  40. v-for="(column, index) in tableColumns"
  41. :key="index"
  42. :prop="column"
  43. :label="column"
  44. :width="column === '省市领导批示件' ? '120' : '80'"
  45. align="center"
  46. />
  47. <el-table-column
  48. prop="合计"
  49. label="合计"
  50. width="60"
  51. align="center"
  52. />
  53. </el-table>
  54. </div>
  55. </div>
  56. </div>
  57. </template>
  58. <script>
  59. import CRUD, { presenter, crud } from '@crud/crud'
  60. import { getCurrentTime } from '@/utils/index'
  61. import { FetchAnnualStatistics, FetchAnnualStatisticsByMonth, FetchAnnualStatisticsByBorrow } from '@/api/system/documentArchives'
  62. import * as XLSX from 'xlsx'
  63. import { saveAs } from 'file-saver' // 简化文件保存
  64. export default {
  65. name: 'MiodTable',
  66. components: { },
  67. mixins: [presenter(), crud()],
  68. cruds() {
  69. return CRUD({
  70. url: 'api/archivesUtilize/initborrowLog',
  71. title: '公文统计',
  72. optShow: {
  73. add: false,
  74. edit: false,
  75. del: false,
  76. download: false,
  77. reset: false,
  78. group: false
  79. }
  80. })
  81. },
  82. props: {
  83. activeIndex: {
  84. type: Number,
  85. default: 0
  86. },
  87. isUserCenter: {
  88. type: Boolean,
  89. default: false
  90. }
  91. },
  92. data() {
  93. return {
  94. exportLoading: false,
  95. nowDate: getCurrentTime(),
  96. yearValue: 2025,
  97. yearsOptions: [],
  98. customYear: this.calculateDefaultRange(),
  99. tableDataLoading: false,
  100. tableData: [],
  101. fixedColumnOrder: ['中共中央', '国务院', '省交通厅', '省委', '市委', '省政府', '市政府', '省市领导批示件', '综合'],
  102. tableColumns: []
  103. }
  104. },
  105. watch: {
  106. activeIndex: function(newValue, oldValue) {
  107. this.loadDataByTab(newValue)
  108. }
  109. },
  110. created() {
  111. this.generateYearsData()
  112. },
  113. mounted() {
  114. this.loadDataByTab(this.activeIndex)
  115. },
  116. methods: {
  117. loadDataByTab(tabIndex) {
  118. this.tableDataLoading = true
  119. this.tableData = []
  120. switch (tabIndex) {
  121. case 0:
  122. this.getAnnualStatistics()
  123. break
  124. case 1:
  125. this.getAnnualStatisticsByMonth()
  126. break
  127. case 2:
  128. this.getAnnualStatisticsByBorrow()
  129. break
  130. default:
  131. break
  132. }
  133. },
  134. handleYear(val) {
  135. this.tableDataLoading = true
  136. this.tableData = []
  137. this.yearValue = val
  138. this.getAnnualStatisticsByMonth()
  139. },
  140. handleCustomYear(val) {
  141. console.log('this.customYear', this.customYear)
  142. console.log('this.val', val)
  143. this.tableDataLoading = true
  144. this.tableData = []
  145. this.getAnnualStatisticsByBorrow()
  146. },
  147. getFirstColumnProp() {
  148. switch (this.activeIndex) {
  149. case 0: return 'year' // 年度统计:年份
  150. case 1: return 'month' // 月度统计:月份
  151. case 2: return 'name' // 传阅统计:借阅者(假设prop为name)
  152. default: return ''
  153. }
  154. },
  155. getFirstColumnLabel() {
  156. switch (this.activeIndex) {
  157. case 0: return '年度'
  158. case 1: return '月份'
  159. case 2: return '借阅者'
  160. default: return ''
  161. }
  162. },
  163. getAnnualStatistics() {
  164. FetchAnnualStatistics().then((res) => {
  165. this.tableData = this.formatTableData('year', res)
  166. setTimeout(() => {
  167. this.tableDataLoading = false
  168. }, 500)
  169. }).catch(err => {
  170. console.log(err)
  171. })
  172. },
  173. getAnnualStatisticsByMonth() {
  174. const params = {
  175. year: this.yearValue
  176. }
  177. FetchAnnualStatisticsByMonth(params).then((res) => {
  178. this.tableData = this.formatTableData('month', res)
  179. setTimeout(() => {
  180. this.tableDataLoading = false
  181. }, 500)
  182. }).catch(err => {
  183. console.log(err)
  184. })
  185. },
  186. getAnnualStatisticsByBorrow() {
  187. console.log('customYear', this.customYear)
  188. const params = {
  189. 'startTime': this.customYear[0],
  190. 'endTime': this.customYear[1]
  191. }
  192. console.log('params', params)
  193. FetchAnnualStatisticsByBorrow(params).then((res) => {
  194. this.tableData = this.formatTableData('name', res)
  195. setTimeout(() => {
  196. this.tableDataLoading = false
  197. }, 500)
  198. }).catch(err => {
  199. console.log(err)
  200. })
  201. },
  202. formatTableData(dataType, rawData) {
  203. const allColumns = new Set()
  204. Object.keys(rawData).forEach(key => {
  205. if (key === '合计') return // 跳过合计行
  206. Object.keys(rawData[key]).forEach(column => {
  207. if (column !== '合计') { // 排除合计字段
  208. allColumns.add(column)
  209. }
  210. })
  211. })
  212. // 按固定顺序过滤并排序列名
  213. this.tableColumns = this.fixedColumnOrder.filter(column =>
  214. allColumns.has(column)
  215. )
  216. // 处理不在固定顺序中的额外列(如果有)
  217. const extraColumns = [...allColumns].filter(
  218. column => !this.fixedColumnOrder.includes(column)
  219. )
  220. this.tableColumns = [...this.tableColumns, ...extraColumns]
  221. // 处理数据行
  222. const formattedData = []
  223. let sortedKeys
  224. if (dataType === 'year') {
  225. // 年份数据:倒序排列(最新年份在前)
  226. sortedKeys = Object.keys(rawData)
  227. .filter(key => key !== '合计')
  228. .sort((a, b) => parseInt(b) - parseInt(a))
  229. } else if (dataType === 'month') {
  230. // 月份数据:正序排列(1月到12月)
  231. sortedKeys = Object.keys(rawData)
  232. .filter(key => key !== '合计')
  233. .sort((a, b) => parseInt(a) - parseInt(b))
  234. } else if (dataType === 'name') {
  235. // 名称数据:按字母顺序排序
  236. sortedKeys = Object.keys(rawData)
  237. .filter(key => key !== '合计')
  238. .sort((a, b) => a.localeCompare(b))
  239. } else {
  240. // 默认不排序
  241. sortedKeys = Object.keys(rawData).filter(key => key !== '合计')
  242. }
  243. // 格式化数据行
  244. sortedKeys.forEach(key => {
  245. const values = rawData[key]
  246. const item = dataType === 'year'
  247. ? { year: key, ...values } // 年份数据
  248. : dataType === 'month'
  249. ? { month: `${key}`, ...values } // 月份数据
  250. : { name: key, ...values } // 名称数据
  251. formattedData.push(item)
  252. })
  253. // 处理合计行
  254. if (rawData['合计']) {
  255. formattedData.push({
  256. [dataType === 'year' ? 'year' : dataType === 'month' ? 'month' : 'name']: '合计',
  257. ...rawData['合计']
  258. })
  259. }
  260. return formattedData
  261. },
  262. getExportMeta() {
  263. console.log('this.customYear', this.customYear)
  264. let sheetName, firstColumnLabel, firstColumnProp
  265. const formatDateRange = (dates) => {
  266. if (!dates || dates.length !== 2) return ''
  267. // 解析日期字符串并获取年月日信息
  268. const formatDate = (dateStr) => {
  269. const date = new Date(dateStr)
  270. const year = date.getFullYear()
  271. const month = String(date.getMonth() + 1).padStart(2, '0')
  272. const day = String(date.getDate()).padStart(2, '0')
  273. return `${year}${month}${day}`
  274. }
  275. const startDate = formatDate(dates[0])
  276. const endDate = formatDate(dates[1])
  277. return `${startDate}-${endDate}`
  278. }
  279. const dateRange = formatDateRange(this.customYear)
  280. switch (this.activeIndex) {
  281. case 0:
  282. sheetName = '馆藏年度统计'
  283. firstColumnLabel = '年度'
  284. firstColumnProp = 'year' // 对应数据中的年份字段
  285. break
  286. case 1:
  287. // 月度统计:拼接 yearValue(需确保已正确获取)
  288. sheetName = `馆藏月份统计_${this.yearValue}年度`
  289. firstColumnLabel = '月份'
  290. firstColumnProp = 'month' // 对应数据中的月份字段(如 '1月')
  291. break
  292. case 2:
  293. sheetName = `传阅统计_${dateRange}`
  294. firstColumnLabel = '借阅者'
  295. firstColumnProp = 'name' // 对应数据中的借阅者名称字段
  296. break
  297. default:
  298. return [null, null, null]
  299. }
  300. return [sheetName, firstColumnLabel, firstColumnProp]
  301. },
  302. async handleExport() {
  303. this.exportLoading = true
  304. try {
  305. // 直接使用统一的 tableData(需确保切换 tab 时已更新为对应数据)
  306. if (!this.tableData || this.tableData.length === 0) {
  307. this.$message.warning('无数据可导出')
  308. return
  309. }
  310. // 动态生成 sheetName 和首列标签
  311. const [sheetName, firstColumnLabel, firstColumnProp] = this.getExportMeta()
  312. // 生成导出数据
  313. const exportData = this.tableData.map(item => {
  314. const row = { [firstColumnLabel]: item[firstColumnProp] } // 首列数据
  315. this.tableColumns.forEach(column => {
  316. row[column] = item[column]
  317. })
  318. // 添加合计列(若存在)
  319. if (item['合计'] !== undefined) {
  320. row['合计'] = item['合计']
  321. }
  322. return row
  323. })
  324. // 生成工作簿
  325. const workbook = XLSX.utils.book_new()
  326. const sheet = XLSX.utils.json_to_sheet(exportData)
  327. XLSX.utils.book_append_sheet(workbook, sheet, sheetName)
  328. // 导出文件(文件名与 sheetName 一致,添加时间戳)
  329. const fileName = `${sheetName}_${new Date().getTime()}.xlsx`
  330. // const now = new Date()
  331. // const fileName = `${sheetName}_${now.getFullYear()}-${
  332. // String(now.getMonth() + 1).padStart(2, '0')}-${
  333. // String(now.getDate()).padStart(2, '0')}_${
  334. // String(now.getHours()).padStart(2, '0')}-${
  335. // String(now.getMinutes()).padStart(2, '0')}-${
  336. // String(now.getSeconds()).padStart(2, '0')}.xlsx`
  337. const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'array', cellDates: true })
  338. const blob = new Blob([wbout], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
  339. saveAs(blob, fileName)
  340. } catch (error) {
  341. console.error('导出错误:', error)
  342. this.$message.error('导出失败,请检查数据格式!')
  343. } finally {
  344. this.exportLoading = false
  345. }
  346. },
  347. tableCellStyle({ row, column, rowIndex, columnIndex }) {
  348. // 最后一列设置背景色为 lightblue
  349. if (columnIndex === this.$refs.table.columns.length - 1) {
  350. return 'background-color: #f5f7fa;border-top: 1px solid #dfe6ec;'
  351. }
  352. return ''
  353. },
  354. generateYearsData() {
  355. const currentYear = new Date().getFullYear() // 获取当前年份
  356. const startYear = 2007 // 起始年份
  357. const years = []
  358. for (let year = currentYear; year >= startYear; year--) {
  359. years.push({
  360. id: year,
  361. label: year
  362. })
  363. }
  364. this.yearsOptions = years
  365. },
  366. // 计算默认时间范围的方法
  367. calculateDefaultRange() {
  368. const now = new Date() // 获取当前时间
  369. const oneMonthAgo = new Date() // 创建一个月前的时间对象
  370. oneMonthAgo.setMonth(now.getMonth() - 1) // 月份减 1(当前月的第一天向前推一个月)
  371. // 处理日期边界:若当前是月初,需取上个月的同一天(避免跨月错误)
  372. if (now.getDate() < oneMonthAgo.getDate()) {
  373. oneMonthAgo.setDate(1) // 若上月天数不足,设为上月第一天
  374. }
  375. // 转换为 ISO 格式字符串(需与组件的时间格式匹配,默认支持 'YYYY-MM-DD HH:mm:ss')
  376. const start = this.formatDate(oneMonthAgo)
  377. const end = this.formatDate(now) // 结束时间为当前日期
  378. return [start, end]
  379. },
  380. formatDate(date) {
  381. const year = date.getFullYear()
  382. const month = String(date.getMonth() + 1).padStart(2, '0') // 月份补零
  383. const day = String(date.getDate()).padStart(2, '0') // 日期补零
  384. const hours = String(date.getHours()).padStart(2, '0') // 小时补零(可选)
  385. const minutes = String(date.getMinutes()).padStart(2, '0') // 分钟补零(可选)
  386. const seconds = String(date.getSeconds()).padStart(2, '0') // 秒补零(可选)
  387. // 若不需要时分秒,可省略后面部分,格式为 'YYYY-MM-DD'
  388. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  389. }
  390. }
  391. }
  392. </script>
  393. <style lang="scss" scoped>
  394. .table-content {
  395. box-sizing: border-box;
  396. display: inline-block;
  397. // border: 1px solid #dfe6ec;
  398. border-bottom: none;
  399. border-right: none;
  400. font-size: 14px;
  401. .table-right-date{
  402. line-height: 40px;
  403. text-align: right;
  404. padding: 5px 10px;
  405. font-weight: bold;
  406. color: #0c0e1e;
  407. // border-bottom: 1px solid #dfe6ec;
  408. // border-right: 1px solid #dfe6ec;
  409. }
  410. }
  411. // ::v-deep .el-table{
  412. // width: auto !important;
  413. // }
  414. // ::v-deep .el-table .cell{
  415. // padding: 0 !important;
  416. // margin-left: -2px !important;
  417. // margin-top: -2px !important;
  418. // }
  419. // ::v-deep .el-table tr .el-table__cell{
  420. // height: 28px !important;
  421. // }
  422. ::v-deep .el-table__header-wrapper {
  423. background-color: transparent !important;
  424. border-top: 1px solid #dfe6ec;
  425. border-left: 1px solid #dfe6ec;
  426. }
  427. ::v-deep .el-table::before, .el-table--group::after,
  428. ::v-deep .el-table--border::after{
  429. background-color: transparent !important;
  430. }
  431. ::v-deep .el-table__body-wrapper {
  432. border-left: 1px solid #dfe6ec !important;
  433. }
  434. // ::v-deep .el-table .el-table__body-wrapper td.el-table__cell,
  435. // ::v-deep .el-table .el-table__fixed-right td.el-table__cell {
  436. // padding: 0 !important;
  437. // border-left: 1px solid #000;
  438. // border-bottom: 1px solid #000;
  439. // border-right: 1px solid #000;
  440. // }
  441. // ::v-deep.el-table .el-table__header-wrapper th.el-table__cell,
  442. // ::v-deep.el-table .el-table__header th.el-table__cell{
  443. // border: 1px solid #000;
  444. // }
  445. ::v-deep .el-table--group, .el-table--border{
  446. border: none !important;
  447. }
  448. /* 在 style 中 */
  449. ::v-deep .el-table .el-table__body tr:last-child td {
  450. background-color: #f5f7fa !important;
  451. border-top: 1px solid #dfe6ec !important;
  452. border-bottom: 1px solid #dfe6ec !important;
  453. }
  454. </style>