Compare commits

...

12 Commits

@ -0,0 +1,28 @@
import request from '@/config/axios'
// 实时报警记录 VO
export interface DeviceWarningRecordVO {
createTime: string
updateTime: string
creator: string
updater: string
deleted: boolean
id: number
deviceId: number
modelId: number
rule: string
alarmLevel: string
addressValue: string
ruleId: number
deviceName: string
modelName: string
ruleName: string
}
// 实时报警记录 API
export const DeviceWarningRecordApi = {
// 获得实时报警记录列表
getList: async () => {
return await request.get({ url: `/iot/device-warinning-record/getList` })
}
}

@ -58,6 +58,11 @@ export const EnergyDeviceApi = {
return await request.get({ url: `/mes/energy-device/getList`, params })
},
// 获取最近的能源统计数据
getLastEnergyStatistics: async (params: any) => {
return await request.get({ url: `/mes/energy-device/lastEnergyStatistics`, params })
},
queryDataRecords: async (params: any) => {
return await request.get({ url: `/mes/energy-device/queryDataRecords`, params })
},
@ -110,5 +115,10 @@ export const EnergyDeviceApi = {
// 获得抄表记录
getEnergyDeviceCheckRecord: async (id: number) => {
return await request.get({ url: `/mes/energy-device/energy-device-check-record/get?id=` + id })
},
// 获取最后一次能耗统计
getLastEnergyStatistics: async (params: any) => {
return await request.get({ url: `/mes/energy-device/lastEnergyStatistics`, params })
}
}

@ -165,7 +165,7 @@ service.interceptors.response.use(
'<div>5 分钟搭建本地环境</div>'
})
return Promise.reject(new Error(msg))
} else if (code !== 200) {
} else if (code !== 200 && code !== 0) {
if (msg === '无效的刷新令牌') {
// hard coding忽略这个提示直接登出
console.log(msg)

@ -1583,5 +1583,198 @@ export default {
updateSuccess: 'Update successful',
updateFail: 'Update failed'
}
},
RecipeManagement: {
RecipeType: {
moduleName: 'Recipe Type',
searchNameLabel: 'Name',
searchNamePlaceholder: 'Please enter name',
searchProcessLabel: 'Process',
searchProcessPlaceholder: 'Please select process',
searchButtonText: 'Search',
resetButtonText: 'Reset',
createButtonText: 'Add',
exportButtonText: 'Export',
tableNameColumn: 'Name',
tableProcessColumn: 'Process',
tableRemarkColumn: 'Remark',
tableOperateColumn: 'Operation',
tableEditAction: 'Edit',
tableDeleteAction: 'Delete',
dialogCreateTitle: 'Create Recipe Type',
dialogEditTitle: 'Edit Recipe Type',
dialogNameLabel: 'Name',
dialogNamePlaceholder: 'Please enter name',
dialogProcessLabel: 'Process',
dialogProcessPlaceholder: 'Please select process',
dialogRemarkLabel: 'Remark',
dialogRemarkPlaceholder: 'Please enter remark',
dialogCancelButton: 'Cancel',
dialogSaveButton: 'Save',
validatorNameRequired: 'Name cannot be empty',
validatorProcessRequired: 'Process cannot be empty',
exportNoSelectionMessage: 'Please select data to export',
exportFileName: 'RecipeType.xls'
},
RecipeConfig: {
moduleName: 'Recipe Config',
searchRecipeCodeLabel: 'Recipe Code',
searchRecipeCodePlaceholder: 'Please enter recipe code',
searchNameLabel: 'Recipe Name',
searchNamePlaceholder: 'Please enter recipe name',
searchProductNameLabel: 'Product Name',
searchProductNamePlaceholder: 'Please enter product name',
searchButtonText: 'Search',
resetButtonText: 'Reset',
createButtonText: 'Add',
exportButtonText: 'Export',
tableRecipeCodeColumn: 'Recipe Code',
tableNameColumn: 'Recipe Name',
tableRecipeTypeColumn: 'Recipe Type',
tableProductNameColumn: 'Related Product',
tableMachineNameColumn: 'Related Device',
tableRecipeDescColumn: 'Recipe Description',
tableOperateColumn: 'Operation',
tableConfigAction: 'Config',
tableEditAction: 'Edit',
tableDeleteAction: 'Delete',
dialogCreateTitle: 'Create Recipe Config',
dialogEditTitle: 'Edit Recipe Config',
dialogRecipeCodeLabel: 'Recipe Code',
dialogRecipeCodePlaceholder: 'Please enter recipe code',
dialogNameLabel: 'Recipe Name',
dialogNamePlaceholder: 'Please enter recipe name',
dialogRecipeTypeLabel: 'Recipe Type',
dialogRecipeTypePlaceholder: 'Please select recipe type',
dialogProductNameLabel: 'Related Product',
dialogProductNamePlaceholder: 'Please select related product',
dialogMachineNameLabel: 'Related Device',
dialogMachineNamePlaceholder: 'Please select related device',
dialogRecipeDescLabel: 'Recipe Description',
dialogRecipeDescPlaceholder: 'Please enter recipe description',
dialogCancelButton: 'Cancel',
dialogSaveButton: 'Save',
validatorRecipeCodeRequired: 'Recipe code cannot be empty',
validatorNameRequired: 'Recipe name cannot be empty',
exportNoSelectionMessage: 'Please select data to export',
exportFileName: 'RecipeConfig.xls',
detailCollapseButton: 'Collapse',
detailTabDetailLabel: 'Detail',
detailTabManualLabel: 'Manual Parameters',
detailConfigButton: 'Config',
detailIndexColumn: 'Index',
detailPointNameColumn: 'Point Name',
detailPointTypeColumn: 'Point Type',
detailDataTypeColumn: 'Data Type',
detailDataUnitColumn: 'Unit',
manualCreateButton: 'Add',
manualTableNameColumn: 'Name',
manualTableMaxColumn: 'Upper Limit',
manualTableMinColumn: 'Lower Limit',
manualTableDataUnitColumn: 'Unit',
manualTableRemarkColumn: 'Remark',
manualTableOperateColumn: 'Operation',
manualTableEditAction: 'Edit',
manualTableDeleteAction: 'Delete',
manualDialogCreateTitle: 'Create Parameter',
manualDialogEditTitle: 'Edit Parameter',
manualDialogNameLabel: 'Name',
manualDialogNamePlaceholder: 'Please enter name',
manualDialogMaxLabel: 'Upper Limit',
manualDialogMaxPlaceholder: 'Please enter upper limit',
manualDialogMinLabel: 'Lower Limit',
manualDialogMinPlaceholder: 'Please enter lower limit',
manualDialogDataTypeLabel: 'Data Type',
manualDialogDataTypePlaceholder: 'Please select data type',
manualDialogDataUnitLabel: 'Unit',
manualDialogDataUnitPlaceholder: 'Please select unit',
manualDialogRemarkLabel: 'Remark',
manualDialogRemarkPlaceholder: 'Please enter remark',
manualDialogCancelButton: 'Cancel',
manualDialogSaveButton: 'Save',
manualValidatorNameRequired: 'Name cannot be empty',
manualValidatorMaxRequired: 'Upper limit cannot be empty',
manualValidatorMinRequired: 'Lower limit cannot be empty',
manualValidatorDataTypeRequired: 'Data type cannot be empty',
manualValidatorDataUnitRequired: 'Unit cannot be empty',
configDialogTitle: 'Config',
configSourceTitle: 'Source',
configTargetTitle: 'Target',
configFilterPlaceholder: 'Filter',
configMoveToTargetButton: 'Add to Target',
configMoveToSourceButton: 'Move to Source',
configCancelButton: 'Cancel',
configSaveButton: 'Save',
configDeviceLabelFallback: '-',
configSourceCountLabel: 'Source Count',
configTargetCountLabel: 'Target Count'
},
RecipeLibrary: {
moduleName: 'Recipe Library',
searchCodeLabel: 'Code',
searchCodePlaceholder: 'Please enter code',
searchNameLabel: 'Name',
searchNamePlaceholder: 'Please enter name',
searchRecipeLabel: 'Recipe Name',
searchRecipePlaceholder: 'Please select recipe',
searchPlanLabel: 'Related Plan',
searchPlanPlaceholder: 'Please select plan',
searchButtonText: 'Search',
resetButtonText: 'Reset',
createButtonText: 'Add',
exportButtonText: 'Export',
tableCodeColumn: 'Code',
tableNameColumn: 'Name',
tableRecipeNameColumn: 'Recipe Name',
tablePlanCodeColumn: 'Related Plan',
tableCreatorColumn: 'Creator',
tableCreateTimeColumn: 'Create Time',
tableOperateColumn: 'Operation',
tableReadAction: 'Read',
tableEditAction: 'Edit',
tableDeleteAction: 'Delete',
dialogCodeLabel: 'Code',
dialogCodePlaceholder: 'Please enter code',
dialogNameLabel: 'Name',
dialogNamePlaceholder: 'Please enter name',
dialogRecipeLabel: 'Recipe',
dialogRecipePlaceholder: 'Please select recipe',
dialogPlanLabel: 'Related Plan',
dialogPlanPlaceholder: 'Please select plan',
dialogSourceLabel: 'Source',
dialogSourcePlaceholder: 'Please select source',
dialogCancelButton: 'Cancel',
dialogSaveButton: 'Save',
validatorCodeRequired: 'Code cannot be empty',
validatorNameRequired: 'Name cannot be empty',
validatorRecipeRequired: 'Related recipe cannot be empty',
validatorPlanRequired: 'Related plan cannot be empty',
exportFileName: 'RecipeLibrary.xls',
sourceOptionNewLabel: 'New',
sourceOptionProducingLabel: 'In Production',
readDialogTitle: 'Read',
readDialogNameColumn: 'Name',
readDialogDataTypeColumn: 'Data Type',
readDialogDataUnitColumn: 'Unit',
readDialogMaxColumn: 'Upper Limit',
readDialogMinColumn: 'Lower Limit',
readDialogRemarkColumn: 'Remark',
readDialogReferColumn: 'Collected Value',
readDialogReferPlaceholder: 'Please enter collected value',
readDialogSubmitButtonText: 'Read',
readDialogCancelButton: 'Cancel',
readDeviceConfirmMessage: 'Do you want to read device data?',
detailTabDeviceDataLabel: 'Device Data',
detailTabManualLabel: 'Manual Parameters',
detailDevicePointNameColumn: 'Point Name',
detailDeviceDataTypeColumn: 'Data Type',
detailDeviceDataUnitColumn: 'Unit',
detailDeviceValueColumn: 'Collected Value',
manualTableNameColumn: 'Name',
manualTableDataTypeColumn: 'Data Type',
manualTableDataUnitColumn: 'Unit',
manualTableReferColumn: 'Reference Value',
manualTableRemarkColumn: 'Remark'
}
}
}

