<!-- Description: h5-新装设备 Author: 李亚光 Date: 2024-12-27 --> <script lang="ts" setup name="H5DeviceAdd"> import dayjs from 'dayjs' import { Toast, showImagePreview } from 'vant' import { getProductListPage } from '@/api/home/device/product' import { getDeviceTypeListPage } from '@/api/home/device/type' import { showToast, showLoadingToast, closeToast, showConfirmDialog } from 'vant' import { getWellListPage } from '@/api/home/well/well' import { getStationListPage } from '@/api/home/station/station' import AMap from '@/components/map/index.vue' import { keepSearchParams } from '@/utils/keepQuery' import { uploadApi } from '@/api/mobile/operation' const $router = useRouter() const deciceInfo = ref({ productId: '', productName: '', devcode: '', tagNumber: '', watchObject: '', installDate: '', }) // 产品相关 const showProduct = ref(false) const productValue = ref([]) const productColumns = ref([]) const onConfirm = ({ selectedValues, selectedOptions }) => { deciceInfo.value.productId = selectedOptions[0]?.value deciceInfo.value.productName = selectedOptions[0]?.text productValue.value = selectedValues showProduct.value = false } // 安装位置 -- 闸井/场站 const searchForWell = ref(false) const searchForInput = ref('') const resultForWell = ref(false) // 搜索安装位置 const searchTagNumber = (action: string) => { if (!action) { return true } showLoadingToast({ duration: 0, message: '加载中...', forbidClick: true, loadingType: 'spinner', }); // 搜索场站或闸井 (deciceInfo.value.watchObject === '1' ? getWellListPage : getStationListPage)({ offset: 1, limit: 1, tagNumber: searchForInput.value }).then(res => { closeToast() searchForWell.value = false let message = '' if (!res.data.rows.length) { message = `未找到位号为 ${searchForInput.value} ${deciceInfo.value.watchObject === '1' ? '闸井' : '场站'} ,请确认位号后重新搜索` } else { // 判断位置 // 存在同类设备 // 正常 } // 展示查询结果 showConfirmDialog({ title: '搜索位置', message: '123123123232121' }) .then(() => { }) .catch(() => { }) }) } // 选择安装位置 const selectTagNumber = () => { if (!deciceInfo.value.productId) { showToast('请先选择产品') return } //1闸井 2场站 3管线 if (deciceInfo.value.watchObject == '1' || deciceInfo.value.watchObject == '2') { searchForWell.value = true searchForInput.value = '' } // else { // } } // 安装日期 const showDatePicker = ref(false) const pickerValueInstallDate = ref<string[]>([]) // 默认日期 pickerValueInstallDate.value = dayjs().format('YYYY-MM-DD').split('-') deciceInfo.value.installDate = pickerValueInstallDate.value.join('-') const onConfirmInstallDate = ({ selectedValues }) => { deciceInfo.value.installDate = selectedValues.join('-') pickerValueInstallDate.value = selectedValues showDatePicker.value = false } // 现场照片 const photo = ref<string[]>([]) // 拍照 const takePictures = () => { if (deciceInfo.value.photo.length >= 3) { showToast('最多上传三张照片') return } // 调用摄像头 const camera = document.getElementById('camera-photo') camera?.click() } const fileRef = ref() // 文件上传input,获取input的引用 const onFileChange = (event: any) => { // 原生上传 if (event.target.files?.length !== 0) { const file = event.target.files[0] const fd = new FormData() fd.append('file', file) uploadApi(fd).then(res => { photo.value.push(res.data as string) fileRef.value.value = '' }).catch(() => { fileRef.value.value = '' }) // const reader = new FileReader() // 实例化FileReader // reader.readAsDataURL(file) // 读取成功以后执行的方法 // reader.onload = (e) => { // deciceInfo.value.photo.push(e.target.result) // let img = new Image() // // base64 // img.src = e.target.result // console.log(img.src, 'img.src') // img.onload = () => { // imagetoCanvas(img) //Image 对象转变为一个 Canvas 类型对象,i为遍历的下标 // } // } } } // 图片地址 const getPhotoUrl = computed(() => { return (url: string) => { return `${window.localStorage.getItem('url-bj-well')}/static/${url}` } }) // 删除图片 const deletePhoto = (index: number) => { photo.value.splice(index, 1) } // 预览图片 const previewPhoto = (index: number) => { showImagePreview({ images: photo.value, startPosition: index }) } // 获取字典 const deviceTypeList = ref<any[]>([]) // 设备类型列表 const fetchDict = () => { getProductListPage({ offset: 1, limit: 9999 }).then(res => { productColumns.value = res.data.rows.map((item: any) => ({ text: `${item.productName}-${item.productCode}`, value: item.id, deviceType: item.deviceType, })) }) getDeviceTypeListPage({ limit: 9999, offset: 1 }).then((res) => { deviceTypeList.value = res.data.rows }) } fetchDict() // 监听产品变化,判断该设备的 监测对象 watch(() => deciceInfo.value.productId, (newVal) => { if (newVal) { const select = productColumns.value.filter((item: any) => item.value === newVal) as any const watchObject = deviceTypeList.value.filter((item: any) => item.id === select[0].deviceType) deciceInfo.value.watchObject = watchObject[0].watchObject } else { deciceInfo.value.tagNumber = '' deciceInfo.value.watchObject = '' } }) // 计算地图区域高度 const scrollHeight = ref(0) const calcHeight = () => { // 公共头部高度40 // 边距安全 30 // 底部按钮 const exportBtnHeight = document.getElementById('handler-btn')?.offsetHeight || 0 // 头部 const searchHeaderHeight = document.getElementById('info-area')?.offsetHeight || 0 scrollHeight.value = window.innerHeight - 40 - exportBtnHeight - searchHeaderHeight - 30 } onMounted(() => { calcHeight() }) window.addEventListener('resize', () => { calcHeight() }) onBeforeUnmount(() => { window.addEventListener('resize', () => { }) }) // 地图操作 const mapRef = ref() // 当前状态 收起retract/展开expand const mapStatus = ref('expand') const changeStatus = () => { if (mapStatus.value === 'expand') { mapStatus.value = 'retract' } else { mapStatus.value = 'expand' } } // 重置地图到初始状态 const resetMap = () => { // mapRef.value.map.setCenter(position.value) // mapRef.value.map.setZoom(17.5) } // 查看数据 const deviceData = () => { if (!deciceInfo.value.devcode) { showToast('设备编号不能为空') return } $router.push({ name: 'OperationData', query: { devcode: deciceInfo.value.devcode } }) } // 页面缓存 onBeforeRouteLeave((to: any) => { keepSearchParams(to.path, 'H5DeviceAdd') }) </script> <template> <div class="device-container"> <!-- 设备信息 --> <div id="info-area" class="device-info"> <span class="title">设备信息</span> <van-form> <van-cell-group> <!-- 产品 --> <van-field v-model="deciceInfo.productName" is-link readonly label="产品" name="productId" placeholder="选择产品" required @click="showProduct = true" input-align="right"> </van-field> <van-popup v-model:show="showProduct" destroy-on-close position="bottom"> <van-picker :columns="productColumns" title="产品" :model-value="productValue" @confirm="onConfirm" @cancel="showProduct = false" /> </van-popup> <!-- 设备编号 --> <van-field v-model="deciceInfo.devcode" label="设备编号" name="devcode" placeholder="输入设备编号" required input-align="right" /> <!-- 安装位置 --> <van-field v-model="deciceInfo.tagNumber" is-link readonly label="安装位置" name="tagNumber" placeholder="去搜索" required @click="selectTagNumber" input-align="right" /> <!-- 安装位置 闸井/场站查询--> <van-dialog v-model:show="searchForWell" title="搜索位置" confirm-button-text="搜索" :closeOnClickOverlay="true" :before-close="searchTagNumber"> <van-field v-model="searchForInput" placeholder="请输入安装位号" /> </van-dialog> <!-- 安装日期 --> <van-field v-model="deciceInfo.installDate" is-link readonly name="installDate" label="安装日期" placeholder="点击选择时间" @click="showDatePicker = true" required input-align="right" /> <van-popup v-model:show="showDatePicker" destroy-on-close position="bottom"> <van-date-picker :model-value="pickerValueInstallDate" @confirm="onConfirmInstallDate" @cancel="showDatePicker = false" /> </van-popup> <!-- 现场照片 --> <van-field readonly name="photo" label="现场照片" required input-align="center"> <template #input> <!-- 展示照片 --> <div v-for="(item, index) in photo" :key="index" class="show-photo"> <div class="del_d"> <div class="del" @click="deletePhoto(index)"></div> </div> <img :src="getPhotoUrl(item)" alt="" srcset="" width="30px" height="30px" @click="previewPhoto(index)"> </div> </template> <template #right-icon> <div class="camera" @click="takePictures" /> </template> </van-field> <!-- 照相组件 --> <input ref="fileRef" style="display: none;opacity: 0;" multiple id="camera-photo" type="file" accept="image/*" @change="onFileChange"> </van-cell-group> </van-form> </div> <!-- 地图组件 --> <div class="map" :class="mapStatus === 'retract' ? 'retract1' : ''" :style="{ width: '96%', margin: '0 auto', height: scrollHeight + 'px', marginTop: '5px', borderRadius: '10px', overflow: 'hidden' }"> <a-map ref="mapRef" :class="mapStatus === 'retract' ? 'retract2' : ''" style="width: 100%;height: 100%;" :zoom="15" /> <!-- 重置按钮 --> <div v-if="mapStatus === 'expand'" class="reset-icon" @click="resetMap"> <div class="icon"></div> </div> <!-- 收起/展开按钮 --> <div class="expand-icon" @click="changeStatus"> <div v-if="mapStatus === 'expand'" class="icon1"></div> <div v-if="mapStatus === 'retract'" class="icon2"></div> </div> <!-- 遮罩 --> <div v-if="mapStatus === 'retract'" class="mask"></div> </div> <!-- 按钮 --> <div id="handler-btn" class="btn-container"> <el-button type="primary" style="width: 48%;" @click="save">保存</el-button> <el-button type="primary" plain style="width: 48%;" @click="deviceData">查看设备数据</el-button> </div> </div> </template> <style lang="scss" scoped> .show-photo { position: relative; margin-left: 10px; } .del_d { position: absolute; width: 16px; height: 16px; background: red; border-radius: 50%; top: -10px; right: -10px; display: flex; align-items: center; justify-content: center; cursor: pointer; .del { width: 10px; height: 3px; background: white; } } $primary: #0D76D4; $--van-primary-color: #0D76D4; ::v-deep(.van-picker-column__item--selected) { color: $primary !important; } .device-container { width: 100%; height: calc(100vh - 40px); overflow: hidden; .device-info { background-color: #fff; width: 96%; margin: 0 auto; margin-top: 1vh; border-radius: 6px; padding: 4px; .title { font-weight: 700; font-size: 1.2rem; padding-left: 0.5rem; margin-top: 0.5rem; } ::v-deep(.van-cell__title) { font-weight: 700; font-size: 1rem; } ::v-deep(.van-cell__value) { // font-weight: 700; font-size: 1rem; } } } .camera { width: 24px; height: 24px; background: url('@/assets/icons/icon-camera.svg') no-repeat center center / cover; } .btn-container { position: absolute; width: 96%; bottom: 1vh; display: flex; justify-content: space-around; // margin: 0 auto; left: 50%; transform: translateX(-50%); ::v-deep(.el-button) { font-size: 18px; } } .map { position: relative; .mask { width: 100px !important; height: 100px !important; position: absolute; z-index: 1; top: 0; left: 0; } .reset-icon { width: 40px; height: 40px; position: absolute; z-index: 2; bottom: 10px; right: 16px; border-radius: 6px; display: flex; justify-content: center; flex-direction: column; align-items: center; box-shadow: 0px 0px 12px rgba(0, 0, 0, .12); background-color: #fff; .icon { width: 30px; height: 30px; background: url('@/assets/icons/icon-location_current.svg') no-repeat center center / cover; } } .expand-icon { width: 40px; height: 40px; position: absolute; top: 10px; right: 16px; border-radius: 6px; display: flex; justify-content: center; flex-direction: column; align-items: center; box-shadow: 0px 0px 12px rgba(0, 0, 0, .12); background-color: #fff; z-index: 2; .icon1 { width: 30px; height: 30px; background: url('@/assets/mobile/retract.svg') no-repeat center center / cover; } .icon2 { width: 30px; height: 30px; background: url('@/assets/mobile/expand.svg') no-repeat center center / cover; } } } .retract1 { width: 100px !important; height: 100px !important; overflow: hidden; position: absolute; right: 0; bottom: 50px; } .retract2 { width: 100px !important; height: 100px !important; overflow: hidden; opacity: 0.5; } </style>