| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- <template>
- <Dialog v-model="dialogVisible"
- :title="t('monitor.details')"
- :width="dialogWidth"
- class="fixed-height-dialog"
- @close="handleClose" >
- <ContentWrap>
- <!-- 添加设备信息展示区域 -->
- <div v-if="deviceInfo" class="device-info-card">
- <div class="info-item">
- <span class="info-label">{{ t('iotDevice.code') }}:</span>
- <span class="info-value">{{ deviceInfo.deviceCode }}</span>
- </div>
- <div class="info-item">
- <span class="info-label">{{ t('iotDevice.name') }}:</span>
- <span class="info-value">{{ deviceInfo.deviceName }}</span>
- </div>
- <div class="info-item" v-if="deviceInfo.model">
- <span class="info-label">{{ t('deviceForm.model') }}:</span>
- <span class="info-value">{{ deviceInfo.model }}</span>
- </div>
- </div>
- <div class="table-container">
- <!-- 添加表格容器并设置滚动 -->
- <el-table
- v-loading="loading"
- :data="paginatedList"
- :stripe="true"
- :show-overflow-tooltip="true"
- style="width: auto"
- height="100%"
- class="scrollable-table"
- ref="tableRef"
- >
- <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode"
- :width="columnWidths.deviceCode" v-if="false"/>
- <el-table-column :label="t('iotDevice.name')" align="center" prop="deviceName"
- :width="columnWidths.deviceName" v-if="false"/>
- <el-table-column :label="t('mainPlan.MaintItems')" align="center" prop="name"
- :width="columnWidths.name"/>
- <el-table-column :label="t('operationFillForm.sumTime')" align="center" prop="totalRunTime"
- :width="columnWidths.totalRunTime">
- <template #default="{ row }">
- {{ row.totalRunTime ?? row.tempTotalRunTime }}
- </template>
- </el-table-column>
- <el-table-column :label="t('operationFillForm.sumKil')" align="center" prop="totalMileage"
- :width="columnWidths.totalMileage">
- <template #default="{ row }">
- {{ row.totalMileage ?? row.tempTotalMileage }}
- </template>
- </el-table-column>
- <!-- 时间分组列 -->
- <el-table-column v-if="showTimeColumns" label="保养时长"
- align="center"
- :width="columnWidths.timeGroup">
- <el-table-column :label="t('mainPlan.lastMaintenanceOperationTime')" align="center" prop="lastRunningTime"
- :width="columnWidths.lastRunningTime"/>
- <el-table-column :label="t('mainPlan.RunTimeCycle')" align="center" prop="nextRunningTime"
- :width="columnWidths.nextRunningTime"/>
- <el-table-column :label="t('mainPlan.nextMaintTime')" align="center" prop="timePeriod"
- :width="columnWidths.timePeriod">
- <template #default="{ row }">
- <span :class="{ 'negative-value': isNegative(row.timePeriod) }">
- {{ row.timePeriod }}
- </span>
- </template>
- </el-table-column>
- </el-table-column>
- <!-- 里程分组列 -->
- <el-table-column v-if="showMileageColumns" label="保养里程"
- align="center"
- :width="columnWidths.mileageGroup">
- <el-table-column :label="t('mainPlan.lastMaintenanceMileage')" align="center" prop="lastRunningKilometers"
- :width="columnWidths.lastRunningKilometers"/>
- <el-table-column :label="t('mainPlan.operatingMileageCycle')" align="center" prop="nextRunningKilometers"
- :width="columnWidths.nextRunningKilometers"/>
- <el-table-column :label="t('mainPlan.nextMaintKil')" align="center" prop="kilometerCycle"
- :width="columnWidths.kilometerCycle">
- <template #default="{ row }">
- <span :class="{ 'negative-value': isNegative(row.kilometerCycle) }">
- {{ row.kilometerCycle }}
- </span>
- </template>
- </el-table-column>
- </el-table-column>
- <!-- 日期分组列 -->
- <el-table-column v-if="showNaturalDateColumns" label="保养日期"
- align="center"
- :width="columnWidths.dateGroup">
- <el-table-column :label="t('mainPlan.lastMaintenanceNaturalDate')" align="center" prop="lastNaturalDate"
- :width="columnWidths.lastNaturalDate">
- <template #default="scope">
- <el-date-picker
- v-model="scope.row.lastNaturalDate"
- type="date"
- placeholder="选择日期"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 100%"
- :disabled="true"
- />
- </template>
- </el-table-column>
- <el-table-column :label="t('mainPlan.NaturalDailyCycle')" align="center" prop="nextNaturalDate"
- :width="columnWidths.nextNaturalDate"/>
- <el-table-column :label="t('mainPlan.nextMaintDate')" align="center" prop="naturalDatePeriod"
- :width="columnWidths.naturalDatePeriod"/>
- </el-table-column>
- </el-table>
- </div>
- <Pagination
- :total="total"
- v-model:page="queryParams.pageNo"
- v-model:limit="queryParams.pageSize"
- @pagination="handlePagination"
- />
- </ContentWrap>
- </Dialog>
- </template>
- <script setup lang="ts">
- import { DictDataVO } from '@/api/system/dict/dict.data'
- import dayjs from 'dayjs'
- import { IotMainWorkOrderBomApi, IotMainWorkOrderBomVO } from '@/api/pms/iotmainworkorderbom'
- import { IotMaintenanceBomApi, IotMaintenanceBomVO } from '@/api/pms/iotmaintenancebom'
- import {propTypes} from "@/utils/propTypes";
- const { t } = useI18n() // 国际化
- const emit = defineEmits(['close']) // 定义 success 事件,用于操作成功后的回调
- const dialogVisible = ref(false) // 弹窗的是否展示
- const loading = ref(true) // 列表的加载中
- const queryFormRef = ref() // 搜索的表单
- const list = ref<IotMaintenanceBomVO[]>([]) // 列表的数据
- const total = ref(0) // 列表的总页数
- // 分页重置标志
- const shouldResetPagination = ref(false)
- // 添加外部传入的设备信息
- const externalDeviceInfo = ref(null)
- const dialogWidth = '1500px';
- const tableRef = ref(null) // 表格实例引用
- const columnWidths = ref({}) // 存储列宽的对象
- const queryParams = reactive({
- pageNo: 1,
- pageSize: 10,
- workOrderId: undefined,
- planId: undefined,
- deviceId: undefined
- })
- const props = defineProps({
- flag: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
- })
- const selectedRow = ref(null)
- const getTextWidth = (str: string) => {
- if (!str) return 0;
- if (widthCache.has(str)) return widthCache.get(str)!;
- const span = document.createElement('span');
- span.style.visibility = 'hidden';
- span.style.position = 'absolute';
- span.style.whiteSpace = 'nowrap';
- span.style.font = '14px Microsoft YaHei';
- span.textContent = str;
- document.body.appendChild(span);
- const width = span.offsetWidth;
- document.body.removeChild(span);
- widthCache.set(str, width);
- return width;
- };
- // 添加列宽计算函数
- const widthCache = new Map<string, number>();
- const flexColumnWidth = (label: string, prop: keyof IotMaintenanceBomVO) => {
- // 确保有数据时才计算
- if (!paginatedList.value.length) return "auto";
- // 基础内边距30px
- const basePadding = 20;
- const labelWidth = getActualWidth(label) + basePadding; // 文本宽度 + 内边距
- // 计算内容最大宽度
- let maxContentWidth = 0;
- // 获取该列所有内容的宽度
- for (const row of paginatedList.value) {
- let value = "";
- // 特殊列处理
- if (prop === "totalRunTime") {
- value = (row.totalRunTime ?? row.tempTotalRunTime)?.toString() || "";
- } else if (prop === "totalMileage") {
- value = (row.totalMileage ?? row.tempTotalMileage)?.toString() || "";
- } else {
- value = row[prop]?.toString() || "";
- }
- // 数值格式化
- if (value && !isNaN(Number(value))) {
- value = Number(value).toLocaleString();
- }
- const contentWidth = getActualWidth(value) + basePadding;
- if (contentWidth > maxContentWidth) {
- maxContentWidth = contentWidth;
- }
- }
- // 返回较大值加上安全边距
- return Math.max(labelWidth, maxContentWidth, 120) + "px";
- };
- // 处理单选逻辑
- const selectRow = (row) => {
- selectedRow.value = selectedRow.value?.id === row.id ? null : row
- emit('choose', row)
- dialogVisible.value = false
- }
- // 分页事件处理
- const handlePagination = () => {
- console.log("分页变化,当前页:", queryParams.pageNo);
- };
- // 改进的宽度计算函数
- const getActualWidth = (text: string) => {
- if (!text) return 0;
- // 更精准的字符宽度计算
- const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
- const otherChars = text.length - chineseChars;
- // 中文16px,英文9px
- return chineseChars * 16 + otherChars * 9;
- };
- // 设置列宽
- const setColumnWidths = () => {
- const widths: Record<string, string> = {};
- // 固定列
- // widths.deviceCode = flexColumnWidth(t('iotDevice.code'), 'deviceCode');
- // widths.deviceName = flexColumnWidth(t('iotDevice.name'), 'deviceName');
- widths.totalRunTime = flexColumnWidth(t('operationFillForm.sumTime'), 'totalRunTime');
- widths.totalMileage = flexColumnWidth(t('operationFillForm.sumKil'), 'totalMileage');
- widths.name = flexColumnWidth(t('mainPlan.MaintItems'), 'name');
- // 动态列
- if (showTimeColumns.value) {
- widths.lastRunningTime = flexColumnWidth(t('mainPlan.lastMaintenanceOperationTime'), 'lastRunningTime');
- widths.nextRunningTime = flexColumnWidth(t('mainPlan.RunTimeCycle'), 'nextRunningTime');
- widths.timePeriod = flexColumnWidth(t('mainPlan.nextMaintTime'), 'timePeriod');
- // 分组列宽度 = 子列宽度之和 + 边框补偿
- widths.timeGroup = `${[
- parseFloat(widths.lastRunningTime),
- parseFloat(widths.nextRunningTime),
- parseFloat(widths.timePeriod)
- ].reduce((sum, w) => sum + w, 0) + 2}px`;
- }
- if (showMileageColumns.value) {
- widths.lastRunningKilometers = flexColumnWidth(t('mainPlan.lastMaintenanceMileage'), 'lastRunningKilometers');
- widths.nextRunningKilometers = flexColumnWidth(t('mainPlan.operatingMileageCycle'), 'nextRunningKilometers');
- widths.kilometerCycle = flexColumnWidth(t('mainPlan.nextMaintKil'), 'kilometerCycle');
- widths.mileageGroup = `${[
- parseFloat(widths.lastRunningKilometers),
- parseFloat(widths.nextRunningKilometers),
- parseFloat(widths.kilometerCycle)
- ].reduce((sum, w) => sum + w, 0) + 2}px`;
- }
- if (showNaturalDateColumns.value) {
- widths.lastNaturalDate = flexColumnWidth(t('mainPlan.lastMaintenanceOperationTime'), 'lastNaturalDate'); // 固定日期选择器的宽度
- widths.nextNaturalDate = flexColumnWidth(t('mainPlan.NaturalDailyCycle'), 'nextNaturalDate');
- widths.naturalDatePeriod = flexColumnWidth(t('mainPlan.nextMaintDate'), 'naturalDatePeriod');
- widths.dateGroup = `${[
- parseFloat(widths.lastNaturalDate),
- parseFloat(widths.nextNaturalDate),
- parseFloat(widths.naturalDatePeriod)
- ].reduce((sum, w) => sum + w, 0) + 2}px`;
- }
- columnWidths.value = widths;
- };
- // 分页计算属性
- const paginatedList = computed(() => {
- const start = (queryParams.pageNo - 1) * queryParams.pageSize;
- const end = start + queryParams.pageSize;
- return list.value.slice(start, end);
- });
- const open = async (id?: number, flag?: string, deviceInfo?: any) => {
- // 重置分页参数
- queryParams.pageNo = 1
- queryParams.pageSize = 10
- list.value = [] // 清空列表避免显示旧数据
- total.value = 0
- await nextTick() // 确保DOM更新完成
- if (deviceInfo) {
- externalDeviceInfo.value = deviceInfo
- queryParams.deviceId = deviceInfo.deviceId // 如果需要的话
- }
- if('workOrder' === flag) {
- // 加载保养工单 BOM
- queryParams.workOrderId = id
- queryParams.planId = undefined
- await getWorkOrderList()
- } else if ('plan' === flag) {
- queryParams.planId = id
- queryParams.workOrderId = undefined
- await getPlanList()
- }
- dialogVisible.value = true
- }
- defineExpose({ open }) // 提供 open 方法,用于打开弹窗
- const getWorkOrderList = async () => {
- loading.value = true
- try {
- const data = await IotMainWorkOrderBomApi.getWorkOrderBOMs(queryParams)
- // 格式化日期字段
- data.forEach(item => {
- if (item.lastNaturalDate) {
- // 将时间戳转换为 YYYY-MM-DD 格式
- item.lastNaturalDate = dayjs(item.lastNaturalDate).format('YYYY-MM-DD')
- } else {
- // 处理空值情况
- item.lastNaturalDate = ''
- }
- // 计算下次保养运行时长 H
- item.timePeriod = calculateTimePeriod(item);
- // 计算下次保养公里数 KM
- item.kilometerCycle = calculateKiloPeriod(item);
- // 计算下次保养日期
- item.naturalDatePeriod = calculateNextNaturalDate(item)
- })
- list.value = data
- total.value = data.total
- } finally {
- loading.value = false
- }
- }
- const getPlanList = async () => {
- widthCache.clear();
- loading.value = true
- try {
- const data = await IotMaintenanceBomApi.getMainPlanBOMs(queryParams)
- // 格式化日期字段
- data.forEach(item => {
- if (item.lastNaturalDate) {
- // 将时间戳转换为 YYYY-MM-DD 格式
- item.lastNaturalDate = dayjs(item.lastNaturalDate).format('YYYY-MM-DD')
- } else {
- // 处理空值情况
- item.lastNaturalDate = ''
- }
- // 计算下次保养运行时长 H
- item.timePeriod = calculateTimePeriod(item);
- // 计算下次保养公里数 KM
- item.kilometerCycle = calculateKiloPeriod(item);
- // 计算下次保养日期
- item.naturalDatePeriod = calculateNextNaturalDate(item)
- })
- list.value = data
- total.value = data.length
- } finally {
- loading.value = false
- nextTick(setColumnWidths); // 数据加载后计算列宽
- }
- }
- // 添加设备信息计算属性
- const deviceInfo = computed(() => {
- // 优先使用外部传入的设备信息
- if (externalDeviceInfo.value) {
- return externalDeviceInfo.value;
- }
- if (list.value.length > 0) {
- const firstRecord = list.value[0];
- return {
- deviceCode: firstRecord.deviceCode,
- deviceName: firstRecord.deviceName,
- model: firstRecord.model // 确保列表数据中也有 model
- };
- }
- return null;
- });
- const handleClose = () => {
- // 重置状态避免多个弹窗出现
- dialogVisible.value = false
- loading.value = false
- list.value = []
- total.value = 0
- queryParams.pageNo = 1
- queryParams.pageSize = 10
- // 通知父组件弹窗已关闭
- emit('close')
- }
- // 计算 距离下次保养运行时长 H
- const calculateTimePeriod = (item: IotMaintenanceBomVO) => {
- if (item.runningTimeRule === 0) {
- const totalRunVal = item.totalRunTime ?? item.tempTotalRunTime;
- const next = Number(item.nextRunningTime) || 0;
- const totalRun = totalRunVal != null ? Number(totalRunVal) : 0;
- const lastRun = Number(item.lastRunningTime) || 0;
- const result = next - (totalRun - lastRun);
- return Number(result.toFixed(2));
- }
- return typeof item.timePeriod === 'number'
- ? Number(item.timePeriod.toFixed(2))
- : item.timePeriod;
- };
- // 计算 距离下次保养公里数 KM
- const calculateKiloPeriod = (item: IotMaintenanceBomVO) => {
- if (item.mileageRule === 0) {
- const totalRunVal = item.totalMileage ?? item.tempTotalMileage;
- const next = Number(item.nextRunningKilometers) || 0;
- const totalRun = totalRunVal != null ? Number(totalRunVal) : 0;
- const lastRun = Number(item.lastRunningKilometers) || 0;
- const result = next - (totalRun - lastRun);
- return Number(result.toFixed(2));
- }
- return typeof item.kilometerCycle === 'number'
- ? Number(item.kilometerCycle.toFixed(2))
- : item.kilometerCycle;
- };
- // 计算下次保养日期
- const calculateNextNaturalDate = (item: IotMaintenanceBomVO): string => {
- if (item.naturalDateRule !== 0 || !item.lastNaturalDate || !item.nextNaturalDate) {
- return '-'
- }
- return dayjs(item.lastNaturalDate).add(item.nextNaturalDate, 'day').format('YYYY-MM-DD')
- }
- // 计算属性:控制时间相关列的显示
- const showTimeColumns = computed(() => {
- return paginatedList.value.some(item => item.runningTimeRule === 0);
- });
- // 计算属性:控制里程相关列的显示
- const showMileageColumns = computed(() => {
- return paginatedList.value.some(item => item.mileageRule === 0);
- });
- // 计算属性:自然日期相关列的显示
- const showNaturalDateColumns = computed(() => {
- return paginatedList.value.some(item => item.naturalDateRule === 0);
- });
- /** 搜索按钮操作 */
- const handleQuery = () => {
- queryParams.pageNo = 1
- // getList()
- }
- const choose = (row: DictDataVO) => {
- emit('choose', row)
- dialogVisible.value = false
- }
- /** 重置按钮操作 */
- const resetQuery = () => {
- queryFormRef.value.resetFields()
- handleQuery()
- }
- // 判断是否为负数的辅助函数
- const isNegative = (value: any): boolean => {
- if (value === null || value === undefined || value === '') return false;
- const num = Number(value);
- return !isNaN(num) && num < 0;
- };
- // 监听分页数据变化,重新设置列宽
- watch(paginatedList, () => {
- nextTick(() => {
- setColumnWidths();
- });
- });
- // 监听动态列的变化
- watch([showTimeColumns, showMileageColumns, showNaturalDateColumns], () => {
- nextTick(setColumnWidths);
- });
- </script>
- <style lang="scss" scoped>
- /* 设备信息卡片样式 */
- .device-info-card {
- display: flex;
- flex-wrap: wrap;
- gap: 24px; /* 项间距 */
- margin-bottom: 16px;
- padding: 16px;
- background-color: #f8f9fa;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
- .info-item {
- display: flex;
- align-items: center;
- .info-label {
- font-weight: 600;
- color: #606266;
- margin-right: 8px;
- white-space: nowrap;
- }
- .info-value {
- font-weight: 500;
- color: #303133;
- padding: 4px 12px;
- background: #ffffff;
- border-radius: 4px;
- border: 1px solid #ebeef5;
- min-width: 200px;
- }
- }
- }
- /* 分组表头样式 */
- :deep(.el-table .el-table__header .el-table-column--group) {
- background-color: #f5f7fa;
- font-weight: bold;
- > .cell {
- font-weight: bold;
- color: #303133;
- }
- background-color: #f0f7ff;
- border: 1px solid #409eff !important;
- box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
- > .cell {
- font-weight: 700;
- color: #1d63dc;
- }
- }
- /* 加深所有单元格边框 */
- :deep(.el-table__header th) {
- border: 1px solid #c0c4cc !important;
- }
- /* 分组内部单元格特殊样式 */
- :deep(.el-table-column--group .el-table__cell) {
- border-top: 1px dashed #a0cfff !important;
- }
- /* 分组间垂直线 */
- :deep(.el-table-column--group) {
- position: relative;
- &::after {
- content: "";
- position: absolute;
- right: -1px;
- top: 0;
- bottom: 0;
- width: 2px;
- background: linear-gradient(to bottom, #409eff, #79bbff);
- }
- }
- /* 新版CSS解决方案 */
- .fixed-height-dialog {
- :deep(.el-dialog) {
- width: 1500px !important; /* 固定宽度 */
- height: 85vh !important; /* 使用视口高度 */
- display: flex;
- flex-direction: column;
- margin-top: 5vh !important; /* 垂直居中 */
- }
- /* 添加媒体查询,确保在小屏幕上也能完整显示 */
- @media (max-width: 1500px) {
- :deep(.el-dialog) {
- width: 95% !important;
- max-width: 1500px; /* 仍然限制最大宽度 */
- }
- }
- :deep(.el-dialog__header) {
- padding: 20px;
- flex-shrink: 0;
- }
- :deep(.el-dialog__body) {
- flex: 1;
- padding: 10px 20px;
- display: flex;
- flex-direction: column;
- overflow: hidden; /* 防止内容溢出 */
- }
- }
- /* 表格容器 */
- .table-container {
- flex: 1;
- overflow: auto;
- position: relative;
- .scrollable-table {
- width: 100%; /* 自适应宽度 */
- :deep(.el-table__header) {
- th {
- white-space: nowrap !important; /* 强制表头不换行 */
- text-overflow: ellipsis;
- overflow: hidden;
- padding: 8px 0; /* 增加内边距 */
- }
- }
- :deep(.el-table__body) {
- td {
- white-space: nowrap !important; /* 强制内容不换行 */
- text-overflow: ellipsis;
- overflow: hidden;
- padding: 8px 0; /* 增加内边距 */
- }
- }
- :deep(.el-table__body-wrapper) {
- overflow-x: hidden !important; /* 隐藏X轴滚动条 */
- }
- }
- }
- /* 分页样式 */
- .pagination-footer {
- margin-top: 15px;
- flex-shrink: 0;
- }
- /* 响应式处理 - 确保在小屏幕上布局合理 */
- @media (max-width: 1500px) {
- .fixed-height-dialog :deep(.el-dialog) {
- width: 95% !important;
- max-width: 98vw;
- }
- .table-container {
- overflow-x: auto;
- }
- }
- /* 负数值样式 */
- .negative-value {
- color: #f56c6c;
- font-weight: 600;
- }
- </style>
|