DeviceStep1.vue 12.1 KB
<template>
  <div class="step1">
    <div class="step1-form">
      <BasicForm @register="register">
        <template #iconSelect>
          <Upload
            name="avatar"
            list-type="picture-card"
            class="avatar-uploader"
            :show-upload-list="false"
            :customRequest="customUpload"
            :before-upload="beforeUpload"
          >
            <img v-if="devicePic" :src="devicePic" alt="avatar" />
            <div v-else>
              <PlusOutlined />
              <div class="ant-upload-text">图片上传</div>
            </div>
          </Upload>
        </template>
        <template #deviceAddress>
          <Input disabled v-model:value="positionState.address">
            <template #addonAfter>
              <EnvironmentTwoTone @click="selectPosition" />
            </template>
          </Input>
        </template>
      </BasicForm>
      <div class="flex justify-center" v-if="isUpdate">
        <a-button type="primary" @click="nextStep">下一步</a-button>
      </div>
    </div>
    <Modal
      v-model:visible="visible"
      title="设备位置"
      @ok="handleOk"
      width="800px"
      @cancel="handleCancel"
      centered
    >
      <div>
        <Form :label-col="labelCol" :colon="false">
          <Row :gutter="20" class="mt-4">
            <Col :span="20">
              <FormItem label="搜索位置">
                <AutoComplete
                  v-model:value="positionState.address"
                  :options="dataSource"
                  style="width: 100%"
                  placeholder="搜索位置"
                  @search="debounceSearch"
                  @select="handleSelect"
                  backfill
                />
              </FormItem>
            </Col>
          </Row>
          <Row :gutter="20" class="">
            <Col :span="10">
              <FormItem label="经度">
                <Input v-model:value="positionState.longitude" disabled />
              </FormItem>
            </Col>
            <Col :span="10">
              <FormItem label="纬度">
                <Input v-model:value="positionState.latitude" disabled />
              </FormItem>
            </Col>
          </Row>
        </Form>
        <div ref="wrapRef" style="height: 300px; width: 90%" class="ml-6"></div>
      </div>
    </Modal>
  </div>
