| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- <script lang="ts" setup>
- import { ref, onMounted } from 'vue'
- import dayjs from 'dayjs'
- import { useDebounceFn } from '@vueuse/core'
- import MiniBarChart from '@/components/WorkOrderCompletionBar/index.vue'
- import CountTo from '@/components/count-to1.vue'
- import { IotReportApi } from '@/api/pms/report'
- // 定义时间类型
- type TimeType = 'year' | 'month' | 'day'
- interface Query {
- deptId?: number
- createTime?: [string, string]
- pageNo: number
- pageSize: number
- }
- interface ChartDataItem {
- label: string
- num: number
- }
- interface StatItem {
- key: string
- title: string
- icon: string
- total: number
- charts: ChartDataItem[]
- trend: number
- }
- // 选项配置数组
- const timeOptions: { label: string; value: TimeType }[] = [
- { label: '年', value: 'year' },
- { label: '月', value: 'month' },
- { label: '日', value: 'day' }
- ]
- const activeTimeType = ref<TimeType>('year')
- const query = ref<Query>({
- pageNo: 1,
- pageSize: 10
- })
- const defaultStats: StatItem[] = [
- {
- key: 'yx',
- title: '运行记录',
- icon: 'i-material-symbols:list-alt-outline',
- total: 0,
- charts: [],
- trend: 0
- },
- {
- key: 'rb',
- title: '生产日报',
- icon: 'i-material-symbols:calendar-today-outline',
- total: 0,
- charts: [],
- trend: 0
- },
- {
- key: 'wx',
- title: '维修工单',
- icon: 'i-material-symbols:home-repair-service-outline',
- total: 0,
- charts: [],
- trend: 0
- },
- {
- key: 'by',
- title: '保养工单',
- icon: 'i-material-symbols:construction-rounded',
- total: 0,
- charts: [],
- trend: 0
- },
- {
- key: 'xj',
- title: '巡检工单',
- icon: 'i-material-symbols:warning-outline',
- total: 0,
- charts: [],
- trend: 0
- }
- ]
- const statList = ref<StatItem[]>(JSON.parse(JSON.stringify(defaultStats)))
- const dataLoading = ref(false)
- const list = ref<any[]>([])
- const loading = ref(false)
- const total = ref(0)
- const handleTimeChange = (type: TimeType, init = false) => {
- activeTimeType.value = type
- const formatStr = 'YYYY-MM-DD HH:mm:ss'
- const endTime = dayjs().endOf('day').format(formatStr)
- let startTime = ''
- switch (type) {
- case 'year':
- startTime = dayjs().startOf('year').format(formatStr)
- break
- case 'month':
- startTime = dayjs().startOf('month').format(formatStr)
- break
- case 'day':
- startTime = dayjs().startOf('day').format(formatStr)
- break
- }
- query.value.createTime = [startTime, endTime]
- console.log(`切换为[${type}]:`, query.value.createTime)
- if (!init) loadData()
- }
- const labelMap = {
- wxoareject: '审批不通过',
- wxoa: '审批中',
- wxclose: '关闭',
- wxfinished: '完成',
- wxtx: '待填写',
- xjtodo: '待执行',
- xjignore: '忽略',
- xjfinished: '已执行',
- yx0: '待执行',
- yx1: '已执行',
- yx2: '执行中',
- yx3: '填写中',
- by1: '未保养',
- by2: '已保养',
- rb0: '未完成',
- rb1: '已完成'
- }
- // 模拟数据加载
- const loadData = useDebounceFn(async function () {
- dataLoading.value = true
- const { pageNo, pageSize, ...other } = query.value
- const res = await IotReportApi.getOrderNumber(other)
- statList.value.forEach((item) => {
- const data = res[item.key] || []
- item.total = data.reduce((acc, cur) => acc + cur.num, 0)
- item.charts = data.map((d) => ({
- label: labelMap[item.key + d.status],
- num: d.num
- }))
- })
- dataLoading.value = false
- }, 500)
- const loadList = useDebounceFn(async function () {
- loading.value = true
- const res = await IotReportApi.getOrderPage(query.value)
- // console.log('res :>> ', res)
- // const mockTableData = Array.from({ length: query.value.pageSize }).map((_, index) => {
- // const types = ['维修工单', '保养工单', '巡检工单', '运行记录', '生产日报']
- // const companies = ['第一工程公司', '第二工程公司', '总包单位']
- // const statuses = ['已完成', '未完成', '处理中']
- // return {
- // id: index + 1,
- // orderType: types[Math.floor(Math.random() * types.length)],
- // createTime: dayjs()
- // .subtract(Math.floor(Math.random() * 10), 'day')
- // .format('YYYY-MM-DD HH:mm:ss'),
- // companyName: companies[Math.floor(Math.random() * companies.length)],
- // projectDept: `项目部-${Math.floor(Math.random() * 10) + 1}`,
- // teamName: `作业队-${String.fromCharCode(65 + Math.floor(Math.random() * 5))}`,
- // status: statuses[Math.floor(Math.random() * statuses.length)],
- // deviceName: `设备-${Math.floor(Math.random() * 1000)}`
- // }
- // })
- list.value = res.list
- total.value = res.total
- loading.value = false
- }, 500)
- function handleSizeChange(val: number) {
- query.value.pageSize = val
- query.value.pageNo = 1
- loadList()
- }
- function handleCurrentChange(val: number) {
- query.value.pageNo = val
- loadList()
- }
- function handleQuery(setPage = true) {
- if (setPage) {
- query.value.pageNo = 1
- }
- loadList()
- loadData()
- }
- onMounted(() => {
- handleTimeChange('year', true)
- })
- watch(
- [() => query.value.createTime, () => query.value.deptId],
- () => {
- handleQuery()
- },
- { immediate: true }
- )
- </script>
- <template>
- <div
- class="grid grid-cols-[15%_1fr] grid-rows-[196px_1fr] gap-4 h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
- >
- <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
- <DeptTreeSelect
- :top-id="156"
- :deptId="156"
- v-model="query.deptId"
- :init-select="false"
- :show-title="false"
- />
- </div>
- <div class="flex flex-col gap-4 h-full overflow-hidden">
- <div class="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-5 gap-4" v-loading="dataLoading">
- <section
- v-for="item in statList"
- :key="item.key"
- class="bg-white dark:bg-[#1d1e1f] rounded-xl shadow flex justify-between items-stretch min-h-[140px] border border-gray-200 dark:border-gray-700/50 relative overflow-hidden group transition-colors duration-300"
- >
- <!-- 左侧:文字信息 -->
- <div class="flex flex-col justify-between z-10 p-4 pr-0">
- <div>
- <!-- 标题:白底深灰,黑底浅灰 -->
- <div class="text-gray-500 dark:text-gray-400 text-sm font-medium mb-1">
- {{ item.title }}
- </div>
- <!-- 数值:白底黑色,黑底白色 -->
- <div class="text-3xl font-bold tracking-tight text-gray-900! dark:text-white! mt-1">
- <count-to :start-val="0" :end-val="item.total" :duration="100" />
- </div>
- </div>
- <!-- 环比 -->
- <div class="flex items-center gap-2 mt-2">
- <!-- 标签:针对亮色/暗色分别设置背景和文字颜色 -->
- <div
- class="px-2 py-0.5 rounded text-xs font-bold flex items-center space-x-1"
- :class="
- item.trend >= 0
- ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400'
- : 'bg-rose-100 text-rose-600 dark:bg-rose-500/20 dark:text-rose-400'
- "
- >
- <div
- :class="
- item.trend >= 0
- ? 'i-material-symbols:trending-up'
- : 'i-material-symbols:trending-down'
- "
- class="text-sm"
- ></div>
- <span>{{ Math.abs(item.trend) }}%</span>
- </div>
- <span class="text-xs text-gray-500 dark:text-gray-400">环比</span>
- </div>
- </div>
- <!-- 右侧:ECharts图表 -->
- <div class="flex-1 h-full z-10 relative">
- <MiniBarChart :items="item.charts" :max="item.total" />
- </div>
- <!-- 背景装饰:颜色自适应 -->
- <div
- class="absolute -right-6 -bottom-6 opacity-[0.05] pointer-events-none transition-transform group-hover:scale-150 duration-500 text-black dark:text-white"
- >
- <div :class="item.icon" class="text-9xl"></div>
- </div>
- </section>
- </div>
- <div class="flex justify-between">
- <el-button-group size="default">
- <el-button
- v-for="item in timeOptions"
- :key="item.value"
- :type="activeTimeType === item.value ? 'primary' : ''"
- @click="handleTimeChange(item.value)"
- >
- {{ item.label }}
- </el-button>
- </el-button-group>
- <el-button size="default" type="primary">导出</el-button>
- </div>
- </div>
- <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
- <div class="flex-1 relative">
- <el-auto-resizer class="absolute">
- <template #default="{ width, height }">
- <el-table
- :data="list"
- v-loading="loading"
- stripe
- class="absolute"
- :max-height="height"
- :height="height"
- show-overflow-tooltip
- :width="width"
- scrollbar-always-on
- >
- <el-table-column label="序号" type="index" width="60" align="center" />
- <el-table-column label="工单类别" prop="type" align="center" width="80" />
- <el-table-column
- label="生成日期"
- prop="createTime"
- align="center"
- width="160"
- :formatter="(row) => row.createTime.split(' ')[0]"
- />
- <el-table-column label="公司" prop="company" align="center" width="100" />
- <el-table-column label="项目部" prop="project" align="center" />
- <el-table-column label="队伍" prop="deptName" align="center" />
- <el-table-column label="状态" prop="status" align="center" width="80">
- <!-- <template #default="{ row }">
- <el-tag v-if="row.status === '已完成'" type="success" effect="dark" size="small"
- >已完成</el-tag
- >
- <el-tag
- v-else-if="row.status === '未完成'"
- type="danger"
- effect="dark"
- size="small"
- >未完成</el-tag
- >
- <el-tag v-else type="warning" effect="plain" size="small">{{
- row.status
- }}</el-tag>
- </template> -->
- </el-table-column>
- <el-table-column label="设备" prop="device" align="center" />
- </el-table>
- </template>
- </el-auto-resizer>
- </div>
- <div class="h-10 mt-4 flex items-center justify-end">
- <el-pagination
- size="default"
- v-show="total > 0"
- v-model:current-page="query.pageNo"
- v-model:page-size="query.pageSize"
- :background="true"
- :page-sizes="[10, 20, 30, 50, 100]"
- :total="total"
- layout="total, sizes, prev, pager, next, jumper"
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- />
- </div>
- </div>
- </div>
- </template>
- <style scoped>
- :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>
|