You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/views/mes/deviceledger/index.vue

1889 lines
68 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="device-ledger-layout">
<ContentWrap class="device-ledger-left">
<el-tree
v-loading="typeTreeLoading" :data="typeTreeData" node-key="id" highlight-current
:props="typeTreeProps"
:default-expanded-keys="typeTreeExpandedKeys" :expand-on-click-node="false"
@node-click="handleTypeNodeClick"/>
</ContentWrap>
<div class="device-ledger-right">
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"
label-width="60px">
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.deviceCode')"
prop="deviceCode">
<el-input
v-model="queryParams.deviceCode"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderDeviceCode')"
clearable @keyup.enter="handleQuery"
class="!w-240px"/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.deviceName')"
prop="deviceName">
<el-input
v-model="queryParams.deviceName"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderDeviceName')"
clearable @keyup.enter="handleQuery"
class="!w-240px"/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.deviceStatus')"
prop="deviceStatus">
<el-select v-model="queryParams.deviceStatus"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderDeviceStatus')"
clearable class="!w-240px">
<el-option v-for="dict in tzStatusOptions" :key="dict.value" :label="dict.label"
:value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px"/>
{{ t('common.query') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px"/>
{{ t('common.reset') }}
</el-button>
<el-button type="primary" plain @click="openForm('create')"
v-hasPermi="['mes:device-ledger:create']">
<Icon icon="ep:plus" class="mr-5px"/>
{{ t('action.add') }}
</el-button>
<el-button type="danger" plain @click="handleBatchDelete"
v-hasPermi="['mes:device-ledger:delete']">
<Icon icon="ep:delete" class="mr-5px"/>
{{ t('EquipmentManagement.EquipmentLedger.batchDelete') }}
</el-button>
<el-button
type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['mes:device-ledger:export']">
<Icon icon="ep:download" class="mr-5px"/>
{{ t('action.export') }}
</el-button>
<!-- 视图切换按钮 -->
<!-- <el-button
:type="currentView === 'grid' ? 'primary' : 'default'"
:icon="currentView === 'grid' ? Menu : Grid"
@click="toggleView"
class="view-toggle-btn"
>
{{ currentView === 'grid' ? '表格视图' : '九宫格' }}
</el-button>-->
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<div v-show="currentView === 'table'" class="simple-table-view">
<el-table
ref="tableRef" v-loading="loading" :data="list" :stripe="true"
:show-overflow-tooltip="true"
row-key="id" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" fixed="left" reserve-selection/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.serialNumber')"
align="center" width="50" fixed="left">
<template #default="scope">
{{ (queryParams.pageNo - 1) * queryParams.pageSize + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceCode')"
align="center" prop="deviceCode" min-width="160px" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceName')"
align="center" prop="deviceName" min-width="140px" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceType')"
align="center" prop="deviceType" min-width="110px" sortable>
<template #default="scope">
<el-tag effect="light">
{{ getDeviceTypeName(scope.row.deviceTypeName ?? scope.row.deviceType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceStatus')"
align="center" prop="deviceStatus" sortable>
<template #default="scope">
<el-switch
:model-value="isDeviceLedgerEnabled(scope.row)"
:loading="Boolean(deviceStatusUpdatingMap[scope.row.id])"
inline-prompt
@change="(val) => handleDeviceStatusChange(scope.row, val)"
/>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.isSchedueld')"
align="center" prop="isSchedueld" min-width="100px">
<template #default="scope">
<el-tag :type="Number(scope.row.isSchedueld ?? scope.row.isScheduled) === 1 ? 'success' : 'info'" effect="light">
{{ formatScheduleLabel(scope.row.isSchedueld ?? scope.row.isScheduled) }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.ratedCapacity')"
align="center" prop="ratedCapacity" min-width="120px"/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceSpec')"
align="center" prop="deviceSpec"/>
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceModel')"
align="center" prop="deviceModel"/>-->
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceBrand')" align="center" prop="deviceBrand" /> -->
<el-table-column
:label="t('EquipmentManagement.EquipmentLedger.productionDate')" align="center"
prop="productionDate" :formatter="dateFormatter2"
width="120px" sortable/>
<el-table-column
:label="t('EquipmentManagement.EquipmentLedger.factoryEntryDate')" align="center"
prop="factoryEntryDate" :formatter="dateFormatter2"
width="120px" sortable/>
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.supplier')" align="center" prop="supplier" width="110px" /> -->
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.workshop')" align="center" prop="workshop" width="110px" /> -->
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.workshop')"
align="center" prop="workshopName" min-width="150px" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceLocation')"
align="center" prop="deviceLocation" min-width="150px"/>
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.systemOrg')" align="center" prop="systemOrg" width="110px" /> -->
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.deviceManagerName')"
align="center" prop="deviceManagerName" width="150px" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.remark')" align="center"
prop="remark"/>
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.creatorName')" align="center" prop="creatorName" width="150px" sortable />
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable /> -->
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.updateTime')" align="center" prop="updateTime" :formatter="dateFormatter" width="180px" sortable /> -->
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.operate')"
align="center" min-width="160px" fixed="right">
<template #default="scope">
<el-button link @click="handleDetail(scope.row.id)">
{{ t('EquipmentManagement.EquipmentLedger.detail') }}
</el-button>
<el-button
link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['mes:device-ledger:update']">
{{ t('EquipmentManagement.EquipmentLedger.edit') }}
</el-button>
<el-button
link type="danger" @click="handleDelete(scope.row.id)"
v-hasPermi="['mes:device-ledger:delete']">
{{ t('EquipmentManagement.EquipmentLedger.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList"/>
</div>
<!-- 九宫格视图 -->
<div v-show="currentView === 'grid'" class="simple-grid-view">
<div v-if="!list || list.length === 0" class="empty-grid">
<el-empty description="暂无设备数据"/>
</div>
<div v-else class="grid-container">
<div
v-for="(item, index) in list"
:key="item.id || index"
class="grid-card"
@click="handleView(item, index)"
>
<!-- 设备状态指示 -->
<div class="status-indicator" :class="`status-${item.deviceStatus}`"></div>
<!-- 设备图标 -->
<div class="card-icon">
<el-icon :size="32" :color="getEquipmentColor(item.type)">
<component :is="getEquipmentIcon(item.type)"/>
</el-icon>
</div>
<!-- 设备基本信息 -->
<div class="card-content">
<div class="card-title">{{ item.name }}</div>
<div class="card-code">{{ item.code }}</div>
<div class="card-model">{{ item.model }}</div>
<!-- 设备状态 -->
<div class="card-status">
<el-tag
:type="getStatusTag(item.status)"
size="small"
class="status-tag"
>
{{ getStatusText(item.status) }}
</el-tag>
</div>
<!-- 运行信息 -->
<div v-if="item.runningHours" class="card-running">
<span>运行: {{ formatRunningHours(item.runningHours) }}</span>
</div>
<!-- 位置信息 -->
<div v-if="item.location" class="card-location">
<el-icon :size="12">
<Location/>
</el-icon>
<span>{{ item.location }}</span>
</div>
</div>
</div>
</div>
<Pagination
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[12, 24, 48, 96]"
@pagination="getList"/>
<!-- 分页 -->
<!-- <div class="simple-pagination">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:page-sizes="[12, 24, 48, 96]"
layout="total, sizes, prev, pager, next"
:total="total"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>-->
</div>
</ContentWrap>
<ContentWrap v-if="detailVisible" v-loading="detailLoading">
<div class="device-ledger-detail-header">
<div class="device-ledger-detail-title">{{
t('EquipmentManagement.EquipmentLedger.detail')
}}
</div>
<el-button link @click="closeDetail">
<Icon icon="ep:close"/>
</el-button>
</div>
<el-descriptions :column="3" class="device-ledger-detail-desc">
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceNo')">
{{ detailData?.deviceCode ?? '' }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceName')">
{{ detailData?.deviceName ?? '' }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceStatus')">
<dict-tag type="mes_tz_status" :value="detailData?.deviceStatus"/>
</el-descriptions-item>
<!-- <el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceBrand')">{{ detailData?.deviceBrand ?? '' }}</el-descriptions-item> -->
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceModel')">
{{ detailData?.deviceModel ?? '' }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceSpec')">
{{ detailData?.deviceSpec ?? '' }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceType')">
<el-tag effect="light">{{
getDeviceTypeName(detailData?.deviceTypeName ?? detailData?.deviceType)
}}
</el-tag>
</el-descriptions-item>
<!-- <el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.supplier')">{{ detailData?.supplier ?? '' }}</el-descriptions-item> -->
<!-- <el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.workshop')">{{ detailData?.workshop ?? '' }}</el-descriptions-item> -->
<!-- <el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.systemOrg')">{{ detailData?.systemOrg ?? '' }}</el-descriptions-item> -->
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceLocation')">
{{ detailData?.deviceLocation ?? '' }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.deviceManagerName')">
{{ detailData?.deviceManagerName ?? '' }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.productionDate')">
{{ formatDetailDate(detailData?.productionDate) }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.factoryEntryDate')">
{{
formatDetailDate(detailData?.factoryEntryDate)
}}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.remark')">{{
detailData?.remark ?? detailData?.deviceRemark ?? ''
}}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.creatorName')">
{{ detailData?.creatorName ?? '' }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.createTime')">
{{ formatDetailDate(detailData?.createTime) }}
</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.updateTime')">
{{ formatDetailDate(detailData?.updateTime) }}
</el-descriptions-item>
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.creatorName')" align="center" prop="creatorName" width="150px" sortable />
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable /> -->
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.updateTime')" align="center" prop="updateTime" :formatter="dateFormatter" width="180px" sortable /> -->
</el-descriptions>
<!-- <div v-if="detailData?.qrcodeUrl" class="device-ledger-detail-qrcode">
<QrcodeActionCard
:image-url="detailData.qrcodeUrl"
:print-id="detailData.id || detailData.deviceCode"
:print-title="`${detailData.deviceName || '设备'}码打印预览`"
:print-paper-width="80"
:print-paper-height="80"
:print-max-width="220"
:empty-text="t('EquipmentManagement.EquipmentLedger.qrcodeLoadError')"
:error-text="t('EquipmentManagement.EquipmentLedger.qrcodeLoadError')"
:show-refresh="false"
/>
<UploadImg style="height: 100px" :v-model="detailData?.images" disabled="false" />
</div>-->
<div class="container">
<div v-if="detailData?.qrcodeUrl" >
<QrcodeActionCard
:image-url="detailData.qrcodeUrl"
:print-id="detailData.id || detailData.deviceCode"
:print-title="`${detailData.deviceName || '设备'}码打印预览`"
:print-paper-width="80"
:print-paper-height="80"
:print-max-width="220"
:empty-text="t('EquipmentManagement.EquipmentLedger.qrcodeLoadError')"
:error-text="t('EquipmentManagement.EquipmentLedger.qrcodeLoadError')"
:show-refresh="false"
/>
</div>
<div v-if="detailData?.images">
<!-- <UploadImg style="height: 100px" v-model="detailData?.images" :disabled="false" />-->
<img :src="detailData?.images" class="upload-image" />
</div>
</div>
<el-tabs v-model="detailActiveTab" class="mt-12px">
<el-tab-pane :label="t('EquipmentManagement.EquipmentLedger.checkHistory')" name="check">
<div style="margin-bottom: 16px;">
<el-date-picker
v-model="inspectionDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('common.startTimeText')"
:end-placeholder="t('common.endTimeText')" range-separator="-" :unlink-panels="true"
class="mr-10px"/>
<el-button type="primary" plain @click="handleQueryInspection">{{
t('common.query')
}}
</el-button>
<el-button @click="handleResetInspection">{{ t('common.reset') }}</el-button>
<el-button
type="success" plain :loading="inspectionExportLoading"
@click="handleExportInspection"
v-hasPermi="['mes:device-ledger:export']">
<Icon icon="ep:download" class="mr-5px"/>
{{ t('action.export') }}
</el-button>
</div>
<el-empty v-if="!inspectionStepGroups.length"/>
<el-steps
v-else direction="vertical" :active="inspectionStepGroups.length"
class="device-ledger-history-steps">
<el-step v-for="group in inspectionStepGroups" :key="group.key"
style="margin-top:8px">
<template #title>
<div class="device-ledger-history-title">
<span class="device-ledger-history-time">[{{ group.time }}]</span>
<span class="device-ledger-history-operator">{{
t('EquipmentManagement.EquipmentLedger.operator')
}}: {{ group.operator }}</span>
</div>
</template>
<template #description>
<div class="device-ledger-history-items">
<div v-for="item in group.items" :key="item.key"
class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag :type="getResultTagType(item.result)">{{
getResultLabel(item.result)
}}
</el-tag>
<span class="device-ledger-history-item-text">{{ item.name }}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.checkMethod')
}}</span>
<span class="device-ledger-history-item-value">
<dict-tag type="Inspection_method" :value="item.method"/>
</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.criteria')
}}</span>
<span class="device-ledger-history-item-value">{{
item.criteria ?? '-'
}}</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.checkTime')
}}</span>
<span class="device-ledger-history-item-value">{{
formatHistoryTime(item.taskTime)
}}</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.createTime')
}}</span>
<span class="device-ledger-history-item-value">{{
formatHistoryTime(item.createTime)
}}</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.remark')
}}</span>
<span class="device-ledger-history-item-value">{{
item.remark ?? '-'
}}</span>
</div>
<div v-if="item.images?.length" class="device-ledger-history-item-images">
<el-image
v-for="img in item.images" :key="img" :src="img"
:preview-src-list="item.images"
preview-teleported fit="cover" class="device-ledger-history-item-image">
<template #error>
<div class="device-ledger-history-image-error">图片加载失败</div>
</template>
</el-image>
</div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</el-tab-pane>
<el-tab-pane :label="t('EquipmentManagement.EquipmentLedger.maintainHistory')"
name="maintain">
<div style="margin-bottom: 16px;">
<el-date-picker
v-model="maintainDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('common.startTimeText')"
:end-placeholder="t('common.endTimeText')" range-separator="-" :unlink-panels="true"
class="mr-10px"/>
<el-button type="primary" plain @click="handleQueryMaintain">{{
t('common.query')
}}
</el-button>
<el-button @click="handleResetMaintain">{{ t('common.reset') }}</el-button>
<el-button
type="success" plain :loading="maintainExportLoading" @click="handleExportMaintain"
v-hasPermi="['mes:device-ledger:export']">
<Icon icon="ep:download" class="mr-5px"/>
{{ t('action.export') }}
</el-button>
</div>
<el-empty v-if="!maintainStepGroups.length"/>
<el-steps
v-else direction="vertical" class="device-ledger-history-steps"
:active="maintainStepGroups.length">
<el-step v-for="group in maintainStepGroups" :key="group.key" style="margin-top:8px">
<template #title>
<div class="device-ledger-history-title">
<span class="device-ledger-history-time">[{{ group.time }}]</span>
<span class="device-ledger-history-operator">{{
t('EquipmentManagement.EquipmentLedger.operator')
}}: {{ group.operator }}</span>
</div>
</template>
<template #description>
<div class="device-ledger-history-items">
<div v-for="item in group.items" :key="item.key"
class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag :type="getResultTagType(item.result)">{{
getResultLabel(item.result)
}}
</el-tag>
<span class="device-ledger-history-item-text">{{ item.name }}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.maintainMethod')
}}</span>
<span class="device-ledger-history-item-value">
<dict-tag type="Inspection_method" :value="item.method"/>
</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.criteria')
}}</span>
<span class="device-ledger-history-item-value">{{
item.criteria ?? '-'
}}</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.maintainTime')
}}</span>
<span class="device-ledger-history-item-value">
{{ String(formatHistoryTime(item.taskTime)) }}
</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.createTime')
}}</span>
<span class="device-ledger-history-item-value">{{
formatHistoryTime(item.createTime)
}}</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.remark')
}}</span>
<span class="device-ledger-history-item-value">{{
item.remark ?? '-'
}}</span>
</div>
<div v-if="item.images?.length" class="device-ledger-history-item-images">
<el-image
v-for="img in item.images" :key="img" :src="img"
:preview-src-list="item.images"
preview-teleported fit="cover" class="device-ledger-history-item-image">
<template #error>
<div class="device-ledger-history-image-error">图片加载失败</div>
</template>
</el-image>
</div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</el-tab-pane>
<el-tab-pane :label="t('EquipmentManagement.EquipmentLedger.repairHistory')"
name="repair">
<div style="margin-bottom: 16px;">
<el-date-picker
v-model="repairDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('common.startTimeText')"
:end-placeholder="t('common.endTimeText')" range-separator="-" :unlink-panels="true"
class="mr-10px"/>
<el-button type="primary" plain @click="handleQueryRepair">{{
t('common.query')
}}
</el-button>
<el-button @click="handleResetRepair">{{ t('common.reset') }}</el-button>
<el-button
type="success" plain :loading="repairExportLoading" @click="handleExportRepair"
v-hasPermi="['mes:device-ledger:export']">
<Icon icon="ep:download" class="mr-5px"/>
{{ t('action.export') }}
</el-button>
</div>
<el-empty v-if="!repairGroups.length"/>
<el-collapse v-else v-model="repairActiveNames" class="device-ledger-repair-collapse">
<el-collapse-item v-for="group in repairGroups" :key="group.key" :name="group.key">
<template #title>
<div class="device-ledger-repair-title">
<span class="device-ledger-repair-name">{{ group.name }}</span>
<span class="device-ledger-repair-meta">共{{ group.items.length }}条</span>
</div>
</template>
<div class="device-ledger-history-items">
<div
v-for="row in group.items"
:key="String(row.id ?? row.subjectId ?? row.subjectCode)"
class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag type="info" effect="light">{{ row.subjectCode ?? '-' }}</el-tag>
<span class="device-ledger-history-item-text">{{
row.subjectName ?? '-'
}}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.projectName')
}}</span>
<span class="device-ledger-history-item-value">{{
row.subjectContent ?? '-'
}}</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.repairResult')
}}</span>
<span class="device-ledger-history-item-value">
<el-tag :type="getResultTagType(row.result ?? row.repairResult)">
{{ getResultLabel(row.repairResult ?? row.result) }}
</el-tag>
</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.remark')
}}</span>
<span class="device-ledger-history-item-value">{{
row.remark ?? '-'
}}</span>
</div>
<div class="device-ledger-history-item-row">
<span class="device-ledger-history-item-label">{{
t('EquipmentManagement.EquipmentLedger.finishDate')
}}</span>
<span class="device-ledger-history-item-value">{{
String(formatHistoryTime(row.finishDate)).split(' ')[0]
}}</span>
</div>
<div v-if="row.malfunctionImages?.length"
class="device-ledger-history-item-images">
<el-image
v-for="img in row.malfunctionImages" :key="img" :src="img"
:preview-src-list="row.malfunctionImages" preview-teleported fit="cover"
class="device-ledger-history-item-image">
<template #error>
<div class="device-ledger-history-image-error">图片加载失败</div>
</template>
</el-image>
</div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</el-tab-pane>
<el-tab-pane :label="t('EquipmentManagement.EquipmentLedger.criticalComponent')"
name="criticalComponent">
<div class="device-ledger-tab-toolbar">
<el-button
type="success" plain :loading="criticalExportLoading"
@click="handleExportCriticalComponent"
v-hasPermi="['mes:device-ledger:export']">
<Icon icon="ep:download" class="mr-5px"/>
{{ t('action.export') }}
</el-button>
</div>
<el-table
v-loading="loading" :data="detailData?.componentList" :stripe="true"
:show-overflow-tooltip="true">
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.componentCode')"
align="center" prop="code" min-width="140" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.componentName')"
align="center" prop="name" min-width="140" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.componentDesc')"
align="center" prop="description" min-width="180"/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.remark')"
align="center" prop="remark" min-width="180"/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.createTime')"
align="center" prop="createTime" :formatter="dateFormatter"
width="180" sortable/>
</el-table>
</el-tab-pane>
<el-tab-pane :label="t('EquipmentManagement.EquipmentLedger.sparePart')" name="component">
<div class="device-ledger-tab-toolbar">
<el-button
type="success" plain :loading="spareExportLoading" @click="handleExportSpareBased"
v-hasPermi="['mes:device-ledger:export']">
<Icon icon="ep:download" class="mr-5px"/>
{{ t('action.export') }}
</el-button>
</div>
<el-table v-loading="loading" :data="detailData?.beijianList" :stripe="true"
:show-overflow-tooltip="true">
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.spareCode')"
align="center" prop="barCode" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.spareName')"
align="left" prop="name" width="220px" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.category')"
align="center" prop="categoryName" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.unit')" align="center"
prop="unitName" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.createTime')"
align="center" prop="createTime" :formatter="dateFormatter"
width="180px" sortable/>
</el-table>
</el-tab-pane>
<el-tab-pane label="模具" name="mold">
<el-table v-loading="loading" :data="detailData?.moldList" :stripe="true"
:show-overflow-tooltip="true">
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.moldCode')"
align="center" prop="code" min-width="140" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.moldName')"
align="center" prop="name" min-width="140" sortable/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.moldRemark')"
align="center" prop="remark" min-width="180"/>
<el-table-column :label="t('EquipmentManagement.EquipmentLedger.createTime')"
align="center" prop="createTime" :formatter="dateFormatter"
width="180" sortable/>
</el-table>
<Pagination
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList"/>
</el-tab-pane>
</el-tabs>
</ContentWrap>
</div>
</div>
<!-- 表单弹窗:添加/修改 -->
<DeviceLedgerForm ref="formRef" @success="getList"/>
</template>
<script setup lang="ts">
import {dateFormatter, dateFormatter2, formatDate} from '@/utils/formatTime'
import download from '@/utils/download'
import {DeviceLedgerApi, DeviceLedgerVO} from '@/api/mes/deviceledger'
import {DeviceTypeApi, DeviceTypeTreeVO} from '@/api/mes/devicetype'
import {CriticalComponentApi} from '@/api/mes/criticalComponent'
import {TicketManagementApi} from '@/api/mes/ticketManagement'
import {DvRepairApi} from '@/api/mes/dvrepair'
import DeviceLedgerForm from './DeviceLedgerForm.vue'
import { getIntDictOptions } from '@/utils/dict'
import { isHexColor } from '@/utils/color'
import { useDictStoreWithOut } from '@/store/modules/dict'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
import {ref} from "vue";
import {
Refresh,
Grid,
Menu,
Search,
Location
} from '@element-plus/icons-vue'
import {useRouter} from "vue-router";
const currentView = ref('table') // 'table' 'grid'
//
const router = useRouter()
//
const handleView = (row) => {
router.push({
path: '/equipment/detail',
query: {id: row.id}
})
}
/** 设备类型 列表 */
defineOptions({name: 'DeviceLedger'})
const getEquipmentColor = (type) => {
const colorMap = {
'production': '#409eff',
'inspection': '#67c23a',
'packaging': '#e6a23c',
'transport': '#f56c6c',
'other': '#909399'
}
return colorMap[type] || '#409eff'
}
const getEquipmentIcon = (type) => {
const iconMap = {
'production': 'Monitor',
'inspection': 'Search',
'packaging': 'Box',
'transport': 'Truck',
'other': 'Tools'
}
return iconMap[type] || 'Tools'
}
// 工具函数
const getStatusTag = (status) => {
const tagMap = {
'running': 'success',
'standby': 'info',
'fault': 'danger',
'maintenance': 'warning',
'stopped': 'info'
}
return tagMap[status] || 'info'
}
const getStatusText = (status) => {
const textMap = {
'running': '运行中',
'standby': '待机',
'fault': '故障',
'maintenance': '维修中',
'stopped': '已停用'
}
return textMap[status] || '未知'
}
// 分页
const handleSizeChange = (size) => {
queryParams.pageSize = size
getList()
}
const handlePageChange = (page) => {
queryParams.pageNo = page
getList()
}
const formatRunningHours = (hours) => {
if (!hours) return '0h'
if (hours < 24) return `${hours}h`
const days = Math.floor(hours / 24)
const remainingHours = hours % 24
return remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`
}
const message = useMessage() // 消息弹窗
const {t} = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<DeviceLedgerVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceCode: undefined,
deviceName: undefined,
deviceStatus: undefined,
deviceType: undefined,
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
const criticalExportLoading = ref(false)
const spareExportLoading = ref(false)
const inspectionExportLoading = ref(false)
const inspectionDateRange = ref<string[] | undefined>(undefined)
const maintainExportLoading = ref(false)
const maintainDateRange = ref<string[] | undefined>(undefined)
const repairExportLoading = ref(false)
const repairDateRange = ref<string[] | undefined>(undefined)
const tableRef = ref()
const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []
}
// 切换视图
const toggleView = () => {
currentView.value = currentView.value === 'table' ? 'grid' : 'table'
// 保存视图偏好
localStorage.setItem('equipment-view', currentView.value)
}
const dictStore = useDictStoreWithOut()
const dictReady = ref(false)
const inspectionHistory = ref<any[]>([])
const maintainHistory = ref<any[]>([])
const formatHistoryTime = (value: any) => {
const raw = value ?? '-'
if (Array.isArray(raw) && raw.length >= 3) {
const [y, m, d, hh, mm, ss] = raw
const pad = (n: any) => String(n).padStart(2, '0')
if (hh !== undefined) return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
return `${y}-${pad(m)}-${pad(d)}`
}
const tryDateFromNumber = (v: any) => {
const num = Number(v)
if (!Number.isFinite(num)) return undefined
const s = String(v).trim()
const ms = s.length === 10 ? num * 1000 : num
const d = new Date(ms)
if (Number.isNaN(d.getTime())) return undefined
return d
}
const tryDateFromString = (v: any) => {
const s = String(v).trim()
if (!s) return undefined
const d = new Date(s)
if (Number.isNaN(d.getTime())) return undefined
return d
}
const d = typeof raw === 'number' ? tryDateFromNumber(raw) : tryDateFromNumber(raw) ?? tryDateFromString(raw)
if (d) return formatDate(d, 'YYYY-MM-DD HH:mm:ss')
return String(raw)
}
const getResultLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (!v) return '-'
const upper = v.toUpperCase()
if (v === '0') return '待检测'
if (v === '1' || upper === 'OK') return '通过'
if (v === '2' || upper === 'NG') return '不通过'
return v
}
const getResultTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (!v) return 'info'
const upper = v.toUpperCase()
if (v === '1' || upper === 'OK') return 'success'
if (v === '2' || upper === 'NG') return 'danger'
if (v === '0') return 'info'
return 'info'
}
type HistoryStepItem = {
key: string
name: string
result: any
method?: any
criteria?: any
images?: string[]
remark?: any
taskTime?: any
createTime?: any
}
type HistoryStepGroup = { key: string; time: string; operator: string; items: HistoryStepItem[] }
const buildStepGroups = (rows: any[], options: { timeField: string; nameFieldCandidates: string[]; resultFieldCandidates: string[] }) => {
const groups = new Map<string, HistoryStepGroup>()
for (const row of rows ?? []) {
const time = formatHistoryTime(row?.taskTime ?? row?.[options.timeField] ?? row?.createTime)
const operator = String(row?.operator ?? row?.creatorName ?? row?.creator ?? '-')
const groupKey = `${row?.managementId ?? ''}__${time}__${operator}`
const name =
options.nameFieldCandidates.map((k) => row?.[k]).find((v) => v !== undefined && v !== null && String(v).trim() !== '') ??
'-'
const result = options.resultFieldCandidates.map((k) => row?.[k]).find((v) => v !== undefined && v !== null) ?? undefined
const item: HistoryStepItem = {
key: String(row?.id ?? `${groupKey}__${String(name)}`),
name: String(name),
result,
method: row?.inspectionMethod,
criteria: row?.judgmentCriteria,
images: parseImages(row?.images),
remark: row?.remark,
taskTime: row?.taskTime,
createTime: row?.createTime
}
if (!groups.has(groupKey)) {
groups.set(groupKey, {key: groupKey, time, operator, items: [item]})
} else {
groups.get(groupKey)!.items.push(item)
}
}
return Array.from(groups.values()).sort((a, b) => String(b.time).localeCompare(String(a.time)))
}
const inspectionStepGroups = computed<HistoryStepGroup[]>(() => {
return buildStepGroups(inspectionHistory.value, {
timeField: 'inspectionTime',
nameFieldCandidates: ['inspectionItemName', 'name'],
resultFieldCandidates: ['inspectionResult']
})
})
const maintainStepGroups = computed<HistoryStepGroup[]>(() => {
return buildStepGroups(maintainHistory.value, {
timeField: 'inspectionTime',
nameFieldCandidates: ['maintainItemName', 'inspectionItemName', 'name'],
resultFieldCandidates: ['maintainResult', 'inspectionResult']
})
})
type RepairHistoryRow = {
id?: any
repairId?: any
repairCode?: any
repairName?: any
subjectId?: any
subjectCode?: any
subjectName?: any
subjectContent?: any
subjectStandard?: any
malfunction?: any
malfunctionUrl?: any
repairDes?: any
remark?: any
createTime?: any
finishDate?: any
result?: any
repairResult?: any
malfunctionImages?: string[]
}
type RepairHistoryGroup = { key: string; name: string; items: RepairHistoryRow[] }
const repairActiveNames = ref<string[]>([])
const repairList = ref<RepairHistoryRow[]>([])
const repairGroups = computed<RepairHistoryGroup[]>(() => {
const groupsMap = new Map<string, RepairHistoryGroup>()
for (const row of repairList.value ?? []) {
const key = String(row.repairCode ?? row.repairId ?? row.subjectName ?? '-')
if (!groupsMap.has(key)) {
groupsMap.set(key, {
key,
name: String(row.repairName ?? row.repairCode ?? key),
items: []
})
}
const group = groupsMap.get(key)!
group.items.push({
...row,
malfunctionImages: parseImages(row?.malfunctionUrl)
})
}
const groups = Array.from(groupsMap.values()).filter((g) => g.items.length)
return groups.sort((a, b) => {
const at = formatHistoryTime(a.items?.[0]?.finishDate ?? a.items?.[0]?.createTime)
const bt = formatHistoryTime(b.items?.[0]?.finishDate ?? b.items?.[0]?.createTime)
return String(bt).localeCompare(String(at))
})
})
const fetchInspectionHistory = async () => {
if (!selectedDetailId.value) return
const params: any = {deviceId: selectedDetailId.value}
if (inspectionDateRange.value && inspectionDateRange.value.length === 2) {
params.startTime = inspectionDateRange.value[0]
params.endTime = inspectionDateRange.value[1]
}
const data = await TicketManagementApi.getInspectionByDeviceId(params)
inspectionHistory.value = Array.isArray(data) ? data : []
}
const handleQueryInspection = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
await fetchInspectionHistory()
}
const handleResetInspection = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
inspectionDateRange.value = undefined
await fetchInspectionHistory()
}
const fetchMaintainHistory = async () => {
if (!selectedDetailId.value) return
const params: any = {deviceId: selectedDetailId.value}
if (maintainDateRange.value && maintainDateRange.value.length === 2) {
params.startTime = maintainDateRange.value[0]
params.endTime = maintainDateRange.value[1]
}
const data = await TicketManagementApi.getMaintenanceByDeviceId(params)
maintainHistory.value = Array.isArray(data) ? data : []
}
const handleQueryMaintain = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
await fetchMaintainHistory()
}
const handleResetMaintain = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
maintainDateRange.value = undefined
await fetchMaintainHistory()
}
const fetchRepairHistory = async () => {
if (!selectedDetailId.value) return
const params: any = {deviceId: selectedDetailId.value}
if (repairDateRange.value && repairDateRange.value.length === 2) {
params.startTime = repairDateRange.value[0]
params.endTime = repairDateRange.value[1]
}
const data = await DvRepairApi.getRepairListByDeviceId(params)
repairList.value = Array.isArray(data) ? data : []
}
const handleQueryRepair = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
await fetchRepairHistory()
}
const handleResetRepair = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
repairDateRange.value = undefined
await fetchRepairHistory()
}
const tzStatusOptions = computed(() => {
if (!dictReady.value) return []
const options = getIntDictOptions('mes_tz_status')
return options.filter((o) => o.value !== null && o.value !== undefined && !Number.isNaN(o.value))
})
const detailVisible = ref(false)
const detailLoading = ref(false)
const detailData = ref<DeviceLedgerVO | undefined>()
const detailStatusLoading = ref(false)
const deviceStatusUpdatingMap = ref<Record<number, boolean>>({})
const detailActiveTab = ref('check')
const selectedDetailId = ref<number | undefined>()
const typeTreeLoading = ref(false)
const typeTreeData = ref<DeviceTypeTreeVO[]>([])
const typeTreeProps = {label: 'name', children: 'children'}
const typeTreeExpandedKeys = ref<number[]>([0])
const deviceTypeNameMap = ref<Record<number, string>>({})
const buildDeviceTypeNameMap = (nodes: DeviceTypeTreeVO[]) => {
const map: Record<number, string> = {}
const stack = [...nodes]
while (stack.length) {
const node = stack.pop()!
if (typeof node.id === 'number') map[node.id] = node.name
if (Array.isArray(node.children) && node.children.length) stack.push(...node.children)
}
deviceTypeNameMap.value = map
}
const getTypeTree = async () => {
typeTreeLoading.value = true
try {
const data = await DeviceTypeApi.getDeviceTypeTree({pageNo: 1, pageSize: 10})
const treeChildren = JSON.parse(JSON.stringify(data ?? []))
typeTreeData.value = [{
id: 0,
code: '',
name: '全部',
remark: '',
sort: 0,
children: treeChildren
} as any]
buildDeviceTypeNameMap(treeChildren)
typeTreeExpandedKeys.value = [0]
} finally {
typeTreeLoading.value = false
}
}
const handleTypeNodeClick = (node: any) => {
const id = node?.id
queryParams.deviceType = id === 0 ? undefined : id
queryParams.pageNo = 1
getList()
}
const getDeviceTypeName = (value: any) => {
const id = typeof value === 'number' ? value : Number(value)
if (!Number.isNaN(id) && deviceTypeNameMap.value[id]) return deviceTypeNameMap.value[id]
return value ?? ''
}
const getTzStatusLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : Number(value)
const found = tzStatusOptions.value.find((d) => d.value === v)
return found?.label ?? ''
}
const getTzStatusTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : Number(value)
const found = tzStatusOptions.value.find((d) => d.value === v)
const type = found?.colorType
if (type + '' === 'primary' || type + '' === 'default') return '' as any
return (type ?? '') as any
}
const getTzStatusTagColor = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : Number(value)
const found = tzStatusOptions.value.find((d) => d.value === v)
if (found?.cssClass && isHexColor(found.cssClass)) return found.cssClass
return ''
}
const getTzStatusTagStyle = (value: any) => {
const color = getTzStatusTagColor(value)
if (!color) return ''
return 'color: #fff'
}
const formatScheduleLabel = (value: any) => {
return Number(value) === 1
? t('EquipmentManagement.EquipmentLedger.yes')
: t('EquipmentManagement.EquipmentLedger.no')
}
const isDeviceLedgerEnabled = (row: DeviceLedgerVO) => {
return Number((row as any)?.deviceStatus) === 0
}
const handleDeviceStatusChange = async (row: DeviceLedgerVO, value: boolean) => {
if (!row?.id) return
const oldValue = Number((row as any).deviceStatus)
const nextValue = value ? 0 : 1
;(row as any).deviceStatus = nextValue
deviceStatusUpdatingMap.value[row.id] = true
try {
await DeviceLedgerApi.updateDeviceLedger({
id: row.id,
deviceStatus: nextValue
} as DeviceLedgerVO)
if (detailData.value?.id === row.id) {
detailData.value.deviceStatus = nextValue
}
message.success(t('common.updateSuccess'))
} catch {
;(row as any).deviceStatus = oldValue
if (detailData.value?.id === row.id) {
detailData.value.deviceStatus = oldValue
}
message.error(t('common.updateFail'))
} finally {
deviceStatusUpdatingMap.value[row.id] = false
}
}
const formatDetailDate = (value: any) => {
if (!value) return ''
return formatDate(new Date(value), 'YYYY-MM-DD')
}
const parseImages = (value: any): string[] => {
if (!value) return []
if (Array.isArray(value)) return value.map(String).filter(Boolean)
const cleaned = String(value).replace(/[`'\"]/g, '').trim()
return cleaned
.split(',')
.map((v) => v.trim())
.filter(Boolean)
}
const parseFirstImage = (value: any): string => {
return parseImages(value)[0] || ''
}
const handleDetail = async (id: number) => {
if (detailVisible.value && selectedDetailId.value === id) {
closeDetail()
return
}
selectedDetailId.value = id
detailVisible.value = true
detailActiveTab.value = 'check'
detailLoading.value = true
try {
detailData.value = await DeviceLedgerApi.getDeviceLedger(id)
await fetchInspectionHistory()
await fetchMaintainHistory()
await fetchRepairHistory()
const keys = repairGroups.value.map((g) => g.key)
repairActiveNames.value = keys.length ? [keys[0]] : []
} finally {
detailLoading.value = false
}
}
const handleDetailStatusChange = async (value: number) => {
if (!detailData.value?.id) return
const oldValue = value === 0 ? 1 : 0
detailStatusLoading.value = true
try {
await DeviceLedgerApi.updateDeviceLedger({
id: detailData.value.id,
deviceStatus: value
} as DeviceLedgerVO)
const current = list.value.find((item) => item.id === detailData.value?.id)
if (current) {
current.deviceStatus = value
}
message.success(t('common.updateSuccess'))
} catch {
detailData.value.deviceStatus = oldValue
message.error(t('common.updateFail'))
} finally {
detailStatusLoading.value = false
}
}
const closeDetail = () => {
detailVisible.value = false
selectedDetailId.value = undefined
detailData.value = undefined
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await DeviceLedgerApi.getDeviceLedgerPage({
pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize,
deviceCode: queryParams.deviceCode,
deviceName: queryParams.deviceName,
deviceStatus: queryParams.deviceStatus,
deviceType: queryParams.deviceType
})
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id, queryParams.deviceType)
}
/** 删除按钮操作 */
const buildIdsParam = (ids: number | number[]) => {
return Array.isArray(ids) ? ids.join(',') : String(ids)
}
const handleDelete = async (ids: number | number[]) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
const idsParam = buildIdsParam(ids)
await DeviceLedgerApi.deleteDeviceLedger(idsParam)
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()
// 刷新列表
await getList()
} catch {
}
}
const handleBatchDelete = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要删除的数据')
return
}
await handleDelete(selectedIds.value)
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const params: any = {
...queryParams,
ids: selectedIds.value.length ? selectedIds.value.join(',') : undefined
}
const data = await DeviceLedgerApi.exportDeviceLedger(params)
download.excel(data, '设备台账.xls')
} catch {
} finally {
exportLoading.value = false
}
}
const handleExportCriticalComponent = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
try {
await message.exportConfirm()
criticalExportLoading.value = true
const data = await CriticalComponentApi.exportDeviceComponent({id: selectedDetailId.value})
download.excel(data, '关键件.xls')
} catch {
} finally {
criticalExportLoading.value = false
}
}
const handleExportSpareBased = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
try {
await message.exportConfirm()
spareExportLoading.value = true
const data = await DeviceLedgerApi.exportSpareBased({id: selectedDetailId.value})
download.excel(data, '备件.xls')
} catch {
} finally {
spareExportLoading.value = false
}
}
const handleExportInspection = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
try {
await message.exportConfirm()
inspectionExportLoading.value = true
const params: any = {deviceId: selectedDetailId.value}
if (inspectionDateRange.value && inspectionDateRange.value.length === 2) {
params.startTime = inspectionDateRange.value[0]
params.endTime = inspectionDateRange.value[1]
}
const data = await TicketManagementApi.exportInspection(params)
download.excel(data, '点检履历.xls')
} catch {
} finally {
inspectionExportLoading.value = false
}
}
const handleExportMaintain = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
try {
await message.exportConfirm()
maintainExportLoading.value = true
const params: any = {deviceId: selectedDetailId.value}
if (maintainDateRange.value && maintainDateRange.value.length === 2) {
params.startTime = maintainDateRange.value[0]
params.endTime = maintainDateRange.value[1]
}
const data = await TicketManagementApi.exportMaintenance(params)
download.excel(data, '保养履历.xls')
} catch {
} finally {
maintainExportLoading.value = false
}
}
const handleExportRepair = async () => {
if (!selectedDetailId.value) {
message.error('请先选择设备')
return
}
try {
await message.exportConfirm()
repairExportLoading.value = true
const params: any = {deviceId: selectedDetailId.value}
if (repairDateRange.value && repairDateRange.value.length === 2) {
params.startTime = repairDateRange.value[0]
params.endTime = repairDateRange.value[1]
}
const data = await DvRepairApi.exportRepairExcel(params)
download.excel(data, '维修履历.xls')
} catch {
} finally {
repairExportLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
await dictStore.setDictMap()
dictReady.value = true
getTypeTree()
getList()
})
</script>
<style lang="scss" scoped>
.container {
margin-top: 10px;
display: flex; /* 关键:开启弹性布局 */
gap: 20px; /* 可选两个div之间的间距 */
}
.left-div, .right-div {
//flex: 1; /* 关键让两个div等宽 */
/* 或者设置固定宽度,如 width: 300px; */
border: 1px solid #ccc;
}
.upload-image {
width: 150px;
height: 150px;
object-fit: contain;
border: 1px solid #ccc;
}
.device-ledger-layout {
display: flex;
gap: 12px;
}
.device-ledger-left {
width: 280px;
flex: 0 0 auto;
}
.device-ledger-right {
flex: 1;
min-width: 0;
}
.device-ledger-detail-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.device-ledger-detail-title {
font-size: 14px;
font-weight: 600;
}
.device-ledger-history-steps {
padding: 8px 8px 0;
}
.device-ledger-history-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
}
.device-ledger-history-time {
font-weight: 600;
}
.device-ledger-history-operator {
color: var(--el-text-color-secondary);
}
.device-ledger-tab-toolbar {
margin-bottom: 8px;
text-align: right;
}
.device-ledger-history-items {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 8px;
}
.device-ledger-history-item {
display: flex;
padding: 10px;
background: var(--el-fill-color-blank);
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
flex-direction: column;
gap: 8px;
}
.device-ledger-history-item-head {
display: flex;
align-items: center;
gap: 10px;
}
.device-ledger-history-item-body {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.device-ledger-history-item-row {
display: flex;
gap: 10px;
}
.device-ledger-history-item-label {
width: 70px;
flex: 0 0 70px;
color: var(--el-text-color-secondary);
}
.device-ledger-history-item-value {
flex: 1;
color: var(--el-text-color-regular);
word-break: break-word;
}
.device-ledger-history-item-images {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.device-ledger-history-item-image {
width: 76px;
height: 76px;
overflow: hidden;
border-radius: 6px;
}
.device-ledger-history-image-error {
display: flex;
width: 100%;
height: 100%;
font-size: 12px;
color: var(--el-text-color-secondary);
background: var(--el-fill-color);
align-items: center;
justify-content: center;
}
.device-ledger-repair-collapse {
padding: 8px 8px 0;
}
.device-ledger-repair-title {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.device-ledger-repair-name {
font-weight: 600;
color: var(--el-text-color-primary);
}
.device-ledger-repair-meta {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.device-ledger-history-item-text {
color: var(--el-text-color-regular);
}
.device-ledger-detail-qrcode {
margin-top: 10px;
}
.simple-grid-view {
background-color: #fff;
border-radius: 4px;
padding: 16px;
height: calc(100vh - 600px);
overflow-y: auto;
.empty-grid { // 注意:这里缩进要与上面一致
display: flex;
align-items: center;
justify-content: center;
height: 300px;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
padding: 8px;
.grid-card {
position: relative;
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 20px;
background-color: #fafafa;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 16px;
&:hover {
border-color: #409eff;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
transform: translateY(-2px);
background-color: #fff;
}
.status-indicator {
position: absolute;
top: 0;
left: 0;
width: 6px;
height: 100%;
border-radius: 8px 0 0 8px;
&.status-running {
background-color: #67c23a;
}
&.status-standby {
background-color: #909399;
}
&.status-fault {
background-color: #f56c6c;
}
&.status-maintenance {
background-color: #e6a23c;
}
&.status-stopped {
background-color: #909399;
}
}
.card-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
background-color: #f5f7fa;
border-radius: 8px;
}
.card-content {
flex: 1;
min-width: 0;
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-code {
font-size: 12px;
color: #909399;
margin-bottom: 2px;
}
.card-model {
font-size: 13px;
color: #606266;
margin-bottom: 8px;
}
.card-status {
margin-bottom: 8px;
.status-tag {
width: 100%;
justify-content: center;
}
}
.card-running {
font-size: 12px;
color: #606266;
margin-bottom: 4px;
}
.card-location {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #909399;
.el-icon {
color: #909399;
}
}
}
}
}
.simple-pagination {
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
text-align: center;
}
}
// 响应式
@media (max-width: 768px) {
.equipment-simple {
padding: 12px;
.simple-toolbar {
flex-direction: column;
gap: 12px;
.toolbar-left {
width: 100%;
.simple-search {
flex: 1;
width: 100%;
}
.status-filter,
.type-filter {
width: 100px;
}
}
.toolbar-right {
width: 100%;
justify-content: flex-end;
}
}
.simple-grid-view {
.grid-container {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
}
}
@media (max-width: 480px) {
.equipment-simple {
.simple-grid-view {
.grid-container {
grid-template-columns: 1fr;
}
}
.simple-toolbar {
.toolbar-left {
flex-wrap: wrap;
.simple-search {
width: 100%;
}
.status-filter,
.type-filter {
flex: 1;
min-width: 120px;
}
}
}
}
}
</style>