</template>
<script lang="ts">
  import { defineComponent, ref, nextTick, unref, reactive } from 'vue';
  import { BasicForm, useForm } from '/@/components/Form';
  import { step1Schemas } from '../../config/data';
  import { useScript } from '/@/hooks/web/useScript';
  import { Input, Upload, message, Modal, Form, Row, Col, AutoComplete } from 'ant-design-vue';
  import { EnvironmentTwoTone, PlusOutlined } from '@ant-design/icons-vue';
  import { upload } from '/@/api/oss/ossFileUploader';
  import { FileItem } from '/@/components/Upload/src/typing';
  import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';

  import icon from '/@/assets/images/wz.png';
  import { useDebounceFn } from '@vueuse/core';
  export default defineComponent({
    components: {
      BasicForm,
      Input,
      AutoComplete,
      Upload,
      EnvironmentTwoTone,
      PlusOutlined,
      Modal,
      Form,
      FormItem: Form.Item,
      Row,
      Col,
    },
    props: {
      isUpdate: {
        type: Boolean,
      },
    },
    emits: ['next'],
    setup(props, { emit }) {
      const devicePic = ref('');

      const [register, { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema }] =
        useForm({
          labelWidth: 100,
          schemas: step1Schemas,
          actionColOptions: {
            span: 14,
          },
          labelAlign: 'right',
          showResetButton: false,
          showSubmitButton: false,
        });
      async function nextStep() {
        try {
          let values = await validate();
          values = { devicePic: devicePic.value, ...positionState, ...values };
          delete values.icon;
          delete values.deviceAddress;
          emit('next', values);
          // 获取输入的数据
        } catch (e) {}
      }

      // 图片上传
      async function customUpload({ file }) {
        if (beforeUpload(file)) {
          const formData = new FormData();
          formData.append('file', file);
          const response = await upload(formData);
          if (response.fileStaticUri) {
            devicePic.value = response.fileStaticUri;
          }
        }
      }
      const beforeUpload = (file: FileItem) => {
        const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
        if (!isJpgOrPng) {
          message.error('只能上传图片文件!');
        }
        const isLt2M = (file.size as number) / 1024 / 1024 < 2;
        if (!isLt2M) {
          message.error('图片大小不能超过2MB!');
        }
        return isJpgOrPng && isLt2M;
      };

      // 地图的弹框
      const visible = ref(false);
      const selectPosition = () => {
        visible.value = true;
        if (!positionState.longitude) {
          positionState.longitude = '104.04666605565338';
          positionState.latitude = '30.543516387560476';
          initMap(positionState.longitude, positionState.latitude);
        } else {
          initMap(positionState.longitude, positionState.latitude);
        }
      };

      let positionState = reactive<{
        longitude: string;
        latitude: string;
        description?: string;
        address: string;
        map: null;
        marker: null;
      }>({
        longitude: '',
        latitude: '',
        address: '',
        map: null,
        marker: null,
      });
      /**
       * 逆地址解析函数(根据坐标点获取详细地址)
       * @param {Object} point   百度地图坐标点,必传
       */
      function getAddrByPoint(point) {
        let geco = new BMap.Geocoder();
        geco.getLocation(point, function (res) {
          positionState.marker.setPosition(point); //重新设置标注的地理坐标
          positionState.map.panTo(point); //将地图的中心点更改为给定的点
          positionState.address = res.address; //记录该点的详细地址信息
          positionState.longitude = point.lng; //记录当前坐标点
          positionState.latitude = point.lat;
        });
      }

      // 地图
      const wrapRef = ref<HTMLDivElement | null>(null);
      const { toPromise } = useScript({ src: BAI_DU_MAP_URL });

      // 初始化地图
      async function initMap(longitude, latitude) {
        await toPromise();
        await nextTick();
        const wrapEl = unref(wrapRef);
        const BMap = (window as any).BMap;
        if (!wrapEl) return;
        let preMarker = null;
        positionState;
        positionState.map = new BMap.Map(wrapEl, { enableMapClick: false });
        let myIcon = new BMap.Icon(icon, new BMap.Size(20, 30));
        const point = new BMap.Point(Number(longitude), Number(latitude));
        positionState.marker = new BMap.Marker(point, { icon: myIcon, enableDragging: true });
        if (positionState.marker) {
          positionState.map.removeOverlay(preMarker);
        }
        positionState.map.addOverlay(positionState.marker);
        preMarker = positionState.marker;
        positionState.map.centerAndZoom(point, 15);
        positionState.map.enableScrollWheelZoom(true);
        let navigationControl = new BMap.NavigationControl({
          //创建一个特定样式的地图平移缩放控件
          anchor: BMAP_ANCHOR_TOP_RIGHT, //靠右上角位置
          type: BMAP_NAVIGATION_CONTROL_LARGE, //SMALL控件类型
        });
        positionState.map.addControl(navigationControl); //将控件添加到地图

        positionState.marker.addEventListener('dragend', function (e) {
          getAddrByPoint(e.point); //拖拽结束后调用逆地址解析函数,e.point为拖拽后的地理坐标
        });
        positionState.map.addEventListener('click', (e) => {
          getAddrByPoint(e.point);
        });
      }
      const dataSource = ref<any[]>([]);
      const onSearch = (searchText: string) => {
        if (!searchText) {
          dataSource.value = [];
          return;
        }
        let local = new BMap.LocalSearch(positionState.map, {
          onSearchComplete(res) {
            //检索完成后的回调函数
            if (local.getStatus() == BMAP_STATUS_SUCCESS) {
              const searchArr = [];
              for (let i = 0; i < res.getCurrentNumPois(); i++) {
                const item = res.getPoi(i);
                searchArr.push({
                  label: item.address + item.title,
                  value: item.address + item.title,
                  point: item.point,
                });
              }
              dataSource.value = searchArr;
            }
          },
        }); //调用search方法,根据检索词searchText发起检索
        local.search(searchText);
      };
      // 防抖函数包装一下,防止频繁执行
      const debounceSearch = useDebounceFn(onSearch, 500);
      function handleSelect(value, option) {
        dataSource.value = [];

        positionState.address = option.value; //记录详细地址,含建筑物名
        const { lat, lng } = option.point;
        positionState.latitude = lat; //记录当前选中地址坐标
        positionState.longitude = lng; //记录当前选中地址坐标
        positionState.map.clearOverlays(); //清除地图上所有覆盖物
        let myIcon = new BMap.Icon(icon, new BMap.Size(20, 30));
        positionState.marker = new BMap.Marker(option.point, {
          icon: myIcon,
          enableDragging: true,
        }); //根据所选坐标重新创建Marker
        positionState.marker.addEventListener('dragend', function (e) {
          getAddrByPoint(e.point); //拖拽结束后调用逆地址解析函数,e.point为拖拽后的地理坐标
        });
        positionState.map.addOverlay(positionState.marker); //将覆盖物重新添加到地图中
        positionState.map.panTo(option.point); //将地图的中心点更改为选定坐标点
      }

      // 确定选择的位置
      const handleOk = () => {
        visible.value = false;
      };
      // 取消选择位置
      const handleCancel = () => {
        dataSource.value = [];
        for (let key in positionState) {
          positionState[key] = '';
        }
      };
      // 父组件调用更新字段值的方法
      function parentSetFieldsValue(data) {
        const { deviceInfo } = data;
        positionState.longitude = deviceInfo.longitude;
        positionState.latitude = deviceInfo.latitude;
        positionState.address = deviceInfo.address;
        devicePic.value = deviceInfo.avatar;
        setFieldsValue(data);
      }
      // 父组件调用获取字段值的方法
      function parentGetFieldsValue() {
        return getFieldsValue();
      }

      // 父组件调用表单验证
      async function parentValidate() {
        const valid = await validate();
        return valid;
      }

      // 父组件重置图片
      function parentResetDevicePic(deviceInfo) {
        devicePic.value = deviceInfo.avatar;
      }
      // 父组件重置位置
      function parentResetPositionState() {
        for (let key in positionState) {
          positionState[key] = '';
        }
      }
      // 禁用设备类型
      function disabledDeviceType(disabled: boolean) {
        updateSchema({
          field: 'deviceType',
          componentProps: {
            disabled,
          },
        });
      }
      return {
        resetFields,
        positionState,
        register,
        beforeUpload,
        customUpload,
        selectPosition,
        devicePic,
        visible,
        handleOk,
        handleCancel,
        wrapRef,
        labelCol: { style: { width: '100px' } },
        parentSetFieldsValue,
        parentGetFieldsValue,
        parentValidate,
        parentResetDevicePic,
        parentResetPositionState,
        disabledDeviceType,
        nextStep,
        onSearch,
        handleSelect,
        dataSource,
        debounceSearch,
      };
    },
  });
</script>
<style lang="less" scoped>
  .step1-form {
    width: 450px;
    margin: 0 auto;
  }
</style>