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/detail/editIndex.vue

2111 lines
70 KiB
Vue

<template>
<div ref="detailPageRef" class="device-ledger-detail-page">
<ContentWrap>
<div v-loading="detailLoading" class="device-ledger-detail-body">
<div class="device-ledger-detail-title">{{
t('EquipmentManagement.EquipmentLedger.BasicInformation')
}}</div>
<div class="device-ledger-detail-main">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading || fileUploading"
:element-loading-text="t('common.loading')"
>
<el-row :gutter="20" class="device-basic-layout">
<el-col :xs="24" :lg="18" class="device-basic-fields">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceCode')"
prop="deviceCode"
>
<el-row :gutter="12" class="device-code-row">
<el-col :xs="18" :sm="18" :md="18" :lg="19" :xl="20">
<el-input
v-model="formData.deviceCode"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderDeviceCode')
"
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="6" :sm="6" :md="6" :lg="5" :xl="4">
<div class="device-code-switch">
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceName')"
prop="deviceName"
required
>
<el-input
v-model="formData.deviceName"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderDeviceName')
"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceType')"
prop="deviceType"
required
>
<el-tree-select
v-model="formData.deviceType"
:data="deviceTypeTree"
:props="treeSelectProps"
check-strictly
default-expand-all
value-key="id"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderDeviceType')
"
class="!w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceSpec')"
prop="deviceSpec"
>
<el-input
v-model="formData.deviceSpec"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderDeviceSpec')
"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.factoryEntryDate')"
prop="factoryEntryDate"
>
<el-date-picker
v-model="formData.factoryEntryDate"
type="date"
value-format="YYYY-MM-DD"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderFactoryEntryDate')
"
class="!w-full"
/>
</el-form-item>
</el-col>
<el-col v-if="isScheduledEnabled" :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.ratedCapacity')"
prop="ratedCapacity"
:required="isScheduledEnabled"
>
<el-input-number
v-model="formData.ratedCapacity"
:min="0"
:precision="0"
controls-position="right"
class="!w-full"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderRatedCapacity')
"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.isSchedueld')"
prop="isScheduled"
>
<el-switch
v-model="formData.isScheduled"
:active-value="1"
:inactive-value="0"
:active-text="t('EquipmentManagement.EquipmentLedger.yes')"
:inactive-text="t('EquipmentManagement.EquipmentLedger.no')"
inline-prompt
/>
</el-form-item>
</el-col>
<el-col :span="12">
<!-- <el-form-item :label="t('EquipmentManagement.EquipmentLedger.productionDate')" prop="productionDate">
<el-date-picker v-model="formData.productionDate" type="date" value-format="YYYY-MM-DD"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderProductionDate')" class="!w-full" />
</el-form-item>-->
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.outgoingTime')"
prop="outgoingTime"
>
<el-date-picker
v-model="formData.outgoingTime"
type="date"
value-format="YYYY-MM-DD"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderOutgoingTime')
"
class="!w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceBrand')"
prop="deviceBrand"
>
<el-input
v-model="formData.deviceBrand"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderDeviceBrand')
"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceLocation')"
prop="deviceLocation"
>
<el-input
v-model="formData.deviceLocation"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderDeviceLocation')
"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.deviceManagerName')"
prop="deviceManagerIds"
>
<el-select
v-model="formData.deviceManagerIds"
multiple
filterable
clearable
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderDeviceManagerIds')
"
class="!w-full"
>
<el-option
v-for="item in users"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.sn')" prop="sn">
<el-input
v-model="formData.sn"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderSn')"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.dvName')"
prop="dvId"
>
<el-input
:model-value="iotDeviceDisplay"
readonly
clearable
class="device-ledger-selection-input"
:placeholder="t('EquipmentManagement.EquipmentLedger.dvId')"
@clear="clearIotDevice"
@click="openIotDeviceDialog"
>
<template #append>
<el-button
v-if="iotDeviceDisplay"
@click.stop.prevent="clearIotDevice"
>
<Icon icon="ep:close" />
</el-button>
<el-button @click.stop="openIotDeviceDialog">
<Icon icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.criticalComponent')"
prop="componentIds"
>
<el-input
:model-value="criticalComponentDisplay"
readonly
clearable
class="device-ledger-selection-input"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderComponentIds')
"
@clear="clearCriticalComponent"
@click="openCriticalComponentDialog"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.sparePart')"
prop="beijianIds"
>
<el-input
:model-value="beijianDisplay"
readonly
clearable
class="device-ledger-selection-input"
:placeholder="
t('EquipmentManagement.EquipmentLedger.placeholderBeijianIds')
"
@clear="clearBeijian"
@click="openBeijianDialog"
/>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col :xs="24" :lg="6" class="device-basic-media">
<div class="device-media-panel">
<el-form-item label-width="0" prop="images" class="device-media-item">
<div class="device-media-box device-media-upload-box">
<span class="device-media-label">{{
t('EquipmentManagement.EquipmentLedger.images')
}}</span>
<UploadImg class="device-media-upload" v-model="formData.images" />
</div>
</el-form-item>
<el-form-item
v-if="formType === 'update'"
label-width="0"
prop="qrcodeUrl"
class="device-media-item"
>
<div class="device-media-box device-media-code-box">
<span class="device-media-label">{{
t('EquipmentManagement.EquipmentLedger.qrcode')
}}</span>
<div class="device-media-code">
<QrcodeActionCard
:image-url="formData.qrcodeUrl"
:print-id="formData.id"
business-scene="device-qrcode-print"
:print-title="`${formData.deviceName || 'Device'} QR Code`"
:print-paper-width="80"
:print-paper-height="80"
:print-max-width="220"
:empty-text="t('EquipmentManagement.EquipmentLedger.qrcodeEmpty')"
:error-text="t('EquipmentManagement.EquipmentLedger.qrcodeLoadError')"
:refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!formData.id || !formData.deviceCode"
refresh-confirm-text="Refresh QR code?"
:template-json="formData.templateJson"
:print-data="buildPrintData()"
@refresh-success="handleQrcodeRefreshSuccess"
/>
</div>
</div>
</el-form-item>
</div>
</el-col>
<el-col :span="24">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.fileUrl')"
prop="fileUrl"
>
<UploadFile
:is-show-tip="false"
v-model="formData.fileUrl"
:file-type="materialFileTypes"
:file-size="100"
:limit="9"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item
:label="t('EquipmentManagement.EquipmentLedger.remark')"
prop="remark"
>
<el-input
v-model="formData.remark"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderRemark')"
type="textarea"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="device-ledger-detail-tabs">
<el-tabs v-model="detailActiveTab" class="mt-12px">
<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
ref="tableRef"
v-loading="tableLoading"
:data="editableCriticalComponentRows"
: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.EquipmentKeyItems.count')"
align="center"
prop="count"
min-width="180"
>
<template #default="scope">
<el-input-number
v-model="scope.row.count"
:min="0"
:precision="0"
controls-position="right"
class="!w-full"
/>
</template>
</el-table-column>
<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-column
:label="t('EquipmentManagement.EquipmentLedger.operate')"
align="center"
min-width="160px"
fixed="right"
>
<template #default="scope">
<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>
</el-tab-pane>
</el-tabs>
</div>
<div class="device-ledger-action-bar" :style="actionBarStyle">
<el-button @click="handleCancel">{{ t('common.cancel') }}</el-button>
<el-button
type="primary"
:loading="formLoading"
:disabled="fileUploading"
@click="submitForm"
>{{ t('common.save') }}</el-button
>
</div>
</div>
<!-- 鍏抽敭浠跺脊鍑烘 -->
<TableSelectDialog
ref="criticalComponentSelectDialogRef"
:title="t('EquipmentManagement.EquipmentLedger.gjTitle')"
:columns="criticalComponentColumns"
:fetch-api="fetchCriticalComponentPage"
:query-params="queryParams"
:default-selected-keys="ids"
row-key="id"
@confirm="handleCriticalComponentSelectConfirm"
>
<template #createTime="{ row }">
{{ formatCriticalComponentCreateTime(row?.createTime) }}
</template>
<template #header>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
min-label-width="68px"
style="margin-bottom: 10px"
>
<el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.code')" prop="code">
<el-input
v-model="queryParams.code"
:placeholder="t('EquipmentManagement.EquipmentKeyItems.placeholderCode')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.name')" prop="name">
<el-input
v-model="queryParams.name"
:placeholder="t('EquipmentManagement.EquipmentKeyItems.placeholderName')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item
:label="t('EquipmentManagement.EquipmentKeyItems.description')"
prop="description"
>
<el-input
v-model="queryParams.description"
:placeholder="t('EquipmentManagement.EquipmentKeyItems.placeholderDescription')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</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-form-item>
</el-form>
</template>
</TableSelectDialog>
<!-- 澶囦欢寮瑰嚭妗? -->
<el-dialog
v-model="beijianDialogVisible"
:title="t('EquipmentManagement.EquipmentLedger.bjTitle')"
width="860px"
class="device-ledger-transfer-dialog"
append-to-body
>
<!-- <div class="device-ledger-transfer">
<el-transfer
v-model="beijianDraft"
:data="beijianTransferData"
filterable
:filter-placeholder="t('EquipmentManagement.EquipmentLedger.placeholderBeijianIds')"
/>
</div>-->
<!-- 鍒楄〃 -->
<el-form
class="-mb-15px"
:model="bjQueryParams"
ref="bjQueryFormRef"
:inline="true"
min-label-width="68px"
style="margin-bottom: 10px"
>
<el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="barCode">
<el-input
v-model="bjQueryParams.barCode"
:placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareInfo.name')" prop="name">
<el-input
v-model="bjQueryParams.name"
:placeholder="t('SparePartsManagement.SpareInfo.placeholderName')"
clearable
@keyup.enter="bjHandleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="bjHandleQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('common.query') }}
</el-button>
<el-button @click="bjResetQuery">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('common.reset') }}
</el-button>
</el-form-item>
</el-form>
<ContentWrap>
<el-table
ref="bjMultipleTableRef"
v-loading="loading"
:data="bjList"
:stripe="true"
class="no-select-all"
@selection-change="bjHandleSelectionChange"
@select="bjHandleSelect"
@select-all="bjHandleSelectAll"
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column type="selection" width="55" :reserve-selection="true" />
<el-table-column
:label="t('SparePartsManagement.SpareInfo.code')"
align="center"
prop="barCode"
width="240px"
sortable
/>
<el-table-column
:label="t('SparePartsManagement.SpareInfo.name')"
align="left"
prop="name"
width="220px"
sortable
/>
<el-table-column
:label="t('SparePartsManagement.SpareInfo.unit')"
align="center"
prop="unitName"
sortable
/>
<el-table-column
:label="t('SparePartsManagement.SpareInfo.status')"
align="center"
prop="status"
sortable
>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
:label="t('SparePartsManagement.SpareInfo.createTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable
/>
</el-table>
<!-- 鍒嗛〉 -->
<Pagination
:total="bjTotal"
v-model:page="bjQueryParams.pageNo"
v-model:limit="bjQueryParams.pageSize"
@pagination="bjGetList"
/>
</ContentWrap>
<template #footer>
<el-button @click="beijianDialogVisible = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="confirmBeijianDialog">{{ t('common.ok') }}</el-button>
</template>
</el-dialog>
<el-dialog
v-model="iotDeviceDialogVisible"
:title="t('EquipmentManagement.EquipmentLedger.dvName')"
width="1000px"
class="device-ledger-device-dialog"
append-to-body
>
<el-form
ref="iotDeviceQueryFormRef"
:model="iotDeviceQueryParams"
:inline="true"
min-label-width="68px"
class="-mb-15px"
style="margin-bottom: 10px"
>
<el-form-item :label="t('DataCollection.Device.deviceCode')" prop="deviceCode">
<el-input
v-model="iotDeviceQueryParams.deviceCode"
:placeholder="t('DataCollection.Device.placeholderDeviceCode')"
clearable
class="!w-240px"
@keyup.enter="handleIotDeviceQuery"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.deviceName')" prop="deviceName">
<el-input
v-model="iotDeviceQueryParams.deviceName"
:placeholder="t('DataCollection.Device.placeholderDeviceName')"
clearable
class="!w-240px"
@keyup.enter="handleIotDeviceQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleIotDeviceQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('common.query') }}
</el-button>
<el-button @click="resetIotDeviceQuery">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('common.reset') }}
</el-button>
</el-form-item>
</el-form>
<ContentWrap>
<el-table
v-loading="iotDeviceLoading"
:data="iotDeviceList"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
class="device-ledger-device-table"
:row-class-name="getIotDeviceRowClassName"
@row-click="handleIotDeviceRowClick"
>
<el-table-column width="56" align="center">
<template #default="scope">
<el-radio
:model-value="iotDeviceDraftId"
:label="scope.row.id"
:disabled="!isIotDeviceSelectable(scope.row)"
@change="selectIotDevice(scope.row)"
@click.stop
>
<span></span>
</el-radio>
</template>
</el-table-column>
<el-table-column
:label="t('DataCollection.Device.deviceCode')"
align="left"
prop="deviceCode"
min-width="160"
/>
<el-table-column
:label="t('DataCollection.Device.deviceName')"
align="left"
prop="deviceName"
min-width="180"
/>
<el-table-column
label="已绑定的设备台账名称"
align="left"
prop="boundLedgerName"
min-width="180"
/>
<el-table-column
:label="t('DataCollection.Device.operatingStatus')"
align="center"
prop="operatingStatus"
width="120"
>
<template #default="scope">
<el-tag size="small" type="info">{{ scope.row.operatingStatus || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column
:label="t('DataCollection.Device.protocol')"
align="center"
prop="protocol"
width="130"
>
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_PROTOCOL" :value="scope.row.protocol" />
</template>
</el-table-column>
<el-table-column
:label="t('DataCollection.Device.isEnable')"
align="center"
prop="isEnable"
width="110"
>
<template #default="scope">
<el-tag :type="isIotDeviceEnabled(scope.row) ? 'success' : 'info'" size="small">
{{
isIotDeviceEnabled(scope.row)
? t('EquipmentManagement.EquipmentLedger.statusEnabled')
: t('EquipmentManagement.EquipmentLedger.statusDisabled')
}}
</el-tag>
</template>
</el-table-column>
<el-table-column
:label="t('DataCollection.Device.collectionTime')"
align="center"
prop="collectionTime"
min-width="170"
/>
</el-table>
<div class="device-ledger-dialog-pagination">
<Pagination
:total="iotDeviceTotal"
v-model:page="iotDeviceQueryParams.pageNo"
v-model:limit="iotDeviceQueryParams.pageSize"
@pagination="getIotDeviceList"
/>
</div>
</ContentWrap>
<template #footer>
<div class="device-ledger-dialog-footer">
<el-button @click="iotDeviceDialogVisible = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="confirmIotDeviceDialog">{{
t('common.ok')
}}</el-button>
</div>
</template>
</el-dialog>
</ContentWrap>
</div>
</template>
<script setup lang="ts">
import { dateFormatter, 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, CriticalComponentVO } from '@/api/mes/criticalComponent'
import { getSimpleUserList, UserVO } from '@/api/system/user'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { DeviceApi, DeviceVO } from '@/api/iot/device'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { useAppStore } from '@/store/modules/app'
import type { FormRules } from 'element-plus'
import { ref } from 'vue'
import { ElTable } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'MesDeviceLedgerEditDetail' })
const { t } = useI18n()
const message = useMessage()
const route = useRoute()
const { delView } = useTagsViewStore()
const { currentRoute } = useRouter()
const router = useRouter()
const appStore = useAppStore()
const deviceId = computed(() => String(route.params.id || ''))
const detailLoading = ref(false)
const tableLoading = ref(false)
const detailData = ref<DeviceLedgerVO | undefined>()
const detailActiveTab = ref('criticalComponent')
const detailPageRef = ref<HTMLElement>()
const actionBarStyle = reactive({
left: '0px',
width: '100%'
})
let actionBarObserver: ResizeObserver | undefined
const criticalComponentSelectDialogRef = ref(false)
const updateActionBarRect = () => {
const rect = detailPageRef.value?.getBoundingClientRect()
if (!rect) return
actionBarStyle.left = rect.left + 'px'
actionBarStyle.width = rect.width + 'px'
}
const tableRef = ref()
const updateActionBarRectAfterLayout = () => {
nextTick(updateActionBarRect)
window.setTimeout(updateActionBarRect, 320)
}
const criticalComponentDialogVisible = ref(false)
const beijianDialogVisible = ref(false)
const criticalComponentDraft = ref<number[]>([])
const beijianDraft = ref<number[]>([])
const list = ref<CriticalComponentVO[]>([])
const bjList = ref<ProductVO[]>([])
const loading = ref(true)
const total = ref(0)
const bjTotal = ref(0)
const selectedIds = ref<number[]>([])
const bjSelectedIds = ref<number[]>([])
// 琛ㄦ牸寮曠敤
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const bjMultipleTableRef = ref<InstanceType<typeof ElTable>>()
const queryFormRef = ref()
const bjQueryFormRef = ref()
const iotDeviceQueryFormRef = ref()
const iotDeviceLoading = ref(false)
const iotDeviceDialogVisible = ref(false)
const iotDeviceList = ref<DeviceVO[]>([])
const iotDeviceTotal = ref(0)
const iotDeviceSelectedLabel = ref('')
const iotDeviceDraftId = ref<number | undefined>()
const iotDeviceDraftRow = ref<DeviceVO | undefined>()
const parseIdsValue = (value: any): number[] => {
if (!value) return []
if (Array.isArray(value)) return value.map((v) => Number(v)).filter((v) => !Number.isNaN(v))
return String(value)
.split(',')
.map((v) => Number(v.trim()))
.filter((v) => !Number.isNaN(v))
}
const selectedRows = ref<any[]>([]) // 瀛樺偍鎵€鏈夐€変腑鐨勮
const bjSelectedRows = ref<any[]>([]) // 瀛樺偍鎵€鏈夐€変腑鐨勮
const queryParams = reactive({
code: undefined as string | undefined,
name: undefined as string | undefined,
description: undefined as string | undefined,
remark: undefined as string | undefined,
createTime: [] as string[]
})
const bjQueryParams = reactive({
pageNo: 1,
pageSize: 10,
barCode: undefined as string | undefined,
name: undefined as string | undefined,
categoryType: 3
})
const iotDeviceQueryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceCode: undefined as string | undefined,
deviceName: undefined as string | undefined
})
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 閰嶄欢鎼滅储鎸夐挳鎿嶄綔 */
const bjHandleQuery = () => {
bjQueryParams.pageNo = 1
bjGetList()
}
/** 閰嶄欢閲嶇疆鎸夐挳鎿嶄綔 */
const bjResetQuery = () => {
bjQueryFormRef.value.resetFields()
bjHandleQuery()
}
const handleSelectionChange = (rows: CriticalComponentVO[]) => {
selectedIds.value = rows.map((r) => r.id).filter((id): id is number => typeof id === 'number')
// 鑾峰彇褰撳墠椤垫墍鏈夎锟?id
const currentPageIds = rows.map((item) => item.id)
// 浠庡凡閫変腑鐨勬暟缁勪腑绉婚櫎褰撳墠椤电殑鏁版嵁
selectedRows.value = selectedRows.value.filter((item) => !currentPageIds.includes(item.id))
// 娣诲姞褰撳墠椤垫柊閫変腑鐨勬暟锟? selectedRows.value.push(...rows)
}
// 瀛樺偍褰撳墠宸查€変腑鐨勮
const handleSelect = (selection: any[], row: any) => {
// 鍒ゆ柇鏄€変腑杩樻槸鍙栨秷閫変腑
const isSelected = selection.includes(row)
if (isSelected) {
// console.log(`锟?琛岃閫変腑: ID=${row.id}, Name=${row.name}`)
ids.value.push(row.id)
} else {
ids.value = ids.value.filter((item) => item !== row.id)
// console.log(`锟?琛岃鍙栨秷閫変腑: ID=${row.id}, Name=${row.name}`)
}
// 鏇存柊褰撳墠閫変腑鐘讹拷? currentSelectedRows.value = selection
}
const handleSelectAll = (selection) => {
ids.value = selection?.map((row) => row.id).filter((id) => id !== undefined) ?? []
/* let newVar = selection?.map((row) => row.id).filter((id) => id !== undefined) ?? [];
newVar.forEach(row => {
ids.value.push(row)
})*/
}
const confirmCriticalComponentDialog = () => {
//let ids = selectedRows.value.map(item => item.id);
//const validMap = new Set(criticalComponentOptions.value.map((item) => item.value))
//const selected = Array.from(new Set(criticalComponentDraft.value.map((v) => Number(v)).filter((v) => validMap.has(v))))
formData.value.componentIds = filterValidSelectedIds(ids.value, criticalComponentOptions.value)
criticalComponentDialogVisible.value = false
syncCriticalComponentRows()
//multipleTableRef.value.clearSelection()
}
const filterValidSelectedIds = (selectedIds: any[], options: SelectionOption[]) => {
const validIds = new Set(options.map((item) => item.value))
return Array.from(
new Set(
(selectedIds ?? [])
.map((id) => normalizeNumberish(id))
.filter((id): id is number => id !== undefined && validIds.has(id))
)
)
}
const confirmBeijianDialog = () => {
// let ids = bjSelectedRows.value.map(item => item.id);
/* const validMap = new Set(beijianOptions.value.map((item) => item.value))
const selected = Array.from(new Set(beijianDraft.value.map((v) => Number(v)).filter((v) => validMap.has(v))))*/
formData.value.beijianIds = filterValidSelectedIds(bjIds.value, beijianOptions.value)
beijianDialogVisible.value = false
//multipleTableRef.value.clearSelection()
}
const bjHandleSelectionChange = (rows: CriticalComponentVO[]) => {
bjSelectedIds.value = rows.map((r) => r.id).filter((id): id is number => typeof id === 'number')
// 鑾峰彇褰撳墠椤垫墍鏈夎锟?id
const currentPageIds = rows.map((item) => item.id)
// 浠庡凡閫変腑鐨勬暟缁勪腑绉婚櫎褰撳墠椤电殑鏁版嵁
bjSelectedRows.value = bjSelectedRows.value.filter((item) => !currentPageIds.includes(item.id))
// 娣诲姞褰撳墠椤垫柊閫変腑鐨勬暟锟? bjSelectedRows.value.push(...rows)
}
// 瀛樺偍褰撳墠宸查€変腑鐨勮
const bjHandleSelect = (selection: any[], row: any) => {
// 鍒ゆ柇鏄€変腑杩樻槸鍙栨秷閫変腑
const isSelected = selection.includes(row)
if (isSelected) {
// console.log(`锟?琛岃閫変腑: ID=${row.id}, Name=${row.name}`)
bjIds.value.push(row.id)
} else {
bjIds.value = bjIds.value.filter((item) => item !== row.id)
// console.log(`锟?琛岃鍙栨秷閫変腑: ID=${row.id}, Name=${row.name}`)
}
// 鏇存柊褰撳墠閫変腑鐘讹拷? bjCurrentSelectedRows.value = selection
}
const bjHandleSelectAll = (selection) => {
bjIds.value = selection?.map((row) => row.id).filter((id) => id !== undefined) ?? []
}
const getList = async () => {
loading.value = true
try {
const data = await CriticalComponentApi.getCriticalComponentPage(queryParams)
list.value = data.list
total.value = data.total
nextTick(() => {
toggleSelection()
})
} finally {
loading.value = false
}
}
/** 閰嶄欢鏌ヨ鍒楄〃 */
const bjGetList = async () => {
loading.value = true
try {
bjQueryParams.categoryType = 3
const data = await ProductApi.getProductPage(bjQueryParams)
bjList.value = data.list
bjTotal.value = data.total
beijianOptions.value = mergeSelectionOptions(beijianOptions.value, buildBeijianOptions(bjList.value))
nextTick(() => {
bjToggleSelection()
})
} finally {
loading.value = false
}
}
const toggleSelection = () => {
if (!multipleTableRef.value || !selectedRows.value.length) return
// 閬嶅巻褰撳墠椤电殑鏁版嵁
list.value.forEach((row) => {
const isSelected = selectedRows.value.some((item) => item.id === row.id)
if (isSelected) {
// 濡傛灉搴旇閫変腑锛屽氨閫変腑
multipleTableRef.value!.toggleRowSelection(row, true)
} else {
// 鍚﹀垯鍙栨秷閫変腑
multipleTableRef.value!.toggleRowSelection(row, false)
}
})
}
const bjToggleSelection = () => {
if (!bjMultipleTableRef.value || !bjSelectedRows.value.length) return
// 閬嶅巻褰撳墠椤电殑鏁版嵁
bjList.value.forEach((row) => {
const isSelected = bjSelectedRows.value.some((item) => item.id === row.id)
if (isSelected) {
// 濡傛灉搴旇閫変腑锛屽氨閫変腑
bjMultipleTableRef.value!.toggleRowSelection(row, true)
} else {
// 鍚﹀垯鍙栨秷閫変腑
bjMultipleTableRef.value!.toggleRowSelection(row, false)
}
})
}
const selectedDeviceRows = ref<any[]>([])
const ids = ref<number[]>([])
const bjIds = ref<number[]>([])
const openCriticalComponentDialog = () => {
queryParams.name = ''
queryParams.description = ''
queryParams.code = ''
const rows = selectedDeviceRows.value.map((item) => ({ ...item, id: Number(item.id) }))
criticalComponentSelectDialogRef.value?.open(rows)
let initIds =
formData.value.componentIds != undefined
? formData.value.componentIds.toString().split(',')
: []
ids.value = initIds.map((id) => Number(id))
}
const openBeijianDialog = () => {
beijianDraft.value = [...(formData.value.beijianIds ?? [])]
beijianDialogVisible.value = true
bjIds.value = [...(formData.value.beijianIds ?? [])]
setBJDefaultSelections()
}
// 璁剧疆榛樿閫変腑鐨勮
const setDefaultSelections = () => {
// 绛夊緟DOM鏇存柊瀹屾垚
nextTick(() => {
if (!multipleTableRef.value) return
multipleTableRef.value.clearSelection()
const rawSubjectIds = toRaw(formData.value.componentIds)
if (rawSubjectIds.length != 0) {
let row = {
id: undefined
}
multipleTableRef.value!.toggleRowSelection(row, true)
}
// 閬嶅巻鏁版嵁锛屾壘鍒伴渶瑕侀€変腑鐨勮
list.value.forEach((row) => {
if (rawSubjectIds.includes(row.id)) {
multipleTableRef.value!.toggleRowSelection(row, true)
}
})
})
}
const normalizeNumberish = (value: any): number | undefined => {
if (value === null || value === undefined || value === '') return undefined
if (typeof value === 'number') return Number.isFinite(value) ? value : undefined
if (typeof value === 'string') {
const trimmed = value.trim()
if (!trimmed) return undefined
const n = Number(trimmed)
return Number.isFinite(n) ? n : undefined
}
return undefined
}
const normalizeYmd = (value: any): string | undefined => {
if (value === null || value === undefined || value === '') return undefined
if (typeof value === 'string') {
const trimmed = value.trim()
const matched = trimmed.match(/^(\d{4}-\d{2}-\d{2})/)
if (matched?.[1]) return matched[1]
const parsed = Date.parse(trimmed)
if (!Number.isNaN(parsed)) return formatDate(new Date(parsed), 'YYYY-MM-DD')
return trimmed
}
if (typeof value === 'number') return formatDate(new Date(value), 'YYYY-MM-DD')
if (value instanceof Date) return formatDate(value, 'YYYY-MM-DD')
return formatDate(new Date(value), 'YYYY-MM-DD')
}
const initFormData = () => ({
id: undefined,
images: undefined,
deviceCode: undefined,
isCode: true,
deviceName: undefined,
deviceStatus: undefined,
deviceBrand: undefined,
deviceModel: undefined,
deviceSpec: undefined,
isScheduled: 0,
ratedCapacity: undefined,
deviceType: undefined as string | undefined,
deviceLine: undefined as string | undefined,
supplier: undefined,
workshop: undefined,
deviceLocation: undefined,
systemOrg: undefined,
deviceManagerIds: [] as number[],
productionDate: undefined,
factoryEntryDate: undefined,
sn: undefined,
outgoingTime: undefined,
remark: undefined,
componentIds: [] as number[],
beijianIds: [] as number[],
fileUrl: '',
qrcodeUrl: undefined,
templateJson: undefined,
sort: undefined,
dvId: undefined
})
const formLoading = ref(false)
const fileUploading = ref(false)
const formType = ref('update')
const formRef = ref()
const materialFileTypes = [
'png',
'jpg',
'jpeg',
'webp',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'pdf',
'zip',
'rar',
'7z',
'dwg',
'dxf',
'mp4',
'avi',
'mov',
'wmv',
'flv',
'mkv',
'webm'
]
const formData = ref<any>({
...initFormData()
})
const isScheduledEnabled = computed(() => Number(formData.value.isScheduled) === 1)
const validateDeviceCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('EquipmentManagement.EquipmentLedger.validatorDeviceCodeRequired')))
return
}
callback()
}
const validateScheduledRequired = (label: string) => (_rule, value, callback) => {
if (!isScheduledEnabled.value) {
callback()
return
}
const normalized = normalizeNumberish(value)
if (normalized === undefined) {
callback(new Error(String(label)))
return
}
callback()
}
const formRules = reactive<FormRules>({
deviceCode: [{ validator: validateDeviceCode, trigger: ['blur', 'change'] }],
deviceName: [
{
required: true,
message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceName'),
trigger: 'blur'
}
],
deviceType: [
{
required: true,
message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceType'),
trigger: 'change'
}
],
ratedCapacity: [
{ validator: validateScheduledRequired('ratedCapacity'), trigger: ['blur', 'change'] }
]
})
const treeSelectProps = { label: 'name', children: 'children' }
const deviceTypeTree = ref<DeviceTypeTreeVO[]>([])
const users = ref<UserVO[]>([])
type SelectionOption = { label: string; value: number }
const criticalComponentOptions = ref<SelectionOption[]>([])
const beijianOptions = ref<SelectionOption[]>([])
const savedCriticalComponentOptions = ref<SelectionOption[]>([])
const savedBeijianOptions = ref<SelectionOption[]>([])
const editableCriticalComponentRows = ref<any[]>([])
const getCriticalComponentId = (item: any): number | undefined =>
normalizeNumberish(
item?.componentId ??
item?.criticalComponentId ??
item?.criticalId ??
item?.criticalComponent?.id ??
item?.component?.id ??
item?.id
)
const buildCriticalComponentOptions = (items: any[] = []): SelectionOption[] =>
(items ?? [])
.map((item: any) => {
const id = normalizeNumberish(item?.id)
if (id === undefined) return undefined
const code = item.code ? String(item.code) : ''
const name = item.name ? String(item.name) : ''
const label = code && name ? `${code}-${name}` : name || code || String(id)
return { label, value: id }
})
.filter((item): item is SelectionOption => Boolean(item))
const buildBeijianOptions = (items: any[] = []): SelectionOption[] =>
(items ?? [])
.map((item: any) => {
const id = normalizeNumberish(item?.id)
if (id === undefined) return undefined
const code = item.barCode ? String(item.barCode) : ''
const name = item.name ? String(item.name) : ''
const label = code && name ? `${code}-${name}` : name || code || String(id)
return { label, value: id }
})
.filter((item): item is SelectionOption => Boolean(item))
const mergeSelectionOptions = (...groups: SelectionOption[][]): SelectionOption[] => {
const optionMap = new Map<number, SelectionOption>()
groups.flat().forEach((item) => optionMap.set(item.value, item))
return Array.from(optionMap.values())
}
const formatSelectedSummary = (ids: number[], options: SelectionOption[]) => {
const optionMap = new Map<number, string>(options.map((item) => [item.value, item.label]))
const labels = ids.map((id) => optionMap.get(id)).filter((v): v is string => Boolean(v))
if (!labels.length) return ''
if (labels.length <= 3) return labels.join(', ')
return `${labels.slice(0, 3).join(', ')}...${labels.length}`
}
const criticalComponentDisplay = computed(() =>
/* formatSelectedSummary(
formData.value.componentIds ?? [],
mergeSelectionOptions(criticalComponentOptions.value, savedCriticalComponentOptions.value)
)*/
{
if (!ids.value.length) return ''
if (!list.value.length) {
return formData.value.machineId ? String(formData.value.machineId) : ''
}
const map = new Map(list.value.map((item) => [item.id, item.name]))
const names = ids.value.map((id) => map.get(id)).filter((name) => name)
return names.join(',')
}
)
const beijianDisplay = computed(() =>
formatSelectedSummary(
formData.value.beijianIds ?? [],
mergeSelectionOptions(beijianOptions.value, savedBeijianOptions.value)
)
)
const buildCriticalComponentTableRows = () => {
const detailList = (detailData.value as any)?.componentList
if (Array.isArray(detailList) && detailList.length) {
/* detailList.forEach(item => {
item.id = null
})*/
return detailList.map((item: any) => ({ ...item, count: normalizeNumberish(item?.count) ?? 0 }))
}
const selectedIds = new Set<number>(
(formData.value.componentIds ?? [])
.map((id: any) => Number(id))
.filter((id: number) => !Number.isNaN(id))
)
if (!selectedIds.size) return []
const optionMap = new Map<number, any>()
;[...criticalComponentOptions.value, ...savedCriticalComponentOptions.value].forEach(
(item: any) => {
optionMap.set(Number(item.value), item)
}
)
return Array.from(selectedIds).map((id) => {
const option = optionMap.get(id)
const label = option?.label ?? String(id)
const [code, ...nameParts] = label.split('-')
return {
id,
code: nameParts.length ? code : '',
name: nameParts.length ? nameParts.join('-') : label,
count: 0,
description: '',
remark: '',
createTime: undefined
}
})
}
const syncCriticalComponentRows = () => {
editableCriticalComponentRows.value = buildCriticalComponentTableRows()
}
const clearCriticalComponent = () => {
formData.value.componentIds = []
}
const clearBeijian = () => {
formData.value.beijianIds = []
}
const buildIotDeviceLabel = (item: any) => {
if (!item) return ''
const code = item.deviceCode ? String(item.deviceCode) : ''
const name = item.deviceName ? String(item.deviceName) : ''
return code && name ? `${code}-${name}` : name || code || (item.id ? String(item.id) : '')
}
const getIotDeviceIdFromDetail = (detail: any) =>
normalizeNumberish(
detail?.dvId ?? detail?.iotDeviceId ?? detail?.iotDevice?.id ?? detail?.device?.id
)
const getIotDeviceLabelFromDetail = (detail: any) => {
const directLabel =
detail?.dvName ??
detail?.iotDeviceName ??
detail?.iotDeviceLabel ??
detail?.iotDevice?.deviceName ??
detail?.device?.deviceName
if (directLabel) return String(directLabel)
const code = detail?.iotDeviceCode ?? detail?.iotDevice?.deviceCode ?? detail?.device?.deviceCode
const name = detail?.iotDeviceName ?? detail?.iotDevice?.deviceName ?? detail?.device?.deviceName
return buildIotDeviceLabel({
id: getIotDeviceIdFromDetail(detail),
deviceCode: code,
deviceName: name
})
}
const iotDeviceDisplay = computed(() => iotDeviceSelectedLabel.value)
const isIotDeviceSelectable = (row: any) => {
const value = row?.selectable
return value !== false && String(value ?? 'true').toLowerCase() !== 'false'
}
const isIotDeviceEnabled = (row: any) => {
const value = row?.isEnable
if (typeof value === 'boolean') return value
const text = String(value ?? '')
.toLowerCase()
.trim()
return text === 'true' || text === '1' || text === 'yes'
}
const selectIotDevice = (row: DeviceVO) => {
if (!isIotDeviceSelectable(row)) return
const id = normalizeNumberish(row?.id)
if (id === undefined) return
iotDeviceDraftId.value = id
iotDeviceDraftRow.value = row
}
const handleIotDeviceRowClick = (row: DeviceVO) => {
selectIotDevice(row)
}
const getIotDeviceRowClassName = ({ row }: { row: DeviceVO }) => {
return isIotDeviceSelectable(row) ? '' : 'is-not-selectable'
}
const openIotDeviceDialog = async () => {
const id = normalizeNumberish(formData.value.dvId)
iotDeviceDraftId.value = id
iotDeviceDraftRow.value =
id === undefined ? undefined : ({ id, deviceName: iotDeviceSelectedLabel.value } as DeviceVO)
iotDeviceDialogVisible.value = true
await getIotDeviceList()
}
const clearIotDevice = () => {
formData.value.dvId = undefined
delete formData.value.iotDeviceCode
delete formData.value.iotDeviceName
delete formData.value.iotDeviceLabel
delete formData.value.iotDevice
delete formData.value.device
iotDeviceSelectedLabel.value = ''
iotDeviceDraftId.value = undefined
iotDeviceDraftRow.value = undefined
formRef.value?.clearValidate?.(['dvId'])
}
const confirmIotDeviceDialog = () => {
if (iotDeviceDraftId.value === undefined || !iotDeviceDraftRow.value) {
message.warning(t('EquipmentManagement.EquipmentLedger.dvId'))
return
}
if (!isIotDeviceSelectable(iotDeviceDraftRow.value)) {
message.warning('该物联设备不可选择')
return
}
formData.value.dvId = iotDeviceDraftId.value
iotDeviceSelectedLabel.value = buildIotDeviceLabel(iotDeviceDraftRow.value)
iotDeviceDialogVisible.value = false
formRef.value?.clearValidate?.(['dvId'])
}
const hydrateIotDeviceFromDetail = async (detail: any) => {
const id = getIotDeviceIdFromDetail(detail)
formData.value.dvId = id
if (id === undefined) {
iotDeviceSelectedLabel.value = ''
return
}
const label = getIotDeviceLabelFromDetail(detail)
if (label) {
iotDeviceSelectedLabel.value = label
return
}
try {
const device = await DeviceApi.getDevice(id)
iotDeviceSelectedLabel.value = buildIotDeviceLabel(device)
} catch {
iotDeviceSelectedLabel.value = String(id)
}
}
const getIotDeviceList = async () => {
iotDeviceLoading.value = true
try {
const data = await DeviceApi.getDevicePage(iotDeviceQueryParams)
iotDeviceList.value = (data?.list ?? []) as DeviceVO[]
iotDeviceTotal.value = data?.total ?? 0
} finally {
iotDeviceLoading.value = false
}
}
const handleIotDeviceQuery = () => {
iotDeviceQueryParams.pageNo = 1
getIotDeviceList()
}
const resetIotDeviceQuery = () => {
iotDeviceQueryFormRef.value?.resetFields?.()
handleIotDeviceQuery()
}
const handleFileUploadingChange = (uploading: boolean) => {
fileUploading.value = uploading
}
watch(
() => formData.value.isScheduled,
() => {
formRef.value?.clearValidate?.(['ratedCapacity'])
}
)
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.deviceCode = undefined
}
formRef.value?.clearValidate('deviceCode')
}
const getQrcodeRefreshUrl = () => {
if (!formData.value.id || !formData.value.deviceCode) return ''
return `/mes/device-ledger/regenerate-code?id=${formData.value.id}&code=${encodeURIComponent(String(formData.value.deviceCode))}`
}
const buildPrintData = () => {
return {
id: formData.value.id,
deviceCode: formData.value.deviceCode,
deviceName: formData.value.deviceName,
deviceSpec: formData.value.deviceSpec,
deviceBrand: formData.value.deviceBrand,
deviceModel: formData.value.deviceModel,
sn: formData.value.sn,
outgoingTime: formData.value.outgoingTime,
deviceLocation: formData.value.deviceLocation,
remark: formData.value.remark,
qrcodeUrl: formData.value.qrcodeUrl
}
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!formData.value.id) return
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
return
}
const detail = await DeviceLedgerApi.getDeviceLedger(formData.value.id)
formData.value.qrcodeUrl = detail?.qrcodeUrl
formData.value.deviceCode = detail?.deviceCode ?? formData.value.deviceCode
}
const ensureOptionsLoaded = async () => {
const [deviceTypeRes, userRes, criticalRes, beijianRes] = await Promise.all([
DeviceTypeApi.getDeviceTypeTree({ pageNo: 1, pageSize: 10 }),
getSimpleUserList(),
CriticalComponentApi.getCriticalComponentList(),
ProductApi.getProductPage({ pageNo: 1, pageSize: 10, categoryType: 3 })
])
deviceTypeTree.value = deviceTypeRes ?? []
users.value = userRes ?? []
criticalComponentOptions.value = buildCriticalComponentOptions(criticalRes ?? [])
beijianOptions.value = buildBeijianOptions(beijianRes?.list ?? [])
}
const bindFormData = async (detail: DeviceLedgerVO) => {
const templateJson = (detail as any)?.templateJson
const parsedTemplateJson =
typeof templateJson === 'string'
? (() => {
try {
return JSON.parse(templateJson)
} catch {
return undefined
}
})()
: templateJson
formData.value = {
...initFormData(),
...(detail as any),
templateJson: parsedTemplateJson,
isCode: (detail as any)?.isCode ?? false,
isScheduled: normalizeNumberish((detail as any)?.isScheduled) ?? 0,
ratedCapacity: normalizeNumberish((detail as any)?.ratedCapacity),
deviceType: (detail as any)?.deviceType ?? undefined,
deviceLine: (detail as any)?.deviceLine ?? undefined,
deviceManagerIds: parseIdsValue((detail as any)?.deviceManager),
productionDate: normalizeYmd((detail as any)?.productionDate),
outgoingTime: normalizeYmd((detail as any)?.outgoingTime),
factoryEntryDate: normalizeYmd((detail as any)?.factoryEntryDate),
componentIds: parseIdsValue((detail as any)?.componentId),
beijianIds: parseIdsValue((detail as any)?.beijianId),
qrcodeUrl: (detail as any)?.qrcodeUrl
}
ids.value = parseIdsValue((detail as any)?.componentId)
savedCriticalComponentOptions.value = buildCriticalComponentOptions(
(detail as any)?.componentList ?? []
)
savedBeijianOptions.value = buildBeijianOptions((detail as any)?.beijianList ?? [])
await hydrateIotDeviceFromDetail(detail as any)
syncCriticalComponentRows()
}
const criticalExportLoading = ref(false)
const normalizeFileUrlAsJsonArrayString = (value: any): string | undefined => {
if (value === null || value === undefined || value === '') return undefined
if (typeof value === 'string') return value.trim() || undefined
if (Array.isArray(value)) return JSON.stringify(value)
return JSON.stringify(value)
}
const buildSubmitData = () => {
const componentRows = editableCriticalComponentRows.value
.map((row: any) => ({
...row,
id: normalizeNumberish(row?.id),
count: normalizeNumberish(row?.count) ?? 0
}))
.filter((row: any) => row.id !== undefined)
formData.value.componentIds = componentRows.map((row: any) => row.id)
const data = {
...(formData.value as any),
isScheduled: normalizeNumberish((formData.value as any).isScheduled) ?? 0,
ratedCapacity: normalizeNumberish((formData.value as any).ratedCapacity),
deviceType: (formData.value as any).deviceType ?? undefined,
deviceLine: (formData.value as any).deviceLine ?? undefined,
productionDate: normalizeYmd(formData.value.productionDate),
outgoingTime: normalizeYmd((formData.value as any).outgoingTime),
factoryEntryDate: normalizeYmd(formData.value.factoryEntryDate),
deviceManager: formData.value.deviceManagerIds?.length
? formData.value.deviceManagerIds.join(',')
: undefined,
componentId: formData.value.componentIds?.length
? formData.value.componentIds.join(',')
: undefined,
componentList: componentRows,
beijianId: formData.value.beijianIds?.length ? formData.value.beijianIds.join(',') : undefined,
fileUrl: normalizeFileUrlAsJsonArrayString((formData.value as any).fileUrl)
} as unknown as DeviceLedgerVO
delete (data as any).deviceManagerIds
delete (data as any).componentIds
delete (data as any).beijianIds
delete (data as any).iotDeviceCode
delete (data as any).iotDeviceName
delete (data as any).iotDeviceLabel
delete (data as any).iotDevice
delete (data as any).device
return data
}
const submitForm = async () => {
if (fileUploading.value) {
message.warning(t('common.loading'))
return
}
await formRef.value?.validate?.()
formLoading.value = true
try {
await DeviceLedgerApi.updateDeviceLedger(buildSubmitData())
message.success(t('common.updateSuccess'))
await getDetail()
getDetailList()
} finally {
formLoading.value = false
}
}
const handleCancel = async () => {
router.back()
}
const handleExportCriticalComponent = async () => {
if (!deviceId.value) return
try {
await message.exportConfirm()
criticalExportLoading.value = true
const data = await DeviceLedgerApi.exportDeviceComponent({ deviceId: deviceId.value })
download.excel(data, 'critical-component.xls')
} catch {
} finally {
criticalExportLoading.value = false
}
}
const getDetail = async () => {
if (!deviceId.value) {
message.warning(t('EquipmentManagement.EquipmentLedger.Detail.invalidId'))
delView(unref(currentRoute))
return
}
detailLoading.value = true
try {
const detail = await DeviceLedgerApi.getDeviceLedger(deviceId.value)
detailData.value = detail
await bindFormData(detail)
} finally {
detailLoading.value = false
}
}
watch(
bjList,
(newData) => {
if (newData.length > 0) {
setBJDefaultSelections()
}
},
{ deep: true }
)
//const defaultSelectedIds = [144, 143,141]
// 璁剧疆榛樿閫変腑鐨勮
const setBJDefaultSelections = () => {
// 绛夊緟DOM鏇存柊瀹屾垚
nextTick(() => {
if (!bjMultipleTableRef.value) return
bjMultipleTableRef.value.clearSelection()
const rawSubjectIds = toRaw(formData.value.beijianIds)
if (rawSubjectIds.length != 0) {
let row = {
id: undefined
}
bjMultipleTableRef.value!.toggleRowSelection(row, true)
}
// 閬嶅巻鏁版嵁锛屾壘鍒伴渶瑕侀€変腑鐨勮
bjList.value.forEach((row) => {
if (rawSubjectIds.includes(row.id)) {
bjMultipleTableRef.value!.toggleRowSelection(row, true)
}
})
})
}
async function initForm() {
// 淇敼鏃讹紝璁剧疆鏁版嵁
if (deviceId.value) {
formLoading.value = true
try {
const detail = await DeviceLedgerApi.getDeviceLedger(deviceId.value)
detailData.value = detail
await bindFormData(detail)
} finally {
formLoading.value = false
}
}
}
async function getDetailList() {
if (!deviceId.value) return
detailLoading.value = true
try {
let detail: any = await DeviceLedgerApi.getDeviceCriticalComponent(deviceId.value)
editableCriticalComponentRows.value = detail
} finally {
detailLoading.value = false
}
}
const formatCriticalComponentCreateTime = (value: any) => {
if (value === null || value === undefined || value === '') return ''
const isTimestamp = typeof value === 'number' || /^\d+$/.test(String(value))
const timestamp = isTimestamp ? Number(value) : undefined
const date =
timestamp !== undefined
? new Date(timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp)
: new Date(value)
return Number.isNaN(date.getTime()) ? String(value) : formatDate(date)
}
const criticalComponentColumns = computed(() => [
{ label: t('EquipmentManagement.EquipmentKeyItems.code'), prop: 'code', minWidth: 140 },
{ label: t('EquipmentManagement.EquipmentKeyItems.name'), prop: 'name', minWidth: 140 },
{
label: t('EquipmentManagement.EquipmentKeyItems.description'),
prop: 'description',
minWidth: 180
},
{ label: t('EquipmentManagement.EquipmentKeyItems.remark'), prop: 'remark', minWidth: 180 },
{
label: t('EquipmentManagement.EquipmentKeyItems.createTime'),
slotName: 'createTime',
width: 180
}
])
const fetchCriticalComponentPage = async (params: Record<string, any>) => {
return await CriticalComponentApi.getCriticalComponentPage(params)
}
const handleCriticalComponentSelectConfirm = (payload: {
ids: (number | string)[]
rows: any[]
}) => {
formData.value.devices = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.deviceName || item.name || item.code || `璁惧ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedDeviceRows.value = payload.rows
formData.value.componentIds = payload.ids.join(',')
ids.value = payload.ids.map((id) => Number(id))
appendSelectedCriticalComponentRows(ids.value)
}
const buildCriticalComponentRowById = (id: number) => {
const row = selectedRows.value.find((item: any) => normalizeNumberish(item.id) === id)
if (row) {
return {
...row,
id,
count: normalizeNumberish(row?.count) ?? 0
}
}
const option = mergeSelectionOptions(
savedCriticalComponentOptions.value,
criticalComponentOptions.value
).find((item) => item.value === id)
const label = option?.label ?? String(id)
const [code, ...nameParts] = label.split('-')
return {
id,
code: nameParts.length ? code : '',
name: nameParts.length ? nameParts.join('-') : label,
count: 0,
description: '',
remark: '',
createTime: undefined
}
}
const appendSelectedCriticalComponentRows = (selectedIds: number[]) => {
const existingIds = new Set(
editableCriticalComponentRows.value
.map((row: any) => getCriticalComponentId(row))
.filter((id): id is number => id !== undefined)
)
const rowsToAppend = selectedIds
.filter((id) => !existingIds.has(id))
.map((id) => buildCriticalComponentRowById(id))
if (rowsToAppend.length) {
editableCriticalComponentRows.value = [...editableCriticalComponentRows.value, ...rowsToAppend]
}
}
/** 鍒犻櫎鎸夐挳鎿嶄綔 */
const buildIdsParam = (ids: number | number[]) => {
return Array.isArray(ids) ? ids.join(',') : String(ids)
}
const handleDelete = async (id: string) => {
try {
// 鍒犻櫎鐨勪簩娆$‘璁?
await message.delConfirm()
editableCriticalComponentRows.value = editableCriticalComponentRows.value.filter(
(item: any) => item.id !== id
)
await DeviceLedgerApi.deleteDeviceCriticalComponent(id)
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()
// 鍒锋柊鍒楄〃
//await getList()
} catch {}
}
onMounted(async () => {
await ensureOptionsLoaded()
initForm()
//鍒濆鍖栬澶囧叧閿欢
getList()
//鍒濆鍖栧浠?
bjGetList()
//鍒濆鍖栬澶囧叧閿欢鏄庣粏
getDetailList()
await nextTick()
updateActionBarRect()
window.addEventListener('resize', updateActionBarRect)
if (detailPageRef.value) {
actionBarObserver = new ResizeObserver(updateActionBarRect)
actionBarObserver.observe(detailPageRef.value)
}
})
watch(() => appStore.getCollapse, updateActionBarRectAfterLayout)
onBeforeUnmount(() => {
window.removeEventListener('resize', updateActionBarRect)
actionBarObserver?.disconnect()
})
</script>
<style lang="scss" scoped>
.device-ledger-detail-page {
width: 100%;
}
.device-ledger-detail-body {
position: relative;
min-height: 100vh;
padding: 2px 0 72px;
}
.device-ledger-detail-title {
position: relative;
display: flex;
align-items: center;
height: 28px;
color: var(--el-text-color-primary);
font-size: 16px;
font-weight: 600;
line-height: 22px;
}
.device-ledger-detail-title::before {
width: 3px;
height: 16px;
margin-right: 8px;
content: '';
border-radius: 2px;
background: var(--el-color-primary);
}
.device-ledger-detail-main {
min-height: 224px;
padding-top: 12px;
margin-bottom: 10px;
gap: 28px;
box-sizing: border-box;
}
.device-ledger-detail-main :deep(.el-form) {
width: 100%;
}
.device-basic-layout {
align-items: flex-start;
}
.device-basic-fields {
min-width: 0;
}
.device-code-row {
width: 100%;
}
.device-code-switch {
display: flex;
align-items: center;
height: 32px;
}
.device-basic-media {
display: flex;
justify-content: flex-end;
}
.device-media-panel {
width: 100%;
max-width: 260px;
padding: 12px 12px 4px;
box-sizing: border-box;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
background: var(--el-fill-color-extra-light);
}
.device-media-item {
margin-bottom: 16px;
}
.device-media-item :deep(.el-form-item__label) {
justify-content: flex-start;
}
.device-media-item :deep(.el-form-item__content) {
justify-content: center;
}
.device-media-upload {
width: 100%;
min-height: 120px;
}
.device-media-code {
width: 148px;
height: 148px;
}
.device-media-code :deep(.qrcode-action-card__img),
.device-media-code :deep(.qrcode-action-card__error) {
width: 148px;
height: 148px;
}
.device-ledger-detail-tabs {
padding-right: 4px;
}
:deep(.el-tabs__content) {
max-height: 65vh;
overflow-y: auto;
}
.device-ledger-tab-toolbar {
margin-bottom: 8px;
text-align: right;
}
.device-ledger-selection-input :deep(.el-input__inner) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.device-ledger-action-bar {
position: fixed;
bottom: 0;
z-index: 10;
display: flex;
justify-content: center;
gap: 12px;
padding: 12px 16px;
box-sizing: border-box;
border-top: 1px solid var(--el-border-color-lighter);
background: var(--el-bg-color);
box-shadow: 0 -4px 12px rgb(0 0 0 / 6%);
}
@media (max-width: 1199px) {
.device-basic-media {
justify-content: flex-start;
}
.device-media-panel {
max-width: none;
}
}
@media (max-width: 767px) {
.device-basic-fields :deep(.el-col) {
max-width: 100%;
flex: 0 0 100%;
}
.device-code-row :deep(.el-col:first-child) {
max-width: 75%;
flex: 0 0 75%;
}
.device-code-row :deep(.el-col:last-child) {
max-width: 25%;
flex: 0 0 25%;
}
}
</style>