| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868 |
- <script setup lang="ts">
- import dayjs from 'dayjs'
- import type { ECharts } from 'echarts/core'
- import { DataZoomComponent } from 'echarts/components'
- import {
- IotRhDailyReportApi,
- type IotRhDailyReportTotalWorkloadVO
- } from '@/api/pms/iotrhdailyreport'
- import { useDebounceFn } from '@vueuse/core'
- import CountTo from '@/components/count-to1.vue'
- import echarts from '@/plugins/echarts'
- import UnfilledReportDialog from '../UnfilledReportDialog.vue'
- import { Motion, AnimatePresence } from 'motion-v'
- import download from '@/utils/download'
- import { useTableComponents } from '@/components/ZmTable/useTableComponents'
- echarts.use([DataZoomComponent])
- interface Query {
- pageNo: number
- pageSize: number
- deptId: number
- contractName?: string
- taskName?: string
- createTime: string[]
- }
- const props = defineProps<{
- query: Query
- deptName: string
- refreshKey: number
- }>()
- const totalWorkKeys: [string, string, string, string, number][] = [
- ['totalGasInjection', '万方', '累计注气量', 'i-material-symbols:cloud-outline text-sky', 2],
- [
- 'totalWaterInjection',
- '万方',
- '累计注水量',
- 'i-material-symbols:water-drop-outline-rounded text-sky',
- 2
- ],
- [
- 'utilizationRate',
- '%',
- '设备利用率',
- 'i-material-symbols:check-circle-outline-rounded text-emerald',
- 0
- ],
- [
- 'totalPowerConsumption',
- 'MWh',
- '累计用电量',
- 'i-material-symbols:electric-bolt-outline-rounded text-sky',
- 2
- ],
- [
- 'totalFuelConsumption',
- '升',
- '累计油耗',
- 'i-material-symbols:directions-car-outline-rounded text-sky',
- 2
- ],
- ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky', 0],
- [
- 'alreadyReported',
- '个',
- '已填报',
- 'i-material-symbols:check-circle-outline-rounded text-emerald',
- 0
- ],
- ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose', 0]
- ]
- const totalWork = ref({
- totalCount: 0,
- alreadyReported: 0,
- notReported: 0,
- totalFuelConsumption: 0,
- totalPowerConsumption: 0,
- totalWaterInjection: 0,
- totalGasInjection: 0,
- utilizationRate: 0
- })
- const totalLoading = ref(false)
- const totalWorkloadDetail = ref({
- totalN2GasInjection: 0,
- totalNaturalGasInjection: 0
- })
- const formatGasInjectionTooltipValue = (value?: number | null) => ((value || 0) / 10000).toFixed(4)
- const getTotal = useDebounceFn(async () => {
- totalLoading.value = true
- const { pageNo, pageSize, ...other } = props.query
- try {
- let res1: any[]
- if (props.query.createTime && props.query.createTime.length === 2) {
- res1 = await IotRhDailyReportApi.rhDailyReportStatistics({
- createTime: props.query.createTime,
- deptId: props.query.deptId
- })
- totalWork.value.totalCount = res1[0].count
- totalWork.value.alreadyReported = res1[1].count
- totalWork.value.notReported = res1[2].count
- }
- const res2: IotRhDailyReportTotalWorkloadVO = await IotRhDailyReportApi.totalWorkload(other)
- totalWorkloadDetail.value = {
- totalN2GasInjection: res2.totalN2GasInjection || 0,
- totalNaturalGasInjection: res2.totalNaturalGasInjection || 0
- }
- totalWork.value = {
- ...totalWork.value,
- ...res2,
- totalPowerConsumption: (res2.totalPowerConsumption || 0) / 1000,
- totalWaterInjection: (res2.totalWaterInjection || 0) / 10000,
- totalGasInjection: (res2.totalGasInjection || 0) / 10000,
- totalFuelConsumption: res2.totalFuelConsumption || 0,
- utilizationRate: Number(((res2.utilizationRate || 0) * 100).toFixed(2))
- }
- } finally {
- totalLoading.value = false
- }
- }, 500)
- interface List {
- id: number | null
- name: string | null
- type: '1' | '2' | '3'
- cumulativeGasInjection: number | null
- cumulativeWaterInjection: number | null
- cumulativePowerConsumption: number | null
- cumulativeFuelConsumption: number | null
- transitTime: number | null
- nonProductiveTime: number | null
- utilizationRate: number | null
- }
- const list = ref<List[]>([])
- const type = ref('2')
- function checkIsSameDay(createTime: string[]): boolean {
- if (!createTime || createTime.length < 2) {
- return false
- }
- const [startTime, endTime] = createTime
- return dayjs(startTime).isSame(endTime, 'day')
- }
- const columns = (type: string) => {
- return [
- {
- label: type === '2' ? '项目部' : '队伍',
- prop: 'name'
- },
- {
- label: '累计注气量(万方)',
- prop: 'cumulativeGasInjection'
- },
- {
- label: '累计注水量(万方)',
- prop: 'cumulativeWaterInjection'
- },
- {
- label: '累计用电量(MWh)',
- prop: 'cumulativePowerConsumption'
- },
- {
- label: '累计油耗(升)',
- prop: 'cumulativeFuelConsumption'
- },
- {
- label: '平均时效(%)',
- prop: 'hourUtilizationRate'
- },
- {
- label: '非生产时效(%)',
- prop: 'nonProductiveTime'
- },
- ...(type === '2' && checkIsSameDay(props.query.createTime)
- ? [
- {
- label: '队伍总数',
- prop: 'teamCount'
- },
- {
- label: '驻地待命',
- prop: 'zddmTeamCount'
- },
- {
- label: '施工准备',
- prop: 'zbTeamCount'
- },
- {
- label: '施工队伍',
- prop: 'sgTeamCount'
- }
- ]
- : []),
- {
- label: '设备利用率(%)',
- prop: 'utilizationRate',
- action: true
- }
- ]
- }
- const listLoading = ref(false)
- const formatter = (row: List, column: any) => {
- if (column.property === 'transitTime') {
- return (Number(row.transitTime ?? 0) * 100).toFixed(2) + '%'
- } else if (column.property === 'nonProductiveTime') {
- return (Number(row.nonProductiveTime ?? 0) * 100).toFixed(2) + '%'
- } else if (column.property === 'utilizationRate') {
- return (Number(row.utilizationRate ?? 0) * 100).toFixed(2) + '%'
- } else return row[column.property] ?? 0
- }
- const getList = useDebounceFn(async () => {
- listLoading.value = true
- try {
- const res = await IotRhDailyReportApi.getIotRhDailyReportSummary(props.query)
- const { list: reslist } = res
- type.value = reslist[0]?.type || '2'
- list.value = reslist.map(
- ({ id, projectDeptId, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => {
- return {
- id: type === '2' ? projectDeptId : teamId,
- name: type === '2' ? projectDeptName : teamName,
- ...other,
- cumulativeGasInjection: ((other.cumulativeGasInjection || 0) / 10000).toFixed(4),
- cumulativeWaterInjection: ((other.cumulativeWaterInjection || 0) / 10000).toFixed(2),
- cumulativePowerConsumption: ((other.cumulativePowerConsumption || 0) / 1000).toFixed(2),
- cumulativeFuelConsumption: (other.cumulativeFuelConsumption || 0).toFixed(2),
- utilizationRate: other.utilizationRate || 0,
- hourUtilizationRate: ((other.hourUtilizationRate || 0) * 100).toFixed(2) + '%'
- }
- }
- )
- } finally {
- listLoading.value = false
- }
- }, 500)
- const tab = ref<'表格' | '看板'>('表格')
- const currentTab = ref<'表格' | '看板'>('表格')
- const direction = ref<'left' | 'right'>('right')
- const handleSelectTab = (val: '表格' | '看板') => {
- tab.value = val
- direction.value = val === '看板' ? 'right' : 'left'
- nextTick(() => {
- currentTab.value = val
- setTimeout(() => {
- render()
- })
- })
- }
- const chartRef = ref<HTMLDivElement | null>(null)
- let chart: ECharts | null = null
- let chartContainerEl: HTMLDivElement | null = null
- const xAxisData = ref<string[]>([])
- type ChartKey =
- | 'cumulativeFuelConsumption'
- | 'cumulativeGasInjection'
- | 'cumulativePowerConsumption'
- | 'cumulativeWaterInjection'
- | 'transitTime'
- | 'utilizationRate'
- interface LegendItem {
- name: string
- key: ChartKey
- unit: string
- decimals: number
- }
- const legendItems: LegendItem[] = [
- { name: '累计油耗 (升)', key: 'cumulativeFuelConsumption', unit: '升', decimals: 2 },
- { name: '累计注气量 (万方)', key: 'cumulativeGasInjection', unit: '万方', decimals: 2 },
- { name: '累计用电量 (KWh)', key: 'cumulativePowerConsumption', unit: 'KWh', decimals: 2 },
- { name: '累计注水量 (方)', key: 'cumulativeWaterInjection', unit: '方', decimals: 2 },
- { name: '平均时效 (%)', key: 'transitTime', unit: '%', decimals: 2 },
- { name: '设备利用率 (%)', key: 'utilizationRate', unit: '%', decimals: 2 }
- ]
- const legendItemMap = legendItems.reduce<Record<string, LegendItem>>((map, item) => {
- map[item.name] = item
- return map
- }, {})
- const chartData = ref<Record<ChartKey, number[]>>({
- cumulativeFuelConsumption: [],
- cumulativeGasInjection: [],
- cumulativePowerConsumption: [],
- cumulativeWaterInjection: [],
- transitTime: [],
- utilizationRate: []
- })
- let chartLoading = ref(false)
- const getChart = useDebounceFn(async () => {
- chartLoading.value = true
- try {
- const res = await IotRhDailyReportApi.getIotRhDailyReportSummaryPolyline(props.query)
- chartData.value = {
- cumulativeFuelConsumption: res.map((item) => item.cumulativeFuelConsumption || 0),
- // cumulativeFuelConsumption: res.map((item) => (item.cumulativeFuelConsumption || 0) / 10000),
- cumulativeGasInjection: res.map((item) => (item.cumulativeGasInjection || 0) / 10000),
- cumulativePowerConsumption: res.map((item) => item.cumulativePowerConsumption || 0),
- // cumulativePowerConsumption: res.map((item) => (item.cumulativePowerConsumption || 0) / 1000),
- cumulativeWaterInjection: res.map((item) => item.cumulativeWaterInjection || 0),
- // cumulativeWaterInjection: res.map((item) => (item.cumulativeWaterInjection || 0) / 10000),
- transitTime: res.map((item) => (item.transitTime || 0) * 100),
- utilizationRate: res.map((item) => Number(((item.utilizationRate || 0) * 100).toFixed(2)))
- }
- xAxisData.value = res.map((item) => item.reportDate || '')
- resetVisibleZoomRange()
- } finally {
- chartLoading.value = false
- }
- }, 500)
- const resizer = useDebounceFn(() => {
- chart?.resize()
- }, 100)
- const selectedLegends = ref<Record<string, boolean>>({})
- const visibleZoomRange = ref({
- startIndex: 0,
- endIndex: 0
- })
- const NORMALIZED_AXIS_MIN = 0
- const NORMALIZED_AXIS_MAX = 100
- const NORMALIZED_AXIS_CENTER = 50
- const NORMALIZED_AXIS_PADDING = 4
- const ensureLegendSelection = () => {
- legendItems.forEach(({ name }) => {
- if (selectedLegends.value[name] === undefined) {
- selectedLegends.value[name] = true
- }
- })
- }
- const resetVisibleZoomRange = () => {
- const lastIndex = Math.max(xAxisData.value.length - 1, 0)
- visibleZoomRange.value = {
- startIndex: 0,
- endIndex: lastIndex
- }
- }
- const clampZoomIndex = (value: number, lastIndex: number) => {
- return Math.min(Math.max(Math.round(value), 0), lastIndex)
- }
- const syncVisibleZoomRangeFromChart = () => {
- const lastIndex = Math.max(xAxisData.value.length - 1, 0)
- if (!chart || lastIndex <= 0) {
- resetVisibleZoomRange()
- return
- }
- const dataZoomOptions = chart.getOption().dataZoom
- const primaryDataZoom = Array.isArray(dataZoomOptions) ? dataZoomOptions[0] : undefined
- if (!primaryDataZoom) {
- resetVisibleZoomRange()
- return
- }
- const startValue = Number(primaryDataZoom.startValue)
- const endValue = Number(primaryDataZoom.endValue)
- let nextStartIndex = 0
- let nextEndIndex = lastIndex
- if (Number.isFinite(startValue) && Number.isFinite(endValue)) {
- nextStartIndex = clampZoomIndex(startValue, lastIndex)
- nextEndIndex = clampZoomIndex(endValue, lastIndex)
- } else {
- const startPercent = Number(primaryDataZoom.start ?? 0)
- const endPercent = Number(primaryDataZoom.end ?? 100)
- nextStartIndex = clampZoomIndex((startPercent / 100) * lastIndex, lastIndex)
- nextEndIndex = clampZoomIndex((endPercent / 100) * lastIndex, lastIndex)
- }
- visibleZoomRange.value = {
- startIndex: Math.min(nextStartIndex, nextEndIndex),
- endIndex: Math.max(nextStartIndex, nextEndIndex)
- }
- }
- const getVisibleDatasetValues = (dataset: number[]) => {
- if (!dataset.length) return []
- const lastIndex = dataset.length - 1
- const startIndex = clampZoomIndex(visibleZoomRange.value.startIndex, lastIndex)
- const endIndex = Math.max(startIndex, clampZoomIndex(visibleZoomRange.value.endIndex, lastIndex))
- return dataset.slice(startIndex, endIndex + 1).filter((value) => Number.isFinite(value))
- }
- const normalizeSeriesData = (dataset: number[]) => {
- if (!dataset.length) return []
- const validValues = getVisibleDatasetValues(dataset)
- if (!validValues.length) {
- return dataset.map(() => NORMALIZED_AXIS_MIN)
- }
- const min = Math.min(...validValues)
- const max = Math.max(...validValues)
- if (min === max) {
- return dataset.map(() => (min === 0 ? NORMALIZED_AXIS_MIN : NORMALIZED_AXIS_CENTER))
- }
- const usableHeight = NORMALIZED_AXIS_MAX - NORMALIZED_AXIS_MIN - NORMALIZED_AXIS_PADDING * 2
- return dataset.map((value) =>
- Number((NORMALIZED_AXIS_PADDING + ((value - min) / (max - min)) * usableHeight).toFixed(2))
- )
- }
- const formatTrendAxisLabel = (value: number) => {
- if (value === NORMALIZED_AXIS_MIN) return '低'
- if (value === NORMALIZED_AXIS_CENTER) return '中'
- if (value === NORMALIZED_AXIS_MAX) return '高'
- return ''
- }
- const formatSeriesValue = (name: string, value: number) => {
- const item = legendItemMap[name]
- const safeValue = Number.isFinite(value) ? value : 0
- if (!item) return safeValue.toFixed(2)
- return `${safeValue.toFixed(item.decimals)} ${item.unit}`
- }
- const getSeries = () => {
- const enableSampling = xAxisData.value.length > 120
- return legendItems.map((item) => ({
- name: item.name,
- type: 'line',
- smooth: false,
- showSymbol: true,
- symbol: 'circle',
- symbolSize: 6,
- connectNulls: true,
- lineStyle: {
- width: 2
- },
- emphasis: {
- focus: 'series'
- },
- sampling: enableSampling ? 'lttb' : undefined,
- progressive: 300,
- progressiveThreshold: 1500,
- data: normalizeSeriesData(chartData.value[item.key])
- }))
- }
- const getChartOption = () => ({
- animation: xAxisData.value.length <= 120,
- animationDuration: 280,
- animationDurationUpdate: 180,
- grid: {
- top: 72,
- right: 24,
- bottom: 68,
- left: 48,
- containLabel: true
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'line' },
- formatter: (params: any) => {
- const list = Array.isArray(params) ? params : [params]
- if (!list.length) return ''
- const content = list.map((item: any) => {
- const legendItem = legendItemMap[item.seriesName]
- const realValue = legendItem ? (chartData.value[legendItem.key][item.dataIndex] ?? 0) : 0
- return `<div class="flex items-center justify-between mt-1 gap-1">
- <span>${item.marker} ${item.seriesName}</span>
- <span>${formatSeriesValue(item.seriesName, realValue)}</span>
- </div>`
- })
- return `${list[0].axisValueLabel}<br>${content.join('')}`
- }
- },
- legend: {
- type: 'scroll',
- top: 16,
- data: legendItems.map((item) => item.name),
- selected: selectedLegends.value,
- show: true
- },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- data: xAxisData.value,
- axisLabel: {
- hideOverlap: true
- }
- },
- dataZoom: [
- {
- type: 'inside',
- xAxisIndex: 0,
- filterMode: 'none',
- throttle: 50,
- startValue: visibleZoomRange.value.startIndex,
- endValue: visibleZoomRange.value.endIndex
- },
- {
- type: 'slider',
- xAxisIndex: 0,
- filterMode: 'none',
- height: 18,
- bottom: 8,
- brushSelect: false,
- showDetail: false,
- moveHandleSize: 0,
- throttle: 50,
- startValue: visibleZoomRange.value.startIndex,
- endValue: visibleZoomRange.value.endIndex
- }
- ],
- yAxis: {
- type: 'value',
- min: NORMALIZED_AXIS_MIN,
- max: NORMALIZED_AXIS_MAX,
- splitNumber: 4,
- name: '相对趋势',
- nameGap: 16,
- axisLabel: {
- formatter: formatTrendAxisLabel
- },
- splitLine: {
- lineStyle: {
- type: 'dashed'
- }
- }
- },
- series: getSeries()
- })
- const initChart = () => {
- if (!chartRef.value) return
- if (chart && chartContainerEl === chartRef.value) {
- return
- }
- chart?.dispose()
- window.removeEventListener('resize', resizer)
- chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas', useDirtyRect: true })
- chartContainerEl = chartRef.value
- window.addEventListener('resize', resizer)
- chart.off('legendselectchanged')
- chart.on('legendselectchanged', (params: any) => {
- selectedLegends.value = { ...params.selected }
- })
- chart.off('datazoom')
- chart.on('datazoom', () => {
- syncVisibleZoomRangeFromChart()
- chart?.setOption(
- {
- series: getSeries()
- },
- { lazyUpdate: true }
- )
- })
- }
- const render = () => {
- if (!chartRef.value) return
- initChart()
- ensureLegendSelection()
- chart?.setOption(getChartOption(), { notMerge: true, lazyUpdate: true })
- }
- onUnmounted(() => {
- window.removeEventListener('resize', resizer)
- chart?.dispose()
- chart = null
- chartContainerEl = null
- })
- const handleQuery = () => {
- getChart().then(() => {
- render()
- })
- getList()
- getTotal()
- }
- watch(
- () => [
- props.refreshKey,
- props.query.deptId,
- props.query.contractName,
- props.query.taskName,
- props.query.createTime?.[0],
- props.query.createTime?.[1]
- ],
- () => {
- if (!props.query.createTime) {
- totalWork.value.totalCount = 0
- totalWork.value.notReported = 0
- totalWork.value.alreadyReported = 0
- }
- handleQuery()
- },
- { immediate: true }
- )
- const exportChart = () => {
- if (!chart) return
- let img = new Image()
- img.src = chart.getDataURL({
- type: 'png',
- pixelRatio: 1,
- backgroundColor: '#fff'
- })
- img.onload = function () {
- let canvas = document.createElement('canvas')
- canvas.width = img.width
- canvas.height = img.height
- let ctx = canvas.getContext('2d')
- ctx?.drawImage(img, 0, 0)
- let dataURL = canvas.toDataURL('image/png')
- let a = document.createElement('a')
- let event = new MouseEvent('click')
- a.href = dataURL
- a.download = `瑞恒日报统计数据.png`
- a.dispatchEvent(event)
- }
- }
- const exportData = async () => {
- const res = await IotRhDailyReportApi.exportRhDailyReportStatistics(props.query)
- download.excel(res, '瑞恒日报统计数据.xlsx')
- }
- const exportAll = async () => {
- if (tab.value === '看板') exportChart()
- else exportData()
- }
- const message = useMessage()
- const unfilledDialogRef = ref()
- const openUnfilledDialog = () => {
- // 检查是否选择了创建时间
- if (!props.query.createTime || props.query.createTime.length === 0) {
- message.warning('请先选择创建时间范围')
- return
- }
- // 打开弹窗
- unfilledDialogRef.value?.open()
- }
- const router = useRouter()
- const tolist = (id: number, non: boolean = false) => {
- const { pageNo, pageSize, ...rest } = props.query
- router.push({
- path: '/iotdayilyreport/IotRhDailyReport',
- query: {
- ...rest,
- deptId: id,
- ...(non ? { nonProductFlag: 'Y' } : {})
- }
- })
- }
- const { ZmTable, ZmTableColumn } = useTableComponents()
- </script>
- <template>
- <div class="grid grid-rows-[128px_1fr] gap-4 h-full min-h-0">
- <div class="grid grid-cols-8 gap-8">
- <template v-for="info in totalWorkKeys" :key="info[0]">
- <el-tooltip :disabled="info[0] !== 'totalGasInjection'" placement="top">
- <template #content>
- <div>
- 累计氮气注气量:{{
- formatGasInjectionTooltipValue(totalWorkloadDetail.totalN2GasInjection)
- }}
- 万方
- </div>
- <div>
- 累计天然气注气量:{{
- formatGasInjectionTooltipValue(totalWorkloadDetail.totalNaturalGasInjection)
- }}
- 万方
- </div>
- </template>
- <div
- class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-1 flex flex-col items-center justify-center gap-1">
- <div class="size-7.5" :class="info[3]"></div>
- <count-to
- class="text-2xl font-medium"
- :class="{ 'cursor-help': info[0] === 'totalGasInjection' }"
- :start-val="0"
- :end-val="totalWork[info[0]]"
- :decimals="info[4]"
- @click="info[2] === '未填报' ? openUnfilledDialog() : ''">
- <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span>
- </count-to>
- <div class="text-sm font-medium text-[var(--el-text-color-regular)] whitespace-nowrap">
- {{ info[1] ? info[2] + '(' + info[1] + ')' : info[2] }}
- </div>
- </div>
- </el-tooltip>
- </template>
- </div>
- <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow flex flex-col p-4 gap-2 min-h-0">
- <div class="flex h-12 items-center justify-between">
- <el-button-group>
- <el-button
- size="default"
- :type="tab === '表格' ? 'primary' : 'default'"
- @click="handleSelectTab('表格')"
- >表格
- </el-button>
- <el-button
- size="default"
- :type="tab === '看板' ? 'primary' : 'default'"
- @click="handleSelectTab('看板')"
- >看板
- </el-button>
- </el-button-group>
- <h3 class="text-xl font-medium">{{ `${props.deptName}-${tab}` }}</h3>
- <el-button size="default" type="primary" @click="exportAll">导出</el-button>
- </div>
- <div class="flex-1 relative min-h-0">
- <el-auto-resizer class="absolute">
- <template #default="{ height }">
- <Motion
- as="div"
- :style="{ position: 'relative', overflow: 'hidden' }"
- :animate="{ height: `${height}px`, width: `100%` }"
- :transition="{ type: 'spring', stiffness: 200, damping: 25, duration: 0.3 }">
- <AnimatePresence :initial="false" mode="sync">
- <Motion
- :key="currentTab"
- as="div"
- :initial="{ x: direction === 'left' ? '-100%' : '100%', opacity: 0 }"
- :animate="{ x: '0%', opacity: 1 }"
- :exit="{ x: direction === 'left' ? '50%' : '-50%', opacity: 0 }"
- :transition="{ type: 'tween', stiffness: 300, damping: 30, duration: 0.3 }"
- :style="{ position: 'absolute', left: 0, right: 0, top: 0 }">
- <div :style="{ width: `100%`, height: `${height}px` }">
- <zm-table
- v-if="currentTab === '表格'"
- :loading="listLoading"
- :data="list"
- :height="height"
- show-border>
- <template v-for="item in columns(type)" :key="item.prop">
- <zm-table-column
- v-if="item.prop !== 'name' && item.prop !== 'nonProductiveTime'"
- :label="item.label"
- :prop="item.prop"
- :formatter="formatter"
- :action="item.action" />
- <zm-table-column
- v-else-if="item.prop === 'name'"
- :label="item.label"
- :prop="item.prop">
- <template #default="{ row }">
- <el-button text type="primary" @click.prevent="tolist(row.id)">{{
- row.name
- }}</el-button>
- </template>
- </zm-table-column>
- <zm-table-column v-else :label="item.label" :prop="item.prop">
- <template #default="{ row }">
- <el-button
- v-if="row.nonProductiveTime > 0"
- text
- type="primary"
- @click.prevent="tolist(row.id, true)">
- {{ (Number(row.nonProductiveTime ?? 0) * 100).toFixed(2) + '%' }}
- </el-button>
- <span v-else>
- {{ (Number(row.nonProductiveTime ?? 0) * 100).toFixed(2) + '%' }}
- </span>
- </template>
- </zm-table-column>
- </template>
- </zm-table>
- <div
- ref="chartRef"
- v-loading="chartLoading"
- v-else
- :style="{ width: `100%`, height: `${height}px` }">
- </div>
- </div>
- </Motion>
- </AnimatePresence>
- </Motion>
- </template>
- </el-auto-resizer>
- </div>
- </div>
- </div>
- <UnfilledReportDialog ref="unfilledDialogRef" :query-params="props.query" />
- </template>
|