@ -1579,5 +1579,198 @@ export default {
updateSuccess: '更新成功',
updateFail: '更新失败'
}
},
RecipeManagement: {
RecipeType: {
moduleName: '配方类型',
searchNameLabel: '名称',
searchNamePlaceholder: '请输入名称',
searchProcessLabel: '工序',
searchProcessPlaceholder: '请选择工序',
searchButtonText: '查询',
resetButtonText: '重置',
createButtonText: '新增',
exportButtonText: '导出',
tableNameColumn: '名称',
tableProcessColumn: '工序',
tableRemarkColumn: '备注',
tableOperateColumn: '操作',
tableEditAction: '编辑',
tableDeleteAction: '删除',
dialogCreateTitle: '新增配方类型',
dialogEditTitle: '编辑配方类型',
dialogNameLabel: '名称',
dialogNamePlaceholder: '请输入名称',
dialogProcessLabel: '工序',
dialogProcessPlaceholder: '请选择工序',
dialogRemarkLabel: '备注',
dialogRemarkPlaceholder: '请输入备注',
dialogCancelButton: '取 消',
dialogSaveButton: '保 存',
validatorNameRequired: '名称不能为空',
validatorProcessRequired: '工序不能为空',
exportNoSelectionMessage: '请选择需要导出的数据',
exportFileName: '配方类型.xls'
},
RecipeConfig: {
moduleName: '配方配置',
searchRecipeCodeLabel: '配方编码',
searchRecipeCodePlaceholder: '请输入配方编码',
searchNameLabel: '配方名称',
searchNamePlaceholder: '请输入配方名称',
searchProductNameLabel: '产品名称',
searchProductNamePlaceholder: '请输入产品名称',
searchButtonText: '查询',
resetButtonText: '重置',
createButtonText: '新增',
exportButtonText: '导出',
tableRecipeCodeColumn: '配方编码',
tableNameColumn: '配方名称',
tableRecipeTypeColumn: '配方类型',
tableProductNameColumn: '关联产品',
tableMachineNameColumn: '关联设备',
tableRecipeDescColumn: '配方描述',
tableOperateColumn: '操作',
tableConfigAction: '配置',
tableEditAction: '编辑',
tableDeleteAction: '删除',
dialogCreateTitle: '新增配方配置',
dialogEditTitle: '编辑配方配置',
dialogRecipeCodeLabel: '配方编码',
dialogRecipeCodePlaceholder: '请输入配方编码',
dialogNameLabel: '配方名称',
dialogNamePlaceholder: '请输入配方名称',
dialogRecipeTypeLabel: '配方类型',
dialogRecipeTypePlaceholder: '请选择配方类型',
dialogProductNameLabel: '关联产品',
dialogProductNamePlaceholder: '请选择关联产品',
dialogMachineNameLabel: '关联设备',
dialogMachineNamePlaceholder: '请选择关联设备',
dialogRecipeDescLabel: '配方描述',
dialogRecipeDescPlaceholder: '请输入配方描述',
dialogCancelButton: '取 消',
dialogSaveButton: '保 存',
validatorRecipeCodeRequired: '配方编码不能为空',
validatorNameRequired: '配方名称不能为空',
exportNoSelectionMessage: '请选择需要导出的数据',
exportFileName: '配方配置.xls',
detailCollapseButton: '收起',
detailTabDetailLabel: '详情',
detailTabManualLabel: '手动录入参数',
detailConfigButton: '配置',
detailIndexColumn: '序号',
detailPointNameColumn: '点位名称',
detailPointTypeColumn: '点位类型',
detailDataTypeColumn: '数据类型',
detailDataUnitColumn: '单位',
manualCreateButton: '新增',
manualTableNameColumn: '名称',
manualTableMaxColumn: '上限',
manualTableMinColumn: '下限',
manualTableDataUnitColumn: '单位',
manualTableRemarkColumn: '备注',
manualTableOperateColumn: '操作',
manualTableEditAction: '编辑',
manualTableDeleteAction: '删除',
manualDialogCreateTitle: '新增参数',
manualDialogEditTitle: '编辑参数',
manualDialogNameLabel: '名称',
manualDialogNamePlaceholder: '请输入名称',
manualDialogMaxLabel: '上限',
manualDialogMaxPlaceholder: '请输入上限',
manualDialogMinLabel: '下限',
manualDialogMinPlaceholder: '请输入下限',
manualDialogDataTypeLabel: '数据类型',
manualDialogDataTypePlaceholder: '请选择数据类型',
manualDialogDataUnitLabel: '单位',
manualDialogDataUnitPlaceholder: '请选择单位',
manualDialogRemarkLabel: '备注',
manualDialogRemarkPlaceholder: '请输入备注',
manualDialogCancelButton: '取 消',
manualDialogSaveButton: '保 存',
manualValidatorNameRequired: '名称不能为空',
manualValidatorMaxRequired: '上限不能为空',
manualValidatorMinRequired: '下限不能为空',
manualValidatorDataTypeRequired: '数据类型不能为空',
manualValidatorDataUnitRequired: '单位不能为空',
configDialogTitle: '配置',
configSourceTitle: '来源',
configTargetTitle: '目标',
configFilterPlaceholder: '筛选',
configMoveToTargetButton: '添加到目标',
configMoveToSourceButton: '移回来源',
configCancelButton: '取 消',
configSaveButton: '保 存',
configDeviceLabelFallback: '-',
configSourceCountLabel: '来源数量',
configTargetCountLabel: '目标数量'
},
RecipeLibrary: {
moduleName: '配方库',
searchCodeLabel: '编码',
searchCodePlaceholder: '请输入编码',
searchNameLabel: '名称',
searchNamePlaceholder: '请输入名称',
searchRecipeLabel: '配方名称',
searchRecipePlaceholder: '请选择配方',
searchPlanLabel: '关联计划',
searchPlanPlaceholder: '请选择关联计划',
searchButtonText: '查询',
resetButtonText: '重置',
createButtonText: '新增',
exportButtonText: '导出',
tableCodeColumn: '编码',
tableNameColumn: '名称',
tableRecipeNameColumn: '配方名称',
tablePlanCodeColumn: '关联计划',
tableCreatorColumn: '创建人',
tableCreateTimeColumn: '创建时间',
tableOperateColumn: '操作',
tableReadAction: '读取',
tableEditAction: '编辑',
tableDeleteAction: '删除',
dialogCodeLabel: '编码',
dialogCodePlaceholder: '请输入编码',
dialogNameLabel: '名称',
dialogNamePlaceholder: '请输入名称',
dialogRecipeLabel: '配方',
dialogRecipePlaceholder: '请选择配方',
dialogPlanLabel: '关联计划',
dialogPlanPlaceholder: '请选择关联计划',
dialogSourceLabel: '来源',
dialogSourcePlaceholder: '请选择来源',
dialogCancelButton: '取 消',
dialogSaveButton: '保 存',
validatorCodeRequired: '编码不能为空',
validatorNameRequired: '名称不能为空',
validatorRecipeRequired: '关联配方不能为空',
validatorPlanRequired: '关联计划不能为空',
exportFileName: '配方库.xls',
sourceOptionNewLabel: '新增',
sourceOptionProducingLabel: '生产中',
readDialogTitle: '读取',
readDialogNameColumn: '名称',
readDialogDataTypeColumn: '数据类型',
readDialogDataUnitColumn: '单位',
readDialogMaxColumn: '上限',
readDialogMinColumn: '下限',
readDialogRemarkColumn: '备注',
readDialogReferColumn: '采集值',
readDialogReferPlaceholder: '请输入采集值',
readDialogSubmitButtonText: '读 取',
readDialogCancelButton: '取 消',
readDeviceConfirmMessage: '是否读取设备数据?',
detailTabDeviceDataLabel: '设备数据',
detailTabManualLabel: '手动录入参数',
detailDevicePointNameColumn: '点位名称',
detailDeviceDataTypeColumn: '数据类型',
detailDeviceDataUnitColumn: '单位',
detailDeviceValueColumn: '采集值',
manualTableNameColumn: '名称',
manualTableDataTypeColumn: '数据类型',
manualTableDataUnitColumn: '单位',
manualTableReferColumn: '参考值',
manualTableRemarkColumn: '备注'
}
}
}

@ -278,6 +278,7 @@ export enum DICT_TYPE {
IOT_ALERT_LEVEL = "iot_alert_level",
IOT_PROTOCOL = "iot_protocol",
IOT_DEVICE_ATTRIBUTE_UNIT = "iot_device_attribute_unit",
IOT_ALARM_REGISTRATION = " alarm_registration",
PRIMARY_FLAG = "primary_flag"
}

