| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- <script setup lang="ts">
- import dayjs from 'dayjs'
- import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
- import { useDebounceFn } from '@vueuse/core'
- import CountTo from '@/components/count-to1.vue'
- import * as echarts from 'echarts'
- import UnfilledReportDialog from './UnfilledReportDialog.vue'
- import { Motion, AnimatePresence } from 'motion-v'
- import { rangeShortcuts } from '@/utils/formatTime'
- import download from '@/utils/download'
- import { useUserStore } from '@/store/modules/user'
- const deptId = useUserStore().getUser.deptId
- interface Query {
- pageNo: number
- pageSize: number
- deptId: number
- contractName?: string
- taskName?: string
- createTime: string[]
- projectClassification: 1 | 2
- }
- const id = deptId
- const query = ref<Query>({
- pageNo: 1,
- pageSize: 10,
- deptId: deptId,
- createTime: [
- ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
- ],
- projectClassification: 2
- })
- const totalWorkKeys: [string, string, string, string, number][] = [
- ['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],
- [
- 'totalFuelConsumption',
- '吨',
- '累计油耗',
- 'i-material-symbols:directions-car-outline-rounded text-sky',
- 2
- ],
- [
- 'totalPowerConsumption',
- 'KWH',
- '累计用电量',
- 'i-material-symbols:electric-bolt-outline-rounded text-sky',
- 2
- ],
- ['constructionWells', '个', '累计施工井数', 'i-mdi:progress-wrench text-sky', 0],
- ['completedWells', '个', '累计完工井数', 'i-mdi:wrench-check-outline text-emerald', 0]
- ]
- const totalWork = ref({
- totalCount: 0,
- alreadyReported: 0,
- notReported: 0,
- totalFuelConsumption: 0,
- totalPowerConsumption: 0,
- constructionWells: 0,
- completedWells: 0
- })
- const totalLoading = ref(false)
- const getTotal = useDebounceFn(async () => {
- totalLoading.value = true
- const { pageNo, pageSize, ...other } = query.value
- try {
- let res1: any[]
- if (query.value.createTime && query.value.createTime.length === 2) {
- res1 = await IotRyDailyReportApi.ryDailyReportStatistics({
- createTime: query.value.createTime,
- projectClassification: query.value.projectClassification
- })
- totalWork.value.totalCount = res1[0].count
- totalWork.value.alreadyReported = res1[1].count
- totalWork.value.notReported = res1[2].count
- }
- const res2 = await IotRyDailyReportApi.totalWorkload(other)
- totalWork.value = {
- ...totalWork.value,
- ...res2
- }
- } finally {
- totalLoading.value = false
- }
- }, 1000)
- 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
- }
- const list = ref<List[]>([])
- const type = ref('2')
- const columns = (type: string) => {
- return [
- {
- label: type === '2' ? '项目部' : '队伍',
- prop: 'name'
- },
- {
- label: '累计施工井数',
- prop: 'cumulativeConstructWells'
- },
- {
- label: '累计完工井数',
- prop: 'cumulativeCompletedWells'
- },
- {
- label: '累计用电量(KWH)',
- prop: 'cumulativePowerConsumption'
- },
- {
- label: '累计油耗(吨)',
- prop: 'cumulativeFuelConsumption'
- }
- ]
- }
- const listLoading = ref(false)
- const formatter = (row: List, column: any) => {
- return row[column.property] ?? 0
- }
- const getList = useDebounceFn(async () => {
- listLoading.value = true
- try {
- const res = await IotRyDailyReportApi.getIotRyDailyReportSummary(query.value)
- const { list: reslist } = res
- type.value = reslist[0]?.type || '2'
- list.value = reslist.map(
- ({ id, projectDeptId, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => ({
- id: type === '2' ? projectDeptId : teamId,
- name: type === '2' ? projectDeptName : teamName,
- ...other
- })
- )
- } finally {
- listLoading.value = false
- }
- }, 1000)
- const tab = ref<'表格' | '看板'>('表格')
- const currentTab = ref<'表格' | '看板'>('表格')
- const deptName = 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.ECharts | null = null
- const xAxisData = ref<string[]>([])
- const legend = ref<string[][]>([
- ['累计施工井数 (个)', 'cumulativeConstructWells'],
- ['累计完工井数 (个)', 'cumulativeCompletedWells'],
- ['累计油耗 (吨)', 'cumulativeFuelConsumption'],
- ['累计用电量 (KWH)', 'cumulativePowerConsumption'],
- ['平均时效 (%)', 'transitTime']
- ])
- const chartData = ref<Record<string, number[]>>({
- cumulativeFuelConsumption: [],
- cumulativeConstructWells: [],
- cumulativeCompletedWells: [],
- cumulativePowerConsumption: [],
- transitTime: []
- })
- let chartLoading = ref(false)
- const getChart = useDebounceFn(async () => {
- chartLoading.value = true
- try {
- const res = await IotRyDailyReportApi.getIotRyDailyReportSummaryPolyline(query.value)
- chartData.value = {
- cumulativeFuelConsumption: res.map((item) => item.cumulativeFuelConsumption || 0),
- cumulativeConstructWells: res.map((item) => item.cumulativeConstructWells || 0),
- cumulativeCompletedWells: res.map((item) => item.cumulativeCompletedWells || 0),
- cumulativePowerConsumption: res.map((item) => item.cumulativePowerConsumption || 0),
- transitTime: res.map((item) => (item.transitTime || 0) * 100)
- }
- xAxisData.value = res.map((item) => item.reportDate || '')
- } finally {
- chartLoading.value = false
- }
- }, 1000)
- const resizer = () => {
- chart?.resize()
- }
- onUnmounted(() => {
- window.removeEventListener('resize', resizer)
- })
- const render = () => {
- if (!chartRef.value) return
- chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
- window.addEventListener('resize', resizer)
- const values: number[] = []
- for (const [_name, key] of legend.value) {
- values.push(...(chartData.value[key] || []))
- }
- const maxVal = values.length === 0 ? 10000 : Math.max(...values)
- const minVal = values.length === 0 ? 0 : Math.min(...values)
- const maxDigits = (Math.floor(maxVal) + '').length
- const minDigits = (Math.floor(Math.abs(minVal)) + '').length
- const interval = Math.max(maxDigits, minDigits)
- const maxInterval = interval
- const minInterval = minDigits
- const intervalArr = [0]
- for (let i = 1; i <= interval; i++) {
- intervalArr.push(Math.pow(10, i))
- }
- chart.setOption({
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'line'
- },
- formatter: (params) => {
- let d = `${params[0].axisValueLabel}<br>`
- let item = params.map((el) => {
- return `<div class="flex items-center justify-between mt-1 gap-1">
- <span>${el.marker} ${el.seriesName}</span>
- <span>${chartData.value[legend.value[el.componentIndex][1]][el.dataIndex].toFixed(2)} ${el.seriesName.split(' ')[1]}</span>
- </div>`
- })
- return d + item.join('')
- }
- },
- legend: {
- data: legend.value.map(([name]) => name),
- show: true
- },
- xAxis: {
- type: 'category',
- data: xAxisData.value
- },
- yAxis: {
- type: 'value',
- min: minInterval,
- max: maxInterval,
- interval: 1,
- axisLabel: {
- formatter: (v) => {
- const num = v === 0 ? 0 : v > 0 ? Math.pow(10, v) : -Math.pow(10, -v)
- return num.toLocaleString()
- }
- }
- },
- series: legend.value.map(([name, key]) => ({
- name,
- type: 'line',
- smooth: true,
- showSymbol: true,
- data: chartData.value[key].map((value) => {
- // return value
- if (value === 0) return 0
- const isPositive = value > 0
- const absItem = Math.abs(value)
- const min_value = Math.max(...intervalArr.filter((v) => v <= absItem))
- const min_index = intervalArr.findIndex((v) => v === min_value)
- const new_value =
- (absItem - min_value) / (intervalArr[min_index + 1] - intervalArr[min_index]) + min_index
- return isPositive ? new_value : -new_value
- })
- }))
- })
- }
- const handleDeptNodeClick = (node: any) => {
- deptName.value = node.name
- // query.value.deptId = node.id
- handleQuery()
- }
- const handleQuery = (setPage = true) => {
- if (setPage) {
- query.value.pageNo = 1
- }
- getChart().then(() => {
- render()
- })
- getList()
- getTotal()
- }
- const resetQuery = () => {
- query.value = {
- pageNo: 1,
- pageSize: 10,
- deptId: deptId,
- createTime: [],
- projectClassification: 1
- }
- handleQuery()
- }
- watch(
- () => query.value.createTime,
- (val) => {
- if (!val) {
- totalWork.value.totalCount = 0
- totalWork.value.notReported = 0
- totalWork.value.alreadyReported = 0
- }
- handleQuery(false)
- }
- )
- onMounted(() => {
- handleQuery()
- })
- 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 IotRyDailyReportApi.exportRyDailyReportStatistics(query.value)
- download.excel(res, '瑞鹰修井日报统计数据.xlsx')
- }
- const exportAll = async () => {
- if (tab.value === '看板') exportChart()
- else exportData()
- }
- const message = useMessage()
- const unfilledDialogRef = ref()
- const openUnfilledDialog = () => {
- // 检查是否选择了创建时间
- if (!query.value.createTime || query.value.createTime.length === 0) {
- message.warning('请先选择创建时间范围')
- return
- }
- // 打开弹窗
- unfilledDialogRef.value?.open()
- }
- const router = useRouter()
- const tolist = (id: number) => {
- const { pageNo, pageSize, ...rest } = query.value
- router.push({
- path: '/iotdayilyreport/IotRyXjDailyReport',
- query: {
- ...rest,
- deptId: id
- }
- })
- }
- </script>
- <template>
- <div class="grid grid-cols-[16%_1fr] gap-5">
- <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4">
- <!-- <DeptTree2 :deptId="id" @node-click="handleDeptNodeClick" /> -->
- <DeptTreeSelect
- :deptId="id"
- :top-id="158"
- v-model="query.deptId"
- @node-click="handleDeptNodeClick"
- />
- </div>
- <div class="grid grid-rows-[62px_164px_1fr] h-full gap-5">
- <el-form
- size="default"
- class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
- >
- <div class="flex items-center gap-8">
- <el-form-item label="项目">
- <el-input
- v-model="query.contractName"
- placeholder="请输入项目"
- clearable
- @keyup.enter="handleQuery()"
- class="!w-240px"
- />
- </el-form-item>
- <el-form-item label="任务">
- <el-input
- v-model="query.taskName"
- placeholder="请输入任务"
- clearable
- @keyup.enter="handleQuery()"
- class="!w-240px"
- />
- </el-form-item>
- <el-form-item label="创建时间">
- <el-date-picker
- v-model="query.createTime"
- value-format="YYYY-MM-DD HH:mm:ss"
- type="daterange"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- :shortcuts="rangeShortcuts"
- class="!w-220px"
- :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
- />
- </el-form-item>
- </div>
- <el-form-item>
- <el-button type="primary" @click="handleQuery()">
- <Icon icon="ep:search" class="mr-5px" /> 搜索
- </el-button>
- <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
- </el-form-item>
- </el-form>
- <div class="grid grid-cols-7 gap-8">
- <div
- v-for="info in totalWorkKeys"
- :key="info[0]"
- class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 flex flex-col items-center justify-center gap-2"
- >
- <div class="size-7.5" :class="info[3]"></div>
- <count-to
- class="text-2xl font-medium"
- :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-xs font-medium text-[var(--el-text-color-regular)]">{{ info[1] }}</div>
- <div class="text-sm font-medium text-[var(--el-text-color-regular)]">{{ info[2] }}</div>
- </div>
- </div>
- <div
- class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-4 grid grid-rows-[48px_1fr] gap-2"
- >
- <div class="flex 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">{{ `${deptName}-${tab}` }}</h3>
- <el-button size="default" type="primary" @click="exportAll">导出</el-button>
- </div>
- <!-- <el-auto-resizer>
- <template #default="{ height, width }"> -->
- <Motion
- as="div"
- :style="{ position: 'relative', overflow: 'hidden' }"
- :animate="{ height: `${500}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: `${500}px` }">
- <el-table
- v-if="currentTab === '表格'"
- v-loading="listLoading"
- :data="list"
- :stripe="true"
- :max-height="500"
- show-overflow-tooltip
- >
- <template v-for="item in columns(type)" :key="item.prop">
- <el-table-column
- v-if="item.prop !== 'name'"
- :label="item.label"
- :prop="item.prop"
- align="center"
- :formatter="formatter"
- />
- <el-table-column v-else :label="item.label" :prop="item.prop" align="center">
- <template #default="{ row }">
- <el-button text type="primary" @click.prevent="tolist(row.id)">{{
- row.name
- }}</el-button>
- </template>
- </el-table-column>
- </template>
- </el-table>
- <div
- ref="chartRef"
- v-loading="chartLoading"
- :key="dayjs().valueOf()"
- v-else
- :style="{ width: `100%`, height: `${500}px` }"
- >
- </div>
- </div>
- </Motion>
- </AnimatePresence>
- </Motion>
- <!-- </template>
- </el-auto-resizer> -->
- </div>
- </div>
- </div>
- <UnfilledReportDialog ref="unfilledDialogRef" :query-params="query" />
- </template>
- <style scoped>
- :deep(.el-form-item) {
- margin-bottom: 0;
- }
- :deep(.el-table) {
- border-top-right-radius: 8px;
- border-top-left-radius: 8px;
- .el-table__cell {
- height: 52px;
- }
- .el-table__header-wrapper {
- .el-table__cell {
- background: var(--el-fill-color-light);
- }
- }
- }
- </style>
|