index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <template>
  2. <div class="airport-view">
  3. <div class="airport-header">
  4. <AirportForm :name="name" @form-data-change="formDataChangeHandler" />
  5. <div class="airport-count">
  6. <CountBox
  7. :count-number="airportCount.flightNum"
  8. label="今日计划航班数(班)"
  9. :length="3"
  10. />
  11. <CountBox
  12. :count-number="airportCount.finishFlightNum"
  13. label="已完成航班数(班)"
  14. :length="3"
  15. />
  16. <CountBox
  17. :count-number="airportCount.weight"
  18. :label="`已${isDeparture ? '装载' : '卸载'}重量(吨)`"
  19. :length="4"
  20. />
  21. </div>
  22. <div class="airport-settings">
  23. <div v-permission="getPermission('count')">
  24. <CommonSwitch v-model:flag="countFlag" label="显示件数" />
  25. </div>
  26. <!-- <div v-permission="getPermission('UTC')">
  27. <CommonSwitch v-model:flag="UTCFlag" label="开启UTC" />
  28. </div> -->
  29. <div v-permission="getPermission('columnSet')">
  30. <ColumnSet
  31. :table-columns="tableColumns"
  32. @checked-submit="columnChecked"
  33. />
  34. </div>
  35. </div>
  36. </div>
  37. <div class="airport-table">
  38. <SimpleTable
  39. ref="tableRef"
  40. :columns="tableColumns"
  41. :data="tableData"
  42. row-key="rowKey"
  43. sequence
  44. highlight-current-row
  45. scrollbar-always-on
  46. :stripe="false"
  47. show-summary
  48. :summary-method="summaryMethod"
  49. :cache-keys="cacheKeys"
  50. :filter-sort-options="filterSortOptions"
  51. :label-formatter="tableColumnFormatter"
  52. :row-class-name="rowClassName"
  53. :cell-class-name="cellClass"
  54. :column-props="{ formatter: tableDataFormatter }"
  55. @sort-rule-change="sortRuleChangeHandler"
  56. @cell-click="cellClickHandler"
  57. />
  58. </div>
  59. </div>
  60. </template>
  61. <script lang="tsx" setup>
  62. import AirportForm from './AirportForm.vue'
  63. import ColumnSet from '@/components/ColumnSet/index.vue'
  64. import CountBox from '../../components/CountBox/index.vue'
  65. import CommonSwitch from '../../components/CommonSwitch/index.vue'
  66. import SimpleTable from '@/components/SimpleTable/index.vue'
  67. import { useTableColumnSet } from '@/hooks/useTableColumnSet'
  68. import { useAirportTable } from './useAirportTable'
  69. import { useTableStyle } from '../../hooks/useTableStyle'
  70. import { useTableCellClick } from '../../hooks/useTableCellClick'
  71. import { useFormatter } from './useFormatter'
  72. import { CommonData } from '~/common'
  73. import { useLoop } from '@/hooks/useLoop'
  74. import { useTableSettingsStore } from '@/store/tableSettings'
  75. import { useFlightState } from './useFlightState'
  76. import { Query } from '@/api/webApi'
  77. import { ElMessage, SummaryMethod } from 'element-plus'
  78. const props = defineProps({
  79. name: {
  80. type: String,
  81. required: true,
  82. },
  83. })
  84. const isInternational = props.name.includes('International')
  85. const isDeparture = props.name.includes('Departure')
  86. const formData: CommonData = reactive({})
  87. const formDataChangeHandler = (data: CommonData) => {
  88. Object.assign(formData, data)
  89. }
  90. const airportCount = reactive({
  91. flightNum: 0,
  92. finishFlightNum: 0,
  93. weight: 0,
  94. })
  95. const getAirportCount = async () => {
  96. try {
  97. const { startDate, endDate } = formData
  98. if (typeof startDate !== 'string' || typeof endDate !== 'string') {
  99. throw new Error('Type Error: date must be string')
  100. }
  101. const dataContent = [startDate.slice(0, 10), endDate.slice(0, 10)]
  102. const {
  103. code,
  104. returnData: { listValues },
  105. message,
  106. } = await Query({
  107. id:
  108. DATACONTENT_ID[
  109. `${
  110. props.name.slice(0, 1).toLowerCase() + props.name.slice(1)
  111. }AirportCount`
  112. ],
  113. dataContent,
  114. })
  115. if (Number(code) !== 0) {
  116. throw new Error(message || '失败')
  117. }
  118. if (!listValues.length) {
  119. ElMessage.info('未查询到统计信息')
  120. airportCount.flightNum = airportCount.finishFlightNum = airportCount.weight = 0
  121. return
  122. }
  123. const { flightNum, finishFlightNum, weight } = listValues[0]
  124. airportCount.flightNum = flightNum ?? 0
  125. airportCount.finishFlightNum = finishFlightNum ?? 0
  126. if (typeof weight === 'number' && weight > 0) {
  127. if (weight <= 1000) {
  128. airportCount.weight = 1
  129. } else {
  130. airportCount.weight = Math.round(weight / 1000)
  131. }
  132. } else {
  133. airportCount.weight = 0
  134. }
  135. } catch (error) {
  136. console.error(error)
  137. }
  138. }
  139. const { tableColumns, tableData, getTableData } = useAirportTable(
  140. props.name,
  141. formData
  142. )
  143. const finishedCount = ref(0)
  144. const { getWarningRules } = useFlightState(props.name, tableData, finishedCount)
  145. useLoop(
  146. [
  147. getAirportCount,
  148. getTableData,
  149. // getWarningRules,
  150. ],
  151. 'airport',
  152. [formData]
  153. )
  154. const countFlag = ref(false)
  155. const { tableColumnFormatter, tableDataFormatter } = useFormatter(countFlag)
  156. // const UTCFlag = ref(true)
  157. const summaryMethod: SummaryMethod<CommonData> = ({ columns, data }) => {
  158. const sums: string[] = []
  159. columns.forEach((column, index) => {
  160. const countColumn = tableColumns.value.find(
  161. col => column.property === col.columnName && col.needCount
  162. )
  163. if (countColumn) {
  164. if (countColumn.countMode === 'split') {
  165. let sumArr = data.reduce((prev: number[], curr: CommonData) => {
  166. const cellData = curr[column.property]
  167. if (typeof cellData === 'string') {
  168. const splitData = cellData.split('/')
  169. splitData.forEach((str, i) => {
  170. const num = Number(str)
  171. if (!Number.isNaN(num)) {
  172. if (prev[i]) {
  173. prev[i] += num
  174. } else {
  175. prev[i] = num
  176. }
  177. }
  178. })
  179. }
  180. return prev
  181. }, [])
  182. const matched = column.label.match(/(?<=\()\S+(?=\))/)
  183. if (matched && !countFlag.value) {
  184. const machedStr = matched[0]
  185. const countIndex = machedStr.split('/').findIndex(str => str === '件')
  186. if (countIndex > -1 && countIndex < sumArr.length) {
  187. sumArr.splice(countIndex, 1)
  188. }
  189. }
  190. sums[index] = sumArr.join('/')
  191. } else {
  192. const sumNumber = data.reduce((prev: number, curr: CommonData) => {
  193. const cellData = curr[column.property]
  194. if (countColumn.countMode === 'all') {
  195. return prev + 1
  196. }
  197. if (countColumn.countMode === 'notNull') {
  198. return cellData ? prev + 1 : prev
  199. }
  200. const value = Number(cellData)
  201. if (!Number.isNaN(value)) {
  202. prev += value
  203. }
  204. return prev
  205. }, 0)
  206. sums[index] = sumNumber.toString()
  207. }
  208. }
  209. })
  210. sums[0] = '合计:' + (sums[0] ?? '')
  211. return sums
  212. }
  213. /* 离港视图默认的排序方式:
  214. * 0.国内离港-有收运核单的排在前
  215. * 1.已起飞排在前
  216. * 2.未起飞中已装机在前
  217. * 3.已起飞和未起飞分类中各自按照预计起飞时间排序
  218. */
  219. const defaultDepartureSortFunction = (a: CommonData, b: CommonData) => {
  220. const departureTimeCompare = (a: CommonData, b: CommonData) => {
  221. if (a.planDepartureTime) {
  222. if (b.planDepartureTime) {
  223. if (a.planDepartureTime > b.planDepartureTime) {
  224. return 1
  225. } else if (a.planDepartureTime < b.planDepartureTime) {
  226. return -1
  227. } else {
  228. return 0
  229. }
  230. } else {
  231. return -1
  232. }
  233. } else if (b.planDepartureTime) {
  234. return 1
  235. } else {
  236. return 0
  237. }
  238. }
  239. const loadCompare = (a: CommonData, b: CommonData) => {
  240. if (a.loadPlaneSureTime) {
  241. if (b.loadPlaneSureTime) {
  242. return departureTimeCompare(a, b)
  243. } else {
  244. return -1
  245. }
  246. } else if (b.loadPlaneSureTime) {
  247. return 1
  248. } else {
  249. return departureTimeCompare(a, b)
  250. }
  251. }
  252. const takeOffCompare = (a: CommonData, b: CommonData) => {
  253. if (a.hasTakenOff === 'Y') {
  254. if (b.hasTakenOff === 'Y') {
  255. return departureTimeCompare(a, b)
  256. } else {
  257. return -1
  258. }
  259. } else if (b.hasTakenOff === 'Y') {
  260. return 1
  261. } else {
  262. return loadCompare(a, b)
  263. }
  264. }
  265. const receiveCompare = (a: CommonData, b: CommonData) => {
  266. if (a.receiveSure) {
  267. if (b.receiveSure) {
  268. return takeOffCompare(a, b)
  269. } else {
  270. return -1
  271. }
  272. } else if (b.receiveSure) {
  273. return 1
  274. } else {
  275. return takeOffCompare(a, b)
  276. }
  277. }
  278. const receiveSureCompare = (a: CommonData, b: CommonData) => {
  279. if (a.receiveSure1) {
  280. if (b.receiveSure1) {
  281. return takeOffCompare(a, b)
  282. } else {
  283. return -1
  284. }
  285. } else if (b.receiveSure1) {
  286. return 1
  287. } else {
  288. return takeOffCompare(a, b)
  289. }
  290. }
  291. const enterCompare = (a: CommonData, b: CommonData) => {
  292. if (a.enterPark) {
  293. if (b.enterPark) {
  294. return receiveSureCompare(a, b)
  295. } else {
  296. return -1
  297. }
  298. } else if (b.enterPark) {
  299. return 1
  300. } else {
  301. return receiveSureCompare(a, b)
  302. }
  303. }
  304. return isInternational ? enterCompare(a, b) : receiveCompare(a, b)
  305. }
  306. const defaultArrivalSortFunction = (a: CommonData, b: CommonData) => {
  307. const landingTimeCompare = (a: CommonData, b: CommonData) => {
  308. if (a.planLandingTime) {
  309. if (b.planLandingTime) {
  310. if (a.planLandingTime > b.planLandingTime) {
  311. return 1
  312. } else if (a.planLandingTime < b.planLandingTime) {
  313. return -1
  314. } else {
  315. return 0
  316. }
  317. } else {
  318. return -1
  319. }
  320. } else if (b.planLandingTime) {
  321. return 1
  322. } else {
  323. return 0
  324. }
  325. }
  326. const tallyCompare = (a: CommonData, b: CommonData) => {
  327. if (a.tally) {
  328. if (b.tally) {
  329. return landingTimeCompare(a, b)
  330. } else {
  331. return -1
  332. }
  333. } else if (b.tally) {
  334. return 1
  335. } else {
  336. return landingTimeCompare(a, b)
  337. }
  338. }
  339. const unloadCompare = (a: CommonData, b: CommonData) => {
  340. if (a.unLoad) {
  341. if (b.unLoad) {
  342. return tallyCompare(a, b)
  343. } else {
  344. return -1
  345. }
  346. } else if (b.unLoad) {
  347. return 1
  348. } else {
  349. return tallyCompare(a, b)
  350. }
  351. }
  352. return unloadCompare(a, b)
  353. }
  354. const filterSortOptions = computed(() => ({
  355. defaultFilterValueMap,
  356. extraFilterValueMap: flightStateFilter,
  357. defaultSortFunction: isDeparture
  358. ? defaultDepartureSortFunction
  359. : defaultArrivalSortFunction,
  360. }))
  361. const sortRuleMap = ref({})
  362. const sortRuleChangeHandler = map => {
  363. sortRuleMap.value = map
  364. }
  365. const { columnChecked } = useTableColumnSet(tableColumns)
  366. const { rowClass, cellClass } = useTableStyle(props.name)
  367. const rowClassName = params => {
  368. const { row, rowIndex } = params
  369. const classes: string[] = []
  370. if (
  371. (row.hasTakenOff === 'Y' || row.hasLanded === 'Y') &&
  372. (['planDepartureTime', 'planLandingTime'].some(
  373. key => sortRuleMap.value[key] === 'ascending'
  374. ) ||
  375. Object.keys(sortRuleMap.value).length === 0) &&
  376. finishedCount.value < tableData.value.length &&
  377. rowIndex === finishedCount.value - 1
  378. ) {
  379. classes.push('underline-red')
  380. }
  381. return `${rowClass(params)} ${classes.join(' ')}`
  382. }
  383. const { cellClickHandler } = useTableCellClick(props.name)
  384. const route = useRoute()
  385. const { savedTableFilterValueMap } = useTableSettingsStore()
  386. const defaultFilterValueMap = savedTableFilterValueMap[route.path]
  387. const flightStateFilter = computed<{} | { [x: string]: string[] }>(() => {
  388. switch (formData.flightState) {
  389. case 'hasTakenOff':
  390. return { hasTakenOff: ['Y'] }
  391. case 'hasNotTakenOff':
  392. return { hasTakenOff: ['N'] }
  393. case 'hasLanded':
  394. return { hasLanded: ['Y'] }
  395. case 'hasNotLanded':
  396. return { hasLanded: ['N'] }
  397. case 'canceled':
  398. return { flightState: ['CAN'] }
  399. case 'groundService':
  400. return { groundService: ['Y'] }
  401. case 'groundServiceSZ':
  402. return { groundServiceSZ: ['Y'] }
  403. default:
  404. return {}
  405. }
  406. })
  407. const cacheKeys = ['IATACode']
  408. const permissionMap = {
  409. DepartureAirport: {
  410. count:
  411. 'number_of_pieces_displayed_in_domestic_departure_terminal_view_button',
  412. UTC: 'turn_on_utc_in_view_of_domestic_departure_terminal_button',
  413. columnSet: 'domestic_departure_terminal_view_column_setting_button',
  414. },
  415. InternationalDepartureAirport: {
  416. count:
  417. 'number_of_pieces_displayed_in_international_departure_terminal_view_button',
  418. UTC: 'international_departure_terminal_view_opens_utc_button',
  419. columnSet: 'international_departure_terminal_view_column_setting_button',
  420. },
  421. ArrivalAirport: {
  422. count:
  423. 'number_of_pieces_displayed_in_domestic_inbound_terminal_view_button',
  424. UTC: 'turn_on_utc_in_view_of_domestic_inbound_terminal_button',
  425. columnSet: 'domestic_inbound_terminal_view_column_setting_button',
  426. },
  427. InternationalArrivalAirport: {
  428. count:
  429. 'number_of_display_pieces_of_international_inbound_terminal_view_button',
  430. UTC: 'the_view_of_international_inbound_terminal_opens_utc_button',
  431. columnSet: 'view_column_setting_of_international_inbound_terminal_button',
  432. },
  433. }
  434. const getPermission = (type?: string) => {
  435. return [permissionMap[props.name][type]]
  436. }
  437. const tableRef = ref<InstanceType<typeof SimpleTable> | null>(null)
  438. const hasSetTableScroll = ref(false)
  439. watch(
  440. [() => formData.startDate, () => formData.endDate],
  441. ([startDate, endDate], [preStartDate, preEndDate]) => {
  442. if (startDate !== preStartDate || endDate !== preEndDate) {
  443. hasSetTableScroll.value = false
  444. }
  445. }
  446. )
  447. const flightTypeMap = ['货机', '客机', '其他']
  448. watch(tableData, async data => {
  449. data.forEach(row => {
  450. row.flightType =
  451. typeof row.flightType === 'number'
  452. ? flightTypeMap[row.flightType] ?? '其他'
  453. : '其他'
  454. })
  455. await nextTick()
  456. if (hasSetTableScroll.value || !finishedCount.value) {
  457. return
  458. }
  459. if (tableRef.value?.table) {
  460. tableRef.value?.table?.setScrollTop((finishedCount.value - 1) * 50)
  461. }
  462. hasSetTableScroll.value = true
  463. })
  464. </script>
  465. <style lang="scss" scoped>
  466. @import './index.scss';
  467. </style>