@ -2,22 +2,49 @@
<ContentWrap v-if="visible">
<el-tabs v-model="activeTab" class="mb-12px">
<template #extra>
<el-button link type="info" @click="handleClose"></el-button>
<el-button link type="info" @click="handleClose">
{{ t('RecipeManagement.RecipeConfig.detailCollapseButton') }}
</el-button>
</template>
<el-tab-pane :label="`详情:${recipeCode} - ${name}`" name="detail">
<el-tab-pane
:label="`${t('RecipeManagement.RecipeConfig.detailTabDetailLabel')}${recipeCode} - ${name}`"
name="detail"
>
<div class="mb-12px text-left">
<el-button type="primary" @click="handleConfig"></el-button>
<el-button type="primary" @click="handleConfig">
{{ t('RecipeManagement.RecipeConfig.detailConfigButton') }}
</el-button>
</div>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id">
<el-table-column label="序号" align="center" width="80">
<el-table-column
:label="t('RecipeManagement.RecipeConfig.detailIndexColumn')"
align="center"
width="80"
>
<template #default="scope">
{{ (queryParams.pageNo - 1) * queryParams.pageSize + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="点位名称" align="center" prop="pointName" />
<el-table-column label="点位类型" align="center" prop="pointType" />
<el-table-column label="数据类型" align="center" prop="dataType" />
<el-table-column label="单位" align="center" prop="dataUnit" />
<el-table-column
:label="t('RecipeManagement.RecipeConfig.detailPointNameColumn')"
align="center"
prop="pointName"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.detailPointTypeColumn')"
align="center"
prop="pointType"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.detailDataTypeColumn')"
align="center"
prop="dataType"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.detailDataUnitColumn')"
align="center"
prop="dataUnit"
/>
</el-table>
<Pagination
:total="total"
@ -27,9 +54,11 @@
/>
</el-tab-pane>
<el-tab-pane label="手动录入参数" name="manual">
<el-tab-pane :label="t('RecipeManagement.RecipeConfig.detailTabManualLabel')" name="manual">
<div class="mb-12px flex justify-start">
<el-button type="primary" @click="openManualDialog('create')"></el-button>
<el-button type="primary" @click="openManualDialog('create')">
{{ t('RecipeManagement.RecipeConfig.manualCreateButton') }}
</el-button>
</div>
<el-table
v-loading="manualLoading"
@ -38,15 +67,44 @@
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="上限" align="center" prop="max" />
<el-table-column label="下限" align="center" prop="min" />
<el-table-column label="单位" align="center" prop="dataUnit" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" width="150px" fixed="right">
<el-table-column
:label="t('RecipeManagement.RecipeConfig.manualTableNameColumn')"
align="center"
prop="name"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.manualTableMaxColumn')"
align="center"
prop="max"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.manualTableMinColumn')"
align="center"
prop="min"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.manualTableDataUnitColumn')"
align="center"
prop="dataUnit"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.manualTableRemarkColumn')"
align="center"
prop="remark"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.manualTableOperateColumn')"
align="center"
width="150px"
fixed="right"
>
<template #default="scope">
<el-button link type="primary" @click="openManualDialog('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click="handleManualDelete(scope.row)"></el-button>
<el-button link type="primary" @click="openManualDialog('update', scope.row)">
{{ t('RecipeManagement.RecipeConfig.manualTableEditAction') }}
</el-button>
<el-button link type="danger" @click="handleManualDelete(scope.row)">
{{ t('RecipeManagement.RecipeConfig.manualTableDeleteAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
@ -60,7 +118,11 @@
</el-tabs>
<Dialog
:title="manualDialogMode === 'create' ? '新增参数' : '编辑参数'"
:title="
manualDialogMode === 'create'
? t('RecipeManagement.RecipeConfig.manualDialogCreateTitle')
: t('RecipeManagement.RecipeConfig.manualDialogEditTitle')
"
v-model="manualDialogVisible"
width="640px"
>
@ -68,20 +130,39 @@
ref="manualFormRef"
:model="manualForm"
:rules="manualRules"
label-width="90px"
class="recipe-config-manual-dialog-form"
label-width="auto"
v-loading="manualDialogLoading"
>
<el-form-item label="名称" prop="name">
<el-input v-model="manualForm.name" placeholder="请输入名称" clearable />
<el-form-item :label="t('RecipeManagement.RecipeConfig.manualDialogNameLabel')" prop="name">
<el-input
v-model="manualForm.name"
:placeholder="t('RecipeManagement.RecipeConfig.manualDialogNamePlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="上限" prop="max">
<el-input v-model="manualForm.max" placeholder="请输入上限" clearable />
<el-form-item :label="t('RecipeManagement.RecipeConfig.manualDialogMaxLabel')" prop="max">
<el-input
v-model="manualForm.max"
:placeholder="t('RecipeManagement.RecipeConfig.manualDialogMaxPlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="下限" prop="min">
<el-input v-model="manualForm.min" placeholder="请输入下限" clearable />
<el-form-item :label="t('RecipeManagement.RecipeConfig.manualDialogMinLabel')" prop="min">
<el-input
v-model="manualForm.min"
:placeholder="t('RecipeManagement.RecipeConfig.manualDialogMinPlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="数据类型" prop="dataType">
<el-select v-model="manualForm.dataType" placeholder="请选择数据类型" clearable filterable class="!w-full">
<el-form-item :label="t('RecipeManagement.RecipeConfig.manualDialogDataTypeLabel')" prop="dataType">
<el-select
v-model="manualForm.dataType"
:placeholder="t('RecipeManagement.RecipeConfig.manualDialogDataTypePlaceholder')"
clearable
filterable
class="!w-full"
>
<el-option
v-for="dict in dataTypeDictOptions"
:key="dict.value"
@ -90,8 +171,14 @@
/>
</el-select>
</el-form-item>
<el-form-item label="单位" prop="dataUnit">
<el-select v-model="manualForm.dataUnit" placeholder="请选择单位" clearable filterable class="!w-full">
<el-form-item :label="t('RecipeManagement.RecipeConfig.manualDialogDataUnitLabel')" prop="dataUnit">
<el-select
v-model="manualForm.dataUnit"
:placeholder="t('RecipeManagement.RecipeConfig.manualDialogDataUnitPlaceholder')"
clearable
filterable
class="!w-full"
>
<el-option
v-for="dict in unitDictOptions"
:key="dict.value"
@ -100,13 +187,22 @@
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="manualForm.remark" placeholder="请输入备注" clearable type="textarea" />
<el-form-item :label="t('RecipeManagement.RecipeConfig.manualDialogRemarkLabel')" prop="remark">
<el-input
v-model="manualForm.remark"
:placeholder="t('RecipeManagement.RecipeConfig.manualDialogRemarkPlaceholder')"
clearable
type="textarea"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="manualDialogVisible = false"> </el-button>
<el-button type="primary" :disabled="manualDialogLoading" @click="submitManualDialog"> </el-button>
<el-button @click="manualDialogVisible = false">
{{ t('RecipeManagement.RecipeConfig.manualDialogCancelButton') }}
</el-button>
<el-button type="primary" :disabled="manualDialogLoading" @click="submitManualDialog">
{{ t('RecipeManagement.RecipeConfig.manualDialogSaveButton') }}
</el-button>
</template>
</Dialog>
</ContentWrap>
@ -167,11 +263,41 @@ const manualForm = reactive({
remark: '' as string | undefined
})
const manualRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
max: [{ required: true, message: '上限不能为空', trigger: 'blur' }],
min: [{ required: true, message: '下限不能为空', trigger: 'blur' }],
dataType: [{ required: true, message: '数据类型不能为空', trigger: 'change' }],
dataUnit: [{ required: true, message: '单位不能为空', trigger: 'change' }]
name: [
{
required: true,
message: t('RecipeManagement.RecipeConfig.manualValidatorNameRequired'),
trigger: 'blur'
}
],
max: [
{
required: true,
message: t('RecipeManagement.RecipeConfig.manualValidatorMaxRequired'),
trigger: 'blur'
}
],
min: [
{
required: true,
message: t('RecipeManagement.RecipeConfig.manualValidatorMinRequired'),
trigger: 'blur'
}
],
dataType: [
{
required: true,
message: t('RecipeManagement.RecipeConfig.manualValidatorDataTypeRequired'),
trigger: 'change'
}
],
dataUnit: [
{
required: true,
message: t('RecipeManagement.RecipeConfig.manualValidatorDataUnitRequired'),
trigger: 'change'
}
]
})
const dataTypeDictOptions = computed(() => getStrDictOptions('iot_device_data_type'))
@ -330,3 +456,9 @@ const refresh = async () => {
defineExpose({ refresh })
</script>
<style scoped>
.recipe-config-manual-dialog-form :deep(.el-form-item__label) {
min-width: 90px;
}
</style>

@ -1,34 +1,56 @@
<template>
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form-item label="配方编码" prop="recipeCode">
<el-form
class="-mb-15px recipe-config-filter-form"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="auto"
>
<el-form-item :label="t('RecipeManagement.RecipeConfig.searchRecipeCodeLabel')" prop="recipeCode">
<el-input
v-model="queryParams.recipeCode" placeholder="请输入配方编码" clearable @keyup.enter="handleQuery"
class="!w-240px" />
v-model="queryParams.recipeCode"
:placeholder="t('RecipeManagement.RecipeConfig.searchRecipeCodePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="配方名称" prop="name">
<el-form-item :label="t('RecipeManagement.RecipeConfig.searchNameLabel')" prop="name">
<el-input
v-model="queryParams.name" placeholder="请输入配方名称" clearable @keyup.enter="handleQuery"
class="!w-240px" />
v-model="queryParams.name"
:placeholder="t('RecipeManagement.RecipeConfig.searchNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="产品名称" prop="productName">
<el-form-item :label="t('RecipeManagement.RecipeConfig.searchProductNameLabel')" prop="productName">
<el-input
v-model="queryParams.productName" placeholder="请输入产品名称" clearable @keyup.enter="handleQuery"
class="!w-240px" />
v-model="queryParams.productName"
:placeholder="t('RecipeManagement.RecipeConfig.searchProductNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
<Icon icon="ep:search" class="mr-5px" />
{{ t('RecipeManagement.RecipeConfig.searchButtonText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('RecipeManagement.RecipeConfig.resetButtonText') }}
</el-button>
<el-button type="primary" plain @click="openDialog('create')">
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon icon="ep:plus" class="mr-5px" />
{{ t('RecipeManagement.RecipeConfig.createButtonText') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading">
<Icon icon="ep:download" class="mr-5px" /> 导出
<Icon icon="ep:download" class="mr-5px" />
{{ t('RecipeManagement.RecipeConfig.exportButtonText') }}
</el-button>
</el-form-item>
</el-form>
@ -36,24 +58,67 @@ v-model="queryParams.productName" placeholder="请输入产品名称" clearable
<ContentWrap>
<el-table
ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id"
highlight-current-row @selection-change="handleSelectionChange" @row-click="handleRowClick">
ref="tableRef"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
highlight-current-row
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column label="配方编码" align="center" prop="recipeCode" />
<el-table-column label="配方名称" align="center" prop="name" />
<el-table-column label="配方类型" align="center" prop="recipeType">
<el-table-column
:label="t('RecipeManagement.RecipeConfig.tableRecipeCodeColumn')"
align="center"
prop="recipeCode"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.tableNameColumn')"
align="center"
prop="name"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.tableRecipeTypeColumn')"
align="center"
prop="recipeType"
>
<template #default="scope">
{{ getRecipeTypeLabel(scope.row.recipeType) }}
</template>
</el-table-column>
<el-table-column label="关联产品" align="center" prop="productName" />
<el-table-column label="关联设备" align="center" prop="machineName" />
<el-table-column label="配方描述" align="center" prop="recipeDesc" />
<el-table-column label="操作" align="center" width="240px" fixed="right">
<el-table-column
:label="t('RecipeManagement.RecipeConfig.tableProductNameColumn')"
align="center"
prop="productName"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.tableMachineNameColumn')"
align="center"
prop="machineName"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.tableRecipeDescColumn')"
align="center"
prop="recipeDesc"
/>
<el-table-column
:label="t('RecipeManagement.RecipeConfig.tableOperateColumn')"
align="center"
width="240px"
fixed="right"
>
<template #default="scope">
<el-button link type="primary" @click.stop="openDetail(scope.row)">配置</el-button>
<el-button link type="primary" @click.stop="openDialog('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click.stop="handleDelete(scope.row)">删除</el-button>
<el-button link type="primary" @click.stop="openDetail(scope.row)">
{{ t('RecipeManagement.RecipeConfig.tableConfigAction') }}
</el-button>
<el-button link type="primary" @click.stop="openDialog('update', scope.row)">
{{ t('RecipeManagement.RecipeConfig.tableEditAction') }}
</el-button>
<el-button link type="danger" @click.stop="handleDelete(scope.row)">
{{ t('RecipeManagement.RecipeConfig.tableDeleteAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
@ -68,54 +133,97 @@ ref="detailRef" :visible="detailVisible"
:recipe-code="detailMeta.recipeCode" :name="detailMeta.name" @config="handleDetailConfig" @close="closeDetail" />
<Dialog :title="dialogTitle" v-model="dialogVisible" width="720px">
<el-form ref="dialogFormRef" :model="dialogForm" :rules="dialogRules" label-width="100px" v-loading="dialogLoading">
<el-form-item label="配方编码" prop="recipeCode">
<el-input v-model="dialogForm.recipeCode" placeholder="请输入配方编码" clearable />
<el-form
ref="dialogFormRef"
:model="dialogForm"
:rules="dialogRules"
class="recipe-config-dialog-form"
label-width="auto"
v-loading="dialogLoading"
>
<el-form-item :label="t('RecipeManagement.RecipeConfig.dialogRecipeCodeLabel')" prop="recipeCode">
<el-input
v-model="dialogForm.recipeCode"
:placeholder="t('RecipeManagement.RecipeConfig.dialogRecipeCodePlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="配方名称" prop="name">
<el-input v-model="dialogForm.name" placeholder="请输入配方名称" clearable />
<el-form-item :label="t('RecipeManagement.RecipeConfig.dialogNameLabel')" prop="name">
<el-input
v-model="dialogForm.name"
:placeholder="t('RecipeManagement.RecipeConfig.dialogNamePlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="配方类型" prop="recipeType">
<el-form-item :label="t('RecipeManagement.RecipeConfig.dialogRecipeTypeLabel')" prop="recipeType">
<el-select
v-model="dialogForm.recipeType" placeholder="请选择配方类型" clearable filterable class="!w-full"
:loading="recipeTypeLoading">
v-model="dialogForm.recipeType"
:placeholder="t('RecipeManagement.RecipeConfig.dialogRecipeTypePlaceholder')"
clearable
filterable
class="!w-full"
:loading="recipeTypeLoading"
>
<el-option v-for="item in recipeTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="关联产品" prop="productName">
<el-form-item :label="t('RecipeManagement.RecipeConfig.dialogProductNameLabel')" prop="productName">
<el-select
v-model="dialogForm.productName" placeholder="请选择关联产品" clearable filterable class="!w-full"
:loading="productLoading">
v-model="dialogForm.productName"
:placeholder="t('RecipeManagement.RecipeConfig.dialogProductNamePlaceholder')"
clearable
filterable
class="!w-full"
:loading="productLoading"
>
<el-option v-for="item in productOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="关联设备" prop="machineName">
<el-form-item :label="t('RecipeManagement.RecipeConfig.dialogMachineNameLabel')" prop="machineName">
<el-select
v-model="dialogForm.machineName" placeholder="请选择关联设备" clearable filterable class="!w-full"
:loading="deviceLoading">
v-model="dialogForm.machineName"
:placeholder="t('RecipeManagement.RecipeConfig.dialogMachineNamePlaceholder')"
clearable
filterable
class="!w-full"
:loading="deviceLoading"
>
<el-option v-for="item in deviceOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="配方描述" prop="recipeDesc">
<el-input v-model="dialogForm.recipeDesc" placeholder="请输入配方描述" clearable type="textarea" />
<el-form-item :label="t('RecipeManagement.RecipeConfig.dialogRecipeDescLabel')" prop="recipeDesc">
<el-input
v-model="dialogForm.recipeDesc"
:placeholder="t('RecipeManagement.RecipeConfig.dialogRecipeDescPlaceholder')"
clearable
type="textarea"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitDialog" :disabled="dialogLoading"> </el-button>
<el-button @click="dialogVisible = false">
{{ t('RecipeManagement.RecipeConfig.dialogCancelButton') }}
</el-button>
<el-button type="primary" @click="submitDialog" :disabled="dialogLoading">
{{ t('RecipeManagement.RecipeConfig.dialogSaveButton') }}
</el-button>
</template>
</Dialog>
<Dialog title="配置" v-model="configVisible" width="920px">
<Dialog :title="t('RecipeManagement.RecipeConfig.configDialogTitle')" v-model="configVisible" width="920px">
<div v-loading="configLoading">
<div class="formula-config-picklist">
<div class="formula-config-panel">
<div class="formula-config-panel__header">
<div>来源</div>
<div>{{ t('RecipeManagement.RecipeConfig.configSourceTitle') }}</div>
<div class="formula-config-panel__meta">{{ filteredSourceItems.length }}</div>
</div>
<div class="formula-config-panel__filter">
<el-input v-model="sourceKeyword" placeholder="筛选" clearable />
<el-input
v-model="sourceKeyword"
:placeholder="t('RecipeManagement.RecipeConfig.configFilterPlaceholder')"
clearable
/>
</div>
<el-table
class="formula-config-panel__table"
@ -133,20 +241,24 @@ v-model="dialogForm.machineName" placeholder="请选择关联设备" clearable f
<div class="formula-config-actions">
<el-button type="primary" :disabled="!sourceCheckedKeys.length" @click="addToTarget">
&gt;&gt;
{{ t('RecipeManagement.RecipeConfig.configMoveToTargetButton') }}
</el-button>
<el-button :disabled="!targetCheckedKeys.length" @click="removeFromTarget">
&lt;&lt;
{{ t('RecipeManagement.RecipeConfig.configMoveToSourceButton') }}
</el-button>
</div>
<div class="formula-config-panel">
<div class="formula-config-panel__header">
<div>目标</div>
<div>{{ t('RecipeManagement.RecipeConfig.configTargetTitle') }}</div>
<div class="formula-config-panel__meta">{{ filteredTargetItems.length }}</div>
</div>
<div class="formula-config-panel__filter">
<el-input v-model="targetKeyword" placeholder="筛选" clearable />
<el-input
v-model="targetKeyword"
:placeholder="t('RecipeManagement.RecipeConfig.configFilterPlaceholder')"
clearable
/>
</div>
<el-table
class="formula-config-panel__table"
@ -163,8 +275,12 @@ v-model="dialogForm.machineName" placeholder="请选择关联设备" clearable f
</div>
</div>
<template #footer>
<el-button @click="configVisible = false"> </el-button>
<el-button type="primary" @click="submitConfig" :disabled="configLoading"> </el-button>
<el-button @click="configVisible = false">
{{ t('RecipeManagement.RecipeConfig.configCancelButton') }}
</el-button>
<el-button type="primary" @click="submitConfig" :disabled="configLoading">
{{ t('RecipeManagement.RecipeConfig.configSaveButton') }}
</el-button>
</template>
</Dialog>
</template>
@ -243,14 +359,14 @@ const handlePagination = () => {
const handleExport = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要导出的数据')
message.error(t('RecipeManagement.RecipeConfig.exportNoSelectionMessage'))
return
}
try {
await message.exportConfirm()
exportLoading.value = true
const data = await RecipeConfigApi.exportRecipeConfig({ ids: selectedIds.value.join(',') })
download.excel(data, '配方配置.xls')
download.excel(data, t('RecipeManagement.RecipeConfig.exportFileName'))
} catch {
} finally {
exportLoading.value = false
@ -316,7 +432,11 @@ const ensureOptionsLoaded = async () => {
type DialogMode = 'create' | 'update'
const dialogVisible = ref(false)
const dialogMode = ref<DialogMode>('create')
const dialogTitle = computed(() => (dialogMode.value === 'create' ? '新增配方配置' : '编辑配方配置'))
const dialogTitle = computed(() =>
dialogMode.value === 'create'
? t('RecipeManagement.RecipeConfig.dialogCreateTitle')
: t('RecipeManagement.RecipeConfig.dialogEditTitle')
)
const dialogFormRef = ref()
const dialogLoading = ref(false)
const dialogForm = reactive({
@ -330,8 +450,20 @@ const dialogForm = reactive({
})
const dialogRules = reactive({
recipeCode: [{ required: true, message: '配方编码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '配方名称不能为空', trigger: 'blur' }]
recipeCode: [
{
required: true,
message: t('RecipeManagement.RecipeConfig.validatorRecipeCodeRequired'),
trigger: 'blur'
}
],
name: [
{
required: true,
message: t('RecipeManagement.RecipeConfig.validatorNameRequired'),
trigger: 'blur'
}
]
})
const openDialog = async (mode: DialogMode, row?: RecipeConfigVO) => {
@ -416,7 +548,7 @@ const handleDetailConfig = () => {
const handleRowClick = async (row: RecipeConfigVO, column: any) => {
if (column?.type === 'selection') return
if (column?.label === '操作') return
if (column?.label === t('RecipeManagement.RecipeConfig.tableOperateColumn')) return
await openDetail(row)
}
@ -445,7 +577,7 @@ const configDeviceLabel = computed(() => {
const name = detailMeta.machineName?.trim()
if (name) return name
if (detailMeta.deviceId !== undefined && detailMeta.deviceId !== null) return String(detailMeta.deviceId)
return '-'
return t('RecipeManagement.RecipeConfig.configDeviceLabelFallback')
})
const normalizeKey = (value: any): number | undefined => {
@ -588,6 +720,14 @@ onMounted(() => {
</script>
<style scoped>
.recipe-config-filter-form :deep(.el-form-item__label) {
min-width: 68px;
}
.recipe-config-dialog-form :deep(.el-form-item__label) {
min-width: 100px;
}
.formula-config-picklist {
display: flex;
width: 100%;

@ -1,13 +1,33 @@
<template>
<el-tabs v-model="activeTab">
<el-tab-pane label="设备数据" name="detail">
<el-tab-pane :label="t('RecipeManagement.RecipeLibrary.detailTabDeviceDataLabel')" name="detail">
<el-table :data="detailList" :stripe="true" :show-overflow-tooltip="true" row-key="key">
<!-- <el-table-column type="index" label="序号" align="center" width="70" /> -->
<el-table-column label="点位名称" align="center" prop="attributeName" min-width="180" />
<el-table-column label="数据类型" align="center" prop="dataType" min-width="180" />
<!-- <el-table-column label="地址" align="center" prop="address" min-width="180" /> -->
<el-table-column label="单位" align="center" prop="dataUnit" width="120" />
<el-table-column label="采集值" align="center" prop="value" min-width="180" />
<!-- <el-table-column type="index" :label="t('RecipeManagement.RecipeLibrary.detailIndexColumn')" align="center" width="70" /> -->
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.detailDevicePointNameColumn')"
align="center"
prop="attributeName"
min-width="180"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.detailDeviceDataTypeColumn')"
align="center"
prop="dataType"
min-width="180"
/>
<!-- <el-table-column :label="t('RecipeManagement.RecipeLibrary.detailDeviceAddressColumn')" align="center" prop="address" min-width="180" /> -->
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.detailDeviceDataUnitColumn')"
align="center"
prop="dataUnit"
width="120"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.detailDeviceValueColumn')"
align="center"
prop="value"
min-width="180"
/>
</el-table>
<Pagination
:total="to"
@ -16,7 +36,7 @@
@pagination="getList"
/>
</el-tab-pane>
<el-tab-pane label="手动录入参数" name="manual">
<el-tab-pane :label="t('RecipeManagement.RecipeLibrary.detailTabManualLabel')" name="manual">
<!-- <el-form
class="-mb-15px"
:model="queryParams"
@ -70,12 +90,37 @@
</el-form-item>
</el-form> -->
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id">
<!-- <el-table-column type="index" label="序号" align="center" width="70" /> -->
<el-table-column label="名称" align="center" prop="name" min-width="140" />
<el-table-column label="数据类型" align="center" prop="dataType" width="140" />
<el-table-column label="单位" align="center" prop="dataUnit" width="140" />
<el-table-column label="参考值" align="center" prop="refer" min-width="160" />
<el-table-column label="备注" align="center" prop="remark" min-width="180" />
<!-- <el-table-column type="index" :label="t('RecipeManagement.RecipeLibrary.detailIndexColumn')" align="center" width="70" /> -->
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.manualTableNameColumn')"
align="center"
prop="name"
min-width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.manualTableDataTypeColumn')"
align="center"
prop="dataType"
width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.manualTableDataUnitColumn')"
align="center"
prop="dataUnit"
width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.manualTableReferColumn')"
align="center"
prop="refer"
min-width="160"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.manualTableRemarkColumn')"
align="center"
prop="remark"
min-width="180"
/>
</el-table>
<Pagination
:total="total"
@ -93,6 +138,8 @@ import { RecipeDeviceRecordApi } from '@/api/iot/recipeDeviceRecord'
defineOptions({ name: 'FormulaLibraryDetailTabs' })
const { t } = useI18n()
const props = defineProps({
recipeId: {
type: [String, Number],

@ -2,17 +2,56 @@
<Dialog v-model="visible" :title="title" width="920px">
<div class="formula-library-read-content">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id">
<!-- <el-table-column type="index" label="序号" align="center" width="70" /> -->
<el-table-column label="名称" align="center" prop="name" min-width="160" />
<!-- <el-table-column label="参考值" align="center" prop="refer" min-width="160" /> -->
<el-table-column label="数据类型" align="center" prop="dataType" width="140" />
<el-table-column label="单位" align="center" prop="dataUnit" width="140" />
<el-table-column label="上限" align="center" prop="max" width="140" />
<el-table-column label="下限" align="center" prop="min" width="140" />
<el-table-column label="备注" align="center" prop="remark" min-width="180" />
<el-table-column label="采集值" align="center" min-width="160" fixed="right" prop="refer">
<!-- <el-table-column type="index" :label="t('RecipeManagement.RecipeLibrary.tableIndexColumn')" align="center" width="70" /> -->
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.readDialogNameColumn')"
align="center"
prop="name"
min-width="160"
/>
<!-- <el-table-column :label="t('RecipeManagement.RecipeLibrary.readDialogReferColumn')" align="center" prop="refer" min-width="160" /> -->
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.readDialogDataTypeColumn')"
align="center"
prop="dataType"
width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.readDialogDataUnitColumn')"
align="center"
prop="dataUnit"
width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.readDialogMaxColumn')"
align="center"
prop="max"
width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.readDialogMinColumn')"
align="center"
prop="min"
width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.readDialogRemarkColumn')"
align="center"
prop="remark"
min-width="180"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.readDialogReferColumn')"
align="center"
min-width="160"
fixed="right"
prop="refer"
>
<template #default="{ row }">
<el-input v-model="row.refer" placeholder="请输入采集值" />
<el-input
v-model="row.refer"
:placeholder="t('RecipeManagement.RecipeLibrary.readDialogReferPlaceholder')"
/>
</template>
</el-table-column>
</el-table>
@ -33,8 +72,12 @@
</div>
<template #footer>
<el-button @click="submitForm" type="primary"> </el-button>
<el-button @click="visible = false"> </el-button>
<el-button @click="submitForm" type="primary">
{{ t('RecipeManagement.RecipeLibrary.readDialogSubmitButtonText') }}
</el-button>
<el-button @click="visible = false">
{{ t('RecipeManagement.RecipeLibrary.readDialogCancelButton') }}
</el-button>
</template>
</Dialog>
</template>
@ -49,7 +92,7 @@ defineOptions({ name: 'FormulaLibraryReadDialog' })
type PageResult<T> = { list: T[]; total: number }
const visible = ref(false)
const title = ref('读取')
const title = ref('')
const loading = ref(false)
const recipeId = ref<string | number | undefined>(undefined)
@ -106,7 +149,8 @@ const open = async (options: {
visible.value = true
formulaLibraryId.value = options.id
recipeId.value = options.recipeId
title.value = options.recipeName ? `读取-${options.recipeName}` : '读取'
const baseTitle = t('RecipeManagement.RecipeLibrary.readDialogTitle')
title.value = options.recipeName ? `${baseTitle}-${options.recipeName}` : baseTitle
queryParams.pageNo = 1
list.value = options.data ?? []
// if (options.initialData) {

@ -1,34 +1,34 @@
<template>
<ContentWrap>
<el-form
class="-mb-15px"
class="-mb-15px recipe-library-filter-form"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="70px"
label-width="auto"
>
<el-form-item label="编码" prop="code">
<el-form-item :label="t('RecipeManagement.RecipeLibrary.searchCodeLabel')" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入编码"
:placeholder="t('RecipeManagement.RecipeLibrary.searchCodePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-form-item :label="t('RecipeManagement.RecipeLibrary.searchNameLabel')" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
:placeholder="t('RecipeManagement.RecipeLibrary.searchNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="配方名称" prop="recipeId">
<el-form-item :label="t('RecipeManagement.RecipeLibrary.searchRecipeLabel')" prop="recipeId">
<el-select
v-model="queryParams.recipeId"
placeholder="请选择配方"
:placeholder="t('RecipeManagement.RecipeLibrary.searchRecipePlaceholder')"
clearable
filterable
class="!w-240px"
@ -41,10 +41,10 @@
/>
</el-select>
</el-form-item>
<el-form-item label="关联计划" prop="planId">
<el-form-item :label="t('RecipeManagement.RecipeLibrary.searchPlanLabel')" prop="planId">
<el-select
v-model="queryParams.planId"
placeholder="请选择关联计划"
:placeholder="t('RecipeManagement.RecipeLibrary.searchPlanPlaceholder')"
clearable
filterable
class="!w-240px"
@ -64,16 +64,20 @@
</el-form-item> -->
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
<Icon icon="ep:search" class="mr-5px" />
{{ t('RecipeManagement.RecipeLibrary.searchButtonText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('RecipeManagement.RecipeLibrary.resetButtonText') }}
</el-button>
<el-button type="primary" plain @click="openDialog('create')">
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon icon="ep:plus" class="mr-5px" />
{{ t('RecipeManagement.RecipeLibrary.createButtonText') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading">
<Icon icon="ep:download" class="mr-5px" /> 导出
<Icon icon="ep:download" class="mr-5px" />
{{ t('RecipeManagement.RecipeLibrary.exportButtonText') }}
</el-button>
</el-form-item>
</el-form>
@ -89,31 +93,73 @@
highlight-current-row
@row-click="handleRowClick"
>
<el-table-column label="编码" align="center" prop="code" min-width="140" />
<el-table-column label="名称" align="center" prop="name" min-width="140" />
<el-table-column label="配方名称" align="center" prop="recipeName" min-width="160">
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.tableCodeColumn')"
align="center"
prop="code"
min-width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.tableNameColumn')"
align="center"
prop="name"
min-width="140"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.tableRecipeNameColumn')"
align="center"
prop="recipeName"
min-width="160"
>
<template #default="scope">
<span>{{ scope.row.recipeName ?? '-' }}</span>
</template>
</el-table-column>
<el-table-column label="关联计划" align="center" prop="planCode" min-width="160">
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.tablePlanCodeColumn')"
align="center"
prop="planCode"
min-width="160"
>
<template #default="scope">
<span>{{ scope.row.planCode ?? '-' }}</span>
</template>
</el-table-column>
<el-table-column label="创建人" align="center" prop="creator" width="120" />
<el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180" />
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.tableCreatorColumn')"
align="center"
prop="creator"
width="120"
/>
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.tableCreateTimeColumn')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<!-- <el-table-column label="来源" align="center" prop="source" width="120">
<template #default="scope">
<el-tag v-if="scope.row.source" :type="getSourceTagType(scope.row.source)">{{ scope.row.source }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column> -->
<el-table-column label="操作" align="center" width="180" fixed="right">
<el-table-column
:label="t('RecipeManagement.RecipeLibrary.tableOperateColumn')"
align="center"
width="180"
fixed="right"
>
<template #default="scope">
<el-button link type="primary" @click.stop="handleRead(scope.row)">读取</el-button>
<el-button link type="primary" @click.stop="openDialog('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click.stop="handleDelete(scope.row)">删除</el-button>
<el-button link type="primary" @click.stop="handleRead(scope.row)">
{{ t('RecipeManagement.RecipeLibrary.tableReadAction') }}
</el-button>
<el-button link type="primary" @click.stop="openDialog('update', scope.row)">
{{ t('RecipeManagement.RecipeLibrary.tableEditAction') }}
</el-button>
<el-button link type="danger" @click.stop="handleDelete(scope.row)">
{{ t('RecipeManagement.RecipeLibrary.tableDeleteAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
@ -133,32 +179,73 @@
<FormulaLibraryReadDialog ref="readDialogRef" />
<Dialog :title="dialogTitle" v-model="dialogVisible" width="720px">
<el-form ref="dialogFormRef" :model="dialogForm" :rules="dialogRules" label-width="100px" v-loading="dialogLoading">
<el-form-item label="编码" prop="code">
<el-input v-model="dialogForm.code" placeholder="请输入编码" clearable />
<el-form
ref="dialogFormRef"
:model="dialogForm"
:rules="dialogRules"
class="recipe-library-dialog-form"
label-width="auto"
v-loading="dialogLoading"
>
<el-form-item :label="t('RecipeManagement.RecipeLibrary.dialogCodeLabel')" prop="code">
<el-input
v-model="dialogForm.code"
:placeholder="t('RecipeManagement.RecipeLibrary.dialogCodePlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="dialogForm.name" placeholder="请输入名称" clearable />
<el-form-item :label="t('RecipeManagement.RecipeLibrary.dialogNameLabel')" prop="name">
<el-input
v-model="dialogForm.name"
:placeholder="t('RecipeManagement.RecipeLibrary.dialogNamePlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="配方" prop="recipeId">
<el-select v-model="dialogForm.recipeId" placeholder="请选择配方" clearable filterable class="!w-full">
<el-form-item :label="t('RecipeManagement.RecipeLibrary.dialogRecipeLabel')" prop="recipeId">
<el-select
v-model="dialogForm.recipeId"
:placeholder="t('RecipeManagement.RecipeLibrary.dialogRecipePlaceholder')"
clearable
filterable
class="!w-full"
>
<el-option v-for="opt in recipeOptions" :key="String(opt.value)" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
<el-form-item label="关联计划" prop="planId">
<el-select v-model="dialogForm.planId" placeholder="请选择关联计划" clearable filterable class="!w-full">
<el-form-item :label="t('RecipeManagement.RecipeLibrary.dialogPlanLabel')" prop="planId">
<el-select
v-model="dialogForm.planId"
:placeholder="t('RecipeManagement.RecipeLibrary.dialogPlanPlaceholder')"
clearable
filterable
class="!w-full"
>
<el-option v-for="opt in planOptions" :key="String(opt.value)" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
<el-form-item label="来源" prop="source">
<el-select v-model="dialogForm.source" placeholder="请选择来源" clearable class="!w-full">
<el-option v-for="opt in sourceOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
<el-form-item :label="t('RecipeManagement.RecipeLibrary.dialogSourceLabel')" prop="source">
<el-select
v-model="dialogForm.source"
:placeholder="t('RecipeManagement.RecipeLibrary.dialogSourcePlaceholder')"
clearable
class="!w-full"
>
<el-option
v-for="opt in sourceOptions"
:key="opt.value"
:label="getSourceLabel(opt.value)"
:value="opt.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitDialog" :disabled="dialogLoading"> </el-button>
<el-button @click="dialogVisible = false">
{{ t('RecipeManagement.RecipeLibrary.dialogCancelButton') }}
</el-button>
<el-button type="primary" @click="submitDialog" :disabled="dialogLoading">
{{ t('RecipeManagement.RecipeLibrary.dialogSaveButton') }}
</el-button>
</template>
</Dialog>
</template>
@ -270,7 +357,7 @@ const handleExport = async () => {
await message.exportConfirm()
exportLoading.value = true
const data = await RecipePlanDetailApi.exportRecipePlanDetail(buildQueryParams())
download.excel(data, '配方库.xls')
download.excel(data, t('RecipeManagement.RecipeLibrary.exportFileName'))
} catch {
} finally {
exportLoading.value = false
@ -294,6 +381,12 @@ const getSourceTagType = (source: string) => {
return 'info'
}
const getSourceLabel = (value: string) => {
if (value === '新增') return t('RecipeManagement.RecipeLibrary.sourceOptionNewLabel')
if (value === '生产中') return t('RecipeManagement.RecipeLibrary.sourceOptionProducingLabel')
return value
}
const dialogVisible = ref(false)
const dialogTitle = ref('')
const dialogLoading = ref(false)
@ -311,10 +404,18 @@ const dialogForm = ref<RecipePlanDetailVO>({
})
const dialogRules = reactive({
code: [{ required: true, message: '编码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
recipeId: [{ required: true, message: '关联配方不能为空', trigger: 'change' }],
planId: [{ required: true, message: '关联计划不能为空', trigger: 'change' }]
code: [
{ required: true, message: t('RecipeManagement.RecipeLibrary.validatorCodeRequired'), trigger: 'blur' }
],
name: [
{ required: true, message: t('RecipeManagement.RecipeLibrary.validatorNameRequired'), trigger: 'blur' }
],
recipeId: [
{ required: true, message: t('RecipeManagement.RecipeLibrary.validatorRecipeRequired'), trigger: 'change' }
],
planId: [
{ required: true, message: t('RecipeManagement.RecipeLibrary.validatorPlanRequired'), trigger: 'change' }
]
})
const resetDialogForm = () => {
@ -356,7 +457,7 @@ const handleRead = async (row: RecipePlanDetailVO) => {
const recipeName = row?.recipeName
const data = await RecipePointApi.getRecipePointList(Number(recipeId))
if (!data?.length) {
await message.confirm('是否读取设备数据?')
await message.confirm(t('RecipeManagement.RecipeLibrary.readDeviceConfirmMessage'))
if (id != null) {
await RecipeDeviceRecordApi.createRecipeDeviceRecordBatch(id)
message.success(t('common.createSuccess'))
@ -393,7 +494,17 @@ const submitDialog = async () => {
}
onMounted(async () => {
await ensureOptionsLoaded()
await getList()
await ensureOptionsLoaded()
await getList()
})
</script>
<style scoped>
.recipe-library-filter-form :deep(.el-form-item__label) {
min-width: 68px;
}
.recipe-library-dialog-form :deep(.el-form-item__label) {
min-width: 100px;
}
</style>

@ -1,25 +1,25 @@
<template>
<ContentWrap>
<el-form
class="-mb-15px"
class="-mb-15px recipe-type-filter-form"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
label-width="auto"
>
<el-form-item label="名称" prop="name">
<el-form-item :label="t('RecipeManagement.RecipeType.searchNameLabel')" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
:placeholder="t('RecipeManagement.RecipeType.searchNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="工序" prop="process">
<el-form-item :label="t('RecipeManagement.RecipeType.searchProcessLabel')" prop="process">
<el-select
v-model="queryParams.process"
placeholder="请选择工序"
:placeholder="t('RecipeManagement.RecipeType.searchProcessPlaceholder')"
clearable
class="!w-240px"
@change="handleQuery"
@ -32,15 +32,22 @@
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 查询</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('RecipeManagement.RecipeType.searchButtonText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('RecipeManagement.RecipeType.resetButtonText') }}
</el-button>
<el-button type="primary" plain @click="openDialog('create')">
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon icon="ep:plus" class="mr-5px" />
{{ t('RecipeManagement.RecipeType.createButtonText') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading">
<Icon icon="ep:download" class="mr-5px" /> 导出
<Icon icon="ep:download" class="mr-5px" />
{{ t('RecipeManagement.RecipeType.exportButtonText') }}
</el-button>
</el-form-item>
</el-form>
@ -57,17 +64,38 @@
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="工序" align="center" prop="process">
<el-table-column
:label="t('RecipeManagement.RecipeType.tableNameColumn')"
align="center"
prop="name"
/>
<el-table-column
:label="t('RecipeManagement.RecipeType.tableProcessColumn')"
align="center"
prop="process"
>
<template #default="scope">
{{ getProcessLabel(scope.row.process) }}
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" width="160px" fixed="right">
<el-table-column
:label="t('RecipeManagement.RecipeType.tableRemarkColumn')"
align="center"
prop="remark"
/>
<el-table-column
:label="t('RecipeManagement.RecipeType.tableOperateColumn')"
align="center"
width="160px"
fixed="right"
>
<template #default="scope">
<el-button link type="primary" @click="openDialog('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)"></el-button>
<el-button link type="primary" @click="openDialog('update', scope.row)">
{{ t('RecipeManagement.RecipeType.tableEditAction') }}
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">
{{ t('RecipeManagement.RecipeType.tableDeleteAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
@ -84,14 +112,24 @@
ref="dialogFormRef"
:model="dialogForm"
:rules="dialogRules"
label-width="100px"
class="recipe-type-dialog-form"
label-width="auto"
v-loading="dialogLoading"
>
<el-form-item label="名称" prop="name">
<el-input v-model="dialogForm.name" placeholder="请输入名称" clearable />
<el-form-item :label="t('RecipeManagement.RecipeType.dialogNameLabel')" prop="name">
<el-input
v-model="dialogForm.name"
:placeholder="t('RecipeManagement.RecipeType.dialogNamePlaceholder')"
clearable
/>
</el-form-item>
<el-form-item label="工序" prop="process">
<el-select v-model="dialogForm.process" placeholder="请选择工序" clearable class="!w-full">
<el-form-item :label="t('RecipeManagement.RecipeType.dialogProcessLabel')" prop="process">
<el-select
v-model="dialogForm.process"
:placeholder="t('RecipeManagement.RecipeType.dialogProcessPlaceholder')"
clearable
class="!w-full"
>
<el-option
v-for="option in processOptions"
:key="option.value"
@ -100,13 +138,22 @@
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="dialogForm.remark" placeholder="请输入备注" clearable type="textarea" />
<el-form-item :label="t('RecipeManagement.RecipeType.dialogRemarkLabel')" prop="remark">
<el-input
v-model="dialogForm.remark"
:placeholder="t('RecipeManagement.RecipeType.dialogRemarkPlaceholder')"
clearable
type="textarea"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitDialog" :disabled="dialogLoading"> </el-button>
<el-button @click="dialogVisible = false">
{{ t('RecipeManagement.RecipeType.dialogCancelButton') }}
</el-button>
<el-button type="primary" @click="submitDialog" :disabled="dialogLoading">
{{ t('RecipeManagement.RecipeType.dialogSaveButton') }}
</el-button>
</template>
</Dialog>
</template>
@ -199,14 +246,14 @@ const handlePagination = () => {
const handleExport = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要导出的数据')
message.error(t('RecipeManagement.RecipeType.exportNoSelectionMessage'))
return
}
try {
await message.exportConfirm()
exportLoading.value = true
const data = await RecipeApi.exportRecipe({ ids: selectedIds.value.join(',') })
download.excel(data, '配方类型.xls')
download.excel(data, t('RecipeManagement.RecipeType.exportFileName'))
} catch {
} finally {
exportLoading.value = false
@ -217,7 +264,11 @@ type DialogMode = 'create' | 'update'
const dialogVisible = ref(false)
const dialogMode = ref<DialogMode>('create')
const dialogTitle = computed(() => (dialogMode.value === 'create' ? '新增配方类型' : '编辑配方类型'))
const dialogTitle = computed(() =>
dialogMode.value === 'create'
? t('RecipeManagement.RecipeType.dialogCreateTitle')
: t('RecipeManagement.RecipeType.dialogEditTitle')
)
const dialogFormRef = ref()
const dialogLoading = ref(false)
const dialogForm = reactive({
@ -228,8 +279,14 @@ const dialogForm = reactive({
})
const dialogRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
process: [{ required: true, message: '工序不能为空', trigger: 'change' }]
name: [{ required: true, message: t('RecipeManagement.RecipeType.validatorNameRequired'), trigger: 'blur' }],
process: [
{
required: true,
message: t('RecipeManagement.RecipeType.validatorProcessRequired'),
trigger: 'change'
}
]
})
const openDialog = (mode: DialogMode, row?: RecipeVO) => {
@ -291,3 +348,13 @@ onMounted(() => {
getList()
})
</script>
<style scoped>
.recipe-type-filter-form :deep(.el-form-item__label) {
min-width: 68px;
}
.recipe-type-dialog-form :deep(.el-form-item__label) {
min-width: 100px;
}
</style>

@ -24,8 +24,9 @@
clearable
filterable
placeholder="请选择点位类型"
@change="handleAttributeTypeChange"
>
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.name" />
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="数据类型" prop="dataType">
@ -95,7 +96,8 @@ const formData = ref({
id: undefined as number | undefined,
attributeCode: undefined as string | undefined,
attributeName: undefined as string | undefined,
attributeType: undefined as string | undefined,
attributeType: undefined as number | undefined,
typeName: undefined as string | undefined,
dataType: undefined as string | undefined,
address: undefined as string | undefined,
dataUnit: undefined as string | undefined,
@ -140,6 +142,13 @@ const handleSortInput = (val: string) => {
formData.value.sort = val?.replace(/\D/g, '')
}
const handleAttributeTypeChange = (val: number | string) => {
const matched = typeList.value.find(
(item) => item.id === val || String(item.id) === String(val)
)
formData.value.typeName = matched?.name
}
const formRules = reactive({
attributeCode: [
{ required: true, message: '点位编码不能为空', trigger: 'blur' },
@ -196,6 +205,7 @@ const buildSubmitData = () => {
attributeCode,
attributeName,
attributeType,
typeName,
dataType,
address,
dataUnit,
@ -215,6 +225,7 @@ const buildSubmitData = () => {
attributeCode,
attributeName,
attributeType,
typeName,
dataType,
address,
dataUnit,
@ -255,13 +266,14 @@ const open = async (type: string, id?: number, deviceId: number) => {
if (currentType !== undefined && currentType !== null && currentType !== '') {
const matched = typeList.value.find(
(item) =>
item.name === currentType ||
item.id === currentType ||
item.code === currentType ||
String(item.id) === String(currentType)
String(item.id) === String(currentType) ||
item.name === currentType ||
item.code === currentType
)
if (matched?.name) {
;(formData.value as any).attributeType = matched.name
if (matched) {
;(formData.value as any).attributeType = matched.id
;(formData.value as any).typeName = matched.name
}
}
} finally {
@ -302,6 +314,7 @@ const resetForm = () => {
attributeCode: undefined,
attributeName: undefined,
attributeType: undefined,
typeName: undefined,
dataType: undefined,
address: undefined,
dataUnit: undefined,

@ -45,11 +45,7 @@ ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-to
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column label="点位编码" align="left" prop="attributeCode" width="150px" />
<el-table-column label="点位名称" align="left" prop="attributeName" width="150px" />
<el-table-column label="点位类型" align="center" prop="attributeType" width="120px">
<template #default="scope">
{{ getAttributeTypeLabel(scope.row.attributeType) }}
</template>
</el-table-column>
<el-table-column label="点位类型" align="center" prop="typeName" width="140px" />
<el-table-column label="数据类型" align="center" prop="dataType" width="120px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_DEVICE_DATA_TYPE" :value="scope.row.dataType" />
@ -64,12 +60,8 @@ ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-to
<el-table-column label="单位" align="center" prop="dataUnit" width="80px" />
<el-table-column label="倍率" align="center" prop="ratio" width="80px" />
<el-table-column
label="最新采集时间"
align="center"
prop="latestCollectionTime"
:formatter="dateFormatter"
width="170px"
/>
label="最新采集时间" align="center" prop="latestCollectionTime" :formatter="dateFormatter"
width="170px" />
<el-table-column label="顺序" align="center" prop="sort" width="80px">
<template #default="scope">
{{ scope.row.sort ?? '-' }}
@ -240,14 +232,14 @@ const handleBatchDelete = async () => {
/** 导出按钮操作 */
const handleExport = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要导出的数据')
return
}
try {
await message.exportConfirm()
exportLoading.value = true
const data = await DeviceModelAttributeApi.exportDeviceModelAttribute({ ids: selectedIds.value.join(',') })
const params: any = {}
if (selectedIds.value.length) {
params.ids = selectedIds.value.join(',')
}
const data = await DeviceModelAttributeApi.exportDeviceModelAttribute(params)
download.excel(data, '采集设备-点位管理.xls')
} catch {
} finally {

@ -163,8 +163,15 @@ link :type="isRowConnected(scope.row) ? 'warning' : 'success'"
<!-- 子表的列表 -->
<ContentWrap>
<template v-if="attributeDeviceId">
<div class="mb-10px text-sm text-gray-500">
当前设备<span class="font-medium text-gray-700">{{ attributeDeviceName || '-' }}</span>
<div class="mb-10px flex items-center justify-between text-sm text-gray-500">
<div>
当前设备<span class="font-medium text-gray-700">{{ attributeDeviceName || '-' }}</span>
</div>
<div>
<el-button type="primary" link @click="handleShowDeviceAlarmHistory">
设备告警历史数据
</el-button>
</div>
</div>
<el-tabs v-model="deviceTabActive">
<el-tab-pane :label="deviceAttributeTabLabel" name="deviceAttribute">
@ -273,6 +280,19 @@ link :type="isRowConnected(scope.row) ? 'warning' : 'success'"
<el-form-item label="默认值">
<el-input v-model="ruleForm.defaultValue" disabled />
</el-form-item>
<el-form-item
v-if="(ruleForm.identifier || '').toString().toUpperCase() === 'ALARM'"
label="告警登记"
>
<el-select v-model="ruleForm.alarmLevel" placeholder="请选择告警登记">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ALARM_REGISTRATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<div class="flex flex-col w-full">
<div class="border border-gray-200 dark:border-gray-600 rounded-md py-12px">
@ -413,6 +433,16 @@ link :type="isRowConnected(scope.row) ? 'warning' : 'success'"
<el-form-item label="默认值">
<el-input v-model="createRuleForm.defaultValue" disabled />
</el-form-item>
<el-form-item label="告警登记">
<el-select v-model="createRuleForm.alarmLevel" placeholder="请选择告警登记">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ALARM_REGISTRATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
@ -428,6 +458,25 @@ link :type="isRowConnected(scope.row) ? 'warning' : 'success'"
</template>
<el-empty v-else description="请点击设备列表的“点位”查看采集点和点位规则" />
</ContentWrap>
<Dialog title="设备告警历史数据" v-model="deviceAlarmDialogVisible" width="800px">
<el-table
:data="deviceAlarmList"
v-loading="deviceAlarmLoading"
:stripe="true"
:show-overflow-tooltip="true"
:max-height="700"
>
<el-table-column label="规则名称" align="center" prop="ruleName" />
<el-table-column label="点位名称" align="center" prop="modelName" />
<el-table-column label="点位值" align="center" prop="addressValue" />
<el-table-column label="告警等级" align="center" prop="alarmLevel">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_ALARM_REGISTRATION" :value="scope.row.alarmLevel" />
</template>
</el-table-column>
</el-table>
</Dialog>
</template>
<script setup lang="ts">
@ -626,6 +675,7 @@ const ruleQueryParams = reactive({
fieldName: undefined as string | undefined,
fieldRule: undefined as string | undefined,
defaultValue: undefined as string | undefined,
alarmLevel: undefined as string | undefined,
deviceId: undefined as number | undefined,
})
const ruleQueryFormRef = ref()
@ -700,6 +750,7 @@ const ruleForm = reactive<Partial<DevicePointRuleVO>>({
ruleAttributeId: undefined,
ruleOperator: undefined,
ruleValue: undefined,
alarmLevel: '',
})
const extraPointRules = ref<
@ -763,6 +814,7 @@ const openRuleForm = async (row: DevicePointRuleVO & { pointRulesVOList?: any[]
ruleForm.fieldName = row.fieldName
ruleForm.defaultValue = row.defaultValue
ruleForm.deviceId = row.deviceId
ruleForm.alarmLevel = (row as any).alarmLevel
extraPointRules.value = []
@ -853,6 +905,7 @@ const handleRuleSubmit = async () => {
fieldRule: ruleForm.fieldRule,
defaultValue: ruleForm.defaultValue,
deviceId: ruleForm.deviceId ?? attributeDeviceId.value,
alarmLevel: ruleForm.alarmLevel,
pointRulesVOList,
}
@ -908,6 +961,7 @@ const createRuleForm = reactive({
identifier: 'ALARM',
fieldName: '',
defaultValue: '报警',
alarmLevel: '',
})
const openCreateRuleForm = () => {
@ -918,6 +972,7 @@ const openCreateRuleForm = () => {
createRuleForm.identifier = 'ALARM'
createRuleForm.fieldName = '报警'
createRuleForm.defaultValue = '报警'
createRuleForm.alarmLevel = ''
createRuleDialogVisible.value = true
}
@ -933,6 +988,7 @@ const handleCreateRuleSubmit = async () => {
identifier: createRuleForm.identifier,
fieldName: createRuleForm.fieldName,
defaultValue: createRuleForm.defaultValue,
alarmLevel: createRuleForm.alarmLevel,
deviceId: attributeDeviceId.value,
}
await request.post({ url: '/iot/device-point-rules/create', data: payload })
@ -1017,4 +1073,27 @@ onBeforeUnmount(() => {
stopDeviceTimer()
})
const deviceAlarmDialogVisible = ref(false)
const deviceAlarmLoading = ref(false)
const deviceAlarmList = ref<any[]>([])
const handleShowDeviceAlarmHistory = async () => {
if (!attributeDeviceId.value) {
message.error('请选择一个物联设备')
return
}
deviceAlarmDialogVisible.value = true
deviceAlarmLoading.value = true
try {
const res = await request.get({
url: '/iot/device-warinning-record/getList',
params: { deviceId: attributeDeviceId.value },
})
const data = (res as any)?.data ?? res
deviceAlarmList.value = Array.isArray(data) ? data : []
} finally {
deviceAlarmLoading.value = false
}
}
</script>

@ -20,6 +20,7 @@
filterable
placeholder="请选择点位类型"
class="!w-180px"
@change="handleAttributeTypeChange"
>
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
@ -92,17 +93,18 @@ const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
attributeCode: undefined,
attributeName: undefined,
attributeType: undefined,
dataType: undefined,
address: undefined,
dataUnit: undefined,
ratio: undefined,
remark: undefined,
deviceModelId: undefined,
})
id: undefined,
attributeCode: undefined,
attributeName: undefined,
attributeType: undefined,
typeName: undefined,
dataType: undefined,
address: undefined,
dataUnit: undefined,
ratio: undefined,
remark: undefined,
deviceModelId: undefined,
})
const formRules = reactive({
attributeCode: [{ required: true, message: '点位编码不能为空', trigger: 'blur' }],
attributeName: [{ required: true, message: '点位名称不能为空', trigger: 'blur' }],
@ -122,6 +124,21 @@ const open = async (type: string, id: number, modelId: number) => {
formLoading.value = true
try {
formData.value = await DeviceModelAttributeApi.getDeviceModelAttribute(id)
const currentType = (formData.value as any)?.attributeType
if (currentType !== undefined && currentType !== null && currentType !== '') {
const matched = typeList.value.find(
(item) =>
item.id === currentType ||
String(item.id) === String(currentType) ||
item.name === currentType ||
item.code === currentType
)
if (matched) {
;(formData.value as any).attributeType = matched.id
;(formData.value as any).typeName = matched.name
}
}
} finally {
formLoading.value = false
}
@ -153,6 +170,13 @@ const submitForm = async () => {
}
}
const handleAttributeTypeChange = (val: number | string) => {
const matched = typeList.value.find(
(item) => item.id === val || String(item.id) === String(val)
)
;(formData.value as any).typeName = matched?.name
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
@ -160,6 +184,7 @@ const resetForm = () => {
attributeCode: undefined,
attributeName: undefined,
attributeType: undefined,
typeName: undefined,
dataType: undefined,
address: undefined,
dataUnit: undefined,

@ -201,6 +201,19 @@ link type="primary" @click="openForm('update', scope.row.id)"
<el-form-item label="默认值">
<el-input v-model="ruleForm.defaultValue" disabled />
</el-form-item>
<el-form-item
v-if="(ruleForm.identifier || '').toString().toUpperCase() === 'ALARM'"
label="告警登记"
>
<el-select v-model="ruleForm.alarmLevel" placeholder="请选择告警登记">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ALARM_REGISTRATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<div class="flex flex-col w-full">
<div class="border border-gray-200 dark:border-gray-600 rounded-md py-12px">
@ -496,6 +509,7 @@ interface DeviceModelRuleVO {
ruleAttributeId?: number
ruleOperator?: string
ruleValue?: string | number
alarmLevel?: string
}
const ruleLoading = ref(false)
@ -508,6 +522,7 @@ const ruleQueryParams = reactive({
fieldName: undefined as string | undefined,
fieldRule: undefined as string | undefined,
defaultValue: undefined as string | undefined,
alarmLevel: undefined as string | undefined,
modelId: undefined as number | undefined,
})
const ruleQueryFormRef = ref()
@ -582,6 +597,7 @@ const ruleForm = reactive<Partial<DeviceModelRuleVO>>({
ruleAttributeId: undefined,
ruleOperator: undefined,
ruleValue: undefined,
alarmLevel: '',
})
const createRuleDialogVisible = ref(false)
@ -667,6 +683,7 @@ const openRuleForm = async (row: DeviceModelRuleVO & { pointRulesVOList?: any[]
ruleForm.fieldName = row.fieldName
ruleForm.defaultValue = row.defaultValue
ruleForm.modelId = row.modelId
ruleForm.alarmLevel = row.alarmLevel
extraPointRules.value = []
@ -756,6 +773,7 @@ const handleRuleSubmit = async () => {
fieldName: ruleForm.fieldName,
defaultValue: ruleForm.defaultValue,
modelId: ruleForm.modelId,
alarmLevel: ruleForm.alarmLevel,
pointRulesVOList,
}

@ -33,8 +33,8 @@
v-model="queryParams.requireDate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
:start-placeholder="t('common.startTime')"
:end-placeholder="t('common.endTime')"
:start-placeholder="t('common.startTimeText')"
:end-placeholder="t('common.endTimeText')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('EquipmentManagement.DvRepair.finishDate')" prop="finishDate">
@ -42,8 +42,8 @@
v-model="queryParams.finishDate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
:start-placeholder="t('common.startTime')"
:end-placeholder="t('common.endTime')"
:start-placeholder="t('common.startTimeText')"
:end-placeholder="t('common.endTimeText')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('EquipmentManagement.DvRepair.confirmDate')" prop="confirmDate">
@ -51,8 +51,8 @@
v-model="queryParams.confirmDate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
:start-placeholder="t('common.startTime')"
:end-placeholder="t('common.endTime')"
:start-placeholder="t('common.startTimeText')"
:end-placeholder="t('common.endTimeText')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('EquipmentManagement.DvRepair.acceptedBy')" prop="acceptedBy">

@ -118,8 +118,8 @@
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('action.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('action.reset') }}</el-button>
<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

@ -6,8 +6,20 @@
<span>能耗监测</span>
</div>
<div class="tabs">
<span class="tab" :class="{ active: energyTab === 'first' }" @click="energyTab = 'first'">First aid</span>
<span class="tab" :class="{ active: energyTab === 'after' }" @click="energyTab = 'after'">Aftermarket</span>
<el-select
v-model="selectedEnergyTypeId"
placeholder="请选择"
style="width: 120px"
size="small"
@change="getChartData"
>
<el-option
v-for="item in energyTypes"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</div>
</div>
<div class="panel-body">
@ -17,27 +29,76 @@
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
import * as echarts from 'echarts'
import { colors, style } from '../utils'
import { EnergyTypeApi, EnergyTypeVO } from '@/api/mes/energytype'
import { EnergyDeviceApi } from '@/api/mes/energydevice'
const energyTab = ref<'first' | 'after'>('first')
const energyTypes = ref<EnergyTypeVO[]>([])
const selectedEnergyTypeId = ref<number | undefined>(undefined)
const chartRef = ref<HTMLElement | null>(null)
let chart: echarts.ECharts | null = null
const render = () => {
const getEnergyTypes = async () => {
try {
const res = await EnergyTypeApi.getEnergyTypeList()
const list = (res as any).data || (Array.isArray(res) ? res : [])
energyTypes.value = list
if (list.length > 0) {
selectedEnergyTypeId.value = list[0].id
getChartData()
}
} catch (e) {
console.error('Failed to fetch energy types:', e)
}
}
const getChartData = async () => {
if (!selectedEnergyTypeId.value) return
try {
const res = await EnergyDeviceApi.getLastEnergyStatistics({
deviceTypeId: selectedEnergyTypeId.value,
orgId: 132
})
render(res)
} catch (e) {
console.error('Failed to fetch chart data:', e)
}
}
const render = (data: any = []) => {
if (!chart) return
const x = ['9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00']
const y =
energyTab.value === 'first'
? [820, 1650, 980, 1240, 1560, 1320, 1680]
: [680, 1420, 880, 1100, 1320, 1180, 1480]
// (time/createTime)(value/consumption)
//
// fallback
let x: string[] = []
let y: number[] = []
const list = (data as any).data || (Array.isArray(data) ? data : [])
if (list && list.length > 0) {
x = list.map((item: any) => {
const h = item.hour || ''
//
if (typeof h === 'string' && h.includes(' ')) {
return h.split(' ')[1]
}
return h
})
y = list.map((item: any) => item.value || item.consumption || item.energy || 0)
} else {
//
x = []
y = []
}
chart.setOption({
backgroundColor: 'transparent',
tooltip: { trigger: 'axis' },
grid: { top: '18%', left: '6%', right: '4%', bottom: '12%', containLabel: true },
xAxis: { type: 'category', data: x, axisLine: style.axisLine, axisLabel: { ...style.axisLabel, fontSize: 10 } },
xAxis: { type: 'category', data: x, axisLine: style.axisLine, axisLabel: { ...style.axisLabel, fontSize: 10, rotate: 40 } },
yAxis: { type: 'value', axisLine: { show: false }, axisLabel: { ...style.axisLabel, fontSize: 10 }, splitLine: style.splitLine },
series: [
{
@ -63,7 +124,10 @@ const resize = () => {
onMounted(() => {
if (!chartRef.value) return
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
render()
//
render([])
//
getEnergyTypes()
window.addEventListener('resize', resize)
})
@ -71,13 +135,20 @@ onUnmounted(() => {
window.removeEventListener('resize', resize)
chart?.dispose()
})
watch(energyTab, () => {
render()
})
</script>
<style scoped>
:deep(.el-select .el-input__wrapper) {
background-color: rgba(2, 6, 23, 0.3);
box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.4) inset;
}
:deep(.el-select .el-input__inner) {
color: #e5f0ff;
}
:deep(.el-select .el-input.is-focus .el-input__wrapper) {
box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.9) inset !important;
}
.card {
height: 100%;
background: linear-gradient(135deg, rgba(15,23,42,0.96), rgba(15,23,42,0.88));

@ -5,59 +5,60 @@
<span>任务列表</span>
</div>
<div class="panel-body table-body">
<table class="task-table">
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>类型</th>
<th>完成状态</th>
<th>结果</th>
</tr>
</thead>
<tbody ref="tbodyRef">
<tr v-for="row in taskRows" :key="row.id">
<td>{{ row.code }}</td>
<td>{{ row.name }}</td>
<td>{{ row.type }}</td>
<td>
<el-tag
v-if="row.finishStatusLabel"
class="status-tag"
size="small"
:disable-transitions="true"
:hit="false"
:round="true"
:type="row.finishStatusType || 'info'"
>
{{ row.finishStatusLabel }}
</el-tag>
</td>
<td class="status-cell">
<el-tag
v-if="row.resultStatusLabel !== '-'"
class="status-tag"
size="small"
:disable-transitions="true"
:hit="false"
:round="true"
:type="row.resultStatusType || 'info'"
>
{{ row.resultStatusLabel }}
</el-tag>
<span v-else>-</span>
</td>
</tr>
</tbody>
</table>
<el-table
ref="tableRef"
:data="taskRows"
class="task-table"
:show-header="true"
:stripe="false"
:border="false"
:highlight-current-row="false"
>
<el-table-column prop="code" label="编号" width="100" show-overflow-tooltip />
<el-table-column prop="name" label="名称" />
<el-table-column prop="type" label="类型" />
<el-table-column label="完成状态">
<template #default="scope">
<el-tag
v-if="scope.row.finishStatusLabel"
class="status-tag"
size="small"
:disable-transitions="true"
:hit="false"
:round="true"
:type="scope.row.finishStatusType || 'info'"
>
{{ scope.row.finishStatusLabel }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="结果">
<template #default="scope">
<div class="status-cell">
<el-tag
v-if="scope.row.resultStatusLabel !== '-'"
class="status-tag"
size="small"
:disable-transitions="true"
:hit="false"
:round="true"
:type="scope.row.resultStatusType || 'info'"
>
{{ scope.row.resultStatusLabel }}
</el-tag>
<span v-else>-</span>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import type { ElTable } from 'element-plus'
import { DashboardApi, DashboardTaskItem } from '@/api/dashboard'
import { colors } from '../utils'
interface TaskRow {
id: string
@ -69,9 +70,8 @@ interface TaskRow {
finishStatusType: '' | 'success' | 'warning' | 'danger' | 'info'
resultStatusType: '' | 'success' | 'warning' | 'danger' | 'info'
}
const taskRows = ref<TaskRow[]>([])
const tbodyRef = ref<HTMLElement | null>(null)
const tableRef = ref<InstanceType<typeof ElTable> | null>(null)
let scrollTimer: number | undefined
const mapItemToRow = (item: DashboardTaskItem, index: number): TaskRow => {
@ -124,22 +124,29 @@ const loadTaskList = async () => {
}
const startAutoScroll = () => {
if (!tbodyRef.value) return
const rows = tbodyRef.value.querySelectorAll('tr')
const el = tableRef.value?.$el as HTMLElement | undefined
if (!el) return
const tbody = el.querySelector('tbody') as HTMLElement | null
if (!tbody) return
const rows = tbody.querySelectorAll('tr')
if (!rows || rows.length <= 8) return
scrollTimer = window.setInterval(() => {
if (!tbodyRef.value) return
const first = tbodyRef.value.firstElementChild as HTMLElement | null
const currentBody = (tableRef.value?.$el as HTMLElement | undefined)?.querySelector('tbody') as
HTMLElement | null
if (!currentBody) return
const first = currentBody.firstElementChild as HTMLElement | null
if (!first) return
first.style.transition = 'all 0.35s'
first.style.transform = 'translateY(-36px)'
first.style.opacity = '0'
window.setTimeout(() => {
if (!tbodyRef.value) return
const bodyAgain = (tableRef.value?.$el as HTMLElement | undefined)?.querySelector('tbody') as
HTMLElement | null
if (!bodyAgain || !first.parentElement) return
first.style.transition = 'none'
first.style.transform = 'translateY(0)'
first.style.opacity = '1'
tbodyRef.value.appendChild(first)
bodyAgain.appendChild(first)
}, 360)
}, 5000)
}
@ -231,34 +238,64 @@ onUnmounted(() => {
.task-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 12px;
/* Element Plus Table Variables Override */
--el-table-bg-color: transparent;
--el-table-tr-bg-color: transparent;
--el-table-header-bg-color: transparent;
--el-table-text-color: #e5f0ff;
--el-table-header-text-color: #22d3ee;
--el-table-row-hover-bg-color: rgba(56, 189, 248, 0.12);
--el-table-border-color: rgba(30, 64, 175, 0.3);
background-color: transparent !important;
}
/* Ensure inner wrapper is transparent */
.task-table :deep(.el-table__inner-wrapper) {
background-color: transparent !important;
}
.task-table thead {
background: radial-gradient(circle at 0 0, rgba(56,189,248,0.18), transparent 70%);
/* Remove bottom border line */
.task-table :deep(.el-table__inner-wrapper::before) {
display: none;
}
.task-table th {
padding: 10px 8px;
color: var(--accent);
/* Header Styles */
.task-table :deep(thead th.el-table__cell) {
background: radial-gradient(circle at 0 0, rgba(56,189,248,0.18), transparent 90%) !important;
font-weight: 700;
text-align: left;
border-bottom: 1px solid rgba(51,65,85,0.9);
border-bottom: 1px solid rgba(51,65,85,0.9) !important;
padding: 8px 0;
}
.task-table td {
padding: 10px 8px;
border-bottom: 1px solid rgba(30,64,175,0.3);
color: #e5f0ff;
/* Cell Styles */
.task-table :deep(td.el-table__cell) {
background-color: transparent !important;
border-bottom: 1px solid rgba(30,64,175,0.3) !important;
padding: 8px 0;
white-space: nowrap;
}
/* Ensure code, name, type columns show ellipsis */
.task-table :deep(td.el-table__cell:nth-child(1) .cell),
.task-table :deep(td.el-table__cell:nth-child(2) .cell),
.task-table :deep(td.el-table__cell:nth-child(3) .cell) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.task-table tbody tr:hover td {
background: rgba(56,189,248,0.08);
/* Ensure finishStatus and result columns DO NOT show ellipsis */
.task-table :deep(td.el-table__cell:nth-child(4) .cell),
.task-table :deep(td.el-table__cell:nth-child(5) .cell) {
white-space: nowrap;
overflow: visible;
text-overflow: clip;
}
/* Hover Styles */
.task-table :deep(tbody tr:hover > td.el-table__cell) {
background-color: rgba(56,189,248,0.1) !important;
}
.status-cell {

@ -29,25 +29,64 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { DeviceWarningRecordApi, DeviceWarningRecordVO } from '@/api/iot/deviceWarningRecord'
const alarms = ref([
{ time: '08:12:03', level: '严重', type: 'danger', msg: '产线B-包装工位异常停机' },
{ time: '08:15:27', level: '严重', type: 'danger', msg: '产线C-温控传感器漂移' },
{ time: '08:20:10', level: '警告', type: 'warn', msg: '产线A-物料待料' },
{ time: '08:35:42', level: '提示', type: 'safe', msg: '产线E-设备保养提醒' },
{ time: '08:40:18', level: '提示', type: 'safe', msg: '产线F-成品抽检合格' },
{ time: '09:02:56', level: '严重', type: 'danger', msg: '产线D-站点扫码失败' },
{ time: '09:15:12', level: '警告', type: 'warn', msg: '产线H-人员到岗不足' },
{ time: '09:26:33', level: '警告', type: 'warn', msg: '产线I-实时能耗升高' },
{ time: '09:37:05', level: '严重', type: 'danger', msg: '产线G-夹具寿命告警' },
{ time: '09:45:21', level: '警告', type: 'warn', msg: '产线J-物流滞后' }
])
const alarms = ref<any[]>([])
const isAnimating = ref(false)
const listRef = ref<HTMLElement | null>(null)
let timer: ReturnType<typeof setInterval> | null = null
onMounted(() => {
const getAlarms = async () => {
try {
const data = await DeviceWarningRecordApi.getList() || []
console.log('data',data)
alarms.value = data.map((item: DeviceWarningRecordVO) => {
//
let timeStr = ''
if (item.createTime) {
const d = new Date(item.createTime)
timeStr = d.toTimeString().slice(0, 8)
}
//
let type = 'safe'
let levelText = item.alarmLevel
if (item.alarmLevel === '2') {
type = 'danger'
levelText = '严重'
} else if (item.alarmLevel === '1') {
type = 'warn'
levelText = '警告'
} else if (item.alarmLevel === '0') {
type = 'safe'
levelText = '提示'
} else {
//
if (item.alarmLevel === '严重') type = 'danger'
else if (item.alarmLevel === '警告') type = 'warn'
else if (item.alarmLevel === '提示') type = 'safe'
}
return {
time: timeStr,
level: levelText,
type: type,
msg: `${item.deviceName}-${item.ruleName}`
}
})
startAnimation()
} catch (error) {
console.error('Failed to fetch alarms:', error)
}
}
const startAnimation = () => {
if (timer) clearInterval(timer)
if (alarms.value.length === 0) return
timer = setInterval(() => {
isAnimating.value = true
setTimeout(() => {
@ -58,6 +97,10 @@ onMounted(() => {
isAnimating.value = false
}, 360)
}, 3000)
}
onMounted(() => {
getAlarms()
})
onUnmounted(() => {

Loading…
Cancel
Save