Merge branch 'dev'
This commit is contained in:
91
frontend/src/api/device/device/formData.ts
Normal file
91
frontend/src/api/device/device/formData.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import type { Device } from '@/api/device/interface/device'
|
||||||
|
import type { Monitor } from '@/api/device/interface/monitor'
|
||||||
|
|
||||||
|
const DEVICE_FIELD_NAMES = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'pattern',
|
||||||
|
'devType',
|
||||||
|
'manufacturer',
|
||||||
|
'createDate',
|
||||||
|
'createId',
|
||||||
|
'hardwareVersion',
|
||||||
|
'softwareVersion',
|
||||||
|
'protocol',
|
||||||
|
'ip',
|
||||||
|
'port',
|
||||||
|
'encryptionFlag',
|
||||||
|
'series',
|
||||||
|
'devKey',
|
||||||
|
'sampleId',
|
||||||
|
'arrivedDate',
|
||||||
|
'cityName',
|
||||||
|
'gdName',
|
||||||
|
'subName',
|
||||||
|
'reportPath',
|
||||||
|
'icdId',
|
||||||
|
'planId',
|
||||||
|
'factorFlag',
|
||||||
|
'preinvestmentPlan',
|
||||||
|
'delegate',
|
||||||
|
'inspectChannel',
|
||||||
|
'inspectDate',
|
||||||
|
'harmSysId',
|
||||||
|
'importFlag'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const MONITOR_FIELD_NAMES = [
|
||||||
|
'id',
|
||||||
|
'devId',
|
||||||
|
'busbar',
|
||||||
|
'name',
|
||||||
|
'num',
|
||||||
|
'pt',
|
||||||
|
'ct',
|
||||||
|
'connection',
|
||||||
|
'statInterval',
|
||||||
|
'harmSysId',
|
||||||
|
'checkFlag'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function appendIfPresent(formData: FormData, key: string, value: unknown) {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
formData.append(key, String(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendMonitorFields(formData: FormData, monitorList: Monitor.ResPqMon[]) {
|
||||||
|
monitorList.forEach((monitor, index) => {
|
||||||
|
MONITOR_FIELD_NAMES.forEach(fieldName => {
|
||||||
|
appendIfPresent(formData, `monitorList[${index}].${fieldName}`, monitor[fieldName])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPqDevFormData(device: Device.ResPqDev, imageFile?: File | null) {
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
DEVICE_FIELD_NAMES.forEach(fieldName => {
|
||||||
|
appendIfPresent(formData, fieldName, device[fieldName])
|
||||||
|
})
|
||||||
|
|
||||||
|
appendMonitorFields(formData, device.monitorList ?? [])
|
||||||
|
|
||||||
|
if (imageFile) {
|
||||||
|
formData.append('image', imageFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDeviceImagePreviewSrc(
|
||||||
|
hasImage?: boolean,
|
||||||
|
imageBase64?: string | null,
|
||||||
|
imageContentType?: string | null
|
||||||
|
) {
|
||||||
|
if (!hasImage || !imageBase64 || !imageContentType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return `data:${imageContentType};base64,${imageBase64}`
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type {Device} from '@/api/device/interface/device'
|
import type {Device} from '@/api/device/interface/device'
|
||||||
import http from '@/api'
|
import http from '@/api'
|
||||||
|
import { buildPqDevFormData } from './formData'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name 被检设备管理模块
|
* @name 被检设备管理模块
|
||||||
@@ -11,13 +12,13 @@ export const getPqDevList = (params: Device.ReqPqDevParams) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//添加被检设备
|
//添加被检设备
|
||||||
export const addPqDev = (params: Device.ResPqDev) => {
|
export const addPqDev = (params: Device.ResPqDev, imageFile?: File | null) => {
|
||||||
return http.post(`/pqDev/add`, params)
|
return http.upload(`/pqDev/add`, buildPqDevFormData(params, imageFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
//编辑被检设备
|
//编辑被检设备
|
||||||
export const updatePqDev = (params: Device.ResPqDev) => {
|
export const updatePqDev = (params: Device.ResPqDev, imageFile?: File | null) => {
|
||||||
return http.post(`/pqDev/update`, params)
|
return http.upload(`/pqDev/update`, buildPqDevFormData(params, imageFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
//删除被检设备
|
//删除被检设备
|
||||||
@@ -71,3 +72,7 @@ export const getPqDev = () => {
|
|||||||
export const getSelectOptions = (params:{ pattern: string }) => {
|
export const getSelectOptions = (params:{ pattern: string }) => {
|
||||||
return http.get(`/pqDev/getSelectOptions`, params)
|
return http.get(`/pqDev/getSelectOptions`, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const downloadDeviceImage = (id: string) => {
|
||||||
|
return http.downloadGetWithHeaders(`/pqDev/image/download?id=${id}`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ export namespace Device {
|
|||||||
monitorList: Monitor.ResPqMon[]
|
monitorList: Monitor.ResPqMon[]
|
||||||
checked?: boolean // 是否已选择
|
checked?: boolean // 是否已选择
|
||||||
disabled?: boolean // 是否禁用
|
disabled?: boolean // 是否禁用
|
||||||
|
hasImage?: boolean // 是否存在设备图片
|
||||||
|
imageBase64?: string | null // 设备图片 Base64,用于详情回显
|
||||||
|
imageContentType?: string | null // 设备图片 MIME 类型
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectOption {
|
export interface SelectOption {
|
||||||
|
|||||||
@@ -95,6 +95,33 @@
|
|||||||
|
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="设备图片" class="device-image-form-item">
|
||||||
|
<div class="device-image-field">
|
||||||
|
<input
|
||||||
|
ref="imageInputRef"
|
||||||
|
type="file"
|
||||||
|
accept=".jpg,.jpeg,.png,image/jpeg,image/png"
|
||||||
|
style="display: none"
|
||||||
|
@change="handleImageChange"
|
||||||
|
/>
|
||||||
|
<div v-if="imagePreviewUrl" class="device-image-preview">
|
||||||
|
<img :src="imagePreviewUrl" alt="device image preview" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="device-image-empty">暂无图片</div>
|
||||||
|
<div class="device-image-actions">
|
||||||
|
<el-button size="small" @click="triggerImageSelect">
|
||||||
|
{{ hasPersistedImage || selectedImageFile ? '更换图片' : '上传图片' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="formContent.id && hasPersistedImage"
|
||||||
|
size="small"
|
||||||
|
@click="handleImageDownload"
|
||||||
|
>
|
||||||
|
下载图片
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
<el-divider>参数信息</el-divider>
|
<el-divider>参数信息</el-divider>
|
||||||
<el-form-item label="通道数" prop="devChns" v-if="DevIsShow">
|
<el-form-item label="通道数" prop="devChns" v-if="DevIsShow">
|
||||||
<el-input v-model="formContent.devChns" :disabled="true" />
|
<el-input v-model="formContent.devChns" :disabled="true" />
|
||||||
@@ -259,7 +286,8 @@
|
|||||||
import { dialogBig } from '@/utils/elementBind'
|
import { dialogBig } from '@/utils/elementBind'
|
||||||
import { type Device } from '@/api/device/interface/device'
|
import { type Device } from '@/api/device/interface/device'
|
||||||
import { ElMessage, type FormItemRule } from 'element-plus'
|
import { ElMessage, type FormItemRule } from 'element-plus'
|
||||||
import { addPqDev, getSelectOptions, updatePqDev } from '@/api/device/device'
|
import { addPqDev, downloadDeviceImage, getSelectOptions, updatePqDev } from '@/api/device/device'
|
||||||
|
import { buildDeviceImagePreviewSrc } from '@/api/device/device/formData'
|
||||||
import { computed, reactive, ref } from 'vue'
|
import { computed, reactive, ref } from 'vue'
|
||||||
import { useDictStore } from '@/stores/modules/dict'
|
import { useDictStore } from '@/stores/modules/dict'
|
||||||
// 使用 dayjs 库格式化
|
// 使用 dayjs 库格式化
|
||||||
@@ -286,6 +314,11 @@ const activeTab = ref('0') // '0' 对应第一个 tab
|
|||||||
const monitorTableHeight = ref(375) // 默认高度
|
const monitorTableHeight = ref(375) // 默认高度
|
||||||
|
|
||||||
const selectOptions = ref<Record<string, Device.SelectOption[]>>({})
|
const selectOptions = ref<Record<string, Device.SelectOption[]>>({})
|
||||||
|
const imageInputRef = ref<HTMLInputElement>()
|
||||||
|
const selectedImageFile = ref<File | null>(null)
|
||||||
|
const imagePreviewUrl = ref<string | null>(null)
|
||||||
|
const imagePreviewObjectUrl = ref<string | null>(null)
|
||||||
|
const hasPersistedImage = ref(false)
|
||||||
|
|
||||||
const pqChannelArray = ref([
|
const pqChannelArray = ref([
|
||||||
{
|
{
|
||||||
@@ -305,6 +338,85 @@ const onTabChange = (tabName: string | number) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const revokeImageObjectUrl = () => {
|
||||||
|
if (imagePreviewObjectUrl.value) {
|
||||||
|
URL.revokeObjectURL(imagePreviewObjectUrl.value)
|
||||||
|
imagePreviewObjectUrl.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetImageState = () => {
|
||||||
|
revokeImageObjectUrl()
|
||||||
|
selectedImageFile.value = null
|
||||||
|
imagePreviewUrl.value = null
|
||||||
|
hasPersistedImage.value = false
|
||||||
|
if (imageInputRef.value) {
|
||||||
|
imageInputRef.value.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateImageFile = (file: File) => {
|
||||||
|
const allowedTypes = ['image/jpeg', 'image/png']
|
||||||
|
const isAllowedType = allowedTypes.includes(file.type) || /\.(jpe?g|png)$/i.test(file.name)
|
||||||
|
if (!isAllowedType) {
|
||||||
|
ElMessage.warning({ message: '仅支持 JPG、JPEG、PNG 图片' })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
|
ElMessage.warning({ message: '图片大小不能超过 10MB' })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerImageSelect = () => {
|
||||||
|
imageInputRef.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImageChange = (event: Event) => {
|
||||||
|
const input = event.target as HTMLInputElement
|
||||||
|
const file = input.files?.[0]
|
||||||
|
if (!file) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!validateImageFile(file)) {
|
||||||
|
input.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
revokeImageObjectUrl()
|
||||||
|
selectedImageFile.value = file
|
||||||
|
imagePreviewObjectUrl.value = URL.createObjectURL(file)
|
||||||
|
imagePreviewUrl.value = imagePreviewObjectUrl.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateImageEchoState = (device: Partial<Device.ResPqDev>) => {
|
||||||
|
revokeImageObjectUrl()
|
||||||
|
selectedImageFile.value = null
|
||||||
|
hasPersistedImage.value = Boolean(device.hasImage)
|
||||||
|
imagePreviewUrl.value = buildDeviceImagePreviewSrc(device.hasImage, device.imageBase64, device.imageContentType)
|
||||||
|
if (imageInputRef.value) {
|
||||||
|
imageInputRef.value.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDownloadFileName = (contentDisposition?: string | null) => {
|
||||||
|
const match = contentDisposition?.match(/filename\*=UTF-8''([^;]+)|filename="?([^";]+)"?/i)
|
||||||
|
return decodeURIComponent(match?.[1] ?? match?.[2] ?? 'device-image')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImageDownload = async () => {
|
||||||
|
if (!formContent.id || !hasPersistedImage.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await downloadDeviceImage(formContent.id)
|
||||||
|
const blobUrl = URL.createObjectURL(response.data)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = blobUrl
|
||||||
|
link.download = getDownloadFileName(response.headers['content-disposition'])
|
||||||
|
link.click()
|
||||||
|
URL.revokeObjectURL(blobUrl)
|
||||||
|
}
|
||||||
|
|
||||||
function useMetaInfo() {
|
function useMetaInfo() {
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const titleType = ref('add')
|
const titleType = ref('add')
|
||||||
@@ -473,6 +585,7 @@ const close = () => {
|
|||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
// 清空dialogForm中的值
|
// 清空dialogForm中的值
|
||||||
resetFormContent()
|
resetFormContent()
|
||||||
|
resetImageState()
|
||||||
// 重置表单
|
// 重置表单
|
||||||
dialogFormRef.value?.resetFields()
|
dialogFormRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
@@ -535,7 +648,7 @@ const save = () => {
|
|||||||
formContent.monitorList = monitor.value
|
formContent.monitorList = monitor.value
|
||||||
|
|
||||||
if (titleType.value != 'add') {
|
if (titleType.value != 'add') {
|
||||||
await updatePqDev(formContent)
|
await updatePqDev(formContent, selectedImageFile.value)
|
||||||
ElMessage.success({ message: `${dialogTitle.value}成功!` })
|
ElMessage.success({ message: `${dialogTitle.value}成功!` })
|
||||||
} else {
|
} else {
|
||||||
// 新增需要把设备模式转成字典ID
|
// 新增需要把设备模式转成字典ID
|
||||||
@@ -550,7 +663,7 @@ const save = () => {
|
|||||||
if (protocolItem) {
|
if (protocolItem) {
|
||||||
formContent.protocol = protocolItem.id
|
formContent.protocol = protocolItem.id
|
||||||
}
|
}
|
||||||
await addPqDev(formContent)
|
await addPqDev(formContent, selectedImageFile.value)
|
||||||
ElMessage.success({ message: `${dialogTitle.value}成功!` })
|
ElMessage.success({ message: `${dialogTitle.value}成功!` })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,9 +730,11 @@ const open = async (
|
|||||||
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean)
|
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean)
|
||||||
}
|
}
|
||||||
monitor.value = data.monitorList
|
monitor.value = data.monitorList
|
||||||
|
updateImageEchoState(data)
|
||||||
} else {
|
} else {
|
||||||
monitor.value = []
|
monitor.value = []
|
||||||
resetFormContent()
|
resetFormContent()
|
||||||
|
resetImageState()
|
||||||
//只有比对式设备ID由前端传
|
//只有比对式设备ID由前端传
|
||||||
if (currentMode === '比对式') formContent.id = generateUUID().replaceAll('-', '')
|
if (currentMode === '比对式') formContent.id = generateUUID().replaceAll('-', '')
|
||||||
|
|
||||||
@@ -695,3 +810,53 @@ const props = defineProps<{
|
|||||||
refreshTable: (() => Promise<void>) | undefined
|
refreshTable: (() => Promise<void>) | undefined
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.form-three {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-image-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-image-preview {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-image-preview img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-image-empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border: 1px dashed #dcdfe6;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #909399;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-image-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-image-actions :deep(.el-button + .el-button) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user